- Adicionado estrutura completa do projeto - Configurado MCP server para Premiere Pro - Adicionado documentação e skills - Configurado Gitignore para o projeto
154 lines
6.3 KiB
JavaScript
Executable File
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
|
|
};
|
|
});
|