premiere-pro-mcp 1.20.0 → 1.22.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 (66) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +15 -11
  3. package/after-effects-cep-plugin/CSXS/manifest.xml +3 -3
  4. package/after-effects-cep-plugin/bridge-directory-security.cjs +1 -1
  5. package/artifacts/MCPBridgeCEP.zxp +0 -0
  6. package/cep-plugin/CSXS/manifest.xml +3 -3
  7. package/cep-plugin/bridge-directory-security.cjs +1 -1
  8. package/cep-plugin/index.html +1 -1
  9. package/cep-plugin/updater.cjs +1 -1
  10. package/dist/bridge/file-bridge.js +4 -1
  11. package/dist/bridge/script-builder.js +188 -26
  12. package/dist/bridge/uxp-websocket-bridge.js +4 -2
  13. package/dist/diagnostics.js +2 -1
  14. package/dist/doctor-repairs.js +5 -0
  15. package/dist/env-config.d.ts +2 -0
  16. package/dist/env-config.js +12 -0
  17. package/dist/index.js +28 -15
  18. package/dist/tools/adjacent-media-bounds.js +8 -2
  19. package/dist/tools/advanced.js +172 -26
  20. package/dist/tools/effects.js +56 -23
  21. package/dist/tools/export.d.ts +5 -0
  22. package/dist/tools/export.js +119 -12
  23. package/dist/tools/keyframes.js +20 -7
  24. package/dist/tools/markers.js +45 -16
  25. package/dist/tools/media.js +55 -21
  26. package/dist/tools/mogrt-bake.d.ts +2 -0
  27. package/dist/tools/mogrt-bake.js +42 -3
  28. package/dist/tools/playhead.js +86 -20
  29. package/dist/tools/project-item-start-time.d.ts +6 -0
  30. package/dist/tools/project-item-start-time.js +23 -0
  31. package/dist/tools/project.js +27 -3
  32. package/dist/tools/selection.js +104 -26
  33. package/dist/tools/sequence.js +9 -1
  34. package/dist/tools/source-monitor.js +24 -12
  35. package/dist/tools/stock-titles.js +15 -4
  36. package/dist/tools/timeline.js +87 -11
  37. package/dist/tools/track-targeting.js +115 -54
  38. package/dist/tools/transitions.js +23 -8
  39. package/dist/tools/utility.js +103 -15
  40. package/dist/tools/uxp-advanced-workflows.d.ts +38 -13
  41. package/dist/tools/uxp-advanced-workflows.js +89 -34
  42. package/dist/tools/uxp-apply-snapshot.d.ts +20 -1
  43. package/dist/tools/uxp-apply-snapshot.js +82 -5
  44. package/dist/tools/uxp-next-workflows.d.ts +29 -0
  45. package/dist/tools/uxp-next-workflows.js +57 -17
  46. package/dist/tools/uxp-sequence-preview-frame-workflows.d.ts +4 -0
  47. package/dist/tools/uxp-sequence-preview-frame-workflows.js +3 -1
  48. package/dist/tools/uxp-timeline-source-label-workflows.d.ts +4 -0
  49. package/dist/tools/uxp-timeline-source-label-workflows.js +3 -1
  50. package/dist/tools/uxp-workflows.d.ts +25 -6
  51. package/dist/tools/uxp-workflows.js +51 -29
  52. package/dist/tools/uxp.d.ts +139 -46
  53. package/dist/tools/uxp.js +62 -32
  54. package/dist/uxp-lazy-start.d.ts +16 -0
  55. package/dist/uxp-lazy-start.js +67 -0
  56. package/docs/supported-actions.md +27 -27
  57. package/package.json +1 -1
  58. package/public-product-manifest.json +1 -1
  59. package/uxp-plugin/advanced-workflows.cjs +79 -54
  60. package/uxp-plugin/commands.cjs +91 -34
  61. package/uxp-plugin/frame-time.cjs +26 -0
  62. package/uxp-plugin/index.cjs +1 -1
  63. package/uxp-plugin/index.html +1 -1
  64. package/uxp-plugin/manifest.json +1 -1
  65. package/uxp-plugin/slide-workflows.cjs +24 -12
  66. package/uxp-plugin/slip-workflows.cjs +32 -20
package/CHANGELOG.md CHANGED
@@ -6,6 +6,60 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.22.0] - 2026-10-08
10
+
11
+ ### Fixed
12
+
13
+ - The Windows private-directory ACL check waits up to 30 seconds for `powershell.exe` instead of 5, in the server and in both CEP panels. On a loaded machine (including CI runners with coverage) PowerShell could take longer than 5 seconds to start, which made the check throw and blocked the bridge. A timeout still fails closed: the directory is not used unless the ACL check succeeds.
14
+ - Effect writes, stabilization, smart-bin creation, proxy detachment, folder imports, sequence creation, marker deletion and clip selection now distinguish requested edits from host readback, report ignored writes as failures and unreadable results as `committed_unverified`. Selection counts describe applied states; stabilization does not claim a completed analysis. CEP selected-clip removal refuses ripple requests before deleting because clip absence cannot verify gap closure.
15
+ - `set_sequence_display_format` writes Premiere's display codes (video 100-113, audio 200-201) and maps the older 0-11 / 0-1 inputs onto them. Premiere 26.5.2 stores and reads back any number, so the old values "verified" but did not select the format (2 formatted as non-drop timecode, 102 as drop-frame). `set_sequence_frame_rate` now reports the clips Premiere re-snaps to the new frame grid (`clipsMoved`, `maxShiftSeconds`, `movedClips`) with a warning that changing back does not restore them.
16
+ - `create_subclip` now returns the new subclip's `nodeId` and reads its stored range back. On Premiere 26.5.2 a soft-boundary subclip's `getInPoint`/`getOutPoint` read the whole media, so the range is read from the subclip's project metadata (`VideoInPoint`/`VideoOutPoint` timecode and frame rate). The result reports requested and observed in/out seconds, with `outcome: "verified"` when both are within one media frame of the request and `committed_unverified` otherwise or when the metadata cannot be read.
17
+ - `add_title` reports the start Premiere stored for the placed graphic. Premiere snaps it to the frame grid (a requested 5 s lands at 5.005 s at 29.97 fps), so `startSeconds` and the new `appliedStartSeconds` come from the placed clip, and the request is kept as `requestedStartSeconds`.
18
+ - `update_marker` reports the stored marker start as `timeSeconds` (for example 7.5075 s rather than the requested 7.5 s) and adds `requestedSeconds`.
19
+ - `manage_proxies` with `action: "toggle"` can set a known proxy display state. The action changes Premiere's application-wide proxy display (`app.setEnableProxies`), not the given item, and it only flipped the current state, so on Premiere 26.5.2 an agent could not ask for proxies on or off and two calls undid each other. It now accepts `enabled: true | false`, writes only when the state differs, and verifies by reading `app.getEnableProxies()` back; a host that ignores the write is reported as a failure. Omitting `enabled` still flips the state. Results report `previousEnabled`, `proxiesEnabled`, `changed` and `mode` (`set` or `flip`), and the description now says the action is application-wide.
20
+ - Still-image mark clearing now identifies unreadable still duration explicitly; `set_clip_start_time` shares the verified implementation of `set_start_time` and is documented as its alias. Colour selection states that it uses the source project item label because CEP has no timeline clip-label getter. Baked title-cache cleanup retains the newest 50 matching files and leaves unrelated directory entries untouched.
21
+
22
+ ## [1.21.0] - 2026-10-07
23
+
24
+ ### Fixed
25
+
26
+ - Effect-property lookup errors now list up to 25 non-empty display names; repeated names include their `property_index`, helping identify controls such as Gaussian Blur's `Amount` without guessing.
27
+
28
+ - `add_to_render_queue` now asks Media Encoder to render the active sequence's In/Out range instead of the entire sequence.
29
+ - `set_frame_blend` and `set_time_interpolation` report `committed_unverified` because Premiere exposes no readback for those QE writes; thrown writes warn that the mutation outcome is unknown.
30
+
31
+ - UXP inspect snapshots now include every expected guard needed by their matching apply or update action, including sequence identity, selection, display-format, work-area, playhead, and parameter animation state. Sequence-range inspection treats Premiere's negative In/Out sentinels as unset and returns explicit set flags so an unset range can be reviewed and updated safely.
32
+
33
+ - CEP `set_work_area` turns on a disabled work-area bar through the public Sequence API before writing, and checks the bar state and stored points afterwards. Premiere 26.5.2 ignores the CEP work-area point setters; when the points read back unchanged, the tool now fails, turns the bar back off if it turned it on, and points to the UXP `set_work_area`. Unreadable or partial writes still report `committed_unverified`. Work-area reads keep an unreadable bar state as unknown instead of treating it as disabled.
34
+ - `slide_edit` accepts source/timeline duration drift up to half a sequence frame or exactly one frame of host rounding, reports the signed drift, and continues to refuse larger or intermediate mismatches. Neighbour source and cut readback checks remain exact.
35
+
36
+ - `lift_selection` and `extract_selection` no longer cut one extra frame. Premiere 26.5.2 stores sequence marks floored to the 48 kHz sample grid, so at 29.97 an In on a frame was stored just before it: video lost the frame before the range and Extract rippled one frame short. Inexact marks are re-written on their exact frame through QE first (`marksRewritten`), and coverage and ripple checks now allow half a frame instead of a full frame, so a lost frame is reported instead of verified.
37
+ - `set_sequence_in_out_points` writes each mark on the first audio sample at or after its frame start. Premiere 26.5.2 stores sequence marks floored to the 48 kHz sample grid and renders an In/Out export from the frame holding the In to the frame holding the Out, so at 29.97 an export of a marked range started and ended one frame early on four of every five frames.
38
+
39
+ - `clear_sequence_in_out` now writes Premiere's unset value, so cleared points read back as unset like a new sequence, instead of writing 0 and the sequence end. On 26.5.2 the old method failed its own readback because the Out point is stored rounded to an audio sample. It reports each point after the change and leaves the point it was not asked to clear unchanged.
40
+
41
+ - `replace_clip` checks, before removing the original clip, that the replacement accepts the source range, and refuses an item that also carries the other media type, because Premiere 26.5.2's `Track.overwriteClip` also places that media on the matching track and overwrites the clips there. A replacement at another frame rate now fills the span: item marks may land up to one media frame early, and the placed end is extended to the original end (`endCorrected`). `unnest_sequence` runs the same source-range check before removing the nested clip.
42
+
43
+ - `link_selection` and `unlink_selection` read the clip links back instead of always reporting success. On Premiere 26.5.2 `unlinkSelection()` returns false and changes nothing unless every clip of the linked group is selected; `unlink_selection` now refuses such a selection before calling Premiere and names the partner clips to add. An already-unlinked selection is reported as `alreadyUnlinked`, and `link_selection` needs at least two selected clips.
44
+
45
+ - `set_item_in_out` verifies a mark within one media frame and reports the requested and applied seconds. Premiere 26.5.2 snaps project-item video marks to the media's own frame grid, so 0.5 s on 23.976 media (applied 0.4588 s) used to fail with a partial-state error. `clear_item_in_out` now reads MediaDuration as nominal-rate timecode ("23.98 fps" is 24000/1001, and 29.97 media can use drop-frame `;` timecode), so clearing 23.976 and 29.97 media verifies instead of returning `committed_unverified`. `set_source_in_out` gets the same media-frame tolerance and reports applied seconds.
46
+
47
+ - `add_transition` snaps `cut_point_seconds` to the sequence frame grid and uses the nearest clip edge within half a frame. It used to require the edge within one tick, so a cut point given to a few decimals (22.5892 s for the cut at 22.5892333 s at 29.97) was refused. The result reports the requested and applied cut seconds.
48
+
49
+ - `set_zero_point` writes the frame-snapped zero point and reads it back instead of always returning `set: true`. It reports the requested, applied, and previous seconds, and refuses a negative or non-finite `start_seconds` before contacting Premiere.
50
+
51
+ - Temporary project-item mark writes and failed source-mark updates preserve soft-subclip ranges using private metadata when the DOM reports the whole media. Restore reads support fractional and drop-frame video timecodes and audio sample timecodes; unreadable ranges refuse before a write.
52
+
53
+ - `duplicate_clip` now copies clips whose media frame rate differs from the sequence's. Premiere floors project-item marks to the media's own frame grid, so on Premiere 26.5.2 a 23.976 fps clip in a 29.97 fps sequence was copied with its source in one sequence frame early (19.9533 s instead of 19.9866 s) and reported `committed_unverified`. The tool now writes each mark a quarter media frame late, slips a copy whose source in is still less than one media frame off back to the original's source in, and accepts a mixed-rate source in within half a media frame. Results add `sourceIn` with the requested and applied seconds, `corrected` and `snappedToMediaFrame`. Same-rate clips are unchanged. Covered by unit tests with a fake host.
54
+
55
+ - The Claude Desktop extension starts when optional settings are left blank. Desktop can pass an unset `user_config` field through as the literal `${user_config.name}`; `PREMIERE_MCP_PROTOCOL_MODE` and `PREMIERE_UXP_TOKEN` now treat that, and blank values, as unset instead of failing startup with `PREMIERE_MCP_PROTOCOL_MODE must be either auto or legacy` (#828).
56
+
57
+ - The UXP loopback port binds on the first request other than `server/discover`, so Desktop's disposable discover-only probe copy no longer takes port 7777 and pushes the real server to CEP-only tools. A busy port is retried every 3 seconds instead of abandoned, a non-port UXP startup failure is reported without taking the CEP tools down, and the server exits when the client closes stdin (#828).
58
+
59
+ - A UXP bridge token with surrounding whitespace or a trailing newline (for example from `pbcopy`) is trimmed in the server and in the UXP panel instead of failing with an unexplained HTTP 401 (#828).
60
+
61
+ - `--diagnose-cep` repair inside the `.mcpb` bundle, which omits `scripts/`, now says so and points to the signed `MCPBridgeCEP.zxp` or `npx -y premiere-pro-mcp --install-cep` instead of failing on a missing script (#828).
62
+
9
63
  ## [1.20.0] - 2026-10-06
10
64
 
11
65
  ### Added
@@ -19,6 +73,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
19
73
 
20
74
  ### Fixed
21
75
 
76
+ - UXP tools on Premiere 26.5.2, found live on macOS:
77
+ - **Project-item IDs.** The project root and some folders return no `getId` until cast with `ProjectItem.cast`. `inspect_project_tree_uxp` and `inspect_unique_object_identity_uxp` failed, and bin listings reported empty IDs. The advanced-workflow and unique-identity helpers now cast, as the other helpers already did.
78
+ - **Slip, slide and split edits.** Premiere 26.5 applies TrackItem start/end and in/out actions like trims, so each edge action also moves its source point.
79
+ - `slip_track_item_uxp` landed as a move. It now restores the timeline position with a second, measured move (two undo steps).
80
+ - `slide_track_item_uxp` trimmed both neighbours twice. It now sends only the edge actions and writes source points afterwards only when they did not follow.
81
+ - `make_split_edit_uxp` extended the audio twice, and it never checked whether the extension was free. An L-cut into the next audio clip left two items over the same range, and Premiere crashed (heap corruption) while drawing them. It now refuses when the extension would overlap another item on the same track, and it applies the source point only when needed.
82
+ - **`ripple_delete_track_item_uxp`** deleted without rippling when another track had an item over the deleted range, such as a linked audio partner or a spanning adjustment layer. It now refuses before any change and names those items.
83
+ - **Capabilities.** The bridge re-reads the panel's live capabilities before refusing a command that the connect-time handshake marked unsupported, and `get_uxp_capabilities` reports the live list. A panel that connected before a project was open or a workspace was approved hid 25 of 192 commands for the session.
84
+ - **Markers.** `manage_markers_uxp` `add` now forwards and applies `color_index` and verifies every requested field. Previously the colour was dropped while the add reported verified.
85
+ - **Frame export.** `export_frame_uxp` retries with the file extension when Premiere rejects an extension-less name ("File Format is not supported"). It also waits up to 15 s for the PNG, which 26.5.2 writes after the call returns, and it checks for the file by walking the approved workspace folder: a plugin with request-only file access cannot open arbitrary `file:` URLs, so the old check reported a written frame as missing.
86
+ - **Media health.** `maintain_media_health_uxp` treats a single `project_item_id` as a one-item list for inspect, refresh and set_offline.
87
+
22
88
  - `import_media` no longer reports an error when an `.xml`, `.aaf`, `.edl` or `.prproj` file imports correctly. Interchange files create sequences and bins rather than an item with the file's path, so they now return `outcome: "committed_unverified"` with `interchange: true`, which stops retries from duplicating the sequence (#805).
23
89
 
24
90
  - `duplicate_clip` no longer places a copy one frame short. Premiere can snap the source out point a frame early, so on Premiere 26.5.2 a 677-frame clip was copied as 676 frames, and the tool still reported `verified` because it allowed two frames of drift. It now trims each placed copy, and its linked partner, back to the original end, reports `endCorrected`, and verifies start, end and source in-point to within half a frame.
@@ -42,6 +108,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
42
108
 
43
109
  - `navigate_playhead` now reports timecode using Premiere's sequence display format, including drop-frame punctuation.
44
110
 
111
+ - Guarded UXP inspect results for slip, slide, ripple-delete, duplicate, sequence range, work area, preview frame, source label, marker removal, display formats, static effect values, keyframe times, media timing and overrides, selection, and video transitions now include schema-shaped expected objects accepted unchanged by apply. The converter maps panel camelCase values to the apply schema.
112
+ - UXP playhead, work-area, sequence-range, timeline insert/overwrite, selected-item clone, track-item move, slip/slide, split-edit, sequence and beat markers, MOGRT, and frame-export time inputs snap to the active sequence timebase using exact ticks per frame. Results report requested and applied values, and timeline readback accepts at most half a frame.
113
+
45
114
  - Transition receipts distinguish verified placement from handle-limited duration, mark each deviating placement, and count duration deviations. Premiere-built title copies report their application-support location and are documented as user-managed files that must not be removed while any project references them.
46
115
  - Filler-removal guidance explains that hesitation sounds require transcripts that preserve disfluencies; Whisper's default output omits them.
47
116
 
package/README.md CHANGED
@@ -129,13 +129,13 @@ The current source exposes 393 core tools for supported workflow steps spanning
129
129
 
130
130
  <a id="latest-release"></a>
131
131
 
132
- ### Latest release: 1.20.0
132
+ ### Latest release: 1.22.0
133
133
 
134
- The published v1.20.0 npm artifact contains **393 core tools**, 391 in its default profile,
134
+ The published v1.22.0 npm artifact contains **393 core tools**, 391 in its default profile,
135
135
  and 487 with a compatible UXP connection. The development catalog above can include
136
136
  unreleased work. See the [versioned facts and package provenance](https://premiere-pro-mcp.com/facts/).
137
137
 
138
- GitHub releases use `v<SemVer>` for both title and tag, such as `v1.20.0`.
138
+ GitHub releases use `v<SemVer>` for both title and tag, such as `v1.22.0`.
139
139
  See [release naming and version conventions](docs/release-conventions.md).
140
140
 
141
141
  ### Try a bounded workflow
@@ -154,6 +154,10 @@ if the connection is unavailable.
154
154
 
155
155
  ### Release highlights
156
156
 
157
+ - **1.22.0 readback receipts:** effect writes, stabilization, smart bins, proxies, folder imports, sequence creation, marker deletion and clip selection report what Premiere applied as `verified`, `committed_unverified` or `failed`; `create_subclip`, `add_title` and `update_marker` report the stored values; `set_sequence_frame_rate` lists clips Premiere re-snapped.
158
+ - **1.22.0 display codes and proxies:** `set_sequence_display_format` writes Premiere's real display codes (video 100-113, audio 200-201) and maps the older 0-11 and 0-1 inputs onto them; `manage_proxies` toggle can set a known proxy display state; the Windows ACL check waits up to 30 seconds for PowerShell.
159
+ - **1.21.0 Claude Desktop startup fix:** the extension starts when optional settings are blank, and Desktop's discover-only probe no longer holds the UXP port, so the real server keeps its UXP tools.
160
+ - **1.21.0 frame-grid verification:** project-item marks, sequence In/Out, work area, link/unlink, zero point, transitions, `replace_clip` and `duplicate_clip` verify against Premiere's own frame and sample grids and fail closed; UXP tools snap times to the sequence frame grid.
157
161
  - **1.20.0 verified range removal:** `ripple_remove_timeline_ranges` previews, then applies a multi-range ripple removal with a single-use confirmation token and clip-level readback (QE razor; experimental).
158
162
  - **1.20.0 effect safety:** effect colours read and write losslessly, ambiguous property names (for example repeated Lumetri controls) are refused unless `property_index` is given, and `duplicate_clip` copies keep the original length.
159
163
  - **1.20.0 import and metadata additions:** `import_fcp_xml` can import into the open project, `import_folder` accepts `target_bin`, `get_encoder_presets` pages with `limit`/`offset`, `set_footage_interpretation` sets `field_type`, and XML/AAF/EDL imports through `import_media` no longer report a false error.
@@ -191,7 +195,7 @@ if the connection is unavailable.
191
195
  local Premiere processes. See the generated [supported action catalog](docs/supported-actions.md)
192
196
  for individual capability and verification contracts.
193
197
 
194
- See the [v1.20.0 release notes](https://github.com/leancoderkavy/premiere-pro-mcp/releases/tag/v1.20.0)
198
+ See the [v1.22.0 release notes](https://github.com/leancoderkavy/premiere-pro-mcp/releases/tag/v1.22.0)
195
199
  for complete details. Live installation in Premiere Pro still requires host verification.
196
200
 
197
201
  ### Current MCP protocol support
@@ -239,7 +243,7 @@ their bins, media rules, and organization rules before a facility uses one.
239
243
  > ### Install the published package (verify the name)
240
244
  >
241
245
  > ```bash
242
- > npm i -g premiere-pro-mcp@1.20.0
246
+ > npm i -g premiere-pro-mcp@1.22.0
243
247
  > ```
244
248
  >
245
249
  > 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:
@@ -247,7 +251,7 @@ their bins, media rules, and organization rules before a facility uses one.
247
251
  > | Check | Expected |
248
252
  > | --- | --- |
249
253
  > | Package name | `premiere-pro-mcp` (not `adobe-premiere-pro-mcp`) |
250
- > | Version | `1.20.0` |
254
+ > | Version | `1.22.0` |
251
255
  > | Homepage / repo | https://premiere-pro-mcp.com/ · https://github.com/leancoderkavy/premiere-pro-mcp |
252
256
  >
253
257
  > ```bash
@@ -260,9 +264,9 @@ their bins, media rules, and organization rules before a facility uses one.
260
264
 
261
265
  ### Easiest supported path: Claude Desktop
262
266
 
263
- 1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.20.0/premiere-pro-mcp-1.20.0.mcpb).
267
+ 1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.22.0/premiere-pro-mcp-1.22.0.mcpb).
264
268
  2. In Claude Desktop, open **Settings > Extensions > Advanced settings > Install Extension**, select the downloaded bundle, and restart Claude Desktop.
265
- 3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.20.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.
269
+ 3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.22.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.
266
270
  4. Restart Premiere, open a project, then open **Window > Extensions > MCP for Adobe Premiere Pro**.
267
271
  5. In Claude, enter: `Safely check my Premiere connection with verify_premiere_connection. Make no changes.`
268
272
 
@@ -566,11 +570,11 @@ From a clone of this repository:
566
570
  ```bash
567
571
  codex plugin marketplace add .
568
572
  codex plugin add premiere-pro@premiere-pro-mcp
569
- npx -y premiere-pro-mcp@1.20.0 --install-cep
573
+ npx -y premiere-pro-mcp@1.22.0 --install-cep
570
574
  ```
571
575
 
572
576
  Restart Premiere Pro and start a new Codex session after installation. The plugin
573
- launches `premiere-pro-mcp@1.20.0` through `npx`; the separate CEP installation is
577
+ launches `premiere-pro-mcp@1.22.0` through `npx`; the separate CEP installation is
574
578
  required because the MCP server communicates with the running Premiere host through
575
579
  the local bridge.
576
580
 
@@ -608,7 +612,7 @@ For Claude Code, add this repository as a marketplace and install the plugin:
608
612
  Then install the Premiere bridge and start a new Claude Code session:
609
613
 
610
614
  ```bash
611
- npx -y premiere-pro-mcp@1.20.0 --install-cep
615
+ npx -y premiere-pro-mcp@1.22.0 --install-cep
612
616
  ```
613
617
 
614
618
  The Claude Code package lives in
@@ -1,8 +1,8 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.20.0" ExtensionBundleName="MCP for Adobe After Effects">
2
+ <ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.22.0" ExtensionBundleName="MCP for Adobe After Effects">
3
3
  <ExtensionList>
4
- <Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.20.0"/>
5
- <Extension Id="com.mcp.aftereffects.bridge.headless" Version="1.20.0"/>
4
+ <Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.22.0"/>
5
+ <Extension Id="com.mcp.aftereffects.bridge.headless" Version="1.22.0"/>
6
6
  </ExtensionList>
7
7
  <ExecutionEnvironment>
8
8
  <HostList>
@@ -91,7 +91,7 @@
91
91
  encoding: "utf8",
92
92
  windowsHide: true,
93
93
  stdio: ["ignore", "pipe", "pipe"],
94
- timeout: 5000,
94
+ timeout: 30000,
95
95
  maxBuffer: 64 * 1024,
96
96
  env: commandEnvironment,
97
97
  }
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.20.0" ExtensionBundleName="MCP for Adobe Premiere Pro">
2
+ <ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.22.0" ExtensionBundleName="MCP for Adobe Premiere Pro">
3
3
  <ExtensionList>
4
- <Extension Id="com.mcp.premiere.bridge.panel" Version="1.20.0"/>
5
- <Extension Id="com.mcp.premiere.bridge.headless" Version="1.20.0"/>
4
+ <Extension Id="com.mcp.premiere.bridge.panel" Version="1.22.0"/>
5
+ <Extension Id="com.mcp.premiere.bridge.headless" Version="1.22.0"/>
6
6
  </ExtensionList>
7
7
  <ExecutionEnvironment>
8
8
  <HostList>
@@ -91,7 +91,7 @@
91
91
  encoding: "utf8",
92
92
  windowsHide: true,
93
93
  stdio: ["ignore", "pipe", "pipe"],
94
- timeout: 5000,
94
+ timeout: 30000,
95
95
  maxBuffer: 64 * 1024,
96
96
  env: commandEnvironment,
97
97
  }
@@ -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.20.0</strong>
84
+ <strong id="updateTitle">Version 1.22.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>
@@ -7,7 +7,7 @@
7
7
  })(this, function () {
8
8
  "use strict";
9
9
 
10
- var CURRENT_VERSION = "1.20.0";
10
+ var CURRENT_VERSION = "1.22.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 =
@@ -183,6 +183,9 @@ export const WINDOWS_BRIDGE_ACL_SCRIPT = [
183
183
  '})',
184
184
  '[pscustomobject]@{ ownerSid = $owner; currentUserSid = $current; unsafeWriteAces = $unsafe; unsafeAncestorEntries = $unsafeAncestors } | ConvertTo-Json -Compress',
185
185
  ].join("\n");
186
+ // Starting powershell.exe can take several seconds on a loaded Windows machine (CI runners
187
+ // with coverage timed out at 5 s). A timeout still fails closed: the caller throws.
188
+ const WINDOWS_ACL_CHECK_TIMEOUT_MS = 30_000;
186
189
  export function inspectWindowsBridgeDirectoryAcl(directory, initialize, privacy = {}) {
187
190
  let script = WINDOWS_BRIDGE_ACL_SCRIPT;
188
191
  if (privacy.readAccess) {
@@ -198,7 +201,7 @@ export function inspectWindowsBridgeDirectoryAcl(directory, initialize, privacy
198
201
  encoding: "utf8",
199
202
  windowsHide: true,
200
203
  stdio: ["ignore", "pipe", "pipe"],
201
- timeout: 5000,
204
+ timeout: WINDOWS_ACL_CHECK_TIMEOUT_MS,
202
205
  maxBuffer: 64 * 1024,
203
206
  env: {
204
207
  ...process.env,
@@ -44,6 +44,14 @@ function __ticksToSeconds(ticks) {
44
44
  return parseFloat(ticks) / TICKS_PER_SECOND;
45
45
  }
46
46
 
47
+ function __workAreaEnabled(seq) {
48
+ try {
49
+ if (typeof seq.isWorkAreaEnabled !== "function") return null;
50
+ var enabled = seq.isWorkAreaEnabled();
51
+ return typeof enabled === "boolean" ? enabled : null;
52
+ } catch (workAreaStateError) { return null; }
53
+ }
54
+
47
55
  // Sequence work-area getters return seconds (often as a string) on live 25.x
48
56
  // and 26.x hosts. Values above 1e6 can only be ticks, so convert those.
49
57
  function __workAreaSeconds(value) {
@@ -109,6 +117,96 @@ function __isTrackLocked(track) {
109
117
  return false;
110
118
  }
111
119
 
120
+ // Read the private source range before temporarily changing project-item marks.
121
+ function __itemMarksForRestore(item, mediaType) {
122
+ function domMarks() {
123
+ try {
124
+ var inMark = item.getInPoint(mediaType), outMark = item.getOutPoint(mediaType);
125
+ if (!inMark || !outMark || inMark.ticks === null || outMark.ticks === null || inMark.ticks === undefined || outMark.ticks === undefined || String(inMark.ticks) === "" || String(outMark.ticks) === "") return null;
126
+ var left = Number(inMark.ticks), right = Number(outMark.ticks);
127
+ if (isFinite(left) && isFinite(right) && left >= 0 && right >= left) return { inTicks: String(Math.round(left)), outTicks: String(Math.round(right)), inSeconds: left / TICKS_PER_SECOND, outSeconds: right / TICKS_PER_SECOND };
128
+ } catch (domMarkError) {}
129
+ return null;
130
+ }
131
+ var dom = domMarks(), xml = "";
132
+ try { if (typeof item.getProjectMetadata === "function") xml = String(item.getProjectMetadata() || ""); } catch (metadataMarkError) { return null; }
133
+ if (!xml) return dom;
134
+ if (xml.length > 1000000) return null;
135
+ function field(name, text) {
136
+ var escaped = name.replace(/\\./g, "\\\\.");
137
+ var pattern = new RegExp("<(?:[A-Za-z_][\\\\w.-]*:)?" + escaped + "\\\\b[^>]*>([\\\\s\\\\S]*?)</(?:[A-Za-z_][\\\\w.-]*:)?" + escaped + "\\\\s*>");
138
+ var match = pattern.exec(text);
139
+ return match ? match[1] : null;
140
+ }
141
+ function value(text) {
142
+ if (text === null) return null;
143
+ var nested = field("value", text);
144
+ return String(nested === null ? text : nested).replace(/^\\s+|\\s+$/g, "");
145
+ }
146
+ var timebase = value(field("Column.Intrinsic.MediaTimebase", xml));
147
+ function point(block, audio) {
148
+ if (block === null) return null;
149
+ var tc = value(block), match = /^(\\d+):(\\d{2}):(\\d{2})([:;])(\\d+)$/.exec(tc);
150
+ if (!match) return null;
151
+ var hours = Number(match[1]), minutes = Number(match[2]), seconds = Number(match[3]), last = Number(match[5]);
152
+ if (minutes > 59 || seconds > 59) return null;
153
+ var rateText = value(field("frame_rate", block));
154
+ var hz = audio && timebase && (/Hz/i.test(timebase) || parseFloat(timebase) > 240) ? parseFloat(timebase) : NaN;
155
+ if (audio && isFinite(hz) && hz > 0) {
156
+ if (match[4] === ";" || last >= hz || Math.floor(hz) !== hz) return null;
157
+ return { ticks: Math.round(((hours * 3600 + minutes * 60 + seconds) * hz + last) * TICKS_PER_SECOND / hz), frameTicks: TICKS_PER_SECOND / hz };
158
+ }
159
+ var frameTicks = rateText !== null ? Number(rateText) : NaN;
160
+ if (rateText !== null && (!isFinite(frameTicks) || frameTicks <= 0)) return null;
161
+ if (!(frameTicks > 0)) {
162
+ var fps = timebase ? parseFloat(timebase) : NaN;
163
+ if (!isFinite(fps) || fps <= 0 || fps > 240 || /Hz/i.test(timebase)) {
164
+ try { fps = Number(item.getFootageInterpretation().frameRate); } catch (footageRateError) { fps = NaN; }
165
+ }
166
+ if (!isFinite(fps) || fps <= 0 || fps > 240) return null;
167
+ if (Math.abs(fps - 23.976) < 0.001) frameTicks = TICKS_PER_SECOND * 1001 / 24000;
168
+ else if (Math.abs(fps - 29.97) < 0.001) frameTicks = TICKS_PER_SECOND * 1001 / 30000;
169
+ else if (Math.abs(fps - 59.94) < 0.001) frameTicks = TICKS_PER_SECOND * 1001 / 60000;
170
+ else frameTicks = TICKS_PER_SECOND / fps;
171
+ }
172
+ var actualRate = TICKS_PER_SECOND / frameTicks, nominal = Math.round(actualRate);
173
+ if (nominal < 1 || nominal > 240 || last >= nominal) return null;
174
+ var frames = (hours * 3600 + minutes * 60 + seconds) * nominal + last;
175
+ if (match[4] === ";") {
176
+ var dropped = 0;
177
+ if (nominal === 30) dropped = 2;
178
+ else if (nominal === 60) dropped = 4;
179
+ if (!dropped || Math.abs(actualRate - nominal * 1000 / 1001) > 0.01 || (minutes % 10 !== 0 && seconds === 0 && last < dropped)) return null;
180
+ var totalMinutes = hours * 60 + minutes;
181
+ frames -= dropped * (totalMinutes - Math.floor(totalMinutes / 10));
182
+ }
183
+ return { ticks: Math.round(frames * frameTicks), frameTicks: frameTicks };
184
+ }
185
+ var kind = mediaType === 2 ? "Audio" : "Video";
186
+ var inBlock = field("Column.Intrinsic." + kind + "InPoint", xml), outBlock = field("Column.Intrinsic." + kind + "OutPoint", xml);
187
+ var videoFieldsPresent = xml.indexOf("Column.Intrinsic.VideoInPoint") >= 0 || xml.indexOf("Column.Intrinsic.VideoOutPoint") >= 0;
188
+ if (mediaType === 4 && inBlock === null && outBlock === null && !videoFieldsPresent) {
189
+ kind = "Audio";
190
+ inBlock = field("Column.Intrinsic.AudioInPoint", xml); outBlock = field("Column.Intrinsic.AudioOutPoint", xml);
191
+ }
192
+ if (inBlock === null && outBlock === null) {
193
+ if (xml.indexOf("Column.Intrinsic." + kind + "InPoint") >= 0 || xml.indexOf("Column.Intrinsic." + kind + "OutPoint") >= 0) return null;
194
+ return dom;
195
+ }
196
+ var left = point(inBlock, kind === "Audio"), right = point(outBlock, kind === "Audio");
197
+ // The private VideoOutPoint names the last frame (inclusive): a subclip made
198
+ // with an exclusive Out at frame 719 shows 00:00:29:22 (frame 718) on 26.5.2.
199
+ if (right && kind !== "Audio") right.ticks += right.frameTicks;
200
+ if (!left || !right || !isFinite(left.ticks) || !isFinite(right.ticks) || left.ticks < 0 || right.ticks > 9007199254740991 || right.ticks <= left.ticks || Math.abs(left.frameTicks - right.frameTicks) > 1) return null;
201
+ var tolerance = Math.max(left.frameTicks, right.frameTicks);
202
+ if (dom && Math.abs(Number(dom.inTicks) - left.ticks) <= tolerance && Math.abs(Number(dom.outTicks) - right.ticks) <= tolerance) return dom;
203
+ // Premiere floors a written mark to the media's frame (or sample) grid, so a
204
+ // value exactly on the boundary can land one frame early (live 26.5.2: a
205
+ // restored Out of 00:00:29:22 read back 00:00:29:21). Write a quarter frame
206
+ // past the boundary; the ticks stay on the boundary for readback checks.
207
+ return { inTicks: String(left.ticks), outTicks: String(right.ticks), inSeconds: (left.ticks + left.frameTicks / 4) / TICKS_PER_SECOND, outSeconds: (right.ticks + right.frameTicks / 4) / TICKS_PER_SECOND };
208
+ }
209
+
112
210
  // Place exactly [inTicks, outTicks) of a project item on ONE track at
113
211
  // startTicks without rippling. Track.overwriteClip places the item's In/Out
114
212
  // range, so set those marks first and restore the item's own marks afterwards.
@@ -116,7 +214,64 @@ function __isTrackLocked(track) {
116
214
  // empty first; overwrite never shifts neighbours but would replace them.
117
215
  // Returns { ok, attempted, marksRestored, error }. attempted=true means the
118
216
  // overwrite call ran (or threw), so the timeline may have changed.
119
- function __overwriteRangeOnTrack(track, item, startTicks, inTicks, outTicks, mediaType) {
217
+ // Set a project item's In/Out marks, ordering the writes so the In never
218
+ // passes the current Out, and report whether both read back within tolerance
219
+ // ticks (default: exact).
220
+ function __setItemMarks(item, wantIn, wantOut, mediaType, tolerance) {
221
+ var tol = tolerance > __TICK_MATCH_TOL ? tolerance : __TICK_MATCH_TOL;
222
+ var currentOut = parseFloat(item.getOutPoint(mediaType).ticks);
223
+ if (parseFloat(wantIn) >= currentOut) {
224
+ item.setOutPoint(__ticksToSeconds(wantOut), mediaType);
225
+ item.setInPoint(__ticksToSeconds(wantIn), mediaType);
226
+ } else {
227
+ item.setInPoint(__ticksToSeconds(wantIn), mediaType);
228
+ item.setOutPoint(__ticksToSeconds(wantOut), mediaType);
229
+ }
230
+ return Math.abs(parseFloat(item.getInPoint(mediaType).ticks) - parseFloat(wantIn)) <= tol &&
231
+ Math.abs(parseFloat(item.getOutPoint(mediaType).ticks) - parseFloat(wantOut)) <= tol;
232
+ }
233
+
234
+ // Live 26.5.2: a project item's video marks are floored to the media's own
235
+ // frame grid (a 23.976 clip in a 29.97 sequence loses up to 1.25 sequence
236
+ // frames) and audio marks to the sample grid. Returns the most a mark can
237
+ // move, in ticks.
238
+ function __itemMarkToleranceTicks(item, seq, mediaType) {
239
+ var seqFrame = __sequenceFrameTicks(seq);
240
+ if (!isFinite(seqFrame) || !(seqFrame > 0)) seqFrame = 0;
241
+ if (mediaType !== 1) return seqFrame / 2;
242
+ var mediaFrame = 0;
243
+ try {
244
+ var fps = parseFloat(item.getFootageInterpretation().frameRate);
245
+ if (fps > 0) mediaFrame = TICKS_PER_SECOND / fps;
246
+ } catch (eInterp) {}
247
+ return Math.max(mediaFrame, seqFrame);
248
+ }
249
+
250
+ function __readItemMarks(item, mediaType) {
251
+ if (!item || typeof item.getInPoint !== "function" || typeof item.getOutPoint !== "function" ||
252
+ typeof item.setInPoint !== "function" || typeof item.setOutPoint !== "function") return null;
253
+ try {
254
+ return { inTicks: String(item.getInPoint(mediaType).ticks), outTicks: String(item.getOutPoint(mediaType).ticks) };
255
+ } catch (eMarks) { return null; }
256
+ }
257
+
258
+ // Check, before any timeline change, that the item accepts this source range
259
+ // (a still or a too-short clip does not), then put its marks back.
260
+ function __itemAcceptsRange(item, inTicks, outTicks, mediaType, tolerance) {
261
+ var original = __itemMarksForRestore(item, mediaType);
262
+ if (!original || typeof item.getInPoint !== "function" || typeof item.setInPoint !== "function") return { ok: false, marksRestored: true, error: "Could not reliably read or set the project item's In/Out marks" };
263
+ var accepted = false;
264
+ try { accepted = __setItemMarks(item, inTicks, outTicks, mediaType, tolerance); } catch (eSet) { accepted = false; }
265
+ var restored = false;
266
+ try { restored = __setItemMarks(item, original.inTicks, original.outTicks, mediaType); } catch (eRestore) { restored = false; }
267
+ return {
268
+ ok: accepted,
269
+ marksRestored: restored,
270
+ error: accepted ? "" : "Premiere did not accept a source range of " + __ticksToSeconds(String(parseFloat(outTicks) - parseFloat(inTicks))) + "s from " + __ticksToSeconds(String(inTicks)) + "s on project item " + item.name
271
+ };
272
+ }
273
+
274
+ function __overwriteRangeOnTrack(track, item, startTicks, inTicks, outTicks, mediaType, tolerance) {
120
275
  if (!track || typeof track.overwriteClip !== "function") {
121
276
  return { ok: false, attempted: false, marksRestored: true, error: "Track.overwriteClip is unavailable on this Premiere build" };
122
277
  }
@@ -124,31 +279,15 @@ function __overwriteRangeOnTrack(track, item, startTicks, inTicks, outTicks, med
124
279
  typeof item.setInPoint !== "function" || typeof item.setOutPoint !== "function") {
125
280
  return { ok: false, attempted: false, marksRestored: true, error: "Project item In/Out marks cannot be set on this Premiere build" };
126
281
  }
127
- var originalIn = null;
128
- var originalOut = null;
129
- try {
130
- originalIn = String(item.getInPoint(mediaType).ticks);
131
- originalOut = String(item.getOutPoint(mediaType).ticks);
132
- } catch (eMarks) {
133
- return { ok: false, attempted: false, marksRestored: true, error: "Could not read the project item's In/Out marks" };
134
- }
135
- function setMarks(wantIn, wantOut) {
136
- var currentOut = parseFloat(item.getOutPoint(mediaType).ticks);
137
- if (parseFloat(wantIn) >= currentOut) {
138
- item.setOutPoint(__ticksToSeconds(wantOut), mediaType);
139
- item.setInPoint(__ticksToSeconds(wantIn), mediaType);
140
- } else {
141
- item.setInPoint(__ticksToSeconds(wantIn), mediaType);
142
- item.setOutPoint(__ticksToSeconds(wantOut), mediaType);
143
- }
144
- return Math.abs(parseFloat(item.getInPoint(mediaType).ticks) - parseFloat(wantIn)) <= __TICK_MATCH_TOL &&
145
- Math.abs(parseFloat(item.getOutPoint(mediaType).ticks) - parseFloat(wantOut)) <= __TICK_MATCH_TOL;
282
+ var original = __itemMarksForRestore(item, mediaType);
283
+ if (!original) {
284
+ return { ok: false, attempted: false, marksRestored: true, error: "Could not reliably read the project item's In/Out marks; nothing was changed" };
146
285
  }
147
286
  function restore() {
148
- try { return setMarks(originalIn, originalOut); } catch (eRestore) { return false; }
287
+ try { return __setItemMarks(item, original.inTicks, original.outTicks, mediaType); } catch (eRestore) { return false; }
149
288
  }
150
289
  var applied = false;
151
- try { applied = setMarks(inTicks, outTicks); } catch (eSet) { applied = false; }
290
+ try { applied = __setItemMarks(item, inTicks, outTicks, mediaType, tolerance); } catch (eSet) { applied = false; }
152
291
  if (!applied) {
153
292
  return { ok: false, attempted: false, marksRestored: restore(), error: "Premiere did not apply the source In/Out range to project item " + item.name };
154
293
  }
@@ -327,14 +466,37 @@ function __propertyNameMatches(actual, wanted, component) {
327
466
  var aliased = (wanted === "Scale" && actual === "Scale Height") || (wanted === "Scale Height" && actual === "Scale");
328
467
  return aliased && __isUniformScale(component);
329
468
  }
469
+ function __availablePropertyNames(component) {
470
+ var names = [];
471
+ var counts = {};
472
+ var total = 0;
473
+ for (var i = 0; i < component.properties.numItems; i++) {
474
+ var name = "";
475
+ try { name = String(component.properties[i].displayName || ""); } catch (eName) {}
476
+ if (!name.replace(/\\s/g, "")) continue;
477
+ total++;
478
+ names.push({ name: name, index: i });
479
+ var key = "$" + name;
480
+ counts[key] = (counts[key] || 0) + 1;
481
+ }
482
+ var shown = [];
483
+ var limit = Math.min(names.length, 25);
484
+ for (var n = 0; n < limit; n++) {
485
+ var entry = names[n];
486
+ shown.push(counts["$" + entry.name] > 1 ? entry.name + " (property_index " + entry.index + ")" : entry.name);
487
+ }
488
+ var summary = shown.length ? shown.join(", ") : "none with a non-empty display name";
489
+ if (total > limit) summary += " (first " + limit + " of " + total + ")";
490
+ return summary;
491
+ }
330
492
  function __resolveProperty(component, wanted, propertyIndex) {
331
493
  var matches = [];
332
494
  if (propertyIndex !== null && propertyIndex !== undefined) {
333
495
  if (typeof propertyIndex !== "number" || !isFinite(propertyIndex) || Math.floor(propertyIndex) !== propertyIndex || propertyIndex < 0 || propertyIndex >= component.properties.numItems) {
334
- return { property: null, index: null, candidates: [], error: "property_index is out of range." };
496
+ return { property: null, index: null, candidates: [], error: "property_index is out of range. Available properties: " + __availablePropertyNames(component) + "." };
335
497
  }
336
498
  if (!__propertyNameMatches(component.properties[propertyIndex].displayName, wanted, component)) {
337
- return { property: null, index: null, candidates: [], error: "property_index " + propertyIndex + " does not match property name '" + wanted + "'." };
499
+ return { property: null, index: null, candidates: [], error: "property_index " + propertyIndex + " does not match property name '" + wanted + "'. Available properties: " + __availablePropertyNames(component) + "." };
338
500
  }
339
501
  return { property: component.properties[propertyIndex], index: propertyIndex, candidates: [propertyIndex], error: null };
340
502
  }
@@ -342,9 +504,9 @@ function __resolveProperty(component, wanted, propertyIndex) {
342
504
  if (__propertyNameMatches(component.properties[i].displayName, wanted, component)) matches.push(i);
343
505
  }
344
506
  if (matches.length > 1) {
345
- return { property: null, index: null, candidates: matches, error: "Property name '" + wanted + "' is ambiguous at property indices [" + matches.join(", ") + "]; pass property_index." };
507
+ return { property: null, index: null, candidates: matches, error: "Property name '" + wanted + "' is ambiguous at property indices [" + matches.join(", ") + "]; pass property_index. Available properties: " + __availablePropertyNames(component) + "." };
346
508
  }
347
- if (!matches.length) return { property: null, index: null, candidates: [], error: null };
509
+ if (!matches.length) return { property: null, index: null, candidates: [], error: "Property name '" + wanted + "' not found. Available properties: " + __availablePropertyNames(component) + "." };
348
510
  return { property: component.properties[matches[0]], index: matches[0], candidates: matches, error: null };
349
511
  }
350
512
 
@@ -42,11 +42,13 @@ export class UxpWebSocketBridge extends EventEmitter {
42
42
  pending = new Map();
43
43
  constructor(options) {
44
44
  super();
45
- if (!options.token || options.token.length < 16) {
45
+ // A token pasted from a clipboard often carries a trailing newline.
46
+ const token = options.token?.trim();
47
+ if (!token || token.length < 16) {
46
48
  throw new Error("PREMIERE_UXP_TOKEN must contain at least 16 characters");
47
49
  }
48
50
  this.options = {
49
- token: options.token,
51
+ token,
50
52
  port: validPort(options.port ?? 7777),
51
53
  path: options.path ?? "/uxp",
52
54
  requestTimeoutMs: options.requestTimeoutMs ?? 30_000,
@@ -1,4 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { readEnvValue } from "./env-config.js";
2
3
  import path from "node:path";
3
4
  const PRIVACY_EXCLUSIONS = [
4
5
  "prompts",
@@ -61,7 +62,7 @@ export function collectLocalDoctor(options = {}) {
61
62
  const exists = options.exists ?? existsSync;
62
63
  const manifest = cepManifestPath(platform, environment);
63
64
  const connectorInstalled = manifest ? exists(manifest) : false;
64
- const uxpConfigured = Boolean(environment.PREMIERE_UXP_TOKEN);
65
+ const uxpConfigured = Boolean(readEnvValue(environment.PREMIERE_UXP_TOKEN));
65
66
  const nodeVersion = options.nodeVersion ?? process.version;
66
67
  const now = options.now ?? (() => new Date());
67
68
  const components = [
@@ -12,6 +12,11 @@ function connectorDirectory(platform, environment) {
12
12
  return undefined;
13
13
  }
14
14
  function defaultRunInstaller(platform, projectRoot) {
15
+ const script = path.join(projectRoot, "scripts", platform === "win32" ? "install-cep.ps1" : "install-cep.sh");
16
+ if ((platform === "win32" || platform === "darwin") && !existsSync(script)) {
17
+ throw new Error("The connector installer script is not bundled here (the Claude Desktop extension omits scripts/). "
18
+ + "Install the signed MCPBridgeCEP.zxp from the GitHub release, or run `npx -y premiere-pro-mcp --install-cep`.");
19
+ }
15
20
  if (platform === "win32") {
16
21
  execFileSync("powershell.exe", [
17
22
  "-NoProfile",
@@ -0,0 +1,2 @@
1
+ export declare function readEnvValue(value: string | undefined): string | undefined;
2
+ //# sourceMappingURL=env-config.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Claude Desktop can pass an optional `user_config` field through unsubstituted
3
+ * (the literal `${user_config.name}` string). Treat that, and blank values, as unset.
4
+ */
5
+ const UNSUBSTITUTED_PLACEHOLDER = /^\$\{user_config\.[^}]*\}$/;
6
+ export function readEnvValue(value) {
7
+ const trimmed = value?.trim();
8
+ if (!trimmed || UNSUBSTITUTED_PLACEHOLDER.test(trimmed))
9
+ return undefined;
10
+ return trimmed;
11
+ }
12
+ //# sourceMappingURL=env-config.js.map