afterpresets 0.1.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 (173) 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-after-effects.md +57 -0
  38. package/runtime/docs/cep/host-premiere.md +84 -0
  39. package/runtime/docs/{host.md → cep/host.md} +42 -16
  40. package/runtime/docs/{README.md → common/README.md} +10 -8
  41. package/runtime/docs/{assets.md → common/assets.md} +16 -2
  42. package/runtime/docs/{licensing.md → common/licensing.md} +1 -1
  43. package/runtime/docs/{panels.md → common/panels.md} +5 -5
  44. package/runtime/docs/{storage.md → common/storage.md} +5 -5
  45. package/runtime/docs/{updates.md → common/updates.md} +1 -1
  46. package/runtime/docs/native/README.md +44 -0
  47. package/runtime/docs/native/debugging.md +140 -0
  48. package/runtime/docs/native/rules.md +402 -0
  49. package/runtime/docs/native/testing.md +154 -0
  50. package/runtime/package.json +1 -1
  51. package/runtime/scripts/ae-sdk-docs/config.js +102 -0
  52. package/runtime/scripts/ae-sdk-docs/convert.js +430 -0
  53. package/runtime/scripts/ae-sdk-docs/index.js +314 -0
  54. package/runtime/scripts/ae-sdk-docs/source.js +205 -0
  55. package/runtime/scripts/ae-sdk-docs/split.js +170 -0
  56. package/runtime/scripts/ae-sdk-docs.js +72 -0
  57. package/runtime/src/host.js +1 -1
  58. package/runtime/src/updates.js +1 -1
  59. package/src/api/error-messages.js +1 -1
  60. package/src/build/build.js +7 -7
  61. package/src/build/html.js +1 -1
  62. package/src/build/jsx.js +2 -2
  63. package/src/build/runtime.js +3 -3
  64. package/src/check/check.js +3 -3
  65. package/src/check/native.js +14 -0
  66. package/src/check/pricing.js +1 -1
  67. package/src/check/types.js +2 -2
  68. package/src/cli.js +5 -5
  69. package/src/commands/add.js +8 -3
  70. package/src/commands/build.js +8 -2
  71. package/src/commands/check.js +11 -3
  72. package/src/commands/dev.js +9 -2
  73. package/src/commands/docs.js +5 -1
  74. package/src/commands/init.js +15 -3
  75. package/src/commands/push.js +5 -2
  76. package/src/commands/sdk.js +14 -0
  77. package/src/commands/testers.js +1 -1
  78. package/src/commands/upgrade.js +2 -2
  79. package/src/commands/whoami.js +1 -1
  80. package/src/config.js +1 -1
  81. package/src/dev/bridge.js +8 -0
  82. package/src/dev/cep-link.js +1 -1
  83. package/src/dev/debug-file.js +5 -5
  84. package/src/dev/native.js +3 -0
  85. package/src/dev/session.js +4 -3
  86. package/src/mcp/extendscript.js +10 -7
  87. package/src/mcp/host-target.js +1 -0
  88. package/src/mcp/panels.js +1 -1
  89. package/src/mcp/png.js +1 -0
  90. package/src/mcp/server.js +2 -2
  91. package/src/mcp/tools/capture.js +3 -0
  92. package/src/mcp/tools/host.js +1 -1
  93. package/src/mcp/tools/native.js +6 -0
  94. package/src/mcp/tools/panel.js +4 -4
  95. package/src/mcp/tools/project.js +47 -31
  96. package/src/mcp/tools/releases.js +23 -20
  97. package/src/native/build.js +27 -0
  98. package/src/native/cmake/CMakeLists.txt +88 -0
  99. package/src/native/cmake/zig-windows.cmake +19 -0
  100. package/src/native/effect.js +3 -0
  101. package/src/native/install.js +8 -0
  102. package/src/native/installers.js +4 -0
  103. package/src/native/pipl.js +20 -0
  104. package/src/native/restart.js +2 -0
  105. package/src/native/sdk.js +8 -0
  106. package/src/project/add-panel.js +1 -1
  107. package/src/project/docs.js +3 -1
  108. package/src/project/manifest.js +3 -2
  109. package/src/project/project.js +2 -2
  110. package/src/project/template.js +5 -1
  111. package/src/prompt.js +1 -1
  112. package/src/release/installers.js +4 -4
  113. package/src/release/native.js +3 -0
  114. package/src/release/package.js +1 -1
  115. package/src/release/publish.js +1 -1
  116. package/src/system/run-command.js +1 -1
  117. package/src/targets.js +1 -0
  118. package/src/user-config.js +2 -2
  119. package/template/cep/AGENTS.md +104 -0
  120. package/template/{README.md → cep/README.md} +16 -11
  121. package/template/cep/docs/code_guide.md +67 -0
  122. package/template/cep/docs/initial_setup.md +115 -0
  123. package/template/cep/docs/release_checklist.md +24 -0
  124. package/template/{src → cep/src}/features/home/home.html +4 -4
  125. package/template/{src → cep/src}/features/home/home.js +10 -11
  126. package/template/{src → cep/src}/shared/lib/host-theme.js +3 -3
  127. package/template/{src → cep/src}/shared/lib/view.js +1 -1
  128. package/template/{src → cep/src}/shared/ui/tokens.css +1 -1
  129. package/template/hosts/AGENTS.two-apps.md +12 -0
  130. package/template/hosts/aeft/AGENTS.host.md +10 -0
  131. package/template/hosts/aeft/jsx/aeft/home.jsx +16 -0
  132. package/template/hosts/ppro/AGENTS.host.md +15 -0
  133. package/template/hosts/ppro/jsx/ppro/home.jsx +20 -0
  134. package/template/native/.mcp.json +8 -0
  135. package/template/native/AGENTS.md +105 -0
  136. package/template/native/README.md +71 -0
  137. package/template/native/docs/code_guide.md +72 -0
  138. package/template/native/docs/initial_setup.md +179 -0
  139. package/template/native/docs/release_checklist.md +24 -0
  140. package/template/native/effect.json +16 -0
  141. package/template/native/gitignore +8 -0
  142. package/template/native/package.json +14 -0
  143. package/template/native/src/Effect.cpp +224 -0
  144. package/template/native/src/License.cpp +95 -0
  145. package/template/native/src/License.h +29 -0
  146. package/template/native/src/Render.h +68 -0
  147. package/vendor/afterpresets-additions/AfterEffects.d.ts +291 -0
  148. package/vendor/afterpresets-additions/Premiere.d.ts +179 -0
  149. package/vendor/types-for-adobe/Premiere/24.0/index.d.ts +2567 -0
  150. package/vendor/types-for-adobe/README.md +8 -4
  151. package/template/AGENTS.md +0 -168
  152. package/template/docs/initial_setup.md +0 -69
  153. package/template/jsx/home.jsx +0 -17
  154. /package/runtime/docs/{boot.md → common/boot.md} +0 -0
  155. /package/template/{.mcp.json → cep/.mcp.json} +0 -0
  156. /package/template/{gitignore → cep/gitignore} +0 -0
  157. /package/template/{package.json → cep/package.json} +0 -0
  158. /package/template/{src → cep/src}/features/home/home.css +0 -0
  159. /package/template/{src → cep/src}/features/license-screen/license-screen.css +0 -0
  160. /package/template/{src → cep/src}/features/license-screen/license-screen.html +0 -0
  161. /package/template/{src → cep/src}/features/license-screen/license-screen.js +0 -0
  162. /package/template/{src → cep/src}/features/update-screen/update-screen.css +0 -0
  163. /package/template/{src → cep/src}/features/update-screen/update-screen.html +0 -0
  164. /package/template/{src → cep/src}/features/update-screen/update-screen.js +0 -0
  165. /package/template/{src → cep/src}/panels/main/index.html +0 -0
  166. /package/template/{src → cep/src}/panels/main/main.js +0 -0
  167. /package/template/{src → cep/src}/shared/lib/dom.js +0 -0
  168. /package/template/{src → cep/src}/shared/lib/markdown.js +0 -0
  169. /package/template/{src → cep/src}/shared/ui/base.css +0 -0
  170. /package/template/{src → cep/src}/shared/ui/button.css +0 -0
  171. /package/template/{src → cep/src}/shared/ui/card.css +0 -0
  172. /package/template/{src → cep/src}/shared/ui/field.css +0 -0
  173. /package/template/{src → cep/src}/shared/ui/index.css +0 -0
package/README.md CHANGED
@@ -1,20 +1,22 @@
1
1
  # AfterPresets CLI
2
2
 
3
- Make Adobe After Effects extensions with an AI agent, and sell them on
3
+ Make Adobe After Effects and Premiere Pro extensions with an AI agent, and sell them on
4
4
  [AfterPresets](https://afterpresets.com) with licensing, signed auto-updates
5
5
  and checkout handled.
6
6
 
7
7
  You do not need to write code. Open an empty folder in your agent (the Code
8
8
  tab of the Claude desktop app, Claude Code, Codex, Cursor...) and follow the
9
- **[Quickstart](https://docs.afterpresets.com/get-started/quickstart/)**.
9
+ **[Quickstart](https://docs.afterpresets.com/extensions/quickstart/)**.
10
+ Building a native After Effects effect plugin (C++) instead? See the
11
+ **[plugin Quickstart](https://docs.afterpresets.com/plugins/quickstart/)**.
10
12
 
11
13
  ```sh
12
14
  npx afterpresets login
13
15
  npx afterpresets init my-extension
14
16
  ```
15
17
 
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
+ Inside a project, `ap dev` puts the extension into After Effects or Premiere
19
+ Pro and reloads it on every save, `ap push` publishes a release, and `ap upgrade` updates
18
20
  this package. Every project includes `AGENTS.md` and an MCP server
19
21
  (`ap mcp`) so your agent can see, click and test the live panel.
20
22
 
@@ -0,0 +1,82 @@
1
+ cmake_minimum_required(VERSION 3.20)
2
+ project(ap_native VERSION 0.1.0 LANGUAGES C CXX)
3
+
4
+ # Plugins add this folder with add_subdirectory() and link ap_native statically.
5
+ # Tests: tests/run.sh (configures this file with -DAP_NATIVE_TESTS=ON).
6
+ option(AP_NATIVE_TESTS "Build the host-free unit tests" OFF)
7
+
8
+ if(APPLE)
9
+ enable_language(OBJCXX)
10
+ if(NOT CMAKE_OSX_DEPLOYMENT_TARGET)
11
+ set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING "" FORCE)
12
+ endif()
13
+ endif()
14
+
15
+ set(AP_NATIVE_SOURCES
16
+ src/apply.cpp
17
+ src/crypto.cpp
18
+ src/dialog.cpp
19
+ src/engine.cpp
20
+ src/fsutil.cpp
21
+ src/json.cpp
22
+ src/platform_common.cpp
23
+ src/runtime.cpp
24
+ src/util.cpp
25
+ src/zip.cpp
26
+ third_party/monocypher/monocypher.c
27
+ third_party/monocypher/monocypher-ed25519.c
28
+ third_party/tinf/tinflate.c
29
+ third_party/tinf/crc32.c
30
+ )
31
+ if(APPLE)
32
+ list(APPEND AP_NATIVE_SOURCES src/platform_mac.mm)
33
+ elseif(WIN32)
34
+ list(APPEND AP_NATIVE_SOURCES src/platform_win.cpp)
35
+ else()
36
+ message(FATAL_ERROR "ap_native supports macOS and Windows only")
37
+ endif()
38
+
39
+ add_library(ap_native STATIC ${AP_NATIVE_SOURCES})
40
+ target_include_directories(ap_native
41
+ PUBLIC include
42
+ PRIVATE src third_party/monocypher third_party/tinf)
43
+ target_compile_features(ap_native PUBLIC cxx_std_17)
44
+ # Hidden symbols: two plugins carrying different copies never clash, and the
45
+ # shipped binary does not advertise the license functions by name.
46
+ set_target_properties(ap_native PROPERTIES
47
+ CXX_VISIBILITY_PRESET hidden
48
+ C_VISIBILITY_PRESET hidden
49
+ OBJCXX_VISIBILITY_PRESET hidden
50
+ VISIBILITY_INLINES_HIDDEN ON
51
+ POSITION_INDEPENDENT_CODE ON)
52
+
53
+ # Warnings for our own sources only (not the vendored C files).
54
+ set_source_files_properties(
55
+ src/apply.cpp src/crypto.cpp src/dialog.cpp src/engine.cpp src/fsutil.cpp src/json.cpp
56
+ src/platform_common.cpp src/runtime.cpp src/util.cpp src/zip.cpp src/platform_win.cpp
57
+ PROPERTIES COMPILE_OPTIONS "-Wall;-Wextra;-Wno-unused-parameter")
58
+
59
+ if(APPLE)
60
+ set_source_files_properties(src/platform_mac.mm PROPERTIES COMPILE_FLAGS "-fobjc-arc -Wall -Wextra -Wno-unused-parameter")
61
+ target_link_libraries(ap_native PUBLIC "-framework Foundation" "-framework AppKit" "-framework IOKit")
62
+ elseif(WIN32)
63
+ target_compile_definitions(ap_native PRIVATE UNICODE _UNICODE WIN32_LEAN_AND_MEAN)
64
+ target_link_libraries(ap_native PUBLIC winhttp iphlpapi bcrypt shell32 ole32 advapi32 user32)
65
+ endif()
66
+
67
+ if(AP_NATIVE_TESTS)
68
+ add_executable(ap_native_tests
69
+ tests/test_main.cpp
70
+ tests/test_crypto.cpp
71
+ tests/test_json_zip.cpp
72
+ tests/test_license.cpp
73
+ tests/test_updates.cpp
74
+ tests/test_runtime.cpp
75
+ tests/test_server.cpp
76
+ tests/test_dialog.cpp)
77
+ target_include_directories(ap_native_tests PRIVATE src tests third_party/monocypher third_party/tinf)
78
+ target_link_libraries(ap_native_tests PRIVATE ap_native)
79
+ add_executable(ap_native_probe tests/probe.cpp)
80
+ target_include_directories(ap_native_probe PRIVATE src)
81
+ target_link_libraries(ap_native_probe PRIVATE ap_native)
82
+ endif()
@@ -0,0 +1,40 @@
1
+ # ap_native
2
+
3
+ The AfterPresets runtime for native (C++) After Effects plugins: licensing
4
+ (browser sign-in, signed license proofs, offline grace) and signed self-updates.
5
+ It is the C++ counterpart of `@afterpresets/runtime` and talks to the same API.
6
+ AfterPresets maintains it; developers do not edit it. The CLI copies the files
7
+ in `vendor-files.json` into each native project.
8
+
9
+ - Design, threading, security and the integration snippet:
10
+ [`docs/native-runtime-design.md`](../../docs/native-runtime-design.md)
11
+ - C API: [`include/ap_native.h`](include/ap_native.h)
12
+ - Third-party code: [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md)
13
+
14
+ ## Use from a plugin's CMake
15
+
16
+ ```cmake
17
+ add_subdirectory(vendor/ap_native)
18
+ target_link_libraries(MyEffect PRIVATE ap_native)
19
+ ```
20
+
21
+ ## Tests (maintainers)
22
+
23
+ ```sh
24
+ packages/native-runtime/tests/run.sh # 63 C++ tests + Node parity/integration tests
25
+ AP_SANITIZE=1 packages/native-runtime/tests/run.sh # AddressSanitizer + UBSan
26
+ AP_TSAN=1 packages/native-runtime/tests/run.sh # ThreadSanitizer
27
+ ```
28
+
29
+ Requirements: macOS with the Command Line Tools, `brew install cmake ninja`,
30
+ Node 18+, and `pnpm install` at the repo root. The server-signature tests run
31
+ the API's signing code through `apps/api`'s `tsx`.
32
+
33
+ Windows cross-build check (needs `brew install zig`; links, but cannot run without Windows):
34
+
35
+ ```sh
36
+ cmake -S packages/native-runtime -B packages/native-runtime/build/win -G Ninja \
37
+ -DAP_NATIVE_TESTS=ON \
38
+ -DCMAKE_TOOLCHAIN_FILE=docs/native-research/spike-hello/cmake/zig-x86_64-windows.cmake
39
+ ninja -C packages/native-runtime/build/win
40
+ ```
@@ -0,0 +1,14 @@
1
+ # Third-party code in ap_native
2
+
3
+ Both are vendored unmodified. Their licences allow use in closed-source,
4
+ commercial plugins. Keep this file, and each folder's licence file, with every
5
+ copy of the library.
6
+
7
+ | Folder | Project | Version | Licence | Used for |
8
+ |---|---|---|---|---|
9
+ | `third_party/monocypher/` | [Monocypher](https://monocypher.org) (`monocypher.c/.h`, `optional/monocypher-ed25519.c/.h`) | 4.0.2 (matches the GitHub tag apart from the version comment) | CC0-1.0 or BSD-2-Clause, your choice (`LICENCE.md`) | Ed25519 signature verification (SHA-512 inside) |
10
+ | `third_party/tinf/` | [tinf](https://github.com/jibsen/tinf) by Jørgen Ibsen (`tinflate.c`, `crc32.c`, `tinf.h`) | commit 57ffa1f (2022-02-12) | zlib (`LICENSE`) | Inflate (ZIP deflate entries) and CRC-32 |
11
+
12
+ The CC0 option means Monocypher needs no attribution. tinf's zlib licence asks
13
+ that its notice stay in the source distribution; attribution in shipped
14
+ binaries is appreciated but not required.
@@ -0,0 +1,223 @@
1
+ /* ap_native - AfterPresets runtime for native (C++) After Effects plugins.
2
+ *
3
+ * Licensing (browser sign-in, Ed25519-signed license proofs, offline grace)
4
+ * and signed updates (check, download, verify, stage, apply on next launch)
5
+ * against the same API as the JS runtime (@afterpresets/runtime):
6
+ * POST {apiBase}/v1/api/extensions/{bundleId}/license/sign-in | sign-in/poll
7
+ * | validate | sign-out, and /updates/check.
8
+ * Design: docs/native-runtime-design.md.
9
+ *
10
+ * Threading contract
11
+ * - ap_init / ap_shutdown / ap_apply_staged_update: main (UI) thread, from
12
+ * PF_Cmd_GLOBAL_SETUP / PF_Cmd_GLOBAL_SETDOWN.
13
+ * - ap_is_unlocked, ap_status_get, ap_generation: any thread, including
14
+ * render threads (MFR). Lock-free atomic reads; never block, never allocate.
15
+ * - Everything else: any non-render thread (normally the UI thread).
16
+ * Commands only enqueue work; all network I/O happens on one background
17
+ * worker thread owned by the runtime. No call in this header ever does
18
+ * network I/O on the calling thread.
19
+ *
20
+ * The plugin links this library statically. Build it with hidden symbol
21
+ * visibility (the CMake target does) so two plugins carrying different copies
22
+ * never clash. No secrets live in this library: the public key verifies,
23
+ * it cannot sign. */
24
+ #ifndef AP_NATIVE_H
25
+ #define AP_NATIVE_H
26
+
27
+ #include <stddef.h>
28
+ #include <stdint.h>
29
+
30
+ #ifdef __cplusplus
31
+ extern "C" {
32
+ #endif
33
+
34
+ #define AP_NATIVE_VERSION "0.1.0"
35
+
36
+ typedef struct ap_runtime ap_runtime;
37
+
38
+ /* Same statuses as the JS runtime's AP.license.state.status. */
39
+ typedef enum ap_status {
40
+ AP_STATUS_DISABLED = 0, /* licensing off: always unlocked */
41
+ AP_STATUS_SIGNED_OUT,
42
+ AP_STATUS_SIGNING_IN, /* browser sign-in in progress (state.sign_in_url) */
43
+ AP_STATUS_CHECKING,
44
+ AP_STATUS_ACTIVE,
45
+ AP_STATUS_NOT_OWNED,
46
+ AP_STATUS_REFUNDED,
47
+ AP_STATUS_OFFLINE_GRACE, /* unlocked; state.grace_days_left */
48
+ AP_STATUS_OFFLINE_EXPIRED,
49
+ AP_STATUS_ERROR
50
+ } ap_status;
51
+
52
+ typedef enum ap_update_mode {
53
+ AP_UPDATES_OFF = 0, /* never checks */
54
+ AP_UPDATES_MANUAL, /* only when ap_check_for_update is called */
55
+ AP_UPDATES_NOTIFY, /* checks automatically; downloads on ap_download_update */
56
+ AP_UPDATES_BACKGROUND /* checks, downloads, verifies and stages automatically (default) */
57
+ } ap_update_mode;
58
+
59
+ typedef enum ap_update_status {
60
+ AP_UPDATE_IDLE = 0,
61
+ AP_UPDATE_CHECKING,
62
+ AP_UPDATE_UP_TO_DATE,
63
+ AP_UPDATE_AVAILABLE, /* found, not downloaded (notify/manual mode) */
64
+ AP_UPDATE_DOWNLOADING,
65
+ AP_UPDATE_STAGED, /* verified and staged; applied by ap_apply_staged_update */
66
+ AP_UPDATE_APPLIED, /* files replaced; the new version runs after After Effects restarts */
67
+ AP_UPDATE_ERROR
68
+ } ap_update_status;
69
+
70
+ typedef struct ap_config {
71
+ const char *bundle_id; /* com.afterpresets.<seller>.<slug> (required) */
72
+ const char *version; /* semver of this build (required) */
73
+ const char *api_base; /* https://api.afterpresets.com (http only for 127.0.0.1/localhost) */
74
+ const char *public_key; /* Ed25519 public key: base64 SPKI DER, PEM, base64 of a PEM, or base64 of the raw 32 bytes */
75
+ const char *product_url; /* listing page (buy / download again) */
76
+ const char *manage_devices_url; /* where buyers free a computer slot */
77
+ int license_enabled; /* 0: status DISABLED, no license network calls */
78
+ int offline_grace_days; /* 0-30, measured from the last signed validation */
79
+ int check_in_background; /* nonzero: a signed but stale check unlocks at launch while the API is asked */
80
+ ap_update_mode update_mode;
81
+ const char *channel; /* "production" (default) or "test"; only validated by the API */
82
+ int dev; /* dev build: updates are checked but never staged or applied */
83
+ int apply_when_staged; /* default 1: replace the installed plugin right after a verified download, so the
84
+ effect can show "Update installed: restart After Effects" in this session.
85
+ 0: only ap_apply_staged_update replaces it (GLOBAL_SETDOWN / next GLOBAL_SETUP). */
86
+ const char *install_dir; /* optional: the installed <bundleId> folder. NULL: found from the binary's path */
87
+ const char *data_root; /* optional (tests): replaces ~/Library/Application Support or %LOCALAPPDATA%; logs then go to <data_root>/AfterPresets/Logs */
88
+ const char *build_id; /* optional; written to the log at ap_init so tools can prove which build is loaded */
89
+ void (*on_change)(void *user); /* optional; called on the WORKER thread after any state change. Only set a flag. */
90
+ void (*log)(void *user, int level, const char *message); /* optional; 0 debug 1 info 2 warn 3 error. Never receives tokens or signed URLs. */
91
+ void *user;
92
+ } ap_config;
93
+
94
+ #define AP_TEXT_MAX 512
95
+
96
+ typedef struct ap_state {
97
+ ap_status status;
98
+ int unlocked; /* active, offline_grace or disabled */
99
+ int grace_days_left;
100
+ uint64_t generation; /* increments on every license or update state change */
101
+ char message[AP_TEXT_MAX]; /* default English message for the status (UTF-8) */
102
+ char sign_in_url[AP_TEXT_MAX]; /* while SIGNING_IN: the page to reopen */
103
+ char username[128]; /* signed-in AfterPresets username, if known */
104
+ } ap_state;
105
+
106
+ typedef struct ap_update_state {
107
+ ap_update_status status;
108
+ int restart_required; /* 1 after a successful apply */
109
+ int needs_manual_install; /* staged but the install folder is not writable */
110
+ char version[64]; /* target version */
111
+ char direction[16]; /* "upgrade" | "downgrade" */
112
+ char change_log[2048]; /* target release's changelog, truncated */
113
+ char error_code[48]; /* e.g. network, bad_signature, bad_checksum, bad_zip, not_writable */
114
+ char error_message[AP_TEXT_MAX];
115
+ } ap_update_state;
116
+
117
+ typedef enum ap_apply_result {
118
+ AP_APPLY_NOTHING = 0, /* nothing staged */
119
+ AP_APPLY_OK = 1, /* replaced; restart After Effects to run it */
120
+ AP_APPLY_NOT_INSTALLED = -1, /* the binary is not inside an installed <bundleId> folder (dev build, symlink) */
121
+ AP_APPLY_NOT_WRITABLE = -2, /* install folder not writable by this user: buyer must reinstall */
122
+ AP_APPLY_FAILED = -3, /* error while replacing; the previous version was restored */
123
+ AP_APPLY_DEV = -4 /* dev build */
124
+ } ap_apply_result;
125
+
126
+ /* Fills defaults: licensing on, 0 grace days, check_in_background on,
127
+ * AP_UPDATES_BACKGROUND, apply_when_staged on, channel "production", everything else NULL/0. */
128
+ void ap_config_defaults(ap_config *config);
129
+
130
+ /* Creates the runtime: reads the license cache from disk and verifies its
131
+ * proof synchronously (no network; a few ms), then starts the worker thread,
132
+ * which validates online when needed and checks for updates. Returns NULL for
133
+ * an invalid config (bad bundle id, version or api_base). Call once per
134
+ * process (PF_Cmd_GLOBAL_SETUP); keep the pointer in global data. */
135
+ ap_runtime *ap_init(const ap_config *config);
136
+
137
+ /* Stops the worker (cancels in-flight requests; waits at most ~3 s) and frees
138
+ * the runtime. PF_Cmd_GLOBAL_SETDOWN. NULL is ignored. */
139
+ void ap_shutdown(ap_runtime *rt);
140
+
141
+ /* Render-thread safe. 1 when the effect may render without the watermark. */
142
+ int ap_is_unlocked(const ap_runtime *rt);
143
+ ap_status ap_status_get(const ap_runtime *rt);
144
+ /* Changes whenever anything in ap_state or ap_update_state changes. Cheap:
145
+ * compare it in PF_Cmd_UPDATE_PARAMS_UI and refresh the UI when it moved. */
146
+ uint64_t ap_generation(const ap_runtime *rt);
147
+
148
+ void ap_get_state(ap_runtime *rt, ap_state *out);
149
+ void ap_get_update_state(ap_runtime *rt, ap_update_state *out);
150
+
151
+ /* Starts a browser sign-in: asks the API for a sign-in request, opens the
152
+ * verification page in the default browser and polls until the buyer approves
153
+ * it (status SIGNING_IN, then ACTIVE). Returns 0 when queued, -1 when
154
+ * licensing is off. Safe to call repeatedly (one sign-in at a time). */
155
+ int ap_begin_sign_in(ap_runtime *rt);
156
+ void ap_cancel_sign_in(ap_runtime *rt);
157
+ /* Reopens the pending sign-in page (e.g. the buyer closed the tab). */
158
+ void ap_open_sign_in_page(ap_runtime *rt);
159
+ /* Re-validates the stored license (after NOT_OWNED, OFFLINE_EXPIRED, ERROR). */
160
+ void ap_retry(ap_runtime *rt);
161
+ /* Deletes the local license and frees this computer's slot (best effort when offline). */
162
+ void ap_sign_out(ap_runtime *rt);
163
+
164
+ /* Checks now (any mode except OFF). In BACKGROUND mode a found update is downloaded and staged. */
165
+ void ap_check_for_update(ap_runtime *rt);
166
+ /* Downloads, verifies and stages the update found by the last check (NOTIFY/MANUAL modes). */
167
+ void ap_download_update(ap_runtime *rt);
168
+ /* One line for the effect's UI (UTF-8), or "" when there is nothing to say:
169
+ * "Update installed: restart After Effects to use v1.2.0"
170
+ * "Update v1.2.0 downloaded: it installs when After Effects quits"
171
+ * "Update v1.2.0 is ready, but this plugin's folder is not writable: download it again from AfterPresets"
172
+ * "Update v1.2.0 available"
173
+ * Copies into buf (NUL-terminated, never cut inside a UTF-8 sequence); returns the full length. */
174
+ size_t ap_update_notice(ap_runtime *rt, char *buf, size_t size);
175
+ /* The update's changelog (up to 8000 bytes, UTF-8) for the Options dialog; returns the full length. */
176
+ size_t ap_update_changelog(ap_runtime *rt, char *buf, size_t size);
177
+
178
+ typedef enum ap_dialog_action {
179
+ AP_DIALOG_NONE = -1, /* could not be shown (not the main thread) */
180
+ AP_DIALOG_CLOSE = 0,
181
+ AP_DIALOG_SIGN_IN, /* browser sign-in started */
182
+ AP_DIALOG_OPEN_SIGN_IN_PAGE,
183
+ AP_DIALOG_CANCEL_SIGN_IN,
184
+ AP_DIALOG_SIGN_OUT,
185
+ AP_DIALOG_RETRY,
186
+ AP_DIALOG_OPEN_PRODUCT, /* product_url opened (buy) */
187
+ AP_DIALOG_MANAGE_DEVICES /* manage_devices_url opened */
188
+ } ap_dialog_action;
189
+
190
+ /* The effect's Options dialog (PF_OutFlag_I_DO_DIALOG -> PF_Cmd_DO_DIALOG).
191
+ * Shows the license status, the signed-in account, the update notice and
192
+ * changelog, with buttons for the current state (Sign in / Buy / Open
193
+ * sign-in page / Sign out / Try again / Manage devices), performs the chosen
194
+ * action (network work goes to the worker) and returns it. Modal; main thread
195
+ * only. title: the effect's name (UTF-8). */
196
+ ap_dialog_action ap_options_dialog(ap_runtime *rt, const char *title);
197
+
198
+ /* Replaces the installed files with the staged, verified update. Filesystem
199
+ * only, synchronous; call from PF_Cmd_GLOBAL_SETDOWN (After Effects is
200
+ * quitting) and/or PF_Cmd_GLOBAL_SETUP. The running code keeps running; the new
201
+ * version loads at the next launch. Never call from a render thread. */
202
+ ap_apply_result ap_apply_staged_update(ap_runtime *rt);
203
+
204
+ /* Appends a line to this plugin's log (macOS ~/Library/Logs/AfterPresets/<bundleId>.log,
205
+ * Windows %LOCALAPPDATA%\AfterPresets\Logs\<bundleId>.log; 1 MB, one rotated
206
+ * file). Any thread, including render threads: never blocks, never does I/O on
207
+ * the caller (the worker writes the file); a line is dropped if the queue is
208
+ * busy or full. Never log tokens or URLs; tokens and query strings are scrubbed anyway. */
209
+ void ap_log(ap_runtime *rt, int level, const char *message);
210
+ /* Copies the log file path into buf (NUL-terminated); returns its full length. */
211
+ size_t ap_log_path(ap_runtime *rt, char *buf, size_t size);
212
+
213
+ /* Waits until the worker has no queued or running work (tests, tools). 0 on idle, -1 on timeout. */
214
+ int ap_wait_idle(ap_runtime *rt, int timeout_ms);
215
+
216
+ const char *ap_status_name(ap_status status); /* "active", "signed_out", ... (JS names) */
217
+ const char *ap_update_status_name(ap_update_status status);
218
+
219
+ #ifdef __cplusplus
220
+ }
221
+ #endif
222
+
223
+ #endif /* AP_NATIVE_H */
@@ -0,0 +1,199 @@
1
+ // Applies a staged, verified update to the installed <bundleId> folder.
2
+ //
3
+ // Install layout (docs/native-runtime-design.md, "Install layout"):
4
+ // .../MediaCore/AfterPresets/<bundleId>/<Name>.plugin (macOS)
5
+ // ...\MediaCore\AfterPresets\<bundleId>\<Name>.aex (Windows)
6
+ // The buyer's install script creates MediaCore/AfterPresets once with admin and
7
+ // gives it to the user, so updates never need admin. Only this OS's plugin is
8
+ // replaced; other files in <bundleId>/ are left alone, and any other plugin of
9
+ // this OS there (an old name after a rename) is removed so AE never loads two.
10
+ // macOS swaps the bundle atomically (renamex_np RENAME_SWAP); elsewhere the old
11
+ // plugin is renamed away first (Windows allows renaming a loaded DLL, not
12
+ // deleting or overwriting it). Work copies are named "<Name>.plugin.ap-new" /
13
+ // "<Name>.aex.ap-old" inside "(ap-update)" / "(ap-old)": AE only loads
14
+ // *.plugin / *.aex, so an interrupted apply never leaves a second loadable
15
+ // copy. The running process keeps its already-loaded code; the new version
16
+ // loads at the next launch.
17
+ #include <cctype>
18
+ #include <set>
19
+
20
+ #include "engine.hpp"
21
+ #include "fsutil.hpp"
22
+ #include "util.hpp"
23
+
24
+ namespace apn {
25
+
26
+ namespace {
27
+
28
+ const char *const WORK_DIR = "(ap-update)";
29
+ const char *const OLD_DIR = "(ap-old)";
30
+
31
+ struct Lock {
32
+ std::string path;
33
+ bool held = false;
34
+ Lock(const std::string &p, int64_t now) : path(p) {
35
+ for (int attempt = 0; attempt < 2 && !held; attempt++) {
36
+ auto r = fsu::write_exclusive(path, std::to_string(now));
37
+ if (r == fsu::Excl::Ok) { held = true; break; }
38
+ if (r == fsu::Excl::Error) break;
39
+ int64_t age = fsu::age_ms(path, now);
40
+ if (age >= 0 && age <= Engine::LOCK_STALE_MS) break;
41
+ fsu::remove_file(path);
42
+ }
43
+ }
44
+ ~Lock() {
45
+ if (held) fsu::remove_file(path);
46
+ }
47
+ };
48
+
49
+ } // namespace
50
+
51
+ std::string find_install_dir(const std::string &binary_path, const std::string &bundle_id) {
52
+ if (binary_path.empty() || bundle_id.empty()) return "";
53
+ std::string dir = fsu::parent(binary_path);
54
+ for (int depth = 0; depth < 8 && !dir.empty(); depth++) {
55
+ if (fsu::basename(dir) == bundle_id) return dir;
56
+ std::string up = fsu::parent(dir);
57
+ if (up == dir) break;
58
+ dir = up;
59
+ }
60
+ return "";
61
+ }
62
+
63
+ void cleanup_install_leftovers(const Settings &s) {
64
+ if (s.install_dir.empty() || fsu::basename(s.install_dir) != s.bundle_id) return;
65
+ fsu::remove_all(fsu::join(s.install_dir, OLD_DIR));
66
+ fsu::remove_all(fsu::join(s.install_dir, WORK_DIR));
67
+ }
68
+
69
+ namespace {
70
+
71
+ bool is_os_plugin(const std::string &name, const std::string &os) {
72
+ std::string lower = name;
73
+ for (auto &c : lower) c = (char)std::tolower((unsigned char)c);
74
+ const std::string ext = os == "windows" ? ".aex" : ".plugin";
75
+ return lower.size() > ext.size() && lower.compare(lower.size() - ext.size(), ext.size(), ext) == 0;
76
+ }
77
+
78
+ } // namespace
79
+
80
+ ApplyResult apply_staged_update(const Settings &s, const Env &env, std::string *version, std::string *error) {
81
+ auto say = [&](const std::string &m) {
82
+ if (error) *error = m;
83
+ if (env.log && !m.empty()) env.log(2, m);
84
+ };
85
+ Paths paths = Paths::make(env.data_root, s.bundle_id);
86
+ auto meta_text = fsu::read(paths.staged_meta, 1 << 20);
87
+ auto meta = meta_text ? Json::parse(*meta_text) : std::nullopt;
88
+ if (!meta || !meta->is_object() || !fsu::is_dir(paths.staged)) return ApplyResult::Nothing;
89
+ std::string staged_version = (*meta)["version"].str();
90
+ if (version) *version = staged_version;
91
+ if (s.dev) return ApplyResult::Dev;
92
+ if (staged_version.empty() || staged_version == s.version) {
93
+ fsu::remove_all(paths.staged);
94
+ fsu::remove_file(paths.staged_meta);
95
+ return ApplyResult::Nothing;
96
+ }
97
+ std::vector<std::string> staged_entries = fsu::list(paths.staged);
98
+ if (staged_entries.size() != 1 || !is_os_plugin(staged_entries[0], s.os)) {
99
+ fsu::remove_all(paths.staged);
100
+ fsu::remove_file(paths.staged_meta);
101
+ say("The staged update is incomplete; it was discarded.");
102
+ return ApplyResult::Failed;
103
+ }
104
+ const std::string plugin = staged_entries[0];
105
+ // Only ever touch a folder named exactly <bundleId>: a wrong install_dir
106
+ // must never make us rename things in the shared MediaCore folder.
107
+ const std::string &install = s.install_dir;
108
+ if (install.empty() || fsu::basename(install) != s.bundle_id || !fsu::is_dir(install)) {
109
+ say("The plugin is not inside an installed " + s.bundle_id + " folder; the update was not applied.");
110
+ return ApplyResult::NotInstalled;
111
+ }
112
+ if (!fsu::dir_writable(install)) {
113
+ say("The plugin folder is not writable; reinstall the plugin to update it.");
114
+ return ApplyResult::NotWritable;
115
+ }
116
+ Lock lock(paths.lock, env.now_ms());
117
+ if (!lock.held) {
118
+ say("Another process is installing this update.");
119
+ return ApplyResult::Failed;
120
+ }
121
+
122
+ std::string work = fsu::join(install, WORK_DIR);
123
+ std::string old = fsu::join(install, OLD_DIR);
124
+ fsu::remove_all(work);
125
+ fsu::remove_all(old); // may fail on Windows while an old DLL is still loaded; names below are unique
126
+ std::string fresh = fsu::join(work, plugin + ".ap-new");
127
+ std::string copy_error;
128
+ bool copied = fsu::is_dir(fsu::join(paths.staged, plugin))
129
+ ? fsu::copy_tree(fsu::join(paths.staged, plugin), fresh, &copy_error)
130
+ : (fsu::mkdirs(work) && [&] {
131
+ auto bytes = fsu::read(fsu::join(paths.staged, plugin), 2ULL * 1024 * 1024 * 1024);
132
+ return bytes && fsu::write_file(fresh, *bytes, true);
133
+ }());
134
+ if (!copied) {
135
+ fsu::remove_all(work);
136
+ say("The update could not be copied: " + copy_error);
137
+ return ApplyResult::Failed;
138
+ }
139
+ // The staged files were verified; a quarantine flag must not block loading.
140
+ if (env.strip_quarantine) env.strip_quarantine(fresh);
141
+ if (!fsu::mkdirs(old)) {
142
+ fsu::remove_all(work);
143
+ say("The update could not be prepared.");
144
+ return ApplyResult::Failed;
145
+ }
146
+ std::string stamp = env.random_hex ? env.random_hex(4) : std::to_string(env.now_ms());
147
+ auto park_path = [&](const std::string &name) { return fsu::join(old, name + ".ap-old-" + stamp); };
148
+
149
+ std::string target = fsu::join(install, plugin);
150
+ std::string parked; // where the previous version went ("" if there was none)
151
+ bool failed = false;
152
+ if (!fsu::exists(target)) {
153
+ failed = !fsu::rename(fresh, target);
154
+ } else if (fsu::swap(fresh, target)) {
155
+ parked = park_path(plugin);
156
+ if (!fsu::rename(fresh, parked)) parked = fresh; // fresh now holds the previous version
157
+ } else {
158
+ parked = park_path(plugin);
159
+ if (!fsu::rename(target, parked)) {
160
+ parked.clear();
161
+ failed = true;
162
+ } else if (!fsu::rename(fresh, target)) {
163
+ fsu::rename(parked, target);
164
+ parked.clear();
165
+ failed = true;
166
+ }
167
+ }
168
+ // Other plugins of this OS in the folder (the product's previous name): park them.
169
+ std::vector<std::pair<std::string, std::string>> removed;
170
+ if (!failed) {
171
+ for (const auto &name : fsu::list(install)) {
172
+ if (name == plugin || name == WORK_DIR || name == OLD_DIR || !is_os_plugin(name, s.os)) continue;
173
+ std::string to = park_path(name);
174
+ if (!fsu::rename(fsu::join(install, name), to)) { failed = true; break; }
175
+ removed.emplace_back(name, to);
176
+ }
177
+ if (failed) {
178
+ // Roll everything back, including the plugin itself.
179
+ for (auto it = removed.rbegin(); it != removed.rend(); ++it) fsu::rename(it->second, fsu::join(install, it->first));
180
+ fsu::rename(target, fsu::join(work, plugin + ".rollback"));
181
+ if (!parked.empty()) fsu::rename(parked, target);
182
+ }
183
+ }
184
+ if (failed) {
185
+ fsu::remove_all(work);
186
+ fsu::remove_all(old);
187
+ say("The update could not be installed; the previous version was restored.");
188
+ return ApplyResult::Failed;
189
+ }
190
+ fsu::remove_all(work);
191
+ fsu::remove_all(old); // Windows: a renamed loaded .aex stays until the next launch's cleanup
192
+ fsu::remove_all(paths.staged);
193
+ fsu::remove_file(paths.staged_meta);
194
+ if (error) error->clear();
195
+ if (env.log) env.log(1, "Update " + staged_version + " installed; it loads the next time After Effects starts.");
196
+ return ApplyResult::Ok;
197
+ }
198
+
199
+ } // namespace apn