@synthetic-ai/premiere-mcp 2.3.1 → 2.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/cep-plugin/CSXS/manifest.xml +1 -1
- package/cep-plugin/i18n-core.cjs +224 -0
- package/cep-plugin/i18n-locales.js +3 -0
- package/cep-plugin/i18n-panel.cjs +178 -0
- package/cep-plugin/index.html +60 -25
- package/cep-plugin/main.js +793 -689
- package/dist/bridge/file-bridge.js +5 -4
- package/dist/http-server.d.ts +1 -0
- package/dist/http-server.js +3 -0
- package/dist/i18n.d.ts +42 -0
- package/dist/i18n.js +69 -0
- package/dist/index.js +41 -31
- package/dist/license.js +8 -6
- package/dist/server.js +9 -5
- package/dist/tools/effects.js +3 -2
- package/dist/tools/export.js +3 -2
- package/dist/tools/transitions.js +3 -2
- package/i18n/i18n-core.cjs +224 -0
- package/i18n/locales/de.json +1488 -0
- package/i18n/locales/en.json +1488 -0
- package/i18n/locales/es.json +1488 -0
- package/i18n/locales/fr.json +1488 -0
- package/i18n/locales/hashes/de.json +1488 -0
- package/i18n/locales/hashes/es.json +1488 -0
- package/i18n/locales/hashes/fr.json +1488 -0
- package/i18n/locales/hashes/ja.json +1488 -0
- package/i18n/locales/hashes/pt-BR.json +1488 -0
- package/i18n/locales/ja.json +1488 -0
- package/i18n/locales/pt-BR.json +1488 -0
- package/i18n/node.cjs +124 -0
- package/package.json +67 -65
- package/scripts/package.mjs +18 -2
- package/scripts/sync-i18n.mjs +117 -0
package/cep-plugin/main.js
CHANGED
|
@@ -1,689 +1,793 @@
|
|
|
1
|
-
/* MCP Bridge - CEP Plugin Main Script
|
|
2
|
-
* Polls a temp directory for command files (.jsx), executes them
|
|
3
|
-
* in Premiere Pro's ExtendScript engine, and writes results back.
|
|
4
|
-
*
|
|
5
|
-
* Improvements over the original:
|
|
6
|
-
* - isRunning flag guards the poll loop so concurrent executions can't pile up.
|
|
7
|
-
* - executeScript wraps cs.evalScript with a 25-second timeout to prevent hangs.
|
|
8
|
-
* - Status indicator updates on every state change (idle / running / error).
|
|
9
|
-
* - Reconnect logic: if Premiere Pro is unresponsive, the bridge backs off and retries. */
|
|
10
|
-
|
|
11
|
-
var cs = new CSInterface();
|
|
12
|
-
var bridgeRunning = false;
|
|
13
|
-
var isRunning = false; // true while a script is actively executing (prevents concurrent runs)
|
|
14
|
-
var pollInterval = null;
|
|
15
|
-
var commandCount = 0;
|
|
16
|
-
var errorCount = 0;
|
|
17
|
-
var consecutiveErrors = 0;
|
|
18
|
-
var tempDir = "";
|
|
19
|
-
var POLL_MS = 200;
|
|
20
|
-
var EXEC_TIMEOUT_MS = 25000; // 25-second hard timeout for ExtendScript execution
|
|
21
|
-
var PANEL_VERSION = "1.1.6"; // stamped by scripts/package.mjs on every build — see step 4b2
|
|
22
|
-
|
|
23
|
-
// ── Auto-update config ────────────────────────────────────────────────────
|
|
24
|
-
// The panel checks this URL at startup and offers in-app updates when a
|
|
25
|
-
// newer version is published. Files come back through /api/v1/panel/file
|
|
26
|
-
// gated by the user's license key. Self-installs only the hot-swappable
|
|
27
|
-
// JS/JSX/HTML files — manifest changes still need a fresh .zxp.
|
|
28
|
-
//
|
|
29
|
-
// Ported from Synthetic AE MCP's cep-plugin/main.js (checkForUpdates /
|
|
30
|
-
// showUpdateBanner / applyUpdate). One deliberate difference: AE MCP's Node
|
|
31
|
-
// server SPAWNS AfterFX.exe, so AE_MCP_LICENSE (set in the MCP client config)
|
|
32
|
-
// is inherited by the child process and read via $.getenv() inside
|
|
33
|
-
// ExtendScript. Premiere MCP's Node server does NOT spawn Premiere Pro — it
|
|
34
|
-
// talks to the already-running CEP panel as an HTTP client — so there is no
|
|
35
|
-
// env-var-inheritance path. Instead the license key is pasted into this
|
|
36
|
-
// panel's own Settings field and persisted to a local license.json (see
|
|
37
|
-
// readLicenseKeyLocal / saveLicenseKey below).
|
|
38
|
-
var UPDATE_CHECK_URL = "https://synthetic.com.ar/api/v1/panel/version";
|
|
39
|
-
var UPDATE_FILE_URL = "https://synthetic.com.ar/api/v1/panel/file";
|
|
40
|
-
var UPDATE_PRODUCT = "premiere-mcp";
|
|
41
|
-
var pendingUpdate = null;
|
|
42
|
-
var lastDismissedVersion = null;
|
|
43
|
-
|
|
44
|
-
// ----
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
function
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
function
|
|
272
|
-
var
|
|
273
|
-
var
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
//
|
|
355
|
-
// the
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
function
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
}
|
|
1
|
+
/* MCP Bridge - CEP Plugin Main Script
|
|
2
|
+
* Polls a temp directory for command files (.jsx), executes them
|
|
3
|
+
* in Premiere Pro's ExtendScript engine, and writes results back.
|
|
4
|
+
*
|
|
5
|
+
* Improvements over the original:
|
|
6
|
+
* - isRunning flag guards the poll loop so concurrent executions can't pile up.
|
|
7
|
+
* - executeScript wraps cs.evalScript with a 25-second timeout to prevent hangs.
|
|
8
|
+
* - Status indicator updates on every state change (idle / running / error).
|
|
9
|
+
* - Reconnect logic: if Premiere Pro is unresponsive, the bridge backs off and retries. */
|
|
10
|
+
|
|
11
|
+
var cs = new CSInterface();
|
|
12
|
+
var bridgeRunning = false;
|
|
13
|
+
var isRunning = false; // true while a script is actively executing (prevents concurrent runs)
|
|
14
|
+
var pollInterval = null;
|
|
15
|
+
var commandCount = 0;
|
|
16
|
+
var errorCount = 0;
|
|
17
|
+
var consecutiveErrors = 0;
|
|
18
|
+
var tempDir = "";
|
|
19
|
+
var POLL_MS = 200;
|
|
20
|
+
var EXEC_TIMEOUT_MS = 25000; // 25-second hard timeout for ExtendScript execution
|
|
21
|
+
var PANEL_VERSION = "1.1.6"; // stamped by scripts/package.mjs on every build — see step 4b2
|
|
22
|
+
|
|
23
|
+
// ── Auto-update config ────────────────────────────────────────────────────
|
|
24
|
+
// The panel checks this URL at startup and offers in-app updates when a
|
|
25
|
+
// newer version is published. Files come back through /api/v1/panel/file
|
|
26
|
+
// gated by the user's license key. Self-installs only the hot-swappable
|
|
27
|
+
// JS/JSX/HTML files — manifest changes still need a fresh .zxp.
|
|
28
|
+
//
|
|
29
|
+
// Ported from Synthetic AE MCP's cep-plugin/main.js (checkForUpdates /
|
|
30
|
+
// showUpdateBanner / applyUpdate). One deliberate difference: AE MCP's Node
|
|
31
|
+
// server SPAWNS AfterFX.exe, so AE_MCP_LICENSE (set in the MCP client config)
|
|
32
|
+
// is inherited by the child process and read via $.getenv() inside
|
|
33
|
+
// ExtendScript. Premiere MCP's Node server does NOT spawn Premiere Pro — it
|
|
34
|
+
// talks to the already-running CEP panel as an HTTP client — so there is no
|
|
35
|
+
// env-var-inheritance path. Instead the license key is pasted into this
|
|
36
|
+
// panel's own Settings field and persisted to a local license.json (see
|
|
37
|
+
// readLicenseKeyLocal / saveLicenseKey below).
|
|
38
|
+
var UPDATE_CHECK_URL = "https://synthetic.com.ar/api/v1/panel/version";
|
|
39
|
+
var UPDATE_FILE_URL = "https://synthetic.com.ar/api/v1/panel/file";
|
|
40
|
+
var UPDATE_PRODUCT = "premiere-mcp";
|
|
41
|
+
var pendingUpdate = null;
|
|
42
|
+
var lastDismissedVersion = null;
|
|
43
|
+
|
|
44
|
+
// ---- i18n ----------------------------------------------------------------
|
|
45
|
+
// UI strings come from i18n/ (vendored from synthetic-idiomas by
|
|
46
|
+
// scripts/sync-i18n.mjs): [data-i18n*] attributes in index.html + t() here.
|
|
47
|
+
// The language picked in Settings is saved to
|
|
48
|
+
// <USER_DATA>/com.mcp.premiere.bridge/settings.json — the same file the AI
|
|
49
|
+
// Chat panel uses and the MCP server reads when it starts. This panel's UI
|
|
50
|
+
// switches immediately; the AI tool descriptions switch when the MCP server
|
|
51
|
+
// restarts (MCP clients only read them on connect).
|
|
52
|
+
var I18N_SETTINGS_FOLDER = "com.mcp.premiere.bridge";
|
|
53
|
+
var i18n = initI18n();
|
|
54
|
+
var lastStatus = { state: "", key: "ui.bridge.status.stopped", vars: null };
|
|
55
|
+
|
|
56
|
+
function initI18n() {
|
|
57
|
+
var none = {
|
|
58
|
+
language: "en",
|
|
59
|
+
t: function (key) { return key; },
|
|
60
|
+
apply: function () {},
|
|
61
|
+
onChange: function () {},
|
|
62
|
+
renderSelector: function () { return null; }
|
|
63
|
+
};
|
|
64
|
+
if (typeof SyntheticPanelI18n === "undefined") return none; // i18n/ missing: HTML stays English
|
|
65
|
+
try {
|
|
66
|
+
// The minimal CSInterface shim doesn't fill cs.hostEnvironment; the
|
|
67
|
+
// host app's UI language is the default until the user picks one.
|
|
68
|
+
// (panel.cjs does its own file:// URI → path cleanup for USER_DATA.)
|
|
69
|
+
if (!cs.hostEnvironment && cs.getHostEnvironment) cs.hostEnvironment = cs.getHostEnvironment();
|
|
70
|
+
return SyntheticPanelI18n.init({ product: "premiere-mcp", settingsFolder: I18N_SETTINGS_FOLDER, cs: cs });
|
|
71
|
+
} catch (e) {
|
|
72
|
+
try { console.error("[panel init] i18n failed:", e); } catch (_) {}
|
|
73
|
+
try { log("i18n init failed: " + e.message, "err"); } catch (_) {}
|
|
74
|
+
return none;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function t(key, vars) {
|
|
79
|
+
return i18n.t(key, vars);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Set an element's text from a key and remember key + vars on it, so a
|
|
83
|
+
// language switch re-renders it (see applyI18n).
|
|
84
|
+
function setText(el, key, vars) {
|
|
85
|
+
if (!el) return;
|
|
86
|
+
el.setAttribute("data-i18n", key);
|
|
87
|
+
if (vars) el.setAttribute("data-i18n-vars", JSON.stringify(vars));
|
|
88
|
+
else el.removeAttribute("data-i18n-vars");
|
|
89
|
+
el.textContent = t(key, vars);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// i18n.apply() translates [data-i18n], [data-i18n-title], [data-i18n-placeholder]
|
|
93
|
+
// and [data-i18n-aria-label]; elements with {placeholders} also carry
|
|
94
|
+
// data-i18n-vars and are filled here.
|
|
95
|
+
function applyI18n() {
|
|
96
|
+
i18n.apply(document);
|
|
97
|
+
var nodes = document.querySelectorAll("[data-i18n-vars]");
|
|
98
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
99
|
+
var vars = null;
|
|
100
|
+
try { vars = JSON.parse(nodes[i].getAttribute("data-i18n-vars")); } catch (e) {}
|
|
101
|
+
nodes[i].textContent = t(nodes[i].getAttribute("data-i18n"), vars);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Settings: language selector next to the license key.
|
|
106
|
+
function renderLanguageSettings() {
|
|
107
|
+
var box = document.getElementById("languageSettings");
|
|
108
|
+
if (!box) return;
|
|
109
|
+
i18n.renderSelector(box, { restartNote: true });
|
|
110
|
+
i18n.onChange(function (lang, saved) {
|
|
111
|
+
applyI18n();
|
|
112
|
+
setStatus(lastStatus.state, lastStatus.key, lastStatus.vars);
|
|
113
|
+
setText(document.getElementById("languageStatus"), saved ? "ui.i18n.saved" : "ui.i18n.saveFailed");
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---- Logging ----
|
|
118
|
+
function log(msg, cls) {
|
|
119
|
+
var el = document.getElementById("log");
|
|
120
|
+
var entry = document.createElement("div");
|
|
121
|
+
entry.className = "log-entry " + (cls || "");
|
|
122
|
+
var ts = new Date().toLocaleTimeString();
|
|
123
|
+
entry.textContent = "[" + ts + "] " + msg;
|
|
124
|
+
el.appendChild(entry);
|
|
125
|
+
el.scrollTop = el.scrollHeight;
|
|
126
|
+
// Keep max 100 entries
|
|
127
|
+
while (el.children.length > 100) el.removeChild(el.firstChild);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---- Status ----
|
|
131
|
+
// key/vars: i18n key of the status line (kept so a language switch re-renders it).
|
|
132
|
+
function setStatus(state, key, vars) {
|
|
133
|
+
lastStatus = { state: state, key: key, vars: vars || null };
|
|
134
|
+
var text = t(key, vars);
|
|
135
|
+
var dot = document.getElementById("statusDot");
|
|
136
|
+
dot.className = "status-dot " + state;
|
|
137
|
+
setText(document.getElementById("statusText"), key, vars);
|
|
138
|
+
|
|
139
|
+
// Mirror the same state onto the small dot inside the product block, so the
|
|
140
|
+
// "is the panel OK?" cue is visible up-top without scanning the status row.
|
|
141
|
+
// Map: connected -> ok, warning -> warn, error -> err, waiting/* -> grey.
|
|
142
|
+
var ps = document.getElementById("productStatus");
|
|
143
|
+
if (ps) {
|
|
144
|
+
ps.className = "product-status" +
|
|
145
|
+
(state === "connected" ? " ok" :
|
|
146
|
+
state === "warning" ? " warn" :
|
|
147
|
+
state === "error" ? " err" : "");
|
|
148
|
+
ps.title = text || t("ui.bridge.product.statusReady");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ---- File I/O via Node.js (CEP has access to Node) ----
|
|
153
|
+
var fs = require("fs");
|
|
154
|
+
var path = require("path");
|
|
155
|
+
var os = require("os");
|
|
156
|
+
tempDir = path.join(os.tmpdir(), "premiere-mcp-bridge");
|
|
157
|
+
|
|
158
|
+
function ensureDir(dir) {
|
|
159
|
+
try {
|
|
160
|
+
if (!fs.existsSync(dir)) {
|
|
161
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {
|
|
164
|
+
log(t("ui.bridge.log.createDirFailed", { error: e.message }), "err");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function listCommandFiles() {
|
|
169
|
+
try {
|
|
170
|
+
if (!fs.existsSync(tempDir)) return [];
|
|
171
|
+
var files = fs.readdirSync(tempDir);
|
|
172
|
+
return files
|
|
173
|
+
.filter(function (f) { return f.indexOf("cmd_") === 0 && f.indexOf(".jsx") > 0; })
|
|
174
|
+
.sort(); // process in order
|
|
175
|
+
} catch (e) {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function readFile(filePath) {
|
|
181
|
+
try {
|
|
182
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
183
|
+
} catch (e) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function writeFile(filePath, content) {
|
|
189
|
+
try {
|
|
190
|
+
// Atomic write: write to a temp file the MCP server's poller ignores
|
|
191
|
+
// (it waits for the exact res_<id>.json name), then rename into place.
|
|
192
|
+
// Prevents the server from reading a half-written response file.
|
|
193
|
+
var tmpPath = filePath + ".part";
|
|
194
|
+
fs.writeFileSync(tmpPath, content, "utf-8");
|
|
195
|
+
fs.renameSync(tmpPath, filePath);
|
|
196
|
+
return true;
|
|
197
|
+
} catch (e) {
|
|
198
|
+
log(t("ui.bridge.log.writeFailed", { file: filePath, error: e.message }), "err");
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function deleteFile(filePath) {
|
|
204
|
+
try {
|
|
205
|
+
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
206
|
+
} catch (e) {}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// cs.getSystemPath() returns a file:// URI, not a plain OS path — observed
|
|
210
|
+
// on Windows as "file:\C:\Users\..." (single backslash, not the POSIX
|
|
211
|
+
// "file:///C:/..."). Passing that straight to fs/path calls tries to
|
|
212
|
+
// create/read a path actually named "file:\C:\Users\...", which fails with
|
|
213
|
+
// ENOENT since its own parent doesn't exist. Strip whatever "file:" prefix
|
|
214
|
+
// shows up and URL-decode any %xx escapes before treating it as a path.
|
|
215
|
+
function systemPathToFsPath(p) {
|
|
216
|
+
var m = /^file:[\\/]*/i.exec(p || "");
|
|
217
|
+
if (m) p = p.slice(m[0].length);
|
|
218
|
+
try { p = decodeURIComponent(p); } catch (e) {}
|
|
219
|
+
return p;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ---- License key storage (local file, no env-var inheritance path) ------
|
|
223
|
+
// Premiere Pro is already running when this panel loads — the MCP server
|
|
224
|
+
// never spawns it — so there's no child-process env var to read the license
|
|
225
|
+
// key from (unlike AE MCP's AE_MCP_LICENSE). Instead the user pastes the key
|
|
226
|
+
// into the Settings field below and it's persisted next to the extension.
|
|
227
|
+
function getLicensePath() {
|
|
228
|
+
try {
|
|
229
|
+
return path.join(systemPathToFsPath(cs.getSystemPath(SystemPath.EXTENSION)), "license.json");
|
|
230
|
+
} catch (e) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function readLicenseKeyLocal() {
|
|
236
|
+
try {
|
|
237
|
+
var p = getLicensePath();
|
|
238
|
+
if (!p || !fs.existsSync(p)) return "";
|
|
239
|
+
var raw = fs.readFileSync(p, "utf-8");
|
|
240
|
+
var parsed = JSON.parse(raw);
|
|
241
|
+
return (parsed && parsed.licenseKey) ? String(parsed.licenseKey).trim() : "";
|
|
242
|
+
} catch (e) {
|
|
243
|
+
return "";
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function writeLicenseKeyLocal(key) {
|
|
248
|
+
var p = getLicensePath();
|
|
249
|
+
if (!p) return false;
|
|
250
|
+
try {
|
|
251
|
+
fs.writeFileSync(p, JSON.stringify({ licenseKey: key }, null, 2), "utf-8");
|
|
252
|
+
return true;
|
|
253
|
+
} catch (e) {
|
|
254
|
+
log(t("ui.bridge.log.licenseSaveError", { error: e.message }), "err");
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Settings UI: load saved key into the input + status line on panel init.
|
|
260
|
+
function loadLicenseUI() {
|
|
261
|
+
try {
|
|
262
|
+
var key = readLicenseKeyLocal();
|
|
263
|
+
var input = document.getElementById("licenseKeyInput");
|
|
264
|
+
if (input) input.value = key;
|
|
265
|
+
var statusEl = document.getElementById("licenseStatus");
|
|
266
|
+
setText(statusEl, key ? "ui.bridge.license.saved" : "ui.bridge.license.none");
|
|
267
|
+
} catch (e) {}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Settings UI: Save button handler.
|
|
271
|
+
function saveLicenseKey() {
|
|
272
|
+
var input = document.getElementById("licenseKeyInput");
|
|
273
|
+
var key = input ? input.value.trim() : "";
|
|
274
|
+
var statusEl = document.getElementById("licenseStatus");
|
|
275
|
+
if (!key) {
|
|
276
|
+
setText(statusEl, "ui.bridge.license.empty");
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (writeLicenseKeyLocal(key)) {
|
|
280
|
+
log(t("ui.bridge.license.saved"), "ok");
|
|
281
|
+
setText(statusEl, "ui.bridge.license.saved");
|
|
282
|
+
} else {
|
|
283
|
+
setText(statusEl, "ui.bridge.license.saveFailed");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Settings is just the license field for now — no need for AE Agent's full
|
|
288
|
+
// Subscription/API Keys/Data section system. Hidden by default so the key
|
|
289
|
+
// isn't sitting in plain view on the main panel; click the ⚙ to reveal it.
|
|
290
|
+
function toggleSettings() {
|
|
291
|
+
var panel = document.getElementById("settingsPanel");
|
|
292
|
+
if (!panel) return;
|
|
293
|
+
panel.style.display = panel.style.display === "none" ? "block" : "none";
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ---- Script Execution with timeout ----
|
|
297
|
+
function executeScript(script, callback) {
|
|
298
|
+
// Script is already wrapped in an IIFE by the MCP server's buildScript(),
|
|
299
|
+
// so we pass it directly to avoid double-wrapping.
|
|
300
|
+
var timedOut = false;
|
|
301
|
+
var settled = false;
|
|
302
|
+
|
|
303
|
+
var timeoutHandle = setTimeout(function () {
|
|
304
|
+
if (settled) return;
|
|
305
|
+
timedOut = true;
|
|
306
|
+
settled = true;
|
|
307
|
+
log(t("ui.bridge.log.execTimeout", { seconds: EXEC_TIMEOUT_MS / 1000 }), "err");
|
|
308
|
+
consecutiveErrors++;
|
|
309
|
+
updateStatusFromErrors();
|
|
310
|
+
callback(JSON.stringify({ success: false, error: t("ui.bridge.error.execTimeout", { seconds: EXEC_TIMEOUT_MS / 1000 }) }));
|
|
311
|
+
}, EXEC_TIMEOUT_MS);
|
|
312
|
+
|
|
313
|
+
cs.evalScript(script, function (result) {
|
|
314
|
+
if (settled) return; // already timed out
|
|
315
|
+
settled = true;
|
|
316
|
+
clearTimeout(timeoutHandle);
|
|
317
|
+
consecutiveErrors = 0; // reset on success
|
|
318
|
+
callback(result);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ---- Update status dot based on error state ----
|
|
323
|
+
function updateStatusFromErrors() {
|
|
324
|
+
if (!bridgeRunning) return;
|
|
325
|
+
if (consecutiveErrors >= 3) {
|
|
326
|
+
setStatus("error", "ui.bridge.status.unresponsive", { count: consecutiveErrors });
|
|
327
|
+
} else if (consecutiveErrors > 0) {
|
|
328
|
+
setStatus("warning", "ui.bridge.status.errors", { count: consecutiveErrors });
|
|
329
|
+
} else {
|
|
330
|
+
setStatus("connected", "ui.bridge.status.running", { count: commandCount });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ---- Command Processing ----
|
|
335
|
+
function processCommands() {
|
|
336
|
+
// Prevent concurrent executions: if a script is still running, skip this poll tick.
|
|
337
|
+
if (isRunning) return;
|
|
338
|
+
|
|
339
|
+
var cmdFiles = listCommandFiles();
|
|
340
|
+
if (cmdFiles.length === 0) return;
|
|
341
|
+
|
|
342
|
+
// Process one command at a time (FIFO). The next tick picks up the next file.
|
|
343
|
+
isRunning = true;
|
|
344
|
+
processOneCommand(cmdFiles[0]);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function processOneCommand(cmdFileName) {
|
|
348
|
+
var cmdFilePath = path.join(tempDir, cmdFileName);
|
|
349
|
+
var script = readFile(cmdFilePath);
|
|
350
|
+
if (!script) {
|
|
351
|
+
log(t("ui.bridge.log.readFailed", { file: cmdFileName }), "err");
|
|
352
|
+
deleteFile(cmdFilePath);
|
|
353
|
+
isRunning = false; // release the lock — AE MCP's sibling file already does this;
|
|
354
|
+
// without it a single transient read failure (e.g. a race with
|
|
355
|
+
// the writer) wedges the bridge forever: isRunning stays true,
|
|
356
|
+
// so every future processCommands() tick bails at its first
|
|
357
|
+
// check and the panel looks "Running" while doing nothing.
|
|
358
|
+
// Reproduced live 2026-09-23.
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Derive response filename: cmd_12345.jsx -> res_12345.json
|
|
363
|
+
var id = cmdFileName.replace("cmd_", "").replace(".jsx", "");
|
|
364
|
+
var resFilePath = path.join(tempDir, "res_" + id + ".json");
|
|
365
|
+
|
|
366
|
+
log(t("ui.bridge.log.executing", { file: cmdFileName, chars: script.length }), "cmd");
|
|
367
|
+
|
|
368
|
+
// Delete the command file immediately to avoid re-processing
|
|
369
|
+
deleteFile(cmdFilePath);
|
|
370
|
+
|
|
371
|
+
executeScript(script, function (result) {
|
|
372
|
+
isRunning = false; // release the lock so the next command can be processed
|
|
373
|
+
commandCount++;
|
|
374
|
+
document.getElementById("cmdCount").textContent = commandCount;
|
|
375
|
+
|
|
376
|
+
var response;
|
|
377
|
+
try {
|
|
378
|
+
// ExtendScript returns a string; try to parse it as JSON
|
|
379
|
+
if (result && result !== "undefined" && result !== "null") {
|
|
380
|
+
// Check if it's already valid JSON
|
|
381
|
+
var parsed = JSON.parse(result);
|
|
382
|
+
response = JSON.stringify(parsed);
|
|
383
|
+
// If the parsed result contains an error field, count it
|
|
384
|
+
if (parsed && parsed.success === false) {
|
|
385
|
+
errorCount++;
|
|
386
|
+
consecutiveErrors++;
|
|
387
|
+
log(t("ui.bridge.log.resultError", { error: parsed.error || t("ui.bridge.log.unknownError") }), "err");
|
|
388
|
+
} else {
|
|
389
|
+
consecutiveErrors = 0;
|
|
390
|
+
log(t("ui.bridge.log.resultOk"), "ok");
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
response = JSON.stringify({ success: true, data: result || null });
|
|
394
|
+
consecutiveErrors = 0;
|
|
395
|
+
log(t("ui.bridge.log.resultOkEmpty"), "ok");
|
|
396
|
+
}
|
|
397
|
+
} catch (e) {
|
|
398
|
+
// If result isn't JSON, wrap it
|
|
399
|
+
if (result && result.indexOf("Error") === 0) {
|
|
400
|
+
response = JSON.stringify({ success: false, error: result });
|
|
401
|
+
errorCount++;
|
|
402
|
+
consecutiveErrors++;
|
|
403
|
+
log(t("ui.bridge.log.resultRaw", { result: result }), "err");
|
|
404
|
+
} else {
|
|
405
|
+
response = JSON.stringify({ success: true, data: result });
|
|
406
|
+
consecutiveErrors = 0;
|
|
407
|
+
log(t("ui.bridge.log.resultOkRaw"), "ok");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
updateStatusFromErrors();
|
|
412
|
+
writeFile(resFilePath, response);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ---- Auto-update --------------------------------------------------------
|
|
417
|
+
// Ported verbatim (logic-wise) from Synthetic AE MCP's cep-plugin/main.js.
|
|
418
|
+
// Compare two semver strings (x.y.z). Returns 1 if a > b, -1 if a < b, 0 if =.
|
|
419
|
+
// Hand-rolled because ExtendScript / CEP-Node don't ship semver.
|
|
420
|
+
function semverCmp(a, b) {
|
|
421
|
+
var pa = String(a || "0.0.0").split(".").map(function (n) { return parseInt(n, 10) || 0; });
|
|
422
|
+
var pb = String(b || "0.0.0").split(".").map(function (n) { return parseInt(n, 10) || 0; });
|
|
423
|
+
for (var i = 0; i < 3; i++) {
|
|
424
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
425
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
426
|
+
}
|
|
427
|
+
return 0;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// One-shot https GET that buffers the response into a string. CEP ships
|
|
431
|
+
// the Node 'https' module already.
|
|
432
|
+
function httpsGetJson(url, cb) {
|
|
433
|
+
try {
|
|
434
|
+
var https = require("https");
|
|
435
|
+
https.get(url, function (res) {
|
|
436
|
+
var chunks = [];
|
|
437
|
+
// Follow one redirect. Our own /api/v1/panel/file endpoint 302s to R2
|
|
438
|
+
// (Supabase signed URLs went through 302 too), but Next's
|
|
439
|
+
// NextResponse.redirect() defaults to 307 — handle the whole 3xx
|
|
440
|
+
// range so this doesn't silently break again on a framework default.
|
|
441
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
442
|
+
return httpsGetJson(res.headers.location, cb);
|
|
443
|
+
}
|
|
444
|
+
res.on("data", function (c) { chunks.push(c); });
|
|
445
|
+
res.on("end", function () {
|
|
446
|
+
var body = Buffer.concat(chunks).toString("utf-8");
|
|
447
|
+
if (res.statusCode !== 200) {
|
|
448
|
+
return cb(new Error("HTTP " + res.statusCode + ": " + body.slice(0, 200)));
|
|
449
|
+
}
|
|
450
|
+
try { cb(null, JSON.parse(body)); }
|
|
451
|
+
catch (e) { cb(new Error("Bad JSON: " + e.message)); }
|
|
452
|
+
});
|
|
453
|
+
}).on("error", function (err) { cb(err); });
|
|
454
|
+
} catch (e) { cb(e); }
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Download a single file as a Buffer. Follows one redirect.
|
|
458
|
+
function httpsDownload(url, cb) {
|
|
459
|
+
try {
|
|
460
|
+
var https = require("https");
|
|
461
|
+
https.get(url, function (res) {
|
|
462
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
463
|
+
return httpsDownload(res.headers.location, cb);
|
|
464
|
+
}
|
|
465
|
+
if (res.statusCode !== 200) {
|
|
466
|
+
return cb(new Error("HTTP " + res.statusCode));
|
|
467
|
+
}
|
|
468
|
+
var chunks = [];
|
|
469
|
+
res.on("data", function (c) { chunks.push(c); });
|
|
470
|
+
res.on("end", function () { cb(null, Buffer.concat(chunks)); });
|
|
471
|
+
}).on("error", function (err) { cb(err); });
|
|
472
|
+
} catch (e) { cb(e); }
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// SHA256 of a Buffer using Node's crypto. Used to verify integrity of
|
|
476
|
+
// downloaded files before overwriting the panel's own code.
|
|
477
|
+
function sha256(buf) {
|
|
478
|
+
var crypto = require("crypto");
|
|
479
|
+
return crypto.createHash("sha256").update(buf).digest("hex");
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// On panel startup. Fetches the manifest, compares versions, surfaces a
|
|
483
|
+
// banner if newer. Never throws — if anything goes wrong we silently skip
|
|
484
|
+
// the check so the panel still starts cleanly even offline.
|
|
485
|
+
function checkForUpdates() {
|
|
486
|
+
try {
|
|
487
|
+
var url = UPDATE_CHECK_URL + "?product=" + encodeURIComponent(UPDATE_PRODUCT) +
|
|
488
|
+
"&from=" + encodeURIComponent(PANEL_VERSION);
|
|
489
|
+
httpsGetJson(url, function (err, manifest) {
|
|
490
|
+
if (err || !manifest || !manifest.version) return;
|
|
491
|
+
if (semverCmp(manifest.version, PANEL_VERSION) <= 0) return;
|
|
492
|
+
if (lastDismissedVersion === manifest.version) return;
|
|
493
|
+
pendingUpdate = manifest;
|
|
494
|
+
showUpdateBanner(manifest);
|
|
495
|
+
});
|
|
496
|
+
} catch (_) {}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function showUpdateBanner(m) {
|
|
500
|
+
var banner = document.getElementById("updateBanner");
|
|
501
|
+
if (!banner) return;
|
|
502
|
+
document.getElementById("updateVersionTag").textContent = "v" + PANEL_VERSION + " -> v" + m.version;
|
|
503
|
+
var notes = Array.isArray(m.releaseNotes) ? m.releaseNotes.slice(0, 3).join("\n") : "";
|
|
504
|
+
document.getElementById("updateNotes").textContent = notes;
|
|
505
|
+
banner.style.display = "block";
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function dismissUpdate() {
|
|
509
|
+
if (pendingUpdate) lastDismissedVersion = pendingUpdate.version;
|
|
510
|
+
pendingUpdate = null;
|
|
511
|
+
var banner = document.getElementById("updateBanner");
|
|
512
|
+
if (banner) banner.style.display = "none";
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function openInstaller() {
|
|
516
|
+
try {
|
|
517
|
+
if (window.cep && cep.util && cep.util.openURLInDefaultBrowser) {
|
|
518
|
+
cep.util.openURLInDefaultBrowser("https://synthetic.com.ar/dashboard/downloads");
|
|
519
|
+
}
|
|
520
|
+
} catch (_) {}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Apply the update: for each file in the manifest, download via the
|
|
524
|
+
// license-gated endpoint, verify SHA256, back up the current file to
|
|
525
|
+
// .bak, write the new one. If ANY file fails verification, roll back all
|
|
526
|
+
// of them — never leave the panel in a half-updated state.
|
|
527
|
+
function applyUpdate() {
|
|
528
|
+
if (!pendingUpdate || !Array.isArray(pendingUpdate.files)) {
|
|
529
|
+
log(t("ui.bridge.log.noUpdate"), "err");
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
var btn = document.getElementById("btnUpdateNow");
|
|
533
|
+
if (btn) { btn.disabled = true; setText(btn, "ui.bridge.update.updating"); }
|
|
534
|
+
|
|
535
|
+
var licenseKey = readLicenseKeyLocal();
|
|
536
|
+
if (!licenseKey) {
|
|
537
|
+
log(t("ui.bridge.log.updateNoLicense"), "err");
|
|
538
|
+
setText(document.getElementById("licenseStatus"), "ui.bridge.license.requiredForUpdate");
|
|
539
|
+
if (btn) { btn.disabled = false; setText(btn, "ui.bridge.update.now"); }
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// CEP can ask its own extension folder via cs.getSystemPath.
|
|
544
|
+
var extDir = systemPathToFsPath(cs.getSystemPath(SystemPath.EXTENSION));
|
|
545
|
+
var files = pendingUpdate.files;
|
|
546
|
+
var version = pendingUpdate.version;
|
|
547
|
+
var staged = []; // {targetPath, backupPath, buf}
|
|
548
|
+
var i = 0;
|
|
549
|
+
|
|
550
|
+
function fetchNext() {
|
|
551
|
+
if (i >= files.length) { commit(); return; }
|
|
552
|
+
var f = files[i++];
|
|
553
|
+
var url = UPDATE_FILE_URL +
|
|
554
|
+
"?product=" + encodeURIComponent(UPDATE_PRODUCT) +
|
|
555
|
+
"&name=" + encodeURIComponent(f.name) +
|
|
556
|
+
"&licenseKey=" + encodeURIComponent(licenseKey) +
|
|
557
|
+
"&version=" + encodeURIComponent(version);
|
|
558
|
+
log(t("ui.bridge.log.downloading", { file: f.name }));
|
|
559
|
+
httpsDownload(url, function (err, buf) {
|
|
560
|
+
if (err) return abort(t("ui.bridge.update.downloadFailed", { file: f.name, error: err.message }));
|
|
561
|
+
// Optional SHA256 check — only enforced if the manifest provides one.
|
|
562
|
+
if (f.sha256 && f.sha256.length === 64) {
|
|
563
|
+
var got = sha256(buf);
|
|
564
|
+
if (got !== f.sha256) return abort(t("ui.bridge.update.shaMismatch", { file: f.name, got: got.slice(0, 12), expected: f.sha256.slice(0, 12) }));
|
|
565
|
+
}
|
|
566
|
+
// Optional size sanity check.
|
|
567
|
+
if (f.size && Math.abs(buf.length - f.size) > 1024) {
|
|
568
|
+
log(t("ui.bridge.log.sizeDiffers", { file: f.name, actual: buf.length, expected: f.size }), "");
|
|
569
|
+
}
|
|
570
|
+
var target = path.join(extDir, f.name);
|
|
571
|
+
// The "CSXS" subfolder gets resolved separately; everything in our
|
|
572
|
+
// panel lives at the extension root, so a flat join is correct.
|
|
573
|
+
staged.push({ target: target, backup: target + ".bak", buf: buf });
|
|
574
|
+
fetchNext();
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function commit() {
|
|
579
|
+
try {
|
|
580
|
+
// Phase 1: backup. If any backup write fails, abort before touching real files.
|
|
581
|
+
for (var k = 0; k < staged.length; k++) {
|
|
582
|
+
var s = staged[k];
|
|
583
|
+
if (fs.existsSync(s.target)) {
|
|
584
|
+
// Best effort — rename can fail if source is locked; copy as fallback.
|
|
585
|
+
try { fs.copyFileSync(s.target, s.backup); }
|
|
586
|
+
catch (eCopy) { return abort(t("ui.bridge.update.backupFailed", { file: s.target, error: eCopy.message })); }
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
// Phase 2: write new files.
|
|
590
|
+
for (var j = 0; j < staged.length; j++) {
|
|
591
|
+
var ss = staged[j];
|
|
592
|
+
var tmpPath = ss.target + ".new";
|
|
593
|
+
// Files may live in subfolders (e.g. i18n/): create them on first update.
|
|
594
|
+
fs.mkdirSync(path.dirname(ss.target), { recursive: true });
|
|
595
|
+
fs.writeFileSync(tmpPath, ss.buf);
|
|
596
|
+
// Replace via rename so the swap is atomic per-file.
|
|
597
|
+
if (fs.existsSync(ss.target)) fs.unlinkSync(ss.target);
|
|
598
|
+
fs.renameSync(tmpPath, ss.target);
|
|
599
|
+
}
|
|
600
|
+
log(t("ui.bridge.log.updateApplied", { version: version }), "ok");
|
|
601
|
+
setStatus("warning", "ui.bridge.status.updateApplied");
|
|
602
|
+
var b = document.getElementById("updateBanner");
|
|
603
|
+
if (b) {
|
|
604
|
+
b.style.background = "rgba(40,180,120,0.10)";
|
|
605
|
+
b.style.borderColor = "#28b478";
|
|
606
|
+
// Built as DOM nodes (not innerHTML) so translated text is never parsed as HTML.
|
|
607
|
+
var notesEl = document.getElementById("updateNotes");
|
|
608
|
+
notesEl.textContent = "";
|
|
609
|
+
var strong = document.createElement("strong");
|
|
610
|
+
strong.style.color = "#9af3c4";
|
|
611
|
+
setText(strong, "ui.bridge.update.appliedTitle");
|
|
612
|
+
var body = document.createElement("span");
|
|
613
|
+
setText(body, "ui.bridge.update.appliedBody", { version: version });
|
|
614
|
+
notesEl.appendChild(strong);
|
|
615
|
+
notesEl.appendChild(document.createTextNode(" "));
|
|
616
|
+
notesEl.appendChild(body);
|
|
617
|
+
var bt = document.getElementById("btnUpdateNow");
|
|
618
|
+
if (bt) { bt.style.display = "none"; }
|
|
619
|
+
var bt2 = document.getElementById("btnUpdateLater");
|
|
620
|
+
if (bt2) setText(bt2, "ui.bridge.update.ok");
|
|
621
|
+
var bt3 = document.getElementById("btnUpdateInstaller");
|
|
622
|
+
if (bt3) bt3.style.display = "none";
|
|
623
|
+
}
|
|
624
|
+
} catch (eCommit) {
|
|
625
|
+
// Rollback: restore any .bak we wrote.
|
|
626
|
+
for (var r = 0; r < staged.length; r++) {
|
|
627
|
+
try {
|
|
628
|
+
var rs = staged[r];
|
|
629
|
+
if (fs.existsSync(rs.backup)) {
|
|
630
|
+
if (fs.existsSync(rs.target)) fs.unlinkSync(rs.target);
|
|
631
|
+
fs.renameSync(rs.backup, rs.target);
|
|
632
|
+
}
|
|
633
|
+
} catch (_) {}
|
|
634
|
+
}
|
|
635
|
+
abort(t("ui.bridge.update.commitFailed", { error: eCommit.message }));
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function abort(msg) {
|
|
640
|
+
log(t("ui.bridge.log.updateAborted", { reason: msg }), "err");
|
|
641
|
+
if (btn) { btn.disabled = false; setText(btn, "ui.bridge.update.now"); }
|
|
642
|
+
setStatus("error", "ui.bridge.status.updateFailed");
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
fetchNext();
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// ---- Bridge Control ----
|
|
650
|
+
function startBridge() {
|
|
651
|
+
tempDir = document.getElementById("tempDir").value.trim();
|
|
652
|
+
if (!tempDir) {
|
|
653
|
+
log(t("ui.bridge.log.tempDirMissing"), "err");
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
ensureDir(tempDir);
|
|
658
|
+
bridgeRunning = true;
|
|
659
|
+
setStatus("connected", "ui.bridge.status.polling", { dir: tempDir });
|
|
660
|
+
log(t("ui.bridge.log.started", { dir: tempDir }));
|
|
661
|
+
|
|
662
|
+
document.getElementById("btnStart").disabled = true;
|
|
663
|
+
document.getElementById("btnStop").disabled = false;
|
|
664
|
+
|
|
665
|
+
// Verify Premiere Pro connection
|
|
666
|
+
cs.evalScript("app.version", function (version) {
|
|
667
|
+
if (version && version !== "undefined") {
|
|
668
|
+
log("Premiere Pro: " + version, "ok");
|
|
669
|
+
} else {
|
|
670
|
+
log(t("ui.bridge.log.versionUnknown"), "err");
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
pollInterval = setInterval(function () {
|
|
675
|
+
if (bridgeRunning) processCommands();
|
|
676
|
+
}, POLL_MS);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function stopBridge() {
|
|
680
|
+
bridgeRunning = false;
|
|
681
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
682
|
+
pollInterval = null;
|
|
683
|
+
|
|
684
|
+
setStatus("", "ui.bridge.status.stopped");
|
|
685
|
+
log(t("ui.bridge.log.stopped"));
|
|
686
|
+
|
|
687
|
+
document.getElementById("btnStart").disabled = false;
|
|
688
|
+
document.getElementById("btnStop").disabled = true;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// "Reload panel" button — location.reload() (even after stopBridge() first,
|
|
692
|
+
// tried in v2.3.3) does not reliably re-run init() in this CEP host: found
|
|
693
|
+
// live 29-sep-2026, twice, on a fully clean install + full Premiere Pro
|
|
694
|
+
// restart in between — badge stuck at the raw HTML placeholder, temp dir
|
|
695
|
+
// field empty, "Please set a temp directory" logged, no auto-start. Rather
|
|
696
|
+
// than ship a third guess at an in-place reload, stop trying to fake one:
|
|
697
|
+
// tell the user to restart Premiere Pro, which is the one path confirmed
|
|
698
|
+
// to always leave the panel in a correct state.
|
|
699
|
+
function reloadPanel() {
|
|
700
|
+
try { stopBridge(); } catch (e) {}
|
|
701
|
+
log("Please restart Premiere Pro completely to reload the panel (in-place reload isn't reliable in this CEP host).", "err");
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function saveTempDir() {
|
|
705
|
+
tempDir = document.getElementById("tempDir").value.trim();
|
|
706
|
+
log(t("ui.bridge.log.tempDirSaved", { dir: tempDir }));
|
|
707
|
+
// Persist via localStorage
|
|
708
|
+
try {
|
|
709
|
+
localStorage.setItem("mcp_bridge_temp_dir", tempDir);
|
|
710
|
+
} catch (e) {}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// ---- Init ----
|
|
714
|
+
(function init() {
|
|
715
|
+
// ORDERING RULE (ported from AE MCP): schedule the auto-update check
|
|
716
|
+
// FIRST, before any statement that could throw, so a future regression
|
|
717
|
+
// elsewhere in init() can't strand users on a buggy version with no way
|
|
718
|
+
// to receive the fix from the panel itself.
|
|
719
|
+
try {
|
|
720
|
+
setTimeout(checkForUpdates, 500);
|
|
721
|
+
} catch (e) {
|
|
722
|
+
try { console.error("[panel init] scheduling auto-update failed:", e); } catch (_) {}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Translate the static UI first (see ---- i18n ---- above), then add the
|
|
726
|
+
// language selector to Settings. Both are non-fatal.
|
|
727
|
+
try { applyI18n(); } catch (e) { try { log("applyI18n failed: " + e.message, "err"); } catch (_) {} }
|
|
728
|
+
try { renderLanguageSettings(); } catch (e) { try { log("renderLanguageSettings failed: " + e.message, "err"); } catch (_) {} }
|
|
729
|
+
|
|
730
|
+
// Set the default temp dir in the input field
|
|
731
|
+
document.getElementById("tempDir").value = tempDir;
|
|
732
|
+
|
|
733
|
+
// Header badge shipped hardcoded to "v2.0.0" (leftover from an npm
|
|
734
|
+
// package rename, never a real panel content version — the actual panel
|
|
735
|
+
// had stayed at 1.1.1-1.1.7 the whole time) and never updated since.
|
|
736
|
+
// Same bug already fixed in AE MCP's and AutoEdit Pro's panels.
|
|
737
|
+
try { document.getElementById("synthHeaderVersion").textContent = "v" + PANEL_VERSION; } catch (e) {}
|
|
738
|
+
|
|
739
|
+
// Populate the Settings license field from local storage.
|
|
740
|
+
try { loadLicenseUI(); } catch (e) {}
|
|
741
|
+
|
|
742
|
+
// Load premiere.jsx library (extended function library from AdobePremiereProMCP).
|
|
743
|
+
// Same file:// URI cleanup as getLicensePath()/applyUpdate() — wasn't the
|
|
744
|
+
// cause of the "EvalScript error." bug (that was premiere.jsx's own
|
|
745
|
+
// obfuscation, see scripts/package.mjs), but a raw file:// URI here is
|
|
746
|
+
// still wrong on principle, so clean it the same way everywhere.
|
|
747
|
+
var premiereJsxPath = systemPathToFsPath(cs.getSystemPath(SystemPath.EXTENSION)).replace(/\\/g, "/");
|
|
748
|
+
cs.evalScript('$.evalFile("' + premiereJsxPath + '/premiere.jsx")', function(result) {
|
|
749
|
+
log(t("ui.bridge.log.libraryLoaded", { result: (result === "true" || result === "undefined" ? "OK" : result) }));
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
// Restore saved temp dir
|
|
753
|
+
try {
|
|
754
|
+
var saved = localStorage.getItem("mcp_bridge_temp_dir");
|
|
755
|
+
if (saved) {
|
|
756
|
+
tempDir = saved;
|
|
757
|
+
document.getElementById("tempDir").value = tempDir;
|
|
758
|
+
}
|
|
759
|
+
} catch (e) {}
|
|
760
|
+
|
|
761
|
+
// Wire any element with [data-href] to open the URL in the user's default
|
|
762
|
+
// browser (CEP intercepts <a href> navigation; we have to bridge through
|
|
763
|
+
// cep.util.openURLInDefaultBrowser to escape the WebView).
|
|
764
|
+
function openExternal(url) {
|
|
765
|
+
try {
|
|
766
|
+
if (window.cep && cep.util && cep.util.openURLInDefaultBrowser) {
|
|
767
|
+
cep.util.openURLInDefaultBrowser(url);
|
|
768
|
+
} else if (window.__adobe_cep__ && window.__adobe_cep__.openURLInDefaultBrowser) {
|
|
769
|
+
window.__adobe_cep__.openURLInDefaultBrowser(url);
|
|
770
|
+
} else {
|
|
771
|
+
window.open(url, "_blank");
|
|
772
|
+
}
|
|
773
|
+
} catch (e) { /* swallow — non-fatal */ }
|
|
774
|
+
}
|
|
775
|
+
var links = document.querySelectorAll("[data-href]");
|
|
776
|
+
for (var i = 0; i < links.length; i++) {
|
|
777
|
+
(function (el) {
|
|
778
|
+
el.addEventListener("click", function (ev) {
|
|
779
|
+
ev.preventDefault();
|
|
780
|
+
openExternal(el.getAttribute("data-href"));
|
|
781
|
+
});
|
|
782
|
+
})(links[i]);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
log(t("ui.bridge.log.loaded", { name: "Synthetic Premiere MCP" }));
|
|
786
|
+
setStatus("waiting", "ui.bridge.status.ready");
|
|
787
|
+
|
|
788
|
+
// Auto-start if temp dir exists
|
|
789
|
+
if (fs.existsSync(tempDir)) {
|
|
790
|
+
log(t("ui.bridge.log.autoStart"));
|
|
791
|
+
setTimeout(startBridge, 500);
|
|
792
|
+
}
|
|
793
|
+
})();
|