premiere-pro-mcp 1.18.6 → 1.19.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 (117) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +26 -18
  3. package/after-effects-cep-plugin/CSXS/manifest.xml +27 -2
  4. package/after-effects-cep-plugin/main.js +14 -2
  5. package/artifacts/MCPBridgeCEP.zxp +0 -0
  6. package/cep-plugin/CSXS/manifest.xml +3 -3
  7. package/cep-plugin/index.html +1 -1
  8. package/cep-plugin/main.js +9 -2
  9. package/cep-plugin/updater.cjs +1 -1
  10. package/dist/ai/film-editorial.js +15 -4
  11. package/dist/ai/reaction-shorts.js +73 -1
  12. package/dist/bridge/after-effects-bridge.js +4 -0
  13. package/dist/bridge/after-effects-script-builder.js +6 -2
  14. package/dist/bridge/file-bridge.d.ts +8 -0
  15. package/dist/bridge/file-bridge.js +55 -24
  16. package/dist/bridge/script-builder.d.ts +2 -3
  17. package/dist/bridge/script-builder.js +1407 -64
  18. package/dist/bridge/script-size.d.ts +3 -0
  19. package/dist/bridge/script-size.js +7 -0
  20. package/dist/bridge/undo-tracking.d.ts +3 -0
  21. package/dist/bridge/undo-tracking.js +16 -0
  22. package/dist/index.js +1 -1
  23. package/dist/intake/project-intake.js +4 -1
  24. package/dist/resources/extendscript-reference.js +6 -4
  25. package/dist/security/capabilities.js +29 -2
  26. package/dist/server.d.ts +14 -0
  27. package/dist/server.js +96 -8
  28. package/dist/tool-capability-report.js +15 -1
  29. package/dist/tools/adjacent-media-bounds.d.ts +12 -0
  30. package/dist/tools/adjacent-media-bounds.js +114 -0
  31. package/dist/tools/advanced.d.ts +29 -3
  32. package/dist/tools/advanced.js +529 -447
  33. package/dist/tools/audio.d.ts +32 -0
  34. package/dist/tools/audio.js +223 -74
  35. package/dist/tools/av-settings.js +1 -0
  36. package/dist/tools/clipboard.d.ts +8 -1
  37. package/dist/tools/clipboard.js +61 -71
  38. package/dist/tools/competitor-gaps.js +185 -46
  39. package/dist/tools/discovery.js +4 -3
  40. package/dist/tools/edit-plan-token-store.d.ts +37 -0
  41. package/dist/tools/edit-plan-token-store.js +94 -0
  42. package/dist/tools/edit-plans.d.ts +110 -14
  43. package/dist/tools/edit-plans.js +183 -20
  44. package/dist/tools/editor-requests.js +54 -19
  45. package/dist/tools/effects.d.ts +4 -1
  46. package/dist/tools/effects.js +150 -116
  47. package/dist/tools/encoder-formats.d.ts +8 -0
  48. package/dist/tools/encoder-formats.js +59 -0
  49. package/dist/tools/export.d.ts +23 -0
  50. package/dist/tools/export.js +195 -39
  51. package/dist/tools/inspection.js +21 -17
  52. package/dist/tools/interchange-analysis.d.ts +3 -0
  53. package/dist/tools/interchange-analysis.js +46 -4
  54. package/dist/tools/keyframes.js +190 -128
  55. package/dist/tools/markers.js +151 -13
  56. package/dist/tools/mask-fit.js +2 -1
  57. package/dist/tools/media-evidence.d.ts +5 -0
  58. package/dist/tools/media-evidence.js +58 -0
  59. package/dist/tools/media.d.ts +7 -0
  60. package/dist/tools/media.js +141 -18
  61. package/dist/tools/metadata.js +37 -6
  62. package/dist/tools/mogrt-authoring.d.ts +10 -0
  63. package/dist/tools/mogrt-authoring.js +92 -14
  64. package/dist/tools/mogrt-bake.d.ts +39 -0
  65. package/dist/tools/mogrt-bake.js +329 -0
  66. package/dist/tools/mogrt-studio.d.ts +20 -0
  67. package/dist/tools/mogrt-studio.js +18 -6
  68. package/dist/tools/playback.d.ts +13 -2
  69. package/dist/tools/playback.js +22 -3
  70. package/dist/tools/playhead.js +55 -16
  71. package/dist/tools/project-file.d.ts +16 -0
  72. package/dist/tools/project-file.js +120 -0
  73. package/dist/tools/project-intake.js +9 -0
  74. package/dist/tools/project-manager.js +62 -27
  75. package/dist/tools/project.d.ts +28 -5
  76. package/dist/tools/project.js +303 -55
  77. package/dist/tools/reaction-shorts.d.ts +12 -0
  78. package/dist/tools/reaction-shorts.js +2 -0
  79. package/dist/tools/render-handoff.js +18 -3
  80. package/dist/tools/review-plans.d.ts +1 -1
  81. package/dist/tools/ripple-delete-script.d.ts +15 -0
  82. package/dist/tools/ripple-delete-script.js +294 -0
  83. package/dist/tools/scratch-disks.d.ts +22 -0
  84. package/dist/tools/scratch-disks.js +81 -0
  85. package/dist/tools/scripting.js +4 -2
  86. package/dist/tools/selection.js +1 -1
  87. package/dist/tools/sequence.d.ts +8 -0
  88. package/dist/tools/sequence.js +120 -14
  89. package/dist/tools/source-monitor.js +96 -10
  90. package/dist/tools/spot-workflows.js +32 -13
  91. package/dist/tools/stock-titles.d.ts +125 -0
  92. package/dist/tools/stock-titles.js +426 -0
  93. package/dist/tools/text.d.ts +7 -0
  94. package/dist/tools/text.js +93 -17
  95. package/dist/tools/timeline.d.ts +23 -1
  96. package/dist/tools/timeline.js +750 -408
  97. package/dist/tools/track-targeting.d.ts +39 -10
  98. package/dist/tools/track-targeting.js +357 -164
  99. package/dist/tools/tracks.d.ts +5 -0
  100. package/dist/tools/tracks.js +108 -18
  101. package/dist/tools/transitions.d.ts +12 -3
  102. package/dist/tools/transitions.js +159 -63
  103. package/dist/tools/utility.d.ts +20 -1
  104. package/dist/tools/utility.js +417 -129
  105. package/dist/tools/uxp-workflows.js +2 -2
  106. package/dist/tools/workflow-recipes.d.ts +1 -1
  107. package/dist/workflows/agent-instructions.js +1 -0
  108. package/dist/workflows/tool-metadata.d.ts +1 -0
  109. package/dist/workflows/tool-metadata.js +6 -1
  110. package/docs/supported-actions.md +103 -101
  111. package/package.json +9 -9
  112. package/public-product-manifest.json +4 -4
  113. package/scripts/preflight-mcp-registry-submission.mjs +25 -8
  114. package/uxp-plugin/advanced-workflows.cjs +21 -2
  115. package/uxp-plugin/dialogue-workflows.cjs +17 -1
  116. package/uxp-plugin/manifest.json +1 -1
  117. package/uxp-plugin/workflows.cjs +8 -4
package/CHANGELOG.md CHANGED
@@ -6,6 +6,84 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.19.0] - 2026-10-02
10
+
11
+ ### Added
12
+
13
+ - EXPERIMENTAL (QE DOM): `undo`, `redo`, and `multiple_undo` step Premiere's undo stack and verify each step against `undoStackIndex`. They stop when the stack stops moving, refuse on a host without `undoStackIndex`, and refuse when the stack has moved past `expected_undo_stack_index`. A step that moves the stack unexpectedly or leaves the index unreadable is reported as `committed_unverified` with a "Do not retry" warning. (#654)
14
+ - `list_stock_titles` lists Premiere's bundled title templates, and `add_title` places a stock title with the text baked into a copy of the template, reading each line back from its own layer. (#648)
15
+
16
+ ### Changed
17
+
18
+ - `undo`, `redo`, and `multiple_undo` now require `expected_undo_stack_index` and refuse if the host cannot read it. `rename_clip` rejects blank names, `set_playhead_position` rejects negative or nonfinite times and clamps excessive times to the readable sequence end. `set_clip_volume` reports the stored level and applied dB (or an unverified write when readback fails) instead of echoing the requested dB. Edit-plan previews bind tokens to stable project, sequence and target identities; apply rejects changed identities before sequence activation or mutation. Enum validation errors list allowed values. (#725)
19
+ - QE and UXP effect-add receipts, plus UXP effect-removal receipts, now explicitly set `renderVerified: false`. A verified component readback does not establish that Premiere rendered the effect; confirm playback or exported output before delivery (#735).
20
+ - Timeline clip-property writes, splits, and duplicates validate and read back their results. Unreadable requested property state returns `committed_unverified`, lists unverified fields, and reports an unknown timeline-change state instead of claiming nothing was applied (#702).
21
+ - Legacy CEP inserts verify the requested source and insertion time for every media stream, and retain actual placement and undo data when Premiere diverts a stream to another track (#680).
22
+ - Blend modes use the contributor-measured Premiere 25.2.3 index order and verify stored values; keyframe times convert clip-relative seconds through the source in-point, reject retimed clips, and preserve unreadable removal outcomes (#683, #693).
23
+ - Audio level, mute, track lock, and video visibility writes validate arguments and read stored state back. Invalid or unreadable numeric/boolean states cannot certify success (#700, #705).
24
+ - Marker receipts flag unreadable requested colors, and CEP/After Effects string escaping preserves control characters and lone UTF-16 surrogates (#688).
25
+ - Effect removal recognizes the measured Spanish built-in component names and English/Spanish Balance names while retaining the refusal for unconfirmed locales (#692).
26
+ - Experimental QE adjustment-layer creation verifies a new public adjustment-layer clip at the playhead and never retries after a throwing insert. Still-frame imports, edit navigation, and project-item markers read back results; unknown marker colors stay unverified and preserve the persistent undo barrier (#698).
27
+ - Timeline marker requests refuse unsupported clip collections with source-time guidance only when the clip clock is known. Batch markers verify requested fields, keep unknown color state unverified, preserve partial writes and persistent undo guards, and retain uncertainty after a first throwing write (#699).
28
+ - Playhead and sequence marks validate bounds before writing and read back observed positions. Source Monitor edits verify the displayed clip, preserve unreadable post-write outcomes, and detect replacement timing when the same source is overwritten at the same cut (#694).
29
+ - Clip parameter, track rename, selected-clip removal, and project bin moves verify stored results. Numeric property reads retain unreadable outcomes, English and measured Spanish lookups remain supported, and clearing an Out mark cannot be certified without independent full-media duration evidence (#696).
30
+ - Media imports confirm new items for every requested path; labels, footage interpretation, bin names, graphics white luminance, and bin moves read back stored results. Invalid numeric interpretation reads cannot certify writes. CEP relink retains its default refusal and unverified unsafe opt-in (#697).
31
+ - Tools that declare parameters now reject unknown top-level arguments instead of ignoring them. A misspelled or renamed argument (for example `item_ID` for `item_id`, or `time_seconds` for `start_seconds`) used to be dropped silently, and the tool ran with its default. The call now fails before anything is sent to Premiere, and the error names the unknown arguments and lists the ones the tool accepts. Clients or saved prompts that send extra or renamed fields must be updated. Tools whose schema already sets `additionalProperties` or `patternProperties` are unchanged, and UXP tools already rejected unknown properties. Tools that declare no parameters (for example `ping` or `save_project`) still ignore extra arguments. A catalog test fails if a handler reads an argument its schema does not declare.
32
+ - CEP tools that change the project report `undoSteps` and `undoStackIndex` when Premiere recorded undo entries for the call, so an agent can reverse exactly that call. Tracking covers every non-inspect call, never reads the stack for inspect-only tools, and is reset across project switches. UXP tools and multi-command workflows are not counted; the undo descriptions say so and recommend `expected_undo_stack_index`. `has_proxy`, `is_work_area_enabled`, `verify_premiere_connection`, and `match_frame` are classed as inspect. (#654)
33
+ - `export_sequence` refuses an `output_path` that already exists unless `overwrite: true`, checks that the preset's extension matches the output file (the default H.264 preset writes `.mov`), supports in/out ranges, and verifies a non-empty file was written. (#648)
34
+ - `apply_lut` refuses instead of reporting `lutApplied: true`: Premiere 25.2.3 did not render a LUT set by path, and the old result had no readback. (#648)
35
+ - `delete_project_item` removes items through a temporary bin. It refuses when the item is used in a sequence unless `confirm_remove_from_sequences: true`, refuses when a bin with the temporary name exists, and reads back that both are gone. `organize_project_items_uxp` remove is named as the preferred route. (#648)
36
+ - A CEP command whose busy file stops changing now fails with "CEP panel appears stuck; reload it in Premiere" once its timeout passes; the busy file is never deleted automatically. (#648)
37
+
38
+ ### Fixed
39
+
40
+ - UXP point and color parameter reads normalize host arrays and native objects before guarded inspection and write readback. RGB arrays use opaque alpha; absent values report `UXP_VALUE_UNAVAILABLE`, while scalar and malformed values fail with type-specific host errors. Client write/snapshot schemas remain strict objects (#765).
41
+ - CEP enabled-state readers use the TrackItem `disabled` property with a legacy method fallback; this release includes the source fix absent from npm 1.18.6 (#764).
42
+
43
+ - `trim_clip` and `slip_edit` now require physical media-duration evidence from ffprobe before changing source ranges, reject out-of-media edits, and refuse unknown duration or linked partners with different unprobed media. Editable project In/Out marks are not used as media boundaries (#712).
44
+ - CEP marker add, update, and delete attempts protect a project-document-ID undo boundary even when the QE index moves or cannot be read. Undo/redo calls refuse a count that crosses it before stepping; `acknowledge_untracked_markers: true` explicitly permits prior non-marker actions. Barriers persist in the CEP engine through MCP server/helper reloads, but do not account for unobserved UXP/manual UI/other-client marker writes or survive engine resets (#733).
45
+
46
+ - Ingest-transcode, timeline-tab closure, and Creative Cloud Library MOGRT import receipts now distinguish a host request from independent verification. Native errors preserve possible mutation and Library failures include Premiere's error text and actionable causes (#641).
47
+ - `save_project` now refuses an untitled project (no saved path) before calling Premiere, which would otherwise open a blocking Save dialog, and fails when the `.prproj` is missing or empty after `save()`. Verification requires fresh file size or modification evidence; an unchanged existing file or unreadable disk evidence reports `committed_unverified` with `saved: null`.
48
+ - `export_sequence` `range: "work_area"` now fails closed unless the enabled state is readable and true, and finite work-area bounds cover only part of the sequence within its readable duration. Unset or full-sequence work areas previously still called `ENCODE_WORKAREA` and could write the entire timeline as a verified work-area export.
49
+ - `import_media` now rejects missing paths before calling Premiere, avoiding a blocking host dialog that can wedge the CEP bridge (#713).
50
+ - Audio keyframes, ducking, and spot scale motion convert clip-relative offsets through the source in-point and refuse speed-changed clips; audio Level keys use the measured +15 dB-normalized mapping (0 dB = 0.1778, maximum +15 dB = 1), and key readback no longer accepts missing or nonfinite values as verified. (#693, #701)
51
+ - Transition edits validate inputs before dispatch, read stored placements and durations, distinguish partial writes from no-ops, and preserve mutation evidence if native writes or later readback throw. QE transition edits remain experimental. (#704)
52
+
53
+ - `apply_edit_plan` confirmation tokens are now random, issued only by `preview_edit_plan`, valid for 30 minutes, and consumed before the host edit. The private bridge directory retains token state across server restarts, so an applied token cannot be replayed after Undo or a restart (#728).
54
+ - `color_correct` requires Lumetri component and every requested property to read back before reporting success. Missing catalog entries, ignored QE insertion, localized or missing controls, and ignored setters fail honestly; receipts explicitly leave rendered output unverified (#720).
55
+ - `roll_edit` and `slide_edit` require physical source-duration evidence for every affected clip, bind edits to inspected placement/media, and reject invalid source windows or unknown duration before mutation. Slide readback checks exact placement and preserved source windows. Failures after a linked edit starts return `committed_unverified` and warn against retrying (#718, #719).
56
+
57
+ - Audio volume tools now recognize Premiere's Spanish `Volumen` and `Nivel` labels and locale-independent `Internal Volume` component match names, including bulk track volume changes and `setup_ducking` (#710).
58
+ - Legacy CEP `relink_media` now refuses by default because `changeMediaPath` can wedge Premiere on a valid file. Its explicit unsafe opt-in preflights the file and reports only `committed_unverified`; use `relink_offline_media_uxp` for capability checks and media-path/online readback (#729).
59
+ - `add_to_render_queue` can request AME batch start with `start_batch: true` after queueing. This starts all ready AME jobs, including unrelated jobs, so the default remains enqueue only. The receipt reports whether batch start was requested, rejected, or unavailable; it does not claim output creation (#687).
60
+ - `remove_all_effects` and `remove_effect_by_name` keep a graphic's Shape layers by their `AE.ADBE Shape` match name (confirmed on Premiere 25.2.3 with a stock lower third). A localized Shape layer still counts as proof of a localized host, so those hosts still refuse while the Time Remapping and Panner match names are unconfirmed. (#674)
61
+ - `apply_effect` and `apply_audio_effect` verify added components instead of trusting QE; missing readback returns `committed_unverified` with a warning to inspect the clip before retrying. An unchanged component count and undo index report `not_applied`, for example `Time Remapping`, which QE accepts but never adds. (#674)
62
+ - `create_sequence` and `create_sequence_from_preset` normalize `preset_path` to native separators before the QE call and report a missing preset file precisely (#691, #714): QE `newSequence` silently ignores forward-slash paths on Windows, which used to surface as a bare "Failed to create sequence from preset" error.
63
+ - `add_to_timeline_batch` now reads each inserted placement again after all later sync-locked inserts. If a later insert moves or splits an earlier clip, the result reports the final observed span as `committed_unverified` instead of retaining an earlier `verified: true` receipt (#721). Automated tests cover same-track stability and cross-track drift; live Premiere behavior remains to be checked.
64
+ - `create_sequence_from_preset` no longer reports `created: true` when QE `newSequence` leaves the already-active same-name sequence in place. It now snapshots sequence IDs first, the same way `create_sequence` does, and fails closed unless a new ID appears in the project collection.
65
+ - The four dedicated video Motion/Opacity setters recognize the measured Spanish (es-ES) built-in component and property names. Position and Scale retain readback, and Rotation and Opacity now read the stored value back before reporting verified success (#722). Automated tests do not establish live host compatibility.
66
+ - `get_export_file_extension` uses the existing AME preset-folder format map to infer an extension when Premiere returns none, but only for an existing .epr in a recognized folder; its receipt marks the inference as unconfirmed. `set_metadata` rejects unqualified project column names before a write. `add_keyframe` warns when the stored keyframe is outside the clip's visible span (#734).
67
+ - Insert helpers now make an experimental QE pre-razor attempt for a target clip that spans the insertion point before calling `Sequence.insertClip`. Required QE routes and the frame boundary are checked before mutation; the existing adjacent-tail readback still refuses false success if a host displaces a tail (#730). This workaround has only been exercised in automated host fakes and needs live Premiere verification.
68
+ - Marker add, update, and delete receipts now report whether Premiere recorded an undo step. On hosts where marker writes leave the undo index unchanged, `undoTracked: false` warns that Undo would reverse an earlier action instead of the marker (#733).
69
+ - `add_to_timeline` and `insert_from_source` detect a target-track split tail that Premiere moved away from the insert point. They report the changed timeline as `committed_unverified`, including the observed tail position, instead of claiming the insert was verified. The edit still needs inspection or Undo on affected hosts. (#730)
70
+ - Dialogue UXP apply accepts the documented Project/Sequence `guid` property when identity methods are unavailable, while still refusing changed or unreadable project identities before mutation (#685).
71
+ - AME queue handoff normalizes output/preset paths to native separators and checks both host paths before launching the encoder (#711). A job ID remains an unverified handoff.
72
+ - Export preflight and standard-DOM effect enumeration now state their verification limits: passing timeline checks does not verify exporter initialization, and omitted QE effects do not prove absence (#687, #690).
73
+
74
+ - Effect removal classifies built-in components by match name, which is the same in every host language: `AE.ADBE Opacity`, `AE.ADBE Motion`, a graphic's `AE.ADBE Graphic Group`, `AE.ADBE Text`, and `AE.ADBE Shape`, and the `Internal Volume …` / `Internal Channel Volume …` audio intrinsics (seen live on Premiere 25.2.3). #654 refused on every mono clip, because mono clips have Volume but no Channel Volume; that is fixed. A built-in picked by index or name is refused whatever its language. Hosts with localized component names still refuse, now detected by a confirmed built-in match name showing a non-English name, because the match names of Time Remapping and Panner are not confirmed and an unknown one could be removed. (#674, #679)
75
+ - EXPERIMENTAL (QE DOM): `remove_effect`, `remove_effect_by_name`, and `remove_all_effects` could not remove anything on Premiere 25.2, which has no DOM `Component.remove()`. They now fall back to QE removal, resolve every target's removal path before removing anything, never remove built-in components (Motion, Opacity, Volume, Channel Volume), and verify the result against the clip's component list. On hosts whose built-in component names are localized they refuse with nothing removed (#674). (#654)
76
+ - `stop_playback` accepts `target: "timeline" | "source"`. Premiere's ExtendScript and UXP APIs have no documented call that stops only the Source Monitor, so `target: "source"` now returns an error and stops nothing, instead of the argument being ignored and the timeline stopped. (#642)
77
+ - `export_as_project` and `export_as_fcp_xml` no longer report `exported: true` after a host call that writes no file. They require a real parent directory, then fail when the output is missing, empty, or identical to a pre-existing file. (#673)
78
+ - `save_project_as` no longer reports `saved: true` when Premiere leaves a pre-existing `.prproj` unchanged. It snapshots size and modification time first, requires a real parent directory, and fails closed if the file is missing, empty, or identical to the snapshot.
79
+ - User text containing U+2028, U+2029 or other control characters (for example a clip or marker name pasted from a document) no longer breaks the generated ExtendScript. ES3 treats U+2028/U+2029 as line terminators, so Premiere rejected the whole script with "EvalScript error"; `escapeForExtendScript` now writes them as `\uXXXX` escapes.
80
+ - `add_marker` and `update_marker` read the marker's name, comments, color and duration back and report `verified`; when Premiere ignores part of the request the error says the marker was still created or changed (`timelineChanged`). Marker times, durations and color indexes (0-7) are validated before a script is built.
81
+ - `remove_from_timeline` and edit plans ripple through Premiere's sync-locked ripple instead of `TrackItem.remove(true, …)`, which never rippled and left gaps and stray audio while reporting success. Every linked partner is checked for locks before anything is removed. (#648)
82
+ - Linked audio and video stay in sync across razor, trim, slip, roll, slide, and duration edits: partner positions are snapshotted before any write, and a trim or duration change applies the main clip's offset to each partner from that snapshot. (#648)
83
+ - `lift_selection` used a QE method that does not exist; `delete_track` now identifies default track names in any language; `consolidate_and_transfer`, both scratch-disk setters, `import_fcp_xml`, the proxy toggle, `encode_file`, and the After Effects MOGRT export work on 25.2. (#648)
84
+ - `normalize_loudness_file` uses two-pass linear loudnorm at the source sample rate instead of single-pass, which missed its target by 1.6 LU and resampled to 96 kHz. (#648)
85
+ - Two regexes in generated ExtendScript lost their `\s` escapes, so a clip's own frame size was never read (anchor points on a 4K clip in a 1080p sequence used the sequence size) and default track-name matching was loose. `set_clip_properties_batch` also writes Scale Width when Uniform Scale is off, so scaling no longer stretches the picture. (#648 review)
86
+
9
87
  ## [1.18.6] - 2026-09-29
10
88
 
11
89
  ### Fixed
package/README.md CHANGED
@@ -14,7 +14,7 @@ Free, MIT licensed, local-first, and published to npm as [`premiere-pro-mcp`](ht
14
14
 
15
15
  [Website](https://premiere-pro-mcp.com/) · [Recorded demo](https://premiere-pro-mcp.com/demo/) · [Compare servers](https://premiere-pro-mcp.com/compare/) · [Setup guides](https://premiere-pro-mcp.com/blog/how-to-set-up-premiere-pro-mcp/) · [Search tools](https://premiere-pro-mcp.com/tools/) · [Troubleshooting](https://premiere-pro-mcp.com/docs/troubleshooting/) · [Release facts](https://premiere-pro-mcp.com/facts/)
16
16
 
17
- Development source: 384 core tools across 57 modules, 4 resources, and 19 guided workflows. A connected UXP host adds 96 capability-gated tools.
17
+ Development source: 386 core tools across 57 modules, 4 resources, and 19 guided workflows. A connected UXP host adds 96 capability-gated tools.
18
18
 
19
19
  The [completed AE render handoff](docs/after-effects-render-handoff.md) previews and confirms importing one finished render into an existing Premiere bin, with host and file rechecks and an import receipt.
20
20
 
@@ -106,14 +106,14 @@ and repository before configuring a client. The new
106
106
  VS Code, or Codex settings that point directly to this installation. It is a
107
107
  feature included in v1.15.1 and later.
108
108
 
109
- The current source exposes 384 core tools for supported workflow steps spanning the supported ExtendScript, QE DOM, local media and interchange analysis, revisioned project-context retrieval, safe edit-planning, project-intake preview, review handoff, connection verification, and guarded After Effects MOGRT authoring, batch, library, render-queue, inspection, and Premiere-handoff workflows. A compatible, authenticated UXP panel adds 96 documented, capability-gated tools without replacing the production CEP bridge.
109
+ The current source exposes 386 core tools for supported workflow steps spanning the supported ExtendScript, QE DOM, local media and interchange analysis, revisioned project-context retrieval, safe edit-planning, project-intake preview, review handoff, connection verification, and guarded After Effects MOGRT authoring, batch, library, render-queue, inspection, and Premiere-handoff workflows. A compatible, authenticated UXP panel adds 96 documented, capability-gated tools without replacing the production CEP bridge.
110
110
 
111
111
  <a id="latest-release"></a>
112
112
 
113
- ### Latest release: 1.18.5
113
+ ### Latest release: 1.19.0
114
114
 
115
- The published v1.18.5 npm artifact contains **384 core tools**, 382 in its default profile,
116
- and 478 with a compatible UXP connection. The development catalog above can include
115
+ The published v1.19.0 npm artifact contains **386 core tools**, 384 in its default profile,
116
+ and 480 with a compatible UXP connection. The development catalog above can include
117
117
  unreleased work. See the [versioned facts and package provenance](https://premiere-pro-mcp.com/facts/).
118
118
 
119
119
  ### Try a bounded workflow
@@ -162,7 +162,7 @@ if the connection is unavailable.
162
162
  local Premiere processes. See the generated [supported action catalog](docs/supported-actions.md)
163
163
  for individual capability and verification contracts.
164
164
 
165
- See the [v1.18.5 release notes](https://github.com/leancoderkavy/premiere-pro-mcp/releases/tag/v1.18.5)
165
+ See the [v1.19.0 release notes](https://github.com/leancoderkavy/premiere-pro-mcp/releases/tag/v1.19.0)
166
166
  for complete details. Live installation in Premiere Pro still requires host verification.
167
167
 
168
168
  ### Current MCP protocol support
@@ -210,7 +210,7 @@ their bins, media rules, and organization rules before a facility uses one.
210
210
  > ### Install the published package (verify the name)
211
211
  >
212
212
  > ```bash
213
- > npm i -g premiere-pro-mcp@1.18.5
213
+ > npm i -g premiere-pro-mcp@1.19.0
214
214
  > ```
215
215
  >
216
216
  > This repository publishes only **`premiere-pro-mcp`**. A differently named package (`adobe-premiere-pro-mcp`) may also declare a `premiere-pro-mcp` executable. Before configuring a client, confirm:
@@ -218,7 +218,7 @@ their bins, media rules, and organization rules before a facility uses one.
218
218
  > | Check | Expected |
219
219
  > | --- | --- |
220
220
  > | Package name | `premiere-pro-mcp` (not `adobe-premiere-pro-mcp`) |
221
- > | Version | `1.18.5` |
221
+ > | Version | `1.19.0` |
222
222
  > | Homepage / repo | https://premiere-pro-mcp.com/ · https://github.com/leancoderkavy/premiere-pro-mcp |
223
223
  >
224
224
  > ```bash
@@ -231,9 +231,9 @@ their bins, media rules, and organization rules before a facility uses one.
231
231
 
232
232
  ### Easiest supported path: Claude Desktop
233
233
 
234
- 1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.18.5/premiere-pro-mcp-1.18.5.mcpb).
234
+ 1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.19.0/premiere-pro-mcp-1.19.0.mcpb).
235
235
  2. In Claude Desktop, open **Settings > Extensions > Advanced settings > Install Extension**, select the downloaded bundle, and restart Claude Desktop.
236
- 3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.18.5/MCPBridgeCEP.zxp). Open it with your trusted ZXP installer. If your computer has no ZXP installer, use the npm connector installer in **Advanced setup** below.
236
+ 3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.19.0/MCPBridgeCEP.zxp). Open it with your trusted ZXP installer. If your computer has no ZXP installer, use the npm connector installer in **Advanced setup** below.
237
237
  4. Restart Premiere, open a project, then open **Window > Extensions > MCP for Adobe Premiere Pro**.
238
238
  5. In Claude, enter: `Safely check my Premiere connection with verify_premiere_connection. Make no changes.`
239
239
 
@@ -268,9 +268,13 @@ connector still need to be installed separately.
268
268
 
269
269
  - Node.js **20.19 or newer** on Windows or macOS.
270
270
  - Adobe Premiere Pro **2020–2026**. Keep Premiere, the CEP bridge, and your MCP client on the same computer for the recommended local setup.
271
- - Optional: [ffmpeg](https://ffmpeg.org/download.html) on `PATH` for `detect_silence`
272
- (`brew install ffmpeg` on macOS or `winget install Gyan.FFmpeg` on Windows).
273
- The production Docker image already includes it.
271
+ - For `trim_clip` and `slip_edit`, install [FFmpeg](https://ffmpeg.org/download.html)
272
+ with `ffprobe` on `PATH` (`brew install ffmpeg` on macOS or
273
+ `winget install Gyan.FFmpeg` on Windows). These source-range edits are unavailable
274
+ without `ffprobe` and accessible media with readable timestamp clocks: they
275
+ refuse before mutation when physical source bounds cannot be verified.
276
+ FFmpeg also provides the optional `detect_silence` dependency. The production
277
+ Docker image already includes it.
274
278
 
275
279
  #### 1. Install
276
280
 
@@ -533,11 +537,11 @@ From a clone of this repository:
533
537
  ```bash
534
538
  codex plugin marketplace add .
535
539
  codex plugin add premiere-pro@premiere-pro-mcp
536
- npx -y premiere-pro-mcp@1.18.5 --install-cep
540
+ npx -y premiere-pro-mcp@1.19.0 --install-cep
537
541
  ```
538
542
 
539
543
  Restart Premiere Pro and start a new Codex session after installation. The plugin
540
- launches `premiere-pro-mcp@1.18.5` through `npx`; the separate CEP installation is
544
+ launches `premiere-pro-mcp@1.19.0` through `npx`; the separate CEP installation is
541
545
  required because the MCP server communicates with the running Premiere host through
542
546
  the local bridge.
543
547
 
@@ -575,7 +579,7 @@ For Claude Code, add this repository as a marketplace and install the plugin:
575
579
  Then install the Premiere bridge and start a new Claude Code session:
576
580
 
577
581
  ```bash
578
- npx -y premiere-pro-mcp@1.18.5 --install-cep
582
+ npx -y premiere-pro-mcp@1.19.0 --install-cep
579
583
  ```
580
584
 
581
585
  The Claude Code package lives in
@@ -899,7 +903,7 @@ reports revision-bound notes, VFX state, change impact and turnover exceptions.
899
903
  It performs local inspection; host edits and exports use separate guarded tools.
900
904
  See [usage, example and remaining execution adapters](docs/film-editorial-workflows.md).
901
905
 
902
- ## Tools (384 core total; 382 under the default profile; 478 with a connected UXP bridge)
906
+ ## Tools (386 core total; 384 under the default profile; 480 with a connected UXP bridge)
903
907
 
904
908
  The [complete supported-actions catalog](docs/supported-actions.md) lists every
905
909
  registered core tool, the two tools restricted behind explicit `unsafe-script`
@@ -974,6 +978,10 @@ the tables below are a shorter workflow-oriented overview.
974
978
  > or exported frames before delivery. On macOS, AME preset discovery scans each installed app
975
979
  > bundle's `Contents/MediaIO/systempresets`; prefer a Match Source preset for vertical projects.
976
980
 
981
+ > **Host limits:** [The capability ledger](docs/host-api-limitations.md) distinguishes
982
+ > unavailable APIs, unverified host requests, and property/structure readback.
983
+ > A verified parameter or component does not establish rendered appearance.
984
+
977
985
  > **Verified track edits:** `add_track` and `add_tracks` validate requested counts and
978
986
  > return success only when the active sequence's track counts exactly match the request.
979
987
  > `overwrite_clip` validates both selected track indices and confirms the requested source
@@ -1384,7 +1392,7 @@ premiere-pro-mcp/
1384
1392
  ├── src/
1385
1393
  │ ├── index.ts # Entry point — stdio transport setup
1386
1394
  │ ├── http-server.ts # Entry point — HTTP/SSE transport (Fly.io / remote)
1387
- │ ├── server.ts # MCP server — registers 384 tools, filtered by authority profile
1395
+ │ ├── server.ts # MCP server — registers 386 tools, filtered by authority profile
1388
1396
  │ ├── bridge/
1389
1397
  │ │ ├── file-bridge.ts # File-based IPC (write .jsx, poll .json)
1390
1398
  │ │ └── script-builder.ts # ExtendScript generator with ES3 helpers
@@ -1,7 +1,8 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.18.6" ExtensionBundleName="MCP for Adobe After Effects">
2
+ <ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.19.0" ExtensionBundleName="MCP for Adobe After Effects">
3
3
  <ExtensionList>
4
- <Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.18.6"/>
4
+ <Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.19.0"/>
5
+ <Extension Id="com.mcp.aftereffects.bridge.headless" Version="1.19.0"/>
5
6
  </ExtensionList>
6
7
  <ExecutionEnvironment>
7
8
  <HostList>
@@ -34,5 +35,29 @@
34
35
  </UI>
35
36
  </DispatchInfo>
36
37
  </Extension>
38
+ <Extension Id="com.mcp.aftereffects.bridge.headless">
39
+ <DispatchInfo>
40
+ <Resources>
41
+ <MainPath>./index.html</MainPath>
42
+ <ScriptPath>./host.jsx</ScriptPath>
43
+ <CEFCommandLine>
44
+ <Parameter>--allow-file-access-from-files</Parameter>
45
+ <Parameter>--enable-nodejs</Parameter>
46
+ </CEFCommandLine>
47
+ </Resources>
48
+ <Lifecycle>
49
+ <AutoVisible>false</AutoVisible>
50
+ <StartOn>
51
+ <Event>com.adobe.csxs.events.ApplicationActivate</Event>
52
+ <Event>applicationActivate</Event>
53
+ </StartOn>
54
+ </Lifecycle>
55
+ <UI>
56
+ <Type>Custom</Type>
57
+ <Geometry><Size><Height>1</Height><Width>1</Width></Size></Geometry>
58
+ <Icons/>
59
+ </UI>
60
+ </DispatchInfo>
61
+ </Extension>
37
62
  </DispatchInfoList>
38
63
  </ExtensionManifest>
@@ -82,6 +82,11 @@
82
82
  }
83
83
  }
84
84
 
85
+ // The visible panel and the headless auto-start instance both run this file.
86
+ // The rename claim decides which one owns a command; each instance runs one
87
+ // command at a time because After Effects' scripting engine is stateful.
88
+ var commandInFlight = false;
89
+
85
90
  function processOne(fileName) {
86
91
  var source = path.join(tempDir, fileName);
87
92
  var claim = source + "." + engineId + ".claimed";
@@ -90,6 +95,7 @@
90
95
  try { script = fs.readFileSync(claim, "utf8"); } catch (error) { script = null; }
91
96
  try { if (fs.existsSync(claim)) fs.unlinkSync(claim); } catch (ignored) {}
92
97
  if (!script) return;
98
+ commandInFlight = true;
93
99
  var id = fileName.replace("cmd_", "").replace(".jsx", "");
94
100
  var busy = path.join(tempDir, "busy_" + id + ".json");
95
101
  var started = Date.now();
@@ -97,6 +103,7 @@
97
103
  try { fs.writeFileSync(busy, JSON.stringify({ id: id, elapsedMs: Date.now() - started }), "utf8"); } catch (ignored) {}
98
104
  }, 2000);
99
105
  cs.evalScript(script, function (result) {
106
+ commandInFlight = false;
100
107
  clearInterval(busyTimer);
101
108
  try { if (fs.existsSync(busy)) fs.unlinkSync(busy); } catch (ignored) {}
102
109
  writeFileAtomic(path.join(tempDir, "res_" + id + ".json"), replyFor(String(result || "")));
@@ -104,8 +111,9 @@
104
111
  }
105
112
 
106
113
  function processCommands() {
114
+ if (commandInFlight) return;
107
115
  var files = readCommandFiles();
108
- for (var index = 0; index < files.length; index++) processOne(files[index]);
116
+ if (files.length > 0) processOne(files[0]);
109
117
  }
110
118
 
111
119
  function start() {
@@ -130,7 +138,8 @@
130
138
 
131
139
  function stop() {
132
140
  running = false;
133
- heartbeat();
141
+ // Leave the last heartbeat alone: a headless instance may still be running,
142
+ // and its heartbeat must not be overwritten with "waiting" by this panel.
134
143
  if (pollTimer) clearInterval(pollTimer);
135
144
  if (heartbeatTimer) clearInterval(heartbeatTimer);
136
145
  pollTimer = null;
@@ -141,4 +150,7 @@
141
150
  var field = document.getElementById("tempDir");
142
151
  try { field.value = localStorage.getItem("after_effects_mcp_temp_dir") || tempDir; } catch (ignored) { field.value = tempDir; }
143
152
  document.getElementById("toggle").onclick = function () { if (running) stop(); else start(); };
153
+ // Start automatically: the headless instance (StartOn ApplicationActivate) has
154
+ // no one to click Start, so without this the connector never ran unattended.
155
+ setTimeout(start, 500);
144
156
  }());
Binary file
@@ -1,8 +1,8 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.18.6" ExtensionBundleName="MCP for Adobe Premiere Pro">
2
+ <ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.19.0" ExtensionBundleName="MCP for Adobe Premiere Pro">
3
3
  <ExtensionList>
4
- <Extension Id="com.mcp.premiere.bridge.panel" Version="1.18.6"/>
5
- <Extension Id="com.mcp.premiere.bridge.headless" Version="1.18.6"/>
4
+ <Extension Id="com.mcp.premiere.bridge.panel" Version="1.19.0"/>
5
+ <Extension Id="com.mcp.premiere.bridge.headless" Version="1.19.0"/>
6
6
  </ExtensionList>
7
7
  <ExecutionEnvironment>
8
8
  <HostList>
@@ -81,7 +81,7 @@
81
81
  <section class="update-section" aria-live="polite">
82
82
  <div class="update-copy">
83
83
  <span class="section-label">MCP updates</span>
84
- <strong id="updateTitle">Version 1.18.6</strong>
84
+ <strong id="updateTitle">Version 1.19.0</strong>
85
85
  <span id="updateDetail">Checking the global MCP server and connector release…</span>
86
86
  </div>
87
87
  <button id="btnUpdate" class="button button-update" onclick="handleUpdateClick()" type="button" aria-describedby="updateDetail" disabled>
@@ -358,8 +358,15 @@ function processOneCommand(cmdFileName) {
358
358
  }
359
359
  } catch (e) {
360
360
  // If result isn't JSON, wrap it
361
- if (result && result.indexOf("Error") === 0) {
362
- response = JSON.stringify({ success: false, error: result });
361
+ // "EvalScript error." is CEP's generic message when the host script throws
362
+ // outside its own try/catch or does not parse. It is a failure, not data.
363
+ if (result && (result.indexOf("Error") === 0 || /^EvalScript error/i.test(result))) {
364
+ response = JSON.stringify({
365
+ success: false,
366
+ error: /^EvalScript error/i.test(result)
367
+ ? "Premiere could not run the generated script (" + result + "). No result was produced; treat the command as failed."
368
+ : result,
369
+ });
363
370
  log("Result: " + result, "err");
364
371
  } else {
365
372
  response = JSON.stringify({ success: true, data: result });
@@ -7,7 +7,7 @@
7
7
  })(this, function () {
8
8
  "use strict";
9
9
 
10
- var CURRENT_VERSION = "1.18.6";
10
+ var CURRENT_VERSION = "1.19.0";
11
11
  var PACKAGE_NAME = "premiere-pro-mcp";
12
12
  var LATEST_PACKAGE_API = "https://registry.npmjs.org/" + PACKAGE_NAME;
13
13
  var LATEST_RELEASE_API =
@@ -126,10 +126,21 @@ export function buildFilmEditorialPacket(document, raw) {
126
126
  const source = sources.get(occurrence.sourceId);
127
127
  const item = coverage.get(occurrence.coverageId);
128
128
  const record = evidence.get(occurrence.evidenceId);
129
- if (!source || !item || item.sourceId !== occurrence.sourceId || !record || record.kind !== "timeline" ||
130
- record.timelineItemId !== occurrence.id || record.sourceId !== occurrence.sourceId || record.sequenceId !== occurrence.sequenceId ||
131
- record.timelineRevision !== occurrence.timelineRevision || (record.sourceRevision !== undefined && record.sourceRevision !== source.sourceRevision))
132
- throw new Error(`Occurrence identity/revision mismatch: ${occurrence.id}`);
129
+ // Name the failing check: the combined "identity/revision mismatch" gave callers
130
+ // nothing to fix (live: occurrence id must be the evidence's timelineItemId).
131
+ const mismatch = !source ? `sourceId ${occurrence.sourceId} is not in sources`
132
+ : !item ? `coverageId ${occurrence.coverageId} is not in coverage`
133
+ : item.sourceId !== occurrence.sourceId ? `coverage ${item.id} is for source ${item.sourceId}, not ${occurrence.sourceId}`
134
+ : !record ? `evidenceId ${occurrence.evidenceId} is not in the captured context`
135
+ : record.kind !== "timeline" ? `evidence ${occurrence.evidenceId} is a ${record.kind} record, not a timeline record`
136
+ : record.timelineItemId !== occurrence.id ? `id must be the evidence's timelineItemId (${record.timelineItemId})`
137
+ : record.sourceId !== occurrence.sourceId ? `evidence source is ${record.sourceId}, not ${occurrence.sourceId}`
138
+ : record.sequenceId !== occurrence.sequenceId ? `evidence sequence is ${record.sequenceId}, not ${occurrence.sequenceId}`
139
+ : record.timelineRevision !== occurrence.timelineRevision ? `timelineRevision must be the evidence's timelineItemRevision (${record.timelineRevision})`
140
+ : record.sourceRevision !== undefined && record.sourceRevision !== source.sourceRevision ? `evidence sourceRevision ${record.sourceRevision} differs from the source's ${source.sourceRevision}`
141
+ : null;
142
+ if (mismatch || !source || !item || !record)
143
+ throw new Error(`Occurrence identity/revision mismatch: ${occurrence.id}: ${mismatch}`);
133
144
  validRange(occurrence.sourceRange, occurrence.id, source.durationFrames);
134
145
  validRange(occurrence.timelineRange, occurrence.id);
135
146
  const secondsPerFrame = occurrence.timebase.denominator / occurrence.timebase.numerator;
@@ -86,6 +86,8 @@ function joinCueText(words) {
86
86
  return sentenceCase(words.map((word) => word.text.trim()).filter(Boolean).join(" "));
87
87
  }
88
88
  function renderCueText(cue, mergeGap) {
89
+ if (cue.continuation)
90
+ return cue.words.map((word) => word.text.trim()).filter(Boolean).join(" ");
89
91
  if (cue.merged_cue_count <= 1)
90
92
  return joinCueText(cue.words);
91
93
  const parts = [];
@@ -203,6 +205,72 @@ function mergeMicroCues(cues, minSoloSeconds, combineGapSeconds) {
203
205
  }
204
206
  return merged;
205
207
  }
208
+ /**
209
+ * Split cues that are too long to read as one caption. Live test: a welcome
210
+ * speech produced 139- and 150-character cues held for 10 s. Breaks prefer the
211
+ * end of a clause (punctuation) once the part is reasonably full, and each part
212
+ * keeps its own word timings.
213
+ */
214
+ const DANGLING_WORDS = new Set([
215
+ "a", "an", "the", "to", "of", "in", "on", "at", "for", "with", "by", "from", "and", "or", "but", "as",
216
+ "that", "this", "our", "my", "your", "their", "its", "i", "we", "is", "are", "be",
217
+ ]);
218
+ function splitLongCues(cues, maxChars, maxSeconds) {
219
+ const out = [];
220
+ for (const cue of cues) {
221
+ if (joinCueText(cue.words).length <= maxChars && cue.end_seconds - cue.start_seconds <= maxSeconds + EPSILON) {
222
+ out.push(cue);
223
+ continue;
224
+ }
225
+ // Aim for evenly sized parts instead of filling each to the limit, so a
226
+ // 140-character sentence becomes two ~70-character captions, not 83 + 57.
227
+ const totalChars = joinCueText(cue.words).length;
228
+ const parts = Math.max(Math.ceil(totalChars / maxChars), Math.ceil((cue.end_seconds - cue.start_seconds) / maxSeconds));
229
+ const targetChars = Math.min(maxChars, Math.ceil(totalChars / parts) + 8);
230
+ let part = [];
231
+ let clauseBreak = -1;
232
+ let first = true;
233
+ const flush = (words) => {
234
+ if (!words.length)
235
+ return;
236
+ out.push({
237
+ ...cue,
238
+ words,
239
+ start_seconds: words[0].start_seconds,
240
+ end_seconds: words[words.length - 1].end_seconds,
241
+ continuation: cue.continuation || !first,
242
+ });
243
+ first = false;
244
+ };
245
+ for (const word of cue.words) {
246
+ const candidate = [...part, word];
247
+ const tooLong = joinCueText(candidate).length > targetChars;
248
+ const tooSlow = part.length > 0 && word.end_seconds - part[0].start_seconds > maxSeconds + EPSILON;
249
+ if (part.length && (tooLong || tooSlow)) {
250
+ if (clauseBreak >= 0 && joinCueText(part.slice(0, clauseBreak + 1)).length >= targetChars * 0.5) {
251
+ flush(part.slice(0, clauseBreak + 1));
252
+ part = part.slice(clauseBreak + 1);
253
+ }
254
+ else {
255
+ // Never leave a caption hanging on a function word ("…welcome to").
256
+ let keep = part.length;
257
+ while (keep > 1 && DANGLING_WORDS.has(part[keep - 1].text.trim().toLowerCase()))
258
+ keep -= 1;
259
+ flush(part.slice(0, keep));
260
+ part = part.slice(keep);
261
+ }
262
+ clauseBreak = -1;
263
+ part.forEach((kept, index) => { if (/[,.;:!?]$/.test(kept.text.trim()))
264
+ clauseBreak = index; });
265
+ }
266
+ part.push(word);
267
+ if (/[,.;:!?]$/.test(word.text.trim()))
268
+ clauseBreak = part.length - 1;
269
+ }
270
+ flush(part);
271
+ }
272
+ return out;
273
+ }
206
274
  function assignStacks(cues, warnings) {
207
275
  const ordered = [...cues].sort((left, right) => left.start_seconds - right.start_seconds || left.end_seconds - right.end_seconds);
208
276
  let stacked = 0;
@@ -256,6 +324,7 @@ export function planReactionCaptions(input) {
256
324
  rejectUnknownKeys(input, [
257
325
  "word_timeline", "speaker_palette", "shot_changes", "frame_rate",
258
326
  "merge_gap_seconds", "min_solo_cue_seconds", "combine_gap_seconds", "min_hold_seconds",
327
+ "max_cue_chars", "max_cue_seconds",
259
328
  ], "arguments");
260
329
  const timeline = validateWordTimeline(input.word_timeline);
261
330
  const palette = parseSpeakerPalette(input.speaker_palette);
@@ -265,9 +334,11 @@ export function planReactionCaptions(input) {
265
334
  const minSolo = boundedNumber(input.min_solo_cue_seconds, "min_solo_cue_seconds", 0.15, 3, 0.45);
266
335
  const combineGap = boundedNumber(input.combine_gap_seconds, "combine_gap_seconds", 0, 5, 0.8);
267
336
  const minHold = boundedNumber(input.min_hold_seconds, "min_hold_seconds", 0.15, 3, 0.35);
337
+ const maxChars = boundedNumber(input.max_cue_chars, "max_cue_chars", 16, 400, 84, true);
338
+ const maxSeconds = boundedNumber(input.max_cue_seconds, "max_cue_seconds", 1, 30, 6);
268
339
  const warnings = [];
269
340
  const grouped = groupSpeakerCues(timeline.words, mergeGap);
270
- const combined = mergeMicroCues(grouped, minSolo, combineGap);
341
+ const combined = splitLongCues(mergeMicroCues(grouped, minSolo, combineGap), maxChars, maxSeconds);
271
342
  alignCuesToShots(combined, shots, minHold, warnings);
272
343
  const stackedOverlapCount = assignStacks(combined, warnings);
273
344
  const uncertain = new Set();
@@ -310,6 +381,7 @@ export function planReactionCaptions(input) {
310
381
  "Overlapping different-speaker cues stack: the earlier in-point stays on the main (bottom) line; later talkers move up one slot.",
311
382
  "A caption never receives a guessed color. Missing palette entries stay uncertain until the editor names the speaker.",
312
383
  "Labeled shot changes clamp a matching speaker's in-point forward when that still leaves a readable hold.",
384
+ "Cues longer than max_cue_chars (default 84, two 42-character lines) or max_cue_seconds (default 6) split at word boundaries, preferring the end of a clause.",
313
385
  ],
314
386
  applied: false,
315
387
  apply_boundary: applyBoundary(),
@@ -25,6 +25,10 @@ export function sendAfterEffectsCommand(script, options = {}) {
25
25
  ...afterEffectsOptions,
26
26
  tempDir: getAfterEffectsTempDir(),
27
27
  helpers: AFTER_EFFECTS_BRIDGE_HELPERS,
28
+ hostLabel: "After Effects",
29
+ // The bundled AE panel always publishes a heartbeat; none means AE or the
30
+ // panel is not running, so fail now rather than after the full timeout.
31
+ requireHeartbeat: true,
28
32
  });
29
33
  }
30
34
  //# sourceMappingURL=after-effects-bridge.js.map
@@ -4,6 +4,7 @@
4
4
  * assumptions from a different host in AE's long-lived ExtendScript engine.
5
5
  */
6
6
  import { createHash } from "node:crypto";
7
+ import { escapeUnsafeLiteralCharacters } from "./script-builder.js";
7
8
  const HELPERS = `
8
9
  function __aeJsonStringify(value) {
9
10
  if (value === null) return "null";
@@ -46,12 +47,15 @@ export function buildAfterEffectsScript(code) {
46
47
  return `(function() {\n try {\n ${code}\n } catch (error) {\n return __aeError(error.toString());\n }\n})();`;
47
48
  }
48
49
  export function escapeForAfterEffects(value) {
49
- return value
50
+ // Same gap as escapeForExtendScript: U+2028/U+2029 end an ES3 string
51
+ // literal, and control characters or lone surrogates do not survive the
52
+ // command file, so they are written as \uXXXX escapes last.
53
+ return escapeUnsafeLiteralCharacters(value
50
54
  .replace(/\\/g, "\\\\")
51
55
  .replace(/"/g, '\\"')
52
56
  .replace(/'/g, "\\'")
53
57
  .replace(/\n/g, "\\n")
54
58
  .replace(/\r/g, "\\r")
55
- .replace(/\t/g, "\\t");
59
+ .replace(/\t/g, "\\t"));
56
60
  }
57
61
  //# sourceMappingURL=after-effects-script-builder.js.map
@@ -7,6 +7,8 @@ export declare const BRIDGE_HEARTBEAT_STALE_MS = 3000;
7
7
  export interface BridgeOptions {
8
8
  tempDir?: string;
9
9
  timeoutMs?: number;
10
+ /** Host application named in timeout guidance (default "Premiere Pro"). */
11
+ hostLabel?: string;
10
12
  /**
11
13
  * Host-specific bootstrap contract. Premiere is the default; companion
12
14
  * bridges (such as After Effects) supply their own narrow helper surface.
@@ -18,6 +20,12 @@ export interface BridgeOptions {
18
20
  * A missing heartbeat remains compatible with older installed connectors.
19
21
  */
20
22
  failFastOnUnreadyHeartbeat?: boolean;
23
+ /**
24
+ * Treat a missing heartbeat as "connector not running" and fail at once.
25
+ * For connectors that always publish one (the bundled After Effects panel),
26
+ * so a closed host is reported immediately instead of after the full timeout.
27
+ */
28
+ requireHeartbeat?: boolean;
21
29
  }
22
30
  export interface BridgeHelpers {
23
31
  source: string;