Files
jhonny-editor/code/src/platform-capabilities.ts
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

434 lines
16 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createRequire } from "node:module";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { Capability, CapabilityConfig } from "./security/capabilities.js";
import {
buildToolCapabilityReport,
type CatalogToolDefinition,
} from "./tool-capability-report.js";
import type { ToolPackReport } from "./workflows/tool-packs.js";
export const SUPPORTED_HOST_PLATFORMS = ["darwin", "win32"] as const;
const ALL_CAPABILITIES: Capability[] = ["inspect", "edit", "export", "filesystem", "unsafe-script"];
const require = createRequire(import.meta.url);
const adobeUxpCoverage: unknown = require("./resources/adobe-uxp-coverage.json");
export type SupportedHostPlatform = (typeof SUPPORTED_HOST_PLATFORMS)[number];
export type AdobeUxpCoverageAvailability = "current" | "planned";
export type AdobeUxpCoverageImplementationStatus = "implemented" | "planned";
export type AdobeUxpCoverageVerificationStatus =
| "automated_contract_verified"
| "committed_unverified"
| "not_started";
export type AdobeUxpCoverageLiveHostVerificationStatus = "not_run" | "verified";
export interface AdobeUxpCoverageEntry {
id: string;
adobeApi: string[];
documentationUrls: string[];
minimumPremiereVersion: string;
backend: "uxp";
uxpCommand: string | null;
mcpTools: string[];
availability: AdobeUxpCoverageAvailability;
implementationStatus: AdobeUxpCoverageImplementationStatus;
verificationStatus: AdobeUxpCoverageVerificationStatus;
liveHostVerificationStatus: AdobeUxpCoverageLiveHostVerificationStatus;
mutatesProject: boolean;
undoable: boolean;
idempotency: "operation_id" | "operation_id_when_supported" | "not_applicable";
verificationBoundary: string;
}
export interface AdobeUxpCoverageManifest {
schemaVersion: 1;
source: {
apiPackage: "@adobe/premierepro";
apiVersion: string;
changelogUrl: string;
};
entries: AdobeUxpCoverageEntry[];
}
export interface AdobeUxpCoverageReport extends AdobeUxpCoverageManifest {
summary: {
total: number;
current: number;
planned: number;
implemented: number;
committedUnverified: number;
automatedContractVerified: number;
liveHostVerified: number;
};
}
function asRecord(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error(`${label} must be an object`);
}
return value as Record<string, unknown>;
}
function stringArray(value: unknown, label: string): string[] {
if (!Array.isArray(value) || value.length === 0 || value.some((item) => typeof item !== "string" || !item)) {
throw new Error(`${label} must be a non-empty string array`);
}
return value;
}
function stringValue(value: unknown, label: string): string {
if (typeof value !== "string" || !value) throw new Error(`${label} must be a non-empty string`);
return value;
}
function validateDocumentationUrl(value: string, label: string): void {
let url: URL;
try {
url = new URL(value);
} catch {
throw new Error(`${label} must be a valid URL`);
}
if (url.protocol !== "https:" || url.hostname !== "developer.adobe.com" || !url.pathname.startsWith("/premiere-pro/uxp/")) {
throw new Error(`${label} must point to the official Premiere Pro UXP documentation`);
}
}
function validateAdobeUxpCoverageManifest(value: unknown): AdobeUxpCoverageManifest {
const manifest = asRecord(value, "Adobe UXP coverage manifest");
if (manifest.schemaVersion !== 1) throw new Error("Adobe UXP coverage manifest schemaVersion must be 1");
const source = asRecord(manifest.source, "Adobe UXP coverage manifest source");
if (source.apiPackage !== "@adobe/premierepro") {
throw new Error("Adobe UXP coverage manifest must identify @adobe/premierepro as its source package");
}
const apiVersion = stringValue(source.apiVersion, "Adobe UXP coverage manifest source.apiVersion");
const changelogUrl = stringValue(source.changelogUrl, "Adobe UXP coverage manifest source.changelogUrl");
validateDocumentationUrl(changelogUrl, "Adobe UXP coverage manifest source.changelogUrl");
if (!Array.isArray(manifest.entries) || manifest.entries.length === 0) {
throw new Error("Adobe UXP coverage manifest entries must be a non-empty array");
}
const ids = new Set<string>();
const entries = manifest.entries.map((rawEntry, index): AdobeUxpCoverageEntry => {
const label = `Adobe UXP coverage manifest entry ${index}`;
const entry = asRecord(rawEntry, label);
const id = stringValue(entry.id, `${label}.id`);
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(id) || ids.has(id)) {
throw new Error(`${label}.id must be a unique kebab-case identifier`);
}
ids.add(id);
const documentationUrls = stringArray(entry.documentationUrls, `${label}.documentationUrls`);
documentationUrls.forEach((url) => validateDocumentationUrl(url, `${label}.documentationUrls`));
const availability = entry.availability;
if (availability !== "current" && availability !== "planned") {
throw new Error(`${label}.availability must be current or planned`);
}
const implementationStatus = entry.implementationStatus;
if (implementationStatus !== "implemented" && implementationStatus !== "planned") {
throw new Error(`${label}.implementationStatus must be implemented or planned`);
}
const verificationStatus = entry.verificationStatus;
if (
verificationStatus !== "automated_contract_verified"
&& verificationStatus !== "committed_unverified"
&& verificationStatus !== "not_started"
) {
throw new Error(`${label}.verificationStatus is not supported`);
}
const liveHostVerificationStatus = entry.liveHostVerificationStatus;
if (liveHostVerificationStatus !== "not_run" && liveHostVerificationStatus !== "verified") {
throw new Error(`${label}.liveHostVerificationStatus is not supported`);
}
const idempotency = entry.idempotency;
if (idempotency !== "operation_id" && idempotency !== "operation_id_when_supported" && idempotency !== "not_applicable") {
throw new Error(`${label}.idempotency is not supported`);
}
if (entry.backend !== "uxp") throw new Error(`${label}.backend must be uxp`);
if (entry.uxpCommand !== null && typeof entry.uxpCommand !== "string") {
throw new Error(`${label}.uxpCommand must be a string or null`);
}
if (typeof entry.mutatesProject !== "boolean" || typeof entry.undoable !== "boolean") {
throw new Error(`${label} mutation metadata must be boolean`);
}
return {
id,
adobeApi: stringArray(entry.adobeApi, `${label}.adobeApi`),
documentationUrls,
minimumPremiereVersion: stringValue(entry.minimumPremiereVersion, `${label}.minimumPremiereVersion`),
backend: "uxp",
uxpCommand: entry.uxpCommand,
mcpTools: stringArray(entry.mcpTools, `${label}.mcpTools`),
availability,
implementationStatus,
verificationStatus,
liveHostVerificationStatus,
mutatesProject: entry.mutatesProject,
undoable: entry.undoable,
idempotency,
verificationBoundary: stringValue(entry.verificationBoundary, `${label}.verificationBoundary`),
};
});
return {
schemaVersion: 1,
source: {
apiPackage: "@adobe/premierepro",
apiVersion,
changelogUrl,
},
entries,
};
}
export const ADOBE_UXP_COVERAGE_MANIFEST = validateAdobeUxpCoverageManifest(adobeUxpCoverage);
export function buildAdobeUxpCoverageReport(): AdobeUxpCoverageReport {
const entries = ADOBE_UXP_COVERAGE_MANIFEST.entries.map((entry) => ({
...entry,
adobeApi: [...entry.adobeApi],
documentationUrls: [...entry.documentationUrls],
mcpTools: [...entry.mcpTools],
}));
return {
schemaVersion: ADOBE_UXP_COVERAGE_MANIFEST.schemaVersion,
source: { ...ADOBE_UXP_COVERAGE_MANIFEST.source },
entries,
summary: {
total: entries.length,
current: entries.filter((entry) => entry.availability === "current").length,
planned: entries.filter((entry) => entry.availability === "planned").length,
implemented: entries.filter((entry) => entry.implementationStatus === "implemented").length,
committedUnverified: entries.filter((entry) => entry.verificationStatus === "committed_unverified").length,
automatedContractVerified: entries.filter((entry) => entry.verificationStatus === "automated_contract_verified").length,
liveHostVerified: entries.filter((entry) => entry.liveHostVerificationStatus === "verified").length,
},
};
}
export function platformName(platform: NodeJS.Platform): string {
if (platform === "darwin") return "macOS";
if (platform === "win32") return "Windows";
return platform;
}
export function buildPlatformCapabilityReport(
capabilities: CapabilityConfig,
platform: NodeJS.Platform = process.platform,
tempDirectory: string = join(tmpdir(), "premiere-mcp-bridge"),
toolCatalog: Record<string, CatalogToolDefinition> = {},
toolPacks?: ToolPackReport,
) {
const supported = SUPPORTED_HOST_PLATFORMS.includes(platform as SupportedHostPlatform);
return {
schemaVersion: 1,
mcp: {
sdk: {
generation: 2,
packages: [
"@modelcontextprotocol/server",
"@modelcontextprotocol/client",
"@modelcontextprotocol/node",
],
},
protocol: {
modernRevision: "2026-07-28",
legacyRevisions: "2024-10-07 through 2025-11-25",
dualEra: true,
},
features: {
statelessRequests: "supported",
serverDiscovery: "supported",
requestMetadataEnvelope: "supported",
headerBasedRouting: "supported",
deterministicCacheableLists: "supported",
resultTypeDiscriminator: "supported",
cancellation: "supported_by_protocol_host_limited",
progressNotifications: "protocol_supported_not_emitted",
traceContext: "transport_passthrough",
jsonSchema202012: "supported",
cursorPagination: "supported_single_page_catalogs",
resourceTemplates: "not_exposed_no_parameterized_resource_family",
completions: "not_exposed_no_bounded_completion_domain",
embeddedResourcesAndLinks: "supported_selective_use",
multiRoundTripRequests: "sdk_supported_no_current_workflow",
subscriptionsListen: "supported",
extensionCapabilities: "supported",
tasksExtension: "not_implemented",
mcpAppsExtension: "not_implemented",
skillsOverMcp: "experimental_not_advertised",
serverCard: "experimental_not_advertised",
oauthClientMetadataDocuments: "external_authorization_boundary",
deprecatedSamplingRootsLogging: "not_advertised",
},
cachePolicy: {
toolsList: { ttlMs: 30_000, cacheScope: "private" },
promptsList: { ttlMs: 300_000, cacheScope: "public" },
resourcesList: { ttlMs: 60_000, cacheScope: "private" },
resourcesRead: { ttlMs: 0, cacheScope: "private" },
},
},
runtime: {
platform,
platformName: platformName(platform),
supported,
tempDirectory,
nodeVersion: process.version,
},
premiere: {
supportedVersions: "2020–2026",
hostVerificationRequired: true,
note: supported
? "Static compatibility is supported; call ping with Premiere Pro running to verify the current host session."
: "The MCP server can run here, but the Adobe Premiere Pro bridge and installer are supported only on macOS and Windows.",
},
backends: {
cep: {
status: "production",
platforms: ["macOS", "Windows"],
premiereVersions: "2020–2026",
transport: "local file bridge",
capabilities: ALL_CAPABILITIES,
delivery: {
interchange: ["FCP XML export", "AAF export", "OMF export"],
presetValidation: true,
postExportChecksum: ["sha256", "sha512"],
unsupported: {
otio: "No documented Premiere ExtendScript or UXP OTIO import/export API.",
edl: "No documented Premiere ExtendScript or UXP EDL import/export API.",
cloudPublish: "No documented Premiere automation API for publishing to cloud destinations.",
contentCredentials: "No documented Premiere automation API for configuring Content Credentials.",
renderAndReplace: "No documented Premiere ExtendScript or UXP Render and Replace API.",
},
},
},
uxp: {
status: "preview",
platforms: ["macOS", "Windows"],
premiereVersions: "25.6+",
transport: "loopback WebSocket",
apiCoverage: buildAdobeUxpCoverageReport(),
commands: [
"capabilities.get",
"state.get",
"operation.cancel",
"frame.export",
"sequence.playhead.inspect",
"sequence.playhead.set",
"sequence.timing.inspect",
"graphics.mogrtPath.inspect",
"timeline.selection.lift",
"transition.video.list",
"transition.video.inspect",
"transition.video.add",
"transition.video.remove",
"transcript.export",
"transcript.search",
"transcript.has",
"transcript.import",
"captions.inspect",
"effects.catalog",
"effects.chain.get",
"trackItem.identity.inspect",
"effects.chain.add",
"effects.chain.remove",
"selection.inspect",
"selection.fingerprints.inspect",
"selection.targets.inspect",
"selection.update",
"effects.selection.add",
"effects.selection.remove",
"sceneEdit.detect",
"proxy.inspect",
"proxy.attach",
"ingest.get",
"ingest.configure",
"media.relink",
"metadata.get",
"metadata.update",
"color.preflight",
"environment.inspect",
"footage.conform",
"sourceMonitor.state",
"sourceMonitor.open",
"sourceMonitor.play",
"sourceMonitor.close",
"storage.preflight",
"scratch.configure",
"workspace.status",
"projectSelection.views",
"projectSelection.inspect",
"markers.inspect",
"markers.add",
"markers.addBeatGrid",
"markers.update",
"markers.remove",
"markers.removeMany",
"bins.inspect",
"bins.create",
"bins.createSmart",
"bins.rename",
"bins.move",
"bins.color",
"bins.remove",
"sequenceSettings.get",
"sequenceSettings.update",
"project.import",
"parameters.inspect",
"parameters.set",
"parameters.keyframeAdd",
"parameters.keyframeRemove",
"parameters.keyframeRemoveRange",
"parameters.keyframeInterpolation",
"trackItem.inspect",
"trackItem.update",
"timeline.insert",
"timeline.overwrite",
"timeline.cloneSelection",
"timeline.removeSelection",
"timeline.mogrtPath",
"timeline.mogrtLibrary",
"sequences.inspect",
"sequences.createFromMedia",
"silence.deriveSequence",
"sequences.clone",
"sequences.subsequence",
"sequences.activate",
"sequences.open",
"sequences.close",
"sequences.delete",
"encoder.preflight",
"encoder.sequence",
"encoder.projectItem",
"encoder.file",
],
unsupportedCommands: {
"captions.create": "No documented Premiere UXP caption creation API.",
"captions.update": "No documented Premiere UXP caption text/timing mutation API.",
"captions.delete": "No documented Premiere UXP caption deletion API.",
},
events: [
"premiere.state.changed",
"premiere.operation.started",
"premiere.operation.progress",
"premiere.operation.completed",
"premiere.operation.failed",
"premiere.operation.cancelled",
],
operationSemantics: {
cancellation: "cooperative preflight only",
atomicRollback: false,
undo: "only action-based mutations executed through Project.executeTransaction",
},
hostVerificationRequired: true,
},
},
authority: {
source: capabilities.source,
enabled: [...capabilities.capabilities].sort(),
disabled: ALL_CAPABILITIES.filter((capability) => !capabilities.capabilities.has(capability)),
},
...(toolPacks === undefined ? {} : { toolPacks }),
tools: buildToolCapabilityReport(toolCatalog, capabilities),
};
}