premiere-pro-mcp 1.5.0 → 1.6.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.
- package/CHANGELOG.md +24 -0
- package/README.md +19 -16
- package/artifacts/MCPBridgeCEP.zxp +0 -0
- package/cep-plugin/CSXS/manifest.xml +3 -3
- package/cep-plugin/index.html +1 -1
- package/cep-plugin/updater.cjs +1 -1
- package/dist/tools/project.js +25 -3
- package/dist/tools/uxp.d.ts +197 -0
- package/dist/tools/uxp.js +86 -0
- package/package.json +4 -4
- package/scripts/build-claude-desktop.mjs +20 -4
- package/uxp-plugin/commands.cjs +125 -9
- package/uxp-plugin/manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.6.0] - 2026-07-31
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added a capability-aware UXP foundation for revisioned project inspection, verified saves,
|
|
14
|
+
preset-based sequence creation, OTIO/FCP XML interchange, transcript-language discovery,
|
|
15
|
+
Object Mask detection, and Adobe Media Encoder controls on compatible Premiere hosts.
|
|
16
|
+
- Added explicit UXP operation outcomes and bounded operation-ID replay protection so a client retry
|
|
17
|
+
does not repeat a completed command within the same panel session.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Documented the 10 UXP MCP tools that become available when an authenticated local panel is
|
|
22
|
+
connected, including their host-version and live-verification boundaries.
|
|
23
|
+
- Updated the MCP SDK and Node type dependencies and GitHub Actions artifact actions.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- `create_project` now rejects directory paths and verifies that Premiere switched to the exact
|
|
28
|
+
requested `.prproj` path before reporting success, preventing edits from continuing in a
|
|
29
|
+
previously open project after a failed creation attempt.
|
|
30
|
+
- Claude Desktop bundle packaging now invokes npm through the active Node executable so the
|
|
31
|
+
release build works on Windows where `npm` is exposed as a command shim.
|
|
32
|
+
|
|
9
33
|
## [1.5.0] - 2026-07-30
|
|
10
34
|
|
|
11
35
|
### Added
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**Give AI full control over Adobe Premiere Pro.**
|
|
8
8
|
|
|
9
|
-
279 tools across 31 modules, 3 resources, and 4 guided workflows.
|
|
9
|
+
279 core tools across 31 modules, 3 resources, and 4 guided workflows. A connected UXP host adds 10 capability-gated tools.
|
|
10
10
|
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
[](https://nodejs.org)
|
|
@@ -27,18 +27,14 @@ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that l
|
|
|
27
27
|
"Add the B-roll clips to V2, apply a cross dissolve between each, color correct them to match the A-roll, and export a 1080p ProRes."
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
The AI handles the entire workflow through 279 tools spanning the supported ExtendScript, QE DOM, local media analysis, and safe edit-planning surfaces.
|
|
30
|
+
The AI handles the entire workflow through 279 core tools spanning the supported ExtendScript, QE DOM, local media analysis, and safe edit-planning surfaces. A compatible, authenticated UXP panel adds 10 documented, capability-gated workflows without replacing the production CEP bridge.
|
|
31
31
|
|
|
32
|
-
### What's new in 1.
|
|
32
|
+
### What's new in 1.6.0
|
|
33
33
|
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
- `
|
|
37
|
-
|
|
38
|
-
- `premiere-pro-mcp --diagnose-cep` checks the installed manifest, debug-key types, and recent
|
|
39
|
-
Premiere signature failures.
|
|
40
|
-
- TypeScript 7, Vitest 4, Zod 4, MCP SDK 1.29, Next.js 16.2, and patched transitive dependencies
|
|
41
|
-
modernize the toolchain and remove production audit findings.
|
|
34
|
+
- **Capability-aware UXP foundation:** compatible Premiere 25.6-26.3 hosts can expose revisioned project inspection, verified saves, preset sequence creation, OTIO/FCP XML interchange, transcript-language discovery, Object Mask detection, and Adobe Media Encoder controls.
|
|
35
|
+
- **Retry-safe UXP mutations:** optional operation IDs prevent a client retry from repeating a completed command in the same panel session, while each command reports `verified` or `committed_unverified` evidence honestly.
|
|
36
|
+
- **Safe project creation:** `create_project` rejects directory paths and verifies Premiere opened the requested `.prproj` before reporting success.
|
|
37
|
+
- **Current distribution metadata:** the CEP/UXP panels, Codex and Claude plugins, landing page, and npm package now all identify v1.6.0.
|
|
42
38
|
|
|
43
39
|
### Added in 1.2.0
|
|
44
40
|
|
|
@@ -252,11 +248,11 @@ From a clone of this repository:
|
|
|
252
248
|
```bash
|
|
253
249
|
codex plugin marketplace add .
|
|
254
250
|
codex plugin add premiere-pro@premiere-pro-mcp
|
|
255
|
-
npx -y premiere-pro-mcp@1.
|
|
251
|
+
npx -y premiere-pro-mcp@1.6.0 --install-cep
|
|
256
252
|
```
|
|
257
253
|
|
|
258
254
|
Restart Premiere Pro and start a new Codex session after installation. The plugin
|
|
259
|
-
launches `premiere-pro-mcp@1.
|
|
255
|
+
launches `premiere-pro-mcp@1.6.0` through `npx`; the separate CEP installation is
|
|
260
256
|
required because the MCP server communicates with the running Premiere host through
|
|
261
257
|
the local bridge.
|
|
262
258
|
|
|
@@ -276,7 +272,7 @@ For Claude Code, add this repository as a marketplace and install the plugin:
|
|
|
276
272
|
Then install the Premiere bridge and start a new Claude Code session:
|
|
277
273
|
|
|
278
274
|
```bash
|
|
279
|
-
npx -y premiere-pro-mcp@1.
|
|
275
|
+
npx -y premiere-pro-mcp@1.6.0 --install-cep
|
|
280
276
|
```
|
|
281
277
|
|
|
282
278
|
The Claude Code package lives in
|
|
@@ -366,7 +362,14 @@ PREMIERE_UXP_TOKEN="replace-with-a-long-random-secret" premiere-pro-mcp
|
|
|
366
362
|
|
|
367
363
|
Enter the same token in the UXP panel. The listener binds only to `127.0.0.1:7777`, authenticates the WebSocket upgrade, requires a versioned capability handshake, correlates concurrent requests, and fails pending work on timeout or disconnect. Set `PREMIERE_UXP_PORT` to use another loopback port.
|
|
368
364
|
|
|
369
|
-
When enabled, MCP discovery includes `get_uxp_capabilities`, `get_uxp_state`, and `export_frame_uxp`. A failed UXP command is never silently retried through CEP because the first operation may have partially succeeded.
|
|
365
|
+
When enabled, MCP discovery includes `get_uxp_capabilities`, `get_uxp_state`, `inspect_project_uxp`, `save_project_uxp`, `create_sequence_with_preset_uxp`, `export_interchange_uxp`, `get_transcript_languages_uxp`, `detect_object_masks_uxp`, `configure_encoder_uxp`, and `export_frame_uxp`. Commands are advertised only while the authenticated local UXP bridge is connected; the host capability handshake remains the authority for support in the running Premiere build. A failed UXP command is never silently retried through CEP because the first operation may have partially succeeded.
|
|
366
|
+
|
|
367
|
+
Premiere 26.2-26.3 hosts also expose documented UXP workflows for revisioned project
|
|
368
|
+
inspection, verified project saves, preset-based sequence creation, OTIO/FCP XML
|
|
369
|
+
interchange, transcript-language discovery, Object Mask detection, and Adobe Media
|
|
370
|
+
Encoder control. Mutations accept optional idempotency keys and return explicit
|
|
371
|
+
verification outcomes. See [the UXP capability foundation](docs/uxp-capability-foundation.md)
|
|
372
|
+
for the command matrix and live-host validation boundary.
|
|
370
373
|
|
|
371
374
|
---
|
|
372
375
|
|
|
@@ -410,7 +413,7 @@ The file-based IPC bridge is simple, reliable, and works across macOS and Window
|
|
|
410
413
|
|
|
411
414
|
---
|
|
412
415
|
|
|
413
|
-
## Tools (279 total; 277 under the default profile)
|
|
416
|
+
## Tools (279 core total; 277 under the default profile; 287 with a connected UXP bridge)
|
|
414
417
|
|
|
415
418
|
### Discovery & Inspection (10 + 10)
|
|
416
419
|
|
|
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.
|
|
2
|
+
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.6.0" ExtensionBundleName="MCP Bridge">
|
|
3
3
|
<ExtensionList>
|
|
4
|
-
<Extension Id="com.mcp.premiere.bridge.panel" Version="1.
|
|
5
|
-
<Extension Id="com.mcp.premiere.bridge.headless" Version="1.
|
|
4
|
+
<Extension Id="com.mcp.premiere.bridge.panel" Version="1.6.0"/>
|
|
5
|
+
<Extension Id="com.mcp.premiere.bridge.headless" Version="1.6.0"/>
|
|
6
6
|
</ExtensionList>
|
|
7
7
|
<ExecutionEnvironment>
|
|
8
8
|
<HostList>
|
package/cep-plugin/index.html
CHANGED
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
<section class="update-section" aria-live="polite">
|
|
66
66
|
<div class="update-copy">
|
|
67
67
|
<span class="section-label">Connector updates</span>
|
|
68
|
-
<strong id="updateTitle">Version 1.
|
|
68
|
+
<strong id="updateTitle">Version 1.6.0</strong>
|
|
69
69
|
<span id="updateDetail">Checking for updates…</span>
|
|
70
70
|
</div>
|
|
71
71
|
<button id="btnUpdate" class="button button-update" onclick="handleUpdateClick()" type="button" disabled>
|
package/cep-plugin/updater.cjs
CHANGED
package/dist/tools/project.js
CHANGED
|
@@ -126,11 +126,33 @@ export function getProjectTools(bridgeOptions) {
|
|
|
126
126
|
required: ["path"],
|
|
127
127
|
},
|
|
128
128
|
handler: async (args) => {
|
|
129
|
+
const requestedPath = args.path.trim();
|
|
130
|
+
if (!/\.prproj$/i.test(requestedPath)) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
error: "create_project path must be a full .prproj file path; Premiere cannot create a project from a directory path.",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
129
136
|
const script = buildToolScript(`
|
|
130
|
-
|
|
137
|
+
var requestedPath = "${escapeForExtendScript(requestedPath)}";
|
|
138
|
+
function __normalizedProjectPath(path) {
|
|
139
|
+
return String(path || "").replace(/\\\\/g, "/").toLowerCase();
|
|
140
|
+
}
|
|
141
|
+
var beforePath = app.project ? String(app.project.path || "") : "";
|
|
142
|
+
if (__normalizedProjectPath(beforePath) === __normalizedProjectPath(requestedPath)) {
|
|
143
|
+
return __error("A project is already open at " + requestedPath + "; choose a new .prproj file path instead.");
|
|
144
|
+
}
|
|
145
|
+
app.newProject(requestedPath);
|
|
131
146
|
var project = app.project;
|
|
132
|
-
|
|
133
|
-
|
|
147
|
+
var actualPath = project ? String(project.path || "") : "";
|
|
148
|
+
if (!project || !actualPath || __normalizedProjectPath(actualPath) !== __normalizedProjectPath(requestedPath)) {
|
|
149
|
+
return __error(
|
|
150
|
+
"Premiere did not create a project at " + requestedPath +
|
|
151
|
+
"; the active project is still " + (actualPath || beforePath || "unavailable") +
|
|
152
|
+
". Check that the path is a new .prproj file and its parent directory exists."
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return __result({ created: true, name: project.name, path: actualPath });
|
|
134
156
|
`);
|
|
135
157
|
return sendCommand(script, bridgeOptions);
|
|
136
158
|
},
|
package/dist/tools/uxp.d.ts
CHANGED
|
@@ -22,6 +22,203 @@ export declare function getUxpTools(bridge: UxpWebSocketBridge): {
|
|
|
22
22
|
error: string;
|
|
23
23
|
}>;
|
|
24
24
|
};
|
|
25
|
+
inspect_project_uxp: {
|
|
26
|
+
description: string;
|
|
27
|
+
parameters: {};
|
|
28
|
+
handler: () => Promise<{
|
|
29
|
+
success: boolean;
|
|
30
|
+
data: {
|
|
31
|
+
backend: string;
|
|
32
|
+
result: unknown;
|
|
33
|
+
};
|
|
34
|
+
} | {
|
|
35
|
+
success: boolean;
|
|
36
|
+
error: string;
|
|
37
|
+
}>;
|
|
38
|
+
};
|
|
39
|
+
save_project_uxp: {
|
|
40
|
+
description: string;
|
|
41
|
+
parameters: {
|
|
42
|
+
type: "object";
|
|
43
|
+
properties: {
|
|
44
|
+
operation_id: {
|
|
45
|
+
type: "string";
|
|
46
|
+
description: string;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
handler: (args: {
|
|
51
|
+
operation_id?: string;
|
|
52
|
+
}) => Promise<{
|
|
53
|
+
success: boolean;
|
|
54
|
+
data: {
|
|
55
|
+
backend: string;
|
|
56
|
+
result: unknown;
|
|
57
|
+
};
|
|
58
|
+
} | {
|
|
59
|
+
success: boolean;
|
|
60
|
+
error: string;
|
|
61
|
+
}>;
|
|
62
|
+
};
|
|
63
|
+
create_sequence_with_preset_uxp: {
|
|
64
|
+
description: string;
|
|
65
|
+
parameters: {
|
|
66
|
+
type: "object";
|
|
67
|
+
properties: {
|
|
68
|
+
name: {
|
|
69
|
+
type: string;
|
|
70
|
+
description: string;
|
|
71
|
+
};
|
|
72
|
+
preset_path: {
|
|
73
|
+
type: string;
|
|
74
|
+
description: string;
|
|
75
|
+
};
|
|
76
|
+
operation_id: {
|
|
77
|
+
type: "string";
|
|
78
|
+
description: string;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
required: string[];
|
|
82
|
+
};
|
|
83
|
+
handler: (args: {
|
|
84
|
+
name: string;
|
|
85
|
+
preset_path: string;
|
|
86
|
+
operation_id?: string;
|
|
87
|
+
}) => Promise<{
|
|
88
|
+
success: boolean;
|
|
89
|
+
data: {
|
|
90
|
+
backend: string;
|
|
91
|
+
result: unknown;
|
|
92
|
+
};
|
|
93
|
+
} | {
|
|
94
|
+
success: boolean;
|
|
95
|
+
error: string;
|
|
96
|
+
}>;
|
|
97
|
+
};
|
|
98
|
+
export_interchange_uxp: {
|
|
99
|
+
description: string;
|
|
100
|
+
parameters: {
|
|
101
|
+
type: "object";
|
|
102
|
+
properties: {
|
|
103
|
+
format: {
|
|
104
|
+
type: string;
|
|
105
|
+
enum: string[];
|
|
106
|
+
description: string;
|
|
107
|
+
};
|
|
108
|
+
output_file_path: {
|
|
109
|
+
type: string;
|
|
110
|
+
description: string;
|
|
111
|
+
};
|
|
112
|
+
suppress_ui: {
|
|
113
|
+
type: string;
|
|
114
|
+
description: string;
|
|
115
|
+
};
|
|
116
|
+
operation_id: {
|
|
117
|
+
type: "string";
|
|
118
|
+
description: string;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
required: string[];
|
|
122
|
+
};
|
|
123
|
+
handler: (args: {
|
|
124
|
+
format: "otio" | "fcpxml";
|
|
125
|
+
output_file_path: string;
|
|
126
|
+
suppress_ui?: boolean;
|
|
127
|
+
operation_id?: string;
|
|
128
|
+
}) => Promise<{
|
|
129
|
+
success: boolean;
|
|
130
|
+
data: {
|
|
131
|
+
backend: string;
|
|
132
|
+
result: unknown;
|
|
133
|
+
};
|
|
134
|
+
} | {
|
|
135
|
+
success: boolean;
|
|
136
|
+
error: string;
|
|
137
|
+
}>;
|
|
138
|
+
};
|
|
139
|
+
get_transcript_languages_uxp: {
|
|
140
|
+
description: string;
|
|
141
|
+
parameters: {};
|
|
142
|
+
handler: () => Promise<{
|
|
143
|
+
success: boolean;
|
|
144
|
+
data: {
|
|
145
|
+
backend: string;
|
|
146
|
+
result: unknown;
|
|
147
|
+
};
|
|
148
|
+
} | {
|
|
149
|
+
success: boolean;
|
|
150
|
+
error: string;
|
|
151
|
+
}>;
|
|
152
|
+
};
|
|
153
|
+
detect_object_masks_uxp: {
|
|
154
|
+
description: string;
|
|
155
|
+
parameters: {
|
|
156
|
+
type: "object";
|
|
157
|
+
properties: {
|
|
158
|
+
scope: {
|
|
159
|
+
type: string;
|
|
160
|
+
enum: string[];
|
|
161
|
+
description: string;
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
handler: (args: {
|
|
166
|
+
scope?: "sequence" | "project";
|
|
167
|
+
}) => Promise<{
|
|
168
|
+
success: boolean;
|
|
169
|
+
data: {
|
|
170
|
+
backend: string;
|
|
171
|
+
result: unknown;
|
|
172
|
+
};
|
|
173
|
+
} | {
|
|
174
|
+
success: boolean;
|
|
175
|
+
error: string;
|
|
176
|
+
}>;
|
|
177
|
+
};
|
|
178
|
+
configure_encoder_uxp: {
|
|
179
|
+
description: string;
|
|
180
|
+
parameters: {
|
|
181
|
+
type: "object";
|
|
182
|
+
properties: {
|
|
183
|
+
launch: {
|
|
184
|
+
type: string;
|
|
185
|
+
description: string;
|
|
186
|
+
};
|
|
187
|
+
embedded_xmp: {
|
|
188
|
+
type: string;
|
|
189
|
+
description: string;
|
|
190
|
+
};
|
|
191
|
+
sidecar_xmp: {
|
|
192
|
+
type: string;
|
|
193
|
+
description: string;
|
|
194
|
+
};
|
|
195
|
+
start_batch: {
|
|
196
|
+
type: string;
|
|
197
|
+
description: string;
|
|
198
|
+
};
|
|
199
|
+
operation_id: {
|
|
200
|
+
type: "string";
|
|
201
|
+
description: string;
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
handler: (args: {
|
|
206
|
+
launch?: boolean;
|
|
207
|
+
embedded_xmp?: boolean;
|
|
208
|
+
sidecar_xmp?: boolean;
|
|
209
|
+
start_batch?: boolean;
|
|
210
|
+
operation_id?: string;
|
|
211
|
+
}) => Promise<{
|
|
212
|
+
success: boolean;
|
|
213
|
+
data: {
|
|
214
|
+
backend: string;
|
|
215
|
+
result: unknown;
|
|
216
|
+
};
|
|
217
|
+
} | {
|
|
218
|
+
success: boolean;
|
|
219
|
+
error: string;
|
|
220
|
+
}>;
|
|
221
|
+
};
|
|
25
222
|
export_frame_uxp: {
|
|
26
223
|
description: string;
|
|
27
224
|
parameters: {
|
package/dist/tools/uxp.js
CHANGED
|
@@ -7,6 +7,10 @@ function invoke(bridge, command, args = {}) {
|
|
|
7
7
|
}));
|
|
8
8
|
}
|
|
9
9
|
export function getUxpTools(bridge) {
|
|
10
|
+
const operationId = {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Optional idempotency key (1-128 letters, numbers, dot, underscore, colon, or dash).",
|
|
13
|
+
};
|
|
10
14
|
return {
|
|
11
15
|
get_uxp_capabilities: {
|
|
12
16
|
description: "Report the authenticated local UXP bridge connection and the capabilities advertised by the connected Premiere host.",
|
|
@@ -18,6 +22,88 @@ export function getUxpTools(bridge) {
|
|
|
18
22
|
parameters: {},
|
|
19
23
|
handler: async () => invoke(bridge, "state.get"),
|
|
20
24
|
},
|
|
25
|
+
inspect_project_uxp: {
|
|
26
|
+
description: "Read a compact, revisioned project and sequence snapshot through documented Premiere UXP APIs.",
|
|
27
|
+
parameters: {},
|
|
28
|
+
handler: async () => invoke(bridge, "project.snapshot"),
|
|
29
|
+
},
|
|
30
|
+
save_project_uxp: {
|
|
31
|
+
description: "Save the active project through UXP and require Premiere to confirm success.",
|
|
32
|
+
parameters: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: { operation_id: operationId },
|
|
35
|
+
},
|
|
36
|
+
handler: async (args) => invoke(bridge, "project.save", {
|
|
37
|
+
...(args.operation_id ? { operationId: args.operation_id } : {}),
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
create_sequence_with_preset_uxp: {
|
|
41
|
+
description: "Create and verify a sequence from a preset path using the documented Premiere 26.3+ UXP API.",
|
|
42
|
+
parameters: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
name: { type: "string", description: "New sequence name." },
|
|
46
|
+
preset_path: { type: "string", description: "Local Premiere sequence preset path." },
|
|
47
|
+
operation_id: operationId,
|
|
48
|
+
},
|
|
49
|
+
required: ["name", "preset_path"],
|
|
50
|
+
},
|
|
51
|
+
handler: async (args) => invoke(bridge, "sequence.createPreset", {
|
|
52
|
+
name: args.name, presetPath: args.preset_path,
|
|
53
|
+
...(args.operation_id ? { operationId: args.operation_id } : {}),
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
export_interchange_uxp: {
|
|
57
|
+
description: "Export the active sequence as OpenTimelineIO or Final Cut Pro XML with explicit verification.",
|
|
58
|
+
parameters: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
format: { type: "string", enum: ["otio", "fcpxml"], description: "Interchange format." },
|
|
62
|
+
output_file_path: { type: "string", description: "Absolute output file path." },
|
|
63
|
+
suppress_ui: { type: "boolean", description: "Suppress Premiere export UI; defaults to true." },
|
|
64
|
+
operation_id: operationId,
|
|
65
|
+
},
|
|
66
|
+
required: ["format", "output_file_path"],
|
|
67
|
+
},
|
|
68
|
+
handler: async (args) => invoke(bridge, "interchange.export", {
|
|
69
|
+
format: args.format, outputFilePath: args.output_file_path,
|
|
70
|
+
...(args.suppress_ui === undefined ? {} : { suppressUI: args.suppress_ui }),
|
|
71
|
+
...(args.operation_id ? { operationId: args.operation_id } : {}),
|
|
72
|
+
}),
|
|
73
|
+
},
|
|
74
|
+
get_transcript_languages_uxp: {
|
|
75
|
+
description: "List transcription languages supported by the connected Premiere 26.3+ host.",
|
|
76
|
+
parameters: {},
|
|
77
|
+
handler: async () => invoke(bridge, "transcript.languages"),
|
|
78
|
+
},
|
|
79
|
+
detect_object_masks_uxp: {
|
|
80
|
+
description: "Detect whether the active project or sequence contains an Object Mask using Premiere 26.3+.",
|
|
81
|
+
parameters: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: { scope: { type: "string", enum: ["sequence", "project"], description: "Inspection scope; defaults to sequence." } },
|
|
84
|
+
},
|
|
85
|
+
handler: async (args) => invoke(bridge, "objectMask.has", args.scope ? { scope: args.scope } : {}),
|
|
86
|
+
},
|
|
87
|
+
configure_encoder_uxp: {
|
|
88
|
+
description: "Launch or configure Adobe Media Encoder and optionally start its queued batch using Premiere 26.3+.",
|
|
89
|
+
parameters: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
launch: { type: "boolean", description: "Launch AME if needed." },
|
|
93
|
+
embedded_xmp: { type: "boolean", description: "Enable or disable embedded XMP." },
|
|
94
|
+
sidecar_xmp: { type: "boolean", description: "Enable or disable sidecar XMP." },
|
|
95
|
+
start_batch: { type: "boolean", description: "Start queued AME encodes." },
|
|
96
|
+
operation_id: operationId,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
handler: async (args) => invoke(bridge, "encoder.configure", {
|
|
100
|
+
...(args.launch === undefined ? {} : { launch: args.launch }),
|
|
101
|
+
...(args.embedded_xmp === undefined ? {} : { embeddedXmp: args.embedded_xmp }),
|
|
102
|
+
...(args.sidecar_xmp === undefined ? {} : { sidecarXmp: args.sidecar_xmp }),
|
|
103
|
+
...(args.start_batch === undefined ? {} : { startBatch: args.start_batch }),
|
|
104
|
+
...(args.operation_id ? { operationId: args.operation_id } : {}),
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
21
107
|
export_frame_uxp: {
|
|
22
108
|
description: "Export a sequence frame through Premiere's supported UXP Exporter and verify the output file in the host panel.",
|
|
23
109
|
parameters: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "premiere-pro-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Adobe Premiere Pro MCP server with 279 AI video editing tools
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "Adobe Premiere Pro MCP server with 279 core AI video editing tools and a capability-aware UXP bridge for supported Premiere workflows.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
@@ -75,13 +75,13 @@
|
|
|
75
75
|
"node": ">=20.19.0"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
78
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
79
79
|
"posthog-node": "^5.46.1",
|
|
80
80
|
"ws": "^8.21.1",
|
|
81
81
|
"zod": "^4.4.3"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@types/node": "^26.1.
|
|
84
|
+
"@types/node": "^26.1.2",
|
|
85
85
|
"@types/ws": "^8.18.1",
|
|
86
86
|
"typescript": "^7.0.2",
|
|
87
87
|
"vitest": "^4.1.10"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { cp, copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
4
5
|
import { spawn } from "node:child_process";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -22,6 +23,22 @@ const run = (command, args, cwd) =>
|
|
|
22
23
|
});
|
|
23
24
|
});
|
|
24
25
|
|
|
26
|
+
function runNpm(args, cwd) {
|
|
27
|
+
// On Windows, spawn("npm", ...) can fail because npm is exposed as npm.cmd
|
|
28
|
+
// rather than an executable. Running npm's bundled CLI through this Node
|
|
29
|
+
// process works on every supported platform and keeps shell execution off.
|
|
30
|
+
const npmCli = path.join(
|
|
31
|
+
path.dirname(process.execPath),
|
|
32
|
+
"node_modules",
|
|
33
|
+
"npm",
|
|
34
|
+
"bin",
|
|
35
|
+
"npm-cli.js",
|
|
36
|
+
);
|
|
37
|
+
return existsSync(npmCli)
|
|
38
|
+
? run(process.execPath, [npmCli, ...args], cwd)
|
|
39
|
+
: run("npm", args, cwd);
|
|
40
|
+
}
|
|
41
|
+
|
|
25
42
|
await rm(stage, { recursive: true, force: true });
|
|
26
43
|
await mkdir(path.join(stage, "server"), { recursive: true });
|
|
27
44
|
await mkdir(artifacts, { recursive: true });
|
|
@@ -49,7 +66,7 @@ await writeFile(
|
|
|
49
66
|
)}\n`,
|
|
50
67
|
);
|
|
51
68
|
|
|
52
|
-
await
|
|
69
|
+
await runNpm(["ci", "--omit=dev", "--ignore-scripts"], stage);
|
|
53
70
|
|
|
54
71
|
const mcpbPath = path.join(
|
|
55
72
|
artifacts,
|
|
@@ -60,9 +77,8 @@ const dxtPath = path.join(
|
|
|
60
77
|
`premiere-pro-mcp-${packageJson.version}.dxt`,
|
|
61
78
|
);
|
|
62
79
|
|
|
63
|
-
await
|
|
64
|
-
"
|
|
65
|
-
["-y", "@anthropic-ai/mcpb@2.1.2", "pack", stage, mcpbPath],
|
|
80
|
+
await runNpm(
|
|
81
|
+
["exec", "--yes", "@anthropic-ai/mcpb@2.1.2", "pack", stage, mcpbPath],
|
|
66
82
|
root,
|
|
67
83
|
);
|
|
68
84
|
await copyFile(mcpbPath, dxtPath);
|
package/uxp-plugin/commands.cjs
CHANGED
|
@@ -7,10 +7,18 @@
|
|
|
7
7
|
|
|
8
8
|
function createCommandRegistry(deps) {
|
|
9
9
|
const ppro = deps.ppro, Protocol = deps.Protocol;
|
|
10
|
+
const completedOperations = new Map();
|
|
10
11
|
const definitions = {
|
|
11
12
|
"capabilities.get": { readOnly: true, handler: capabilities },
|
|
12
13
|
"state.get": { readOnly: true, handler: stateSnapshot },
|
|
13
|
-
"
|
|
14
|
+
"project.snapshot": { readOnly: true, minHostVersion: "25.6.0", probe: canInspectProject, handler: projectSnapshot },
|
|
15
|
+
"project.save": { idempotent: true, minHostVersion: "25.6.0", probe: canSaveProject, handler: saveProject },
|
|
16
|
+
"sequence.createPreset": { destructive: true, undoable: false, minHostVersion: "26.3.0", probe: canCreatePresetSequence, handler: createPresetSequence },
|
|
17
|
+
"interchange.export": { minHostVersion: "26.2.0", probe: canExportInterchange, handler: exportInterchange },
|
|
18
|
+
"transcript.languages": { readOnly: true, minHostVersion: "26.3.0", probe: canQueryTranscriptLanguages, handler: transcriptLanguages },
|
|
19
|
+
"objectMask.has": { readOnly: true, minHostVersion: "26.3.0", probe: canInspectObjectMasks, handler: hasObjectMask },
|
|
20
|
+
"encoder.configure": { minHostVersion: "26.3.0", probe: canConfigureEncoder, handler: configureEncoder },
|
|
21
|
+
"frame.export": { minHostVersion: "25.6.0", probe: canExportFrame, handler: exportFrame },
|
|
14
22
|
"transition.video.list": { readOnly: true, minHostVersion: "25.6.0", probe: canListTransitions, handler: listTransitions },
|
|
15
23
|
"transition.video.add": { destructive: true, undoable: true, minHostVersion: "25.6.0", probe: canMutateTransitions, handler: addTransition },
|
|
16
24
|
"transition.video.remove": { destructive: true, undoable: true, minHostVersion: "25.6.0", probe: canMutateTransitions, handler: removeTransition }
|
|
@@ -19,19 +27,39 @@
|
|
|
19
27
|
async function dispatch(command, args) {
|
|
20
28
|
const definition = definitions[command];
|
|
21
29
|
if (!definition) throw commandError("UXP_UNSUPPORTED_COMMAND", "Unsupported UXP command: " + command);
|
|
22
|
-
if (definition.probe && !definition.probe()) throw commandError("UXP_COMMAND_UNAVAILABLE", command + " is not supported by this Premiere build");
|
|
23
|
-
|
|
30
|
+
if (definition.probe && !await definition.probe()) throw commandError("UXP_COMMAND_UNAVAILABLE", command + " is not supported by this Premiere build");
|
|
31
|
+
const input = args || {};
|
|
32
|
+
const operationId = validateOperationId(input.operationId);
|
|
33
|
+
const operationKey = operationId ? command + ":" + operationId : null;
|
|
34
|
+
if (!definition.readOnly && operationKey && completedOperations.has(operationKey)) {
|
|
35
|
+
return { ...completedOperations.get(operationKey), replayed: true };
|
|
36
|
+
}
|
|
37
|
+
const result = await definition.handler(input);
|
|
38
|
+
const envelope = definition.readOnly ? result : {
|
|
39
|
+
operationId: operationId || null,
|
|
40
|
+
outcome: result && result.outcome ? result.outcome : "verified",
|
|
41
|
+
...result
|
|
42
|
+
};
|
|
43
|
+
if (!definition.readOnly && operationKey) {
|
|
44
|
+
completedOperations.set(operationKey, envelope);
|
|
45
|
+
if (completedOperations.size > 256) completedOperations.delete(completedOperations.keys().next().value);
|
|
46
|
+
}
|
|
47
|
+
return envelope;
|
|
24
48
|
}
|
|
25
49
|
async function capabilities() {
|
|
26
50
|
let project = null, sequence = null;
|
|
27
51
|
try { project = await ppro.Project.getActiveProject(); sequence = project && await project.getActiveSequence(); } catch (_) {}
|
|
28
52
|
const commands = {};
|
|
29
|
-
Object.keys(definitions)
|
|
30
|
-
const definition = definitions[name], supported = !definition.probe || definition.probe();
|
|
31
|
-
commands[name] = {
|
|
53
|
+
for (const name of Object.keys(definitions)) {
|
|
54
|
+
const definition = definitions[name], supported = !definition.probe || await definition.probe();
|
|
55
|
+
commands[name] = {
|
|
56
|
+
supported, backend: "uxp", documented: true,
|
|
57
|
+
readOnly: !!definition.readOnly, destructive: !!definition.destructive,
|
|
58
|
+
undoable: !!definition.undoable, idempotent: !!definition.idempotent
|
|
59
|
+
};
|
|
32
60
|
if (definition.minHostVersion) commands[name].minHostVersion = definition.minHostVersion;
|
|
33
61
|
if (!supported) commands[name].reason = "Required Premiere UXP API is unavailable in this host";
|
|
34
|
-
}
|
|
62
|
+
}
|
|
35
63
|
return {
|
|
36
64
|
backend: "uxp", protocolVersion: Protocol.PROTOCOL_VERSION, hostMinVersion: "25.6.0",
|
|
37
65
|
activeProject: !!project, activeSequence: !!sequence, commands,
|
|
@@ -44,6 +72,73 @@
|
|
|
44
72
|
const position = sequence && await sequence.getPlayerPosition();
|
|
45
73
|
return { projectOpen: !!project, sequenceOpen: !!sequence, playheadSeconds: position ? position.seconds : null };
|
|
46
74
|
}
|
|
75
|
+
async function projectSnapshot() {
|
|
76
|
+
const project = await ppro.Project.getActiveProject();
|
|
77
|
+
if (!project) return { revision: "no-project", project: null, sequences: [] };
|
|
78
|
+
const sequences = Array.from(await project.getSequences() || []).map((sequence) => ({
|
|
79
|
+
guid: String(sequence.guid || ""), name: String(sequence.name || "")
|
|
80
|
+
}));
|
|
81
|
+
const active = await project.getActiveSequence();
|
|
82
|
+
const revision = simpleRevision([project.guid, project.name, project.path, active && active.guid, sequences.map((item) => item.guid).join(",")].join("|"));
|
|
83
|
+
return {
|
|
84
|
+
revision,
|
|
85
|
+
project: { guid: String(project.guid || ""), name: String(project.name || ""), path: String(project.path || "") },
|
|
86
|
+
activeSequenceGuid: active ? String(active.guid || "") : null,
|
|
87
|
+
sequences
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async function saveProject() {
|
|
91
|
+
const project = await ppro.Project.getActiveProject();
|
|
92
|
+
if (!project) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
|
|
93
|
+
const saved = await project.save();
|
|
94
|
+
if (!saved) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm the project save");
|
|
95
|
+
return { saved: true, outcome: "verified", projectGuid: String(project.guid || "") };
|
|
96
|
+
}
|
|
97
|
+
async function createPresetSequence(args) {
|
|
98
|
+
assertOnlyKeys(args, ["name", "presetPath", "operationId"]);
|
|
99
|
+
const name = requiredString(args.name, "name"), presetPath = requiredString(args.presetPath, "presetPath");
|
|
100
|
+
const project = await ppro.Project.getActiveProject();
|
|
101
|
+
if (!project) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
|
|
102
|
+
const sequence = await project.createSequenceWithPresetPath(name, presetPath);
|
|
103
|
+
if (!sequence) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return the created sequence");
|
|
104
|
+
const verified = Array.from(await project.getSequences() || []).some((item) => String(item.guid || "") === String(sequence.guid || ""));
|
|
105
|
+
if (!verified) throw commandError("UXP_VERIFICATION_FAILED", "Created sequence was not present in the project");
|
|
106
|
+
return { created: true, outcome: "verified", sequence: { guid: String(sequence.guid || ""), name: String(sequence.name || name) } };
|
|
107
|
+
}
|
|
108
|
+
async function exportInterchange(args) {
|
|
109
|
+
assertOnlyKeys(args, ["format", "outputFilePath", "suppressUI", "operationId"]);
|
|
110
|
+
const format = requiredString(args.format, "format"), outputFilePath = requiredString(args.outputFilePath, "outputFilePath");
|
|
111
|
+
if (format !== "otio" && format !== "fcpxml") throw commandError("UXP_INVALID_ARGUMENT", "format must be otio or fcpxml");
|
|
112
|
+
const context = await activeContext(false);
|
|
113
|
+
const method = format === "otio" ? "exportAsOpenTimelineIO" : "exportAsFinalCutProXML";
|
|
114
|
+
const exported = await ppro.ProjectConverter[method](context.sequence, outputFilePath, args.suppressUI !== false);
|
|
115
|
+
if (!exported) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm the interchange export");
|
|
116
|
+
return { exported: true, outcome: "verified", format, outputFilePath };
|
|
117
|
+
}
|
|
118
|
+
async function transcriptLanguages() {
|
|
119
|
+
const languages = Array.from(await ppro.Transcript.querySupportedLanguages() || []);
|
|
120
|
+
return { languages, count: languages.length };
|
|
121
|
+
}
|
|
122
|
+
async function hasObjectMask(args) {
|
|
123
|
+
assertOnlyKeys(args, ["scope"]);
|
|
124
|
+
const scope = args.scope == null ? "sequence" : args.scope;
|
|
125
|
+
if (scope !== "sequence" && scope !== "project") throw commandError("UXP_INVALID_ARGUMENT", "scope must be sequence or project");
|
|
126
|
+
const project = await ppro.Project.getActiveProject();
|
|
127
|
+
if (!project) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
|
|
128
|
+
const target = scope === "project" ? project : await project.getActiveSequence();
|
|
129
|
+
if (!target) throw commandError("UXP_NO_ACTIVE_SEQUENCE", "No active sequence");
|
|
130
|
+
return { scope, hasObjectMask: !!ppro.ObjectMaskUtils.hasObjectMask(target) };
|
|
131
|
+
}
|
|
132
|
+
async function configureEncoder(args) {
|
|
133
|
+
assertOnlyKeys(args, ["launch", "embeddedXmp", "sidecarXmp", "startBatch", "operationId"]);
|
|
134
|
+
const performed = [];
|
|
135
|
+
if (args.launch) { if (!await ppro.EncoderManager.launchEncoder()) throw commandError("UXP_VERIFICATION_FAILED", "AME did not launch"); performed.push("launch"); }
|
|
136
|
+
if (args.embeddedXmp != null) { await ppro.EncoderManager.setEmbeddedXMPEnabled(!!args.embeddedXmp); performed.push("embeddedXmp"); }
|
|
137
|
+
if (args.sidecarXmp != null) { await ppro.EncoderManager.setSidecarXMPEnabled(!!args.sidecarXmp); performed.push("sidecarXmp"); }
|
|
138
|
+
if (args.startBatch) { if (!await ppro.EncoderManager.startBatchEncode()) throw commandError("UXP_VERIFICATION_FAILED", "AME batch queue did not start"); performed.push("startBatch"); }
|
|
139
|
+
if (!performed.length) throw commandError("UXP_INVALID_ARGUMENT", "At least one encoder operation is required");
|
|
140
|
+
return { configured: true, outcome: "committed_unverified", performed };
|
|
141
|
+
}
|
|
47
142
|
async function tickTime(seconds, name) {
|
|
48
143
|
const value = Number(seconds);
|
|
49
144
|
if (!Number.isFinite(value) || value < 0) throw commandError("UXP_INVALID_ARGUMENT", name + " must be a non-negative number");
|
|
@@ -119,6 +214,24 @@
|
|
|
119
214
|
if (!committed) throw commandError("UXP_TRANSACTION_FAILED", "Premiere did not commit the transition transaction");
|
|
120
215
|
}
|
|
121
216
|
function canExportFrame() { return !!(ppro.Exporter && typeof ppro.Exporter.exportSequenceFrame === "function"); }
|
|
217
|
+
function canInspectProject() { return !!(ppro.Project && typeof ppro.Project.getActiveProject === "function"); }
|
|
218
|
+
async function activeProjectHas(name) {
|
|
219
|
+
if (!canInspectProject()) return false;
|
|
220
|
+
const project = await ppro.Project.getActiveProject();
|
|
221
|
+
return !project || typeof project[name] === "function";
|
|
222
|
+
}
|
|
223
|
+
function canSaveProject() { return activeProjectHas("save"); }
|
|
224
|
+
function canCreatePresetSequence() { return activeProjectHas("createSequenceWithPresetPath"); }
|
|
225
|
+
function canExportInterchange() {
|
|
226
|
+
return !!(ppro.ProjectConverter && typeof ppro.ProjectConverter.exportAsOpenTimelineIO === "function" &&
|
|
227
|
+
typeof ppro.ProjectConverter.exportAsFinalCutProXML === "function");
|
|
228
|
+
}
|
|
229
|
+
function canQueryTranscriptLanguages() { return !!(ppro.Transcript && typeof ppro.Transcript.querySupportedLanguages === "function"); }
|
|
230
|
+
function canInspectObjectMasks() { return !!(ppro.ObjectMaskUtils && typeof ppro.ObjectMaskUtils.hasObjectMask === "function"); }
|
|
231
|
+
function canConfigureEncoder() {
|
|
232
|
+
return !!(ppro.EncoderManager && typeof ppro.EncoderManager.launchEncoder === "function" &&
|
|
233
|
+
typeof ppro.EncoderManager.startBatchEncode === "function");
|
|
234
|
+
}
|
|
122
235
|
function canListTransitions() { return !!(ppro.TransitionFactory && typeof ppro.TransitionFactory.getVideoTransitionMatchNames === "function"); }
|
|
123
236
|
function canMutateTransitions() {
|
|
124
237
|
return canListTransitions() && typeof ppro.TransitionFactory.createVideoTransition === "function" &&
|
|
@@ -129,7 +242,7 @@
|
|
|
129
242
|
|
|
130
243
|
function validateAddArgs(args) {
|
|
131
244
|
assertObject(args);
|
|
132
|
-
assertOnlyKeys(args, ["videoTrackIndex", "clipIndex", "matchName", "position", "durationSeconds", "forceSingleSided", "transitionAlignment"]);
|
|
245
|
+
assertOnlyKeys(args, ["videoTrackIndex", "clipIndex", "matchName", "position", "durationSeconds", "forceSingleSided", "transitionAlignment", "operationId"]);
|
|
133
246
|
const result = targetArgs(args);
|
|
134
247
|
if (typeof args.matchName !== "string" || !args.matchName.trim() || args.matchName.length > 256) throw commandError("UXP_INVALID_ARGUMENT", "matchName must be a non-empty string of at most 256 characters");
|
|
135
248
|
result.matchName = args.matchName; result.position = optionalPosition(args.position);
|
|
@@ -145,7 +258,7 @@
|
|
|
145
258
|
return result;
|
|
146
259
|
}
|
|
147
260
|
function validateRemoveArgs(args) {
|
|
148
|
-
assertObject(args); assertOnlyKeys(args, ["videoTrackIndex", "clipIndex", "position"]);
|
|
261
|
+
assertObject(args); assertOnlyKeys(args, ["videoTrackIndex", "clipIndex", "position", "operationId"]);
|
|
149
262
|
const result = targetArgs(args); result.position = optionalPosition(args.position); return result;
|
|
150
263
|
}
|
|
151
264
|
function targetArgs(args) { return { videoTrackIndex: nonNegativeInt(args.videoTrackIndex, "videoTrackIndex"), clipIndex: nonNegativeInt(args.clipIndex, "clipIndex") }; }
|
|
@@ -155,6 +268,9 @@
|
|
|
155
268
|
function nonNegativeInt(value, name) { if (!Number.isInteger(value) || value < 0) throw commandError("UXP_INVALID_ARGUMENT", name + " must be a non-negative integer"); return value; }
|
|
156
269
|
function positiveNumber(value, name) { const number = Number(value); if (!Number.isFinite(number) || number <= 0) throw commandError("UXP_INVALID_ARGUMENT", name + " must be positive"); return number; }
|
|
157
270
|
function positiveInt(value, fallback, name) { return Math.round(positiveNumber(value == null ? fallback : value, name)); }
|
|
271
|
+
function requiredString(value, name) { if (typeof value !== "string" || !value.trim() || value.length > 4096) throw commandError("UXP_INVALID_ARGUMENT", name + " must be a non-empty string"); return value; }
|
|
272
|
+
function validateOperationId(value) { if (value == null) return null; if (typeof value !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(value)) throw commandError("UXP_INVALID_ARGUMENT", "operationId must be 1-128 safe characters"); return value; }
|
|
273
|
+
function simpleRevision(value) { var hash = 2166136261; for (var i = 0; i < value.length; i += 1) { hash ^= value.charCodeAt(i); hash = Math.imul(hash, 16777619); } return "uxp-" + (hash >>> 0).toString(16); }
|
|
158
274
|
function commandError(code, message) { const error = new Error(message); error.code = code; return error; }
|
|
159
275
|
return { createCommandRegistry, validateAddArgs, validateRemoveArgs, commandError };
|
|
160
276
|
});
|
package/uxp-plugin/manifest.json
CHANGED