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:
@@ -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.
|
||||||
@@ -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"
|
||||||
@@ -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.
|
||||||
@@ -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"
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "TDD"
|
||||||
|
short_description: "Test-driven red-green-refactor"
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
```
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Write|Edit",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "\"$CLAUDE_PROJECT_DIR/rag/reindex_hook.sh\"",
|
||||||
|
"async": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -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
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"premiere-pro": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "node",
|
||||||
|
"args": [
|
||||||
|
"/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/dist/index.js"
|
||||||
|
],
|
||||||
|
"env": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)...
|
||||||
@@ -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
@@ -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.
|
||||||
@@ -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).
|
||||||
Executable
+81
@@ -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."
|
||||||
Executable
+217
@@ -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."
|
||||||
@@ -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`).
|
||||||
Executable
+127
@@ -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"
|
||||||
Executable
+128
@@ -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
|
||||||
Executable
+88
@@ -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 "============================================================"
|
||||||
Executable
+110
@@ -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
|
||||||
Executable
+57
@@ -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"
|
||||||
Executable
+99
@@ -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
|
||||||
Executable
+155
@@ -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
|
||||||
Executable
+146
@@ -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()
|
||||||
@@ -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()
|
||||||
Executable
+112
@@ -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
|
||||||
Executable
+67
@@ -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"
|
||||||
Executable
+161
@@ -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.
|
||||||
@@ -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
@@ -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.
|
||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
selected_org: tradewink
|
||||||
+47
@@ -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
|
||||||
@@ -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"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Executable
+9
@@ -0,0 +1,9 @@
|
|||||||
|
.git
|
||||||
|
.github
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
coverage
|
||||||
|
landing/node_modules
|
||||||
|
landing/.next
|
||||||
|
landing/out
|
||||||
|
*.log
|
||||||
+42
@@ -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.
|
||||||
+42
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
Executable
+38
@@ -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
|
||||||
Executable
+26
@@ -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
|
||||||
Executable
+894
@@ -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
|
||||||
Executable
+73
@@ -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).
|
||||||
Executable
+153
@@ -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.
|
||||||
Executable
+48
@@ -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"]
|
||||||
Executable
+21
@@ -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.
|
||||||
Executable
+34
@@ -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.
|
||||||
Executable
+1278
File diff suppressed because it is too large
Load Diff
Executable
+470
@@ -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.
|
||||||
Executable
+30
@@ -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
@@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+8
@@ -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
@@ -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
@@ -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>
|
||||||
Executable
+7
@@ -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
@@ -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>
|
||||||
Executable
+676
@@ -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
@@ -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
@@ -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,
|
||||||
|
};
|
||||||
|
});
|
||||||
Executable
+8
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user