premiere-pro-mcp 1.16.1 → 1.16.2
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 +13 -0
- package/README.md +9 -9
- package/after-effects-cep-plugin/CSXS/manifest.xml +2 -2
- 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/package.json +1 -1
- package/public-product-manifest.json +1 -1
- package/scripts/build-uxp-ccx.mjs +50 -11
- package/uxp-plugin/DISTRIBUTION.md +11 -5
- package/uxp-plugin/commands.cjs +1 -1
- package/uxp-plugin/manifest.json +1 -1
- package/uxp-plugin/workspace.cjs +92 -14
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.16.2] - 2026-09-18
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Direct UXP `.ccx` packages now use a plugin-id bundle root and Unix 644/755
|
|
14
|
+
permission bits so Creative Cloud / UPI can extract plugin metadata. (#566)
|
|
15
|
+
- Path-based UXP commands now resolve native paths through the granted
|
|
16
|
+
workspace folder instead of advertising them as unsupported on every host.
|
|
17
|
+
(#567)
|
|
18
|
+
|
|
19
|
+
Automated checks do not establish licensed Premiere or After Effects playback or
|
|
20
|
+
rendered-output verification.
|
|
21
|
+
|
|
9
22
|
## [1.16.1] - 2026-09-17
|
|
10
23
|
|
|
11
24
|
### Added
|
package/README.md
CHANGED
|
@@ -108,10 +108,10 @@ The current source exposes 381 core tools for supported workflow steps spanning
|
|
|
108
108
|
|
|
109
109
|
<a id="latest-release"></a>
|
|
110
110
|
|
|
111
|
-
### Latest release: 1.16.
|
|
111
|
+
### Latest release: 1.16.2
|
|
112
112
|
|
|
113
|
-
The published v1.16.
|
|
114
|
-
and
|
|
113
|
+
The published v1.16.1 npm artifact contains **381 core tools**, 379 in its default profile,
|
|
114
|
+
and 474 with a compatible UXP connection. The development catalog above can include
|
|
115
115
|
unreleased work. See the [versioned facts and package provenance](https://premiere-pro-mcp.com/facts/).
|
|
116
116
|
|
|
117
117
|
### Try a bounded workflow
|
|
@@ -160,7 +160,7 @@ if the connection is unavailable.
|
|
|
160
160
|
local Premiere processes. See the generated [supported action catalog](docs/supported-actions.md)
|
|
161
161
|
for individual capability and verification contracts.
|
|
162
162
|
|
|
163
|
-
See the [v1.16.
|
|
163
|
+
See the [v1.16.2 release notes](https://github.com/leancoderkavy/premiere-pro-mcp/releases/tag/v1.16.2)
|
|
164
164
|
for complete details. Live installation in Premiere Pro still requires host verification.
|
|
165
165
|
|
|
166
166
|
### Current MCP protocol support
|
|
@@ -208,7 +208,7 @@ their bins, media rules, and organization rules before a facility uses one.
|
|
|
208
208
|
> ### Install the published package (verify the name)
|
|
209
209
|
>
|
|
210
210
|
> ```bash
|
|
211
|
-
> npm i -g premiere-pro-mcp@1.16.
|
|
211
|
+
> npm i -g premiere-pro-mcp@1.16.2
|
|
212
212
|
> ```
|
|
213
213
|
>
|
|
214
214
|
> 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:
|
|
@@ -216,7 +216,7 @@ their bins, media rules, and organization rules before a facility uses one.
|
|
|
216
216
|
> | Check | Expected |
|
|
217
217
|
> | --- | --- |
|
|
218
218
|
> | Package name | `premiere-pro-mcp` (not `adobe-premiere-pro-mcp`) |
|
|
219
|
-
> | Version | `1.16.
|
|
219
|
+
> | Version | `1.16.2` |
|
|
220
220
|
> | Homepage / repo | https://premiere-pro-mcp.com/ · https://github.com/leancoderkavy/premiere-pro-mcp |
|
|
221
221
|
>
|
|
222
222
|
> ```bash
|
|
@@ -229,9 +229,9 @@ their bins, media rules, and organization rules before a facility uses one.
|
|
|
229
229
|
|
|
230
230
|
### Easiest supported path: Claude Desktop
|
|
231
231
|
|
|
232
|
-
1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.16.
|
|
232
|
+
1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.16.2/premiere-pro-mcp-1.16.2.mcpb).
|
|
233
233
|
2. In Claude Desktop, open **Settings > Extensions > Advanced settings > Install Extension**, select the downloaded bundle, and restart Claude Desktop.
|
|
234
|
-
3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.16.
|
|
234
|
+
3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.16.2/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.
|
|
235
235
|
4. Restart Premiere, open a project, then open **Window > Extensions > MCP for Adobe Premiere Pro**.
|
|
236
236
|
5. In Claude, enter: `Safely check my Premiere connection with verify_premiere_connection. Make no changes.`
|
|
237
237
|
|
|
@@ -760,7 +760,7 @@ The bounded UXP migration surface also exposes guarded static Color effect-param
|
|
|
760
760
|
|
|
761
761
|
The bounded UXP migration surface also exposes guarded typed Project metadata schema-field creation. It requires the exact inspected active-project identity and 12 KiB-bounded panel XML, an explicit confirmation and operation ID, and serializes this bridge's schema and panel-replacement calls per project. Adobe provides no atomic compare-and-set or field-level schema getter: native acceptance and a changed panel XML are evidence only, so the command always returns `committed_unverified` and does not claim field presence, persistence, UI results, Undo, cancellation, or licensed-host validation.
|
|
762
762
|
|
|
763
|
-
The panel now requests access to one operator-selected workspace instead of declaring full filesystem access. Choose the folder in the panel before invoking a path-based UXP workflow. Media, relink, preset, export, and Source Monitor file paths must remain inside it; the persistent capability token and native root path are never returned over MCP.
|
|
763
|
+
The panel now requests access to one operator-selected workspace instead of declaring full filesystem access. Choose the folder in the panel before invoking a path-based UXP workflow. Media, relink, preset, export, and Source Monitor file paths must remain inside it; the persistent capability token and native root path are never returned over MCP. After a folder is granted, path-based commands resolve each native path through that folder's Entry tree and compare the host `nativePath` against the approved root, so symlink or junction targets outside the workspace still fail closed. If the host Entry cannot be walked and no resolver is supplied, those commands stay unsupported and the CEP fallback remains the path-based compatibility route.
|
|
764
764
|
|
|
765
765
|
Native transcript editing starts with a read-only, revision-locked planning flow. Use
|
|
766
766
|
`get_clip_transcript_uxp` to export the transcript Premiere generated for a source
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.16.
|
|
2
|
+
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.16.2" ExtensionBundleName="MCP for Adobe After Effects">
|
|
3
3
|
<ExtensionList>
|
|
4
|
-
<Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.16.
|
|
4
|
+
<Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.16.2"/>
|
|
5
5
|
</ExtensionList>
|
|
6
6
|
<ExecutionEnvironment>
|
|
7
7
|
<HostList>
|
|
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.16.
|
|
2
|
+
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.16.2" ExtensionBundleName="MCP for Adobe Premiere Pro">
|
|
3
3
|
<ExtensionList>
|
|
4
|
-
<Extension Id="com.mcp.premiere.bridge.panel" Version="1.16.
|
|
5
|
-
<Extension Id="com.mcp.premiere.bridge.headless" Version="1.16.
|
|
4
|
+
<Extension Id="com.mcp.premiere.bridge.panel" Version="1.16.2"/>
|
|
5
|
+
<Extension Id="com.mcp.premiere.bridge.headless" Version="1.16.2"/>
|
|
6
6
|
</ExtensionList>
|
|
7
7
|
<ExecutionEnvironment>
|
|
8
8
|
<HostList>
|
package/cep-plugin/index.html
CHANGED
|
@@ -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.16.
|
|
84
|
+
<strong id="updateTitle">Version 1.16.2</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>
|
package/cep-plugin/updater.cjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "premiere-pro-mcp",
|
|
3
3
|
"mcpName": "io.github.leancoderkavy/premiere-pro",
|
|
4
|
-
"version": "1.16.
|
|
4
|
+
"version": "1.16.2",
|
|
5
5
|
"description": "MCP server for Adobe Premiere Pro with 381 core AI video editing tools, a guarded After Effects MOGRT studio, and a capability-aware UXP bridge for supported Premiere workflows.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "MCP for Adobe Premiere Pro",
|
|
11
11
|
"mcpName": "io.github.leancoderkavy/premiere-pro",
|
|
12
12
|
"npmPackage": "premiere-pro-mcp",
|
|
13
|
-
"version": "1.16.
|
|
13
|
+
"version": "1.16.2",
|
|
14
14
|
"repository": "https://github.com/leancoderkavy/premiere-pro-mcp",
|
|
15
15
|
"homepage": "https://premiere-pro-mcp.com/",
|
|
16
16
|
"license": "MIT",
|
|
@@ -41,6 +41,27 @@ function u32(value) {
|
|
|
41
41
|
return value;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function unixExternalAttributes(kind) {
|
|
45
|
+
const mode = kind === "directory" ? 0o040755 : 0o100644;
|
|
46
|
+
const dos = kind === "directory" ? 0x10 : 0x20;
|
|
47
|
+
return u32(((mode * 0x10000) + dos) >>> 0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function withBundleRoot(files, pluginId) {
|
|
51
|
+
const prefix = `${pluginId}/`;
|
|
52
|
+
const directories = new Set([prefix]);
|
|
53
|
+
const prefixed = files.map((file) => {
|
|
54
|
+
const name = `${prefix}${file.name.replaceAll("\\", "/")}`;
|
|
55
|
+
const segments = name.split("/");
|
|
56
|
+
for (let index = 1; index < segments.length; index += 1) {
|
|
57
|
+
directories.add(`${segments.slice(0, index).join("/")}/`);
|
|
58
|
+
}
|
|
59
|
+
return { name, data: file.data, kind: "file" };
|
|
60
|
+
});
|
|
61
|
+
const dirEntries = [...directories].map((name) => ({ name, data: Buffer.alloc(0), kind: "directory" }));
|
|
62
|
+
return [...dirEntries, ...prefixed].sort((left, right) => left.name.localeCompare(right.name));
|
|
63
|
+
}
|
|
64
|
+
|
|
44
65
|
async function collectFiles(directory, relative = "") {
|
|
45
66
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
46
67
|
const files = [];
|
|
@@ -66,8 +87,10 @@ function buildStoredZip(files) {
|
|
|
66
87
|
let offset = 0;
|
|
67
88
|
|
|
68
89
|
for (const file of files) {
|
|
90
|
+
const isDirectory = file.kind === "directory" || file.name.endsWith("/");
|
|
91
|
+
const data = isDirectory ? Buffer.alloc(0) : file.data;
|
|
69
92
|
const name = Buffer.from(file.name, "utf8");
|
|
70
|
-
const checksum = crc32(
|
|
93
|
+
const checksum = crc32(data);
|
|
71
94
|
const local = Buffer.alloc(30);
|
|
72
95
|
local.writeUInt32LE(0x04034b50, 0);
|
|
73
96
|
local.writeUInt16LE(20, 4);
|
|
@@ -76,8 +99,8 @@ function buildStoredZip(files) {
|
|
|
76
99
|
local.writeUInt16LE(0, 10);
|
|
77
100
|
local.writeUInt16LE(0x0021, 12);
|
|
78
101
|
local.writeUInt32LE(u32(checksum), 14);
|
|
79
|
-
local.writeUInt32LE(u32(
|
|
80
|
-
local.writeUInt32LE(u32(
|
|
102
|
+
local.writeUInt32LE(u32(data.length), 18);
|
|
103
|
+
local.writeUInt32LE(u32(data.length), 22);
|
|
81
104
|
local.writeUInt16LE(u16(name.length), 26);
|
|
82
105
|
local.writeUInt16LE(0, 28);
|
|
83
106
|
|
|
@@ -90,19 +113,19 @@ function buildStoredZip(files) {
|
|
|
90
113
|
central.writeUInt16LE(0, 12);
|
|
91
114
|
central.writeUInt16LE(0x0021, 14);
|
|
92
115
|
central.writeUInt32LE(u32(checksum), 16);
|
|
93
|
-
central.writeUInt32LE(u32(
|
|
94
|
-
central.writeUInt32LE(u32(
|
|
116
|
+
central.writeUInt32LE(u32(data.length), 20);
|
|
117
|
+
central.writeUInt32LE(u32(data.length), 24);
|
|
95
118
|
central.writeUInt16LE(u16(name.length), 28);
|
|
96
119
|
central.writeUInt16LE(0, 30);
|
|
97
120
|
central.writeUInt16LE(0, 32);
|
|
98
121
|
central.writeUInt16LE(0, 34);
|
|
99
122
|
central.writeUInt16LE(0, 36);
|
|
100
|
-
central.writeUInt32LE(
|
|
123
|
+
central.writeUInt32LE(unixExternalAttributes(isDirectory ? "directory" : "file"), 38);
|
|
101
124
|
central.writeUInt32LE(u32(offset), 42);
|
|
102
125
|
|
|
103
|
-
localRecords.push(local, name,
|
|
126
|
+
localRecords.push(local, name, data);
|
|
104
127
|
centralRecords.push(central, name);
|
|
105
|
-
offset += local.length + name.length +
|
|
128
|
+
offset += local.length + name.length + data.length;
|
|
106
129
|
}
|
|
107
130
|
|
|
108
131
|
const centralDirectory = Buffer.concat(centralRecords);
|
|
@@ -118,7 +141,7 @@ function buildStoredZip(files) {
|
|
|
118
141
|
return Buffer.concat([...localRecords, centralDirectory, end]);
|
|
119
142
|
}
|
|
120
143
|
|
|
121
|
-
function verifyStoredZip(archive, expectedNames) {
|
|
144
|
+
function verifyStoredZip(archive, expectedNames, pluginId) {
|
|
122
145
|
const endOffset = archive.length - 22;
|
|
123
146
|
assert(endOffset >= 0 && archive.readUInt32LE(endOffset) === 0x06054b50, "archive is missing a ZIP end record");
|
|
124
147
|
assert(archive.readUInt16LE(endOffset + 20) === 0, "archive must not contain a ZIP comment");
|
|
@@ -128,20 +151,33 @@ function verifyStoredZip(archive, expectedNames) {
|
|
|
128
151
|
assert(cursor + centralSize === endOffset, "archive central directory has an unexpected length");
|
|
129
152
|
assert(entryCount === expectedNames.length, "archive entry count does not match the staged plugin files");
|
|
130
153
|
const names = [];
|
|
154
|
+
const bundleRoot = `${pluginId}/`;
|
|
131
155
|
|
|
132
156
|
for (let index = 0; index < entryCount; index += 1) {
|
|
133
157
|
assert(archive.readUInt32LE(cursor) === 0x02014b50, "archive central directory entry is invalid");
|
|
134
158
|
assert(archive.readUInt16LE(cursor + 10) === 0, "archive must use stored ZIP entries");
|
|
159
|
+
assert((archive.readUInt16LE(cursor + 4) >>> 8) === 3, "archive entries must declare a Unix origin");
|
|
135
160
|
const checksum = archive.readUInt32LE(cursor + 16);
|
|
136
161
|
const compressedSize = archive.readUInt32LE(cursor + 20);
|
|
137
162
|
const uncompressedSize = archive.readUInt32LE(cursor + 24);
|
|
138
163
|
const nameLength = archive.readUInt16LE(cursor + 28);
|
|
139
164
|
const extraLength = archive.readUInt16LE(cursor + 30);
|
|
140
165
|
const commentLength = archive.readUInt16LE(cursor + 32);
|
|
166
|
+
const externalAttributes = archive.readUInt32LE(cursor + 38);
|
|
141
167
|
const localOffset = archive.readUInt32LE(cursor + 42);
|
|
142
168
|
const name = archive.subarray(cursor + 46, cursor + 46 + nameLength).toString("utf8");
|
|
143
169
|
assert(!names.includes(name), `archive contains duplicate entry ${name}`);
|
|
144
170
|
names.push(name);
|
|
171
|
+
assert(name === bundleRoot || name.startsWith(bundleRoot), `archive entry ${name} must stay under ${bundleRoot}`);
|
|
172
|
+
const unixMode = externalAttributes >>> 16;
|
|
173
|
+
const isDirectory = name.endsWith("/");
|
|
174
|
+
if (isDirectory) {
|
|
175
|
+
assert((unixMode & 0o170000) === 0o040000, `archive directory ${name} must use Unix directory bits`);
|
|
176
|
+
assert((unixMode & 0o777) === 0o755, `archive directory ${name} must be mode 755`);
|
|
177
|
+
} else {
|
|
178
|
+
assert((unixMode & 0o170000) === 0o100000, `archive file ${name} must use Unix regular-file bits`);
|
|
179
|
+
assert((unixMode & 0o777) === 0o644, `archive file ${name} must be mode 644`);
|
|
180
|
+
}
|
|
145
181
|
assert(archive.readUInt32LE(localOffset) === 0x04034b50, `archive local entry is invalid for ${name}`);
|
|
146
182
|
const localNameLength = archive.readUInt16LE(localOffset + 26);
|
|
147
183
|
const localExtraLength = archive.readUInt16LE(localOffset + 28);
|
|
@@ -157,6 +193,7 @@ function verifyStoredZip(archive, expectedNames) {
|
|
|
157
193
|
cursor += 46 + nameLength + extraLength + commentLength;
|
|
158
194
|
}
|
|
159
195
|
|
|
196
|
+
assert(names.includes(bundleRoot), `archive must contain the ${bundleRoot} directory`);
|
|
160
197
|
assert(
|
|
161
198
|
JSON.stringify(names.sort()) === JSON.stringify([...expectedNames].sort()),
|
|
162
199
|
"archive contents do not match the staged plugin files",
|
|
@@ -193,11 +230,13 @@ async function main() {
|
|
|
193
230
|
data: Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8"),
|
|
194
231
|
};
|
|
195
232
|
files.sort((left, right) => left.name.localeCompare(right.name));
|
|
233
|
+
const packaged = withBundleRoot(files, pluginId);
|
|
196
234
|
|
|
197
|
-
const archive = buildStoredZip(
|
|
235
|
+
const archive = buildStoredZip(packaged);
|
|
198
236
|
verifyStoredZip(
|
|
199
237
|
archive,
|
|
200
|
-
|
|
238
|
+
packaged.map((file) => file.name),
|
|
239
|
+
pluginId,
|
|
201
240
|
);
|
|
202
241
|
await mkdir(artifacts, { recursive: true });
|
|
203
242
|
const output = path.join(
|
|
@@ -21,14 +21,20 @@ This writes:
|
|
|
21
21
|
artifacts/premiere-pro-mcp-uxp-<version>-direct.ccx
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
The builder uses a fixed ZIP timestamp, lexical file order, and stored entries
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
The builder uses a fixed ZIP timestamp, lexical file order, and stored entries
|
|
25
|
+
under a single bundle root (`<plugin-id>/manifest.json`), with Unix file mode
|
|
26
|
+
644 and directory mode 755. It then reads the generated central directory and
|
|
27
|
+
checks every entry, CRC, bundle prefix, and permission bits. The result is
|
|
28
|
+
reproducible for identical source bytes. It also rejects symlinks, requires
|
|
29
|
+
the UXP v5 Premiere manifest, and checks the version against `package.json`.
|
|
30
|
+
Adobe's Premiere 26.3 runtime requires the compatible `domains: "all"`
|
|
29
31
|
manifest form for the WebSocket connection; `workspace.cjs` remains the enforced authority and
|
|
30
32
|
accepts only `ws://127.0.0.1:<port>/uxp` or `ws://localhost:<port>/uxp`.
|
|
31
33
|
|
|
34
|
+
Adobe's Unified Plugin Installer accepts `--install <path>`. The
|
|
35
|
+
`--install="<path>"` form can exit 0 without installing; use the space-separated
|
|
36
|
+
form when diagnosing a local CCX.
|
|
37
|
+
|
|
32
38
|
For an Adobe Creative Cloud Marketplace submission, Adobe Developer
|
|
33
39
|
Distribution must first create the Marketplace plugin ID. Build a separate
|
|
34
40
|
package without changing the checked-in direct manifest:
|
package/uxp-plugin/commands.cjs
CHANGED
|
@@ -239,7 +239,7 @@
|
|
|
239
239
|
if (definition.conditionalWorkspace) commands[name].workspaceRequired = "path_variant_only";
|
|
240
240
|
if (definition.targetCapabilityProbe) commands[name].targetCapabilityProbe = "invocation";
|
|
241
241
|
if (!apiSupported) commands[name].reason = "Required Premiere UXP API is unavailable in this host";
|
|
242
|
-
else if (!pathValidationSupported) commands[name].reason = "
|
|
242
|
+
else if (!pathValidationSupported) commands[name].reason = "canonical path validation is not implemented in this build; use the CEP fallback for path-based workflows";
|
|
243
243
|
}
|
|
244
244
|
return {
|
|
245
245
|
backend: "uxp", protocolVersion: Protocol.PROTOCOL_VERSION, hostMinVersion: "25.6.0",
|
package/uxp-plugin/manifest.json
CHANGED
package/uxp-plugin/workspace.cjs
CHANGED
|
@@ -74,6 +74,45 @@
|
|
|
74
74
|
return url;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function nativePathOf(fs, entry) {
|
|
78
|
+
if (entry && typeof entry.nativePath === "string" && entry.nativePath) return entry.nativePath;
|
|
79
|
+
if (entry && fs && typeof fs.getNativePath === "function") return fs.getNativePath(entry);
|
|
80
|
+
return "";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function toFileUrl(nativePath) {
|
|
84
|
+
const parsed = parseAbsolutePath(nativePath, "path");
|
|
85
|
+
if (parsed.kind === "windows") return "file:/" + parsed.normalized;
|
|
86
|
+
return "file:" + parsed.normalized;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createCanonicalPathResolver(deps) {
|
|
90
|
+
const fs = deps && deps.fs;
|
|
91
|
+
if (!fs || typeof fs.getEntryWithUrl !== "function") return undefined;
|
|
92
|
+
return async function resolveCanonicalPath(value, options) {
|
|
93
|
+
const label = options && options.label || "path";
|
|
94
|
+
const kind = options && options.kind || "file";
|
|
95
|
+
let entry;
|
|
96
|
+
try {
|
|
97
|
+
entry = await fs.getEntryWithUrl(toFileUrl(value));
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error && /^UXP_[A-Z0-9_]+$/.test(error.code || "")) throw error;
|
|
100
|
+
throw workspaceError("UXP_CANONICAL_PATH_UNAVAILABLE", "This UXP host could not resolve " + label + " to a canonical filesystem path");
|
|
101
|
+
}
|
|
102
|
+
const native = nativePathOf(fs, entry);
|
|
103
|
+
if (!native) {
|
|
104
|
+
throw workspaceError("UXP_CANONICAL_PATH_UNAVAILABLE", "This UXP host could not resolve " + label + " to a canonical filesystem path");
|
|
105
|
+
}
|
|
106
|
+
if (kind === "directory" && entry && entry.isFile) {
|
|
107
|
+
throw workspaceError("UXP_INVALID_ARGUMENT", label + " must be a directory");
|
|
108
|
+
}
|
|
109
|
+
if (kind === "file" && entry && entry.isFolder) {
|
|
110
|
+
throw workspaceError("UXP_INVALID_ARGUMENT", label + " must be a file");
|
|
111
|
+
}
|
|
112
|
+
return parseAbsolutePath(native, label).normalized;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
77
116
|
function createWorkspaceBroker(deps) {
|
|
78
117
|
const fs = deps && deps.fs;
|
|
79
118
|
const resolveCanonicalPath = deps && deps.resolveCanonicalPath;
|
|
@@ -82,9 +121,7 @@
|
|
|
82
121
|
let initialized = false;
|
|
83
122
|
|
|
84
123
|
function nativePathFor(entry) {
|
|
85
|
-
|
|
86
|
-
if (entry && fs && typeof fs.getNativePath === "function") return fs.getNativePath(entry);
|
|
87
|
-
return "";
|
|
124
|
+
return nativePathOf(fs, entry);
|
|
88
125
|
}
|
|
89
126
|
|
|
90
127
|
async function dataFolder() {
|
|
@@ -168,6 +205,10 @@
|
|
|
168
205
|
return status();
|
|
169
206
|
}
|
|
170
207
|
|
|
208
|
+
function canWalkGrantedFolder() {
|
|
209
|
+
return !!(rootEntry && typeof rootEntry.getEntry === "function");
|
|
210
|
+
}
|
|
211
|
+
|
|
171
212
|
function status() {
|
|
172
213
|
return {
|
|
173
214
|
configured: !!rootEntry,
|
|
@@ -175,10 +216,46 @@
|
|
|
175
216
|
rootName: rootEntry && typeof rootEntry.name === "string" ? rootEntry.name : null,
|
|
176
217
|
persistent: !!persistentToken,
|
|
177
218
|
pathDisclosure: "redacted",
|
|
178
|
-
canonicalPathValidation: typeof resolveCanonicalPath === "function" ? "available" : "unavailable"
|
|
219
|
+
canonicalPathValidation: typeof resolveCanonicalPath === "function" || canWalkGrantedFolder() ? "available" : "unavailable"
|
|
179
220
|
};
|
|
180
221
|
}
|
|
181
222
|
|
|
223
|
+
function relativeSegments(rootPath, candidatePath, label) {
|
|
224
|
+
const root = parseAbsolutePath(rootPath, "workspace root");
|
|
225
|
+
const candidate = parseAbsolutePath(candidatePath, label);
|
|
226
|
+
if (candidate.comparison === root.comparison) return [];
|
|
227
|
+
if (candidate.comparison.indexOf(root.comparison + "/") !== 0) {
|
|
228
|
+
throw workspaceError("UXP_PATH_OUTSIDE_WORKSPACE", label + " must stay inside the approved workspace folder");
|
|
229
|
+
}
|
|
230
|
+
return candidate.normalized.slice(root.normalized.length).replace(/^\//, "").split("/").filter(Boolean);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function resolveThroughGrantedFolder(candidatePath, options) {
|
|
234
|
+
const label = options && options.label || "path";
|
|
235
|
+
if (!canWalkGrantedFolder()) {
|
|
236
|
+
throw workspaceError("UXP_CANONICAL_PATH_UNAVAILABLE", "Canonical path validation is not implemented in this build; use the CEP fallback for path-based workflows");
|
|
237
|
+
}
|
|
238
|
+
const rootPath = nativePathFor(rootEntry);
|
|
239
|
+
const segments = relativeSegments(rootPath, candidatePath, label);
|
|
240
|
+
let entry = rootEntry;
|
|
241
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
242
|
+
if (!entry || typeof entry.getEntry !== "function") {
|
|
243
|
+
throw workspaceError("UXP_CANONICAL_PATH_UNAVAILABLE", "Canonical path validation is not implemented in this build; use the CEP fallback for path-based workflows");
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
entry = await entry.getEntry(segments[index]);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
if (error && /^UXP_[A-Z0-9_]+$/.test(error.code || "")) throw error;
|
|
249
|
+
throw workspaceError("UXP_CANONICAL_PATH_UNAVAILABLE", "This UXP host could not resolve " + label + " to a canonical filesystem path");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const native = nativePathFor(entry);
|
|
253
|
+
if (!native) {
|
|
254
|
+
throw workspaceError("UXP_CANONICAL_PATH_UNAVAILABLE", "This UXP host could not resolve " + label + " to a canonical filesystem path");
|
|
255
|
+
}
|
|
256
|
+
return parseAbsolutePath(native, label).normalized;
|
|
257
|
+
}
|
|
258
|
+
|
|
182
259
|
async function assertPathAllowed(value, options) {
|
|
183
260
|
const label = options && options.label || "path";
|
|
184
261
|
const kind = options && options.kind || "file";
|
|
@@ -190,17 +267,18 @@
|
|
|
190
267
|
if (!isContained(rootPath, candidate.normalized, kind === "directory")) {
|
|
191
268
|
throw workspaceError("UXP_PATH_OUTSIDE_WORKSPACE", label + " must stay inside the approved workspace folder");
|
|
192
269
|
}
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
// native paths must fail closed unless the embedding host supplies one.
|
|
197
|
-
if (typeof resolveCanonicalPath !== "function") {
|
|
198
|
-
throw workspaceError("UXP_CANONICAL_PATH_UNAVAILABLE", "This UXP host cannot prove that " + label + " stays inside the approved workspace after resolving filesystem links");
|
|
199
|
-
}
|
|
270
|
+
// Lexical containment cannot detect symlink, junction, or reparse-point
|
|
271
|
+
// escapes. Prefer an embedder-supplied resolver; otherwise walk from the
|
|
272
|
+
// granted folder Entry so nativePath reflects the host's resolved target.
|
|
200
273
|
let canonicalRootValue, canonicalCandidateValue;
|
|
201
274
|
try {
|
|
202
|
-
|
|
203
|
-
|
|
275
|
+
if (typeof resolveCanonicalPath === "function") {
|
|
276
|
+
canonicalRootValue = await resolveCanonicalPath(rootPath, { label: "workspace root", kind: "directory" });
|
|
277
|
+
canonicalCandidateValue = await resolveCanonicalPath(candidate.normalized, { label, kind });
|
|
278
|
+
} else {
|
|
279
|
+
canonicalRootValue = await resolveThroughGrantedFolder(rootPath, { label: "workspace root", kind: "directory" });
|
|
280
|
+
canonicalCandidateValue = await resolveThroughGrantedFolder(candidate.normalized, { label, kind });
|
|
281
|
+
}
|
|
204
282
|
} catch (error) {
|
|
205
283
|
if (error && /^UXP_[A-Z0-9_]+$/.test(error.code || "")) throw error;
|
|
206
284
|
throw workspaceError("UXP_CANONICAL_PATH_UNAVAILABLE", "This UXP host could not resolve " + label + " to a canonical filesystem path");
|
|
@@ -216,5 +294,5 @@
|
|
|
216
294
|
return { initialize, requestRoot, revoke, status, assertPathAllowed };
|
|
217
295
|
}
|
|
218
296
|
|
|
219
|
-
return { createWorkspaceBroker, parseAbsolutePath, isContained, validateLoopbackBridgeUrl, workspaceError };
|
|
297
|
+
return { createWorkspaceBroker, createCanonicalPathResolver, parseAbsolutePath, isContained, validateLoopbackBridgeUrl, workspaceError };
|
|
220
298
|
});
|