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
+87
View File
@@ -0,0 +1,87 @@
---
name: code-review
description: "Review the changes since a fixed point (commit, branch, tag, or merge-base) along two axes: Standards (does the code follow this repo's documented coding standards?) and Spec (does the code match what the originating issue/spec asked for?). Runs both reviews in parallel sub-agents and reports them side by side. Use when the user wants to review a branch, a PR, work-in-progress changes, or asks to \"review since X\"."
---
Two-axis review of the diff between `HEAD` and a fixed point the user supplies:
- **Standards**: does the code conform to this repo's documented coding standards?
- **Spec**: does the code faithfully implement the originating issue / spec?
Both axes run as **parallel sub-agents** so they don't pollute each other's context, then this skill aggregates their findings.
The issue tracker should have been provided to you. If `docs/agents/issue-tracker.md` is missing, tell the user to run `/setup-matt-pocock-skills`.
## Process
### 1. Pin the fixed point
Whatever the user said is the fixed point (a commit SHA, branch name, tag, `main`, `HEAD~5`, etc.). If they didn't specify one, ask for it.
Capture the diff command once: `git diff <fixed-point>...HEAD` (three-dot, so the comparison is against the merge-base). Also note the list of commits via `git log <fixed-point>..HEAD --oneline`.
Before going further, confirm the fixed point resolves (`git rev-parse <fixed-point>`) and the diff is non-empty. A bad ref or empty diff should fail here, not inside two parallel sub-agents.
### 2. Identify the spec source
Look for the originating spec, in this order:
1. Issue references in the commit messages (`#123`, `Closes #45`, GitLab `!67`, etc.), fetched via the workflow in `docs/agents/issue-tracker.md`.
2. A path the user passed as an argument.
3. A spec file under `docs/`, `specs/`, or `.scratch/` matching the branch name or feature.
4. If nothing is found, ask the user where the spec is. If they say there isn't one, the **Spec** sub-agent will skip and report "no spec available".
### 3. Identify the standards sources
Anything in the repo that documents how code should be written, such as `CODING_STANDARDS.md` or `CONTRIBUTING.md`.
On top of whatever the repo documents, the Standards axis always carries the **smell baseline** below: a fixed set of Fowler code smells (_Refactoring_, ch.3) that applies even when a repo documents nothing. Two rules bind it:
- **The repo overrides.** A documented repo standard always wins; where it endorses something the baseline would flag, suppress the smell.
- **Always a judgement call.** Each smell is a labelled heuristic ("possible Feature Envy"), never a hard violation. Like any standard here, skip anything tooling already enforces.
Each smell reads *what it is* → *how to fix*; match it against the diff:
- **Mysterious Name**: a function, variable, or type whose name doesn't reveal what it does or holds. → rename it; if no honest name comes, the design's murky.
- **Duplicated Code**: the same logic shape appears in more than one hunk or file in the change. → extract the shared shape, call it from both.
- **Feature Envy**: a method that reaches into another object's data more than its own. → move the method onto the data it envies.
- **Data Clumps**: the same few fields or params keep travelling together (a type wanting to be born). → bundle them into one type, pass that.
- **Primitive Obsession**: a primitive or string standing in for a domain concept that deserves its own type. → give the concept its own small type.
- **Repeated Switches**: the same `switch`/`if`-cascade on the same type recurs across the change. → replace with polymorphism, or one map both sites share.
- **Shotgun Surgery**: one logical change forces scattered edits across many files in the diff. → gather what changes together into one module.
- **Divergent Change**: one file or module is edited for several unrelated reasons. → split so each module changes for one reason.
- **Speculative Generality**: abstraction, parameters, or hooks added for needs the spec doesn't have. → delete it; inline back until a real need shows.
- **Message Chains**: long `a.b().c().d()` navigation the caller shouldn't depend on. → hide the walk behind one method on the first object.
- **Middle Man**: a class or function that mostly just delegates onward. → cut it, call the real target direct.
- **Refused Bequest**: a subclass or implementer that ignores or overrides most of what it inherits. → drop the inheritance, use composition.
### 4. Spawn both sub-agents in parallel
**Standards sub-agent prompt** should include:
- The full diff command and commit list.
- The list of standards-source files you found in step 3, **plus the smell baseline from step 3** pasted in full (the sub-agent has no other access to it).
- The brief: "Report, per file/hunk where relevant, (a) every place the diff violates a documented standard: cite the standard (file + the rule); and (b) any baseline smell you spot: name it and quote the hunk. Distinguish hard violations from judgement calls: documented-standard breaches can be hard, but baseline smells are always judgement calls, and a documented repo standard overrides the baseline. Skip anything tooling enforces. Under 400 words."
**Spec sub-agent prompt** should include:
- The diff command and commit list.
- The path or fetched contents of the spec.
- The brief: "Report: (a) requirements the spec asked for that are missing or partial; (b) behaviour in the diff that wasn't asked for (scope creep); (c) requirements that look implemented but where the implementation looks wrong. Quote the spec line for each finding. Under 400 words."
If the spec is missing, skip the Spec sub-agent and note this in the final report.
### 5. Aggregate
Present the two reports under `## Standards` and `## Spec` headings, verbatim or lightly cleaned. Do **not** merge or rerank findings, because the two axes are deliberately separate (see _Why two axes_).
End with a one-line summary: total findings per axis, and the worst issue _within each axis_ (if any). Don't pick a single winner across axes: that's the reranking the separation exists to prevent.
## Why two axes
A change can pass one axis and fail the other:
- Code that follows every standard but implements the wrong thing → **Standards pass, Spec fail.**
- Code that does exactly what the issue asked but breaks the project's conventions → **Spec pass, Standards fail.**
Reporting them separately stops one axis from masking the other.
@@ -0,0 +1,3 @@
interface:
display_name: "Code Review"
short_description: "Review a diff on standards and spec"
@@ -0,0 +1,37 @@
# Deepening
How to deepen a cluster of shallow modules safely, given its dependencies. Assumes the vocabulary in [SKILL.md](SKILL.md): **module**, **interface**, **seam**, **adapter**.
## Dependency categories
When assessing a candidate for deepening, classify its dependencies. The category determines how the deepened module is tested across its seam.
### 1. In-process
Pure computation, in-memory state, no I/O. Always deepenable: merge the modules and test through the new interface directly. No adapter needed.
### 2. Local-substitutable
Dependencies that have local test stand-ins (PGLite for Postgres, in-memory filesystem). Deepenable if the stand-in exists. The deepened module is tested with the stand-in running in the test suite. The seam is internal; no port at the module's external interface.
### 3. Remote but owned (Ports & Adapters)
Your own services across a network boundary (microservices, internal APIs). Define a **port** (interface) at the seam. The deep module owns the logic; the transport is injected as an **adapter**. Tests use an in-memory adapter. Production uses an HTTP/gRPC/queue adapter.
Recommendation shape: *"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."*
### 4. True external (Mock)
Third-party services (Stripe, Twilio, etc.) you don't control. The deepened module takes the external dependency as an injected port; tests provide a mock adapter.
## Seam discipline
- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a port unless at least two adapters are justified (typically production + test). A single-adapter seam is just indirection.
- **Internal seams vs external seams.** A deep module can have internal seams (private to its implementation, used by its own tests) as well as the external seam at its interface. Don't expose internal seams through the interface just because tests use them.
## Testing strategy: replace, don't layer
- Old unit tests on shallow modules become waste once tests at the deepened module's interface exist; delete them.
- Write new tests at the deepened module's interface. The **interface is the test surface**.
- Tests assert on observable outcomes through the interface, not internal state.
- Tests should survive internal refactors, since they describe behaviour, not implementation. If a test has to change when the implementation changes, it's testing past the interface.
@@ -0,0 +1,44 @@
# Design It Twice
When the user wants to explore alternative interfaces for a chosen deepening candidate, use this parallel sub-agent pattern. Based on "Design It Twice" (Ousterhout): your first idea is unlikely to be the best.
Uses the vocabulary in [SKILL.md](SKILL.md): **module**, **interface**, **seam**, **adapter**, **leverage**.
## Process
### 1. Frame the problem space
Before spawning sub-agents, write a user-facing explanation of the problem space for the chosen candidate:
- The constraints any new interface would need to satisfy
- The dependencies it would rely on, and which category they fall into (see [DEEPENING.md](DEEPENING.md))
- A rough illustrative code sketch to ground the constraints, not a proposal, just a way to make the constraints concrete
Show this to the user, then immediately proceed to Step 2. The user reads and thinks while the sub-agents work in parallel.
### 2. Spawn sub-agents
Spawn 3+ sub-agents in parallel. Each must produce a **radically different** interface for the deepened module.
Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category from [DEEPENING.md](DEEPENING.md), what sits behind the seam). The brief is independent of the user-facing problem-space explanation in Step 1. Give each agent a different design constraint:
- Agent 1: "Minimize the interface: aim for 1–3 entry points max. Maximise leverage per entry point."
- Agent 2: "Maximise flexibility: support many use cases and extension."
- Agent 3: "Optimise for the most common caller: make the default case trivial."
- Agent 4 (if applicable): "Design around ports & adapters for cross-seam dependencies."
Include both [SKILL.md](SKILL.md) vocabulary and CONTEXT.md vocabulary in the brief so each sub-agent names things consistently with the architecture language and the project's domain language.
Each sub-agent outputs:
1. Interface (types, methods, params, plus invariants, ordering, error modes)
2. Usage example showing how callers use it
3. What the implementation hides behind the seam
4. Dependency strategy and adapters (see [DEEPENING.md](DEEPENING.md))
5. Trade-offs: where leverage is high, where it's thin
### 3. Present and compare
Present designs sequentially so the user can absorb each one, then compare them in prose. Contrast by **depth** (leverage at the interface), **locality** (where change concentrates), and **seam placement**.
After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated: the user wants a strong read, not a menu.
+114
View File
@@ -0,0 +1,114 @@
---
name: codebase-design
description: Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.
---
# Codebase Design
Design **deep modules**: a lot of behaviour behind a small interface, placed at a clean seam, testable through that interface. Use this language and these principles wherever code is being designed or restructured. The aim is leverage for callers, locality for maintainers, and testability for everyone.
## Glossary
Use these terms exactly: don't substitute "component," "service," "API," or "boundary." Consistent language is the whole point.
**Module**: anything with an interface and an implementation. Deliberately scale-agnostic: a function, class, package, or tier-spanning slice. _Avoid_: unit, component, service.
**Interface**: everything a caller must know to use the module correctly: the type signature, but also invariants, ordering constraints, error modes, required configuration, and performance characteristics. _Avoid_: API, signature (too narrow, they refer only to the type-level surface).
**Implementation**: what's inside a module, its body of code. Distinct from **Adapter**: a thing can be a small adapter with a large implementation (a Postgres repo) or a large adapter with a small implementation (an in-memory fake). Reach for "adapter" when the seam is the topic; "implementation" otherwise.
**Depth**: leverage at the interface. The amount of behaviour a caller (or test) can exercise per unit of interface they have to learn. A module is **deep** when a large amount of behaviour sits behind a small interface, **shallow** when the interface is nearly as complex as the implementation.
**Seam** _(Michael Feathers)_: a place where you can alter behaviour without editing in that place; the *location* at which a module's interface lives. Where to put the seam is its own design decision, distinct from what goes behind it. _Avoid_: boundary (overloaded with DDD's bounded context).
**Adapter**: a concrete thing that satisfies an interface at a seam. Describes *role* (what slot it fills), not substance (what's inside).
**Leverage**: what callers get from depth. More capability per unit of interface they learn. One implementation pays back across N call sites and M tests.
**Locality**: what maintainers get from depth. Change, bugs, knowledge, and verification concentrate in one place rather than spreading across callers. Fix once, fixed everywhere.
## Deep vs shallow
**Deep module** = small interface + lots of implementation:
```
┌─────────────────────┐
│ Small Interface │ ← Few methods, simple params
├─────────────────────┤
│ │
│ Deep Implementation│ ← Complex logic hidden
│ │
└─────────────────────┘
```
**Shallow module** = large interface + little implementation (avoid):
```
┌─────────────────────────────────┐
│ Large Interface │ ← Many methods, complex params
├─────────────────────────────────┤
│ Thin Implementation │ ← Just passes through
└─────────────────────────────────┘
```
When designing an interface, ask:
- Can I reduce the number of methods?
- Can I simplify the parameters?
- Can I hide more complexity inside?
## Principles
- **Depth is a property of the interface, not the implementation.** A deep module can be internally composed of small, mockable, swappable parts; they just aren't part of the interface. A module can have **internal seams** (private to its implementation, used by its own tests) as well as the **external seam** at its interface.
- **The deletion test.** Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep.
- **The interface is the test surface.** Callers and tests cross the same seam. If you want to test *past* the interface, the module is probably the wrong shape.
- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a seam unless something actually varies across it.
## Designing for testability
Good interfaces make testing natural:
1. **Accept dependencies, don't create them.**
```typescript
// Testable
function processOrder(order, paymentGateway) {}
// Hard to test
function processOrder(order) {
const gateway = new StripeGateway();
}
```
2. **Return results, don't produce side effects.**
```typescript
// Testable
function calculateDiscount(cart): Discount {}
// Hard to test
function applyDiscount(cart): void {
cart.total -= discount;
}
```
3. **Small surface area.** Fewer methods = fewer tests needed. Fewer params = simpler test setup.
## Relationships
- A **Module** has exactly one **Interface** (the surface it presents to callers and tests).
- **Depth** is a property of a **Module**, measured against its **Interface**.
- A **Seam** is where a **Module**'s **Interface** lives.
- An **Adapter** sits at a **Seam** and satisfies the **Interface**.
- **Depth** produces **Leverage** for callers and **Locality** for maintainers.
## Rejected framings
- **Depth as ratio of implementation-lines to interface-lines** (Ousterhout): rewards padding the implementation. We use depth-as-leverage instead.
- **"Interface" as the TypeScript `interface` keyword or a class's public methods**: too narrow: interface here includes every fact a caller must know.
- **"Boundary"**: overloaded with DDD's bounded context. Say **seam** or **interface**.
## Going deeper
- **Deepening a cluster given its dependencies**, see [DEEPENING.md](DEEPENING.md): dependency categories, seam discipline, and replace-don't-layer testing.
- **Exploring alternative interfaces**, see [DESIGN-IT-TWICE.md](DESIGN-IT-TWICE.md): spin up parallel sub-agents to design the interface several radically different ways, then compare on depth, locality, and seam placement.
@@ -0,0 +1,3 @@
interface:
display_name: "Codebase Design"
short_description: "Vocabulary for deep-module design"
+138
View File
@@ -0,0 +1,138 @@
---
name: diagnosing-bugs
description: Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.
---
# Diagnosing Bugs
A discipline for hard bugs. Skip phases only when explicitly justified.
When exploring the codebase, read `CONTEXT.md` (if it exists) to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.
## Redact
This skill has you show commands, outputs and captured artifacts. **Redact every secret first**: write `<REDACTED>` in its place. Build loops against env vars, so the credential stays in the environment rather than in what you show. Captured artifacts carry auth headers: quote only the lines that carry the signal.
If the redacted output is not enough to diagnose the bug, say so and ask the user.
## Phase 1: Build a feedback loop
**This is the skill.** Everything else is mechanical. If you have a **tight** pass/fail signal for the bug (one that goes red on _this_ bug), you will find the cause; bisection, hypothesis-testing, and instrumentation all just consume it. If you don't have one, no amount of staring at code will save you.
Spend disproportionate effort here. **Be aggressive. Be creative. Refuse to give up.**
### Ways to construct one, in roughly this order
1. **Failing test** at whatever seam reaches the bug: unit, integration, e2e.
2. **Curl / HTTP script** against a running dev server.
3. **CLI invocation** with a fixture input, diffing stdout against a known-good snapshot.
4. **Headless browser script** (Playwright / Puppeteer) that drives the UI and asserts on DOM/console/network.
5. **Replay a captured trace.** Save a real network request / payload / event log to disk; replay it through the code path in isolation.
6. **Throwaway harness.** Spin up a minimal subset of the system (one service, mocked deps) that exercises the bug code path with a single function call.
7. **Property / fuzz loop.** If the bug is "sometimes wrong output", run 1000 random inputs and look for the failure mode.
8. **Bisection harness.** If the bug appeared between two known states (commit, dataset, version), automate "boot at state X, check, repeat" so you can `git bisect run` it.
9. **Differential loop.** Run the same input through old-version vs new-version (or two configs) and diff outputs.
10. **HITL bash script.** Last resort. If a human must click, drive _them_ with `scripts/hitl-loop.template.sh` so the loop is still structured. Captured output feeds back to you.
Build the right feedback loop, and the bug is 90% fixed.
### Tighten the loop
Treat the loop as a product. Once you have _a_ loop, **tighten** it:
- Can I make it faster? (Cache setup, skip unrelated init, narrow the test scope.)
- Can I make the signal sharper? (Assert on the specific symptom, not "didn't crash".)
- Can I make it more deterministic? (Pin time, seed RNG, isolate filesystem, freeze network.)
A 30-second flaky loop is barely better than no loop; a 2-second deterministic one is tight, a debugging superpower.
### Non-deterministic bugs
The goal is not a clean repro but a **higher reproduction rate**. Loop the trigger 100×, parallelise, add stress, narrow timing windows, inject sleeps. A 50%-flake bug is debuggable; 1% is not, so keep raising the rate until it's debuggable.
### When you genuinely cannot build a loop
Stop and say so explicitly. List what you tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a redacted captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation. Do **not** proceed to hypothesise without a loop.
### Completion criterion: a tight loop that goes red
Phase 1 is done when the loop is **tight** and **red-capable**: you can name **one command** (a script path, a test invocation, a curl) that you have **already run at least once** (show the invocation and its output, redacted), and that is:
- [ ] **Red-capable**: it drives the actual bug code path and asserts the **user's exact symptom**, so it can go red on this bug and green once fixed. Not "runs without erroring"; it must be able to _catch this specific bug_.
- [ ] **Deterministic**: same verdict every run (flaky bugs: a pinned, high reproduction rate, per above).
- [ ] **Fast**: seconds, not minutes.
- [ ] **Agent-runnable**: you can run it unattended; a human in the loop only via `scripts/hitl-loop.template.sh`.
If you catch yourself reading code to build a theory before this command exists, **stop: jumping straight to a hypothesis is the exact failure this skill prevents.** No red-capable command, no Phase 2.
## Phase 2: Reproduce + minimise
Run the loop. Watch it go red as the bug appears.
Confirm:
- [ ] The loop produces the failure mode the **user** described, not a different failure that happens to be nearby. Wrong bug = wrong fix.
- [ ] The failure is reproducible across multiple runs (or, for non-deterministic bugs, reproducible at a high enough rate to debug against).
- [ ] You have captured the exact symptom (error message, wrong output, slow timing) so later phases can verify the fix actually addresses it.
### Minimise
Once it's red, shrink the repro to the **smallest scenario that still goes red**. Cut inputs, callers, config, data, and steps **one at a time**, re-running the loop after each cut, and keep only what's load-bearing for the failure.
Why bother: a minimal repro shrinks the hypothesis space in Phase 3 (fewer moving parts left to suspect) and becomes the clean regression test in Phase 5.
Done when **every remaining element is load-bearing**: removing any one of them makes the loop go green.
Do not proceed until you have reproduced **and** minimised.
## Phase 3: Hypothesise
Generate **3–5 ranked hypotheses** before testing any of them. Single-hypothesis generation anchors on the first plausible idea.
Each hypothesis must be **falsifiable**: state the prediction it makes.
> Format: "If <X> is the cause, then <changing Y> will make the bug disappear / <changing Z> will make it worse."
If you cannot state the prediction, the hypothesis is a vibe: discard or sharpen it.
**Show the ranked list to the user before testing.** They often have domain knowledge that re-ranks instantly ("we just deployed a change to #3"), or know hypotheses they've already ruled out. Cheap checkpoint, big time saver. Don't block on it; proceed with your ranking if the user is AFK.
## Phase 4: Instrument
Each probe must map to a specific prediction from Phase 3. **Change one variable at a time.**
Tool preference:
1. **Debugger / REPL inspection** if the env supports it. One breakpoint beats ten logs.
2. **Targeted logs** at the boundaries that distinguish hypotheses.
3. Never "log everything and grep".
**Tag every debug log** with a unique prefix, e.g. `[DEBUG-a4f2]`. Cleanup at the end becomes a single grep. Untagged logs survive; tagged logs die.
**Perf branch.** For performance regressions, logs are usually wrong. Instead: establish a baseline measurement (timing harness, `performance.now()`, profiler, query plan), then bisect. Measure first, fix second.
## Phase 5: Fix + regression test
Write the regression test **before the fix**, but only if there is a **correct seam** for it.
A correct seam is one where the test exercises the **real bug pattern** as it occurs at the call site. If the only available seam is too shallow (single-caller test when the bug needs multiple callers, unit test that can't replicate the chain that triggered the bug), a regression test there gives false confidence.
**If no correct seam exists, that itself is the finding.** Note it. The codebase architecture is preventing the bug from being locked down. Flag this for the next phase.
If a correct seam exists:
1. Turn the minimised repro into a failing test at that seam.
2. Watch it fail.
3. Apply the fix.
4. Watch it pass.
5. Re-run the Phase 1 feedback loop against the original (un-minimised) scenario.
## Phase 6: Cleanup
Required before declaring done:
- [ ] Original repro no longer reproduces (re-run the Phase 1 loop)
- [ ] Regression test passes (or absence of seam is documented)
- [ ] All `[DEBUG-...]` instrumentation removed (`grep` the prefix)
- [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location)
- [ ] The hypothesis that turned out correct is stated in the commit / PR message, so the next debugger learns
@@ -0,0 +1,3 @@
interface:
display_name: "Diagnosing Bugs"
short_description: "Diagnose hard bugs and regressions"
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Human-in-the-loop reproduction loop.
# Copy this file, edit the steps below, and run it.
# The agent runs the script; the user follows prompts in their terminal.
#
# Usage:
# bash hitl-loop.template.sh
#
# Two helpers:
# step "<instruction>" → show instruction, wait for Enter
# capture VAR "<question>" → show question, read response into VAR
#
# At the end, captured values are printed as KEY=VALUE for the agent to parse.
#
# `capture` prints its value back to the terminal, where the agent reads it,
# so capture observations, and leave signing in to the user as a `step`.
set -euo pipefail
step() {
printf '\n>>> %s\n' "$1"
read -r -p " [Enter when done] " _
}
capture() {
local var="$1" question="$2" answer
printf '\n>>> %s\n' "$question"
read -r -p " > " answer
printf -v "$var" '%s' "$answer"
}
# --- edit below ---------------------------------------------------------
step "Open the app at http://localhost:3000 and sign in."
capture ERRORED "Click the 'Export' button. Did it throw an error? (y/n)"
capture ERROR_MSG "Paste the error message (or 'none'):"
# --- edit above ---------------------------------------------------------
printf '\n--- Captured ---\n'
printf 'ERRORED=%s\n' "$ERRORED"
printf 'ERROR_MSG=%s\n' "$ERROR_MSG"
@@ -0,0 +1,47 @@
# ADR Format
ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc.
Create the `docs/adr/` directory lazily: only when the first ADR is needed.
## Template
```md
# {Short title of the decision}
{1-3 sentences: what's the context, what did we decide, and why.}
```
That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why*, not in filling out sections.
## Optional sections
Only include these when they add genuine value. Most ADRs won't need them.
- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`): useful when decisions are revisited
- **Considered Options**: only when the rejected alternatives are worth remembering
- **Consequences**: only when non-obvious downstream effects need to be called out
## Numbering
Scan `docs/adr/` for the highest existing number and increment by one.
## When to offer an ADR
All three of these must be true:
1. **Hard to reverse**: the cost of changing your mind later is meaningful
2. **Surprising without context**: a future reader will look at the code and wonder "why on earth did they do it this way?"
3. **The result of a real trade-off**: there were genuine alternatives and you picked one for specific reasons
If a decision is easy to reverse, skip it: you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing."
### What qualifies
- **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres."
- **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP."
- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library: just the ones that would take a quarter to swap out.
- **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s.
- **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate.
- **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract."
- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it; otherwise someone will suggest GraphQL again in six months.
@@ -0,0 +1,60 @@
# CONTEXT.md Format
## Structure
```md
# {Context Name}
{One or two sentence description of what this context is and why it exists.}
## Language
**Order**:
{A one or two sentence description of the term}
_Avoid_: Purchase, transaction
**Invoice**:
A request for payment sent to a customer after delivery.
_Avoid_: Bill, payment request
**Customer**:
A person or organization that places orders.
_Avoid_: Client, buyer, account
```
## Rules
- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others under `_Avoid_`.
- **Keep definitions tight.** One or two sentences max. Define what it IS, not what it does.
- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs.
- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine.
## Single vs multi-context repos
**Single context (most repos):** One `CONTEXT.md` at the repo root.
**Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other:
```md
# Context Map
## Contexts
- [Ordering](./src/ordering/CONTEXT.md): receives and tracks customer orders
- [Billing](./src/billing/CONTEXT.md): generates invoices and processes payments
- [Fulfillment](./src/fulfillment/CONTEXT.md): manages warehouse picking and shipping
## Relationships
- **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking
- **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices
- **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money`
```
The skill infers which structure applies:
- If `CONTEXT-MAP.md` exists, read it to find contexts
- If only a root `CONTEXT.md` exists, single context
- If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved
When multiple contexts exist, infer which one the current topic relates to. If unclear, ask.
+74
View File
@@ -0,0 +1,74 @@
---
name: domain-modeling
description: Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a CONTEXT.md, or recording or editing an ADR.
---
# Domain Modeling
Actively build and sharpen the project's domain model as you design. This is the *active* discipline: challenging terms, inventing edge-case scenarios, and writing the glossary and decisions down the moment they crystallise. (Merely *reading* `CONTEXT.md` for vocabulary is not this skill: that's a one-line habit any skill can do. This skill is for when you're changing the model, not just consuming it.)
## File structure
Most repos have a single context:
```
/
├── CONTEXT.md
├── docs/
│ └── adr/
│ ├── 0001-event-sourced-orders.md
│ └── 0002-postgres-for-write-model.md
└── src/
```
If a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives:
```
/
├── CONTEXT-MAP.md
├── docs/
│ └── adr/ ← system-wide decisions
├── src/
│ ├── ordering/
│ │ ├── CONTEXT.md
│ │ └── docs/adr/ ← context-specific decisions
│ └── billing/
│ ├── CONTEXT.md
│ └── docs/adr/
```
Create files lazily: only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed.
## During the session
### Challenge against the glossary
When the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y. Which is it?"
### Sharpen fuzzy language
When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account': do you mean the Customer or the User? Those are different things."
### Discuss concrete scenarios
When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts.
### Cross-reference with code
When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible. Which is right?"
### Update CONTEXT.md inline
When a term is resolved, update `CONTEXT.md` right there. Don't batch these up: capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md).
`CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else.
### Offer ADRs sparingly
Only offer to create an ADR when all three are true:
1. **Hard to reverse**: the cost of changing your mind later is meaningful
2. **Surprising without context**: a future reader will wonder "why did they do it this way?"
3. **The result of a real trade-off**: there were genuine alternatives and you picked one for specific reasons
If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md).
@@ -0,0 +1,3 @@
interface:
display_name: "Domain Modeling"
short_description: "Build and sharpen a domain model"
@@ -0,0 +1,123 @@
# HTML Report Format
The architectural review is rendered as a single self-contained HTML file in the OS temp directory. Tailwind and Mermaid both come from CDNs. Mermaid handles graph-shaped diagrams reliably; hand-built divs and inline SVG handle the more editorial visuals (mass diagrams, cross-sections). Mix the two: don't lean on Mermaid for everything, it'll start to look generic.
## Scaffold
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Architecture review for {{repo name}}</title>
<script src="https://cdn.tailwindcss.com"></script>
<script type="module">
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
mermaid.initialize({ startOnLoad: true, theme: "neutral", securityLevel: "loose" });
</script>
<style>
/* small custom layer for things Tailwind doesn't cover cleanly:
dashed seam lines, hand-drawn-feeling arrow heads, etc. */
.seam { stroke-dasharray: 4 4; }
.leak { stroke: #dc2626; }
.deep { background: linear-gradient(135deg, #0f172a, #1e293b); }
</style>
</head>
<body class="bg-stone-50 text-slate-900 font-sans">
<main class="max-w-5xl mx-auto px-6 py-12 space-y-12">
<header>...</header>
<section id="candidates" class="space-y-10">...</section>
<section id="top-recommendation">...</section>
</main>
</body>
</html>
```
## Header
Repo name, date, and a compact legend: solid box = module, dashed line = seam, red arrow = leakage, thick dark box = deep module. No introduction paragraph. Straight into the candidates.
## Candidate card
The diagrams carry the weight. Prose is sparse, plain, and uses the glossary terms (from the `/codebase-design` skill) without ceremony.
Each candidate is one `<article>`:
- **Title**: short, names the deepening (e.g. "Collapse the Order intake pipeline").
- **Badge row**: recommendation strength (`Strong` = emerald, `Worth exploring` = amber, `Speculative` = slate), plus a tag for the dependency category (`in-process`, `local-substitutable`, `ports & adapters`, `mock`).
- **Files**: monospaced list, `font-mono text-sm`.
- **Before / After diagram**: the centrepiece. Two columns, side by side. See patterns below.
- **Problem**: one sentence. What hurts.
- **Solution**: one sentence. What changes.
- **Wins**: bullets, ≤6 words each. e.g. "Tests hit one interface", "Pricing logic stops leaking", "Delete 4 shallow wrappers".
- **ADR callout** (if applicable): one line in an amber-tinted box.
No paragraphs of explanation. If the diagram needs a paragraph to be understood, redraw the diagram.
## Diagram patterns
Pick the pattern that fits the candidate. Mix them. Don't make every diagram look the same. Variety is part of the point.
### Mermaid graph (the workhorse for dependencies / call flow)
Use a Mermaid `flowchart` or `graph` when the point is "X calls Y calls Z, and look at the mess." Wrap it in a Tailwind-styled card so it doesn't feel parachuted in. Style with classDef to colour leakage edges red and the deep module dark. Sequence diagrams work well for "before: 6 round-trips; after: 1."
```html
<div class="rounded-lg border border-slate-200 bg-white p-4">
<pre class="mermaid">
flowchart LR
A[OrderHandler] --> B[OrderValidator]
B --> C[OrderRepo]
C -.leak.-> D[PricingClient]
classDef leak stroke:#dc2626,stroke-width:2px;
class C,D leak
</pre>
</div>
```
### Hand-built boxes-and-arrows (when Mermaid's layout fights you)
Modules as `<div>`s with borders and labels. Arrows as inline SVG `<line>` or `<path>` elements positioned absolutely over a relative container. Reach for this when you want the "after" diagram to feel like one thick-bordered deep module with greyed-out internals, since Mermaid won't render that with the right weight.
### Cross-section (good for layered shallowness)
Stack horizontal bands (`h-12 border-l-4`) to show layers a call passes through. Before: 6 thin layers each doing nothing. After: 1 thick band labelled with the consolidated responsibility.
### Mass diagram (good for "interface as wide as implementation")
Two rectangles per module: one for interface surface area, one for implementation. Before: interface rectangle is nearly as tall as the implementation rectangle (shallow). After: interface rectangle is short, implementation rectangle is tall (deep).
### Call-graph collapse
Before: a tree of function calls rendered as nested boxes. After: the same tree collapsed into one box, with the now-internal calls shown faded inside it.
## Style guidance
- Lean editorial, not corporate-dashboard. Generous whitespace. Serif optional for headings (`font-serif` works well with stone/slate).
- Colour sparingly: one accent (emerald or indigo) plus red for leakage and amber for warnings.
- Keep diagrams ~320px tall so before/after sits comfortably side by side without scrolling.
- Use `text-xs uppercase tracking-wider` for module labels inside diagrams, so they read as schematic, not as UI.
- The only scripts are the Tailwind CDN and the Mermaid ESM import. The report is otherwise static: no app code, no interactivity beyond Mermaid's own rendering.
## Top recommendation section
One larger card. Candidate name, one sentence on why, anchor link to its card. That's it.
## Tone
Plain English, concise, but the architectural nouns and verbs come straight from the `/codebase-design` skill. Concision is not an excuse to drift.
**Use exactly:** module, interface, implementation, depth, deep, shallow, seam, adapter, leverage, locality.
**Never substitute:** component, service, unit (for module) · API, signature (for interface) · boundary (for seam) · layer, wrapper (for module, when you mean module).
**Phrasings that fit the style:**
- "Order intake module is shallow: interface nearly matches the implementation."
- "Pricing leaks across the seam."
- "Deepen: one interface, one place to test."
- "Two adapters justify the seam: HTTP in prod, in-memory in tests."
**Wins bullets** name the gain in glossary terms: *"locality: bugs concentrate in one module"*, *"leverage: one interface, N call sites"*, *"interface shrinks; implementation absorbs the wrappers"*. Don't write *"easier to maintain"* or *"cleaner code"*, because those terms aren't in the glossary and don't earn their place.
No hedging, no throat-clearing, no "it's worth noting that…". If a sentence could be a bullet, make it a bullet. If a bullet could be cut, cut it. If a term isn't in the `/codebase-design` glossary, reach for one that is before inventing a new one.
@@ -0,0 +1,71 @@
---
name: improve-codebase-architecture
description: Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.
disable-model-invocation: true
---
# Improve Codebase Architecture
Surface architectural friction and propose **deepening opportunities**: refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability.
This command is _informed_ by the project's domain model and built on a shared design vocabulary:
- Call the Skill tool with "codebase-design" for the architecture vocabulary (**module**, **interface**, **depth**, **seam**, **adapter**, **leverage**, **locality**) and its principles (the deletion test, "the interface is the test surface", "one adapter = hypothetical seam, two = real"). Use these terms exactly in every suggestion, and don't drift into "component," "service," "API," or "boundary."
- The domain language in `CONTEXT.md` gives names to good seams; ADRs in `docs/adr/` record decisions this command should not re-litigate.
## Process
### 1. Explore
**Scope before you scan: YAGNI.** Deepening a module pays off by making future changes to it easier, so put extra weight on the parts of the codebase that have recently changed. Decide *where* to look before you look:
- If the user named a direction (a module, a subsystem, a pain point), take it, and skip the inference below.
- Otherwise, walk back a good stretch of the commit history (`git log --oneline`) to find the codebase's hot spots, the files and areas that keep coming up, and let those paths pull your attention first. If the changes are scattered with no clear hot spot, widen the net.
Read the project's domain glossary (`CONTEXT.md`) and any ADRs in the area you're touching first.
Then spawn a sub-agent to walk the codebase. Don't follow rigid heuristics; explore organically and note where you experience friction:
- Where does understanding one concept require bouncing between many small modules?
- Where are modules **shallow**, with an interface nearly as complex as the implementation?
- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)?
- Where do tightly-coupled modules leak across their seams?
- Which parts of the codebase are untested, or hard to test through their current interface?
Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want.
### 2. Present candidates as an HTML report
Write a self-contained HTML file to the OS temp directory so nothing lands in the repo. Resolve the temp dir from `$TMPDIR`, falling back to `/tmp` (or `%TEMP%` on Windows), and write to `<tmpdir>/architecture-review-<timestamp>.html` so each run gets a fresh file. Open it for the user (`xdg-open <path>` on Linux, `open <path>` on macOS, `start <path>` on Windows) and tell them the absolute path.
The report uses **Tailwind via CDN** for layout and styling, and **Mermaid via CDN** for diagrams where a graph/flow/sequence reliably communicates the structure. Mix Mermaid with hand-crafted CSS/SVG visuals: use Mermaid when relationships are graph-shaped (call graphs, dependencies, sequences), and hand-built divs/SVG when you want something more editorial (mass diagrams, cross-sections, collapse animations). Each candidate gets a **before/after visualisation**. Be visual.
For each candidate, render a card with:
- **Files**: which files/modules are involved
- **Problem**: why the current architecture is causing friction
- **Solution**: plain English description of what would change
- **Benefits**: explained in terms of locality and leverage, and how tests would improve
- **Before / After diagram**: side-by-side, custom-drawn, illustrating the shallowness and the deepening
- **Recommendation strength**: one of `Strong`, `Worth exploring`, `Speculative`, rendered as a badge
End the report with a **Top recommendation** section: which candidate you'd tackle first and why.
**Use CONTEXT.md vocabulary for the domain, and the `/codebase-design` vocabulary for the architecture.** If `CONTEXT.md` defines "Order," talk about "the Order intake module," not "the FooBarHandler," and not "the Order service."
**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly in the card (e.g. a warning callout: _"contradicts ADR-0007, but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids.
See [HTML-REPORT.md](HTML-REPORT.md) for the full HTML scaffold, diagram patterns, and styling guidance.
Do NOT propose interfaces yet. After the file is written, ask the user: "Which of these would you like to explore?"
### 3. Grilling loop
Once the user picks a candidate, call the Skill tool with "grilling" to walk the decision tree with them: constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive.
Side effects happen inline as decisions crystallize; call the Skill tool with "domain-modeling" to keep the domain model current as you go:
- **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the term to `CONTEXT.md`. Create the file lazily if it doesn't exist.
- **Sharpening a fuzzy term during the conversation?** Update `CONTEXT.md` right there.
- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing; skip ephemeral reasons ("not worth it right now") and self-evident ones.
- **Want to explore alternative interfaces for the deepened module?** Call the Skill tool with "codebase-design" and use its design-it-twice parallel sub-agent pattern.
@@ -0,0 +1,5 @@
interface:
display_name: "Improve Codebase Architecture"
short_description: "Find and grill architecture improvements"
policy:
allow_implicit_invocation: false
@@ -0,0 +1,14 @@
---
name: resolving-merge-conflicts
description: "Use when you need to resolve an in-progress git merge/rebase conflict."
---
1. **See the current state** of the merge/rebase. Check git history, and the conflicting files.
2. **Find the primary sources** for each conflict. Understand deeply why each change was made, and what the original intent was. Read the commit messages, check the PRs, check original issues/tickets.
3. **Resolve each hunk.** Preserve both intents where possible. Where incompatible, pick the one matching the merge's stated goal and note the trade-off. Do **not** invent new behaviour. Always resolve; never `--abort`.
4. Discover the project's **automated checks** and run them, typically typecheck, then tests, then format. Fix anything the merge broke.
5. **Finish the merge/rebase.** Stage everything and commit. If rebasing, continue the rebase process until all commits are rebased.
@@ -0,0 +1,3 @@
interface:
display_name: "Resolving Merge Conflicts"
short_description: "Resolve merge and rebase conflicts"
+38
View File
@@ -0,0 +1,38 @@
---
name: tdd
description: Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
---
# Test-Driven Development
TDD is the red → green loop. This skill is the reference that makes that loop produce tests worth keeping: what a good test is, where tests go, the anti-patterns, and the rules of the loop. Every section applies on every cycle: consult them before and during the loop, not after.
When exploring the codebase, read `CONTEXT.md` (if it exists) so test names and interface vocabulary match the project's domain language, and respect ADRs in the area you're touching.
## What a good test is
Tests verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't. A good test reads like a specification: "user can checkout with valid cart" tells you exactly what capability exists, and it survives refactors because it doesn't care about internal structure.
See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines.
## Seams: where tests go
A **seam** is the public boundary you test at: the interface where you observe behavior without reaching inside. Tests live at seams, never against internals.
**Test only at pre-agreed seams.** Before writing any test, write down the seams under test and confirm them with the user. No test is written at an unconfirmed seam. You can't test everything, so agreeing the seams up front is how testing effort lands on the critical paths and complex logic instead of every edge case.
Ask: "What's the public interface, and which seams should we test?"
When the shape of that interface is itself in question (how deep the module is, where the seam belongs, what the interface should expose), call the Skill tool with "codebase-design" for the vocabulary. It is the shared source of the module, interface, depth, seam, adapter, leverage and locality terms, and it is a reference to consult, not a session to run.
## Anti-patterns
- **Implementation-coupled**: mocks internal collaborators, tests private methods, or verifies through a side channel (querying the database instead of using the interface). The tell: the test breaks when you refactor but behavior hasn't changed.
- **Tautological**: the assertion recomputes the expected value the way the code does (`expect(add(a, b)).toBe(a + b)`, a snapshot derived by hand the same way, a constant asserted equal to itself), so it passes by construction and can never disagree with the code. Expected values must come from an independent source of truth: a known-good literal, a worked example, the spec.
- **Horizontal slicing**: writing all tests first, then all implementation. Bulk tests verify _imagined_ behavior: you test the _shape_ of things rather than user-facing behavior, the tests go insensitive to real changes, and you commit to test structure before understanding the implementation. Work in **vertical slices** instead: one test → one implementation → repeat, each test a **tracer bullet** that responds to what the last cycle taught you.
## Rules of the loop
- **Red before green.** Write the failing test first, then only enough code to pass it. Don't anticipate future tests or add speculative features.
- **One slice at a time.** One seam, one test, one minimal implementation per cycle.
- **Refactoring is not part of the loop.** It belongs to the review stage (see the `code-review` skill), not the red → green implementation cycle.
+3
View File
@@ -0,0 +1,3 @@
interface:
display_name: "TDD"
short_description: "Test-driven red-green-refactor"
+59
View File
@@ -0,0 +1,59 @@
# When to Mock
Mock at **system boundaries** only:
- External APIs (payment, email, etc.)
- Databases (sometimes - prefer test DB)
- Time/randomness
- File system (sometimes)
Don't mock:
- Your own classes/modules
- Internal collaborators
- Anything you control
## Designing for Mockability
At system boundaries, design interfaces that are easy to mock:
**1. Use dependency injection**
Pass external dependencies in rather than creating them internally:
```typescript
// Easy to mock
function processPayment(order, paymentClient) {
return paymentClient.charge(order.total);
}
// Hard to mock
function processPayment(order) {
const client = new StripeClient(process.env.STRIPE_KEY);
return client.charge(order.total);
}
```
**2. Prefer SDK-style interfaces over generic fetchers**
Create specific functions for each external operation instead of one generic function with conditional logic:
```typescript
// GOOD: Each function is independently mockable
const api = {
getUser: (id) => fetch(`/users/${id}`),
getOrders: (userId) => fetch(`/users/${userId}/orders`),
createOrder: (data) => fetch('/orders', { method: 'POST', body: data }),
};
// BAD: Mocking requires conditional logic inside the mock
const api = {
fetch: (endpoint, options) => fetch(endpoint, options),
};
```
The SDK approach means:
- Each mock returns one specific shape
- No conditional logic in test setup
- Easier to see which endpoints a test exercises
- Type safety per endpoint
+77
View File
@@ -0,0 +1,77 @@
# Good and Bad Tests
## Good Tests
**Integration-style**: Test through real interfaces, not mocks of internal parts.
```typescript
// GOOD: Tests observable behavior
test("user can checkout with valid cart", async () => {
const cart = createCart();
cart.add(product);
const result = await checkout(cart, paymentMethod);
expect(result.status).toBe("confirmed");
});
```
Characteristics:
- Tests behavior users/callers care about
- Uses public API only
- Survives internal refactors
- Describes WHAT, not HOW
- One logical assertion per test
## Bad Tests
**Implementation-detail tests**: Coupled to internal structure.
```typescript
// BAD: Tests implementation details
test("checkout calls paymentService.process", async () => {
const mockPayment = jest.mock(paymentService);
await checkout(cart, payment);
expect(mockPayment.process).toHaveBeenCalledWith(cart.total);
});
```
Red flags:
- Mocking internal collaborators
- Testing private methods
- Asserting on call counts/order
- Test breaks when refactoring without behavior change
- Test name describes HOW not WHAT
- Verifying through external means instead of interface
```typescript
// BAD: Bypasses interface to verify
test("createUser saves to database", async () => {
await createUser({ name: "Alice" });
const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]);
expect(row).toBeDefined();
});
// GOOD: Verifies through interface
test("createUser makes user retrievable", async () => {
const user = await createUser({ name: "Alice" });
const retrieved = await getUser(user.id);
expect(retrieved.name).toBe("Alice");
});
```
**Tautological tests**: Expected value restates the implementation, so the test passes by construction.
```typescript
// BAD: Expected value is recomputed the way the code computes it
test("calculateTotal sums line items", () => {
const items = [{ price: 10 }, { price: 5 }];
const expected = items.reduce((sum, i) => sum + i.price, 0);
expect(calculateTotal(items)).toBe(expected);
});
// GOOD: Expected value is an independent, known literal
test("calculateTotal sums line items", () => {
expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);
});
```
+16
View File
@@ -0,0 +1,16 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/rag/reindex_hook.sh\"",
"async": true
}
]
}
]
}
}
+30
View File
@@ -0,0 +1,30 @@
# macOS
.DS_Store
# Node modules
node_modules/
# Python
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.egg-info/
dist/
build/
# IDE
.vscode/
.idea/
# Logs
*.log
# Environment
.env
.env.local
# Temporary files
*.tmp
*.temp
+12
View File
@@ -0,0 +1,12 @@
{
"mcpServers": {
"premiere-pro": {
"type": "stdio",
"command": "node",
"args": [
"/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/dist/index.js"
],
"env": {}
}
}
}
+31
View File
@@ -0,0 +1,31 @@
# AGENTS.md
## Project Skills
Este projeto usa skills do mattpocock/skills para manter integridade e organização do código.
### Skills disponíveis
| Skill | Trigger | Descrição |
|-------|---------|-----------|
| `improve-codebase-architecture` | `/improve-codebase-architecture` | Escaneia oportunidades de melhoria no codebase |
| `codebase-design` | `/codebase-design` | Projetar módulos profundos (muita funcionalidade, interface simples) |
| `domain-modeling` | `/domain-modeling` | Constrói vocabulário compartilhado no projeto |
| `tdd` | `/tdd` | Test-driven development - garante que código funciona antes de commitar |
| `code-review` | `/code-review` | Revisão automática de diffs (Standards + Spec) |
| `diagnosing-bugs` | `/diagnosing-bugs` | Loop disciplinado para debugar bugs complexos |
| `resolving-merge-conflicts` | `/resolving-merge-conflicts` | Resolve conflitos git hunk por hunk |
### Uso
- Para escanear melhorias: `/improve-codebase-architecture`
- Para projetar módulos: `/codebase-design`
- Para modelar domínio: `/domain-modeling`
- Para TDD: `/tdd`
- Para revisão de código: `/code-review`
- Para debugar bugs: `/diagnosing-bugs`
- Para resolver conflitos: `/resolving-merge-conflicts`
### Skills globais
Skills adicionais estão disponíveis globalmente em `~/.agents/skills/` (outras 29 skills)...
+55
View File
@@ -0,0 +1,55 @@
# CLAUDE.md
## Project Context
This is a software engineering project that uses mattpocock/skills for code quality and organization.
## Code Search: use the RAG first
This project has a code RAG at `rag/` (see [rag/README.md](rag/README.md)),
already indexed and kept in sync automatically (a `PostToolUse` hook
reindexes any edited file). Before grepping or reading whole files to find
something in `code/`, run:
```bash
rag/search_jhonny.sh "your query in natural language"
```
This returns exact `file:line-range` hits instead of forcing a full read of
large files — much cheaper in tokens. Use `--snippet` or `--full` when you
need to see the matched code, `--map` for a file inventory, and `--path`/
`--ext` to scope the search. Fall back to Grep/Glob only when the RAG has no
tunnel available (`rag/ensure_tunnel.sh` failed) or for exact-string
searches it isn't built for (e.g. searching non-code config not indexed).
## Available Skills
The following skills are available in this project (located in `.agents/skills/`):
### Code Quality & Architecture
- **`/improve-codebase-architecture`** - Scan codebase for deepening opportunities
- **`/codebase-design`** - Design deep modules with simple interfaces
- **`/domain-modeling`** - Build shared vocabulary and domain model
### Testing & Code Review
- **`/tdd`** - Test-driven development with red-green-refactor loop
- **`/code-review`** - Two-axis review: Standards + Spec
- **`/diagnosing-bugs`** - Disciplined bug diagnosis loop
- **`/resolving-merge-conflicts`** - Resolve git conflicts hunk by hunk
### Global Skills (available via ~/.agents/skills/)
- 29 additional skills for engineering and productivity
## Usage
When working on this project, use these skills to:
1. **Improve architecture**: Run `/improve-codebase-architecture` to scan for improvements
2. **Design modules**: Use `/codebase-design` when creating new modules
3. **Model domain**: Use `/domain-modeling` to build shared vocabulary
## Code Standards
- Follow existing code conventions
- Use shared domain vocabulary from CONTEXT.md
- Write tests before code (TDD)
- Review code changes with `/code-review`
+21
View File
@@ -0,0 +1,21 @@
# Contexto do sistema
## Vocabulário
- **Scanner**: caso de uso que coordena a leitura, análise, decisão, planejamento, aplicação e validação de uma edição.
- **Conteúdo**: unidade analisável identificada por um `content_id`, com texto e metadados.
- **Análise**: observações estruturadas produzidas a partir do conteúdo.
- **Decisão**: escolha de ações editoriais baseada na análise e na configuração.
- **Plano de edição**: sequência ordenada e auditável de operações.
- **Aplicador**: módulo que executa um plano sobre um conteúdo.
- **Validador**: módulo que verifica se o resultado satisfaz as invariantes do plano.
- **Provider de IA**: adaptador opcional que fornece análise ou decisão assistida por modelo.
- **Transcrição**: segmentos de texto sincronizados com um clipe, produzidos por um provider; não é decisão editorial.
- **Cena**: intervalo contínuo da timeline com características visuais e/ou textuais semelhantes.
- **Evento**: observação temporal detectada no áudio, texto ou vídeo; não implica corte.
## Decisões atuais
- A arquitetura Python ficará isolada em `code/engine` enquanto o sistema existente continuar em TypeScript.
- O domínio não conhece filesystem, banco de dados, SDK de IA ou plataforma de edição; essas integrações entram por interfaces e adaptadores.
- O primeiro fluxo é síncrono e determinístico, permitindo evolução posterior para operações assíncronas sem alterar o domínio.
+33
View File
@@ -0,0 +1,33 @@
# DEV-NOTES — Registro de implementações (Ronald)
> Este arquivo é a **fonte da mensagem de commit**.
> O script `admin/update.command` lê automaticamente a seção `## NOVO NESTE LOTE`
> abaixo (se houver texto) e usa o conteúdo no corpo da mensagem no momento do commit.
>
> **Como usar:**
> 1. Ao trabalhar, adicione bullets em `## NOVO NESTE LOTE` descrevendo o que mudou.
> 2. Depois rode `admin/update.command` — ele monta o commit com esse texto + resumo
> automático dos arquivos alterados, faz build/deploy, commita e dá push.
> 3. Após o script rodar com sucesso, você pode mover os bullets desta seção para
> `## Histórico` e esvaziar `NOVO NESTE LOTE`.
---
## NOVO NESTE LOTE
<!-- Cole aqui as implementações do lote atual (uma por linha, resumida). Exemplo:
- adicionada rota /captions/ai com detecção por lotes
- corrigido bug na transcrição de áudio
- atualizada tradução pt-BR dos painéis (2909 chaves)
-->
- adicionado admin/update.command (automatizacao de build+deploy+commit+push)
- adicionado admin/DEV-NOTES.md (fonte de texto para a mensagem de commit)
## Uso / lembretes (não apagar)
- `admin/update.command` → build + deploy + commit + push em um só
- `admin/DEV-NOTES.md` → fonte da mensagem (seção NOVO NESTE LOTE)
## Histórico
- Versão inicial: inicialização do repositório Ronald (backend OpenCut + painéis UXP/CEP + admin)
## Anotações / pendências
- Os PNGs de `code/extension/design/` (~46 MB) são mockups de system design — avaliar se mantém ou otimiza.
- `admin/VPS-ACCESS.md` é o único arquivo com credenciais e NÃO deve ser commitado (já está no .gitignore).
+81
View File
@@ -0,0 +1,81 @@
#!/bin/zsh
# ============================================================
# OpenCut Ronald - admin/OpenCut.command
#
# PONTO DE ENTRADA UNICO. Rode este arquivo a cada atualizacao que
# desenvolvermos: ele executa os tres scripts da pasta admin em ordem.
#
# 1. update.command dependencias + build dos paineis + testes
# 2. deploy.command backend no ar + ponte de live-updates
# 3. commit.command commit + push (pergunta antes de publicar)
#
# Se o update encontrar pendencias (teste quebrado, build falhando),
# ele PARA antes do commit - nada de publicar arvore quebrada. Para
# seguir mesmo assim, use FORCE=1.
#
# Uso:
# admin/OpenCut.command ciclo completo
# AUTO=1 admin/OpenCut.command nao pergunta antes do push
# SKIP_TESTS=1 admin/OpenCut.command pula os testes do passo 1
# FORCE=1 admin/OpenCut.command commita mesmo com pendencias
# ONLY=deploy admin/OpenCut.command roda so uma etapa
# (update | deploy | commit)
# ============================================================
set -u
ADMIN="$(cd "$(dirname "$0")" && pwd -P)"
GRN="$(tput setaf 2 2>/dev/null)"; YLW="$(tput setaf 3 2>/dev/null)"
RD="$(tput setaf 1 2>/dev/null)"; BLU="$(tput setaf 4 2>/dev/null)"
NC="$(tput sgr0 2>/dev/null)"
step() { echo; echo "${BLU}============================================================${NC}"
echo "${BLU} $*${NC}"
echo "${BLU}============================================================${NC}"; }
ok() { echo "${GRN}-> OK ${NC}$*"; }
warn() { echo "${YLW}-> Aviso: ${NC}$*"; }
die() { echo "${RD}-> Erro: ${NC}$*" >&2; exit 1; }
ONLY="${ONLY:-all}"
run_step() { [ "$ONLY" = "all" ] || [ "$ONLY" = "$1" ]; }
for s in update deploy commit; do
[ -x "$ADMIN/$s.command" ] || die "$ADMIN/$s.command ausente ou sem permissao de execucao"
done
UPDATE_RC=0
# ------------------------------------------------------------
# 1) Atualizacoes
# ------------------------------------------------------------
if run_step update; then
step "1/3 ATUALIZACOES (dependencias, build, testes)"
"$ADMIN/update.command" || UPDATE_RC=$?
if [ "$UPDATE_RC" != "0" ]; then
warn "o update terminou com pendencias (codigo $UPDATE_RC)"
fi
fi
# ------------------------------------------------------------
# 2) Deploy
# ------------------------------------------------------------
if run_step deploy; then
step "2/3 DEPLOY (backend + ponte de live-updates)"
"$ADMIN/deploy.command" || warn "o deploy nao confirmou o backend no ar"
fi
# ------------------------------------------------------------
# 3) Commit
# ------------------------------------------------------------
if run_step commit; then
if [ "$UPDATE_RC" != "0" ] && [ "${FORCE:-0}" != "1" ]; then
step "3/3 COMMIT - PULADO"
warn "o passo 1 acusou pendencias; nao vou commitar uma arvore quebrada."
warn "corrija os avisos acima, ou rode de novo com FORCE=1 para commitar assim mesmo."
exit "$UPDATE_RC"
fi
step "3/3 COMMIT (commit + push)"
"$ADMIN/commit.command" || die "o commit falhou"
fi
echo
ok "Ciclo concluido."
+217
View File
@@ -0,0 +1,217 @@
#!/bin/zsh
# ============================================================
# Jhonny - admin/PremiereMCP.command
#
# PONTO DE ENTRADA UNICO do MCP do Premiere Pro (code/) e do pipeline
# de corte de silencio por transcricao (Whisper). Rode este arquivo
# sempre que mexer no codigo antes de testar dentro do Premiere.
#
# 1. BUILD compila o TypeScript (code/, tsc -> dist/), valida a
# sintaxe dos scripts do painel CEP e do apply-silence-cuts,
# e reinstala a COPIA do painel que o Premiere realmente
# carrega (~/Library/.../Adobe/CEP/extensions/MCPBridgeCEP -
# editar code/cep-plugin sozinho nao e o bastante)
# 2. PYTHON confere o venv/dependencias do pipeline de transcricao
# (faster-whisper) e a presenca dos scripts esperados
# 3. RAG reindexa na RAG do Jhonny (jhonny-rag) o que mudou em
# code/ desde o ultimo build - mantem o indice em dia pro
# agente de IA buscar o codigo certo. Best-effort: se o
# abre o tunel temporariamente quando necessario e fecha o
# processo que este ciclo iniciou ao terminar.
# 4. LEMBRETE reinicie o Premiere Pro por completo para o painel
# reinstalado ser carregado - este passo so imprime isso
#
# Nao mexe em git (este projeto nao e um repositorio git) e nao sobe
# nenhum servidor - o bridge roda dentro do proprio Premiere via CEP.
#
# Uso:
# admin/PremiereMCP.command ciclo completo
# SKIP_BUILD=1 admin/PremiereMCP.command pula o build TypeScript
# SKIP_PYTHON=1 admin/PremiereMCP.command pula a checagem Python
# SKIP_RAG=1 admin/PremiereMCP.command pula a reindexacao RAG
# ONLY=build admin/PremiereMCP.command roda so uma etapa
# (build | python | rag | reload)
# ============================================================
set -u
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
CODE="$ROOT/code"
CEP="$CODE/cep-plugin"
SCRIPTS="$CODE/scripts"
# Pipeline de transcricao (Whisper) - mesmo venv usado nesta sessao.
TRANSCRICAO="/Volumes/Merongo/SISTEMAS/WHISPERX/transcricao"
VENV_PY="/Volumes/Merongo/SISTEMAS/venvs/whisperx-transcricao/bin/python"
GRN="$(tput setaf 2 2>/dev/null)"; YLW="$(tput setaf 3 2>/dev/null)"
RD="$(tput setaf 1 2>/dev/null)"; BLU="$(tput setaf 4 2>/dev/null)"
NC="$(tput sgr0 2>/dev/null)"
step() { echo; echo "${BLU}============================================================${NC}"
echo "${BLU} $*${NC}"
echo "${BLU}============================================================${NC}"; }
ok() { echo "${GRN}-> OK ${NC}$*"; }
warn() { echo "${YLW}-> Aviso: ${NC}$*"; }
die() { echo "${RD}-> Erro: ${NC}$*" >&2; exit 1; }
ONLY="${ONLY:-all}"
run_step() { [ "$ONLY" = "all" ] || [ "$ONLY" = "$1" ]; }
FAILED=0
RAG_TUNNEL_PID=""
cleanup_rag_tunnel() {
if [ -n "$RAG_TUNNEL_PID" ] && kill -0 "$RAG_TUNNEL_PID" 2>/dev/null; then
kill "$RAG_TUNNEL_PID" 2>/dev/null || true
wait "$RAG_TUNNEL_PID" 2>/dev/null || true
fi
}
trap cleanup_rag_tunnel EXIT
[ -d "$CODE" ] || die "pasta do projeto nao encontrada: $CODE"
# ------------------------------------------------------------
# 1) Build (TypeScript + validacao de sintaxe dos scripts)
# ------------------------------------------------------------
if run_step build && [ "${SKIP_BUILD:-0}" != "1" ]; then
step "1/4 BUILD (tsc + validacao de sintaxe)"
cd "$CODE" || die "nao consegui entrar em $CODE"
if npm run build; then
ok "npm run build (dist/ atualizado)"
else
warn "npm run build falhou - veja o log acima"
FAILED=1
fi
for f in "$CEP/main.js" "$SCRIPTS/apply-silence-cuts.mjs"; do
if [ -f "$f" ]; then
if node --check "$f" 2>/dev/null; then
ok "sintaxe valida: ${f#$ROOT/}"
else
warn "sintaxe invalida: ${f#$ROOT/}"
FAILED=1
fi
else
warn "arquivo esperado nao encontrado: ${f#$ROOT/}"
FAILED=1
fi
done
# O Premiere carrega o painel de uma COPIA instalada em
# ~/Library/.../Adobe/CEP/extensions/MCPBridgeCEP, nao direto de
# code/cep-plugin. Sem este passo, mudancas no painel nunca aparecem
# dentro do Premiere, mesmo com o app reaberto.
if node dist/index.js --install-cep >/tmp/premiere-mcp-install-cep.log 2>&1; then
ok "painel CEP reinstalado (copia sincronizada)"
else
warn "falha ao reinstalar o painel CEP - veja /tmp/premiere-mcp-install-cep.log"
FAILED=1
fi
elif run_step build; then
step "1/4 BUILD - PULADO (SKIP_BUILD=1)"
fi
# ------------------------------------------------------------
# 2) Pipeline Python (Whisper / corte de silencio)
# ------------------------------------------------------------
if run_step python && [ "${SKIP_PYTHON:-0}" != "1" ]; then
step "2/4 PYTHON (venv + pipeline de transcricao)"
if [ -x "$VENV_PY" ]; then
ok "venv encontrado: $VENV_PY"
else
warn "venv nao encontrado em $VENV_PY"
FAILED=1
fi
if [ -x "$VENV_PY" ] && "$VENV_PY" -c "import faster_whisper" 2>/dev/null; then
ok "faster-whisper importavel"
else
warn "faster-whisper nao importavel neste venv"
FAILED=1
fi
for f in "$TRANSCRICAO/transcribe_cli.py" "$TRANSCRICAO/silence_cutter.py"; do
if [ -f "$f" ]; then
ok "script presente: ${f#/Volumes/Merongo/}"
else
warn "script esperado nao encontrado: ${f#/Volumes/Merongo/}"
FAILED=1
fi
done
elif run_step python; then
step "2/4 PYTHON - PULADO (SKIP_PYTHON=1)"
fi
# ------------------------------------------------------------
# 3) RAG - reindexa na RAG do Jhonny o que mudou em code/
# ------------------------------------------------------------
if run_step rag && [ "${SKIP_RAG:-0}" != "1" ]; then
step "3/4 RAG (reindexacao incremental jhonny-rag)"
RAG_PY="$ROOT/rag/.venv/bin/python3"
[ -x "$RAG_PY" ] || RAG_PY="$(command -v python3)"
if ! nc -z 127.0.0.1 55435 2>/dev/null; then
echo "-> Abrindo tunel RAG temporario (127.0.0.1:55435)..."
ssh -N \
-o ExitOnForwardFailure=yes \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-L "127.0.0.1:55435:127.0.0.1:55435" \
root@179.197.228.240 &
RAG_TUNNEL_PID=$!
RAG_TUNNEL_READY=0
for _ in $(seq 1 10); do
if nc -z 127.0.0.1 55435 2>/dev/null; then
RAG_TUNNEL_READY=1
break
fi
if ! kill -0 "$RAG_TUNNEL_PID" 2>/dev/null; then
break
fi
sleep 0.5
done
if [ "$RAG_TUNNEL_READY" != "1" ]; then
warn "nao foi possivel abrir o tunel RAG - reindexacao pulada."
else
ok "tunel RAG aberto temporariamente"
fi
fi
if [ -n "$RAG_TUNNEL_PID" ] && ! nc -z 127.0.0.1 55435 2>/dev/null; then
warn "tunel RAG ficou indisponivel - reindexacao pulada."
elif ! nc -z 127.0.0.1 55435 2>/dev/null; then
warn "tunel RAG fechado - reindexacao pulada."
elif [ ! -x "$RAG_PY" ]; then
warn "python do rag nao encontrado - reindexacao pulada."
else
if ( cd "$ROOT" && RAG_ENV_FILE="$ROOT/rag/jhonny.env" \
"$RAG_PY" "$ROOT/rag/index_code.py" >/tmp/premiere-mcp-rag.log 2>&1 ); then
ok "RAG reindexado (incremental) - índice em dia"
else
warn "reindexacao RAG falhou - veja /tmp/premiere-mcp-rag.log"
FAILED=1
fi
fi
elif run_step rag; then
step "3/4 RAG - PULADO (SKIP_RAG=1)"
fi
# ------------------------------------------------------------
# 4) Lembrete de recarregar o painel dentro do Premiere
# ------------------------------------------------------------
if run_step reload; then
step "4/4 REINICIAR O PREMIERE"
echo "O painel ja foi reinstalado (passo 1), mas o Premiere so carrega a"
echo "copia nova depois de um restart completo:"
echo " 1. Feche o Premiere Pro por completo (Cmd+Q)"
echo " 2. Abra de novo e va em Window > Extensions > MCP for Adobe Premiere Pro"
echo " 3. Confira se aparecem as abas 'Bridge' e 'Cortar silencio'"
fi
echo
if [ "$FAILED" != "0" ]; then
die "ciclo terminou com pendencias - corrija os avisos acima antes de testar no Premiere."
fi
ok "Ciclo concluido. Pronto para recarregar o painel no Premiere."
+126
View File
@@ -0,0 +1,126 @@
# Acesso à VPS — Doza
> Mesma VPS usada pelo projeto CRM (`/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/CRM/admin/VPS-ACCESS.md`).
> Copiado de lá em 2026-08-31 para uso no projeto Doza.
> ⚠️ **Nunca coloque a senha em texto puro em chat, commit ou qualquer lugar além deste arquivo.**
> A autenticação por chave SSH já está configurada — não é preciso senha no uso normal.
> Este arquivo não deve ser commitado (adicione a um `.gitignore` se o projeto virar repositório git).
## Credenciais
```
VPS_HOST=179.197.228.240
VPS_USER=root
SSH_KEY=~/.ssh/id_ed25519
```
> Senha (`VPS_PASSWORD`) existe só como fallback de emergência — está guardada
> apenas no arquivo original do projeto CRM, não duplicada aqui. Autenticação
> normal é sempre por chave SSH.
Domínio base (wildcard automático da Hostinger, HTTPS via Let's Encrypt/Traefik):
```
TRAEFIK_HOST=srv1838065.hstgr.cloud
```
## Como conectar (sem senha)
A chave pública `~/.ssh/id_ed25519.pub` já está instalada em
`/root/.ssh/authorized_keys` da VPS.
```bash
# conectar e executar um comando
ssh root@179.197.228.240 "<comando>"
# conectar interativo
ssh root@179.197.228.240
# copiar arquivos (rsync)
rsync -az -e "ssh" ./ root@179.197.228.240:/caminho/no/servidor/
```
Alias opcional (se configurado em `~/.ssh/config`):
```bash
ssh gestor-trafego-vps "<comando>"
```
## O que já está rodando na VPS (relevante para o Doza)
Levantado via `ssh root@179.197.228.240 "docker ps"` em 2026-08-31. Cada app
fica isolado em `/docker/<nome-do-projeto>/` com seu próprio `docker-compose.yml`.
- `traefik` (network_mode host, Let's Encrypt automático) — **não mexer**.
- `openclaw` (agente da Hostinger) — **não mexer**.
- Stack **Supabase completa** já rodando: `supabase-db` (Postgres 17),
`supabase-pooler` (porta 5432/6543 públicas), `supabase-rest`
(PostgREST v14.12), `supabase-kong` (porta 8000/8443), `supabase-studio`,
`supabase-auth`, `supabase-storage`, `supabase-meta`, `supabase-realtime`,
`supabase-imgproxy`, `supabase-edge-functions`. **Já é Postgres + PostgREST
prontos** — antes de subir um stack RAG novo, avaliar se dá para usar
schemas isolados aqui em vez de duplicar infraestrutura.
- `genial-crm-postgres-1` já usa imagem `pgvector/pgvector:pg16` (porta local
`127.0.0.1:57432`) — pertence ao projeto CRM, não usar para o Doza.
- `ollama` (porta 11434) e `open-webui` — modelos locais já disponíveis na VPS.
- `automacoes-db` (container Docker avulso, porta local `127.0.0.1:5433`) —
criado via "Gerenciador Docker" do painel Hostinger, é o alvo original
para um novo container RAG (pgvector + PostgREST) discutido nesta sessão.
- `n8n` (porta 5678), `gitea`, `evolution-go`/`evolution-api`,
`gestor-trafego` (porta 4001), `wacrm20` (porta 3000).
## RAG Hub — banco compartilhado de desenvolvimento (equipe, não só Doza)
Criado em 2026-08-31 (renomeado de `rag-hub-doza` para `rag-hub` no mesmo dia,
assim que decidido que a infra é compartilhada). Stack própria, sem relação
com Supabase/CRM/genial-crm — serve exclusivamente para a equipe de dev usar
como RAG (indexação de código/docs) e reduzir tokens gastos por IAs ao
consultar os projetos. **Não é o banco de produção de nenhum sistema.**
Modelo: **um container Postgres+pgvector só**, e **um banco por sistema**
dentro dele (CRM, Doza, contabilidade, etc. cada um cria o seu). Mais barato
de manter (1 backup, 1 upgrade) do que um container Docker por projeto.
- Local na VPS: `/docker/rag-hub/` (compose + `.env`, `chmod 600`, gerado
isolado nesta instalação).
- Container: `rag-hub-db` (imagem `pgvector/pgvector:pg16`).
- **Não exposto publicamente** — porta ligada só em `127.0.0.1:55435` na VPS.
Acesso externo (ex: do Mac/Mini M4) é via túnel SSH:
```bash
ssh -N -L 55435:127.0.0.1:55435 root@179.197.228.240
# depois conectar em localhost:55435 normalmente
```
- Usuário admin: `rag_admin`, senha em `/docker/rag-hub/.env` na própria VPS
(não duplicada aqui).
- Extensões ativas: `vector` (pgvector 0.8.6), `pg_trgm` (habilitadas no
banco `rag_doza`; ao criar um banco novo pra outro sistema, rodar
`CREATE EXTENSION vector; CREATE EXTENSION pg_trgm;` nele também).
- **Banco do Doza**: `rag_doza`, schema `doza`, tabela inicial
`doza.code_chunks` (id, file_path, content, chunk_index,
embedding vector(768), updated_at) com índice ivfflat (cosine, lists=100).
- **Banco do Jhonny** (projeto MCP TypeScript em `code/`): `jhonny-rag`,
schema `jhonny-rag`. Criado em 2026-09-07. Usa o mesmo padrão do
`rag_tigre` (busca híbrida HNSW + pg_trgm, `file_index` com
`summary_embedding`). Role dedicada: `jhonny-rag` (grants por schema, sem
DDL). Wrappers locais: `rag/index_jhonny.sh`, `rag/search_jhonny.sh`,
`rag/migrate_jhonny.sh`; credenciais em `rag/jhonny.env`. Índice: 505
arquivos de `code/` (TypeScript), 3479 chunks. Como o schema tem um guífen,
o `search.py`/`index_code.py` escapam o identificador com aspas duplas
(`_qid`); os bancos sem guífen (doza/tigre) seguem intactos e funcionando.
- **Para adicionar outro sistema** (ex: CRM, contabilidade): conectar como
`rag_admin` e rodar `CREATE DATABASE rag_crm OWNER rag_admin;` (ou outro
nome), depois `\c rag_crm` e criar extensões/schema/tabelas próprias —
sem mexer no banco `rag_doza`.
- Init SQL do banco `rag_doza` versionado em `/docker/rag-hub/init/01-init.sql`
na VPS (roda automaticamente só na primeira subida do volume).
- Subir/parar: `ssh root@179.197.228.240 "cd /docker/rag-hub && docker compose up -d"` (ou `down`).
## Segurança
- A senha **não deve** aparecer em comandos, arquivos ou histórico do shell.
- Se a chave SSH for comprometida, revogá-la removendo a linha de
`/root/.ssh/authorized_keys` na VPS e gerar uma nova.
- Manter `~/.ssh/id_ed25519` protegido (`chmod 600`).
+127
View File
@@ -0,0 +1,127 @@
#!/bin/zsh
# ============================================================
# OpenCut Ronald - admin/commit.command
#
# COMMIT: monta a mensagem, commita e envia para o Gitea.
# 1. Le a secao 'NOVO NESTE LOTE' do admin/DEV-NOTES.md
# 2. Junta o resumo automatico dos arquivos alterados
# 3. git add -A + commit + push
#
# O push e a unica etapa que sai desta maquina, entao ele pede
# confirmacao. Use AUTO=1 para rodar sem perguntar (ex.: dentro do
# OpenCut.command em modo automatico).
#
# Uso:
# admin/commit.command commita e pergunta antes do push
# DRY=1 admin/commit.command so mostra o plano, nao commita
# AUTO=1 admin/commit.command commita e faz push sem perguntar
# ============================================================
set -u
umask 077
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
ADMIN="$ROOT/admin"
NOTES="$ADMIN/DEV-NOTES.md"
REMOTE="origin"
GRN="$(tput setaf 2 2>/dev/null)"; YLW="$(tput setaf 3 2>/dev/null)"
RD="$(tput setaf 1 2>/dev/null)"; BLU="$(tput setaf 4 2>/dev/null)"
NC="$(tput sgr0 2>/dev/null)"
say() { echo "${BLU}== ${NC}$*"; }
ok() { echo "${GRN}-> OK ${NC}$*"; }
warn() { echo "${YLW}-> Aviso: ${NC}$*"; }
die() { echo "${RD}-> Erro: ${NC}$*" >&2; exit 1; }
cd "$ROOT" || die "nao consegui entrar em $ROOT"
[[ -d .git ]] || die "nao e um repositorio git em $ROOT"
git remote get-url "$REMOTE" >/dev/null 2>&1 || die "remote '$REMOTE' nao configurado"
echo "${BLU}============================================================${NC}"
say "commit.command - commit + push"
say "repositorio: $(git remote get-url "$REMOTE")"
say "branch: $(git branch --show-current)"
echo "${BLU}============================================================${NC}"
# ------------------------------------------------------------
# 1) Ha o que commitar?
# ------------------------------------------------------------
CHANGES="$(git status --porcelain)"
if [[ -z "$CHANGES" ]]; then
ok "Nenhuma alteracao para commitar."
exit 0
fi
say "Alteracoes detectadas:"
echo "$CHANGES" | sed 's/^/ /' | head -60
echo " ... ($(echo "$CHANGES" | grep -c .) itens)"
echo
# ------------------------------------------------------------
# 2) Mensagem do commit
# ------------------------------------------------------------
MSG_NOTA=""
if [[ -f "$NOTES" && -s "$NOTES" ]]; then
MSG_NOTA="$(awk '
/^## NOVO NESTE LOTE/ {f=1; next}
/^## / && f {exit}
f
' "$NOTES" 2>/dev/null \
| sed 's/^[[:space:]]*//; s/[[:space:]]*$//' \
| grep -v '^$' \
| awk '/^<!--/{c=1;next} /-->/&&c{c=0;next} c{next} {print}' \
| sed 's/^[-*] //' \
| grep -v '(_vazio' || true)"
fi
FILES="$(git status --porcelain | sed 's/^.../ - /' | head -80)"
STAT="$(git diff --stat HEAD | tail -1)"
if [[ -n "$MSG_NOTA" ]]; then
BODY="$MSG_NOTA"
else
BODY="(sem nota em DEV-NOTES.md; veja os arquivos alterados abaixo)"
fi
COMMIT_MSG="update automatizado: alteracoes no projeto
$(printf '%s\n' "$BODY")
$STAT
Arquivos alterados:
$FILES"
say "Mensagem do commit a ser usada:"
echo "----------------------------------------------"
echo "$COMMIT_MSG"
echo "----------------------------------------------"
echo
if [[ "${DRY:-0}" = "1" ]]; then
say "DRY=1 - nao vou commitar/push."
exit 0
fi
# ------------------------------------------------------------
# 3) Commit
# ------------------------------------------------------------
say "Adicionando arquivos e commitando..."
git add -A
git commit -m "$COMMIT_MSG" || die "commit falhou"
ok "commit criado: $(git rev-parse --short HEAD)"
echo
# ------------------------------------------------------------
# 4) Push (confirmado - e o passo que publica)
# ------------------------------------------------------------
if [[ "${AUTO:-0}" != "1" ]]; then
printf "Enviar para '%s' agora? [s/N] " "$REMOTE"
read -r RESP
case "$RESP" in
[sS]|[sS][iI][mM]|[yY]|[yY][eE][sS]) ;;
*) warn "push cancelado - o commit ficou local (envie depois com: git push $REMOTE HEAD)"; exit 0 ;;
esac
fi
say "Fazendo push para '$REMOTE'..."
git push "$REMOTE" HEAD || die "push falhou"
ok "push concluido"
+128
View File
@@ -0,0 +1,128 @@
#!/bin/zsh
# ============================================================
# OpenCut Ronald - admin/deploy.command
#
# DEPLOY: garante o backend OpenCut no ar em http://127.0.0.1:5679
# e sobe a ponte de live-updates (WebSocket) que o painel consome.
#
# E este o script que o painel do Premiere dispara quando pede
# "iniciar backend" (via ~/.opencut/launcher.command). Por isso ele
# nao pergunta nada e nao mexe no git: roda sozinho e sai.
#
# Uso:
# admin/deploy.command sobe (ou recupera) o backend
# OPENCUT_RESTART=1 admin/deploy.command reinicia o backend atual
# OPENCUT_NO_OPEN=1 admin/deploy.command nao abre o navegador
# ============================================================
set -u
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
CODE="$ROOT/code"
START_SCRIPT="$CODE/start-opencut.sh"
RUNTIME_DIR="$CODE/.opencut-runtime"
LOG="$RUNTIME_DIR/server.log"
URL="http://127.0.0.1:5679"
HEALTH="$URL/health"
RESTART="${OPENCUT_RESTART:-0}"
notify() { osascript -e "display notification \"$1\" with title \"OpenCut\"" 2>/dev/null || true; }
open_ui() { [ "${OPENCUT_NO_OPEN:-0}" = "1" ] || open "$URL" 2>/dev/null || true; }
server_ok() { curl -s -m 5 "$HEALTH" 2>/dev/null | grep -q '"status":"ok"'; }
stop_opencut_pids() {
local signal="$1"
local pid
for pid in ${(f)PGREP}; do
[ -n "$pid" ] && kill "$signal" "$pid" 2>/dev/null || true
done
}
# ------------------------------------------------------------
# Ponte de live-updates (WebSocket)
#
# O backend nao sobe a ponte sozinho: ela so existe apos um POST em
# /ws/start, e esse endpoint exige o token CSRF que o /health entrega.
# Sem este passo o painel abre mostrando "Bridge stopped" e o usuario
# precisa clicar em "Start" na mao a cada boot.
# ------------------------------------------------------------
start_bridge() {
if curl -s -m 5 "$URL/ws/status" 2>/dev/null | grep -q '"running":true'; then
echo "-> ponte de live-updates ja ativa"
return 0
fi
local token
token="$(curl -s -m 5 -H "Origin: $URL" "$HEALTH" 2>/dev/null \
| /usr/bin/python3 -c 'import sys,json
try: print(json.load(sys.stdin).get("csrf_token") or "")
except Exception: print("")' 2>/dev/null)"
if [ -z "$token" ]; then
echo "-> Aviso: /health nao devolveu token CSRF; ponte nao iniciada"
return 1
fi
if curl -s -m 10 -X POST "$URL/ws/start" \
-H "Content-Type: application/json" \
-H "X-OpenCut-Token: $token" \
-H "Origin: $URL" -d '{}' 2>/dev/null | grep -q '"running":true'; then
echo "-> ponte de live-updates ativa"
return 0
fi
echo "-> Aviso: a ponte de live-updates nao subiu (veja $LOG)"
return 1
}
# 1) Ja esta no ar?
if server_ok && [ "$RESTART" != "1" ]; then
echo "-> backend ja rodando em $URL"
start_bridge
notify "Backend ja esta rodando em $URL"
open_ui
exit 0
fi
if [ "$RESTART" = "1" ]; then
echo "-> reinicio solicitado; substituindo o backend atual"
fi
# 2) Processo antigo travado? Derruba antes de subir outro.
PGREP=$(pgrep -f 'opencut.server' || true)
if [ -n "$PGREP" ]; then
notify "Backend travado detectado - reiniciando..."
echo "Encerrando processo(s) antigo(s): $PGREP"
stop_opencut_pids TERM
sleep 3
PGREP=$(pgrep -f 'opencut.server' || true)
[ -n "$PGREP" ] && stop_opencut_pids KILL
sleep 1
fi
# 3) Sobe pelo start-opencut.sh (venv, modelos, ffmpeg e tmp no Merongo)
if [ ! -x "$START_SCRIPT" ]; then
echo "Erro: $START_SCRIPT nao encontrado ou sem permissao de execucao" >&2
notify "Falha: start-opencut.sh ausente"
exit 1
fi
mkdir -p "$RUNTIME_DIR"
mkdir -p "/Volumes/Merongo/SISTEMAS/OPENCUT-MEDIA/tmp"
nohup "$START_SCRIPT" > "$LOG" 2>&1 &
NEW_PID=$!
echo "Backend iniciado (PID $NEW_PID). Aguardando health check..."
# 4) Espera ate 60s pelo health check
for i in {1..30}; do
if server_ok; then
start_bridge
notify "Backend pronto em $URL"
open_ui
exit 0
fi
if ! kill -0 $NEW_PID 2>/dev/null; then
break
fi
sleep 2
done
echo "Falha ao iniciar. Log em $LOG" >&2
notify "Falha ao iniciar o backend. Veja $LOG"
tail -30 "$LOG"
exit 1
+88
View File
@@ -0,0 +1,88 @@
#!/bin/bash
set -e
# Tigre (codeclass) - Deploy do servidor web novo em Swift
# Sobe um nivel: o script vive em admin/, o projeto e a pasta pai.
#
# Uso:
# admin/deploy-codeclass.command # porta 5050 (padrão)
# admin/deploy-codeclass.command 8080 # porta customizada
#
# O que faz:
# 1) localiza a toolchain Swift (Xcode) e compila o codeclass (swift build)
# 2) derruba qualquer instancia antiga na porta de destino
# 3) sobe o servidor novo em background (TigreApp serve) com as classes
# novas (TranscriptionService + AppleVisionAnalyzer)
# 4) abre o navegador quando o servidor responder
#
# Diferente de deploy.command (legado Python/Flask em code/app.py), este
# script sobe o servidor do codeclass/ (100% Swift). Na porta 5050 eles se
# conflitam: rode APENAS UM deles por vez.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
PORT="${1:-5050}"
CLASSDIR="$PROJECT_DIR/codeclass"
echo "============================================================"
echo " Tigre (codeclass) :: Deploy do servidor web novo (Swift)"
echo " Pasta: $PROJECT_DIR"
echo " Porta: $PORT"
echo "============================================================"
echo
# 1) Toolchain Swift (Xcode 26 / swift 6.3). Respeita PATH se ja configurado.
SWIFT="$(command -v swift || true)"
if [ -z "$SWIFT" ] && [ -x "/Volumes/Merongo/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift" ]; then
export PATH="/Volumes/Merongo/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:$PATH"
SWIFT="swift"
fi
if [ -z "$SWIFT" ]; then
echo "ERRO: swift nao encontrado no PATH nem no Xcode padrao."
echo " Instale Xcode 26+ ou exporte o PATH da toolchain."
exit 1
fi
# 2) Compilar o codeclass
echo "[1/3] Compilando o codeclass (swift build)..."
cd "$CLASSDIR"
"$SWIFT" build 2>&1 | tail -5
echo " Build OK."
echo
# 3) Derrubar instancias antigas na porta de destino
echo "[2/3] Parando instancia anterior na porta $PORT..."
lsof -ti:"$PORT" | xargs kill -9 2>/dev/null || true
sleep 1
true
# 4) Subir o servidor novo em background (nao trava o terminal)
echo "[3/3] Levantando o servidor novo (TigreApp serve)..."
echo " http://localhost:$PORT"
echo
LOG="/tmp/tigre-codeclass-web.log"
nohup "$SWIFT" run TigreApp serve --port "$PORT" --project-root "$PROJECT_DIR" > "$LOG" 2>&1 &
SERVER_PID=$!
echo " PID do servidor: $SERVER_PID (log: $LOG)"
echo
# Abre o navegador padrao assim que o servidor responder (nao trava o boot)
(
for _ in $(seq 1 45); do
if curl -s -o /dev/null "http://localhost:$PORT"; then
open "http://localhost:$PORT"
break
fi
sleep 1
done
) &
# Mostra o log por alguns segundos para confirmar o boot
sleep 5
echo "--- primeiras linhas do log ---"
tail -10 "$LOG"
echo
echo "============================================================"
echo " Servidor novo (codeclass) no ar em http://localhost:$PORT"
echo " Para encerrar: lsof -ti:$PORT | xargs kill -9"
echo "============================================================"
+110
View File
@@ -0,0 +1,110 @@
#!/bin/bash
set -e
# Doza Assist - Deploy (Graphify + Servidor)
# Sobe um nivel: o script vive em admin/, o projeto e a pasta pai
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PROJECT_DIR"
echo "============================================================"
echo " Doza Assist :: Deploy - Graphify + Servidor"
echo " Pasta: $(pwd)"
echo "============================================================"
echo
# 1) Resolve quem executa o graphify (preferencia: uv, depois graphify no PATH)
if command -v uv >/dev/null 2>&1; then
RUN="uv tool run --from graphifyy graphify"
elif command -v graphify >/dev/null 2>&1; then
RUN="graphify"
else
echo "graphify nao encontrado. Instalando via pip..."
python3 -m pip install -q --upgrade graphifyy
RUN="graphify"
fi
# 2) Roda o graphify SEMPRE (deep + update; code-only se nao houver API key)
ARGS="--mode deep --update"
if [ -n "$GEMINI_API_KEY" ] || [ -n "$GOOGLE_API_KEY" ] || [ -n "$MOONSHOT_API_KEY" ] \
|| [ -n "$ANTHROPIC_API_KEY" ] || [ -n "$OPENAI_API_KEY" ] || [ -n "$DEEPSEEK_API_KEY" ]; then
:
else
echo "AVISO: nenhuma LLM API key detectada. Usando --code-only (codigo apenas;"
echo " docs/imagens serao ignorados). Para extracao semantica completa,"
echo " defina GEMINI_API_KEY ou GOOGLE_API_KEY."
ARGS="--mode deep --update --code-only"
fi
echo
echo "Executando graphify (DEEP + UPDATE)..."
echo
$RUN "." $ARGS || echo "[AVISO] graphify falhou; o deploy continua mesmo assim."
# 2.5) Reindexar o RAG (doza.code_chunks) - so roda se rag/.env estiver
# preenchido; requer o tunel SSH do banco aberto (ver rag/SETUP.md).
# Best-effort: nunca trava o deploy.
echo
echo "Reindexando RAG..."
PORT=55435
export RAG_DB_PORT=$PORT
export RAG_DB_HOST=127.0.0.1
bash "$PROJECT_DIR/rag/ensure_tunnel.sh" || true
RAG_VENV_PY="$PROJECT_DIR/code/.venv/bin/python3"
if [ ! -x "$RAG_VENV_PY" ]; then
RAG_VENV_PY="python3"
fi
if [ -f "$PROJECT_DIR/rag/.env" ] && grep -q '^RAG_DB_PASSWORD=.\+' "$PROJECT_DIR/rag/.env"; then
"$RAG_VENV_PY" -m pip install -q -r "$PROJECT_DIR/rag/requirements.txt" 2>/dev/null || true
"$RAG_VENV_PY" "$PROJECT_DIR/rag/index_code.py" || echo "[AVISO] indexacao RAG falhou (tunel/banco indisponivel?); deploy continua."
else
echo "[INFO] rag/.env sem RAG_DB_PASSWORD preenchida; pulando indexacao RAG."
fi
# 3) Derrubar qualquer instancia antiga do servidor
echo
echo "Parando instancias anteriores do servidor..."
lsof -ti:5050 | xargs kill -9 2>/dev/null || true
pkill -f "python.*app.py" 2>/dev/null || true
sleep 1
true
# 4) Levantar o servidor Doza Assist
APP_DIR="$PROJECT_DIR/code"
cd "$APP_DIR"
# Ensure Homebrew binaries (ffmpeg, etc.) are on PATH
if [ -d "/opt/homebrew/bin" ]; then
export PATH="/opt/homebrew/bin:$PATH"
elif [ -d "/usr/local/bin" ]; then
export PATH="/usr/local/bin:$PATH"
fi
VENV_PY="$APP_DIR/.venv/bin/python3"
if [ ! -x "$VENV_PY" ]; then
echo "ERRO: ambiente Python do macOS nao encontrado:"
echo " $VENV_PY"
echo
echo "Execute primeiro: admin/install.command"
exit 1
fi
echo
echo "============================================================"
echo " Levantando o servidor Doza Assist..."
echo " http://localhost:5050"
echo "============================================================"
echo
# Abre o navegador padrao assim que o servidor responder (nao trava o boot)
(
for _ in $(seq 1 30); do
if curl -s -o /dev/null "http://localhost:5050"; then
open "http://localhost:5050"
break
fi
sleep 1
done
) &
"$VENV_PY" app.py
+57
View File
@@ -0,0 +1,57 @@
#!/bin/bash
set -e
# Graphify - atualizacao profunda e incremental do projeto
# Sobe um nivel: o script vive em admin/, o projeto e a pasta pai
SCRIPT_DIR="$(dirname "$0")"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PROJECT_DIR"
echo "============================================================"
echo " Graphify :: atualizacao profunda e incremental do projeto"
echo " Pasta: $(pwd)"
echo "============================================================"
echo
# 1) Resolve quem executa o graphify (preferencia: uv, depois graphify no PATH)
if command -v uv >/dev/null 2>&1; then
RUN="uv tool run --from graphifyy graphify"
echo "Runner: uv tool run --from graphifyy graphify"
elif command -v graphify >/dev/null 2>&1; then
RUN="graphify"
echo "Runner: graphify (PATH)"
else
echo "graphify nao encontrado. Instalando via pip..."
python3 -m pip install -q --upgrade graphifyy
RUN="graphify"
echo "Runner: graphify (apos install)"
fi
# 2) Atualizacao profunda e incremental
# --mode deep : extracao semantica agressiva (arestas INFERRED mais ricas)
# --update : re-extrai so o que mudou; 1a execucao faz o build completo
# Sem LLM API key, cai em --code-only (varre so o codigo, sem docs/imagens)
ARGS="--mode deep --update"
if [ -n "$GEMINI_API_KEY" ] || [ -n "$GOOGLE_API_KEY" ] || [ -n "$MOONSHOT_API_KEY" ] \
|| [ -n "$ANTHROPIC_API_KEY" ] || [ -n "$OPENAI_API_KEY" ] || [ -n "$DEEPSEEK_API_KEY" ]; then
:
else
echo "AVISO: nenhuma LLM API key detectada. Usando --code-only (codigo apenas;"
echo " docs/imagens serao ignorados). Para extracao semantica completa,"
echo " defina GEMINI_API_KEY ou GOOGLE_API_KEY e rode novamente."
ARGS="--mode deep --update --code-only"
fi
echo
echo "Executando graphify em modo DEEP + UPDATE..."
echo
$RUN "." $ARGS
echo
echo "============================================================"
echo " Concluido! Resultados em: $(pwd)/graphify-out"
echo " - graph.html (grafo interativo - abra no navegador)"
echo " - GRAPH_REPORT.md (relatorio de auditoria)"
echo " - graph.json (dados brutos / GraphRAG-ready)"
echo "============================================================"
echo
echo "Dica: torne executavel uma unica vez com: chmod +x graphify-update.command"
+99
View File
@@ -0,0 +1,99 @@
#!/bin/bash
set -e
# Doza Assist - Instalador (macOS)
# Sobe um nivel: o script vive em admin/, o projeto e a pasta pai
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
CODE_DIR="$PROJECT_DIR/code"
VENV_DIR="$CODE_DIR/.venv"
echo "============================================================"
echo " Doza Assist - Instalador"
echo " Pasta: $CODE_DIR"
echo "============================================================"
echo
# Homebrew no PATH (ffmpeg, python3 via brew, etc.)
if [ -d "/opt/homebrew/bin" ]; then
export PATH="/opt/homebrew/bin:$PATH"
elif [ -d "/usr/local/bin" ]; then
export PATH="/usr/local/bin:$PATH"
fi
# 1) Verificar Python
echo "[1/4] Verificando Python..."
if ! command -v python3 >/dev/null 2>&1; then
echo "ERRO: python3 nao encontrado."
echo "Instale via Homebrew: brew install python@3.12"
echo "Ou baixe de: https://www.python.org/downloads/"
exit 1
fi
PY_VER="$(python3 --version 2>&1 | awk '{print $2}')"
echo " Python encontrado: $PY_VER"
# 2) Criar ambiente virtual
echo
echo "[2/4] Criando ambiente virtual..."
if [ -d "$VENV_DIR" ] && [ ! -x "$VENV_DIR/bin/python3" ]; then
# venv existente nao e do macOS (ex.: criado no Windows, tem Scripts/ em vez de bin/)
echo " AVISO: existe um .venv incompativel com macOS em:"
echo " $VENV_DIR"
if [ -f "$VENV_DIR/pyvenv.cfg" ]; then
echo " Origem registrada:"
sed -n 's/^home = / /p' "$VENV_DIR/pyvenv.cfg"
fi
echo
echo " Para continuar e preciso recriar esse ambiente."
echo " O diretorio sera movido para .venv.bak (nada e apagado)."
printf " Recriar agora? [s/N] "
read -r RESP
case "$RESP" in
[sS]|[sS][iI][mM]|[yY]|[yY][eE][sS]) ;;
*) echo " Cancelado pelo usuario. Nada foi alterado."; exit 1 ;;
esac
rm -rf "$VENV_DIR.bak"
mv "$VENV_DIR" "$VENV_DIR.bak"
echo " Ambiente antigo movido para: $VENV_DIR.bak"
fi
if [ ! -d "$VENV_DIR" ]; then
python3 -m venv "$VENV_DIR"
echo " Ambiente virtual criado."
else
echo " Ambiente virtual ja existe."
fi
VENV_PY="$VENV_DIR/bin/python3"
# 3) Instalar dependencias
echo
echo "[3/4] Instalando dependencias..."
"$VENV_PY" -m pip install --upgrade pip >/dev/null 2>&1
"$VENV_PY" -m pip install -r "$CODE_DIR/requirements.txt"
echo " Dependencias instaladas."
# 4) Verificar ffmpeg
echo
echo "[4/4] Verificando ffmpeg..."
if ! command -v ffmpeg >/dev/null 2>&1; then
echo " ffmpeg nao encontrado."
echo " Para instalar via Homebrew: brew install ffmpeg"
echo " Ou baixe de: https://ffmpeg.org/download.html"
else
echo " ffmpeg encontrado: $(command -v ffmpeg)"
fi
echo
echo "============================================================"
echo " Instalacao concluida!"
echo
echo " Para iniciar o servidor:"
echo " cd \"$CODE_DIR\""
echo " .venv/bin/python3 app.py"
echo
echo " Ou execute: admin/deploy.command"
echo
echo " Acesse: http://localhost:5050"
echo "============================================================"
echo
+155
View File
@@ -0,0 +1,155 @@
#!/bin/bash
# Tigre (codeclass) - Empacota o TigreAppUI em um bundle .app real.
# Sobe um nivel: o script vive em admin/, o projeto e a pasta pai.
#
# Uso:
# admin/make-app.command # build debug + bundle no ~/Applications
# TIGRE_APP_CONFIG=release admin/... # build release (otimizado)
# TIGRE_APP_DIR=/tmp/Tigre.app admin/... # bundle em outro local
#
# O que faz:
# 1) compila o TigreAppUI (swift build)
# 2) gera o icone do app (make_icon.py + sips + iconutil -> AppIcon.icns)
# 3) monta ~/Applications/Tigre.app:
# Tigre.app/
# └── Contents/
# ├── Info.plist
# ├── MacOS/Tigre (binario compilado)
# └── Resources/AppIcon.icns
# 4) codesign ad-hoc (evita atrito com Gatekeeper/LaunchServices)
#
# NÃO instala nem desinstala nada no sistema: um .app e so uma pasta; cada
# execucao SOBRESCREVE o bundle. "Desinstalar" = apagar a pasta.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
CLASSDIR="$PROJECT_DIR/codeclass"
APP_NAME="Tigre"
APP_DIR="${TIGRE_APP_DIR:-$HOME/Applications/$APP_NAME.app}"
CONFIG="${TIGRE_APP_CONFIG:-debug}"
CONTENTS="$APP_DIR/Contents"
MACOS_DIR="$CONTENTS/MacOS"
RES_DIR="$CONTENTS/Resources"
echo "============================================================"
echo " Tigre :: Empacotando $APP_NAME.app"
echo " Config: $CONFIG"
echo " Destino: $APP_DIR"
echo "============================================================"
echo
# 1) Toolchain Swift (Xcode 26 / swift 6.3). Respeita PATH se ja configurado.
SWIFT="$(command -v swift || true)"
if [ -z "$SWIFT" ] && [ -x "/Volumes/Merongo/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift" ]; then
export PATH="/Volumes/Merongo/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:$PATH"
SWIFT="swift"
fi
if [ -z "$SWIFT" ]; then
echo "ERRO: swift nao encontrado no PATH nem no Xcode padrao."
exit 1
fi
# 2) Compilar o codeclass (ida incremental; release e mais lento)
echo "[1/4] Compilando o codeclass ($CONFIG)..."
cd "$CLASSDIR"
STDOUT_LOG="/tmp/tigre-build.log"
if "$SWIFT" build -c "$CONFIG" > "$STDOUT_LOG" 2>&1; then
tail -3 "$STDOUT_LOG" | sed 's/^/ /' || true
else
echo " ERRO de compilacao. Ultimas linhas:"
tail -20 "$STDOUT_LOG" | sed 's/^/ /'
exit 1
fi
BIN="$CLASSDIR/.build/$CONFIG/TigreAppUI"
if [ ! -x "$BIN" ]; then
echo "ERRO: binario nao encontrado apos build: $BIN"
exit 1
fi
echo " Build OK."
echo
# 3) Icone do app
# Fonte: app-icon.png na RAIZ do projeto (prioridade), depois admin/app-icon.png
# (compat), e fallback make_icon.py (gera o "T" laranja sintetico).
echo "[2/4] Gerando icone (AppIcon.icns)..."
CUSTOM_ICON=""
for _cand in "$PROJECT_DIR/app-icon.png" "$SCRIPT_DIR/app-icon.png"; do
if [ -f "$_cand" ]; then
CUSTOM_ICON="$_cand"
break
fi
done
ICON_PNG="/tmp/tigre_icon_1024.png"
if [ -n "$CUSTOM_ICON" ]; then
echo " Usando icone customizado ($(echo "$CUSTOM_ICON" | sed "s|$PROJECT_DIR|.|"), $(sips -g pixelWidth "$CUSTOM_ICON" 2>/dev/null | awk '/pixelWidth/{print $2}')px)"
# Resample p/ 1024 (o iconutil exige o slot 512@2x = 1024) e deriva os demais
sips -z 1024 1024 "$CUSTOM_ICON" --out "$ICON_PNG" >/dev/null 2>&1
else
echo " app-icon.png ausente — usando icone padrao (make_icon.py)"
python3 "$SCRIPT_DIR/make_icon.py" "$ICON_PNG"
fi
ICONSET="/tmp/Tigre.iconset"
rm -rf "$ICONSET"
mkdir -p "$ICONSET"
for sz in 16 32 128 256 512; do
sips -z "$sz" "$sz" "$ICON_PNG" --out "$ICONSET/icon_${sz}x${sz}.png" >/dev/null 2>&1
dbl=$((sz * 2))
sips -z "$dbl" "$dbl" "$ICON_PNG" --out "$ICONSET/icon_${sz}x${sz}@2x.png" >/dev/null 2>&1
done
iconutil -c icns "$ICONSET" -o "/tmp/TigreAppIcon.icns"
rm -rf "$ICONSET" "$ICON_PNG"
echo " Icone OK."
# 4) Montar o bundle .app
echo "[3/4] Montando o bundle..."
rm -rf "$APP_DIR"
mkdir -p "$MACOS_DIR" "$RES_DIR"
cp "$BIN" "$MACOS_DIR/$APP_NAME"
cp "/tmp/TigreAppIcon.icns" "$RES_DIR/AppIcon.icns"
cat > "$CONTENTS/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>Tigre</string>
<key>CFBundleDisplayName</key>
<string>Tigre</string>
<key>CFBundleIdentifier</key>
<string>com.genial.tigre</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleExecutable</key>
<string>${APP_NAME}</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>LSMinimumSystemVersion</key>
<string>14.0</string>
<key>LSUIElement</key>
<false/>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
</dict>
</plist>
PLIST
echo " Bundle montado."
# 5) Assinatura ad-hoc (sem identity de Developer ID, suficiente p/ uso local)
echo "[4/4] Assinatura ad-hoc..."
codesign --force --deep -s - "$APP_DIR" 2>&1 | sed 's/^/ /' || true
echo " Pronto: $APP_DIR"
echo
echo " Para lançar: open \"$APP_DIR\""
echo " Para remover: rm -rf \"$APP_DIR\""
echo
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""
Gera o ícone do Tigre (app GUI SwiftUI) em 1024x1024 PNG.
Desenho: fundo escuro arredondado (gradiente), um "T" em laranja com
listras diagonais escuras (pele de tigre). A geração dos tamanhos menores
e do .icns é feita pelo make-app.command via sips + iconutil.
Uso:
python3 admin/make_icon.py [caminho_de_saida.png]
"""
import struct
import zlib
import sys
import os
SIZE = 1024
def create_png(width, height, pixels):
"""Cria um PNG RGBA."""
def chunk(ctype, data):
c = ctype + data
crc = struct.pack('>I', zlib.crc32(c) & 0xFFFFFFFF)
return struct.pack('>I', len(data)) + c + crc
header = b'\x89PNG\r\n\x1a\n'
ihdr = chunk(b'IHDR', struct.pack('>IIBBBBB', width, height, 8, 6, 0, 0, 0))
raw = b''
for y in range(height):
raw += b'\x00'
for x in range(width):
i = (y * width + x) * 4
raw += bytes(pixels[i:i+4])
idat = chunk(b'IDAT', zlib.compress(raw, 9))
return header + ihdr + idat + chunk(b'IEND', b'')
def lerp(a, b, t):
return int(a + (b - a) * t)
def draw_icon(size):
px = [0] * (size * size * 4)
def set_px(x, y, r, g, b, a=255):
if 0 <= x < size and 0 <= y < size:
i = (y * size + x) * 4
af = a / 255.0
px[i] = lerp(px[i], r, af)
px[i+1] = lerp(px[i+1], g, af)
px[i+2] = lerp(px[i+2], b, af)
px[i+3] = max(px[i+3], a)
def in_rrect(x, y, x0, y0, x1, y1, r):
"""Ponto dentro de retângulo arredondado (sem AA — ok, sips suaviza)."""
if x < x0 or x > x1 or y < y0 or y > y1:
return False
cx = min(max(x, x0 + r), x1 - r)
cy = min(max(y, y0 + r), y1 - r)
return (x - cx) ** 2 + (y - cy) ** 2 <= r * r
def fill_rrect(x0, y0, x1, y1, r, rg, g_, b_, a=255):
for y in range(int(y0), int(y1)):
for x in range(int(x0), int(x1)):
if in_rrect(x, y, x0, y0, x1, y1, r):
set_px(x, y, rg, g_, b_, a)
# ── Fundo: gradiente vertical escuro + cantos arredondados (squircle liso) ──
m = size * 0.03
corner = size * 0.22
for y in range(size):
t = y / size
br = lerp(0x2b, 0x12, t)
bgc = lerp(0x2b, 0x12, t)
bb = lerp(0x33, 0x16, t)
for x in range(size):
if in_rrect(x, y, m, m, size - m - 1, size - m - 1, corner):
set_px(x, y, br, bgc, bb)
# ── Letra "T" (barra + haste) em laranja com gradiente ──
t_x0, t_x1 = size * 0.16, size * 0.84
bar_y0, bar_y1 = size * 0.28, size * 0.47
stem_x0, stem_x1 = size * 0.42, size * 0.58
stem_y0, stem_y1 = size * 0.47, size * 0.78
lr = size * 0.04
for y in range(size):
t = y / size
or_ = lerp(0xff, 0xee, t)
og = lerp(0xa2, 0x5c, t)
ob = lerp(0x38, 0x0d, t)
# barra
for x in range(size):
if in_rrect(x, y, t_x0, bar_y0, t_x1, bar_y1, lr):
set_px(x, y, or_, og, ob)
# haste
for x in range(size):
if in_rrect(x, y, stem_x0, stem_y0, stem_x1, stem_y1, lr):
if x < stem_x0 + lr or x > stem_x1 - lr or y > stem_y1 - lr:
if in_rrect(x, y, stem_x0, stem_y0, stem_x1, stem_y1, lr):
set_px(x, y, or_, og, ob)
else:
set_px(x, y, or_, og, ob)
# ── Listras de tigre (faixas diagonais escuras) ──
stripes = [
(0.20, 0.36, 0.58, 0.76), # haste (diagonal)
(0.34, 0.50, 0.26, 0.42),
(0.50, 0.66, 0.22, 0.40),
(0.22, 0.40, 0.62, 0.82),
]
# Converte faixa reta em diagonal: x' = x + (y - bar_y0) * slant
slant = 0.35
for (x0f, x1f, y0f, y1f) in stripes:
x0, x1 = size * x0f, size * x1f
y0, y1 = size * y0f, size * y1f
for y in range(int(y0), int(y1)):
shift = (y - y0) * slant
if shift < 0:
continue
sx0, sx1 = x0 + shift, x1 + shift
for x in range(int(sx0), int(sx1)):
if x < 0 or x >= size:
continue
i = (y * size + x) * 4
# cor escura translúcida (mistura manual p/ lerp com canal alpha)
af = 0.78
px[i] = lerp(px[i], 0x0c, af)
px[i+1] = lerp(px[i+1], 0x0a, af)
px[i+2] = lerp(px[i+2], 0x06, af)
px[i+3] = max(px[i+3], int(255 * 0.85))
return px
def main():
out = sys.argv[1] if len(sys.argv) > 1 else "/tmp/tigre_icon_1024.png"
px = draw_icon(SIZE)
with open(out, "wb") as f:
f.write(create_png(SIZE, SIZE, px))
print(f"OK: {out} ({SIZE}x{SIZE})")
if __name__ == "__main__":
main()
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Compara o pixel medio de dois PNGs (palette grossa, suficiente p/ conferir se o icns e o mesmo desenho)."""
import struct
import sys
import zlib
def read_png(path):
d = open(path, 'rb').read()
assert d[:8] == b'\x89PNG\r\n\x1a\n', f'nao e PNG: {path}'
pos, w, h = 8, None, None
idat = b''
while pos < len(d):
ln = struct.unpack('>I', d[pos:pos+4])[0]
typ = d[pos+4:pos+8]
data = d[pos+8:pos+8+ln]
if typ == b'IHDR':
w, h = struct.unpack('>II', data[:8])
elif typ == b'IDAT':
idat += data
pos += 12 + ln
if typ == b'IEND':
break
raw = zlib.decompress(idat)
assert w is not None
stride = w * 4 + 1
prev = bytearray(w * 4)
out = bytearray()
for y in range(h):
row = bytearray(raw[y*stride:(y+1)*stride])
f = row[0]
line = bytearray(row[1:])
for x in range(w):
for k in range(4):
i = x * 4 + k
a = line[i-4] if x > 0 and k < 3 else 0 # aprox: usa vizinho p/ filtros
b = prev[i]
c = line[i-4] if x > 0 else 0
if f == 1:
v = (line[i] + (line[i-4] if x > 0 else 0)) & 255
elif f == 2:
v = (line[i] + prev[i]) & 255
elif f == 3:
v = (line[i] + ((a + b) >> 1)) & 255
elif f == 4:
p = a + b - c
pa, pb, pc = abs(p-a), abs(p-b), abs(p-c)
pr = a if (pa <= pb and pa <= pc) else (b if pb <= pc else c)
v = (line[i] + pr) & 255
else:
v = line[i]
line[i] = v
out += line
prev = line
return w, h, out
def average(path):
w, h, px = read_png(path)
n = w * h
r = sum(px[i] for i in range(0, len(px), 4)) // n
g = sum(px[i] for i in range(1, len(px), 4)) // n
b = sum(px[i] for i in range(2, len(px), 4)) // n
opaque = sum(1 for i in range(3, len(px), 4) if px[i] > 200) * 100 // n
return w, h, r, g, b, f'{opaque}% opaco'
def main():
icns_png = sys.argv[1] # 1024 extraido do .icns
orig_png = sys.argv[2] # PNG original do usuario
a = average(icns_png)
b = average(orig_png)
print(f'icns 1024 -> {a}')
print(f'original -> {b}')
# Mesmo desenho se cores medias proximas e ambos coloridos/transparentes com padrao similar
close = abs(a[2]-b[2]) <= 24 and abs(a[3]-b[3]) <= 24 and abs(a[4]-b[4]) <= 24
print('MATCH (mesmo desenho)' if close else 'DIFF — verificar manualmente')
sys.exit(0 if close else 1)
if __name__ == '__main__':
main()
+112
View File
@@ -0,0 +1,112 @@
#!/bin/bash
# Tigre (codeclass) - App GUI SwiftUI: build + empacota .app + abre.
# Sobe um nivel: o script vive em admin/, o projeto e a pasta pai.
#
# Uso:
# admin/run-app.command # build + indexa RAG + abre app
# TIGRE_SKIP_RAG_INDEX=1 admin/run-app.command # pula a reindexação do RAG
# TIGRE_APP_CONFIG=release admin/run-app.command# build release (otimizado)
#
# O que faz:
# 1) build do TigreAppUI (via make-app.command) e monta o bundle Tigre.app
# em ~/Applications/Tigre.app — um .app no macOS é uma pasta; a cada run
# nós SOBRESCREVEMOS o bundle (sem "instalar/desinstalar" nada no sistema).
# 2) reindexa o RAG do codeclass (rag/index_tigre.sh) — o build acabou de
# atualizar Sources/; pode ser pulado com TIGRE_SKIP_RAG_INDEX=1
# 3) finaliza instancia anterior do app GUI, se houver
# 4) abre o Tigre.app com `open` → macOS registra no LaunchServices: o app
# aparece no Dock com ícone, Cmd+Tab, menu no topo e nome "Tigre".
#
# Nao conflita com deploy-codeclass.command (servidor web na porta 5050) —
# os dois podem rodar juntos e enxergam os mesmos projetos.
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
CLASSDIR="$PROJECT_DIR/codeclass"
APP_DIR="${TIGRE_APP_DIR:-$HOME/Applications/Tigre.app}"
APP_BIN="$APP_DIR/Contents/MacOS/Tigre"
LOG="/tmp/tigre-appui.log"
echo "============================================================"
echo " Tigre (codeclass) :: App GUI SwiftUI"
echo " Pasta: $PROJECT_DIR"
echo " App bundle: $APP_DIR"
echo "============================================================"
echo
# 1) Empacotar/atualizar o bundle .app (faz o swift build internamente)
echo "[1/3] Build + bundle .app (make-app.command)..."
"$SCRIPT_DIR/make-app.command" || exit 1
echo
# 2) Reindexar o RAG do codeclass (rag_tigre) — passo best effort: falha de
# indexação (ex.: túnel SSH fora) NÃO impede o app de subir.
RAG_INDEXER="$PROJECT_DIR/rag/index_tigre.sh"
RAG_LOG="/tmp/tigre-rag-index.log"
if [ "${TIGRE_SKIP_RAG_INDEX:-0}" = "1" ]; then
echo "[2/3] Reindexação do RAG pulada (TIGRE_SKIP_RAG_INDEX=1)."
elif [ ! -x "$RAG_INDEXER" ]; then
echo "[2/3] AVISO: rag/index_tigre.sh não encontrado — reindexação pulada."
else
echo "[2/3] Reindexando o RAG do codeclass (rag/index_tigre.sh)..."
if "$RAG_INDEXER" > "$RAG_LOG" 2>&1; then
grep -E 'OK:|gravado' "$RAG_LOG" | tail -2 | sed 's/^/ /'
echo " RAG indexado."
else
echo " AVISO: reindexação do RAG falhou (o app segue). Log: $RAG_LOG"
tail -3 "$RAG_LOG" | sed 's/^/ /'
fi
fi
echo
# 3) Encerrar instancia anterior do app GUI — tanto do bundle (.app) quanto do
# binario solto do .build (debug/release), senao sobram instancias antigas
# vivas e o `open -n` abre uma nova por cima.
echo "[3/3] Encerrando instâncias anteriores do Tigre (se houver)..."
pkill -f "$APP_BIN" 2>/dev/null || true # instancia do bundle .app
pkill -f "TigreAppUI" 2>/dev/null || true # binario solto do .build (debug/release)
pkill -x "Tigre" 2>/dev/null || true # qualquer processo "Tigre" restante
# Espera o processo encerrar de verdade (SIGTERM pode ser ignorado pelo app)
for _ in 1 2 3 4 5; do
pgrep -f "TigreAppUI|$APP_BIN" >/dev/null 2>&1 || break
sleep 1
done
# Forca a saida se ainda estiver vivo
pkill -9 -f "$APP_BIN" 2>/dev/null || true
pkill -9 -f "TigreAppUI" 2>/dev/null || true
pkill -9 -x "Tigre" 2>/dev/null || true
sleep 1
true
# 4) Abrir o app via LaunchServices (`open`) — não é nohup de binário solto.
# TIGRE_PROJECT_ROOT garante que o app ache o puente Python do legado.
echo " Abrindo o Tigre.app..."
TIGRE_PROJECT_ROOT="$PROJECT_DIR" open -n "$APP_DIR"
echo
# Confere se o processo do bundle subiu
sleep 3
if pgrep -f "$APP_BIN" >/dev/null 2>&1; then
echo "============================================================"
echo " Tigre no ar. Ele aparece no Dock com icon e nome 'Tigre'."
echo " Para encerrar: Cmd+Q no app | pkill -f '$APP_BIN'"
echo " Para remover: rm -rf '$APP_DIR'"
echo "============================================================"
else
echo "============================================================"
echo " AVISO: nao detectamos o processo do app. Log do ultimo launch:"
echo "------------------------------------------------------------"
tail -20 "$LOG" 2>/dev/null || true
echo "============================================================"
fi
# Fecha a janela do Terminal que rodou o .command (padrao G-ART run_app):
# o app ja esta desacoplado, o script so encerra antes do aviso de
# "finalizar processos nesta janela".
(
sleep 2
osascript -e 'tell application "Terminal" to close (every window whose name contains "run-app")' >/dev/null 2>&1
) &
exit 0
+67
View File
@@ -0,0 +1,67 @@
#!/bin/bash
# Doza Assist - Testes automatizados (macOS)
# Sobe um nivel: o script vive em admin/, o projeto e a pasta pai
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="$PROJECT_DIR/code"
VENV_PY="$APP_DIR/.venv/bin/python3"
# Cores (so quando a saida e um terminal)
if [ -t 1 ]; then
C_CYAN=$'\033[36m'; C_GREEN=$'\033[32m'; C_RED=$'\033[31m'
C_YELLOW=$'\033[33m'; C_OFF=$'\033[0m'
else
C_CYAN=''; C_GREEN=''; C_RED=''; C_YELLOW=''; C_OFF=''
fi
echo
echo "${C_CYAN}============================================================${C_OFF}"
echo "${C_CYAN} DOZA ASSIST - TESTES AUTOMATIZADOS${C_OFF}"
echo "${C_CYAN}============================================================${C_OFF}"
echo
if [ ! -x "$VENV_PY" ]; then
echo "${C_RED}ERRO: ambiente Python nao encontrado:${C_OFF}"
echo "${C_YELLOW}$VENV_PY${C_OFF}"
echo
echo "Execute primeiro a instalacao do projeto para criar a pasta .venv:"
echo " admin/install.command"
echo
exit 1
fi
cd "$APP_DIR" || exit 1
export PYTHONUTF8=1
# pytest nao consta em requirements.txt; instala sob demanda no venv do projeto
if ! "$VENV_PY" -c "import pytest" >/dev/null 2>&1; then
echo "${C_YELLOW}pytest nao encontrado no venv. Instalando...${C_OFF}"
if ! "$VENV_PY" -m pip install -q pytest; then
echo "${C_RED}ERRO: falha ao instalar o pytest.${C_OFF}"
exit 1
fi
echo "${C_GREEN}pytest instalado.${C_OFF}"
echo
fi
echo "${C_CYAN}Cada teste aprovado aparecera abaixo como PASSED em verde.${C_OFF}"
echo "${C_CYAN}Falhas aparecerao como FAILED em vermelho, com o motivo resumido.${C_OFF}"
echo
"$VENV_PY" -m pytest -vv --tb=short -ra --color=yes "$@"
TEST_EXIT=$?
echo
echo "============================================================"
if [ "$TEST_EXIT" -eq 0 ]; then
echo "${C_GREEN} TODOS OS TESTES FORAM APROVADOS.${C_OFF}"
echo "============================================================"
echo
exit 0
fi
echo "${C_RED} EXISTEM TESTES COM FALHA. Veja os itens FAILED acima.${C_OFF}"
echo "============================================================"
echo
exit "$TEST_EXIT"
+161
View File
@@ -0,0 +1,161 @@
#!/bin/zsh
# ============================================================
# OpenCut Ronald - admin/update.command
#
# ATUALIZACOES: deixa a arvore pronta para rodar/commitar.
# 1. Sincroniza as dependencias Python do venv de producao
# 2. Rebuild dos paineis CEP/UXP (so se a fonte mudou)
# 3. Testes (painel + backend)
#
# Nao commita, nao faz push e nao sobe servidor - isso e trabalho do
# commit.command e do deploy.command. Rode-o sozinho quando quiser so
# revalidar a arvore, ou deixe o OpenCut.command chamar tudo em ordem.
#
# Uso:
# admin/update.command ciclo completo
# SKIP_TESTS=1 admin/update.command pula os testes
# SKIP_DEPS=1 admin/update.command pula a sincronia de dependencias
# ============================================================
set -u
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
CODE="$ROOT/code"
PANEL="$CODE/extension/com.opencut.panel"
UXP="$CODE/extension/com.opencut.uxp"
# venv de producao (o mesmo que start-opencut.sh usa). Um segundo venv em
# code/.venv ja existiu e divergiu deste - foi assim que o painel passou a
# mostrar "Bridge stopped": o servidor subiu num ambiente sem websockets.
VENV="/Volumes/Merongo/Library/Application Support/OpenCut/venv"
VENV_PY="$VENV/bin/python"
# Python 3.14 currently crashes in native test dependencies on macOS. Use the
# repository's Python 3.13 test environment when available; production keeps
# using the dedicated venv above.
TEST_VENV_PY="$CODE/.venv/bin/python"
GRN="$(tput setaf 2 2>/dev/null)"; YLW="$(tput setaf 3 2>/dev/null)"
RD="$(tput setaf 1 2>/dev/null)"; BLU="$(tput setaf 4 2>/dev/null)"
NC="$(tput sgr0 2>/dev/null)"
say() { echo "${BLU}== ${NC}$*"; }
ok() { echo "${GRN}-> OK ${NC}$*"; }
warn() { echo "${YLW}-> Aviso: ${NC}$*"; }
die() { echo "${RD}-> Erro: ${NC}$*" >&2; exit 1; }
FAILED=0
cd "$ROOT" || die "nao consegui entrar em $ROOT"
# Match start-opencut.sh so tests write logs/caches to the Merongo runtime and
# resolve the security-approved media binaries.
export HOME="$CODE/.opencut-runtime/home"
export OPENCUT_HOME="$HOME/.opencut"
export XDG_CACHE_HOME="$CODE/.opencut-runtime/cache"
export XDG_CONFIG_HOME="$CODE/.opencut-runtime/config"
export HF_HOME="/Volumes/Merongo/SISTEMAS/MODELOSIA/hf-cache"
export WHISPER_MODELS_DIR="/Volumes/Merongo/SISTEMAS/MODELOSIA"
export TORCH_HOME="/Volumes/Merongo/SISTEMAS/MODELOSIA/torch-cache"
mkdir -p "$HOME" "$OPENCUT_HOME" "$XDG_CACHE_HOME" "$XDG_CONFIG_HOME"
echo "${BLU}============================================================${NC}"
say "update.command - dependencias + build + testes"
echo "${BLU}============================================================${NC}"
echo
# ------------------------------------------------------------
# 1) Dependencias Python
# ------------------------------------------------------------
if [ "${SKIP_DEPS:-0}" = "1" ]; then
ok "SKIP_DEPS=1 - pulando dependencias"
elif [ ! -x "$VENV_PY" ]; then
warn "venv de producao nao encontrado em $VENV (o disco Merongo esta montado?)"
else
say "Sincronizando dependencias com code/requirements.txt"
if "$VENV_PY" -m pip install -q -r "$CODE/requirements.txt"; then
ok "dependencias em dia"
else
warn "pip install falhou"
FAILED=1
fi
# Guarda-costas do bug do "Bridge stopped": a ponte importa websockets
# direto, e sem ele o /ws/start responde 400 e o painel fica parado.
if "$VENV_PY" -c "import websockets" >/dev/null 2>&1; then
ok "websockets presente (ponte de live-updates pode subir)"
else
warn "websockets AUSENTE - a ponte de live-updates vai falhar"
FAILED=1
fi
fi
echo
# ------------------------------------------------------------
# 2) Build dos paineis (so se a fonte mudou)
# ------------------------------------------------------------
CHANGES="$(git status --porcelain 2>/dev/null || true)"
if echo "$CHANGES" | grep -qE 'code/extension/(com\.opencut\.panel|com\.opencut\.uxp)/'; then
say "Fonte dos paineis alterada - regenerando build"
if ( cd "$PANEL" && npm run build >/dev/null 2>&1 ); then
ok "build com.opencut.panel"
else
warn "build do painel falhou"
FAILED=1
fi
if [ -f "$UXP/package.json" ]; then
if ( cd "$UXP" && npm run build >/dev/null 2>&1 ); then
ok "build com.opencut.uxp"
else
warn "build do uxp falhou"
FAILED=1
fi
fi
else
ok "Fonte dos paineis inalterada - sem rebuild"
fi
echo
# ------------------------------------------------------------
# 3) Testes
# ------------------------------------------------------------
if [ "${SKIP_TESTS:-0}" = "1" ]; then
ok "SKIP_TESTS=1 - pulando testes"
else
say "Testes do painel (vitest)"
if ( cd "$PANEL" && npm test >/dev/null 2>&1 ); then
ok "testes do painel passaram"
else
warn "testes do painel FALHARAM - rode: (cd \"$PANEL\" && npm test)"
FAILED=1
fi
if [ -x "$TEST_VENV_PY" ] || [ -x "$VENV_PY" ]; then
say "Testes do backend (pytest)"
TEST_PY="$TEST_VENV_PY"
[ -x "$TEST_PY" ] || TEST_PY="$VENV_PY"
if ! "$TEST_PY" -c "import pytest" >/dev/null 2>&1; then
"$TEST_PY" -m pip install -q pytest >/dev/null 2>&1 || true
fi
if "$TEST_PY" -c "import pytest" >/dev/null 2>&1; then
# O backend usa o FFmpeg do projeto (governado pelo gate de CVEs em
# start-opencut.sh). Os testes precisam do MESMO PATH, senão pegam o
# ffmpeg do sistema (ex: Homebrew 8.1) e caem no FfmpegSecurityError.
if [ -x "/Volumes/Merongo/SISTEMAS/OPENCUT-MEDIA/ffmpeg/bin/ffmpeg" ]; then
export PATH="/Volumes/Merongo/SISTEMAS/OPENCUT-MEDIA/ffmpeg/bin:$PATH"
fi
if ( cd "$CODE" && "$TEST_PY" -m pytest -q --tb=short ); then
ok "testes do backend passaram"
else
warn "testes do backend FALHARAM"
FAILED=1
fi
else
warn "pytest indisponivel - testes do backend pulados"
fi
fi
fi
echo
if [ "$FAILED" = "0" ]; then
ok "Atualizacao concluida sem pendencias."
exit 0
fi
warn "Atualizacao concluida COM avisos - reveja os itens acima antes de commitar."
exit 1
Binary file not shown.
@@ -0,0 +1,60 @@
# Premiere Pro MCP Tracking Plan
**Last updated:** 2026-08-23
## Decisions this data should inform
1. Which assistant route produces the most connector downloads and safe first checks?
2. Where do visitors abandon setup or open recovery guidance?
3. Which client, OS, and Premiere-version combinations reach a verified server-side tool result?
4. Which acquisition sources produce verified activation rather than page views alone?
## Tools and boundaries
- The public landing uses GA4 for page views and bounded setup interactions.
- The MCP server uses PostHog only when a production key is configured.
- Never send prompts, arguments, tool results, project or media names, file paths, tokens, IP addresses, or profile contents.
- Website analytics and server activation are separate datasets unless an explicit privacy-reviewed anonymous correlation mechanism is introduced later.
## Website events
| Event | Properties | Trigger | Decision |
| --- | --- | --- | --- |
| `primary_cta_clicked` | `location`, `destination` | Hero, final, and guide CTA | Which top-level path earns intent? |
| `marketing_demo_played` | `demo` | First playback per page view | Does the walkthrough support evaluation? |
| `onboarding_assistant_selected` | `assistant` | Assistant route selected | Which setup path is demanded? |
| `onboarding_download_started` | `route` | Bundle, guide, or connector action | Which routes progress to distribution? |
| `onboarding_safe_prompt_copied` | none | Safe prompt copied | Is the visitor preparing to verify? |
| `onboarding_project_intake_prompt_copied` | `prompt_kind` | Project Intake guide prompt copied | Does the outcome-specific route earn an attempted workflow preview? |
| `onboarding_advanced_opened` | none | Advanced setup opened | How often does guided setup fall short? |
| `onboarding_recovery_opened` | none | Recovery help opened | Where does setup friction appear? |
## Server events
| Event | Approved property themes | Funnel stage |
| --- | --- | --- |
| `mcp_connection_attempt` | bounded transport, status, duration | Connection attempt |
| `mcp_request` | bounded method, outcome, status, duration | MCP request |
| `mcp_tool_call` | tool name, outcome, status, duration, bounded error category | Supported action result |
| `premiere_mcp_activation_completed` | selected bridge and fixed `verified_connection` stage | The read-only check confirms the selected bridge, an open project, and an active sequence |
## GA4 conversions to configure
- Mark `onboarding_download_started` as a key event.
- Mark `onboarding_safe_prompt_copied` as a key event.
- Keep `marketing_cta_clicked` and `marketing_demo_played` diagnostic rather than primary conversions.
- Use lowercase UTM values: `utm_source`, `utm_medium`, `utm_campaign`, and `utm_content`.
## Activation reporting boundary
The repository-owned activation signal is emitted only when `verify_premiere_connection` returns `ready`: the MCP client reached the server, the selected bridge answered, and Premiere reported an open project and active sequence. A website download, connection attempt, incomplete diagnostic, or generic successful tool call is not activation.
There is no privacy-safe way to identify an editor's or client-side installation's first supported value from this event: it carries no editor, project, or client-installation identifier. `mcp_tool_call` remains operational telemetry, not a first-value conversion or proof of a host-observable workflow result.
## Validation checklist
- Confirm each browser event once in GA4 DebugView without duplicate firing.
- Confirm no analytics payload includes user content or local paths.
- Verify server events in the intended PostHog project after deployment.
- Segment server outcomes by client, OS, Premiere major version, and tool only when those fields are bounded and available.
- Review event volume and error categories monthly; retire events that do not change a decision.
+20
View File
@@ -0,0 +1,20 @@
{
"name": "premiere-pro-mcp",
"interface": {
"displayName": "Premiere Pro MCP"
},
"plugins": [
{
"name": "premiere-pro",
"source": {
"source": "local",
"path": "./plugins/premiere-pro"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Creativity"
}
]
}
+188
View File
@@ -0,0 +1,188 @@
# Product Marketing Context
**Document version:** v11
**Last updated:** 2026-09-04
## Product Overview
**One-liner:** Premiere Pro MCP provides reviewable workflow automation for Adobe Premiere Pro through compatible AI clients.
**What it does:** The local MCP server connects a compatible AI client to Premiere through the production CEP bridge, with a capability-gated UXP expansion on supported hosts. It lets an editor inspect local project context, create a bounded plan, confirm meaningful changes, and evaluate returned state or diagnostics before relying on a workflow.
**Product category:** Reviewable Premiere Pro workflow automation; MCP server and AI-assisted editorial infrastructure.
**Product type:** Free, MIT-licensed open-source developer and editor tool. A commercial companion product is a future product hypothesis, not a launched service.
**Business model and pricing:** The current server is free and open source; no paid plan, checkout, revenue, or hosted-media service is currently offered. A design-partner program ($499–$1,500 per team for 60 days) and a Pro companion ($19–$29 per month) are unvalidated pricing hypotheses, not published offers or promises.
## Target Audience
**Primary ICP:** Small post-production teams and agencies (roughly 3–20 editors) with repeatable Premiere setup, organization, cutdown, and delivery work. The practical champion is a technical editor, assistant editor, post supervisor, or workflow lead who can validate an install and define repeatable team workflows.
**Secondary audiences:** High-output independent editors with repeated project-preparation or delivery tasks, and developer-led media teams that need structured Premiere integration.
**Decision-makers:** Post-production leads, technical directors, workflow engineers, individual editors, and developer-tool evaluators.
**Primary use case:** Reduce repetitive Premiere work from a chosen MCP-capable client while keeping the recommended control path and project media on the local computer.
**Jobs to be done:**
- Inspect a project and active sequence before changing anything.
- Preview and carry out a supported, repeatable editing workflow.
- Preflight an export or return observable state and diagnostics after an operation.
**Initial workflow-pack hypotheses:** Project Intake, Platform Cutdowns, and Delivery Preflight. These are roadmap concepts until each workflow has a versioned contract and real-host evidence.
## Personas
| Persona | Cares about | Challenge | Value we promise |
| --- | --- | --- | --- |
| Technical editor or assistant editor | Faster repetitive work without surrendering creative judgment | UI macros and one-off scripts are brittle and hard to verify | Structured tools, previewable plans, diagnostics, and explicit results |
| Post-production lead or workflow owner | Repeatability, supportability, and safe adoption across editor systems | Host versions and undocumented APIs vary | Capability metadata, compatibility guidance, and evidence-bounded workflow contracts |
| Workflow developer | Extensible automation from an existing AI client | Building and maintaining a Premiere bridge is expensive | Open-source MCP, CEP, UXP, and packaging foundations |
## Problems & Pain Points
**Core problem:** Editors spend time on repeatable project inspection, organization, timeline, and delivery tasks that are difficult to coordinate with a natural-language interface alone.
**Why alternatives fall short:**
- Visual UI automation guesses at interface state and breaks across layouts.
- Generic AI video tools can require moving work into a separate hosted workflow.
- Raw scripts lack guided discovery, authority boundaries, and consistent diagnostics.
- A tool catalog alone does not define a reliable, repeatable outcome for a team.
**What it costs them:** Repetitive labor, interrupted creative focus, fragile handoffs, rework, and uncertainty about whether an automated operation changed the intended project state.
**Emotional tension:** Editors want assistance without an opaque system silently making destructive or unverifiable changes.
## Competitive Landscape
**Direct:** Other Premiere-focused MCP servers and AI-control bridges. Compare installability, supported host surfaces, verification behavior, safety boundaries, and maintenance evidence rather than tool count alone.
**Secondary:** Premiere scripts, panels, macros, and outcome-specific automation products. They can solve a narrow task well, but may not offer client choice, structured workflow contracts, or a local inspect-plan-confirm-verify path.
**Adobe AI Assistant:** Adobe's public beta overlaps with media organization, footage preparation, and initial-assembly work. Adobe's current FAQ also says that connecting a user model, reference-document or templated workflows, team conversation sharing, and chat-history export are not available today. Treat it as a complementary and evolving native alternative, not a competitor to dismiss. Do not claim that Premiere Pro MCP is generally better than Adobe AI Assistant; differentiate on client choice, local-first orchestration, structured workflow contracts, and explicit verification boundaries. Sources reviewed 2026-08-23: <https://helpx.adobe.com/premiere/desktop/premiere-ai-assistant/overview.html> and <https://helpx.adobe.com/premiere/desktop/premiere-ai-assistant/assistant-faq.html>.
**Indirect:** Manual editing and separate hosted AI editors. They can be familiar or convenient, but do not provide the same structured local control path into an existing Premiere project.
## Differentiation
**Key differentiators:**
- Local-first recommended architecture.
- Compatible-client choice rather than a single assistant experience.
- Broad structured tool surface with capability and authority metadata.
- Read-only connection verification and diagnostic paths.
- Opt-in local project context with evidence retrieval and stale-state guards.
- Preview-confirmed compound edit plans with exact target revalidation.
- Production CEP compatibility plus capability-gated UXP expansion.
- Open-source client bundles, connectors, and release artifacts.
**How we do it differently:** The product exposes structured tools and workflow boundaries instead of asking an AI to guess at Premiere's interface. Project-context work captures bounded local evidence, creates a non-mutating plan, and requires exact preview confirmation before a compound edit can apply.
**Why that matters:** An editor can inspect available support, preview risk, and evaluate returned state or diagnostics before relying on an operation.
**Positioning boundary:** Say “designed for reviewable workflows,” not “production-proven” or “safe for every project,” until a published licensed-host test matrix supports the narrower claim.
## Objections
| Objection | Response |
| --- | --- |
| “Will it upload my footage?” | The recommended setup keeps Premiere, the bridge, server, and media on the local computer. The chosen AI client's own privacy behavior still applies. |
| “Will every tool work on my Premiere version?” | No static compatibility claim proves a live operation. Run the read-only connection check, inspect capabilities, preview changes, and verify results. |
| “Is setup too technical?” | Claude Desktop has a self-contained bundle; the Premiere connector remains a separate install. Other clients currently use guided or advanced setup. Reducing this friction is a product priority, not a completed claim. |
| “Why not use Adobe AI Assistant?” | It can be the right native choice for its supported beta workflows. Premiere Pro MCP is for teams that value client choice, local structured integration, and explicit workflow verification. |
**Anti-persona:** Anyone seeking unattended destructive editing, guaranteed support across every Premiere build, a hosted service that uploads and edits media without local Premiere, or “viral clip” automation as the only desired outcome.
## Switching Dynamics
**Push:** Repetitive edits, fragile UI macros, scattered scripts, and difficult-to-audit handoffs.
**Pull:** Structured tools, local execution, client choice, plan review, and explicit diagnostics.
**Habit:** Manual Premiere workflows are predictable and already understood.
**Anxiety:** Installation friction, project safety, compatibility variation, assistant privacy, and uncertainty about whether an operation really succeeded.
## Customer Language
**Repository-provided task examples, not customer-interview quotations:**
- “What is my current Premiere project and active sequence? Do not make changes.”
- “Add the B-roll clips to V2, apply a cross dissolve, match the grade, and export.”
**Words to use:** reviewable workflow automation, local-first, structured tools, preview, supported, capability-gated, verified result, read-only check, explicit diagnostics.
**Words to avoid:** autonomous editor, guaranteed, flawless, one-click for every client, unsubstantiated endorsement language, live demo when simulated, uploads nothing under every configuration, full control without qualification.
**Glossary:**
| Term | Meaning |
| --- | --- |
| MCP server | The local service exposing structured Premiere tools to compatible AI clients |
| CEP bridge | The production connector used for the default Premiere compatibility path |
| UXP bridge | A newer capability-gated connection for supported Premiere workflows |
| Reviewable workflow | A bounded Inspect → Plan → Preview → Confirm → Apply → Verify path; availability and success remain host-specific |
| Verified result | A returned outcome backed by observable state or diagnostics, not merely an attempted command |
## Brand Voice
**Tone:** Confident, technical, calm, and evidence-aware.
**Style:** Outcome-led plain language first; technical detail and limitations close to the claim they qualify.
**Personality:** Precise, transparent, pragmatic, capable, editor-respecting.
## Proof Points
**Release facts:** v1.14.9 registers 349 core tools; the default profile exposes 347; an authenticated compatible UXP host can add 93 capability-gated tools for a 440-tool connected surface. The release also declares 43 modules, 4 MCP resources, and 16 workflow prompts. It adds a separately installed After Effects CEP connector and guarded MOGRT studio: five bounded recipes, optional brand-kit constraints, JSON/CSV batch previews, immutable local-library publishing, source inspection, queue-only renders, and explicit Premiere verification handoff. The feature requires a user-opened, saved After Effects project and does not claim visual, import, playback, or completed-render verification. These are catalog, packaging, and HTTP authorization facts from the repository, not a promise that a particular host operation will work.
**Compatibility boundary:** The release targets Premiere Pro 2020–2026; UXP workflows require a compatible Premiere Pro 25.6.0+ host and advertised capabilities. CEP remains the default compatibility route. A compatibility range, package validation, CI pass, HTTP health check, or local build is not real-host proof.
**Customers and testimonials:** No approved customer-logo claims, adoption claims, case studies, or public testimonials are currently documented.
**Activation and revenue:** The landing records only bounded anonymous setup actions and allowlisted UTM fields; the local runtime can separately record aggregate first-run check outcomes when an operator configures telemetry. These streams deliberately have no shared user identifier. Current production activation, retention, support, conversion, and revenue metrics have not been queried and must not be reported as known.
**Marketplace and deployment:** Do not claim current Adobe Marketplace approval, directory approval, signed public distribution, or live deployment from repository artifacts alone. Marketplace submission and publication, trusted signing, and real-host installation are separate external gates.
**Value themes:**
| Theme | Evidence-bound proof |
| --- | --- |
| Installable artifacts | npm package, Claude Desktop bundle, CEP and UXP packaging, and release artifacts; real-host install proof remains separate |
| Local-first | Recommended same-computer server, bridge, Premiere, and media architecture; the selected AI client's privacy behavior remains separate |
| Inspectable | Capability catalog, read-only first check, diagnostics, and explicit verification boundaries |
| Open | MIT license, public source, changelog, security policy, and cross-platform CI; CI does not prove a real Premiere edit |
## Goals
**Business goal:** Establish a repeatable path from install to verified workflow completion before offering a commercial companion broadly.
**Phase-0 conversion action:** Complete the assistant and connector installation, run `verify_premiere_connection`, then complete a supported workflow with a host-observable result. This is the intended activation event, not a reported conversion metric.
**Proof goals:** Maintain a canonical claims registry; test external clean installs; publish a versioned host-test matrix; and collect approved user evidence before using testimonial, adoption, or time-saved claims.
**Commercial validation goal:** Interview target workflow owners and validate a limited design-partner offer before publishing a price, checkout, or revenue target.
**Organic acquisition strategy:** Publish practical, intent-specific guides that lead to the read-only connection check and clearly distinguish package support, connected capabilities, and host-verified outcomes.
**Paid-acquisition gate:** Do not activate paid campaigns until the current release download, privacy policy, browser conversion events, and aggregate first-run reliability evidence have been verified. A campaign budget, platform, and activation remain separate owner decisions.
## Changelog
*Newest first. One line per revision: what changed and why.*
- v11 (2026-09-04) — Aligned the public release facts with v1.14.9's guarded MOGRT studio, assistant workflows, and explicit host-proof boundaries.
- v10 (2026-09-04) — Prepared v1.14.8 guarded After Effects MOGRT-authoring positioning; preserved the licensed-host, visual, and import-verification boundaries.
- v9 (2026-08-23) — Refreshed the Adobe AI Assistant public-beta scope and added project-backup, visual-review, and delivery-QC guide intents with explicit evidence boundaries.
- v8 (2026-08-22) — Prepared v1.13.0 release-candidate positioning for preview-only Project Intake while preserving the unpublished and licensed-host evidence boundaries.
- v7 (2026-08-22) — Added the read-only Project Intake workflow and refreshed source-derived tool, module, and workflow counts; kept release publication and licensed-host proof separate.
- v6 (2026-08-22) — Added privacy-bounded acquisition attribution and the paid-acquisition measurement gate after production-readiness hardening.
- v5 (2026-08-22) — Repositioned around reviewable workflow automation; refreshed v1.12.1 release facts, ICP, Adobe AI Assistant overlap, commercial hypotheses, and explicit proof boundaries.
- v6 (2026-08-22) — Released v1.12.2 with string-backed MOGRT property inputs and clearer legacy-QE effect-catalog diagnostics; real Premiere host validation remains separate.
- v4 (2026-08-20) — Added the project-context review workflow and client-choice differentiation after Adobe AI Assistant comparison.
- v3 (2026-08-19) — Updated proof counts for v1.11.4 and added the organic article strategy and activation path.
- v2 (2026-08-15) — Expanded audience, differentiation, objections, brand voice, proof, and activation goals; aligned the current 280-core and 307-connected tool surfaces.
- v1 (2026-07-27) — Initial context derived from the product README, package requirements, compatibility guidance, and usage-measurement work.
+1
View File
@@ -0,0 +1 @@
selected_org: tradewink
+47
View File
@@ -0,0 +1,47 @@
steps:
- group: ":hammer: Build & Test"
steps:
- label: ":typescript: Type Check"
command: |
npm ci
npx tsc --noEmit
plugins:
- docker#v5.11.0:
image: "node:20-alpine"
- label: ":vitest: Tests"
command: |
npm ci
npm run build
npm test
plugins:
- docker#v5.11.0:
image: "node:20-alpine"
- label: ":nextjs: Landing Page Build"
command: |
cd landing
npm ci
npm run build
plugins:
- docker#v5.11.0:
image: "node:20-alpine"
- wait
- block: ":fly: Deploy to Fly.io"
branches: "main"
- label: ":rocket: Deploy"
command: |
apk add --no-cache curl
curl -L https://fly.io/install.sh | sh
export FLYCTL_INSTALL="/root/.fly"
export PATH="$FLYCTL_INSTALL/bin:$PATH"
fly deploy --remote-only
branches: "main"
plugins:
- docker#v5.11.0:
image: "node:20-alpine"
environment:
- FLY_API_TOKEN
+20
View File
@@ -0,0 +1,20 @@
{
"$schema": "https://json.schemastore.org/claude-code-marketplace.json",
"name": "premiere-pro-mcp",
"owner": {
"name": "Premiere Pro MCP contributors"
},
"metadata": {
"description": "Claude Code integrations for Adobe Premiere Pro."
},
"plugins": [
{
"name": "premiere-pro",
"source": "./claude-plugins/premiere-pro",
"description": "Inspect, edit, verify, and export local Premiere Pro projects through MCP.",
"version": "1.14.9",
"category": "creative",
"tags": ["premiere-pro", "video-editing", "mcp"]
}
]
}
+9
View File
@@ -0,0 +1,9 @@
.git
.github
node_modules
dist
coverage
landing/node_modules
landing/.next
landing/out
*.log
+42
View File
@@ -0,0 +1,42 @@
---
name: Bug Report
about: Report a bug or unexpected behavior
title: "[Bug] "
labels: bug
assignees: ""
---
## Description
A clear and concise description of the bug.
## Steps to Reproduce
1. Tool called: `tool_name`
2. Parameters used: `{ ... }`
3. What happened:
4. What you expected:
## Environment
- **OS:** macOS / Windows
- **Premiere Pro version:**
- **Node.js version:** (`node --version`)
- **MCP server version:**
- **MCP client:** Claude Desktop / Windsurf / Cursor / Other
## CEP Panel Status
- Is the CEP panel open and showing "Running"? Yes / No
- Temp directory path (if known):
- Any errors in the CEP panel console?
## Error Output
```
Paste any error messages, stack traces, or MCP client logs here
```
## Additional Context
Any other context, screenshots, or `.json` response files from the temp directory.
@@ -0,0 +1,42 @@
name: Compatibility report
description: Share a successful or failed sanitized host/client compatibility result.
title: "[Compatibility]: "
labels: ["documentation"]
body:
- type: input
id: platform
attributes:
label: OS and architecture
placeholder: Windows 11 x64 or macOS 15 Apple Silicon
validations:
required: true
- type: input
id: versions
attributes:
label: Premiere Pro and MCP versions
placeholder: Premiere 26.3.0, Premiere Pro MCP 1.12.2
validations:
required: true
- type: input
id: client
attributes:
label: MCP client and version
validations:
required: true
- type: dropdown
id: connection
attributes:
label: Connection verification
options:
- Passed read-only verification and ping
- Installed but not live-verified
- Failed connection verification
validations:
required: true
- type: textarea
id: result
attributes:
label: Sanitized result
description: Describe what was verified and what remains unverified. Do not include local paths or project content.
validations:
required: true
+11
View File
@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Setup documentation
url: https://premiere-pro-mcp.com/docs/
about: Follow the supported installation, compatibility, and safe connection-check guidance.
- name: Security vulnerability
url: https://github.com/leancoderkavy/premiere-pro-mcp/security/policy
about: Report vulnerabilities privately through the security policy instead of a public issue.
- name: Questions and workflow ideas
url: https://github.com/leancoderkavy/premiere-pro-mcp/discussions
about: Ask usage questions or share a Premiere workflow with the community.
+77
View File
@@ -0,0 +1,77 @@
name: Connection or setup problem
description: Report a sanitized installation, connector, or live-connection failure.
title: "[Connection]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Do not attach footage, project files, private prompts, tokens, or full local paths. Run `premiere-pro-mcp --doctor` and the read-only `verify_premiere_connection` prompt first.
- type: dropdown
id: operating-system
attributes:
label: Operating system
options:
- Windows
- macOS Apple Silicon
- macOS Intel
- Other or unsupported
validations:
required: true
- type: input
id: premiere-version
attributes:
label: Premiere Pro version
placeholder: 26.3.0
validations:
required: true
- type: dropdown
id: client
attributes:
label: AI client
options:
- Claude Desktop
- Claude Code
- Codex
- Cursor
- VS Code / Copilot
- Windsurf
- Another MCP client
validations:
required: true
- type: dropdown
id: route
attributes:
label: Installation route
options:
- Claude Desktop bundle plus CEP connector
- npm package plus CEP connector
- Source build plus CEP connector
- UXP preview bridge
- Remote HTTP transport
- Not sure
validations:
required: true
- type: textarea
id: state
attributes:
label: Sanitized connection state
description: Share component status and error codes, but remove usernames, paths, media, project names, prompts, and tokens.
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
placeholder: Describe the shortest clean sequence that reproduces the failure.
validations:
required: true
- type: checkboxes
id: checks
attributes:
label: Safety checks
options:
- label: I removed footage, project files, private prompts, tokens, and full local paths.
required: true
- label: I restarted Premiere and my AI client, opened a project, and ran the read-only connection check.
required: true
+37
View File
@@ -0,0 +1,37 @@
---
name: Feature Request
about: Suggest a new tool or capability
title: "[Feature] "
labels: enhancement
assignees: ""
---
## Description
What tool or capability would you like to see added?
## Use Case
How would you use this? What AI-driven workflow or editing task does it enable?
## Proposed Tool Name & Module
- **Tool name:** `suggested_tool_name`
- **Module:** e.g., `timeline.ts`, `effects.ts`, or a new module
## API Reference
If you know the ExtendScript or QE DOM method, include it here:
```javascript
// e.g., app.project.someMethod()
// or qe.sequence.someMethod()
```
## Alternatives
Have you found a workaround using `execute_extendscript` or existing tools? If so, paste your script.
## Priority
How critical is this to your workflow? (Nice to have / Important / Blocking)
+37
View File
@@ -0,0 +1,37 @@
name: Feature request
description: Propose a Premiere workflow or product improvement with a clear verification boundary.
title: "[Feature]: "
labels: ["enhancement"]
body:
- type: textarea
id: problem
attributes:
label: Workflow problem
description: What repetitive editing or delivery problem should this solve?
validations:
required: true
- type: textarea
id: outcome
attributes:
label: Desired observable outcome
description: Describe what the user should be able to verify after the workflow runs.
validations:
required: true
- type: dropdown
id: authority
attributes:
label: Expected authority
options:
- Inspect only
- Edit
- Export
- Filesystem
- Unsafe script
- Not sure
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Current workaround
description: How is this handled manually or with another supported tool today?
+62
View File
@@ -0,0 +1,62 @@
name: Tool failure
description: Report a supported MCP tool that returned an error or an unverified result.
title: "[Tool]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: Do not include project media, prompt contents, tool arguments containing paths, access tokens, or proprietary project details.
- type: input
id: tool
attributes:
label: Tool name
placeholder: move_clip
validations:
required: true
- type: input
id: package-version
attributes:
label: Premiere Pro MCP version
placeholder: 1.12.2
validations:
required: true
- type: input
id: premiere-version
attributes:
label: Premiere Pro version
placeholder: 26.3.0
validations:
required: true
- type: dropdown
id: backend
attributes:
label: Reported backend
options:
- CEP / ExtendScript
- QE DOM
- UXP
- Local
- Orchestrator
- Not sure
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected observable result
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual sanitized result or diagnostics
description: Include bounded error codes and verification state only.
validations:
required: true
- type: checkboxes
id: privacy
attributes:
label: Privacy check
options:
- label: I removed prompts, arguments, paths, project/media names, tokens, and proprietary content.
required: true
+40
View File
@@ -0,0 +1,40 @@
# GitHub Copilot repository instructions
## Project and architecture
- This repository is the TypeScript MCP server for Adobe Premiere Pro. The production path is a local Node.js server communicating with the CEP bridge through private file-based IPC. The UXP bridge is a capability-aware preview for supported Premiere 25.6+ APIs.
- `src/server.ts` assembles the MCP surface. Tool modules live in `src/tools/`, bridge code in `src/bridge/`, the production CEP extension in `cep-plugin/`, and the preview backend in `uxp-plugin/`.
- Treat `README.md`, `SECURITY.md`, `CONTRIBUTING.md`, and `RESEARCH.md` as the canonical product, trust-model, contribution, and compatibility references.
## Development workflow
- Use Node.js 24 for repository work; the supported runtime floor is Node.js 20.19.
- Install deterministically with `npm ci`.
- Before requesting review, run `npm run check`. For changes that affect coverage-sensitive behavior, also run `npm run test:coverage`.
- Keep generated build output, credentials, certificates, Premiere project/media files, and local diagnostics out of commits.
- Make focused changes. Do not rewrite unrelated files or update dependency lockfiles unless the task requires it.
## Implementation rules
- Generated ExtendScript must remain ECMAScript 3 compatible: use `var`, traditional functions and loops, and no arrow functions, `let`, `const`, template literals, or other modern syntax.
- Escape every user-controlled string embedded in ExtendScript with the existing escaping helpers. Never interpolate raw paths, names, expressions, or prompts into generated scripts.
- Preserve capability and authority boundaries. Raw scripting tools stay disabled unless the explicit `unsafe-script` capability is enabled.
- Prefer documented Premiere APIs. QE DOM behavior is experimental and must be described as such.
- Mutating tools must verify their postconditions. Do not report success from a host API return value alone, and do not silently retry a failed UXP mutation through CEP or QE.
- Keep tool schemas, descriptions, registrations, structured results, tests, documentation, and reported counts synchronized.
- Reuse existing helpers and module patterns before introducing new abstractions or dependencies.
## Testing and review expectations
- Add or update tests for behavior changes, failure paths, escaping, validation, authority enforcement, and tool registration.
- Automated tests and CI prove package behavior only. Claims about Premiere-side compatibility require a real supported Premiere host with the applicable CEP or UXP bridge running.
- Clearly distinguish `committed`, `verified`, `committed_unverified`, and failed host mutations in user-visible results and documentation.
- Do not weaken authentication, private temp-directory ownership checks, script-size limits, telemetry privacy, or secret handling.
- Telemetry must remain bounded to operational metadata. Never collect prompts, arguments, results, tokens, IP addresses, project paths, media names, or person profiles.
## Pull requests
- Explain the user impact and the compatibility boundary.
- Link the related issue when one exists.
- Report the exact checks run and whether live Premiere verification was performed.
- Never claim a release, registry publication, deployment, or host-side validation unless it was directly verified.
+16
View File
@@ -0,0 +1,16 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
groups:
npm-minor-and-patch:
update-types:
- minor
- patch
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
+35
View File
@@ -0,0 +1,35 @@
## What does this PR do?
Brief description of the changes and motivation.
## Related Issue
Closes #
## Type of Change
- [ ] New tool(s)
- [ ] Bug fix
- [ ] Documentation
- [ ] Refactor
- [ ] Other
## New Tools Added (if any)
| Tool Name | Module | Description |
|-----------|--------|-------------|
| | | |
**Total tool count after this PR:** (update `release-metadata.json` when changed)
## Checklist
- [ ] `npm run build` compiles without errors
- [ ] Tool descriptions are clear and useful for an LLM
- [ ] All parameters have descriptions
- [ ] ExtendScript uses ES3 syntax (`var`, no arrow functions, no `let`/`const`)
- [ ] User-provided strings are escaped with `escapeForExtendScript()`
- [ ] New module is exported from `getXTools()` and registered in `server.ts`
- [ ] No duplicate tool names introduced
- [ ] Tested with Premiere Pro (if possible)
- [ ] `RESEARCH.md` updated (if adding new tools)
+36
View File
@@ -0,0 +1,36 @@
name: Attach Premiere connector
on:
release:
types: [published]
permissions:
contents: write
jobs:
attach-connector:
runs-on: windows-latest
env:
GITHUB_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.event.release.tag_name }}
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run check
- name: Verify release identity
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: node scripts/verify-release-tag.mjs
- name: Build and verify signed connector
shell: pwsh
run: ./scripts/build-signed-cep.ps1
- name: Attach connector to release
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "${{ github.event.release.tag_name }}" "artifacts/MCPBridgeCEP.zxp" --clobber
+45
View File
@@ -0,0 +1,45 @@
name: Build Claude Desktop MCPB
on:
workflow_dispatch:
release:
types: [published]
permissions:
contents: write
jobs:
bundle:
name: Build standards-current MCPB
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.event_name == 'release' && github.event.release.tag_name || github.sha }}
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
package-manager-cache: false
- run: npm ci
- run: npm run check
- name: Verify release identity
if: ${{ github.event_name == 'release' }}
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: node scripts/verify-release-tag.mjs
- run: npm run build:claude
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: premiere-pro-mcp-claude-desktop
path: |
artifacts/*.mcpb
if-no-files-found: error
- name: Attach bundles to release
if: ${{ github.event_name == 'release' }}
env:
GH_TOKEN: ${{ github.token }}
run: >-
gh release upload "${{ github.event.release.tag_name }}"
artifacts/*.mcpb --clobber
+95
View File
@@ -0,0 +1,95 @@
name: Connector installers
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
inputs:
require_production_signing:
description: Fail unless platform signing identities are configured
required: true
default: true
type: boolean
permissions:
contents: read
jobs:
connector-package:
runs-on: windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Build and verify CEP connector
shell: pwsh
run: ./scripts/build-signed-cep.ps1
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: connector-package
path: artifacts/MCPBridgeCEP.zxp
if-no-files-found: error
windows-installer:
needs: connector-package
runs-on: windows-latest
env:
WINDOWS_SIGNING_PFX_BASE64: ${{ secrets.WINDOWS_SIGNING_PFX_BASE64 }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: 8.0.x
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: connector-package
path: artifacts
- name: Restore Windows signing certificate
if: ${{ env.WINDOWS_SIGNING_PFX_BASE64 != '' }}
shell: pwsh
run: '[IO.File]::WriteAllBytes("$env:RUNNER_TEMP\windows-signing.pfx", [Convert]::FromBase64String($env:WINDOWS_SIGNING_PFX_BASE64))'
- name: Build Windows installer
shell: pwsh
env:
SIGNING_PASSWORD: ${{ secrets.WINDOWS_SIGNING_PFX_PASSWORD }}
run: |
$params = @{}
if (Test-Path "$env:RUNNER_TEMP\windows-signing.pfx") {
$params.SigningCertificatePath = "$env:RUNNER_TEMP\windows-signing.pfx"
$params.SigningCertificatePassword = $env:SIGNING_PASSWORD
}
if ('${{ inputs.require_production_signing }}' -eq 'true') { $params.RequireSigning = $true }
./scripts/build-connector-installer.ps1 @params
- name: Verify embedded connector package without installing it
shell: pwsh
run: |
$installer = Get-ChildItem "artifacts/connector-installers/*.exe" | Select-Object -First 1
if (-not $installer) { throw "Windows connector installer artifact was not produced." }
$result = Start-Process -FilePath $installer.FullName -ArgumentList "--verify-only" -Wait -PassThru -NoNewWindow
if ($result.ExitCode -ne 0) { throw "Windows connector installer verification failed." }
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: connector-installer-windows
path: artifacts/connector-installers/*.exe
if-no-files-found: error
macos-installer:
needs: connector-package
runs-on: macos-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: connector-package
path: artifacts
- name: Build macOS installer preview
shell: bash
env:
REQUIRE_SIGNING: ${{ inputs.require_production_signing || 'false' }}
run: ./scripts/build-connector-installer.sh
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: connector-installer-macos
path: |
artifacts/connector-installers/*.pkg
artifacts/connector-installers/*.command
if-no-files-found: error
+33
View File
@@ -0,0 +1,33 @@
name: Copilot Setup Steps
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
jobs:
copilot-setup-steps:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
cache: npm
package-manager-cache: false
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
+34
View File
@@ -0,0 +1,34 @@
name: Cross-platform validation
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
build-and-test:
name: ${{ matrix.os }} / Node ${{ matrix.node }}
runs-on: ${{ matrix.os }}
env:
GITHUB_TOKEN: ${{ github.token }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest]
node: [20, 22, 24]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm run check
- name: Enforce unit test coverage baseline
if: matrix.os == 'windows-latest' && matrix.node == 22
run: npm run test:coverage
- name: Verify packaged files
run: npm run pack:check
+69
View File
@@ -0,0 +1,69 @@
name: Publish npm
on:
workflow_dispatch:
inputs:
tag:
description: npm dist-tag to publish
required: true
default: latest
skip_tests:
description: Skip tests after build
required: true
default: "false"
type: choice
options:
- "false"
- "true"
permissions:
contents: read
id-token: write
jobs:
build-signed-cep:
name: Build signed CEP package
runs-on: windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Build and verify signed ZXP
shell: pwsh
run: ./scripts/build-signed-cep.ps1
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: signed-cep
path: artifacts/MCPBridgeCEP.zxp
if-no-files-found: error
publish:
name: Publish package
needs: build-signed-cep
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: signed-cep
path: artifacts
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
registry-url: https://registry.npmjs.org/
package-manager-cache: false
- run: npm install --global npm@latest
- run: npm ci
- run: npm run premiere:docs-inventory:check
- run: npm run build
- if: ${{ inputs.skip_tests != 'true' }}
run: npm test
- run: npm run pack:check
- name: Fail if version is already published
shell: bash
run: |
VERSION="$(node -p "require('./package.json').version")"
if npm view "premiere-pro-mcp@${VERSION}" version >/dev/null 2>&1; then
echo "premiere-pro-mcp@${VERSION} is already published."
exit 1
fi
- name: Publish to npm
run: npm publish --provenance --access public --tag "${{ inputs.tag }}"
+60
View File
@@ -0,0 +1,60 @@
name: Build Premiere UXP CCX
on:
workflow_dispatch:
inputs:
distribution_channel:
description: Distribution channel for the generated CCX
required: true
default: direct
type: choice
options:
- direct
- marketplace
marketplace_plugin_id:
description: Adobe Developer Distribution plugin ID (required only for marketplace)
required: false
type: string
release:
types: [published]
permissions:
contents: write
jobs:
package:
name: Validate and package UXP CCX
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ github.token }}
UXP_DISTRIBUTION_CHANNEL: ${{ inputs.distribution_channel || 'direct' }}
UXP_MARKETPLACE_PLUGIN_ID: ${{ inputs.marketplace_plugin_id }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.event_name == 'release' && github.event.release.tag_name || github.sha }}
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
package-manager-cache: false
- run: npm ci
- run: npm run check
- name: Verify release identity
if: ${{ github.event_name == 'release' }}
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: node scripts/verify-release-tag.mjs
- run: node scripts/validate-distribution.mjs --uxp
- run: node scripts/build-uxp-ccx.mjs
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: premiere-pro-mcp-uxp-${{ inputs.distribution_channel || 'direct' }}
path: artifacts/*.ccx
if-no-files-found: error
- name: Attach direct CCX to release
if: ${{ github.event_name == 'release' }}
env:
GH_TOKEN: ${{ github.token }}
run: >-
gh release upload "${{ github.event.release.tag_name }}"
artifacts/*-direct.ccx --clobber
+38
View File
@@ -0,0 +1,38 @@
# Dependencies
node_modules/
# Build output
dist/
build/
coverage/
.coverage-*/
artifacts/
installer/**/bin/
installer/**/obj/
.rnd
# Source maps
*.js.map
*.d.ts.map
# OS files
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Environment
.env
.env.local
# Logs
*.log
npm-debug.log*
# Temp bridge files (runtime)
*.jsx.response.json
+26
View File
@@ -0,0 +1,26 @@
# Source (dist is published, not src)
src/
tsconfig.json
# Development
.github/
.vscode/
.idea/
.windsurf/
# Documentation (README, LICENSE, CHANGELOG included via files field)
RESEARCH.md
CONTRIBUTING.md
# OS
.DS_Store
Thumbs.db
# Source maps
*.js.map
*.d.ts.map
# Misc
.env
.env.local
*.log
+894
View File
@@ -0,0 +1,894 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
## [1.14.9] - 2026-09-04
### Added
- Expanded the separate After Effects CEP bridge into a guarded MOGRT studio.
Five deterministic title, callout, quote, and social recipes run only in an
already saved, workspace-contained After Effects project and export to an
existing approved directory.
- Added optional brand-kit constraints, bounded JSON/CSV batch previews,
immutable workspace-contained version libraries, source inspection,
queue-only renders, and an explicit empty-track Premiere handoff that
verifies insertion and exposed-control descriptors.
- Added capability-aware assistant-editor workflows and GPT-6 Astra discovery
guidance so clients can inspect the current, authorized tool surface before
proposing an editing workflow.
### Changed
- Hardened the MCP transport's bounded bridge-command backlog and refreshed
public tool counts, workflow documentation, registry metadata, and the
landing's machine-readable release references.
### Safety
- MOGRT workflows never accept arbitrary script text, create or switch After
Effects projects, overwrite artifacts, start a render queue, or treat host
acceptance, a ZIP header, or an import descriptor as rendered-frame or
visual proof.
- Capability discovery and workflow guidance describe the current host surface;
they do not grant authority or establish licensed-host, playback, render, or
marketplace verification.
## [1.14.8] - 2026-09-04
### Added
- Added a separate After Effects CEP bridge and four approval-gated MOGRT
authoring tools. The initial `lower_third` recipe only runs in an already
saved, workspace-contained AE project and exports to an existing approved
directory.
- Added one-time preview tokens, explicit export confirmation, isolated AE
bridge helpers/temp directory, and local ZIP-header artifact verification.
- Added a local SRT/VTT timing-review plan for lecture and interview captions,
including bounded correction previews and a separate structural/playback/
rendered-output verification checklist.
- Added revision-bound, opt-in editorial evidence import for caller-supplied
transcript, shot, audio, note, and opaque frame-reference data; it remains
local and rejects stale source or timeline revisions.
- Added no-write `premiere-pro-mcp --doctor --plan-fixes` repair guidance and a
narrowly scoped, confirmation-gated local connector recovery path.
- Added a generated public workflow manifest, workflow-proof receipt/runbook,
and a universal client setup guide with explicit distribution boundaries.
### Changed
- Added an in-panel global npm/CEP connector update handoff for Windows. It
requires confirmation, waits for Premiere to close without forcing it, and
uses the published-package update path.
- Reused immutable MCP registration descriptors and JSON Schema adapters across
stateless server construction, while retaining per-request context, telemetry,
and UXP state. Concurrent CEP commands now share a response-directory watcher
with polling retained as the correctness fallback.
- Refined the public landing for mobile and reduced motion, removed the deferred
3D dependency path, and refreshed its facts, structured data, sitemap, public
crawl policy, and machine-readable reference files.
### Safety
- MOGRT authoring never accepts arbitrary script text, creates or switches AE
projects, creates output directories, overwrites artifacts, or treats host
acceptance/a ZIP header as import, rendered-frame, or visual proof.
- Caption timing plans, editorial evidence import, doctor repair plans, and
public workflow materials remain distinct from licensed-host, playback,
rendered-output, provider, or marketplace verification.
## [1.14.7] - 2026-09-02
### Added
- Added bounded UXP source-proxy readiness inspection, with explicit opt-in
disclosure for an attached proxy path, and read-only animated PointF
endpoint-displacement inspection.
### Fixed
- Added an explicit `PREMIERE_MCP_PROTOCOL_MODE=legacy` fallback for desktop
clients whose stdio protocol negotiation cannot use the modern server mode;
the default remains the current automatic mode and invalid values fail fast.
- Updated the affected `@humanfs/node`, `fast-uri`, and `qs` dependency paths.
## [1.14.6] - 2026-09-02
### Added
- Added `create_editorial_context_pack`, a review-only, revision-aware Markdown
reading view for explicitly captured transcript, shot, audio, source,
timeline, and editor-note context. It is bounded by entry and character
limits and never invokes a provider, Premiere bridge, or project mutation.
- Added guarded UXP workflows for sequence playhead and range updates, marker
batch removal, native transition application, caption-track inventory,
silence-cut stringouts, atomic split edits, and beat-grid markers.
- Added local-only delivery conformance, sampled video scopes and motion
analysis, Warp Stabilizer status inspection, and shot-match planning.
- Added source-backed inventories for documented UXP, CEP, ExtendScript, and
native SDK integration surfaces.
### Fixed
- Made unsupported sequence pixel-aspect ratios, partial transitions,
unavailable media timing readback, and incomplete delivery probes fail
closed instead of reporting unverified success.
- Corrected marker, encoder, duplicate-media, caption, and capability
inference contracts, with expanded mutation verification coverage.
## [1.14.5] - 2026-08-31
### Added
- Added safe user update commands for global npm installations and guarded
source check/update scripts. Global updates refresh the CEP connector after
npm succeeds; source updates require a clean fast-forwardable checkout.
### Fixed
- Corrected macOS bridge-directory handling when `TMPDIR` is set and made QE
transition writes target the intended clip on current Premiere builds.
## [1.14.4] - 2026-08-29
### Fixed
- Corrected QE razor operations to pass sequence timecode rather than ticks and
added regression coverage for both split and all-track cuts.
- Made batch effect application preflight every target, match QE clips without
assuming gap-free indexes, and require post-application component readback.
- Replaced false playback-success claims with explicit request-only results and
polling guidance when the legacy API cannot provide same-call verification.
- Added direct QE by-name effect probes when Premiere exposes an empty effect
catalog, while labelling bounded fallback lists as partial.
- Made an empty or unavailable QE audio-transition catalog fail closed instead
of appearing as a usable transition list.
## [1.14.3] - 2026-08-29
### Added
- Added an optional, fail-closed OAuth resource-server mode with RFC 9728
protected-resource metadata, remote JWKS verification, exact issuer and
audience validation, required scopes, and an explicit trusted-subject
allowlist for operator-managed HTTP deployments.
### Security
- Added an IP-keyed admission gate before JWT verification and isolated
authenticated rate-limit identities behind random process-local keys.
- Made partial or mixed OAuth/shared-token configuration fail startup, kept the
shared token as an operator-only compatibility mode, and removed internal
admission counters from the public health response.
- Kept public desktop routing deliberately disabled: OAuth does not claim
user-to-device pairing or access to a user's local Premiere process.
## [1.14.2] - 2026-08-28
### Added
- Added dual-era MCP serving with the stable TypeScript SDK v2: modern
`2026-07-28` discovery and stateless request handling over HTTP and stdio,
with legacy protocol compatibility through `2025-11-25`.
- Added validated modern routing headers, cache hints, subscription-listen
support, a formal Premiere extension capability, and a machine-readable MCP
protocol report in `get_capabilities`.
- Added an evidence-backed capability matrix covering implemented, SDK-ready,
external-boundary, deprecated, and intentionally unsupported MCP surfaces.
### Changed
- Migrated tool, resource, prompt, client, stdio, and Node HTTP integrations
from `@modelcontextprotocol/sdk` v1 to the split v2 packages and Standard
Schema registration APIs.
### Fixed
- Restored strict JSON Schema 2020-12 tool compatibility and corrected legacy
CEP argument contracts, Premiere Time units, Adobe Media Encoder output
paths, active-sequence verification, metadata readback, XMP patch merging,
and single-extension UXP frame exports.
- Replaced false-success responses for structural edits, duplicate
consolidation, effect copying, nesting, deletion, and other host mutations
with verified outcomes or explicit fail-closed errors.
- Added bounded UXP selection lift and native transition adapters while keeping
unavailable track-management and global-redo capabilities explicit.
### Safety
- Live Premiere resources remain private and uncached, and tool discovery is
private-cache scoped. The tasks extension and OAuth discovery are not
advertised without the durable storage and authorization infrastructure they
require.
## [1.14.1] - 2026-08-27
### Fixed
- Made npm package verification isolate its temporary tarball and select the
package matching `package.json`, avoiding a current npm CLI packaging
regression before publication.
## [1.14.0] - 2026-08-27
### Added
- Added focused `essential`, `inspection`, `delivery`, and `captions` tool packs
so compatible MCP clients can begin with a smaller task-specific catalog.
- Added `inspect_sequence_review_report`, a read-only, handoff-oriented sequence
report, and explicit MCP output schemas for every registered tool.
### Safety
- Tool packs change discoverability, not authority. Review reports redact media
paths by default and include marker comments only with explicit opt-in.
- Package and response-contract checks remain distinct from licensed Premiere
host verification.
## [1.13.0] - 2026-08-22
### Added
- Added `preview_project_intake`, a bounded, inspect-only project intake tool
that evaluates Premiere project organization against a facility-supplied
template and returns redacted findings plus proposed actions without changing
the project.
- Added a deterministic intake rules engine, a guided workflow entry, a public
facts page, and design-partner/security pilot contracts for human-supervised
assistant-editor adoption.
### Safety
- File paths remain redacted unless explicitly requested, recursive capture and
outputs are bounded, and the intake workflow does not mutate or persist
project data. Automated tests passed, while licensed-host execution remains a
separate gate because Premiere 2026 hung before the CEP panel could open.
## [1.12.2] - 2026-08-22
### Fixed
- `set_effect_property` now accepts safely serialized string values as well as
numbers, unlocking MOGRT and graphic parameters that Premiere exposes as
JSON strings. Responses report parameter readback separately from render
verification.
- An empty legacy QE effect catalog now returns a clear no-mutation capability
response rather than incorrectly reporting a requested effect as missing.
When connected, the documented UXP effect catalog and transaction workflow is
the supported alternative.
## [1.12.1] - 2026-08-22
### Fixed
- Allowed Google Analytics collection requests to `www.google.com` in the
restrictive Content Security Policy, matching the current Google tag client.
## [1.12.0] - 2026-08-22
### Added
- Added local-first editorial planning for organization, stringout, rough-cut,
caption-review, and platform-cutdown workflows. Plans are non-mutating and
can be previewed against captured local project context.
- Added a guarded UXP organization apply route with stable source and parent
guards, structured bin/move/color readback requirements, partial-outcome
reporting, and a licensed-host validation runbook.
- Added a canonical product-claims registry and regression coverage for
release-backed claims and unsupported endorsement language.
### Fixed
- Editorial-plan preview and apply now accept only exact server-issued plans
with opaque confirmation tokens. Client-modified plans and duplicate source
guards are rejected before any UXP mutation.
- Unverified UXP attempts are no longer reported as applied or committed.
## [1.11.5] - 2026-08-19
### Fixed
- macOS Adobe Media Encoder preset discovery now scans application-bundle resources under
`Contents/MediaIO/systempresets`, and preset filtering normalizes names such as `H.264` and
`H264`.
- `add_to_timeline` now validates its arguments and verifies that a single requested item landed
on each affected target track, returning an error instead of a false success when Premiere
creates an unexpected residual fragment at an exact insert boundary.
- Removed calls to unsupported or incorrectly signed speed and raw-text caption APIs. Speed
requests and `add_text_overlay` now return actionable errors before mutating Premiere.
- `add_keyframe` now verifies stored parameter readback and explicitly labels render output as
unverified; `create_caption_track` likewise labels its result as structural rather than
render verification.
### Changed
- Published ten research-backed implementation recommendations covering MCP subscription streams,
contextual completions, workspace boundaries, resource annotations and canonical URIs, prompt and
resource-injection defenses, layered end-to-end health checks, experimental C2PA inspection,
UXP external-launch safeguards, and semantic keyframe verification.
## [1.11.4] - 2026-08-19
### Fixed
- The Claude Desktop MCPB now prompts for a sensitive Premiere UXP token and maps it to
`PREMIERE_UXP_TOKEN` in the bundled server process, allowing the authenticated loopback UXP
listener to start when Claude Desktop does not inherit login-shell environment variables.
## [1.11.3] - 2026-08-18
### Added
- Added a revision-locked `plan_transcript_rough_cut_uxp` workflow that maps native transcript
deletion ranges to verified 1x sequence placements, orders cut instructions from the end of the
timeline, and requires duplicate-sequence and post-mutation verification safeguards.
### Fixed
- Premiere Pro 26.3 can reject a manifest list of loopback WebSocket domains with `Manifest entry
not found`. The UXP package now uses Adobe's compatible network permission while the panel keeps
enforcing the exact loopback-only `/uxp` endpoint at runtime.
## [1.11.2] - 2026-08-18
### Added
- Added a durable local project-context engine with active-sequence capture,
transcript/shot/audio/note enrichment, bounded retrieval, and non-mutating
edit-plan scaffolds. Source-media and timeline revisions are tracked
independently so ordinary timeline changes do not repeat expensive source
analysis.
- Added a context-aware rough-cut prompt and `config://premiere-project-context`
resource documenting privacy, invalidation, retrieval, and preview requirements.
### Fixed
- `add_track` and QE-backed `add_tracks` now validate their inputs and return success
only after the active sequence reports the exact requested track-count increase. The
single-track call uses a bounded QE fallback only when the public DOM call made no
change, and never retries a partially applied call.
- `overwrite_clip` now rejects invalid video and audio track indices before invoking
Premiere and confirms the requested source item appears at the requested frame. A
no-op or an unverifiable repeat placement returns an error instead of false success.
- `trim_clip` now proves the requested source point also produced the expected visible timeline
edge and duration. It refuses retimed clips and, by default, trims that would strand effect
keyframes instead of treating source-metadata-only changes as success on Premiere Pro 26.x.
- `split_clip` now verifies that a clip spans the requested cut and that QE produced each expected
left/right segment, rather than accepting any increase in track clip count. QE keyframe
redistribution remains explicitly unverified.
- `remove_effect` and `remove_effect_by_name` now preflight `Component.remove()` support before
mutation. Unsupported Premiere 26.x components such as Essential Sound's Amplify return an
actionable capability error without crashing or partially removing matched effects.
### Security
- Native media paths are hashed before persistence, credential-like enrichment
metadata is discarded, stale source/timeline enrichments are rejected, and
context clearing remains an explicit filesystem-authorized action.
### Validation
- Added fail-closed CEP/QE contract coverage for trim, split, track creation, overwrite placement,
and component removal. Licensed Premiere Pro 26.x host confirmation remains a separate gate.
## [1.11.1] - 2026-08-16
### Fixed
- Extensionless landing routes such as `/changelog` now resolve to their
exported `index.html` file instead of attempting to stream a directory. The
previous behavior emitted an unhandled `EISDIR` error on Linux and restarted
the remote HTTP process.
- Static asset candidates are required to remain inside the landing directory
and resolve to regular files, and read-stream failures are handled without
terminating the server.
### Validation
- Added regression coverage for extensionless exported routes and asynchronous
static-file read failures. The complete release gates remain distinct from
validation inside a licensed Premiere host.
## [1.11.0] - 2026-08-16
### Fixed
- `set_clip_volume` passed decibels straight into Premiere's `Volume > Level`
property, which is a normalised 0..1 value where 1.0 is +15 dB, not a dB
value. Every negative dB clamped to 0 (silence) and every positive dB clamped
to 1.0 (+15 dB), and Premiere reports no error either way, so the failure was
silent - a whole timeline could be muted with the tool reporting success.
Levels are now converted with `10^((dB-15)/20)`.
### Added
- `get_clip_volume` reads a clip's level back in dB, so a level change can be
verified rather than assumed.
- `set_clips_volume` applies a level to every clip on an audio track (or a
chosen subset) in one call. Setting levels across an 80-clip sequence
previously meant 80 round trips.
- Added eight capability-gated third-wave UXP tools for bounded host events, AME
terminal receipts, host readiness, safe multi-project sessions, growing-media
leases, transactional checkpoints, media health, caption-aware track state,
source-clip trim and framing, and hybrid-acceleration evidence.
- Added a generated supported-actions catalog covering all 282 core tools, the
default profile, resources, prompts, and connected UXP actions with explicit
backend and verification boundaries.
- Added a schema-backed hybrid benchmark evidence template and a fail-closed
verifier so accelerated paths cannot be advertised without matching host,
dataset, correctness, latency, and provenance evidence.
### Changed
- Expanded the authenticated UXP surface from 40 to 48 capability-gated tools,
bringing the connected default profile from 318 to 328 tools while keeping
CEP as the production-compatible bridge.
- Bounded event and readiness history, reported eviction and pending states,
and preserved host timeout budgets with a response-delivery buffer.
- Required explicit confirmation and readback for external project writes,
destructive track or source mutations, and pause leases; failed UXP commands
are never replayed automatically through CEP.
### Validation
- The merged release tree passes 1,490 automated tests across 53 files with
91.26% branch coverage, generated-document checks, landing lint/build, and
package-content validation.
- Real Premiere host validation remains not run; mock and contract evidence does
not establish behavior inside a licensed Premiere installation.
## [1.10.0] - 2026-08-16
### Added
- Added 21 consolidated, capability-gated UXP tools across two stable workflow
groups, expanding the connected surface from 297 to 318 tools while retaining
CEP as the production-compatible bridge.
- Added native effects, selection batches, deterministic timeline selection,
scene detection, proxy and ingest control, offline relinking, transactional
metadata, color conformance, Source Monitor audition, Productions storage
preflight, and an operator-selected workspace broker.
- Added project-panel selection, marker CRUD, bin organization, sequence settings,
workspace-gated imports, typed parameter and keyframe automation, track-item
transforms, SequenceEditor operations, sequence lifecycle controls, and Adobe
Media Encoder submission.
### Changed
- Bounded selection, project, marker, sequence, bin, and keyframe inspection so a
request cannot accidentally traverse or serialize an unbounded production project.
- Grouped compatible mutations into Adobe action transactions with stale-state
guards, replay protection, and post-commit readback. A failed UXP mutation is
returned to the caller and is never silently retried through CEP.
- Replaced UXP filesystem full access with operator-selected folder access and kept
native paths and persistent tokens inside the panel.
### Security
- Updated vulnerable transitive dependencies and refreshed the validated package
lockfiles used by the server and landing build.
### Validation
- Automated unit, contract, distribution, and coverage gates exercise the expanded
UXP surface. Real Premiere host verification and latency benchmarking remain
pending and are not implied by this release.
## [1.9.3] - 2026-08-12
### Added
- Added the Premiere Pro MCP cinematic intro video to the landing assets.
- Added the dated security best-practices audit report for repository reference.
### Changed
- Simplified the README release overview to show only the latest release and link
to the complete GitHub release notes.
### Security
- Updated the landing build's transitive `nanoid` dependency to a patched version.
## [1.9.2] - 2026-08-04
### Fixed
- Changed the CEP Premiere host declaration to a minimum-only supported version
so Adobe Developer Distribution does not reject the signed ZXP for claiming
an unsupported future maximum.
- Updated transitive URL, HTTP middleware, and IP-address parsing dependencies
to patched versions after newly disclosed security advisories.
### Added
- Added a public privacy policy covering local media processing, optional MCP
operational telemetry, website analytics, retention, and user choices.
## [1.9.1] - 2026-08-02
### Security
- Added a production HTTP header baseline for the landing site, health route,
and remote MCP responses: CSP, HSTS, MIME sniffing protection, frame denial,
referrer and permissions policies, and cross-origin opener isolation.
- Restricted the browser connection policy to the application, configured
analytics endpoints, and the bounded PostHog host.
## [1.9.0] - 2026-08-02
### Added
- Added a read-only `verify_premiere_connection` tool, human-readable `--doctor`
diagnostics, and a privacy-sanitized `--support-bundle` for guided recovery.
- Added an accessible in-panel Connection Center and native Windows/macOS CEP
installer pipelines that require trusted platform signing for production use.
- Added deterministic direct and Marketplace-channel UXP CCX packaging with
explicit Adobe identity and live-host verification gates.
### Changed
- Reworked onboarding around the AI assistant an editor already uses, with the
Claude Desktop MCPB route first and npm/JSON configuration under Advanced.
- Upgraded the Claude Desktop bundle manifest to MCPB v0.4 and stopped emitting
an unsupported `.dxt` copy of the same bytes.
- Registered 280 core tools, exposed 278 under the default profile, and exposed
297 tools when the 19 capability-gated UXP tools are connected.
### Validation
- Automated checks cover distribution schemas, deterministic CCX packaging,
support-bundle privacy, installer path containment, production signing gates,
and connection evidence states. Real Premiere host verification and external
Adobe/Anthropic approvals remain separate release gates.
## [1.8.0] - 2026-08-01
### Added
- Added three read-only, capability-gated UXP transcript tools: native transcript
export, native transcript search, and revision-locked transcript edit previews.
- Added a deterministic SHA-256 transcript revision and confirmation token so a
proposed edit cannot be confused with a regenerated transcript.
### Changed
- Expanded the connected UXP surface from 16 to 19 tools while keeping automatic
transcript-to-timeline application unavailable pending real-host validation.
- Added repository Copilot instructions and a deterministic Node 24 setup workflow.
### Validation
- Automated tests cover transcript range validation, revision locking, capability
registration, and the MCP catalog. A real Premiere 25.6 or 26.3 host still must
validate transcript semantics before any apply operation is introduced.
## [1.7.0] - 2026-08-01
### Added
- Added six capability-gated Premiere 26.3+ UXP tools: `rename_track_uxp`,
`create_subclip_uxp`, `list_markers_uxp`, `set_source_monitor_position_uxp`,
`has_transcript_uxp`, and `export_aaf_uxp`.
- Added Adobe 26.3 coverage documentation and contract tests for the public MCP
schemas, protocol commands, and live-host verification gate.
### Changed
- Documented the stable 26.3 baseline separately from Adobe's 26.5 beta type
declarations. Beta-only APIs are not advertised as supported.
### Validation
- Automated contract tests validate catalog exposure, argument translation, host
capability probes, and result envelopes. A real Premiere 26.3+ host still must
validate each mutation and export before it can be called live-host verified.
## [1.6.0] - 2026-07-31
### Added
- Added a capability-aware UXP foundation for revisioned project inspection, verified saves,
preset-based sequence creation, OTIO/FCP XML interchange, transcript-language discovery,
Object Mask detection, and Adobe Media Encoder controls on compatible Premiere hosts.
- Added explicit UXP operation outcomes and bounded operation-ID replay protection so a client retry
does not repeat a completed command within the same panel session.
### Changed
- Documented the 10 UXP MCP tools that become available when an authenticated local panel is
connected, including their host-version and live-verification boundaries.
- Updated the MCP SDK and Node type dependencies and GitHub Actions artifact actions.
### Fixed
- `create_project` now rejects directory paths and verifies that Premiere switched to the exact
requested `.prproj` path before reporting success, preventing edits from continuing in a
previously open project after a failed creation attempt.
- Claude Desktop bundle packaging now invokes npm through the active Node executable so the
release build works on Windows where `npm` is exposed as a command shim.
## [1.5.0] - 2026-07-30
### Added
- Added `detect_silence` for finding dead air in local source media with FFmpeg, including
Docker support and clear local-install guidance.
- Added anonymous, opt-out PostHog usage telemetry with prompt flushing for low-volume servers.
- Added an immersive editorial landing-page experience, product demo video, changelog page, and
a 30-day launch plan.
### Changed
- Expanded the MCP surface to 279 tools and limited advertised tools to those allowed by the
active capability profile.
- Documented capability-filtered discovery, remote media-path constraints, and the difference
between the 279 registered tools and the 277 tools available to the default profile.
### Fixed
- Structural timeline tools now verify razor, ripple-delete, transition, and track-targeting
mutations instead of reporting success when Premiere applied only part or none of an edit.
- Server metadata now reports the package version rather than a stale hard-coded value.
- Resolved CodeQL findings in HTTP authentication and filesystem-path handling.
## [1.4.0] - 2026-07-26
### Added
- Added in-panel connector update discovery and trusted downloads from GitHub Releases.
- Added authenticated MCP-to-UXP WebSocket transport, transcript and caption inspection, event-driven
state reporting, operation semantics, and supported video-transition workflows.
- Added recovery diagnostics, export verification, AV inspection, capability reporting, and
collaboration/AI feature eligibility discovery.
- Added installable Codex, Claude Code, and Claude Desktop distributions.
### Changed
- Expanded the MCP surface to 278 tools and aligned documentation, plugin metadata, and distribution
manifests with the new release.
- Added automated signed CEP connector assets and Claude Desktop bundles to GitHub releases.
## [1.3.1] - 2026-07-25
### Fixed
- Fixed `set_sequence_frame_rate` to convert frames per second into Premiere's required
ticks-per-frame `Time` value and verify the applied setting instead of assigning a numeric frame
period that could corrupt the sequence timebase. ([#37](https://github.com/leancoderkavy/premiere-pro-mcp/issues/37))
## [1.3.0] - 2026-07-25
### Added
- Added a Windows release workflow that builds and verifies a signed CEP ZXP with Adobe's pinned
`ZXPSignCmd`, includes it in the npm package, and installs it ahead of the unsigned development
bundle.
- Added `--diagnose-cep` to verify installation metadata, debug-key types, and recent Premiere
signature failures.
### Changed
- Upgraded the toolchain to TypeScript 7, Vitest 4, Zod 4, `@types/node` 26, and
`@modelcontextprotocol/sdk` 1.29.
- Updated the landing app to Next.js 16.2.12 and patched production transitive dependencies.
- Raised the supported Node.js floor to 20.19 and expanded CI through Node.js 24.
### Fixed
- Added explicit Node types for TypeScript 7 and updated Zod 4 JSON-schema conversion.
- Fixed Windows installations that require a signed CEP extension instead of the debug-mode raw
folder used by development builds. ([#36](https://github.com/leancoderkavy/premiere-pro-mcp/issues/36))
## [1.2.3] - 2026-07-23
### Changed
- Improved npm and GitHub discovery metadata, added explicit TypeScript and public-registry package
configuration, and added automated dependency update configuration.
## [1.2.2] - 2026-07-23
### Fixed
- Corrected obsolete repository links in the npm README and republished package metadata so the
repository, homepage, and issue links point to the maintained project.
### Added
- Added `npm run publish:npm`, `npm run publish:npm:dry-run`, and a manual GitHub Actions npm
publish workflow that validates builds, tests, packed files, duplicate versions, and uses
token-free OIDC trusted publishing with automatic provenance.
## [1.2.1] - 2026-07-21
### Added
- Added `get_capabilities` for machine-readable Windows/macOS runtime, CEP/UXP backend,
authority-profile, and live-host verification reporting.
- Added GitHub Actions build, test, and package validation on Windows and macOS with Node 18 and 22.
### Fixed
- Audio-level writes now convert dB to Premiere's amplitude value and verify the applied value.
- Audio keyframes now use Premiere `Time` objects and verify each written value.
- Ripple delete, razor, and native transition tools now verify host state and return actionable
errors instead of false success on affected Premiere Pro 26.3 installations. ([#21](https://github.com/leancoderkavy/premiere-pro-mcp/issues/21))
- Capability profiles now enforce `inspect` and `edit` across the complete tool surface and treat
expression evaluation as unsafe scripting instead of allowing unclassified tools through.
- The npm CLI now copies the CEP plugin on macOS, verifies installation metadata, rejects unsupported
host operating systems, and avoids platform-specific `/tmp` configuration in cross-platform examples.
### Performance
- Prefer event-driven bridge response notification with a conservative polling fallback, reducing
idle filesystem checks while preserving compatibility with filesystems where watching is
unavailable or unreliable.
- Cache immutable tool catalogs and converted Zod schemas across stateless HTTP server instances.
A local 100-iteration benchmark reduced average repeated server construction from 5.87 ms to
2.21 ms (62.4%).
## [1.2.0] - 2026-07-20
### Added
- Added preview/apply edit plans with strict operation validation, SHA-256 confirmation binding,
operation IDs, and structured audit events.
- Added capability profiles. Raw ExtendScript tools now require explicit `unsafe-script` authority.
- Added structured MCP tool results, safety annotations, four guided workflow prompts, and the
`config://premiere-workflows` resource.
- Added a packaged Premiere 25.6+ UXP bridge preview with capability discovery, state-change
events, reconnecting WebSocket transport, and supported frame export with file verification.
### Validation
- TypeScript build passes, all 333 automated tests pass in a single-worker run, and the npm dry-run
package contains both CEP and UXP bundles. Live Premiere verification of the UXP host API and
loopback transport remains outstanding.
## [1.1.7] - 2026-07-20
### Changed
- Redesigned the Premiere Pro CEP bridge panel with clearer connection status, responsive
controls, improved directory configuration, and a larger live activity monitor.
- Added accessible labels, focus states, reduced-motion support, and consistent status details
without changing the bridge command workflow.
### Validation
- TypeScript build and 315 automated tests pass. The panel was also rendered at a 500 x 700 CEP
viewport and visually checked against the approved design concept.
## [1.1.6] - 2026-07-20
### Fixed
- **Frame capture's Media Encoder fallback now exports exactly one frame.** The fallback passed
tick values to sequence in/out methods that require seconds, producing an invalid export range
when the undocumented QE frame-export method wrote no file. The range and its saved state are
now converted to seconds. ([#9](https://github.com/leancoderkavy/premiere-pro-mcp/issues/9))
- **Windows CEP installation now enables unsigned-extension discovery correctly.** The CLI uses a
native PowerShell installer on Windows and creates `PlayerDebugMode` as the `REG_SZ` value Adobe
requires. Previous instructions incorrectly specified a DWORD, and the Bash installer never
enabled Windows debug mode. ([#14](https://github.com/leancoderkavy/premiere-pro-mcp/issues/14))
- CEP bundle and extension versions now match the npm package version, with regression coverage to
prevent future drift.
### Validation
- TypeScript build and 315 automated tests pass. The corrected Premiere runtime paths still require
live confirmation on a machine with Premiere Pro installed.
## [1.1.2] - 2026-07-11
The headline of this release is that the CEP 12 bridge fix from
[#1](https://github.com/leancoderkavy/premiere-pro-mcp/pull/1) finally ships to npm. It has been on
`main` since March but was never published, so everyone who installed with `npm install -g` still
got a bridge that returned `null` for every tool call. If that was your symptom, upgrading is the
whole fix.
### Fixed
- **The bridge returns data again on Premiere Pro 2023+ / CEP 12.** The published `CSInterface.js`
shim called `__adobe_cep__.evalScript(script)` without forwarding the callback. CEP 9+ is
async-only, so every result was silently discarded and every tool answered
`{"success":true,"data":null}` while the panel cheerfully logged "Result: OK". The manifest was
also missing `--enable-nodejs`, leaving `require("fs")` undefined in the panel.
([#2](https://github.com/leancoderkavy/premiere-pro-mcp/issues/2),
[#5](https://github.com/leancoderkavy/premiere-pro-mcp/issues/5),
[#8](https://github.com/leancoderkavy/premiere-pro-mcp/issues/8))
- **Markers landed at wildly wrong times.** `createMarker()` takes seconds, but was being handed
ticks — a marker requested at 2.0s was placed roughly 508 billion seconds down the timeline,
far past the end of any real sequence. `marker.end` had the same bug, and `list_markers` read
back nonsense as a result. ([#6](https://github.com/leancoderkavy/premiere-pro-mcp/issues/6))
- **`manage_proxies` and `get_encoder_presets` called ExtendScript methods that do not exist.**
`ProjectItem` has no `createProxy()` and `EncoderManager` has no `getFormatList()`, so both threw
every time. `manage_proxies` with `action: "create"` now queues a real proxy encode through Media
Encoder instead of reporting "Proxy creation started" for work that never happened, and
`get_encoder_presets` discovers presets by scanning the `.epr` files Adobe ships on disk, returning
each preset's path so it can be passed straight to `export_sequence`.
([#7](https://github.com/leancoderkavy/premiere-pro-mcp/issues/7))
- **`capture_frame`, `export_frame`, and `freeze_frame` threw on every call.** `exportFramePNG`
exists only on the QE DOM sequence, not the public DOM one. These tools now go through the QE
sequence, and — because QE's return value is unreliable — decide success by checking that a file
actually exists on disk, falling back to a one-frame Media Encoder export. They can no longer
report success having written nothing.
([#9](https://github.com/leancoderkavy/premiere-pro-mcp/issues/9))
- **Six tools repaired for Premiere Pro 2026** via
[#3](https://github.com/leancoderkavy/premiere-pro-mcp/pull/3): `add_audio_keyframes` (used a
nonexistent `Property.addKeyframe`, and wrote dB into a property that stores amplitude),
`color_correct` (one unsettable Lumetri property aborted the whole script and lost every other
change), `add_transition` and friends (`getVideoTransitionList()` returns empty on 2026 even
though by-name lookup works), `add_adjustment_layer` (`qeSeq.addAdjustmentLayer` was removed in
2026), `export_sequence` (defaulted to a hardcoded macOS-only preset path), and `add_text_overlay`
(called `createCaptionTrack` with the wrong signature).
- `manage_proxies` with `action: "toggle"` reported the inverse of the state it had just set.
- The README described this repository as "a temporary fork" of itself — a fork banner that rode in
with the [#1](https://github.com/leancoderkavy/premiere-pro-mcp/pull/1) merge.
### Notes
- The frame-export and proxy-create paths are fixed against the documented API and covered by
regression tests, but have not yet been live-verified against a running Premiere Pro. If you can
test them, reports on
[#7](https://github.com/leancoderkavy/premiere-pro-mcp/issues/7) and
[#9](https://github.com/leancoderkavy/premiere-pro-mcp/issues/9) are very welcome.
- Windows users on CEP 12 may additionally need to sign the extension (`ZXPSignCmd -sign`) — see
[#2](https://github.com/leancoderkavy/premiere-pro-mcp/issues/2) for details. That is an Adobe
signature-verification requirement, not a bug in this package.
## [1.0.0] - 2025-02-26
### Added
- **269 tools** across **28 modules** covering nearly the entire Premiere Pro ExtendScript and QE DOM API surface
- File-based IPC bridge for reliable communication between Node.js MCP server and CEP plugin
- CEP plugin with panel UI for bridge status monitoring and configuration
- Cross-platform support (macOS and Windows)
- Two MCP resources for LLM context: `premiere-instructions` and `extendscript-reference`
- Security validation for generated scripts (blocks eval, new Function, System.callSystem)
- Automated CEP plugin installer script
#### Tool Modules
- **discovery** (10) — Project info, item listing, clip queries
- **project** (26) — Save/open, import, bins, AE comps, bars & tone, scratch disks
- **media** (16) — Proxy management, offline, frame rate override, XMP, color space
- **sequence** (11) — Create, duplicate, delete, settings, auto-reframe, unnest, captions
- **timeline** (10) — Add/remove/move/trim/split clips, properties, replace
- **effects** (8) — Apply/remove effects, color correction, LUTs, stabilization
- **transitions** (5) — Add transitions by name (QE DOM)
- **audio** (3) — Levels, keyframes, mute
- **text** (3) — Text overlays, MOGRTs
- **markers** (4) — Add/delete/update/list markers
- **tracks** (4) — Add/delete/lock/visibility
- **playhead** (6) — Position, work area, in/out points
- **metadata** (9) — XMP, project metadata, color labels, footage interpretation
- **export** (14) — Sequence export, frame capture (base64), FCP XML, AAF, OMF, encoding
- **advanced** (27) — QE DOM: ripple delete, roll/slide/slip edits, speed, reverse, frame blend
- **keyframes** (8) — Full CRUD: add, get, remove, range remove, interpolation, value at time
- **scripting** (6) — Execute arbitrary ExtendScript, expression eval, DOM inspection
- **inspection** (10) — Deep project/sequence/clip analysis, timeline gaps, media reports
- **selection** (7) — Select by name, range, color; invert; select disabled
- **clipboard** (6) — Copy effects, batch apply, replace media, blend modes
- **source-monitor** (7) — Open/close, in/out points, insert/overwrite from source
- **track-targeting** (31) — Target tracks, motion/transform properties, audio properties
- **utility** (29) — Batch rename, enable/disable, project analysis, navigation
- **health** (1) — Connectivity ping
- **workspace** (2) — Get/set workspace layouts
- **captions** (1) — Create caption tracks
- **playback** (4) — Timeline and source monitor playback control
- **project-manager** (1) — Project consolidation and transfer
+73
View File
@@ -0,0 +1,73 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening a GitHub issue or contacting the project maintainers directly. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
+153
View File
@@ -0,0 +1,153 @@
# Contributing to Premiere Pro MCP Server
Thanks for your interest in contributing! This guide covers how to get set up and submit changes.
## Development Setup
### Prerequisites
- Node.js 18+
- Adobe Premiere Pro 2020+ (for testing)
- An MCP-compatible client (Claude Desktop, Windsurf, Cursor, GitHub Copilot, etc.)
### Getting started
```bash
git clone https://github.com/leancoderkavy/premiere-pro-mcp.git
cd premiere-pro-mcp
npm install
npm run dev # Watch mode — recompiles on changes
npm run install-cep # Install CEP plugin into Premiere Pro
```
After making changes, restart your MCP client to pick up the new tools.
## Project Architecture
```
src/
├── index.ts # Entry point
├── server.ts # Registers all tools with the MCP SDK
├── bridge/
│ ├── file-bridge.ts # File-based IPC (.jsx → .json)
│ └── script-builder.ts # Generates ES3 ExtendScript with helpers
└── tools/ # 29 tool modules
```
### How tools work
Each tool module exports a `getXTools(bridgeOptions)` function that returns a `Record<string, ToolDef>`. A tool definition has:
- **`description`** — shown to the AI client
- **`parameters`** — JSON Schema object (converted to Zod at registration)
- **`handler`** — async function that builds ExtendScript and sends it via the bridge
Example:
```typescript
my_tool: {
description: "Does a thing in Premiere Pro",
parameters: {
type: "object" as const,
properties: {
name: { type: "string", description: "Name of the thing" },
},
required: ["name"],
},
handler: async (args: { name: string }) => {
const script = buildToolScript(`
var result = app.project.name;
return __result({ projectName: result, input: "${escapeForExtendScript(args.name)}" });
`);
return sendCommand(script, bridgeOptions);
},
},
```
### ExtendScript rules
All generated scripts must be **ES3-compatible**:
- Use `var`, not `let`/`const`
- No arrow functions — use `function(x) { ... }`
- No template literals — use string concatenation
- No `Array.forEach/map/filter` — use manual `for` loops
- No destructuring, spread, or default parameters
- Always use `escapeForExtendScript()` for user-provided strings
### Helper functions
`buildToolScript()` prepends these helpers to every script:
- `__result(data)` — return success JSON
- `__error(msg)` — return error JSON
- `__findProjectItem(nameOrId)` — find project item by name or node ID
- `__findClip(nodeId)` — find clip on timeline by node ID
- `__findSequence(nameOrId)` — find sequence by name or ID
- `__ticksToSeconds(ticks)` / `__secondsToTicks(seconds)` — time conversion
- `__getClipComponents(clip)` — enumerate effect components
## Adding a New Tool
1. **Find the right module** in `src/tools/` or create a new one if it's a new capability area
2. **Add the tool definition** following the pattern above
3. **If creating a new module**, register it in `src/server.ts`:
```typescript
import { getMyTools } from "./tools/my-module.js";
// ... in createServer():
...getMyTools(bridgeOptions),
```
4. **Build and test**: `npm run build`
5. **Test in Premiere Pro** by calling the tool from your MCP client
## Submitting Changes
### Pull requests
1. Fork the repository
2. Create a feature branch: `git checkout -b feature/my-new-tool`
3. Make your changes
4. Run `npm run build` to verify compilation
5. Test with Premiere Pro if possible
6. Submit a pull request with a clear description
### Commit messages
Use clear, descriptive commit messages:
```
Add stabilize_clip tool using Warp Stabilizer effect
Fix set_clip_properties Position X/Y handling
Add workspace.ts module with get/set workspace tools
```
### Code style
- Follow existing patterns in the codebase
- Keep tool descriptions concise but informative
- Use TypeScript types for handler arguments
- Don't add comments unless they explain non-obvious behavior
## Reporting Issues
When filing an issue, please include:
- Premiere Pro version
- OS (macOS/Windows)
- MCP client (Claude Desktop, Windsurf, Cursor, GitHub Copilot, etc.)
- The tool name and parameters you used
- The error message or unexpected behavior
- Whether the CEP panel shows "Running"
## QE DOM Notes
The QE DOM is undocumented. If you discover new QE methods or behaviors:
1. Test thoroughly — QE operations can be destructive
2. Document what you find in `RESEARCH.md`
3. Mark QE-based tools with "Uses QE DOM" in their descriptions
4. Always call `app.enableQE()` before using QE objects
## License
By contributing, you agree that your contributions will be licensed under the MIT License.
+48
View File
@@ -0,0 +1,48 @@
# ── Stage 1: Build MCP server (TypeScript → dist/) ───────────────────────────
FROM node:20-alpine AS mcp-builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src/ ./src/
COPY scripts/copy-adobe-uxp-coverage.mjs ./scripts/copy-adobe-uxp-coverage.mjs
COPY scripts/generate-adobe-api-inventory.mjs ./scripts/generate-adobe-api-inventory.mjs
COPY scripts/generate-uxp-js-api-inventory.mjs ./scripts/generate-uxp-js-api-inventory.mjs
RUN npm run build
# ── Stage 2: Build Next.js landing page (→ landing/.next/out/) ───────────────
FROM node:20-alpine AS landing-builder
WORKDIR /landing
COPY landing/package*.json ./
RUN npm ci
COPY landing/ ./
RUN npm run build
# ── Stage 3: Production runner ────────────────────────────────────────────────
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN apk add --no-cache ffmpeg
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=mcp-builder /app/dist ./dist
# Copy Next.js static export to landing-dist (referenced in http-server.ts)
COPY --from=landing-builder /landing/out ./landing-dist
EXPOSE 3000
CMD ["node", "dist/http-server.js"]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Premiere Pro MCP Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+34
View File
@@ -0,0 +1,34 @@
# Performance notes
## July 2026 audit
The bridge used synchronous existence checks every 100 ms for every in-flight command. Node's
filesystem documentation recommends `fs.watch()` over stat polling when possible, while warning
that watching can be unreliable on some network and virtualized filesystems. The bridge therefore
uses event notification as the low-latency path and retains a 100 ms initial / 250 ms subsequent
polling fallback for correctness.
The Streamable HTTP endpoint intentionally remains stateless. The MCP transport specification
permits servers without session management, but this means each request constructs a new
`McpServer`. Tool definitions and converted Zod schemas are immutable for a given bridge and
capability configuration, so they are cached while each request still receives an independent MCP
server and transport.
Research sources:
- [Node.js filesystem API](https://nodejs.org/api/fs.html#fswatchfilename-options-listener)
- [MCP Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports)
- [Adobe Premiere UXP ESLint and transaction guidance](https://developer.adobe.com/premiere-pro/uxp/resources/fundamentals/eslint-support/)
## Local benchmark
Windows, Node.js 22, 100 `createServer()` calls:
| Path | Average |
|---|---:|
| Cache bypassed with unique bridge configurations | 5.873 ms |
| Reused bridge configuration | 2.208 ms |
This is a 62.4% reduction in repeated server-construction time. It does not measure Premiere host
execution time or claim equivalent end-to-end editing latency. Bridge watcher behavior is covered
by unit tests; live CEP latency remains dependent on Premiere and the host filesystem.
+1278
View File
File diff suppressed because it is too large Load Diff
+470
View File
@@ -0,0 +1,470 @@
# Premiere Pro MCP Server — API Research & Capability Map
## Sources Researched
1. **ExtendScript Scripting Guide** (ppro-scripting.docsforadobe.dev) — Complete official reference
2. **QE DOM API** (vakago-tools.com, community.adobe.com) — Undocumented internal API via `app.enableQE()`
3. **UXP API Reference** (developer.adobe.com/premiere-pro/uxp/) — Modern API (v25.6+), action-based
4. **Adobe CEP Samples** (github.com/Adobe-CEP/Samples/PProPanel) — Official sample ExtendScript
5. **adb-mcp** (github.com/mikechambers/adb-mcp) — UXP-based MCP for Premiere (Python + proxy)
6. **hetpatel-11/Adobe_Premiere_Pro_MCP** — CEP-based MCP (same architecture as ours)
### Adobe AI editorial workflow boundary (2026-08-21)
Adobe's current Premiere AI Assistant documentation describes a beta, in-product
assistant that can organize Project-panel assets, work with transcripts and
markers, and help construct stringouts or first cuts. It does not document a
public CEP, UXP, REST, or MCP invocation API. The Generative Media Tool is also
beta and requires in-product access, service availability, and generative
credits; it is not wired to this server.
Accordingly, this repository exposes only local, revision-aware planning and
preview artifacts for editorial workflows. Applying any recommendation remains
an explicit call to a supported, separately authorized Premiere tool, and
generation, transcription, translation, cloud upload, and paid-provider use
remain outside this implementation.
Primary references:
- Adobe Premiere AI Assistant FAQ — https://helpx.adobe.com/premiere/desktop/premiere-ai-assistant/assistant-faq.html
- Adobe Premiere UXP Transcript API — https://developer.adobe.com/premiere-pro/uxp/ppro-reference/classes/transcript/
- Adobe Premiere UXP SequenceEditor API — https://developer.adobe.com/premiere-pro/uxp/ppro-reference/classes/sequenceeditor/
- Adobe Premiere UXP Hybrid Plugins guide — https://developer.adobe.com/premiere-pro/uxp/plugins/hybrid-plugins/
---
## Historical Repository Snapshot (2026-07-20)
This is a dated research snapshot, not the source of truth for current releases or
tool counts. Use `README.md` and `CHANGELOG.md` for current product and release
information.
- **Release candidate:** `1.2.0` is integrated on `main`; `package.json`, `package-lock.json`, and
both CEP extension entries in `cep-plugin/CSXS/manifest.xml` are version-aligned.
- **npm status:** `1.1.7` is not yet published. The registry publish was attempted after the GitHub
push and stopped at npm's required one-time-password challenge; the package remains at `1.1.6`
on npm until an authenticated publish completes.
- **MCP surface:** 268 runtime-registered tools across 29 modules, 3 resources, and 4 prompts.
Capability profiles fail closed for raw scripting, and compound edit plans support preview-bound
confirmation and correlated audit events.
- **UXP preview:** `uxp-plugin/` provides a versioned WebSocket protocol, capability discovery,
state events, and verified frame export. Live Premiere and OS-specific loopback validation remain
required before it can replace CEP in production.
- **CEP bridge:** The operational workflow is unchanged, but the visible panel now has a compact
Premiere-oriented dark interface, clearer connection state, responsive controls, an improved
bridge-directory field, and a larger live activity monitor. Accessibility work includes labels,
focus states, live regions, and reduced-motion handling.
- **Website:** The redesigned landing page and its SEO metadata, manifest, dynamic `robots.txt`,
and dynamic sitemap are merged into `main`.
- **Validation:** The root TypeScript build and all 333 automated tests pass. Landing-page lint
passes, and Next.js compiles and generates all seven static pages. On this OneDrive checkout,
the final export cleanup repeatedly reports `EBUSY` while removing `landing/out`; this is an
environment/filesystem lock after page generation, not a source compilation failure.
### Release completion gate
Publish `premiere-pro-mcp@1.2.0` from `main` with a current npm authenticator OTP, then verify both
`npm view premiere-pro-mcp version` and the `latest` dist-tag resolve to `1.2.0`.
---
## Complete API Surface (ExtendScript + QE DOM)
### Application Object (`app`)
| Method | Description | Implemented? |
|--------|-------------|:---:|
| `app.enableQE()` | Enables QE DOM | ✅ |
| `app.project` | Active project | ✅ |
| `app.newProject(path)` | Create new project | ❌ **MISSING** |
| `app.openDocument(path)` | Open project | ✅ |
| `app.openFCPXML()` | Import FCP XML | ❌ |
| `app.quit()` | Quit Premiere | ❌ (dangerous) |
| `app.getEnableProxies()` | Check proxy state | ❌ |
| `app.setEnableProxies()` | Toggle proxies | ✅ (in manage_proxies) |
| `app.getWorkspaces()` | List workspaces | ✅ |
| `app.setWorkspace(name)` | Switch workspace | ✅ |
| `app.setScratchDiskPath(type, path)` | Set scratch disk | ✅ |
| `app.sourceMonitor` | Source monitor control | ✅ |
| `app.encoder` | AME encoder | ✅ |
| `app.properties` | Persistent properties | ❌ |
| `app.bind(eventName, fn)` | Event binding | N/A |
| `app.getProjectViewIDs()` | Multi-project support | ❌ |
| `app.getCurrentProjectViewSelection()` | Current selection | ❌ |
### Project Object (`app.project`)
| Method | Description | Implemented? |
|--------|-------------|:---:|
| `project.save()` | Save | ✅ |
| `project.saveAs(path)` | Save as | ✅ |
| `project.closeDocument(save, prompt)` | Close project | ❌ **MISSING** |
| `project.createNewSequence(name, id)` | Create sequence | ✅ |
| `project.createNewSequenceFromClips(name, items, bin)` | Sequence from clips | ✅ |
| `project.deleteSequence(seq)` | Delete sequence | ✅ |
| `project.importFiles(paths, suppressUI, targetBin, asNumbered)` | Import files | ✅ |
| `project.importAEComps(path, compNames, targetBin)` | Import AE comps | ✅ |
| `project.importAllAEComps(path, targetBin)` | Import all AE comps | ✅ |
| `project.importSequences(project, seqIDs)` | Import sequences from other project | ✅ |
| `project.exportAAF(...)` | Export AAF (14 params!) | ⚠️ Simplified |
| `project.exportFinalCutProXML(path, suppressUI)` | Export FCP XML | ✅ |
| `project.exportOMF(...)` | Export OMF | ✅ |
| `project.exportTimeline(preset)` | Export via preset | ❌ |
| `project.consolidateDuplicates()` | Consolidate | ✅ |
| `project.newBarsAndTone(w, h, base, name)` | Create bars & tone | ✅ |
| `project.newSequence(name, pathToPreset)` | New seq from preset | ❌ |
| `project.openSequence(seqID)` | Open/activate sequence | ✅ |
| `project.getInsertionBin()` | Current target bin | ✅ |
| `project.setEnableTranscodeOnIngest(enable)` | Ingest transcoding | ✅ |
| `project.getGraphicsWhiteLuminance()` | HDR setting | ✅ |
| `project.setGraphicsWhiteLuminance(val)` | HDR setting | ✅ |
| `project.getProjectPanelMetadata()` | Panel metadata columns | ✅ |
| `project.setProjectPanelMetadata(json)` | Set panel metadata | ✅ |
| `project.addPropertyToProjectMetadataSchema(name, label, type)` | Add custom metadata field | ✅ |
### Sequence Object
| Method | Description | Implemented? |
|--------|-------------|:---:|
| `seq.insertClip(item, time, vTrack, aTrack)` | Insert (ripple) clip | ✅ |
| `seq.overwriteClip(item, time, vTrack, aTrack)` | Overwrite clip | ✅ |
| `seq.importMGT(path, time, vOff, aOff)` | Import MOGRT | ✅ |
| `seq.importMGTFromLibrary(lib, name, time, v, a)` | MOGRT from CC Library | ✅ |
| `seq.clone()` | Duplicate sequence | ✅ |
| `seq.close()` | Close sequence tab | ✅ |
| `seq.createSubsequence(ignoreMapping)` | Create subsequence | ✅ |
| `seq.createCaptionTrack(item, startTime, captionFormat)` | Captions | ✅ |
| `seq.autoReframeSequence(num, den, preset, name, nested)` | Auto reframe | ✅ |
| `seq.attachCustomProperty(id, value)` | Custom FCP XML props | ✅ |
| `seq.getSettings()` | Get all settings | ✅ |
| `seq.setSettings(settings)` | Modify settings | ✅ |
| `seq.getSelection()` | Selected clips array | ✅ |
| `seq.getPlayerPosition()` | Playhead position | ✅ |
| `seq.setPlayerPosition(ticks)` | Move playhead | ✅ |
| `seq.getInPoint()` / `getOutPoint()` | Sequence I/O points | ✅ |
| `seq.setInPoint()` / `setOutPoint()` | Set I/O points | ✅ |
| `seq.getWorkAreaInPoint()` / `OutPoint()` | Work area | ✅ |
| `seq.setWorkAreaInPoint()` / `OutPoint()` | Set work area | ✅ |
| `seq.linkSelection()` | Link selected A/V | ✅ |
| `seq.unlinkSelection()` | Unlink selected A/V | ✅ |
| `seq.exportAsMediaDirect(path, preset, workArea)` | Direct export | ✅ |
| `seq.exportAsProject(path)` | Export as .prproj | ✅ |
| `seq.exportAsFinalCutProXML(path)` | FCP XML | ✅ |
| `seq.getExportFileExtension(preset)` | Get extension for preset | ✅ |
| `seq.isDoneAnalyzingForVideoEffects()` | Check analysis status | ❌ |
| `seq.isWorkAreaEnabled()` | Check work area bar | ✅ |
| `seq.setZeroPoint(ticks)` | Set start time code | ✅ |
| `seq.performSceneEditDetectionOnSelection()` | Scene detect | ✅ |
### Track Object
| Method | Description | Implemented? |
|--------|-------------|:---:|
| `track.insertClip(item, time, vTrack, aTrack)` | Insert clip | ✅ |
| `track.overwriteClip(item, time)` | Overwrite clip | ❌ **MISSING** |
| `track.isMuted()` | Check mute | ❌ |
| `track.setMute(muted)` | Set mute | ✅ |
| `track.clips` | TrackItemCollection | ✅ |
| `track.transitions` | Transitions on track | ❌ (read) |
### TrackItem Object
| Method | Description | Implemented? |
|--------|-------------|:---:|
| `clip.name` | Clip name | ✅ |
| `clip.nodeId` | Unique ID | ✅ |
| `clip.start` / `end` | Timeline position | ✅ |
| `clip.inPoint` / `outPoint` | Source I/O | ✅ |
| `clip.duration` | Duration | ✅ |
| `clip.components` | Effect components | ✅ |
| `clip.projectItem` | Source project item | ✅ |
| `clip.getSpeed()` | Speed multiplier | ✅ |
| `clip.isSpeedReversed()` | Is reversed? | ✅ |
| `clip.isAdjustmentLayer()` | Is adjustment layer? | ✅ |
| `clip.isSelected()` | Selection state | ✅ |
| `clip.setSelected(state, updateUI)` | Set selection | ✅ |
| `clip.remove(inRipple, inAlignToVideo)` | Remove clip | ✅ |
| `clip.move(newInPoint)` | Move clip | ✅ |
| `clip.disabled` | Enable/disable | ✅ |
| `clip.getMGTComponent()` | MOGRT params | ✅ |
| `clip.getMatchName()` | Match name | ❌ |
### ProjectItem Object
| Method | Description | Implemented? |
|--------|-------------|:---:|
| `item.name` / `nodeId` / `type` / `treePath` | Identity | ✅ |
| `item.children` | Children (for bins) | ✅ |
| `item.createBin(name)` | Create bin | ✅ |
| `item.createSmartBin(name, query)` | Smart bin | ✅ |
| `item.createSubClip(name, start, end, hard, audio, video)` | Subclip | ✅ |
| `item.deleteBin()` | Delete bin | ✅ |
| `item.moveBin(destBin)` | Move to bin | ✅ |
| `item.renameBin(name)` | Rename bin | ✅ |
| `item.select()` | Select in project panel | ✅ |
| `item.setScaleToFrameSize()` | Scale to frame | ✅ |
| `item.setStartTime(ticks)` | Set start time | ✅ |
| `item.setOverrideFrameRate(fps)` | Override FPS | ✅ |
| `item.setOverridePixelAspectRatio(n, d)` | Override PAR | ✅ |
| `item.setOffline()` | Set offline | ✅ |
| `item.refreshMedia()` | Refresh | ✅ |
| `item.changeMediaPath(path, overrideChecks)` | Relink | ✅ |
| `item.attachProxy(path, isHiRes)` | Proxy | ✅ |
| `item.hasProxy()` | Has proxy? | ✅ |
| `item.canProxy()` | Can proxy? | ❌ |
| `item.isOffline()` | Offline? | ✅ |
| `item.isSequence()` | Is sequence? | ❌ |
| `item.isMergedClip()` | Merged? | ❌ |
| `item.isMulticamClip()` | Multicam? | ❌ |
| `item.findItemsMatchingMediaPath(path)` | Find by path | ✅ |
| `item.getColorLabel()` / `setColorLabel(idx)` | Color label | ✅ |
| `item.getFootageInterpretation()` / `setFootageInterpretation()` | Footage interp | ✅ |
| `item.getProjectMetadata()` / `setProjectMetadata()` | XMP metadata | ✅ |
| `item.getXMPMetadata()` / `setXMPMetadata()` | Raw XMP | ✅ |
| `item.videoComponents()` | Video components on source | ❌ |
| `item.getColorSpace()` | Color space | ✅ |
| `item.getOriginalColorSpace()` | Original color space | ❌ |
| `item.getEmbeddedLUTID()` | Embedded LUT | ❌ |
| `item.getInputLUTID()` | Input LUT | ❌ |
| `item.getInPoint()` / `getOutPoint()` | Source I/O | ❌ |
| `item.setInPoint()` / `setOutPoint()` | Set source I/O | ❌ |
| `item.clearInPoint()` / `clearOutPoint()` | Clear source I/O | ❌ |
### ComponentParam Object (Keyframes & Effect Properties)
| Method | Description | Implemented? |
|--------|-------------|:---:|
| `param.getValue()` | Get current value | ✅ |
| `param.setValue(val, updateUI)` | Set value | ✅ |
| `param.getValueAtKey(time)` | Value at keyframe | ❌ |
| `param.getValueAtTime(time)` | Interpolated value at time | ✅ (in keyframes.ts) |
| `param.setValueAtKey(time, val, updateUI)` | Set at keyframe | ✅ |
| `param.addKey(time)` | Add keyframe | ✅ |
| `param.removeKey(time)` | Remove keyframe | ✅ |
| `param.removeKeyRange(start, end)` | Remove keyframe range | ✅ |
| `param.getKeys()` | All keyframe times | ✅ |
| `param.findNearestKey(time, threshold)` | Find nearest | ❌ |
| `param.findNextKey(time)` | Find next | ❌ |
| `param.findPreviousKey(time)` | Find previous | ❌ |
| `param.areKeyframesSupported()` | Supports keyframes? | ❌ |
| `param.isTimeVarying()` | Has keyframes? | ❌ |
| `param.setTimeVarying(bool)` | Enable keyframes | ✅ |
| `param.setInterpolationTypeAtKey(time, type, updateUI)` | Interp type | ✅ |
| `param.getColorValue()` | Color value | ❌ |
| `param.setColorValue(a, r, g, b, updateUI)` | Set color | ✅ |
| `param.displayName` | Property name | ✅ |
### Encoder Object (`app.encoder`)
| Method | Description | Implemented? |
|--------|-------------|:---:|
| `encoder.encodeSequence(seq, path, preset, workArea, removeOnCompletion)` | Queue encode | ✅ |
| `encoder.encodeProjectItem(item, path, preset, workArea, removeOnCompletion)` | Encode item | ✅ |
| `encoder.encodeFile(path, outputPath, preset, removeOnCompletion, startTime, stopTime)` | Encode file | ✅ |
| `encoder.launchEncoder()` | Launch AME | ✅ |
| `encoder.startBatch()` | Start render queue | ✅ |
| `encoder.setEmbeddedXMPEnabled(enable)` | XMP in output | ❌ |
| `encoder.setSidecarXMPEnabled(enable)` | Sidecar XMP | ❌ |
### Source Monitor (`app.sourceMonitor`)
| Method | Description | Implemented? |
|--------|-------------|:---:|
| `sourceMonitor.openProjectItem(item)` | Open in source | ✅ |
| `sourceMonitor.openFilePath(path)` | Open file in source | ✅ |
| `sourceMonitor.closeClip()` | Close current | ✅ |
| `sourceMonitor.closeAllClips()` | Close all | ✅ |
| `sourceMonitor.play(speed)` | Play | ✅ |
| `sourceMonitor.getPosition()` | CTI position | ✅ |
| `sourceMonitor.getProjectItem()` | Currently loaded item | ✅ |
### Project Manager (`app.projectManager`)
| Attribute | Description | Implemented? |
|-----------|-------------|:---:|
| All 14+ attributes for project consolidation/trimming | Copy, transfer, transcode | ✅ |
---
## QE DOM (Undocumented but Critical)
**Must call `app.enableQE()` first.**
### QE Global (`qe`)
| Method | Description |
|--------|-------------|
| `qe.project` | QE project object |
| `qe.getSequencePresets()` | All sequence presets |
| `qe.newProject(path)` | New project |
| `qe.open(path, showUI)` | Open project |
| `qe.startPlayback()` | Play timeline |
| `qe.stopPlayback()` | Stop playback |
| `qe.stop()` | Stop |
| `qe.exit()` | Exit app |
| `qe.wait(ms)` | Wait |
| `qe.getModalWindowID()` | Modal check |
| `qe.executeConsoleCommand(cmd)` | Console command |
### QE Project (`qe.project`)
| Method | Description | Implemented? |
|--------|-------------|:---:|
| `qe.project.getActiveSequence()` | QE active sequence | ✅ |
| `qe.project.getVideoEffectList()` | All video effects | ✅ |
| `qe.project.getVideoEffectByName(name)` | Get effect by name | ✅ |
| `qe.project.getAudioEffectList()` | All audio effects | ✅ |
| `qe.project.getAudioEffectByName(name)` | Get audio effect | ✅ |
| `qe.project.getVideoTransitionList()` | All video transitions | ✅ |
| `qe.project.getVideoTransitionByName(name)` | Get transition | ✅ |
| `qe.project.getAudioTransitionList()` | All audio transitions | ✅ |
| `qe.project.getAudioTransitionByName(name)` | Get audio transition | ✅ |
| `qe.project.undo()` | Undo | ✅ |
| `qe.project.newSequence(name, presetPath)` | New seq from preset | ❌ **MISSING** |
| `qe.project.importFiles(paths)` | Import | ❌ |
| `qe.project.importAEComps(path, compNames)` | AE comps | ❌ |
### QE Sequence
| Method | Description | Implemented? |
|--------|-------------|:---:|
| `qeSeq.getVideoTrackAt(idx)` | Get video track | ✅ |
| `qeSeq.getAudioTrackAt(idx)` | Get audio track | ✅ |
| `qeSeq.addTracks(vNum, aNum, aMono, a5_1, aAdaptive)` | Add tracks | ✅ |
| `qeSeq.removeTracks(vIdx, aIdx, aMonoIdx, a5_1Idx)` | Remove tracks | ❌ |
### QE Track Item (Clip) — **THE MOST POWERFUL PART**
| Method | Description | Implemented? |
|--------|-------------|:---:|
| `qeClip.addVideoEffect(effect)` | Add video effect | ✅ |
| `qeClip.addAudioEffect(effect)` | Add audio effect | ✅ |
| `qeClip.addTransition(transition, ...)` | Add transition | ✅ |
| `qeClip.removeEffects()` | Remove ALL effects | ✅ |
| `qeClip.remove()` | Remove from timeline | ✅ |
| `qeClip.rippleDelete()` | Ripple delete | ✅ |
| `qeClip.move(newTime)` | Move clip | ❌ |
| `qeClip.moveToTrack(trackIdx)` | Move to different track | ✅ |
| `qeClip.roll(newTime)` | Roll edit | ✅ |
| `qeClip.slide(offset)` | Slide edit | ✅ |
| `qeClip.slip(offset)` | Slip edit | ✅ |
| `qeClip.setSpeed(speed, ...)` | Set playback speed | ✅ |
| `qeClip.setReverse(reverse)` | Reverse playback | ✅ |
| `qeClip.setName(name)` | Rename clip | ✅ |
| `qeClip.setScaleToFrameSize()` | Scale to frame | ❌ |
| `qeClip.setFrameBlend(enable)` | Frame blending | ✅ |
| `qeClip.setTimeInterpolationType(type)` | Time interp (optical flow etc.) | ✅ |
| `qeClip.setAntiAliasQuality(quality)` | Anti-alias | ❌ |
| `qeClip.setStartPercent(pct)` | Transition start % | ❌ |
| `qeClip.setEndPercent(pct)` | Transition end % | ❌ |
| `qeClip.setStartPosition(pos)` | Start position | ❌ |
| `qeClip.setEndPosition(pos)` | End position | ❌ |
| `qeClip.setBorderColor(color)` | Border color | ❌ |
| `qeClip.setBorderWidth(width)` | Border width | ❌ |
| `qeClip.setMulticam(enable)` | Multicam | ❌ |
| `qeClip.setSwitchSources(enable)` | Switch sources | ❌ |
| `qeClip.canDoMulticam()` | Check multicam | ❌ |
| `qeClip.getClipPanComponent()` | Pan component | ❌ |
| `qeClip.getComponentAt(idx)` | Get component | ❌ |
| `qeClip.getProjectItem()` | Source item | ❌ |
---
## Implementation Status — Priority List
**Total runtime-registered tools: 268 across 29 modules** (including safe edit plans)
### P0 — Critical ✅ ALL IMPLEMENTED
1. ~~**`create_project`**~~ — ❌ Intentionally skipped (requires UXP, not available via ExtendScript CEP)
2. ✅ **`create_sequence_from_clips`** — `project.createNewSequenceFromClips` (advanced.ts)
3. ✅ **`overwrite_clip`** — `seq.overwriteClip` (advanced.ts)
4. ✅ **`ripple_delete`** — QE DOM (advanced.ts)
5. ✅ **`close_gaps`** — QE DOM ripple delete approach (advanced.ts)
6. ✅ **`get_clip_speed`** — `clip.getSpeed()` + `isSpeedReversed()` (advanced.ts)
7. ✅ **`set_clip_speed_qe`** — `qeClip.setSpeed()` (advanced.ts)
8. ✅ **`reverse_clip`** — `qeClip.setReverse()` (advanced.ts)
### P1 — Important for full LLM control ✅ ALL IMPLEMENTED
9. ✅ **`link_selection` / `unlink_selection`** — (advanced.ts)
10. ✅ **`set_clip_selection`** — (selection.ts)
11. ✅ **`roll_edit` / `slide_edit` / `slip_edit`** — QE DOM (advanced.ts)
12. ✅ **`move_clip_to_track`** — QE DOM (advanced.ts)
13. ✅ **`remove_all_effects`** — QE DOM (advanced.ts)
14. ✅ **`set_blend_mode`** — (utility.ts)
15. ✅ **`set_color_value`** — `param.setColorValue()` (advanced.ts)
16. ✅ **`capture_frame`** — Export frame + return as base64 image (export.ts)
17. ✅ **`set_keyframe_interpolation`** — Linear/Bezier/Hold (keyframes.ts)
18. ✅ **`get_keyframes` / `remove_keyframe` / `remove_keyframe_range`** — Full CRUD (keyframes.ts)
### P2 — Nice-to-have ✅ ALL IMPLEMENTED
19. ✅ **`close_sequence`** — `seq.close()` (advanced.ts)
20. ✅ **`export_as_project`** — `seq.exportAsProject()` (advanced.ts)
21. ✅ **`create_bars_and_tone`** — `project.newBarsAndTone()` (project.ts)
22. ✅ **`open_in_source_monitor`** — (source-monitor.ts)
23. ✅ **`play_source_monitor`** — (playback.ts)
24. ✅ **`start_batch_encode`** — `encoder.startBatch()` (advanced.ts)
25. ✅ **`encode_project_item` / `encode_file`** — (export.ts)
26. ✅ **`import_ae_comps`** — (project.ts)
27. ✅ **`set_frame_blend`** — QE DOM (advanced.ts)
28. ✅ **`set_time_interpolation`** — Optical flow etc. (advanced.ts)
29. ✅ **`delete_bin` / `rename_bin`** — (advanced.ts)
30. ✅ **`create_smart_bin`** — (advanced.ts)
31. ✅ **`find_items_by_media_path`** — (advanced.ts)
32. ✅ **`add_custom_metadata_field`** — (advanced.ts)
33. ✅ **`set_zero_point`** — (advanced.ts)
34. ✅ **`scene_edit_detection`** — (utility.ts)
35. ✅ **`get/set_workspace`** — (workspace.ts)
36. ✅ **LLM instructions resource** — `config://premiere-instructions` + `config://extendscript-reference`
### New modules added
- **workspace.ts** (2 tools) — get_workspaces, set_workspace
- **captions.ts** (1 tool) — create_caption_track
- **playback.ts** (4 tools) — play_timeline, stop_playback, play_source_monitor, get_source_monitor_position
- **project-manager.ts** (1 tool) — consolidate_and_transfer
- **health.ts** (1 tool) — ping
### Remaining unimplemented (low-value or risky)
- `app.newProject()` — Requires UXP or has severe limitations in ExtendScript
- `app.quit()` — Dangerous, intentionally excluded
- `app.openFCPXML()` — Use import_fcp_xml instead
- `track.overwriteClip()` — Covered by seq.overwriteClip
- `item.canProxy()`, `item.isSequence()`, `item.isMergedClip()`, `item.isMulticamClip()` — Minor read-only checks
- `param.findNearestKey()`, `param.findNextKey()`, `param.findPreviousKey()` — Minor keyframe navigation
- `qeClip.setAntiAliasQuality()`, `qeClip.setBorderColor/Width()`, `qeClip.setMulticam()` — Niche QE features
- `qeSeq.removeTracks()` — Risky operation
---
## Known Effect Match Names (for `appendVideoFilter` via QE)
From adb-mcp research:
- `AE.ADBE Black & White` — Black and white
- `AE.ADBE Gaussian Blur 2` — Gaussian blur (properties: `Blurriness`, `Blur Dimensions`)
- `AE.ADBE Tint` — Tint (properties: `Map Black To`, `Map White To`, `Amount to Tint`)
- `AE.ADBE Motion Blur` — Directional blur (properties: `Direction`, `Blur Length`)
### Valid Transition Names
**ADBE (built-in):**
- `ADBE Additive Dissolve`, `ADBE Cross Zoom`, `ADBE Cube Spin`, `ADBE Film Dissolve`
- `ADBE Flip Over`, `ADBE Gradient Wipe`, `ADBE Iris Cross`, `ADBE Iris Diamond`
- `ADBE Iris Round`, `ADBE Iris Square`, `ADBE Page Peel`, `ADBE Push`, `ADBE Slide`, `ADBE Wipe`
**AE.ADBE (After Effects):**
- `AE.ADBE Center Split`, `AE.ADBE Inset`, `AE.ADBE Cross Dissolve New`
- `AE.ADBE Dip To White`, `AE.ADBE Split`, `AE.ADBE Whip`
- `AE.ADBE Non-Additive Dissolve`, `AE.ADBE Dip To Black`
- `AE.ADBE Barn Doors`, `AE.ADBE MorphCut`
### Blend Modes
`NORMAL`, `DISSOLVE`, `DARKEN`, `MULTIPLY`, `COLORBURN`, `LINEARBURN`, `DARKERCOLOR`,
`LIGHTEN`, `SCREEN`, `COLORDODGE`, `LINEARDODGE`, `LIGHTERCOLOR`, `OVERLAY`, `SOFTLIGHT`,
`HARDLIGHT`, `VIVIDLIGHT`, `LINEARLIGHT`, `PINLIGHT`, `HARDMIX`, `DIFFERENCE`, `EXCLUSION`,
`SUBTRACT`, `DIVIDE`, `HUE`, `SATURATION`, `COLOR`, `LUMINOSITY`
### Interpolation Types
- `0` — KF_Interp_Mode_Linear
- `4` — KF_Interp_Mode_Hold
- `5` — KF_Interp_Mode_Bezier
---
## Architecture Insights from adb-mcp
Their MCP server includes a **resource** (`config://get_instructions`) that gives the LLM context about how to use Premiere effectively:
- "Add clips first, then effects, then transitions"
- "Keep transitions short (≤2 seconds)"
- "No gap between clips for transitions to work"
- "Video clips with higher track index overlap lower ones"
- "Images have default 5-second duration"
- "First clip determines sequence resolution"
This recommendation is implemented. Our server exposes `config://premiere-instructions` for editing
workflow guidance and `config://extendscript-reference` for the scripting surface. Both resources are
registered alongside the tool catalog in `src/server.ts`; version 1.2.0 also registers
`config://premiere-workflows` and four guided prompts.
+30
View File
@@ -0,0 +1,30 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| latest | :white_check_mark: |
## Reporting a Vulnerability
If you discover a security vulnerability in this project, **please do not open a public GitHub issue**.
Instead, report it by opening a [GitHub Security Advisory](https://github.com/kavyrattana/pp-mcp/security/advisories/new) (or contact the maintainer directly via GitHub).
Please include:
- A description of the vulnerability and its potential impact
- Steps to reproduce or a proof-of-concept
- Any suggested mitigations, if known
You can expect an acknowledgement within **48 hours** and a resolution timeline within **7 days** for critical issues.
## Security Considerations
This MCP server executes ExtendScript inside Adobe Premiere Pro via a CEP plugin. Please note:
- **Script validation** blocks dangerous patterns (`eval()`, `new Function()`, `System.callSystem()`) in user-provided scripts
- **`sendRawCommand()`** bypasses validation and should only be used by trusted clients
- The file-based IPC bridge writes temporary files to the system temp directory — ensure your temp directory has appropriate permissions
- This tool grants AI assistants significant control over Premiere Pro; only connect trusted MCP clients
@@ -0,0 +1,9 @@
/* Minimal CEP bridge API used by the local After Effects connector. */
function CSInterface() {}
CSInterface.prototype.evalScript = function (script, callback) {
if (typeof __adobe_cep__ !== "undefined") {
__adobe_cep__.evalScript(script, callback || function () {});
} else if (callback) {
callback("EvalScript Error: Not in CEP environment");
}
};
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.14.9" ExtensionBundleName="MCP for Adobe After Effects">
<ExtensionList>
<Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.14.9"/>
</ExtensionList>
<ExecutionEnvironment>
<HostList>
<Host Name="AEFT" Version="15.0"/>
</HostList>
<LocaleList>
<Locale Code="All"/>
</LocaleList>
<RequiredRuntimeList>
<RequiredRuntime Name="CSXS" Version="9.0"/>
</RequiredRuntimeList>
</ExecutionEnvironment>
<DispatchInfoList>
<Extension Id="com.mcp.aftereffects.bridge.panel">
<DispatchInfo>
<Resources>
<MainPath>./index.html</MainPath>
<ScriptPath>./host.jsx</ScriptPath>
<CEFCommandLine>
<Parameter>--allow-file-access-from-files</Parameter>
<Parameter>--enable-nodejs</Parameter>
</CEFCommandLine>
</Resources>
<Lifecycle><AutoVisible>true</AutoVisible></Lifecycle>
<UI>
<Type>Panel</Type>
<Menu>MCP for Adobe After Effects</Menu>
<Geometry><Size><Height>250</Height><Width>390</Width></Size><MinSize><Height>200</Height><Width>320</Width></MinSize></Geometry>
<Icons/>
</UI>
</DispatchInfo>
</Extension>
</DispatchInfoList>
</ExtensionManifest>
@@ -0,0 +1,5 @@
// The command body is supplied through the local bridge. Keeping this host
// script minimal prevents unreviewed global helpers from persisting in AE.
function mcpAfterEffectsBridgePing() {
return "pong";
}
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MCP for Adobe After Effects</title>
<style>
body { margin: 0; background: #202124; color: #f3f4f6; font: 13px/1.45 Arial, sans-serif; }
main { padding: 18px; } h1 { margin: 0 0 5px; font-size: 16px; } p { color: #c7cbd1; }
label { display: block; margin: 16px 0 6px; font-weight: bold; } input { box-sizing: border-box; width: 100%; padding: 8px; border: 1px solid #555; border-radius: 4px; background: #111827; color: white; }
button { margin-top: 12px; padding: 8px 12px; border: 0; border-radius: 4px; background: #2563eb; color: white; cursor: pointer; } #status { display: inline-block; margin-left: 9px; color: #86efac; }
small { display: block; margin-top: 8px; color: #9ca3af; }
</style>
</head>
<body>
<main>
<h1>MCP for Adobe After Effects</h1>
<p>Local authoring connector for approval-gated MOGRT recipes.</p>
<label for="tempDir">After Effects bridge directory</label>
<input id="tempDir" spellcheck="false" autocomplete="off" aria-describedby="bridgeHelp">
<button id="toggle" type="button">Start connector</button><strong id="status" role="status">Stopped</strong>
<small id="bridgeHelp">This must match AFTER_EFFECTS_MCP_TEMP_DIR when that environment variable is set for your MCP client.</small>
</main>
<script src="CSInterface.js"></script>
<script src="main.js"></script>
</body>
</html>
+129
View File
@@ -0,0 +1,129 @@
/* Dedicated AE CEP file bridge. It deliberately uses a different directory
* from the Premiere connector so simultaneous Adobe hosts cannot claim each
* other's ExtendScript commands. */
(function () {
var cs = new CSInterface();
var fs = nodeRequire("fs");
var path = nodeRequire("path");
var os = nodeRequire("os");
var pollTimer = null;
var heartbeatTimer = null;
var running = false;
var tempDir = defaultBridgeDirectory();
var engineId = Math.random().toString(36).slice(2, 8);
function nodeRequire(moduleName) {
if (typeof require !== "undefined") return require(moduleName);
var node = typeof cep_node !== "undefined" ? cep_node : window.cep_node;
if (node && typeof node.require === "function") return node.require(moduleName);
throw new Error("Node.js is unavailable. Confirm --enable-nodejs in the CEP manifest, then fully restart After Effects.");
}
function defaultBridgeDirectory() {
try {
var process = nodeRequire("process");
var configured = process && process.env && process.env.AFTER_EFFECTS_MCP_TEMP_DIR;
if (typeof configured === "string" && configured.trim()) return configured.trim();
} catch (ignored) {}
return path.join(os.tmpdir(), "after-effects-mcp-bridge");
}
function setStatus(value, active) {
document.getElementById("status").textContent = value;
document.getElementById("status").style.color = active ? "#86efac" : "#fca5a5";
document.getElementById("toggle").textContent = active ? "Stop connector" : "Start connector";
}
function writeFileAtomic(filePath, text) {
var staged = filePath + "." + engineId + ".staged";
try {
fs.writeFileSync(staged, text, "utf8");
fs.renameSync(staged, filePath);
return true;
} catch (error) {
try { if (fs.existsSync(staged)) fs.unlinkSync(staged); } catch (ignored) {}
setStatus("Connector needs attention", false);
return false;
}
}
function heartbeat() {
if (!tempDir) return;
writeFileAtomic(path.join(tempDir, "bridge-heartbeat.json"), JSON.stringify({ protocolVersion: 1, state: running ? "running" : "waiting" }));
}
function readCommandFiles() {
try {
return fs.readdirSync(tempDir).filter(function (entry) {
return entry.indexOf("cmd_") === 0 && entry.slice(-4) === ".jsx";
}).sort();
} catch (ignored) { return []; }
}
function replyFor(result) {
if (!result || result === "undefined" || result === "null") {
return JSON.stringify({ success: false, error: "The After Effects connector received an empty evalScript result. Reopen the panel and retry once." });
}
try { return JSON.stringify(JSON.parse(result)); }
catch (ignored) {
return result.indexOf("Error") === 0
? JSON.stringify({ success: false, error: result })
: JSON.stringify({ success: true, data: result });
}
}
function processOne(fileName) {
var source = path.join(tempDir, fileName);
var claim = source + "." + engineId + ".claimed";
try { fs.renameSync(source, claim); } catch (ignored) { return; }
var script;
try { script = fs.readFileSync(claim, "utf8"); } catch (error) { script = null; }
try { if (fs.existsSync(claim)) fs.unlinkSync(claim); } catch (ignored) {}
if (!script) return;
var id = fileName.replace("cmd_", "").replace(".jsx", "");
var busy = path.join(tempDir, "busy_" + id + ".json");
var started = Date.now();
var busyTimer = setInterval(function () {
try { fs.writeFileSync(busy, JSON.stringify({ id: id, elapsedMs: Date.now() - started }), "utf8"); } catch (ignored) {}
}, 2000);
cs.evalScript(script, function (result) {
clearInterval(busyTimer);
try { if (fs.existsSync(busy)) fs.unlinkSync(busy); } catch (ignored) {}
writeFileAtomic(path.join(tempDir, "res_" + id + ".json"), replyFor(String(result || "")));
});
}
function processCommands() {
var files = readCommandFiles();
for (var index = 0; index < files.length; index++) processOne(files[index]);
}
function start() {
tempDir = document.getElementById("tempDir").value.trim();
if (!tempDir) { setStatus("Set a bridge directory", false); return; }
try { fs.mkdirSync(tempDir, { recursive: true, mode: 0o700 }); }
catch (error) { setStatus("Cannot create bridge directory", false); return; }
running = true;
heartbeat();
if (pollTimer) clearInterval(pollTimer);
if (heartbeatTimer) clearInterval(heartbeatTimer);
pollTimer = setInterval(processCommands, 200);
heartbeatTimer = setInterval(heartbeat, 1000);
try { localStorage.setItem("after_effects_mcp_temp_dir", tempDir); } catch (ignored) {}
setStatus("Connector running", true);
}
function stop() {
running = false;
heartbeat();
if (pollTimer) clearInterval(pollTimer);
if (heartbeatTimer) clearInterval(heartbeatTimer);
pollTimer = null;
heartbeatTimer = null;
setStatus("Stopped", false);
}
var field = document.getElementById("tempDir");
try { field.value = localStorage.getItem("after_effects_mcp_temp_dir") || tempDir; } catch (ignored) { field.value = tempDir; }
document.getElementById("toggle").onclick = function () { if (running) stop(); else start(); };
}());
@@ -0,0 +1,70 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://premiere-pro-mcp.com/schemas/uxp-hybrid-benchmark-evidence-v3.json",
"title": "Premiere Pro MCP UXP hybrid benchmark evidence v3",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "workloadId", "configuration", "memoryMeasurement", "sdkHeaderReceiptSha256", "addonReceiptSha256", "ccxReceiptSha256", "runs"],
"properties": {
"schemaVersion": { "const": 3 },
"workloadId": { "const": "weighted-energy-v1" },
"configuration": {
"type": "object",
"additionalProperties": false,
"required": ["sampleCount", "warmupCount", "iterations", "inputLength", "seed"],
"properties": {
"sampleCount": { "const": 30 },
"warmupCount": { "const": 3 },
"iterations": { "const": 4 },
"inputLength": { "const": 131072 },
"seed": { "const": 1337 }
}
},
"memoryMeasurement": { "type": "string", "minLength": 1, "maxLength": 256 },
"sdkHeaderReceiptSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
"addonReceiptSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
"ccxReceiptSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
"runs": {
"type": "array",
"minItems": 3,
"maxItems": 3,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"platform", "arch", "hostVersion", "sdkVersion", "buildMode",
"addonLoaded", "addonSha256", "sourceCommit", "checksumMatch",
"codeSigned", "notarized", "javascript", "native"
],
"properties": {
"platform": { "enum": ["win", "mac"] },
"arch": { "enum": ["x64", "arm64"] },
"hostVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
"sdkVersion": { "type": "string", "minLength": 1, "maxLength": 128 },
"buildMode": { "const": "Release" },
"addonLoaded": { "const": true },
"addonSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
"sourceCommit": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
"checksumMatch": { "const": true },
"codeSigned": { "type": "boolean" },
"notarized": { "type": "boolean" },
"javascript": { "$ref": "#/$defs/metrics" },
"native": { "$ref": "#/$defs/metrics" }
}
}
}
},
"$defs": {
"metrics": {
"type": "object",
"additionalProperties": false,
"required": ["sampleCount", "p50Ms", "p95Ms", "peakWorkingSetBytes"],
"properties": {
"sampleCount": { "type": "integer", "minimum": 20 },
"p50Ms": { "type": "number", "exclusiveMinimum": 0 },
"p95Ms": { "type": "number", "exclusiveMinimum": 0 },
"peakWorkingSetBytes": { "type": "integer", "exclusiveMinimum": 0 }
}
}
}
}
@@ -0,0 +1,16 @@
{
"schemaVersion": 3,
"workloadId": "weighted-energy-v1",
"configuration": {
"sampleCount": 30,
"warmupCount": 3,
"iterations": 4,
"inputLength": 131072,
"seed": 1337
},
"memoryMeasurement": "Replace with the identical process peak-working-set collection method used on every target",
"sdkHeaderReceiptSha256": "Replace with the canonical digest printed by native:sdk-header-inventory:verify",
"addonReceiptSha256": "Replace with the canonical digest printed by native:hybrid-addon-receipt:verify",
"ccxReceiptSha256": "Replace with the canonical digest printed by native:hybrid-ccx-receipt:verify",
"runs": []
}
@@ -0,0 +1,67 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://premiere-pro-mcp.com/schemas/uxp-hybrid-benchmark-evidence-v1.json",
"title": "Premiere Pro MCP UXP hybrid benchmark evidence v1",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "workloadId", "configuration", "memoryMeasurement", "runs"],
"properties": {
"schemaVersion": { "const": 1 },
"workloadId": { "const": "weighted-energy-v1" },
"configuration": {
"type": "object",
"additionalProperties": false,
"required": ["sampleCount", "warmupCount", "iterations", "inputLength", "seed"],
"properties": {
"sampleCount": { "const": 30 },
"warmupCount": { "const": 3 },
"iterations": { "const": 4 },
"inputLength": { "const": 131072 },
"seed": { "const": 1337 }
}
},
"memoryMeasurement": { "type": "string", "minLength": 1, "maxLength": 256 },
"runs": {
"type": "array",
"minItems": 3,
"maxItems": 3,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"platform", "arch", "hostVersion", "sdkVersion", "buildMode",
"addonLoaded", "addonSha256", "sourceCommit", "checksumMatch",
"codeSigned", "notarized", "javascript", "native"
],
"properties": {
"platform": { "enum": ["win", "mac"] },
"arch": { "enum": ["x64", "arm64"] },
"hostVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
"sdkVersion": { "type": "string", "minLength": 1, "maxLength": 128 },
"buildMode": { "const": "Release" },
"addonLoaded": { "const": true },
"addonSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
"sourceCommit": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
"checksumMatch": { "const": true },
"codeSigned": { "type": "boolean" },
"notarized": { "type": "boolean" },
"javascript": { "$ref": "#/$defs/metrics" },
"native": { "$ref": "#/$defs/metrics" }
}
}
}
},
"$defs": {
"metrics": {
"type": "object",
"additionalProperties": false,
"required": ["sampleCount", "p50Ms", "p95Ms", "peakWorkingSetBytes"],
"properties": {
"sampleCount": { "type": "integer", "minimum": 20 },
"p50Ms": { "type": "number", "exclusiveMinimum": 0 },
"p95Ms": { "type": "number", "exclusiveMinimum": 0 },
"peakWorkingSetBytes": { "type": "integer", "exclusiveMinimum": 0 }
}
}
}
}
@@ -0,0 +1,68 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://premiere-pro-mcp.com/schemas/uxp-hybrid-benchmark-evidence-v2.json",
"title": "Premiere Pro MCP UXP hybrid benchmark evidence v2",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "workloadId", "configuration", "memoryMeasurement", "sdkHeaderReceiptSha256", "runs"],
"properties": {
"schemaVersion": { "const": 2 },
"workloadId": { "const": "weighted-energy-v1" },
"configuration": {
"type": "object",
"additionalProperties": false,
"required": ["sampleCount", "warmupCount", "iterations", "inputLength", "seed"],
"properties": {
"sampleCount": { "const": 30 },
"warmupCount": { "const": 3 },
"iterations": { "const": 4 },
"inputLength": { "const": 131072 },
"seed": { "const": 1337 }
}
},
"memoryMeasurement": { "type": "string", "minLength": 1, "maxLength": 256 },
"sdkHeaderReceiptSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
"runs": {
"type": "array",
"minItems": 3,
"maxItems": 3,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"platform", "arch", "hostVersion", "sdkVersion", "buildMode",
"addonLoaded", "addonSha256", "sourceCommit", "checksumMatch",
"codeSigned", "notarized", "javascript", "native"
],
"properties": {
"platform": { "enum": ["win", "mac"] },
"arch": { "enum": ["x64", "arm64"] },
"hostVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
"sdkVersion": { "type": "string", "minLength": 1, "maxLength": 128 },
"buildMode": { "const": "Release" },
"addonLoaded": { "const": true },
"addonSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
"sourceCommit": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
"checksumMatch": { "const": true },
"codeSigned": { "type": "boolean" },
"notarized": { "type": "boolean" },
"javascript": { "$ref": "#/$defs/metrics" },
"native": { "$ref": "#/$defs/metrics" }
}
}
}
},
"$defs": {
"metrics": {
"type": "object",
"additionalProperties": false,
"required": ["sampleCount", "p50Ms", "p95Ms", "peakWorkingSetBytes"],
"properties": {
"sampleCount": { "type": "integer", "minimum": 20 },
"p50Ms": { "type": "number", "exclusiveMinimum": 0 },
"p95Ms": { "type": "number", "exclusiveMinimum": 0 },
"peakWorkingSetBytes": { "type": "integer", "exclusiveMinimum": 0 }
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionList>
<Extension Id="com.mcp.premiere.bridge.panel">
<HostList>
<Host Name="PPRO" Port="8088"/>
</HostList>
</Extension>
</ExtensionList>
+70
View File
@@ -0,0 +1,70 @@
/**************************************************************************************************
* ADOBE SYSTEMS INCORPORATED
* Copyright 2013 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the
* terms of the Adobe license agreement accompanying it. If you have received this file from a
* source other than Adobe, then your use, modification, or distribution of it requires the prior
* written permission of Adobe.
*
* CSInterface.js - v12.0.0 (minimal shim for MCP Bridge)
* Download the full version from: https://github.com/nicscott9/CSInterface
**************************************************************************************************/
/**
* CSInterface class for Adobe CEP extensions.
* This is a minimal implementation. For production use, download the full
* CSInterface.js from Adobe's GitHub repository.
*/
function CSInterface() {}
/**
* Evaluates an ExtendScript in the host application.
* @param {string} script - The ExtendScript to evaluate.
* @param {function} callback - Callback with the result string.
*/
CSInterface.prototype.evalScript = function (script, callback) {
if (typeof __adobe_cep__ !== "undefined") {
// CEP 9+ requires the callback to be passed directly to __adobe_cep__.evalScript.
// Calling it without a callback causes the result to be silently discarded,
// making every command return null/undefined.
__adobe_cep__.evalScript(script, callback || function () {});
} else {
// Running outside CEP (for testing)
console.warn("[CSInterface] Not running in CEP environment");
if (callback) callback("EvalScript Error: Not in CEP environment");
}
};
/**
* Get the host environment.
*/
CSInterface.prototype.getHostEnvironment = function () {
if (typeof __adobe_cep__ !== "undefined") {
try {
return JSON.parse(__adobe_cep__.getHostEnvironment());
} catch (e) {
return null;
}
}
return null;
};
/**
* Get the system path.
* @param {string} pathType - The path type constant.
*/
CSInterface.prototype.getSystemPath = function (pathType) {
if (typeof __adobe_cep__ !== "undefined") {
return __adobe_cep__.getSystemPath(pathType);
}
return "";
};
// System path constants
CSInterface.prototype.EXTENSION_ID = "extensionId";
// Note: This is a minimal shim. For the full CSInterface.js, download from:
// https://github.com/nicscott9/CSInterface
// and replace this file with the appropriate version for your CEP target.
+79
View File
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.14.9" ExtensionBundleName="MCP for Adobe Premiere Pro">
<ExtensionList>
<Extension Id="com.mcp.premiere.bridge.panel" Version="1.14.9"/>
<Extension Id="com.mcp.premiere.bridge.headless" Version="1.14.9"/>
</ExtensionList>
<ExecutionEnvironment>
<HostList>
<Host Name="PPRO" Version="14.0"/>
</HostList>
<LocaleList>
<Locale Code="All"/>
</LocaleList>
<RequiredRuntimeList>
<RequiredRuntime Name="CSXS" Version="9.0"/>
</RequiredRuntimeList>
</ExecutionEnvironment>
<DispatchInfoList>
<Extension Id="com.mcp.premiere.bridge.panel">
<DispatchInfo>
<Resources>
<MainPath>./index.html</MainPath>
<ScriptPath>./host.jsx</ScriptPath>
<CEFCommandLine>
<Parameter>--allow-file-access-from-files</Parameter>
<Parameter>--enable-nodejs</Parameter>
</CEFCommandLine>
</Resources>
<Lifecycle>
<AutoVisible>true</AutoVisible>
</Lifecycle>
<UI>
<Type>Panel</Type>
<Menu>MCP for Adobe Premiere Pro</Menu>
<Geometry>
<Size>
<Height>300</Height>
<Width>400</Width>
</Size>
<MinSize>
<Height>200</Height>
<Width>300</Width>
</MinSize>
</Geometry>
<Icons/>
</UI>
</DispatchInfo>
</Extension>
<Extension Id="com.mcp.premiere.bridge.headless">
<DispatchInfo>
<Resources>
<MainPath>./index.html</MainPath>
<ScriptPath>./host.jsx</ScriptPath>
<CEFCommandLine>
<Parameter>--allow-file-access-from-files</Parameter>
<Parameter>--enable-nodejs</Parameter>
</CEFCommandLine>
</Resources>
<Lifecycle>
<AutoVisible>false</AutoVisible>
<StartOn>
<Event>com.adobe.csxs.events.ApplicationActivate</Event>
<Event>applicationActivate</Event>
</StartOn>
</Lifecycle>
<UI>
<Type>Custom</Type>
<Geometry>
<Size>
<Height>1</Height>
<Width>1</Width>
</Size>
</Geometry>
<Icons/>
</UI>
</DispatchInfo>
</Extension>
</DispatchInfoList>
</ExtensionManifest>
+7
View File
@@ -0,0 +1,7 @@
// Host-side ExtendScript (runs in Premiere Pro's ExtendScript engine)
// This file can contain ExtendScript helper functions that are always available.
// The main execution happens dynamically via CSInterface.evalScript() from main.js.
function mcpBridgePing() {
return "pong";
}
+108
View File
@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MCP for Adobe Premiere Pro</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main class="panel-shell">
<header class="panel-header">
<div class="brand-mark" aria-hidden="true"><span>M</span></div>
<div class="brand-copy">
<h1>MCP for Adobe Premiere Pro</h1>
<p>Local Premiere connection</p>
</div>
<div class="auto-start"><span></span>Auto-start</div>
</header>
<section class="status-panel" role="status" aria-live="polite" aria-atomic="true">
<div class="status-indicator" aria-hidden="true">
<div class="status-ring"><div class="status-dot waiting" id="statusDot"></div></div>
</div>
<div class="status-copy">
<span class="section-label">Bridge status</span>
<strong id="statusText">Starting connector…</strong>
<span id="statusDetail">Checking Premiere Pro</span>
</div>
<div class="command-stat">
<strong id="cmdCount">0</strong>
<span>commands</span>
</div>
</section>
<section class="connection-center" aria-labelledby="connectionCenterTitle">
<div class="section-heading">
<div>
<span class="section-label">Connection Center</span>
<h2 id="connectionCenterTitle">Ready-to-edit check</h2>
</div>
<button class="save-link" type="button" onclick="refreshConnectionCenter()" aria-controls="connectionChecks">Refresh</button>
</div>
<p class="connection-intro">This panel only checks Premiere. In your AI assistant, run <strong>Verify Premiere connection</strong> for the complete safe check.</p>
<ul class="connection-checks" id="connectionChecks">
<li id="checkConnector" data-state="waiting"><span class="check-dot" aria-hidden="true"></span><span><strong>Connector</strong><small>Starting…</small></span></li>
<li id="checkProject" data-state="waiting"><span class="check-dot" aria-hidden="true"></span><span><strong>Project</strong><small>Checking…</small></span></li>
<li id="checkSequence" data-state="waiting"><span class="check-dot" aria-hidden="true"></span><span><strong>Active sequence</strong><small>Checking…</small></span></li>
</ul>
</section>
<section class="config-section">
<div class="section-heading">
<div>
<span class="section-label">Configuration</span>
<h2>Bridge directory</h2>
</div>
<button class="save-link" id="btnSave" onclick="saveTempDir()" type="button" title="Save bridge directory">
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M4 3.5h9.4L16.5 6v10.5h-13v-13Z"/><path d="M6.5 3.5v5h7v-5M6.5 16.5v-5h7v5"/></svg>
Save
</button>
</div>
<label class="sr-only" for="tempDir">Temporary bridge directory</label>
<div class="path-field">
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M2.5 5.5h5l1.5 2h8.5v8h-15v-10Z"/></svg>
<input type="text" id="tempDir" value="" spellcheck="false" autocomplete="off" aria-describedby="tempDirHelp">
</div>
<p class="field-help" id="tempDirHelp">Commands and responses are exchanged through this local folder.</p>
</section>
<div class="action-row">
<button id="btnStart" class="button button-primary" onclick="startBridge()" type="button" aria-controls="log">
<svg viewBox="0 0 20 20" aria-hidden="true"><path class="fill-icon" d="m7 5 8 5-8 5V5Z"/></svg>
Start Bridge
</button>
<button id="btnStop" class="button button-stop" onclick="stopBridge()" type="button" aria-controls="log" disabled>
<svg viewBox="0 0 20 20" aria-hidden="true"><rect class="fill-icon" x="6" y="6" width="8" height="8" rx="1"/></svg>
Stop
</button>
</div>
<section class="update-section" aria-live="polite">
<div class="update-copy">
<span class="section-label">MCP updates</span>
<strong id="updateTitle">Version 1.14.9</strong>
<span id="updateDetail">Checking the global MCP server and connector release…</span>
</div>
<button id="btnUpdate" class="button button-update" onclick="handleUpdateClick()" type="button" aria-describedby="updateDetail" disabled>
Check again
</button>
</section>
<section class="activity-section">
<div class="section-heading activity-heading">
<div>
<span class="section-label">Live monitor</span>
<h2>Activity</h2>
</div>
<span class="activity-state"><span></span>Listening</span>
</div>
<div id="log" role="log" aria-live="polite" aria-relevant="additions" tabindex="0" aria-label="Bridge activity log"></div>
</section>
</main>
<script src="CSInterface.js"></script>
<script src="updater.cjs"></script>
<script src="main.js"></script>
</body>
</html>
+676
View File
@@ -0,0 +1,676 @@
/* MCP for Adobe Premiere Pro - CEP Plugin Main Script
* Polls a temp directory for command files (.jsx), executes them
* in Premiere Pro's ExtendScript engine, and writes results back. */
var cs = new CSInterface();
var bridgeRunning = false;
var pollInterval = null;
var commandCount = 0;
var tempDir = "";
var POLL_MS = 200;
var HEARTBEAT_MS = 1000;
var heartbeatInterval = null;
// ---- Logging ----
function log(msg, cls) {
var el = document.getElementById("log");
var entry = document.createElement("div");
entry.className = "log-entry " + (cls || "");
var ts = new Date().toLocaleTimeString();
entry.textContent = "[" + ts + "] " + msg;
el.appendChild(entry);
el.scrollTop = el.scrollHeight;
// Keep max 100 entries
while (el.children.length > 100) el.removeChild(el.firstChild);
}
// ---- Status ----
function setStatus(state, text) {
var dot = document.getElementById("statusDot");
dot.className = "status-dot " + state;
var statusText = document.getElementById("statusText");
statusText.textContent = text;
statusText.setAttribute("data-state", state || "stopped");
var detail = document.getElementById("statusDetail");
if (detail) {
if (state === "connected") detail.textContent = "Premiere Pro link is active";
else if (state === "waiting") detail.textContent = "Ready for an AI assistant connection";
else if (state === "error") detail.textContent = "Bridge needs attention";
else detail.textContent = "Waiting for Premiere Pro";
}
}
function setConnectionCheck(id, state, detail) {
var el = document.getElementById(id);
if (!el) return;
el.setAttribute("data-state", state);
var text = el.getElementsByTagName("small")[0];
if (text) text.textContent = detail;
}
// This reads only boolean Premiere state. Do not put project names, paths, or
// media information in the panel: the MCP safe-check uses the same boundary.
function refreshConnectionCenter() {
if (!bridgeRunning) {
setConnectionCheck("checkConnector", "waiting", "Start the connector first");
setConnectionCheck("checkProject", "waiting", "Waiting for the connector");
setConnectionCheck("checkSequence", "waiting", "Waiting for the connector");
return;
}
setConnectionCheck("checkConnector", "ready", "Running in Premiere Pro");
setConnectionCheck("checkProject", "waiting", "Checking…");
setConnectionCheck("checkSequence", "waiting", "Checking…");
cs.evalScript(
'(function(){var p=app&&app.project;return "mcpstate:"+(p&&typeof p.name!=="undefined"?"1":"0")+","+(p&&p.activeSequence?"1":"0");}())',
function (raw) {
var match = /^mcpstate:([01]),([01])$/.exec(String(raw || ""));
if (!match) {
setConnectionCheck("checkProject", "needs-attention", "Could not read Premiere state");
setConnectionCheck("checkSequence", "needs-attention", "Could not read Premiere state");
return;
}
var projectOpen = match[1] === "1";
var sequenceOpen = match[2] === "1";
setConnectionCheck("checkProject", projectOpen ? "ready" : "needs-attention", projectOpen ? "Project open" : "Open a project in Premiere Pro");
setConnectionCheck("checkSequence", sequenceOpen ? "ready" : "needs-attention", sequenceOpen ? "Active sequence open" : "Open a sequence in Premiere Pro");
}
);
}
// ---- File I/O via Node.js (CEP has access to Node) ----
// --enable-nodejs puts `require` in the global scope on most hosts, but on some it
// lands on cep_node instead. Try both, and fail loudly rather than letting fs come
// back undefined and surface later as "Cannot read properties of undefined".
function nodeRequire(moduleName) {
if (typeof require !== "undefined") return require(moduleName);
var cepNode = typeof cep_node !== "undefined" ? cep_node : typeof window !== "undefined" ? window.cep_node : null;
if (cepNode && typeof cepNode.require === "function") return cepNode.require(moduleName);
throw new Error(
'Node.js is not available in this CEP panel, so "' + moduleName + '" could not be loaded. ' +
"Check that CSXS/manifest.xml has <Parameter>--enable-nodejs</Parameter>, then fully quit and reopen Premiere Pro."
);
}
var fs = nodeRequire("fs");
var path = nodeRequire("path");
var os = nodeRequire("os");
var https = nodeRequire("https");
function defaultBridgeDirectory() {
try {
var nodeProcess = nodeRequire("process");
var configured = nodeProcess && nodeProcess.env && nodeProcess.env.PREMIERE_TEMP_DIR;
if (typeof configured === "string" && configured.trim()) return configured.trim();
} catch (e) {
// The panel still has a safe OS temporary-directory fallback.
}
return path.join(os.tmpdir(), "premiere-mcp-bridge");
}
tempDir = defaultBridgeDirectory();
var latestUpdate = null;
var UPDATE_STATUS_STORAGE_KEY = "mcp_bridge_desktop_update_status_path";
var MAX_UPDATE_RESPONSE_BYTES = 64 * 1024;
function getPerUserGlobalInstall() {
try {
var nodeProcess = nodeRequire("process");
var appData = nodeProcess && nodeProcess.env && nodeProcess.env.APPDATA;
if (typeof appData !== "string" || !appData.trim()) return null;
var npmDirectory = path.resolve(appData, "npm");
var commandPath = path.resolve(npmDirectory, "premiere-pro-mcp.cmd");
var packagePath = path.resolve(npmDirectory, "node_modules", "premiere-pro-mcp", "package.json");
var relative = path.relative(npmDirectory, commandPath);
var packageRelative = path.relative(npmDirectory, packagePath);
if (
!relative ||
!packageRelative ||
relative.indexOf(".." + path.sep) === 0 ||
packageRelative.indexOf(".." + path.sep) === 0 ||
path.isAbsolute(relative) ||
path.isAbsolute(packageRelative) ||
!fs.existsSync(commandPath) ||
!fs.existsSync(packagePath)
) return null;
var packageMetadata = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
var serverVersion = MCPBridgeUpdater.normalizeVersion(packageMetadata && packageMetadata.version);
if (!serverVersion) return null;
return { commandPath: commandPath, serverVersion: serverVersion };
} catch (e) {
return null;
}
}
function getPerUserGlobalCommand() {
var install = getPerUserGlobalInstall();
return install ? install.commandPath : null;
}
function saveUpdateStatusPath(statusPath) {
try {
localStorage.setItem(UPDATE_STATUS_STORAGE_KEY, statusPath);
} catch (e) {}
}
function readScheduledUpdateStatus() {
var statusPath = "";
try {
statusPath = localStorage.getItem(UPDATE_STATUS_STORAGE_KEY) || "";
} catch (e) {
return null;
}
if (!statusPath || !path.isAbsolute(statusPath) || !fs.existsSync(statusPath)) return null;
try {
var status = JSON.parse(fs.readFileSync(statusPath, "utf-8"));
var validStates = ["waiting_for_premiere", "updating", "complete", "failed"];
if (
!status ||
status.schemaVersion !== "premiere-pro-mcp.desktop-update.v1" ||
validStates.indexOf(status.state) === -1
) return null;
return status;
} catch (e) {
return null;
}
}
function ensureDir(dir) {
try {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
}
} catch (e) {
log("Error creating dir: " + e.message, "err");
}
}
function listCommandFiles() {
try {
if (!fs.existsSync(tempDir)) return [];
var files = fs.readdirSync(tempDir);
return files
.filter(function (f) { return f.indexOf("cmd_") === 0 && f.slice(-4) === ".jsx"; })
.sort(); // process in order
} catch (e) {
return [];
}
}
function readFile(filePath) {
try {
return fs.readFileSync(filePath, "utf-8");
} catch (e) {
return null;
}
}
function writeFile(filePath, content) {
try {
fs.writeFileSync(filePath, content, "utf-8");
return true;
} catch (e) {
log("Error writing " + filePath + ": " + e.message, "err");
return false;
}
}
// Publish responses atomically so the MCP process never sees a partially-written
// JSON file. The staging suffix is not a response filename the server will read.
function writeResponseFile(filePath, content) {
var stagedPath = filePath + ".staged";
try {
fs.writeFileSync(stagedPath, content, "utf-8");
fs.renameSync(stagedPath, filePath);
return true;
} catch (e) {
deleteFile(stagedPath);
log("Error publishing " + filePath + ": " + e.message, "err");
return false;
}
}
function deleteFile(filePath) {
try {
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
} catch (e) {}
}
// The heartbeat carries only protocol state. It is published by rename so a
// server never observes partial JSON, and an older server can ignore it.
function writeBridgeHeartbeat() {
if (!tempDir) return;
var heartbeatPath = path.join(tempDir, "bridge-heartbeat.json");
var stagedPath = heartbeatPath + "." + ENGINE_ID + ".staged";
try {
fs.writeFileSync(stagedPath, JSON.stringify({
protocolVersion: 1,
state: bridgeRunning ? "running" : "waiting"
}), "utf-8");
fs.renameSync(stagedPath, heartbeatPath);
} catch (e) {
deleteFile(stagedPath);
}
}
function startBridgeHeartbeat() {
if (heartbeatInterval) clearInterval(heartbeatInterval);
writeBridgeHeartbeat();
heartbeatInterval = setInterval(writeBridgeHeartbeat, HEARTBEAT_MS);
}
function stopBridgeHeartbeat() {
if (heartbeatInterval) clearInterval(heartbeatInterval);
heartbeatInterval = null;
// Keep the last heartbeat in place. Its age lets newer servers diagnose a
// stopped connector, while concurrent visible/headless panels stay isolated.
}
// ---- Script Execution ----
function executeScript(script, callback) {
// Script is already wrapped in an IIFE by the MCP server's buildScript(),
// so we pass it directly to avoid double-wrapping.
cs.evalScript(script, function (result) {
callback(result);
});
}
// ---- Command Processing ----
function processCommands() {
if (commandInFlight) return;
var cmdFiles = listCommandFiles();
// Premiere's scripting engine is stateful. Starting every discovered command
// at once lets overlapping edits race each other and overload the host. The
// atomic claim below still prevents duplicate work across the visible and
// headless panels, while this panel dispatches strictly one command at a time.
if (cmdFiles.length > 0) processOneCommand(cmdFiles[0]);
}
// Both the visible panel and the headless auto-start instance run this file.
// A rename is atomic on the same volume, so whichever engine renames first owns
// the command; the loser's rename throws and it skips the file.
var ENGINE_ID = Math.random().toString(36).slice(2, 8);
var commandInFlight = false;
function processOneCommand(cmdFileName) {
var cmdFilePath = path.join(tempDir, cmdFileName);
var claimPath = cmdFilePath + "." + ENGINE_ID + ".claimed";
try {
fs.renameSync(cmdFilePath, claimPath);
} catch (e) {
return; // another engine claimed this command
}
var script = readFile(claimPath);
deleteFile(claimPath);
if (!script) {
log("Failed to read: " + cmdFileName, "err");
return;
}
commandInFlight = true;
// Derive response filename: cmd_12345.jsx -> res_12345.json
var id = cmdFileName.replace("cmd_", "").replace(".jsx", "");
var resFilePath = path.join(tempDir, "res_" + id + ".json");
log("Executing: " + cmdFileName + " (" + script.length + " chars)", "cmd");
// While evalScript is in flight, heartbeat a busy file so the MCP server can
// tell "script still running (modal dialog?)" apart from "plugin not running".
// Only starts after 2s, so fast commands never touch the extra file.
var busyFilePath = path.join(tempDir, "busy_" + id + ".json");
var startedAt = new Date().getTime();
var busyTimer = setInterval(function () {
writeFile(busyFilePath, '{"id":"' + id + '","elapsedMs":' + (new Date().getTime() - startedAt) + "}");
}, 2000);
executeScript(script, function (result) {
clearInterval(busyTimer);
deleteFile(busyFilePath);
commandCount++;
document.getElementById("cmdCount").textContent = commandCount;
var response;
try {
// ExtendScript returns a string; try to parse it as JSON
if (result && result !== "undefined" && result !== "null") {
// Check if it's already valid JSON
var parsed = JSON.parse(result);
response = JSON.stringify(parsed);
log("Result: OK", "ok");
} else {
// An empty result means evalScript gave us nothing back. That is a bridge
// failure, not a successful command with no data — reporting it as "OK" is
// what made this so hard to diagnose. Say so.
response = JSON.stringify({
success: false,
error:
"The bridge received an empty result from evalScript (got " +
(typeof result) +
"). The script may not have run. If every command does this, the CEP panel is stale — " +
"close and reopen it (a reload is not enough), or reinstall the extension.",
});
log("Result: EMPTY — evalScript returned nothing (see response file)", "err");
}
} catch (e) {
// If result isn't JSON, wrap it
if (result && result.indexOf("Error") === 0) {
response = JSON.stringify({ success: false, error: result });
log("Result: " + result, "err");
} else {
response = JSON.stringify({ success: true, data: result });
log("Result: OK (raw)", "ok");
}
}
writeResponseFile(resFilePath, response);
commandInFlight = false;
// Continue without waiting for the next poll interval, preserving FIFO
// ordering while minimizing queue handoff latency.
if (bridgeRunning) processCommands();
});
}
// ---- Bridge Control ----
function startBridge() {
tempDir = document.getElementById("tempDir").value.trim();
if (!tempDir) {
log("Please set a temp directory", "err");
document.getElementById("tempDir").focus();
return;
}
ensureDir(tempDir);
bridgeRunning = true;
startBridgeHeartbeat();
setStatus("waiting", "Connector running");
log("Connector started and ready for safe checks.", "ok");
document.getElementById("btnStart").disabled = true;
document.getElementById("btnStop").disabled = false;
refreshConnectionCenter();
pollInterval = setInterval(function () {
if (bridgeRunning) processCommands();
}, POLL_MS);
}
function stopBridge() {
bridgeRunning = false;
writeBridgeHeartbeat();
stopBridgeHeartbeat();
if (pollInterval) clearInterval(pollInterval);
pollInterval = null;
setStatus("", "Stopped");
refreshConnectionCenter();
log("Bridge stopped");
document.getElementById("btnStart").disabled = false;
document.getElementById("btnStop").disabled = true;
}
function saveTempDir() {
tempDir = document.getElementById("tempDir").value.trim();
log("Temp directory saved: " + tempDir);
// Persist via localStorage
try {
localStorage.setItem("mcp_bridge_temp_dir", tempDir);
} catch (e) {}
}
// ---- Connector Updates ----
function setUpdateUI(title, detail, buttonText, disabled) {
document.getElementById("updateTitle").textContent = title;
document.getElementById("updateDetail").textContent = detail;
var button = document.getElementById("btnUpdate");
button.textContent = buttonText;
button.disabled = !!disabled;
}
function updateInstructionUrl() {
return MCPBridgeUpdater.RELEASES_URL;
}
function openTrustedUpdateInstructions() {
var url = updateInstructionUrl();
if (!MCPBridgeUpdater.isTrustedDownloadUrl(url)) {
showUpdateCheckError("The update instructions link was not trusted.");
return;
}
try {
var childProcess = nodeRequire("child_process");
var command =
os.platform() === "win32"
? ["cmd.exe", ["/d", "/s", "/c", "start", "", url]]
: ["open", [url]];
var child = childProcess.spawn(command[0], command[1], {
detached: true,
stdio: "ignore",
});
child.unref();
} catch (e) {
showUpdateCheckError("Could not open the update instructions. Try again.");
}
}
function restoreScheduledUpdateStatus() {
var status = readScheduledUpdateStatus();
if (!status) return false;
if (status.state === "complete") {
setUpdateUI(
"Update complete",
"Restart your MCP client, then use Verify Premiere connection before editing.",
"Check again",
false
);
return true;
}
if (status.state === "failed") {
setUpdateUI(
"Update needs attention",
"Nothing was changed in your projects. Check the update command or retry after Premiere closes.",
"Check again",
false
);
return true;
}
setUpdateUI(
"Update scheduled",
status.state === "updating"
? "The global MCP server and connector are being updated. Keep Premiere closed."
: "Quit Premiere Pro. The updater will begin after it fully closes.",
"Scheduled",
true
);
return true;
}
function checkForUpdates() {
latestUpdate = null;
var globalInstall = os.platform() === "win32" ? getPerUserGlobalInstall() : null;
var responseTooLarge = false;
setUpdateUI(
"Version " + MCPBridgeUpdater.CURRENT_VERSION,
"Checking for updates…",
"Checking…",
true
);
var request = https.get(
MCPBridgeUpdater.LATEST_PACKAGE_API,
{
headers: {
Accept: "application/vnd.npm.install-v1+json",
"User-Agent": "premiere-pro-mcp-connector/" + MCPBridgeUpdater.CURRENT_VERSION,
},
},
function (response) {
var body = "";
response.setEncoding("utf8");
response.on("data", function (chunk) {
if (body.length + chunk.length > MAX_UPDATE_RESPONSE_BYTES) {
responseTooLarge = true;
request.destroy(new Error("npm registry update record was unexpectedly large."));
return;
}
body += chunk;
});
response.on("end", function () {
if (responseTooLarge) return;
if (response.statusCode !== 200) {
showUpdateCheckError("Could not check npm (HTTP " + response.statusCode + ").");
return;
}
try {
var update = MCPBridgeUpdater.updateStateFromPackageRecord(
MCPBridgeUpdater.CURRENT_VERSION,
JSON.parse(body)
);
var serverUpdateAvailable = Boolean(
globalInstall &&
MCPBridgeUpdater.compareVersions(update.latestVersion, globalInstall.serverVersion) > 0
);
var needsUpdate = update.updateAvailable || serverUpdateAvailable;
if (needsUpdate) {
latestUpdate = {
version: update.latestVersion,
};
if (os.platform() === "win32" && globalInstall) {
var versionSummary =
"Server " + globalInstall.serverVersion + ", connector " + MCPBridgeUpdater.CURRENT_VERSION + ". ";
setUpdateUI(
"Version " + update.latestVersion + " is available",
versionSummary + "Update both together after you close Premiere.",
"Update after quit",
false
);
} else if (os.platform() === "win32") {
setUpdateUI(
"Version " + update.latestVersion + " is available",
"A global npm install was not found. This panel will not modify a source checkout.",
"Open instructions",
false
);
} else {
setUpdateUI(
"Version " + update.latestVersion + " is available",
"Open the matching release, then update your local server using the documented install path.",
"Open instructions",
false
);
}
} else {
var currentDetail = globalInstall
? "Server " + globalInstall.serverVersion + " and connector " + MCPBridgeUpdater.CURRENT_VERSION + " are current."
: "Your connector release is current. This check does not alter your projects or MCP client configuration.";
setUpdateUI(
"Version " + MCPBridgeUpdater.CURRENT_VERSION,
currentDetail,
"Check again",
false
);
}
} catch (e) {
showUpdateCheckError("npm returned an unreadable package record.");
}
});
}
);
request.setTimeout(10000, function () {
request.destroy(new Error("Update check timed out"));
});
request.on("error", function () {
showUpdateCheckError(
responseTooLarge ? "npm returned an unexpectedly large package record." : "Unable to check while offline."
);
});
}
function showUpdateCheckError(message) {
setUpdateUI(
"Version " + MCPBridgeUpdater.CURRENT_VERSION,
message,
"Check again",
false
);
}
function handleUpdateClick() {
if (!latestUpdate) {
checkForUpdates();
return;
}
if (os.platform() !== "win32") {
openTrustedUpdateInstructions();
return;
}
var cliPath = getPerUserGlobalCommand();
if (!cliPath) {
openTrustedUpdateInstructions();
return;
}
var confirmation =
"Update Premiere MCP to " + latestUpdate.version + " after Premiere Pro fully closes?\n\n" +
"This updates only the per-user global MCP server and its connector. " +
"It does not change your projects or MCP client configuration, and it will not force Premiere to close.";
if (typeof window.confirm === "function" && !window.confirm(confirmation)) return;
try {
var childProcess = nodeRequire("child_process");
var nodeCrypto = nodeRequire("crypto");
var scheduled = MCPBridgeUpdater.scheduleWindowsGlobalUpdate({
cliPath: cliPath,
runtime: {
fs: fs,
path: path,
os: os,
childProcess: childProcess,
crypto: nodeCrypto,
},
});
saveUpdateStatusPath(scheduled.statusPath);
setUpdateUI(
"Update scheduled",
"Quit Premiere Pro. The updater will refresh the global server and connector after it fully closes.",
"Scheduled",
true
);
} catch (e) {
showUpdateCheckError("Could not schedule the local update. No files were changed.");
}
}
// ---- Init ----
(function init() {
// Set the default temp dir in the input field
document.getElementById("tempDir").value = tempDir;
// Restore saved temp dir
try {
var saved = localStorage.getItem("mcp_bridge_temp_dir");
if (saved) {
tempDir = saved;
document.getElementById("tempDir").value = tempDir;
}
} catch (e) {}
log("MCP for Adobe Premiere Pro CEP connector loaded");
setStatus("waiting", "Ready — click Start Bridge");
// Always auto-start. The headless instance (StartOn ApplicationActivate) has no
// one to click Start, and macOS periodically purges the temp dir — so create it
// rather than gating auto-start on its existence.
ensureDir(tempDir);
startBridgeHeartbeat();
log("Auto-starting bridge...");
setTimeout(startBridge, 500);
if (!restoreScheduledUpdateStatus()) setTimeout(checkForUpdates, 1200);
})();
+284
View File
@@ -0,0 +1,284 @@
:root {
--bg: #151516;
--surface: #1c1c1f;
--surface-raised: #222226;
--surface-deep: #111113;
--border: #35353a;
--border-strong: #494950;
--text: #f2f1f4;
--text-secondary: #adabb3;
--text-muted: #74727b;
--violet: #9b6cff;
--violet-hover: #ad87ff;
--violet-soft: rgba(155, 108, 255, 0.12);
--green: #70d987;
--green-soft: rgba(112, 217, 135, 0.1);
--red: #ff6565;
--amber: #e9b85d;
--radius: 7px;
--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
--font-mono: "Cascadia Mono", "SFMono-Regular", Consolas, monospace;
}
* { box-sizing: border-box; }
html, body { width: 100%; min-width: 280px; height: 100%; margin: 0; }
body {
overflow: hidden;
background: var(--bg);
color: var(--text);
font-family: var(--font-ui);
font-size: 12px;
-webkit-font-smoothing: antialiased;
user-select: none;
}
button, input { font: inherit; }
button { -webkit-appearance: none; }
.panel-shell {
display: flex;
flex-direction: column;
height: 100%;
min-height: 420px;
padding: 0 14px 14px;
}
.panel-header {
display: flex;
align-items: center;
min-height: 66px;
margin: 0 -14px 14px;
padding: 12px 14px;
border-bottom: 1px solid var(--border);
background: #19191b;
}
.brand-mark {
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
margin-right: 10px;
border: 1px solid #aa83ff;
border-radius: 8px;
background: var(--violet-soft);
color: #cbb8ff;
font-size: 15px;
font-weight: 750;
letter-spacing: -0.04em;
}
.brand-copy { min-width: 0; }
.brand-copy h1 { margin: 0; font-size: 14px; line-height: 1.25; font-weight: 650; letter-spacing: .01em; }
.brand-copy p { margin: 3px 0 0; color: var(--text-muted); font-size: 10px; }
.auto-start {
display: flex;
align-items: center;
gap: 6px;
margin-left: auto;
color: var(--text-muted);
font-size: 10px;
}
.auto-start > span { width: 5px; height: 5px; border-radius: 50%; background: var(--violet); }
.status-panel {
display: grid;
grid-template-columns: 44px minmax(0, 1fr) auto;
align-items: center;
min-height: 88px;
padding: 14px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
}
.status-indicator { display: flex; align-items: center; }
.status-ring {
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border: 1px solid var(--border-strong);
border-radius: 50%;
background: var(--surface-deep);
}
.status-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--text-muted); transition: background .2s, box-shadow .2s; }
.status-dot.connected { background: var(--green); box-shadow: 0 0 0 5px var(--green-soft); animation: breathe 2.4s ease-in-out infinite; }
.status-dot.error { background: var(--red); box-shadow: 0 0 0 5px rgba(255, 101, 101, .1); }
.status-dot.waiting { background: var(--amber); box-shadow: 0 0 0 5px rgba(233, 184, 93, .1); }
@keyframes breathe { 0%, 100% { box-shadow: 0 0 0 4px var(--green-soft); } 50% { box-shadow: 0 0 0 7px rgba(112, 217, 135, .04); } }
.section-label { display: block; margin-bottom: 4px; color: var(--text-muted); font-size: 9px; font-weight: 650; letter-spacing: .09em; text-transform: uppercase; }
.status-copy { min-width: 0; }
.status-copy strong { display: block; overflow: hidden; color: var(--text); font-size: 14px; font-weight: 650; line-height: 1.3; text-overflow: ellipsis; white-space: nowrap; }
.status-copy strong::before { content: "■ "; color: var(--text-muted); }
.status-copy strong[data-state="connected"]::before { content: "✓ "; color: var(--green); }
.status-copy strong[data-state="waiting"]::before { content: "… "; color: var(--amber); }
.status-copy strong[data-state="error"]::before { content: "! "; color: var(--red); }
.status-copy #statusDetail { display: block; overflow: hidden; margin-top: 3px; color: var(--text-secondary); font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
.command-stat { padding-left: 14px; text-align: right; }
.command-stat strong { display: block; font: 600 20px/1 var(--font-mono); }
.command-stat span { display: block; margin-top: 5px; color: var(--text-muted); font-size: 9px; }
.config-section { padding: 19px 0 15px; border-bottom: 1px solid var(--border); }
.section-heading { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: 9px; }
.section-heading h2 { margin: 0; font-size: 12px; font-weight: 600; }
.save-link {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 0 4px 8px;
border: 0;
background: transparent;
color: var(--violet);
cursor: pointer;
font-size: 10px;
}
.save-link svg, .path-field svg, .button svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.5; }
.save-link:hover { color: var(--violet-hover); }
.path-field { position: relative; display: flex; align-items: center; }
.path-field > svg { position: absolute; left: 10px; color: var(--text-muted); pointer-events: none; }
.path-field input {
width: 100%;
height: 36px;
padding: 0 10px 0 32px;
border: 1px solid var(--border);
border-radius: 5px;
outline: none;
background: var(--surface-deep);
color: var(--text-secondary);
font: 10px var(--font-mono);
user-select: text;
transition: border-color .15s, background .15s;
}
.path-field input:hover { border-color: var(--border-strong); }
.path-field input:focus { border-color: var(--violet); background: #141318; color: var(--text); box-shadow: 0 0 0 2px var(--violet-soft); }
.field-help { margin: 7px 0 0; color: var(--text-muted); font-size: 9px; line-height: 1.45; }
.action-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(84px, .65fr); gap: 8px; padding: 14px 0; }
.button {
display: flex;
align-items: center;
justify-content: center;
gap: 7px;
min-width: 0;
height: 34px;
border: 1px solid transparent;
border-radius: 5px;
color: var(--text);
cursor: pointer;
font-size: 11px;
font-weight: 600;
transition: background .15s, border-color .15s, color .15s;
}
.button svg .fill-icon { fill: currentColor; stroke: none; }
.button-primary { background: var(--violet); color: #110d19; }
.button-primary:hover { background: var(--violet-hover); }
.button-stop { border-color: #6b3c3f; background: transparent; color: #ff8b8b; }
.button-stop:hover { border-color: var(--red); background: rgba(255, 101, 101, .08); }
.button:disabled { border-color: var(--border); background: var(--surface); color: #5f5e65; cursor: default; }
.update-section {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 14px;
padding: 11px 12px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
}
.connection-center {
margin: 0 0 14px;
padding: 12px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
}
.connection-center .section-heading { margin-bottom: 8px; }
.connection-center h2 { font-size: 12px; }
.connection-intro { margin: 0 0 10px; color: var(--text-muted); font-size: 9px; line-height: 1.45; }
.connection-intro strong { color: var(--text-secondary); font-weight: 600; }
.connection-checks { display: grid; gap: 6px; padding: 0; margin: 0; list-style: none; }
.connection-checks li { display: flex; align-items: center; gap: 8px; min-height: 34px; padding: 6px 8px; border: 1px solid var(--border); border-radius: 5px; background: var(--surface-deep); }
.connection-checks li > span:last-child { min-width: 0; }
.connection-checks strong, .connection-checks small { display: block; }
.connection-checks strong { color: var(--text-secondary); font-size: 10px; font-weight: 600; }
.connection-checks small { margin-top: 2px; color: var(--text-muted); font-size: 9px; }
.check-dot { width: 7px; height: 7px; flex: 0 0 auto; border-radius: 50%; background: var(--text-muted); }
.connection-checks li[data-state="ready"] .check-dot { background: var(--green); box-shadow: 0 0 0 3px var(--green-soft); }
.connection-checks li[data-state="needs-attention"] .check-dot { background: var(--amber); }
.update-copy { min-width: 0; flex: 1; }
.update-copy strong, .update-copy > span:last-child { display: block; }
.update-copy strong { font-size: 11px; font-weight: 600; }
.update-copy > span:last-child { margin-top: 3px; color: var(--text-muted); font-size: 9px; line-height: 1.4; }
.button-update {
width: auto;
min-width: 92px;
height: 30px;
padding: 0 10px;
border-color: var(--border-strong);
background: var(--surface-raised);
color: var(--violet-hover);
white-space: nowrap;
}
.button-update:hover { border-color: var(--violet); background: var(--violet-soft); }
.activity-section { display: flex; flex: 1; min-height: 120px; flex-direction: column; }
.activity-heading { align-items: center; margin: 2px 0 8px; }
.activity-state { display: flex; align-items: center; gap: 6px; color: var(--text-muted); font-size: 9px; }
.activity-state > span { width: 5px; height: 5px; border-radius: 50%; background: var(--violet); }
#log {
flex: 1;
min-height: 100px;
overflow-y: auto;
padding: 10px 11px;
border: 1px solid var(--border);
border-radius: 5px;
background: var(--surface-deep);
font: 10px/1.65 var(--font-mono);
user-select: text;
}
#log:empty::before { content: "Waiting for activity..."; color: var(--text-muted); }
#log::-webkit-scrollbar { width: 5px; }
#log::-webkit-scrollbar-thumb { border-radius: 3px; background: var(--border-strong); }
.log-entry { color: var(--text-muted); animation: log-in .18s ease-out; }
.log-entry.cmd { color: #b89cff; }
.log-entry.ok { color: var(--green); }
.log-entry.err { color: #ff8585; }
@keyframes log-in { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: none; } }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
button:focus-visible, input:focus-visible, #log:focus-visible { outline: 2px solid var(--violet-hover); outline-offset: 2px; }
@media (max-width: 330px) {
.panel-shell { padding-right: 10px; padding-left: 10px; }
.panel-header { margin-right: -10px; margin-left: -10px; padding-right: 10px; padding-left: 10px; }
.auto-start { display: none; }
.status-panel { grid-template-columns: 38px minmax(0, 1fr); padding: 12px; }
.command-stat { grid-column: 2; padding: 8px 0 0; text-align: left; }
.command-stat strong, .command-stat span { display: inline; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
}
@media (forced-colors: active) {
.status-dot, .auto-start > span, .activity-state > span { forced-color-adjust: none; border: 1px solid CanvasText; }
.button, .path-field input, #log, .status-panel { border-color: CanvasText; }
.status-copy strong::before { color: CanvasText !important; }
}
+204
View File
@@ -0,0 +1,204 @@
/* MCP Bridge update helpers. Kept dependency-free for the older Chromium
* runtime embedded in CEP. */
(function (root, factory) {
var api = factory();
if (typeof module === "object" && module.exports) module.exports = api;
root.MCPBridgeUpdater = api;
})(this, function () {
"use strict";
var CURRENT_VERSION = "1.14.9";
var PACKAGE_NAME = "premiere-pro-mcp";
var LATEST_PACKAGE_API = "https://registry.npmjs.org/" + PACKAGE_NAME;
var LATEST_RELEASE_API =
"https://api.github.com/repos/leancoderkavy/premiere-pro-mcp/releases/latest";
var RELEASES_URL =
"https://github.com/leancoderkavy/premiere-pro-mcp/releases/latest";
function normalizeVersion(value) {
return String(value || "")
.trim()
.replace(/^v/i, "")
.split("-")[0];
}
function compareVersions(left, right) {
var a = normalizeVersion(left).split(".");
var b = normalizeVersion(right).split(".");
var length = Math.max(a.length, b.length);
for (var i = 0; i < length; i++) {
var aPart = parseInt(a[i] || "0", 10);
var bPart = parseInt(b[i] || "0", 10);
if (aPart > bPart) return 1;
if (aPart < bPart) return -1;
}
return 0;
}
function latestPackageVersion(record) {
if (!record || typeof record !== "object") {
throw new Error("The npm registry returned an invalid package record.");
}
var tags = record["dist-tags"];
var latest = tags && tags.latest;
var version = normalizeVersion(latest);
if (!version || !/^\d+\.\d+\.\d+$/.test(version)) {
throw new Error("The npm registry did not provide a valid latest version.");
}
return version;
}
function updateStateFromPackageRecord(currentVersion, record) {
var current = normalizeVersion(currentVersion);
if (!current || !/^\d+\.\d+\.\d+$/.test(current)) {
throw new Error("The installed connector version is invalid.");
}
var latest = latestPackageVersion(record);
return {
currentVersion: current,
latestVersion: latest,
updateAvailable: compareVersions(latest, current) > 0,
};
}
function chooseDownloadUrl(release) {
var assets = release && release.assets ? release.assets : [];
var preferredNames = [
/^MCPBridgeCEP(?:-[\w.-]+)?\.zxp$/i,
/premiere.*(?:connector|bridge).*\.zxp$/i,
/\.zxp$/i,
/premiere.*(?:connector|bridge).*\.(?:zip|dmg|exe)$/i,
];
for (var p = 0; p < preferredNames.length; p++) {
for (var i = 0; i < assets.length; i++) {
if (
preferredNames[p].test(assets[i].name || "") &&
isTrustedDownloadUrl(assets[i].browser_download_url)
) {
return assets[i].browser_download_url;
}
}
}
return isTrustedDownloadUrl(release && release.html_url)
? release.html_url
: RELEASES_URL;
}
function isTrustedDownloadUrl(value) {
return /^https:\/\/(?:github\.com|api\.github\.com|objects\.githubusercontent\.com)\//i.test(
String(value || "")
);
}
function powerShellLiteral(value) {
return "'" + String(value).replace(/'/g, "''") + "'";
}
function randomSuffix(runtime) {
if (runtime.crypto && typeof runtime.crypto.randomBytes === "function") {
return runtime.crypto.randomBytes(12).toString("hex");
}
return String(new Date().getTime()) + "-" + String(Math.random()).slice(2);
}
/**
* The CEP panel cannot replace its own files safely while Premiere is running.
* This small, detached helper waits for Premiere to close, then invokes the
* already-installed per-user npm command. It does not receive project data,
* MCP configuration, or credentials, and it never force-quits Premiere.
*/
function buildWindowsGlobalUpdateScript(cliPath, statusPath, scriptPath) {
return [
"$ErrorActionPreference = 'Stop'",
"$cliPath = " + powerShellLiteral(cliPath),
"$statusPath = " + powerShellLiteral(statusPath),
"$scriptPath = " + powerShellLiteral(scriptPath),
"function Write-UpdateStatus([string]$state) {",
" $payload = @{ schemaVersion = 'premiere-pro-mcp.desktop-update.v1'; state = $state; updatedAt = [DateTime]::UtcNow.ToString('o') } | ConvertTo-Json -Compress",
" [System.IO.File]::WriteAllText($statusPath, $payload, [System.Text.UTF8Encoding]::new($false))",
"}",
"try {",
" Write-UpdateStatus 'waiting_for_premiere'",
" $premiereProcesses = @('Adobe Premiere Pro', 'Adobe Premiere Pro Beta')",
" while (Get-Process -Name $premiereProcesses -ErrorAction SilentlyContinue) { Start-Sleep -Seconds 2 }",
" Write-UpdateStatus 'updating'",
" $npmCommand = (Get-Command npm.cmd -ErrorAction Stop).Source",
" & $npmCommand install --global 'premiere-pro-mcp@latest'",
" if ($LASTEXITCODE -ne 0) { throw 'npm could not install the latest Premiere MCP package.' }",
" & $cliPath --install-cep",
" if ($LASTEXITCODE -ne 0) { throw 'The refreshed Premiere MCP package could not install its connector.' }",
" Write-UpdateStatus 'complete'",
"} catch {",
" Write-UpdateStatus 'failed'",
" exit 1",
"} finally {",
" Remove-Item -LiteralPath $scriptPath -Force -ErrorAction SilentlyContinue",
"}",
"",
].join("\r\n");
}
function scheduleWindowsGlobalUpdate(options) {
if (!options || !options.runtime) throw new Error("A local updater runtime is required.");
var runtime = options.runtime;
var fs = runtime.fs;
var path = runtime.path;
var os = runtime.os;
var childProcess = runtime.childProcess;
if (!fs || !path || !os || !childProcess) {
throw new Error("The local updater runtime is unavailable.");
}
var cliPath = String(options.cliPath || "");
if (!cliPath || typeof path.isAbsolute !== "function" || !path.isAbsolute(cliPath)) {
throw new Error("The per-user Premiere MCP command could not be resolved.");
}
if (typeof fs.existsSync === "function" && !fs.existsSync(cliPath)) {
throw new Error("The per-user Premiere MCP command is not installed.");
}
var updateDirectory = String(options.updateDirectory || os.tmpdir());
if (!updateDirectory || typeof path.isAbsolute !== "function" || !path.isAbsolute(updateDirectory)) {
throw new Error("The local update directory is unavailable.");
}
if (typeof fs.mkdirSync === "function") fs.mkdirSync(updateDirectory, { recursive: true, mode: 0o700 });
var suffix = randomSuffix(runtime);
var statusPath = path.join(updateDirectory, "premiere-pro-mcp-update-" + suffix + ".json");
var scriptPath = path.join(updateDirectory, "premiere-pro-mcp-update-" + suffix + ".ps1");
var script = buildWindowsGlobalUpdateScript(cliPath, statusPath, scriptPath);
fs.writeFileSync(scriptPath, script, { encoding: "utf8", mode: 0o600, flag: "wx" });
try {
var child = childProcess.spawn(
"powershell.exe",
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", scriptPath],
{ detached: true, windowsHide: true, stdio: "ignore" }
);
if (!child || typeof child.unref !== "function") {
throw new Error("The local updater could not be started.");
}
child.unref();
return { statusPath: statusPath };
} catch (error) {
try { fs.unlinkSync(scriptPath); } catch (cleanupError) {}
throw error;
}
}
return {
CURRENT_VERSION: CURRENT_VERSION,
PACKAGE_NAME: PACKAGE_NAME,
LATEST_PACKAGE_API: LATEST_PACKAGE_API,
LATEST_RELEASE_API: LATEST_RELEASE_API,
RELEASES_URL: RELEASES_URL,
normalizeVersion: normalizeVersion,
compareVersions: compareVersions,
latestPackageVersion: latestPackageVersion,
updateStateFromPackageRecord: updateStateFromPackageRecord,
chooseDownloadUrl: chooseDownloadUrl,
isTrustedDownloadUrl: isTrustedDownloadUrl,
buildWindowsGlobalUpdateScript: buildWindowsGlobalUpdateScript,
scheduleWindowsGlobalUpdate: scheduleWindowsGlobalUpdate,
};
});
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionList>
<Extension Id="com.ppro.ai.chat.panel">
<HostList>
<Host Name="PPRO" Port="8098"/>
</HostList>
</Extension>
</ExtensionList>
+75
View File
@@ -0,0 +1,75 @@
/**************************************************************************************************
* ADOBE SYSTEMS INCORPORATED
* Copyright 2013 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the
* terms of the Adobe license agreement accompanying it. If you have received this file from a
* source other than Adobe, then your use, modification, or distribution of it requires the prior
* written permission of Adobe.
*
* CSInterface.js - v12.0.0 (minimal shim for MCP Bridge)
* Download the full version from: https://github.com/nicscott9/CSInterface
**************************************************************************************************/
/**
* CSInterface class for Adobe CEP extensions.
* This is a minimal implementation. For production use, download the full
* CSInterface.js from Adobe's GitHub repository.
*/
function CSInterface() {}
/**
* Evaluates an ExtendScript in the host application.
* @param {string} script - The ExtendScript to evaluate.
* @param {function} callback - Callback with the result string.
*/
CSInterface.prototype.evalScript = function (script, callback) {
if (typeof __adobe_cep__ !== "undefined") {
var result = __adobe_cep__.evalScript(script);
if (callback) {
// CSInterface v9+ uses async callback
if (typeof result === "undefined" || result === "undefined") {
// v9+ path: callback is registered and called asynchronously
// The __adobe_cep__.evalScript already handles the callback via internal mechanism
}
callback(result);
}
} else {
// Running outside CEP (for testing)
console.warn("[CSInterface] Not running in CEP environment");
if (callback) callback("EvalScript Error: Not in CEP environment");
}
};
/**
* Get the host environment.
*/
CSInterface.prototype.getHostEnvironment = function () {
if (typeof __adobe_cep__ !== "undefined") {
try {
return JSON.parse(__adobe_cep__.getHostEnvironment());
} catch (e) {
return null;
}
}
return null;
};
/**
* Get the system path.
* @param {string} pathType - The path type constant.
*/
CSInterface.prototype.getSystemPath = function (pathType) {
if (typeof __adobe_cep__ !== "undefined") {
return __adobe_cep__.getSystemPath(pathType);
}
return "";
};
// System path constants
CSInterface.prototype.EXTENSION_ID = "extensionId";
// Note: This is a minimal shim. For the full CSInterface.js, download from:
// https://github.com/nicscott9/CSInterface
// and replace this file with the appropriate version for your CEP target.
+53
View File
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionManifest Version="7.0" ExtensionBundleId="com.ppro.ai.chat" ExtensionBundleVersion="1.0.0" ExtensionBundleName="Premiere Pro AI Chat">
<ExtensionList>
<Extension Id="com.ppro.ai.chat.panel" Version="1.0.0"/>
</ExtensionList>
<ExecutionEnvironment>
<HostList>
<Host Name="PPRO" Version="[14.0,99.9]"/>
</HostList>
<LocaleList>
<Locale Code="All"/>
</LocaleList>
<RequiredRuntimeList>
<RequiredRuntime Name="CSXS" Version="9.0"/>
</RequiredRuntimeList>
</ExecutionEnvironment>
<DispatchInfoList>
<Extension Id="com.ppro.ai.chat.panel">
<DispatchInfo>
<Resources>
<MainPath>./index.html</MainPath>
<ScriptPath>./host.jsx</ScriptPath>
<CEFCommandLine>
<Parameter>--allow-file-access-from-files</Parameter>
<Parameter>--mixed-context</Parameter>
</CEFCommandLine>
</Resources>
<Lifecycle>
<AutoVisible>true</AutoVisible>
</Lifecycle>
<UI>
<Type>Panel</Type>
<Menu>AI Chat</Menu>
<Geometry>
<Size>
<Height>600</Height>
<Width>420</Width>
</Size>
<MinSize>
<Height>400</Height>
<Width>320</Width>
</MinSize>
<MaxSize>
<Height>2000</Height>
<Width>1200</Width>
</MaxSize>
</Geometry>
<Icons/>
</UI>
</DispatchInfo>
</Extension>
</DispatchInfoList>
</ExtensionManifest>

Some files were not shown because too many files have changed in this diff Show More