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
This commit is contained in:
João Henrique
2026-09-08 09:59:31 -04:00
commit b541f502ba
1507 changed files with 387650 additions and 0 deletions
+222
View File
@@ -0,0 +1,222 @@
import { describe, expect, it, vi } from "vitest";
import type { UxpWebSocketBridge } from "../../src/bridge/uxp-websocket-bridge.js";
import {
normalizeTranscriptDeletionRanges,
planTranscriptRoughCut,
previewTranscriptEdit,
transcriptRevision,
} from "../../src/tools/transcript-edits.js";
import { getUxpTools } from "../../src/tools/uxp.js";
describe("transcript edit planning", () => {
it("creates stable transcript revisions", () => {
expect(transcriptRevision('{"segments":[]}')).toMatch(/^sha256:[a-f0-9]{64}$/);
expect(transcriptRevision('{"segments":[]}')).toBe(transcriptRevision('{"segments":[]}'));
expect(transcriptRevision('{"segments":[1]}')).not.toBe(transcriptRevision('{"segments":[]}'));
});
it("sorts, adds handles, and merges overlapping deletion ranges", () => {
expect(normalizeTranscriptDeletionRanges([
{ start_seconds: 5, end_seconds: 7 },
{ start_seconds: 1, end_seconds: 3 },
{ start_seconds: 2.9, end_seconds: 5.1 },
], 0.1)).toEqual([{ start_seconds: 0.9, end_seconds: 7.1 }]);
});
it("rejects invalid or excessive deletion ranges", () => {
expect(() => normalizeTranscriptDeletionRanges([])).toThrow("at least one");
expect(() => normalizeTranscriptDeletionRanges([{ start_seconds: 2, end_seconds: 1 }])).toThrow("greater than");
expect(() => normalizeTranscriptDeletionRanges([{ start_seconds: 0, end_seconds: 1 }], 11)).toThrow("between 0 and 10");
expect(() => normalizeTranscriptDeletionRanges(Array.from({ length: 101 }, (_, i) => ({ start_seconds: i, end_seconds: i + 0.5 })))).toThrow("limited to 100");
});
it("revision-locks previews and returns a deterministic confirmation token", () => {
const json = '{"segments":[{"text":"remove this"}]}';
const revision = transcriptRevision(json);
const first = previewTranscriptEdit(json, revision, [
{ start_seconds: 4, end_seconds: 5 },
{ start_seconds: 1, end_seconds: 2.5 },
]);
const second = previewTranscriptEdit(json, revision, [
{ start_seconds: 1, end_seconds: 2.5 },
{ start_seconds: 4, end_seconds: 5 },
]);
expect(first).toMatchObject({ deletedSeconds: 2.5, applied: false });
expect(first.confirmationToken).toBe(second.confirmationToken);
expect(() => previewTranscriptEdit(json, transcriptRevision("different"), [{ start_seconds: 1, end_seconds: 2 }])).toThrow("does not match");
});
it("maps transcript deletions to descending verified 1x timeline cut instructions", () => {
const json = '{"segments":[]}';
const preview = previewTranscriptEdit(json, transcriptRevision(json), [
{ start_seconds: 2, end_seconds: 4 },
{ start_seconds: 8, end_seconds: 12 },
]);
const plan = planTranscriptRoughCut(preview, [{
placement_id: "clip-1",
track_type: "video",
track_index: 0,
source_in_seconds: 0,
source_out_seconds: 10,
timeline_start_seconds: 20,
timeline_end_seconds: 30,
}]);
expect(plan).toMatchObject({
applied: false,
requiresDuplicateSequence: true,
requiresPostMutationRequery: true,
ripple: true,
unmappedDeletionRanges: [],
});
expect(plan.steps.map((step) => step.timeline_range)).toEqual([
{ start_seconds: 28, end_seconds: 30 },
{ start_seconds: 22, end_seconds: 24 },
]);
});
it("rejects retimed placements instead of guessing source-to-timeline mapping", () => {
const json = '{"segments":[]}';
const preview = previewTranscriptEdit(json, transcriptRevision(json), [{ start_seconds: 1, end_seconds: 2 }]);
expect(() => planTranscriptRoughCut(preview, [{
placement_id: "retimed",
track_type: "video",
track_index: 0,
source_in_seconds: 0,
source_out_seconds: 10,
timeline_start_seconds: 0,
timeline_end_seconds: 5,
}])).toThrow("retimed");
});
it("fails closed for malformed placements and reports unmapped non-ripple ranges", () => {
const json = '{"segments":[]}';
const preview = previewTranscriptEdit(json, transcriptRevision(json), [{ start_seconds: 20, end_seconds: 21 }]);
const valid = {
placement_id: "clip-1", track_type: "audio", track_index: 0,
source_in_seconds: 0, source_out_seconds: 10,
timeline_start_seconds: 0, timeline_end_seconds: 10,
} as const;
expect(() => planTranscriptRoughCut(preview, [])).toThrow("at least one");
expect(() => planTranscriptRoughCut(preview, Array(257).fill(valid))).toThrow("limited to 256");
expect(() => planTranscriptRoughCut(preview, [null])).toThrow("must be an object");
expect(() => planTranscriptRoughCut(preview, [{ ...valid, placement_id: "" }])).toThrow("1-512");
expect(() => planTranscriptRoughCut(preview, [{ ...valid, track_type: "caption" }])).toThrow("video or audio");
expect(() => planTranscriptRoughCut(preview, [{ ...valid, track_index: -1 }])).toThrow("non-negative integer");
expect(() => planTranscriptRoughCut(preview, [{ ...valid, source_out_seconds: Number.NaN }])).toThrow("finite number");
expect(() => planTranscriptRoughCut(preview, [{ ...valid, source_out_seconds: 0 }])).toThrow("increasing");
const plan = planTranscriptRoughCut(preview, [valid], false);
expect(plan).toMatchObject({ ripple: false, steps: [], unmappedDeletionRanges: preview.deletionRanges });
});
});
describe("transcript UXP MCP tools", () => {
it("exports native transcript JSON with a stable revision", async () => {
const json = '{"segments":[]}';
const request = vi.fn().mockResolvedValue({ projectGuid: "project-1", projectItemId: "clip-1", projectItemName: "Interview", json });
const bridge = { request, getState: vi.fn() } as unknown as UxpWebSocketBridge;
const result = await getUxpTools(bridge).get_clip_transcript_uxp.handler({ project_item_id: "clip-1" });
expect(request).toHaveBeenCalledWith("transcript.export", { projectItemId: "clip-1" });
expect(result).toMatchObject({
success: true,
data: { result: { projectGuid: "project-1", projectItemId: "clip-1", json, transcriptRevision: transcriptRevision(json) } },
});
});
it("maps a guarded transcript replacement to the documented UXP command", async () => {
const request = vi.fn().mockResolvedValue({ committed: true, verified: true });
const bridge = { request, getState: vi.fn() } as unknown as UxpWebSocketBridge;
const result = await getUxpTools(bridge).import_transcript_uxp.handler({
project_item_id: "clip-1",
project_guid: "project-1",
expected_transcript_revision: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
replacement_transcript_json: '{"segments":[]}',
confirm_destructive: true,
operation_id: "transcript-replace-1",
});
expect(request).toHaveBeenCalledWith("transcript.import", {
projectItemId: "clip-1",
expectedProjectGuid: "project-1",
expectedTranscriptRevision: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
json: '{"segments":[]}',
confirmDestructive: true,
operationId: "transcript-replace-1",
});
expect(result).toMatchObject({ success: true, data: { result: { committed: true, verified: true } } });
});
it("fails closed before the bridge when destructive confirmation or replay identity is missing", async () => {
const request = vi.fn();
const bridge = { request, getState: vi.fn() } as unknown as UxpWebSocketBridge;
const tool = getUxpTools(bridge).import_transcript_uxp;
await expect(tool.handler({
project_item_id: "clip-1", project_guid: "project-1", expected_transcript_revision: null,
replacement_transcript_json: '{"segments":[]}', confirm_destructive: false, operation_id: "transcript-replace-2",
})).resolves.toMatchObject({ success: false, error: expect.stringContaining("confirm_destructive") });
await expect(tool.handler({
project_item_id: "clip-1", project_guid: "project-1", expected_transcript_revision: null,
replacement_transcript_json: '{"segments":[]}', confirm_destructive: true, operation_id: "",
})).resolves.toMatchObject({ success: false, error: expect.stringContaining("operation_id") });
expect(request).not.toHaveBeenCalled();
});
it("maps bounded search arguments to the native transcript command", async () => {
const request = vi.fn().mockResolvedValue({ matches: [] });
const bridge = { request, getState: vi.fn() } as unknown as UxpWebSocketBridge;
await getUxpTools(bridge).search_clip_transcript_uxp.handler({
project_item_name: "Interview",
query: "launch date",
case_sensitive: true,
max_results: 10,
});
expect(request).toHaveBeenCalledWith("transcript.search", {
projectItemName: "Interview",
query: "launch date",
caseSensitive: true,
maxResults: 10,
});
});
it("re-exports the transcript before producing a revision-locked preview", async () => {
const json = '{"segments":[{"text":"cut this"}]}';
const request = vi.fn().mockResolvedValue({ projectItemId: "clip-1", projectItemName: "Interview", json });
const bridge = { request, getState: vi.fn() } as unknown as UxpWebSocketBridge;
const result = await getUxpTools(bridge).preview_transcript_edit_uxp.handler({
project_item_id: "clip-1",
transcript_revision: transcriptRevision(json),
deletions: [{ start_seconds: 2, end_seconds: 4 }],
});
expect(request).toHaveBeenCalledWith("transcript.export", { projectItemId: "clip-1" });
expect(result).toMatchObject({
success: true,
data: { result: { projectItemId: "clip-1", deletedSeconds: 2, applied: false } },
});
});
it("builds a transcript rough-cut plan without mutating Premiere", async () => {
const json = '{"segments":[{"text":"cut this"}]}';
const request = vi.fn().mockResolvedValue({ projectItemId: "clip-1", projectItemName: "Interview", json });
const bridge = { request, getState: vi.fn() } as unknown as UxpWebSocketBridge;
const result = await getUxpTools(bridge).plan_transcript_rough_cut_uxp.handler({
project_item_id: "clip-1",
transcript_revision: transcriptRevision(json),
deletions: [{ start_seconds: 2, end_seconds: 4 }],
placements: [{
placement_id: "timeline-clip-1",
track_type: "video",
track_index: 0,
source_in_seconds: 0,
source_out_seconds: 10,
timeline_start_seconds: 20,
timeline_end_seconds: 30,
}],
});
expect(request).toHaveBeenCalledTimes(1);
expect(request).toHaveBeenCalledWith("transcript.export", { projectItemId: "clip-1" });
expect(result).toMatchObject({
success: true,
data: { result: { applied: false, requiresDuplicateSequence: true, steps: [{ timeline_range: { start_seconds: 22, end_seconds: 24 } }] } },
});
});
});