afterpresets 0.2.0 → 0.3.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 (114) hide show
  1. package/README.md +6 -4
  2. package/native-runtime/CMakeLists.txt +82 -0
  3. package/native-runtime/README.md +40 -0
  4. package/native-runtime/THIRD_PARTY_NOTICES.md +14 -0
  5. package/native-runtime/include/ap_native.h +223 -0
  6. package/native-runtime/src/apply.cpp +199 -0
  7. package/native-runtime/src/crypto.cpp +225 -0
  8. package/native-runtime/src/crypto.hpp +51 -0
  9. package/native-runtime/src/dialog.cpp +62 -0
  10. package/native-runtime/src/dialog.hpp +35 -0
  11. package/native-runtime/src/engine.cpp +909 -0
  12. package/native-runtime/src/engine.hpp +232 -0
  13. package/native-runtime/src/fsutil.cpp +291 -0
  14. package/native-runtime/src/fsutil.hpp +57 -0
  15. package/native-runtime/src/json.cpp +326 -0
  16. package/native-runtime/src/json.hpp +70 -0
  17. package/native-runtime/src/platform.hpp +83 -0
  18. package/native-runtime/src/platform_common.cpp +60 -0
  19. package/native-runtime/src/platform_mac.mm +314 -0
  20. package/native-runtime/src/platform_win.cpp +277 -0
  21. package/native-runtime/src/runtime.cpp +473 -0
  22. package/native-runtime/src/util.cpp +169 -0
  23. package/native-runtime/src/util.hpp +32 -0
  24. package/native-runtime/src/zip.cpp +243 -0
  25. package/native-runtime/src/zip.hpp +58 -0
  26. package/native-runtime/third_party/monocypher/LICENCE.md +167 -0
  27. package/native-runtime/third_party/monocypher/monocypher-ed25519.c +500 -0
  28. package/native-runtime/third_party/monocypher/monocypher-ed25519.h +140 -0
  29. package/native-runtime/third_party/monocypher/monocypher.c +2956 -0
  30. package/native-runtime/third_party/monocypher/monocypher.h +321 -0
  31. package/native-runtime/third_party/tinf/LICENSE +22 -0
  32. package/native-runtime/third_party/tinf/crc32.c +57 -0
  33. package/native-runtime/third_party/tinf/tinf.h +140 -0
  34. package/native-runtime/third_party/tinf/tinflate.c +636 -0
  35. package/native-runtime/vendor-files.json +39 -0
  36. package/package.json +6 -3
  37. package/runtime/docs/cep/host.md +8 -0
  38. package/runtime/docs/common/README.md +1 -1
  39. package/runtime/docs/native/README.md +44 -0
  40. package/runtime/docs/native/debugging.md +140 -0
  41. package/runtime/docs/native/rules.md +402 -0
  42. package/runtime/docs/native/testing.md +154 -0
  43. package/runtime/package.json +1 -1
  44. package/runtime/scripts/ae-sdk-docs/config.js +102 -0
  45. package/runtime/scripts/ae-sdk-docs/convert.js +430 -0
  46. package/runtime/scripts/ae-sdk-docs/index.js +314 -0
  47. package/runtime/scripts/ae-sdk-docs/source.js +205 -0
  48. package/runtime/scripts/ae-sdk-docs/split.js +170 -0
  49. package/runtime/scripts/ae-sdk-docs.js +72 -0
  50. package/runtime/src/host.js +1 -1
  51. package/src/api/error-messages.js +1 -1
  52. package/src/check/check.js +3 -3
  53. package/src/check/native.js +14 -0
  54. package/src/check/pricing.js +1 -1
  55. package/src/cli.js +2 -2
  56. package/src/commands/build.js +8 -2
  57. package/src/commands/check.js +10 -2
  58. package/src/commands/dev.js +9 -2
  59. package/src/commands/docs.js +5 -1
  60. package/src/commands/init.js +10 -7
  61. package/src/commands/push.js +5 -2
  62. package/src/commands/sdk.js +14 -0
  63. package/src/commands/testers.js +1 -1
  64. package/src/commands/upgrade.js +2 -2
  65. package/src/commands/whoami.js +1 -1
  66. package/src/dev/bridge.js +8 -0
  67. package/src/dev/native.js +3 -0
  68. package/src/dev/session.js +4 -3
  69. package/src/mcp/extendscript.js +9 -8
  70. package/src/mcp/host-target.js +1 -0
  71. package/src/mcp/panels.js +1 -1
  72. package/src/mcp/png.js +1 -0
  73. package/src/mcp/server.js +2 -2
  74. package/src/mcp/tools/capture.js +3 -0
  75. package/src/mcp/tools/host.js +1 -1
  76. package/src/mcp/tools/native.js +6 -0
  77. package/src/mcp/tools/project.js +48 -32
  78. package/src/mcp/tools/releases.js +23 -20
  79. package/src/native/build.js +27 -0
  80. package/src/native/cmake/CMakeLists.txt +88 -0
  81. package/src/native/cmake/zig-windows.cmake +19 -0
  82. package/src/native/effect.js +3 -0
  83. package/src/native/install.js +8 -0
  84. package/src/native/installers.js +4 -0
  85. package/src/native/pipl.js +20 -0
  86. package/src/native/restart.js +2 -0
  87. package/src/native/sdk.js +8 -0
  88. package/src/project/docs.js +3 -3
  89. package/src/project/project.js +2 -2
  90. package/src/project/template.js +5 -4
  91. package/src/prompt.js +1 -1
  92. package/src/release/native.js +3 -0
  93. package/src/release/publish.js +1 -1
  94. package/src/system/run-command.js +1 -1
  95. package/src/targets.js +1 -1
  96. package/template/cep/AGENTS.md +99 -164
  97. package/template/cep/README.md +7 -2
  98. package/template/cep/docs/code_guide.md +67 -0
  99. package/template/cep/docs/initial_setup.md +58 -12
  100. package/template/cep/docs/release_checklist.md +24 -0
  101. package/template/hosts/AGENTS.two-apps.md +12 -0
  102. package/template/native/.mcp.json +8 -0
  103. package/template/native/AGENTS.md +105 -0
  104. package/template/native/README.md +71 -0
  105. package/template/native/docs/code_guide.md +72 -0
  106. package/template/native/docs/initial_setup.md +179 -0
  107. package/template/native/docs/release_checklist.md +24 -0
  108. package/template/native/effect.json +16 -0
  109. package/template/native/gitignore +8 -0
  110. package/template/native/package.json +14 -0
  111. package/template/native/src/Effect.cpp +224 -0
  112. package/template/native/src/License.cpp +95 -0
  113. package/template/native/src/License.h +29 -0
  114. package/template/native/src/Render.h +68 -0
@@ -0,0 +1,71 @@
1
+ # __EXTENSION_NAME__
2
+
3
+ An After Effects effect plugin built with [AfterPresets](https://afterpresets.com).
4
+ Bundle ID: `__BUNDLE_ID__` (permanent).
5
+
6
+ You do not need to write code. Open this folder in an AI agent (the Code tab
7
+ of the Claude desktop app, Claude Code, Codex, Cursor...) and describe the
8
+ effect you want. The agent follows `AGENTS.md`, builds the plugin, puts it
9
+ into After Effects, and tests it there.
10
+
11
+ ## Start
12
+
13
+ 1. Tell your agent: "Read AGENTS.md and finish the setup." It checks the
14
+ build tools and asks you for the few things it cannot do itself:
15
+ - downloading Adobe's After Effects SDK once (it needs your free Adobe ID),
16
+ - approving a one-time macOS password prompt for the plugin folder,
17
+ - restarting After Effects when you have unsaved work.
18
+
19
+ 2. In After Effects, apply **Effect → AfterPresets → __EXTENSION_NAME__** to a
20
+ layer. The starter tints the layer toward a colour (Tint, Screen or
21
+ Multiply), optionally fading across the layer.
22
+
23
+ 3. Tell your agent what to build. Things you can type:
24
+
25
+ - "Make this a glow: radius, intensity and colour. Show me frames."
26
+ - "Add a Softness slider and test it at 8 and 16 bits."
27
+ - "Turn licensing off; this effect is free."
28
+ - "Check everything and push a test release."
29
+
30
+ Prefer a terminal? `npx ap dev` builds, installs and rebuilds on every save.
31
+
32
+ ## Publish
33
+
34
+ Ask your agent ("push a test release", "publish it to everyone") or type:
35
+
36
+ | Command | What it does |
37
+ | --- | --- |
38
+ | `npx ap build` | Release ZIP in `dist/` with buyer install scripts: both versions on a Mac (macOS `.plugin` + Windows `.aex`), the Windows `.aex` only on a Windows PC |
39
+ | `npx ap push --channel test` | Release to your testers only |
40
+ | `npx ap push --channel production` | Release to everyone |
41
+ | `npx ap releases rollback 1.1.0` | Go back to an earlier version |
42
+
43
+ Buyers run `mac-install.command` or `windows-install.bat` from the ZIP once;
44
+ updates then install themselves at the next After Effects start. They sign
45
+ in from the effect's **Options** (or its **License...** button) in the
46
+ Effect Controls panel.
47
+
48
+ ## Project map
49
+
50
+ How a new project starts. Your agent keeps an overview of the effect it
51
+ builds in `AGENTS.md` ("Project overview").
52
+
53
+ | Path | What it is |
54
+ | --- | --- |
55
+ | `afterpresets.json` | Name, bundle ID, version (`ap push` bumps it), `"platform": "native"` |
56
+ | `effect.json` | The effect: menu name, `matchName` (permanent), category, parameters (permanent `id`s), license and update options, flags. The PiPL resource and the generated headers come from it |
57
+ | `src/Effect.cpp` | After Effects entry points: setup, parameters, Smart Render, Options |
58
+ | `src/Render.h` | The pixel work (8/16/32 bits per channel) |
59
+ | `src/License.cpp`, `src/License.h` | AfterPresets licensing, the Options dialog and the update notice (`ap_native`) |
60
+ | `vendor/ap_native/` | AfterPresets native runtime; copied by `ap init` / `ap upgrade`, do not edit |
61
+ | `native.cmake` | Optional: extra libraries or compiler settings (CMake), included last |
62
+ | `build/` | Build output and the generated `ap_effect_config.h` / `ap_effect_params.h` (not committed) |
63
+ | `dist/` | Release ZIPs from `ap build` (not committed) |
64
+ | `docs/initial_setup.md` | One-time computer and agent setup, ending with "what should it do?" |
65
+ | `docs/code_guide.md` | How the project is organised: `effect.json`, licensing, updates, installing |
66
+ | `docs/release_checklist.md` | What to check before the first production release |
67
+ | `docs/afterpresets/` | Agent reference: rules, testing, debugging, Adobe's SDK guide (generated) |
68
+ | `AGENTS.md` | Instructions for coding agents, and the effect overview they keep current |
69
+
70
+ Adobe's SDK is not part of this project: it lives once per computer in
71
+ `~/.afterpresets/sdk/`. Never commit it.
@@ -0,0 +1,72 @@
1
+ # Code guide
2
+
3
+ How this project is organised. Read it before changing `effect.json`,
4
+ licensing or updates. The SDK rules are in `docs/afterpresets/rules.md`.
5
+
6
+ ## Layout
7
+
8
+ | Path | What goes there |
9
+ | --- | --- |
10
+ | `afterpresets.json` | Name, bundle ID, version (bumped by `ap push`), `"platform": "native"`, `"hosts": ["AEFT"]` |
11
+ | `effect.json` | The effect: name, `matchName` (permanent), category, controls (permanent `id`s), license and update options, flags |
12
+ | `src/Effect.cpp` | After Effects entry points: setup, parameters, Smart Render, Options |
13
+ | `src/Render.h` | The pixel work (8/16/32 bits per channel) |
14
+ | `src/License.cpp`, `src/License.h` | AfterPresets licensing, the Options dialog and the update notice |
15
+ | `vendor/ap_native/` | AfterPresets native runtime; copied by `ap init` / `ap upgrade`. Do not edit |
16
+ | `native.cmake` | Optional: extra libraries or compiler settings (CMake), included last |
17
+ | `build/` | Build output and the generated `ap_effect_config.h` / `ap_effect_params.h`. Do not edit |
18
+ | `test-fixtures/` | Sample footage or images for testing (never shipped) |
19
+
20
+ ## effect.json
21
+
22
+ 1. **Flags, names and versions come only from `effect.json`**, through the
23
+ generated `ap_effect_config.h`. Never type `PF_OutFlag_*` values by hand
24
+ in GlobalSetup, and never edit the generated headers.
25
+ 2. **Controls are declared in `effect.json`.** The generated
26
+ `ap_effect_params.h` adds them (`ap_add_params()`) and names their indices:
27
+ `params[AP_PARAM_AMOUNT]` for key `"amount"`; popup values are
28
+ `AP_<KEY>_<CHOICE>_<n>`.
29
+ 3. **Control `id`s (1-9000) are permanent.** Append new controls with new
30
+ ids; never reuse or renumber one. Saved projects find controls by id.
31
+ Id 9001 is the AfterPresets status button (`params[AP_PARAM_LICENSE]`,
32
+ always last): keep it.
33
+ 4. **The match name is permanent.** `ap init` made it unique for this
34
+ product, and After Effects saves it in every project that uses the
35
+ effect: changing it after the first release breaks every customer's
36
+ projects. The menu `name` can change any time.
37
+ 5. **Keep what makes it a good plugin** when you replace the starter: 8, 16
38
+ and 32 bits per channel, Smart Render, multi-frame rendering safety, and
39
+ the status button and Options dialog.
40
+ 6. **Keep the overview current.** When you add, move or remove controls or
41
+ files, update the Overview in `AGENTS.md`.
42
+
43
+ ## Licensing and updates
44
+
45
+ Both are set in `effect.json`.
46
+
47
+ - `"license": true` turns licensing on. Buyers sign in from the effect's
48
+ status button (caption "License...", or "About..." when licensing is off;
49
+ its name is the status line, e.g. "License: active") or from the Options
50
+ dialog. The effect renders unlocked only when `ap_is_unlocked()` says so
51
+ (safe on any thread). `{ "offlineGraceDays": 7 }` allows offline use
52
+ (0-30 days). Removing it turns licensing off: free products only. **Paid
53
+ products must license**: `ap check` and `ap push` fail otherwise.
54
+ - Dev builds render **without** the watermark unless enforcement is on; see
55
+ "Testing the locked (signed-out) state" in `docs/afterpresets/testing.md`.
56
+ Release builds always enforce.
57
+ - `"updates": "background"` (default) downloads and verifies a new version in
58
+ the background; it is used from the next After Effects start (the status
59
+ button says "Update: restart AE"). `"notify"` only tells the buyer,
60
+ `"manual"` checks when they ask in the Options dialog, `"off"` disables
61
+ updates. Explain the tradeoffs to the user before switching away from
62
+ `background`.
63
+
64
+ ## How buyers install it
65
+
66
+ `ap build` puts `mac-install.command` and `windows-install.bat` in the
67
+ release ZIP. Buyers run the one for their computer: the first install shows
68
+ the system's administrator prompt once (macOS password window, Windows UAC)
69
+ and copies the plugin into Adobe's shared plugin folder. macOS may first need
70
+ **System Settings → Privacy & Security → Open Anyway** for the install
71
+ script. Later updates install themselves and need only an After Effects
72
+ restart. The user guide is at https://docs.afterpresets.com.
@@ -0,0 +1,179 @@
1
+ # Initial setup
2
+
3
+ Do this once per computer. The user is usually not a programmer and may not
4
+ have a terminal open (for example, they use the Code tab of the Claude
5
+ desktop app). **Run every command yourself**; ask the user only for the
6
+ steps marked **User**, one at a time, in plain words, and wait for "Done".
7
+ Never ask them to type a command, and never use `sudo` yourself. If a
8
+ command fails because your sandbox blocks the internet or writing to
9
+ `~/.afterpresets`, ask the user to allow it to run outside the sandbox; do
10
+ not work around it.
11
+
12
+ Follow the steps for this computer's system (macOS or Windows). A Mac builds
13
+ the macOS version and cross-compiles the Windows version; a Windows PC builds
14
+ the Windows version only.
15
+
16
+ ## 1. Tools
17
+
18
+ - [ ] `node --version` prints 20 or newer. If Node is missing: on Windows run
19
+ `winget install -e --id OpenJS.NodeJS.LTS`; on a Mac, **User** installs
20
+ the "LTS" version from https://nodejs.org (a normal installer). Then check
21
+ again (a new chat may be needed for the agent to see it).
22
+ - [ ] `npm install` has run; `node_modules/afterpresets` exists.
23
+
24
+ **macOS:**
25
+
26
+ - [ ] Homebrew: `brew --version` works (also try `/opt/homebrew/bin/brew`
27
+ and `/usr/local/bin/brew`). If not, **User**: "Open https://brew.sh, copy
28
+ the install line into the Terminal app, press Return, and type your Mac
29
+ password when asked (nothing shows while you type). Press Return again
30
+ when it asks. If Apple's Command Line Tools are missing it downloads them
31
+ too (about 1 GB from Apple, 5-15 minutes); wait until it says
32
+ 'Installation successful'." Then check again (Homebrew prints two lines to
33
+ add it to the PATH; follow them).
34
+ - [ ] Apple's Command Line Tools (Apple's compiler and signing tools; Xcode
35
+ itself is not needed): `xcode-select -p` prints a folder. Homebrew's
36
+ installer normally installed them. If not, run `xcode-select --install`
37
+ and tell the **User**: "A macOS window asks to install the Command Line
38
+ Tools: click **Install** and wait until it finishes (about 5-15 minutes,
39
+ it downloads about 1 GB from Apple)."
40
+ - [ ] Build tools: `brew install cmake ninja zig` (you run it). `cmake
41
+ --version`, `ninja --version` and `zig version` all work afterwards.
42
+
43
+ **Windows** (Visual Studio is not needed):
44
+
45
+ - [ ] Build tools, with winget (you run them; no administrator needed):
46
+ `winget install -e --id Kitware.CMake`, `winget install -e --id
47
+ Ninja-build.Ninja`, `winget install -e --id zig.zig`. `cmake --version`,
48
+ `ninja --version` and `zig version` all work afterwards (a new terminal,
49
+ or a new chat, may be needed to see them).
50
+
51
+ **Both:**
52
+
53
+ - [ ] The account: `whoami` (MCP) or `npx ap whoami`. If it says "not logged
54
+ in", call `login` (MCP) or run `npx ap login` in the background, then tell
55
+ the **User**: "Your browser opened an AfterPresets page; click **Authorize
56
+ CLI**." Wait with `login_status`, or until the command finishes.
57
+
58
+ ## 2. Adobe's After Effects SDK (one time per computer)
59
+
60
+ Adobe does not allow AfterPresets to ship its SDK, and downloading it needs
61
+ the user's Adobe ID, so this step is theirs.
62
+
63
+ - [ ] Run `npx ap sdk`. If it says the SDK is installed, go on.
64
+ - [ ] Otherwise run `npx ap sdk install`. It opens Adobe's download page. Tell
65
+ the **User**: "Sign in with your Adobe ID on the page that opened, and
66
+ download the **After Effects** SDK for **macOS** (or **Windows** on a PC; a
67
+ zip). Tell me when it
68
+ is in your Downloads folder." Never ask for their Adobe password.
69
+ - [ ] Run `npx ap sdk install` again: it finds the zip in Downloads, checks
70
+ it and installs it into `~/.afterpresets/sdk/` (Windows:
71
+ `%USERPROFILE%\.afterpresets\sdk\`). If the zip is elsewhere,
72
+ pass its path: `npx ap sdk install <path to zip>`.
73
+
74
+ ## 3. AfterPresets MCP
75
+
76
+ The `afterpresets` MCP server (`ap mcp`) gives you the CLI (login, check,
77
+ dev, build, push), the docs, and After Effects itself (`host_eval`,
78
+ `host_scratch_project`, `comp_capture`).
79
+
80
+ - **Claude (desktop app Code tab, or Claude Code):** `.mcp.json` declares it.
81
+ If its tools are missing because this project was just created in this
82
+ chat (or in another folder), tell the **User** exactly how to start a new
83
+ chat in this folder, with its full path, and to approve the AfterPresets
84
+ tools when Claude asks:
85
+ - Claude app: "In the **Code** tab, start a new session, choose the
86
+ folder `<full path>`, and approve the AfterPresets tools when asked."
87
+ - Claude Code in a terminal: "Type `/exit`, then run
88
+ `cd "<full path>" && claude`, and approve the AfterPresets tools."
89
+ - Cursor or VS Code: "**File → Open Folder…**, choose `<full path>`,
90
+ open a new chat, and enable the afterpresets MCP server if asked."
91
+ Give them the message to paste into the new chat: "Continue the
92
+ AfterPresets setup: follow docs/initial_setup.md step by step until you
93
+ have shown me the starter effect rendering in After Effects, then ask me
94
+ what to build." Stop there; the
95
+ new chat continues from this file.
96
+ - **Codex CLI** (run in this folder):
97
+
98
+ ```sh
99
+ codex mcp add afterpresets -- node node_modules/afterpresets/bin/ap.js mcp
100
+ ```
101
+
102
+ Then tell the **User**: "Quit Codex and start it again in this folder:
103
+ `cd "<full path>" && codex`."
104
+
105
+ - **Other agents:** add a stdio server that runs
106
+ `node node_modules/afterpresets/bin/ap.js mcp` in this folder.
107
+
108
+ - [ ] The `afterpresets` tools are visible to you.
109
+
110
+ ## 4. First build
111
+
112
+ - [ ] Call `dev_start` (or `npx ap dev` if the user runs it). It builds the
113
+ effect, installs it into After Effects' dev plugin folder
114
+ (macOS: `/Library/Application Support/Adobe/Common/Plug-ins/7.0/MediaCore/AfterPresets Dev/`;
115
+ Windows: `C:\Program Files\Adobe\Common\Plug-ins\7.0\MediaCore\AfterPresets Dev\`),
116
+ installs the AfterPresets dev bridge (how the tools reach After Effects),
117
+ and converts Adobe's SDK guide into `docs/afterpresets/sdk/`. Then
118
+ `dev_status` until the build is done.
119
+ - [ ] If it says the dev plugin folder needs an administrator, that is a
120
+ **User** step, needed once. **macOS:** it gives one Terminal line (`sudo mkdir -p …
121
+ && sudo chown …`). Tell the **User**: "Open the Terminal app, paste this
122
+ line, press Return and type your Mac password (nothing shows while you
123
+ type)." and give them the exact line it printed. (When `ap dev` runs in an
124
+ interactive terminal it may show the macOS administrator window instead:
125
+ then the **User** just enters their password there.) Never run `sudo`
126
+ yourself. **Windows:** it gives one PowerShell line (`New-Item … ;
127
+ icacls …`). Tell the **User**: "Right-click **Start**, choose **Terminal
128
+ (Admin)** or **Windows PowerShell (Admin)**, click **Yes**, paste this
129
+ line, press Enter, and tell me Done." and give them the exact line it
130
+ printed. Never elevate yourself. Then run `dev_start` / `dev_status`
131
+ again; later builds install without an administrator.
132
+ - [ ] If it reports compiler errors on the untouched starter, the SDK or a
133
+ build tool is wrong: re-check steps 1 and 2 and read the hint it prints.
134
+ - [ ] `docs/afterpresets/sdk/README.md` exists. If it says the guide could
135
+ not be downloaded, check the internet connection and run `npx ap docs`.
136
+
137
+ ## 5. Restart After Effects once
138
+
139
+ - [ ] **User:** "Save your work in After Effects, then quit and reopen it."
140
+ (Needed now because After Effects loads plugins and the dev bridge only
141
+ when it starts. Later, on a Mac, `ap dev` restarts it for you when that
142
+ is safe, or tells you to ask; on Windows always ask the user.)
143
+ - [ ] `host_bridge` (no action) says the bridge is loaded in After Effects.
144
+
145
+ ## 6. Prove the loop
146
+
147
+ - [ ] A harmless host call works: `host_eval` with `app.version`.
148
+ - [ ] The starter effect is loaded: in `host_eval`, look for the
149
+ `matchName` from `effect.json` in `app.effects` (see
150
+ `docs/afterpresets/debugging.md`).
151
+ - [ ] `host_scratch_project`, then apply the starter effect to a solid in a
152
+ test comp with `host_eval` and capture two frames with `comp_capture`
153
+ (`docs/afterpresets/testing.md`). Look at them: the effect is visible.
154
+ - [ ] Show the **User** the frames: "This is the starter effect, running in
155
+ After Effects. Next, tell me what yours should do."
156
+
157
+ ## 7. Ask what to build
158
+
159
+ Only now, with the starter effect rendering, ask what the effect should do.
160
+ Do not start building from a vague idea.
161
+
162
+ - [ ] Ask the **User** to describe the look in detail, in their own words.
163
+ Invite references: images or video frames of the look they want, another
164
+ effect they like, or a frame from their own project. They can drag images
165
+ into the chat.
166
+ - [ ] Ask short questions about anything unclear, a few at a time (for
167
+ example: which controls they want, what each one changes, how it should
168
+ animate, whether it works on the layer's edges or its whole image).
169
+ - [ ] Repeat back a short plan in plain words (the look, the controls and
170
+ their ranges) and wait for their OK.
171
+ - [ ] Write the Overview in `AGENTS.md` from the plan, following the
172
+ instructions there. Keep it current from now on.
173
+ - [ ] Turn the starter into the product: its name, category, controls and
174
+ render code (`docs/code_guide.md` lists what to keep). Build it in small
175
+ steps, rendering and showing frames after each one.
176
+
177
+ Repeat after moving the project, installing a new After Effects version, or
178
+ switching agents (skip step 7 when the Overview in `AGENTS.md` is already
179
+ written).
@@ -0,0 +1,24 @@
1
+ # Before the first production release
2
+
3
+ Do this once, before the first `push` to `production`. Tell the user what you
4
+ are doing; show them the frames and wait for their feedback.
5
+
6
+ - [ ] **Licensing matches the price.** A paid product keeps
7
+ `"license": true` in `effect.json` (`ap check` and `ap push` refuse
8
+ otherwise). A free one may remove it. See `docs/code_guide.md`.
9
+ - [ ] **The locked state works.** Turn enforcement on as described in
10
+ "Testing the locked (signed-out) state" in `docs/afterpresets/testing.md`,
11
+ restart After Effects, and render frames signed out: the effect must still
12
+ render (with the watermark), never crash or hang. Render frames signed in
13
+ too. Then turn enforcement off again.
14
+ - [ ] **Show the user** the locked and unlocked frames and ask for feedback.
15
+ - [ ] **The match name and control ids are final.** After this release they
16
+ can never change (see `docs/code_guide.md`). Confirm the effect's name and
17
+ controls with the user.
18
+ - [ ] **A test release works.** `push` to `test` and let the user try it.
19
+ Built on a Mac, the release also has the Windows version: tell the user to
20
+ test it on a Windows PC before selling it there.
21
+ - [ ] `check` passes with no errors.
22
+
23
+ Then `push` to `production`: show the user the summary it returns, and call
24
+ it again with its `confirmToken` only after they approve.
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "__EFFECT_NAME__",
3
+ "matchName": "__MATCH_NAME__",
4
+ "category": "AfterPresets",
5
+ "description": "Tints the layer toward a colour.",
6
+ "license": true,
7
+ "updates": "background",
8
+ "params": [
9
+ { "key": "amount", "id": 1, "type": "slider", "name": "Amount", "min": 0, "max": 100, "default": 50, "precision": 1, "display": "percent" },
10
+ { "key": "color", "id": 2, "type": "color", "name": "Color", "default": "#FF4000" },
11
+ { "key": "mode", "id": 3, "type": "popup", "name": "Mode", "choices": ["Tint", "Screen", "Multiply"], "default": "Tint" },
12
+ { "key": "gradient", "id": 4, "type": "checkbox", "name": "Gradient", "label": "Fade across layer", "default": false },
13
+ { "key": "center", "id": 5, "type": "point", "name": "Gradient Center", "default": [50, 50] },
14
+ { "key": "angle", "id": 6, "type": "angle", "name": "Gradient Angle", "default": 90 }
15
+ ]
16
+ }
@@ -0,0 +1,8 @@
1
+ # Build output (ap build / ap dev)
2
+ /build/
3
+ /dist/
4
+
5
+ # Never commit Adobe's SDK (it lives in ~/.afterpresets/sdk/)
6
+ AfterEffectsSDK*/
7
+
8
+ .DS_Store
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "__SLUG__",
3
+ "private": true,
4
+ "scripts": {
5
+ "dev": "ap dev",
6
+ "check": "ap check",
7
+ "build": "ap build",
8
+ "push": "ap push",
9
+ "docs": "ap docs"
10
+ },
11
+ "devDependencies": {
12
+ "afterpresets": "^__CLI_VERSION__"
13
+ }
14
+ }
@@ -0,0 +1,224 @@
1
+ // The starter effect: tints the layer toward a colour (Tint, Screen or
2
+ // Multiply), optionally fading across the layer along a gradient.
3
+ //
4
+ // Plain After Effects SDK code. Names, flags, version and parameters come from
5
+ // effect.json through the generated ap_effect_config.h / ap_effect_params.h:
6
+ // change effect.json, not the numbers here.
7
+ //
8
+ // 8/16/32 bpc DEEP_COLOR_AWARE + FLOAT_COLOR_AWARE, one template (Render.h)
9
+ // Smart Render PF_Cmd_SMART_PRE_RENDER / PF_Cmd_SMART_RENDER
10
+ // Multi-frame render no mutable globals; everything a render needs is on the stack
11
+ // AfterPresets status button + Options dialog (License.cpp, ap_native)
12
+ #include "AEConfig.h"
13
+ #include "entry.h"
14
+ #include "AE_Effect.h"
15
+ #include "AE_EffectVers.h"
16
+ #include "AE_EffectCB.h"
17
+ #include "AE_EffectCBSuites.h"
18
+ #include "AE_EffectSuites.h"
19
+ #include "AE_Macros.h"
20
+ #include "Param_Utils.h"
21
+
22
+ #include <cmath>
23
+ #include <cstdio>
24
+ #include <cstring>
25
+ #include <new>
26
+
27
+ #include "ap_effect_params.h" // generated from effect.json
28
+ #include "License.h"
29
+ #include "Render.h"
30
+
31
+ namespace {
32
+
33
+ // Decided once per frame in PreRender and handed to SmartRender.
34
+ struct PreRenderData {
35
+ A_long left, top; // where the output buffer sits in layer space (downsampled pixels)
36
+ A_long unlocked; // license state for this frame (also mixed into the cache key)
37
+ };
38
+
39
+ void delete_pre_render_data(void *data) { delete static_cast<PreRenderData *>(data); }
40
+
41
+ PF_Err about(PF_OutData *out_data) {
42
+ std::snprintf(out_data->return_msg, sizeof out_data->return_msg, "%s %s\r%s", AP_EFFECT_NAME, AP_VERSION,
43
+ AP_DESCRIPTION);
44
+ return PF_Err_NONE;
45
+ }
46
+
47
+ PF_Err global_setup(PF_OutData *out_data) {
48
+ out_data->my_version = AP_EFFECT_VERSION; // equals the PiPL's eVER (both from afterpresets.json)
49
+ out_data->out_flags = AP_OUT_FLAGS; // equals the PiPL's eGLO (both from effect.json)
50
+ out_data->out_flags2 = AP_OUT_FLAGS2; // equals the PiPL's eGL2
51
+ license::start();
52
+ return PF_Err_NONE;
53
+ }
54
+
55
+ PF_Err params_setup(PF_InData *in_data, PF_OutData *out_data) {
56
+ char status[32];
57
+ license::status_name(status, sizeof status);
58
+ return ap_add_params(in_data, out_data, status, license::button_label());
59
+ }
60
+
61
+ // The status button's name shows the license / update state. Names are the
62
+ // only text an effect can change in the Effect Controls without custom UI.
63
+ PF_Err update_params_ui(PF_InData *in_data, PF_ParamDef *params[]) {
64
+ PF_ParamDef def = *params[AP_PARAM_LICENSE];
65
+ char status[32];
66
+ license::status_name(status, sizeof status);
67
+ if (std::strncmp(def.PF_DEF_NAME, status, sizeof def.PF_DEF_NAME) == 0) return PF_Err_NONE;
68
+ std::snprintf(def.PF_DEF_NAME, sizeof def.PF_DEF_NAME, "%s", status);
69
+ const PF_ParamUtilsSuite3 *pu = nullptr;
70
+ PF_Err err = PF_Err_NONE;
71
+ if (in_data->pica_basicP->AcquireSuite(kPFParamUtilsSuite, kPFParamUtilsSuiteVersion3, (const void **)&pu) == 0 && pu) {
72
+ err = pu->PF_UpdateParamUI(in_data->effect_ref, AP_PARAM_LICENSE, &def);
73
+ in_data->pica_basicP->ReleaseSuite(kPFParamUtilsSuite, kPFParamUtilsSuiteVersion3);
74
+ }
75
+ return err;
76
+ }
77
+
78
+ // Effect > Options or the status button: the AfterPresets dialog (sign in,
79
+ // buy, sign out, update notes). Re-render: signing in or out changes the frame.
80
+ PF_Err show_options(PF_OutData *out_data) {
81
+ license::options_dialog();
82
+ out_data->out_flags |= PF_OutFlag_FORCE_RERENDER;
83
+ return PF_Err_NONE;
84
+ }
85
+
86
+ PF_Err user_changed_param(PF_OutData *out_data, const PF_UserChangedParamExtra *extra) {
87
+ if (extra->param_index == AP_PARAM_LICENSE) return show_options(out_data);
88
+ return PF_Err_NONE;
89
+ }
90
+
91
+ PF_Err pre_render(PF_InData *in_data, PF_PreRenderExtra *extra) {
92
+ PF_RenderRequest req = extra->input->output_request;
93
+ PF_CheckoutResult in_result;
94
+ PF_Err err = extra->cb->checkout_layer(in_data->effect_ref, AP_PARAM_INPUT, AP_PARAM_INPUT, &req,
95
+ in_data->current_time, in_data->time_step, in_data->time_scale, &in_result);
96
+ if (err) return err;
97
+ // Pixel-independent: the output covers exactly what the input provides.
98
+ extra->output->result_rect = in_result.result_rect;
99
+ extra->output->max_result_rect = in_result.max_result_rect;
100
+ // The license state is part of the frame: mix it into After Effects' cache key
101
+ // (with PF_OutFlag2_I_MIX_GUID_DEPENDENCIES) so a frame rendered locked is
102
+ // never shown after sign-in, and the reverse.
103
+ A_long unlocked = license::unlocked() ? 1 : 0;
104
+ err = extra->cb->GuidMixInPtr(in_data->effect_ref, sizeof unlocked, &unlocked);
105
+ if (err) return err;
106
+ PreRenderData *data = new (std::nothrow) PreRenderData{in_result.result_rect.left, in_result.result_rect.top, unlocked};
107
+ if (!data) return PF_Err_OUT_OF_MEMORY;
108
+ extra->output->pre_render_data = data;
109
+ extra->output->delete_pre_render_data_func = delete_pre_render_data;
110
+ return PF_Err_NONE;
111
+ }
112
+
113
+ PF_Err smart_render(PF_InData *in_data, PF_SmartRenderExtra *extra) {
114
+ PF_Err err = PF_Err_NONE;
115
+ PF_EffectWorld *input = nullptr, *output = nullptr;
116
+ err = extra->cb->checkout_layer_pixels(in_data->effect_ref, AP_PARAM_INPUT, &input);
117
+ if (!err) err = extra->cb->checkout_output(in_data->effect_ref, &output);
118
+
119
+ // Check out every parameter at the current time (checked back in below).
120
+ const int ids[] = {AP_PARAM_AMOUNT, AP_PARAM_COLOR, AP_PARAM_MODE, AP_PARAM_GRADIENT, AP_PARAM_CENTER, AP_PARAM_ANGLE};
121
+ const int n = (int)(sizeof ids / sizeof ids[0]);
122
+ PF_ParamDef p[sizeof ids / sizeof ids[0]];
123
+ bool checked[sizeof ids / sizeof ids[0]] = {};
124
+ for (int i = 0; i < n && !err; ++i) {
125
+ AEFX_CLR_STRUCT(p[i]);
126
+ err = PF_CHECKOUT_PARAM(in_data, ids[i], in_data->current_time, in_data->time_step, in_data->time_scale, &p[i]);
127
+ checked[i] = !err;
128
+ }
129
+
130
+ if (!err && input && output) {
131
+ const PreRenderData *pre = static_cast<const PreRenderData *>(extra->input->pre_render_data);
132
+ TintParams tp;
133
+ tp.amount = (float)(p[0].u.fs_d.value / 100.0);
134
+ tp.r = p[1].u.cd.value.red / 255.f;
135
+ tp.g = p[1].u.cd.value.green / 255.f;
136
+ tp.b = p[1].u.cd.value.blue / 255.f;
137
+ tp.mode = p[2].u.pd.value;
138
+ tp.gradient = p[3].u.bd.value != 0;
139
+ // Point values arrive in layer pixels at the current resolution (16.16
140
+ // fixed; After Effects already applied the downsample factor; verified at
141
+ // half resolution). The buffers start at the pre-render rectangle's corner.
142
+ tp.cx = (float)p[4].u.td.x_value / 65536.f - (pre ? pre->left : 0);
143
+ tp.cy = (float)p[4].u.td.y_value / 65536.f - (pre ? pre->top : 0);
144
+ const float rad = (float)p[5].u.ad.value / 65536.f * 3.14159265f / 180.f; // 0 degrees points up
145
+ tp.dirx = std::sin(rad);
146
+ tp.diry = -std::cos(rad);
147
+ tp.span = (float)(input->width > input->height ? input->width : input->height);
148
+ if (tp.span < 1.f) tp.span = 1.f;
149
+ tp.watermark = pre ? !pre->unlocked : !license::unlocked();
150
+
151
+ PF_PixelFormat fmt = PF_PixelFormat_ARGB32;
152
+ const PF_WorldSuite2 *ws = nullptr;
153
+ if (in_data->pica_basicP->AcquireSuite(kPFWorldSuite, kPFWorldSuiteVersion2, (const void **)&ws) == 0 && ws) {
154
+ err = ws->PF_GetPixelFormat(output, &fmt);
155
+ in_data->pica_basicP->ReleaseSuite(kPFWorldSuite, kPFWorldSuiteVersion2);
156
+ }
157
+ const int w = output->width < input->width ? output->width : input->width;
158
+ const int h = output->height < input->height ? output->height : input->height;
159
+ const char *src = (const char *)input->data;
160
+ char *dst = (char *)output->data;
161
+ if (!err) {
162
+ switch (fmt) {
163
+ case PF_PixelFormat_ARGB128:
164
+ tint_rows<PF_PixelFloat, 1, true>(src, input->rowbytes, dst, output->rowbytes, w, h, tp);
165
+ break;
166
+ case PF_PixelFormat_ARGB64:
167
+ tint_rows<PF_Pixel16, PF_MAX_CHAN16, false>(src, input->rowbytes, dst, output->rowbytes, w, h, tp);
168
+ break;
169
+ case PF_PixelFormat_ARGB32:
170
+ tint_rows<PF_Pixel8, PF_MAX_CHAN8, false>(src, input->rowbytes, dst, output->rowbytes, w, h, tp);
171
+ break;
172
+ default:
173
+ err = PF_Err_BAD_CALLBACK_PARAM;
174
+ }
175
+ }
176
+ }
177
+
178
+ for (int i = 0; i < n; ++i) {
179
+ if (checked[i]) {
180
+ PF_Err e2 = PF_CHECKIN_PARAM(in_data, &p[i]);
181
+ if (!err) err = e2;
182
+ }
183
+ }
184
+ if (input) {
185
+ PF_Err e2 = extra->cb->checkin_layer_pixels(in_data->effect_ref, AP_PARAM_INPUT);
186
+ if (!err) err = e2;
187
+ }
188
+ return err;
189
+ }
190
+
191
+ } // namespace
192
+
193
+ extern "C" DllExport PF_Err PluginDataEntryFunction2(PF_PluginDataPtr inPtr, PF_PluginDataCB2 inPluginDataCallBackPtr,
194
+ SPBasicSuite *, const char *, const char *) {
195
+ PF_Err result = PF_Err_INVALID_CALLBACK;
196
+ result = PF_REGISTER_EFFECT_EXT2(inPtr, inPluginDataCallBackPtr, AP_EFFECT_NAME, AP_MATCH_NAME, AP_CATEGORY,
197
+ AE_RESERVED_INFO, "EffectMain", AP_SUPPORT_URL);
198
+ return result;
199
+ }
200
+
201
+ extern "C" DllExport PF_Err EffectMain(PF_Cmd cmd, PF_InData *in_data, PF_OutData *out_data, PF_ParamDef *params[],
202
+ PF_LayerDef *output, void *extra) {
203
+ (void)output;
204
+ PF_Err err = PF_Err_NONE;
205
+ try {
206
+ switch (cmd) {
207
+ case PF_Cmd_ABOUT: err = about(out_data); break;
208
+ case PF_Cmd_GLOBAL_SETUP: err = global_setup(out_data); break;
209
+ case PF_Cmd_GLOBAL_SETDOWN: license::stop(); break;
210
+ case PF_Cmd_PARAMS_SETUP: err = params_setup(in_data, out_data); break;
211
+ case PF_Cmd_UPDATE_PARAMS_UI: err = update_params_ui(in_data, params); break;
212
+ case PF_Cmd_USER_CHANGED_PARAM:
213
+ err = user_changed_param(out_data, static_cast<const PF_UserChangedParamExtra *>(extra));
214
+ break;
215
+ case PF_Cmd_DO_DIALOG: err = show_options(out_data); break;
216
+ case PF_Cmd_SMART_PRE_RENDER: err = pre_render(in_data, static_cast<PF_PreRenderExtra *>(extra)); break;
217
+ case PF_Cmd_SMART_RENDER: err = smart_render(in_data, static_cast<PF_SmartRenderExtra *>(extra)); break;
218
+ default: break;
219
+ }
220
+ } catch (...) {
221
+ err = PF_Err_INTERNAL_STRUCT_DAMAGED; // never let a C++ exception reach After Effects
222
+ }
223
+ return err;
224
+ }