Files
jhonny-editor/bm/premiere-pro-mcp-main/uxp-plugin/protocol.cjs
T
João Henrique b541f502ba feat: initial commit - Jhonny Editor
- Adicionado estrutura completa do projeto
- Configurado MCP server para Premiere Pro
- Adicionado documentação e skills
- Configurado Gitignore para o projeto
2026-09-08 09:59:31 -04:00

154 lines
6.3 KiB
JavaScript
Executable File

(function (root, factory) {
const api = factory();
if (typeof module === "object" && module.exports) module.exports = api;
root.PremiereMcpProtocol = api;
})(typeof globalThis !== "undefined" ? globalThis : this, function () {
"use strict";
const PROTOCOL_VERSION = 2;
const MAX_COMMAND_BYTES = 64 * 1024;
const MAX_RESULT_BYTES = 1024 * 1024;
const COMMAND_NAME = /^[a-z][A-Za-z0-9]*(?:\.[a-z][A-Za-z0-9]*)+$/;
function envelope(type, payload, requestId) {
const value = { protocolVersion: PROTOCOL_VERSION, type, payload: payload || {}, sentAt: new Date().toISOString() };
if (requestId) value.requestId = requestId;
return value;
}
function utf8ByteLength(value) {
const text = String(value);
let bytes = 0;
for (let index = 0; index < text.length; index += 1) {
const code = text.charCodeAt(index);
if (code < 0x80) bytes += 1;
else if (code < 0x800) bytes += 2;
else if (code >= 0xd800 && code <= 0xdbff && index + 1 < text.length && text.charCodeAt(index + 1) >= 0xdc00 && text.charCodeAt(index + 1) <= 0xdfff) {
bytes += 4;
index += 1;
} else bytes += 3;
}
return bytes;
}
function resultTooLarge() {
const error = new Error("UXP bridge result exceeds 1 MiB after UTF-8 serialization");
error.code = "UXP_RESULT_TOO_LARGE";
return error;
}
function serializeEnvelope(value) {
const encoded = JSON.stringify(value);
if (utf8ByteLength(encoded) > MAX_RESULT_BYTES) throw resultTooLarge();
return encoded;
}
function assertResultSize(result) {
// Use the longest legal request id and the same fallback operation payload
// dispatch adds to read-only command results, so this is conservative for
// metadata snapshots before the exact final envelope is serialized.
serializeEnvelope(envelope("result", {
ok: true,
result,
operation: operationSemantics({
mutatesProject: false,
verificationStatus: "verified",
verificationBoundary: "host_snapshot"
})
}, "x".repeat(128)));
return result;
}
function parseCommand(raw) {
if (typeof raw === "string" && utf8ByteLength(raw) > MAX_COMMAND_BYTES) throw new Error("UXP bridge command exceeds 64 KiB");
const value = typeof raw === "string" ? JSON.parse(raw) : raw;
if (!isPlainObject(value) || value.type !== "command" || typeof value.command !== "string" || !COMMAND_NAME.test(value.command)) throw new Error("Invalid UXP bridge command");
if (value.protocolVersion != null && value.protocolVersion !== PROTOCOL_VERSION) throw new Error("Unsupported UXP protocol version: " + value.protocolVersion);
if (value.requestId != null && (typeof value.requestId !== "string" || value.requestId.length < 1 || value.requestId.length > 128)) throw new Error("requestId must be a non-empty string of at most 128 characters");
if (value.args != null && !isPlainObject(value.args)) throw new Error("command args must be an object");
return { requestId: value.requestId || null, command: value.command, args: value.args || {} };
}
function isPlainObject(value) { return !!value && typeof value === "object" && !Array.isArray(value); }
function operationEvent(name, operation, detail) {
return envelope("event", {
name: "premiere.operation." + name,
operation: Object.assign({
requestId: operation.requestId,
command: operation.command
}, detail || {})
}, operation.requestId);
}
function operationSemantics(options) {
const value = options || {};
return {
mutatesProject: value.mutatesProject === true,
verification: {
status: value.verificationStatus || "not_verified",
boundary: value.verificationBoundary || "command_return_value",
evidence: value.verificationEvidence || []
},
undo: {
supported: value.undoSupported === true,
boundary: value.undoSupported === true ? "premiere_undo_history" : "not_undoable",
label: value.undoLabel || null
},
transaction: {
actionGroup: value.transactionActionGroup === true,
boundary: value.transactionActionGroup === true ? "project_executeTransaction" : "single_host_call",
atomicRollback: false
},
cancellation: {
supported: value.cancellationSupported === true,
boundary: value.cancellationBoundary || "before_non_cancellable_host_call"
}
};
}
function createOperationTracker() {
const operations = new Map();
return {
begin(requestId, command) {
const operation = {
requestId: requestId || null,
command,
phase: "preflight",
cancelRequested: false,
complete: false
};
if (requestId) operations.set(requestId, operation);
return operation;
},
get(requestId) { return requestId ? operations.get(requestId) || null : null; },
requestCancel(requestId) {
const operation = this.get(requestId);
if (!operation || operation.complete) return { accepted: false, reason: "operation_not_active" };
if (operation.phase !== "preflight") return { accepted: false, reason: "host_call_not_cancellable" };
operation.cancelRequested = true;
return { accepted: true, reason: "cancellation_requested" };
},
finish(operation) {
operation.complete = true;
if (operation.requestId) operations.delete(operation.requestId);
}
};
}
function safeFilename(value) {
const name = String(value || "mcp-frame.png");
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*\.png$/i.test(name)) throw new Error("filename must be a simple .png name");
return name;
}
// Premiere's frame exporter selects PNG from the name and appends that extension
// itself. Keep the public, fully qualified filename for reporting, but pass the
// bare stem to the host so `frame.png` does not become `frame.png.png`.
function exporterFrameName(value) { return safeFilename(value).replace(/\.png$/i, ""); }
function joinPath(dir, name) { return /[\\\/]$/.test(dir) ? dir + name : dir + "/" + name; }
return {
PROTOCOL_VERSION,
MAX_COMMAND_BYTES,
MAX_RESULT_BYTES,
envelope,
utf8ByteLength,
serializeEnvelope,
assertResultSize,
parseCommand,
operationEvent,
operationSemantics,
createOperationTracker,
safeFilename,
exporterFrameName,
joinPath
};
});