@ps-generator-bridge/generator 0.1.0

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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/dist/contract-C4vydf6-.d.ts +1270 -0
  3. package/dist/contract.d.ts +3 -0
  4. package/dist/contract.js +19 -0
  5. package/dist/contract.js.map +1 -0
  6. package/dist/index.d.ts +8 -0
  7. package/dist/index.js +2482 -0
  8. package/dist/index.js.map +1 -0
  9. package/jsx/Action/autoCutout.jsx +12 -0
  10. package/jsx/Action/redo.jsx +5 -0
  11. package/jsx/Action/removeBackground.jsx +10 -0
  12. package/jsx/Common/alert.jsx +4 -0
  13. package/jsx/Common/debug.jsx +43 -0
  14. package/jsx/Common/event-dispatch.jsx +4 -0
  15. package/jsx/Document/exportDocument.jsx +128 -0
  16. package/jsx/Document/getDocumentInfo.jsx +222 -0
  17. package/jsx/Document/openPsd.jsx +6 -0
  18. package/jsx/Document/openPsdFile.jsx +7 -0
  19. package/jsx/Document/saveDocument.jsx +37 -0
  20. package/jsx/Layer/addImageLayer.jsx +182 -0
  21. package/jsx/Layer/createSelectionStroke.jsx +44 -0
  22. package/jsx/Layer/getActiveLayerID.jsx +1 -0
  23. package/jsx/Layer/getLayerBounds.jsx +114 -0
  24. package/jsx/Layer/getLayerInfo.jsx +323 -0
  25. package/jsx/Layer/getLayerPixmap.jsx +337 -0
  26. package/jsx/Layer/getSelection.jsx +6 -0
  27. package/jsx/Layer/layer.jsx +284 -0
  28. package/jsx/Layer/saveEngineDataToLayer.jsx +26 -0
  29. package/jsx/Layer/setLayerWorkpathMask.jsx +126 -0
  30. package/jsx/Layer/transformLayer.jsx +121 -0
  31. package/jsx/Selection/getSelectionPath.jsx +389 -0
  32. package/jsx/Selection/pathtosvg.jsx +257 -0
  33. package/jsx/Selection/registerEvent.jsx +10 -0
  34. package/jsx/Selection/selectiontopath.jsx +9 -0
  35. package/jsx/polyfills/Array.js +159 -0
  36. package/jsx/polyfills/Function.js +29 -0
  37. package/jsx/polyfills/JSON.js +182 -0
  38. package/jsx/polyfills/Number.js +24 -0
  39. package/jsx/polyfills/Object.js +80 -0
  40. package/jsx/polyfills/String.js +87 -0
  41. package/jsx/types/extendscript/README.md +34 -0
  42. package/jsx/types/extendscript/actions.d.ts +148 -0
  43. package/jsx/types/extendscript/application.d.ts +74 -0
  44. package/jsx/types/extendscript/channel.d.ts +70 -0
  45. package/jsx/types/extendscript/color.d.ts +92 -0
  46. package/jsx/types/extendscript/document.d.ts +110 -0
  47. package/jsx/types/extendscript/enums.d.ts +796 -0
  48. package/jsx/types/extendscript/index.d.ts +504 -0
  49. package/jsx/types/extendscript/layer.d.ts +530 -0
  50. package/jsx/types/extendscript/misc.d.ts +274 -0
  51. package/jsx/types/extendscript/open-options.d.ts +86 -0
  52. package/jsx/types/extendscript/path.d.ts +196 -0
  53. package/jsx/types/extendscript/save-options.d.ts +144 -0
  54. package/jsx/types/extendscript/selection.d.ts +191 -0
  55. package/jsx/types/extendscript/text.d.ts +169 -0
  56. package/main.js +16 -0
  57. package/package.json +75 -0
@@ -0,0 +1,1270 @@
1
+ import { Stream } from 'node:stream';
2
+ import { EventEmitter } from 'node:events';
3
+
4
+ type PsBounds$1 = {
5
+ left: number;
6
+ right: number;
7
+ top: number;
8
+ bottom: number;
9
+ };
10
+ type PsRect = {
11
+ x: number;
12
+ y: number;
13
+ width: number;
14
+ height: number;
15
+ };
16
+ type PsPixmap = {
17
+ format: number;
18
+ width: number;
19
+ height: number;
20
+ rowBytes: number;
21
+ colorMode: number;
22
+ channelCount: number;
23
+ bitsPerChannel: number;
24
+ pixels: Buffer;
25
+ bytesPerPixel: number;
26
+ padding: number;
27
+ bounds: PsBounds$1;
28
+ resolution: number;
29
+ getPixel: (n: number) => any;
30
+ readChannel: (n?: number) => any;
31
+ };
32
+ interface PsDocumentInfo {
33
+ version: string;
34
+ timeStamp: number;
35
+ count: number;
36
+ id: number;
37
+ file: string;
38
+ bounds: PsBounds$1;
39
+ selection: number[];
40
+ resolution: number;
41
+ globalLight: {
42
+ angle: number;
43
+ altitude: number;
44
+ };
45
+ generatorSettings?: any;
46
+ profile: string;
47
+ mode: string;
48
+ depth: number;
49
+ layers: any[];
50
+ }
51
+
52
+ /**
53
+ * The plugin's contract with generator-core, typed faithfully from
54
+ * `generator-core/lib/generator.js` (every public method, the low-level
55
+ * `_sendJSX*` transport, and the underscore-prefixed internals). Public
56
+ * thenable methods are typed as `Promise<T>` (consumers `await` them); the
57
+ * `_sendJSX*` methods return a `Deferred<T>` — generator-core's Q deferred,
58
+ * which streams progress messages for pixmap/shape protocols.
59
+ *
60
+ * This is an interface (not a class): the plugin never `instanceof`-checks the
61
+ * generator and only depends on the surface. A test fake cannot satisfy the
62
+ * full required surface, so tests build one via the `fakeGenerator()` factory
63
+ * (a single `as unknown as FakeGenerator & PsGenerator` cast) — see
64
+ * `test/fakeGenerator.ts`.
65
+ */
66
+
67
+
68
+ /**
69
+ * generator-core's Q `Deferred` shape — the return of `_sendJSXString` /
70
+ * `_sendJSXFile`. The promise streams `progress` / `fail` notifications (used
71
+ * by the pixmap/shape protocols) and settles via `resolve` / `reject`. Only the
72
+ * members the plugin reaches are modelled.
73
+ */
74
+ interface Deferred<T> {
75
+ promise: {
76
+ then<TResult>(
77
+ onFulfilled?: (value: T) => TResult | PromiseLike<TResult>,
78
+ onRejected?: (reason: unknown) => TResult | PromiseLike<TResult>
79
+ ): Promise<TResult>;
80
+ progress(fn: (message: { type: string; value: unknown }) => void): void;
81
+ fail(fn: (reason: unknown) => void): void;
82
+ finally(fn: () => void): void;
83
+ };
84
+ resolve(value?: T): void;
85
+ reject(reason?: unknown): void;
86
+ notify(message: unknown): void;
87
+ }
88
+
89
+ declare namespace PsGenerator {
90
+ /** Settings for `getPixmap` / `getDocumentPixmap` (generator.js:1201-1208). */
91
+ export interface GetPixmapSettings {
92
+ boundsOnly?: boolean;
93
+ inputRect?: PsBounds$1;
94
+ outputRect?: PsBounds$1;
95
+ scaleX?: number;
96
+ scaleY?: number;
97
+ clipBounds?: PsBounds$1;
98
+ useJPGEncoding?: string;
99
+ useSmartScaling?: boolean;
100
+ convertToWorkingRGBProfile?: boolean;
101
+ useICCProfile?: string;
102
+ getICCProfileData?: boolean;
103
+ allowDither?: boolean;
104
+ useColorSettingsDither?: boolean;
105
+ interpolationType?: string;
106
+ forceSmartPSDPixelScaling?: boolean;
107
+ clipToDocumentBounds?: boolean;
108
+ maxDimension?: number;
109
+ compId?: number;
110
+ compIndex?: number;
111
+ includeAncestorMasks?: boolean;
112
+ includeAdjustors?: boolean;
113
+ includeChildren?: boolean;
114
+ includeClipBase?: boolean;
115
+ includeClipped?: boolean;
116
+ }
117
+
118
+ /** Settings for `savePixmap` / `streamPixmap` (generator.js:1959-1977). */
119
+ export interface SavePixmapSettings {
120
+ format: string;
121
+ quality?: number;
122
+ lossless?: boolean;
123
+ ppi?: number;
124
+ padding?: PsBounds$1;
125
+ extract?: { x: number; y: number; width: number; height: number };
126
+ background?: [number, number, number, number];
127
+ _scale?: number;
128
+ usePngquant?: boolean;
129
+ useFlite?: boolean;
130
+ useJPGEncoding?: boolean;
131
+ }
132
+
133
+ /** Flags overriding `getDocumentInfo` defaults (generator.js:720-748). */
134
+ export interface DocumentInfoFlags {
135
+ compInfo?: boolean;
136
+ imageInfo?: boolean;
137
+ layerInfo?: boolean;
138
+ expandSmartObjects?: boolean;
139
+ getTextStyles?: boolean;
140
+ getFullTextStyles?: boolean;
141
+ selectedLayers?: boolean;
142
+ getCompLayerSettings?: boolean;
143
+ getDefaultLayerFX?: boolean;
144
+ getPathData?: boolean;
145
+ }
146
+
147
+ /** A layer spec: a layer id, or an index range with optional hidden indices. */
148
+ export type LayerSpec =
149
+ | number
150
+ | { firstLayerIndex: number; lastLayerIndex: number; hidden?: number[] };
151
+
152
+ /** Result of `getGuides` (generator.js:2047). */
153
+ export interface Guides {
154
+ horizontal: number[];
155
+ vertical: number[];
156
+ }
157
+
158
+ /** Result of `getMenuState` / `checkPluginCompatibility`. */
159
+ export interface MenuState {
160
+ enabled: boolean;
161
+ checked: boolean;
162
+ }
163
+
164
+ export interface PluginCompatibility {
165
+ compatible: boolean;
166
+ message: string | null;
167
+ }
168
+
169
+ /** Scaling settings for `getPixmapParams` (generator.js:1566-1574). */
170
+ export interface PixmapParamsSettings {
171
+ width?: number;
172
+ height?: number;
173
+ scaleX?: number;
174
+ scaleY?: number;
175
+ scale?: number;
176
+ }
177
+ }
178
+
179
+ interface PsGenerator {
180
+ // --- lifecycle -----------------------------------------------------------
181
+
182
+ /** Launch the generator: connect to Photoshop, resolve with self. */
183
+ start(options?: Record<string, any>): Promise<PsGenerator>;
184
+
185
+ /** Disconnect from Photoshop. */
186
+ shutdown(): void;
187
+
188
+ /** Whether the Photoshop connection is currently live. */
189
+ isConnected(): boolean;
190
+
191
+ /** Send a keep-alive; resolves true when Photoshop acknowledges. */
192
+ checkConnection(): Promise<boolean>;
193
+
194
+ // --- jsx evaluation ------------------------------------------------------
195
+
196
+ /** Evaluate a local jsx file with optional params; resolves `message.value`. */
197
+ evaluateJSXFile(
198
+ path: string,
199
+ params?: Record<string, any>,
200
+ sharedEngineSafe?: boolean
201
+ ): Promise<any>;
202
+
203
+ /** `evaluateJSXFile` forced into the shared script engine. */
204
+ evaluateJSXFileSharedSafe(path: string, params?: Record<string, any>): Promise<any>;
205
+
206
+ /** Evaluate a jsx string; resolves `message.value`. */
207
+ evaluateJSXString(s: string, sharedEngineSafe?: boolean): Promise<any>;
208
+
209
+ /** `evaluateJSXString` forced into the shared script engine. */
210
+ evaluateJSXStringSharedSafe(s: string): Promise<any>;
211
+
212
+ /** Low-level jsx-string transport (private in generator-core). Returns the
213
+ * raw deferred so callers can subscribe to the progress stream. */
214
+ _sendJSXString(s: string, deferred?: Deferred<any>, sharedEngineSafe?: boolean): Deferred<any>;
215
+
216
+ /** Low-level jsx-file transport (private in generator-core). Returns the raw
217
+ * deferred; the pixmap/shape protocols subscribe to its progress messages. */
218
+ _sendJSXFile(
219
+ path: string,
220
+ params?: Record<string, any>,
221
+ sharedEngineSafe?: boolean
222
+ ): Deferred<any>;
223
+
224
+ // --- UI / clipboard ------------------------------------------------------
225
+
226
+ /** Show a Photoshop alert dialog. */
227
+ alert(message: string, stringReplacements?: string): void;
228
+
229
+ /** Copy a string to the system clipboard. */
230
+ copyToClipboard(str: string): void;
231
+
232
+ // --- Photoshop environment ----------------------------------------------
233
+
234
+ /** Resolve to the Photoshop install directory path. */
235
+ getPhotoshopPath(): Promise<string>;
236
+
237
+ /** Resolve to the Photoshop executable directory (inside the .app on Mac). */
238
+ getPhotoshopExecutableLocation(): Promise<string>;
239
+
240
+ /** Resolve to the Photoshop locale string. */
241
+ getPhotoshopLocale(): Promise<string>;
242
+
243
+ /** Resolve to the Photoshop version, e.g. "19.0.0". */
244
+ getPhotoshopVersion(): Promise<string>;
245
+
246
+ // --- menus ---------------------------------------------------------------
247
+
248
+ /** Register a menu item; resolves once Photoshop has rebuilt the menu. */
249
+ addMenuItem(name: string, displayName: string, enabled: boolean, checked: boolean): Promise<void>;
250
+
251
+ /** Toggle (and optionally rename) an existing menu item. */
252
+ toggleMenu(name: string, enabled: boolean, checked: boolean, displayName?: string): Promise<void>;
253
+
254
+ /** Read the enabled/checked state of a menu item, or null if absent. */
255
+ getMenuState(name: string): PsGenerator.MenuState | null;
256
+
257
+ // --- documents -----------------------------------------------------------
258
+
259
+ /** Resolve to the ids of all open documents. */
260
+ getOpenDocumentIDs(): Promise<number[]>;
261
+
262
+ /** Resolve to a document's info (layers, comps, image, …); rejects with
263
+ * "No Open Document" when none is open. */
264
+ getDocumentInfo(
265
+ documentId?: number,
266
+ flags?: PsGenerator.DocumentInfoFlags
267
+ ): Promise<PsDocumentInfo>;
268
+
269
+ // --- generator settings --------------------------------------------------
270
+
271
+ /** Get a layer's generator settings for a plugin. */
272
+ getLayerSettingsForPlugin(documentId: number, layerId: number, pluginId: string): Promise<any>;
273
+
274
+ /** Set a layer's generator settings for a plugin. */
275
+ setLayerSettingsForPlugin(
276
+ settings: Record<string, any>,
277
+ layerId: number,
278
+ pluginId: string
279
+ ): Promise<any>;
280
+
281
+ /** Get document-wide generator settings for a plugin. */
282
+ getDocumentSettingsForPlugin(documentId: number, pluginId: string): Promise<any>;
283
+
284
+ /** Set document-wide generator settings for a plugin. */
285
+ setDocumentSettingsForPlugin(
286
+ settings: Record<string, any>,
287
+ documentId: number,
288
+ pluginId: string
289
+ ): Promise<any>;
290
+
291
+ /** Extract and parse generator settings from a document object. */
292
+ extractDocumentSettings(document: Record<string, any>, pluginId?: string): any;
293
+
294
+ // --- Photoshop events ----------------------------------------------------
295
+
296
+ /** Subscribe to one or more Photoshop events over the connection. */
297
+ subscribeToPhotoshopEvents(events: string | string[]): Promise<boolean>;
298
+
299
+ /** Register a listener for a Photoshop event. */
300
+ onPhotoshopEvent(event: string, listener: (event: any) => void): void;
301
+
302
+ /** Register a one-shot listener for a Photoshop event. */
303
+ oncePhotoshopEvent(event: string, listener: (event: any) => void): void;
304
+
305
+ /** Remove a Photoshop event listener. */
306
+ removePhotoshopEventListener(event: string, listener: (event: any) => void): void;
307
+
308
+ /** List current listeners for a Photoshop event. */
309
+ photoshopEventListeners(event: string): Array<(event: any) => void>;
310
+
311
+ /** Emit a Photoshop event to its listeners. */
312
+ emitPhotoshopEvent(event: string, data?: any): void;
313
+
314
+ // --- pixmaps -------------------------------------------------------------
315
+
316
+ /** Get a pixmap (or bounds) for a layer id. */
317
+ getPixmap(
318
+ documentId: number,
319
+ layerSpec: number,
320
+ settings: PsGenerator.GetPixmapSettings
321
+ ): Promise<PsPixmap>;
322
+
323
+ /** Get a pixmap (or bounds) for an index range of layers. */
324
+ getPixmap(
325
+ documentId: number,
326
+ layerSpec: {
327
+ firstLayerIndex: number;
328
+ lastLayerIndex: number;
329
+ hidden?: number[];
330
+ },
331
+ settings: PsGenerator.GetPixmapSettings
332
+ ): Promise<PsPixmap>;
333
+
334
+ /** Get a pixmap of the whole document in its current visibility state. */
335
+ getDocumentPixmap(documentId: number, settings: PsGenerator.GetPixmapSettings): Promise<PsPixmap>;
336
+
337
+ /** Compute the `getPixmap` settings for a target scaling/padding. */
338
+ getPixmapParams(
339
+ settings: PsGenerator.PixmapParamsSettings,
340
+ staticInputBounds: PsBounds$1,
341
+ visibleInputBounds: PsBounds$1,
342
+ paddedInputBounds: PsBounds$1,
343
+ clipToBounds?: PsBounds$1
344
+ ): PsGenerator.GetPixmapSettings;
345
+
346
+ /** Write a pixmap to `path` via ImageMagick; resolves to the written path. */
347
+ savePixmap(
348
+ pixmap: PsPixmap,
349
+ path: string,
350
+ settings: PsGenerator.SavePixmapSettings
351
+ ): Promise<string>;
352
+
353
+ /** Stream a pixmap's converted bytes to `outputStream`. */
354
+ streamPixmap(
355
+ pixmap: PsPixmap,
356
+ outputStream: Stream,
357
+ settings: PsGenerator.SavePixmapSettings
358
+ ): Promise<void>;
359
+
360
+ /** Parse and coerce a pixmap's numeric properties in place. */
361
+ _parsePixmapProperties(pixmap: PsPixmap): void;
362
+
363
+ /** Parse and coerce a save-pixmap settings object in place. */
364
+ _parsePixmapSaveSettings(settings: PsGenerator.SavePixmapSettings): void;
365
+
366
+ // --- shapes / svg / guides ----------------------------------------------
367
+
368
+ /** Resolve to a layer's path/shape data, or reject "layer does not contain a shape". */
369
+ getLayerShape(documentId: number, layerId: number): Promise<{ path: any }>;
370
+
371
+ /** Resolve to an SVG string for the layer, optionally scaled. */
372
+ getSVG(documentId: number, layerId: number, settings?: { scale?: number }): Promise<string>;
373
+
374
+ /** Resolve to the horizontal/vertical guide positions in a document. */
375
+ getGuides(documentId: number): Promise<PsGenerator.Guides>;
376
+
377
+ /** Recursively compute the containing bounds of a layer tree (or undefined). */
378
+ getDeepBounds(layer: any): PsBounds$1 | undefined;
379
+
380
+ // --- bounds helpers (private) -------------------------------------------
381
+
382
+ /** True when a bounds rect has zero or non-finite area. */
383
+ _isBoundEmpty(bounds: PsBounds$1): boolean;
384
+
385
+ /** Smallest rect containing both bounds. */
386
+ _unionBounds(boundsA: PsBounds$1, boundsB: PsBounds$1): PsBounds$1;
387
+
388
+ /** Largest rect inside both bounds (zeroed when empty). */
389
+ _intersectBounds(boundsA: PsBounds$1, boundsB: PsBounds$1): PsBounds$1;
390
+
391
+ /** Union of a layer's raster + vector mask bounds, or undefined. */
392
+ _getTotalMaskBounds(bounds: any): PsBounds$1 | undefined;
393
+
394
+ // --- hidden layers (private) --------------------------------------------
395
+
396
+ /** Recursively collect indices of hidden layers (children of hidden groups
397
+ * are hidden too). Used by `getDocumentPixmap`. */
398
+ _computeHiddenLayers(parent: { layers: any[] }, hideAll?: boolean): number[];
399
+
400
+ // --- style (private) -----------------------------------------------------
401
+
402
+ /** Resolve to extracted style info for a document (private, unstable API). */
403
+ _getStyleInfo(documentId: number, flags?: { selectedLayers?: boolean }): Promise<any>;
404
+
405
+ // --- settings parsing (private) -----------------------------------------
406
+
407
+ /** Parse a `{ json }`-wrapped settings blob into an object. */
408
+ _parseDocumentSettings(settings: { json?: string } | any): any;
409
+
410
+ /** Register/subscribe a Photoshop event listener helper (private). */
411
+ _registerPhotoshopEventHelper(
412
+ event: string,
413
+ listener: (event: any) => void,
414
+ isOnce: boolean
415
+ ): void;
416
+
417
+ // --- headlights (private) -----------------------------------------------
418
+
419
+ /** Log a Headlights event (Adobe-internal). */
420
+ _logHeadlights(event: string): Promise<void>;
421
+
422
+ /** Log a plugin-loaded Headlights record (Adobe-internal). */
423
+ _logHeadlightsPluginLoaded(pluginName: string, pluginVersion: string): Promise<void>;
424
+
425
+ // --- plugin management ---------------------------------------------------
426
+
427
+ /** Read a plugin's `package.json` metadata; throws on invalid input. */
428
+ getPluginMetadata(directory: string): any;
429
+
430
+ /** Check a plugin's `generator-core-version` compatibility. */
431
+ checkPluginCompatibility(metadata: any): PsGenerator.PluginCompatibility;
432
+
433
+ /** Load a plugin from a directory; throws on incompatibility / failure. */
434
+ loadPlugin(directory: string): void;
435
+
436
+ /** Return an already-loaded plugin by name, or null. */
437
+ getPlugin(name: string): any | null;
438
+
439
+ // --- custom options ------------------------------------------------------
440
+
441
+ /** Resolve to the custom-options table for a plugin (persists until PS quit). */
442
+ getCustomOptions(pluginId: string): Promise<Record<string, any>>;
443
+
444
+ /** Replace the custom-options table for a plugin (no merge). */
445
+ setCustomOptions(pluginId: string, settings: Record<string, any>): Promise<void>;
446
+
447
+ /** Update a single custom option key for a plugin. */
448
+ updateCustomOption(pluginId: string, key: string, value: unknown): Promise<void>;
449
+
450
+ /** Delete a single custom option key for a plugin. */
451
+ deleteCustomOption(pluginId: string, key: string): Promise<void>;
452
+
453
+ // --- websocket servers (generator-core built-in) ------------------------
454
+
455
+ /** Start a generator-core websocket server for a plugin; resolves to the port. */
456
+ startWebsocketServer(
457
+ pluginId: string,
458
+ desiredPort?: number,
459
+ domain?: any,
460
+ origin?: string
461
+ ): Promise<number>;
462
+
463
+ /** Stop a plugin's generator-core websocket server. */
464
+ stopWebsocketServer(pluginId: string): Promise<void>;
465
+
466
+ // --- interpolation constants (generator.js:1102-1135) -------------------
467
+
468
+ readonly INTERPOLATION_NEAREST_NEIGHBOR: "nearestNeighbor";
469
+ readonly INTERPOLATION_BILINEAR: "bilinear";
470
+ readonly INTERPOLATION_BICUBIC: "bicubic";
471
+ readonly INTERPOLATION_BICUBIC_SMOOTHER: "bicubicSmoother";
472
+ readonly INTERPOLATION_BICUBIC_SHARPER: "bicubicSharper";
473
+ readonly INTERPOLATION_BICUBIC_AUTOMATIC: "bicubicAutomatic";
474
+ readonly INTERPOLATION_PRESERVE_DETAILS_UPSCALE: "preserveDetailsUpscale";
475
+ readonly INTERPOLATION_AUTOMATIC: "automaticInterpolation";
476
+ }
477
+
478
+ interface Logger {
479
+ debug(message: string, ...args: unknown[]): void;
480
+ info(message: string, ...args: unknown[]): void;
481
+ warn(message: string, ...args: unknown[]): void;
482
+ error(message: string, ...args: unknown[]): void;
483
+ }
484
+
485
+ /**
486
+ * A layer spec: either a layer id, or an index range plus the indices of layers
487
+ * to hide (the form Photoshop's `getLayerPixmap.jsx` accepts). Modeled here as a
488
+ * wire type (RFC 0008) so the protocol is self-contained; the generator's image
489
+ * module re-exports it for its plugin-facing API.
490
+ */
491
+ type LayerSpec = number | {
492
+ firstLayerIndex: number;
493
+ lastLayerIndex: number;
494
+ hidden: number[];
495
+ };
496
+ /**
497
+ * The result of an image `@ws` method (RFC 0008). `data` is an out-of-the-box
498
+ * image string the client can drop straight into an `<img src>`:
499
+ * `data:image/png;base64,...` when inlined, or `https://...` when a `CosService`
500
+ * uploaded it. The client tells them apart by the `data`/`http` prefix — there
501
+ * is deliberately no separate discriminator field. `bounds`/`width`/`height`
502
+ * carry the same geometry as the module-internal `ImageResult`.
503
+ */
504
+ interface WsImageResult {
505
+ data: string;
506
+ bounds: PsBounds;
507
+ width: number;
508
+ height: number;
509
+ }
510
+ interface PsBounds {
511
+ left: number;
512
+ right: number;
513
+ top: number;
514
+ bottom: number;
515
+ }
516
+
517
+ /**
518
+ * The plugin contract a Plugin depends on (RFC 0003) — a deliberately narrowed
519
+ * view of the server's `PsBridgeHost`. Exposes only what a Plugin needs: the JSX
520
+ * seam and the feature-module accessors (all typed by the generator's own
521
+ * contract interfaces), plus the listen-only event stream. Excludes
522
+ * broadcast/emit/server/the raw generator handle — a Plugin manages its own
523
+ * clients through BasePlugin.broadcast/send.
524
+ *
525
+ * This is the one hand-written piece of the plugin surface that stays in the
526
+ * SDK: it is a *curation* of the host, not a mirror of it. Its member types are
527
+ * imported (type-only) from the generator contract so they can never drift from
528
+ * the implementation. The server's `PsBridgeHost implements PluginHost`;
529
+ * external Plugins only ever see this interface, so they depend on the SDK alone
530
+ * at runtime, never on the server package.
531
+ */
532
+ interface PluginHost {
533
+ readonly jsx: JsxRunnerApi;
534
+ /** Feature modules, reached by short key (e.g. `plugin.modules.layer`). */
535
+ readonly modules: {
536
+ layer: LayerModuleApi;
537
+ document: DocumentModuleApi;
538
+ action: ActionModuleApi;
539
+ image: ImageModuleApi;
540
+ };
541
+ /** Typed, listen-only Photoshop event stream (lazy subscribe). */
542
+ readonly events: PhotoshopEvents;
543
+ /**
544
+ * Optional object-storage upload service (RFC 0008). Present only when the host
545
+ * has COS configured via the environment; undefined otherwise. A plugin guards
546
+ * on it: `if (this.plugin.cos) await this.plugin.cos.uploadObject(bytes)`.
547
+ */
548
+ readonly cos?: CosServiceApi;
549
+ }
550
+
551
+ /**
552
+ * A single progress notification emitted by Photoshop while a jsx file
553
+ * evaluates via `_sendJSXFile`. `type` is `"javascript"` (an evaluation result
554
+ * — e.g. a bounds object), `"pixmap"` (a raw pixmap buffer), or `"iccProfile"`
555
+ * (a raw ICC profile buffer).
556
+ */
557
+ interface JsxProgressMessage {
558
+ type: "javascript" | "pixmap" | "iccProfile" | string;
559
+ value: unknown;
560
+ }
561
+ /**
562
+ * Typed handle to an in-flight `_sendJSXFile` evaluation. `onProgress`/`onFail`
563
+ * subscribe to the underlying deferred's streams; `resolve`/`reject` let the
564
+ * caller signal completion (required — the deferred won't settle on its own).
565
+ * Isolates the `_sendJSXFile` touchpoint inside the JSX seam so callers like
566
+ * `ImageModule.getPixmap` never reach generator-core's transport directly.
567
+ */
568
+ interface JsxChannel {
569
+ onProgress(fn: (message: JsxProgressMessage) => void): void;
570
+ onFail(fn: (err: unknown) => void): void;
571
+ resolve(): void;
572
+ reject(err?: unknown): void;
573
+ }
574
+ /**
575
+ * The slice of JsxRunner a Plugin reaches through `plugin.jsx` (RFC 0003 /
576
+ * RFC 0005). The concrete JsxRunner `implements` this (and `forPlugin` returns a
577
+ * scoped view that also implements it), so the plugin contract can never drift
578
+ * from the implementation; the SDK re-exports it (via src/contract.ts) as the
579
+ * type of `PluginHost.jsx`. Excludes lifecycle (`init`) and the low-level pixmap
580
+ * channel (`openJSXFile`) — plugins only run jsx by name (or raw string).
581
+ *
582
+ * `execute` resolves against *this handle's own* jsx scope: the built-in `jsx/`
583
+ * tree for the host's root runner, or the plugin's own `jsx/` dir for the scoped
584
+ * view `plugin.jsx` returns. `executeBuiltin` always targets the built-in tree,
585
+ * so a plugin can reach a host domain (e.g. `Document/getDocumentInfo`) without
586
+ * knowing its own id or dir. `run` takes a raw script and is scope-independent.
587
+ */
588
+ interface JsxRunnerApi {
589
+ execute<T = unknown>(name: string, params?: Record<string, unknown>, sharedEngineSafe?: boolean): Promise<T>;
590
+ executeBuiltin<T = unknown>(name: string, params?: Record<string, unknown>, sharedEngineSafe?: boolean): Promise<T>;
591
+ run<T = unknown>(script: string): Promise<T>;
592
+ }
593
+ /**
594
+ * Runs packaged jsx by name (ADR 0008). Resolves a name like
595
+ * `Document/getDocumentInfo` to its physical path under the bundle's `jsx/`
596
+ * directory, hands it to the injected generator's `evaluateJSXFile`, and
597
+ * normalizes the result.
598
+ *
599
+ * Returns `message.value` verbatim — it does NOT `JSON.parse` for the caller;
600
+ * the `T` type parameter is a labelling convenience only. A string value
601
+ * starting with `"Error:"` becomes a thrown `Error` carrying the remainder.
602
+ *
603
+ * jsx text caching is handled by generator-core's `_sendJSXCache`, so this seam
604
+ * adds none of its own. `__dirname` is `dist` for all bundled code, so
605
+ * `polyfillsDir` defaults to `dist/jsx/polyfills`.
606
+ *
607
+ * ExtendScript's default engine persists globals across evaluations, so the
608
+ * polyfills injected once in `init()` remain available to every later
609
+ * `execute` (and to `run` when it uses the default engine). The default engine
610
+ * is the only one primed — `sharedEngineSafe` callers use a separate engine
611
+ * that does NOT receive the polyfills.
612
+ */
613
+ declare class JsxRunner implements JsxRunnerApi {
614
+ private readonly generator;
615
+ private readonly logger;
616
+ private readonly polyfillsDir;
617
+ private readonly jsxDir;
618
+ private polyfillsCache;
619
+ constructor(generator: PsGenerator, logger: Logger, polyfillsDir?: string);
620
+ /**
621
+ * A jsx runner scoped to a plugin's own `jsx/` dir (RFC 0005). The returned
622
+ * handle's `execute` resolves under `dir`, while `executeBuiltin` still targets
623
+ * the built-in tree; `run` is unchanged. The scope is the *only* per-plugin
624
+ * state — there is no shared mutable registry; the host builds one of these per
625
+ * plugin in `hostFor` and injects it as `plugin.jsx`.
626
+ */
627
+ forPlugin(dir: string): JsxRunnerApi;
628
+ /**
629
+ * Resolve a jsx name to an absolute `.jsx` path under `baseDir`. The name may
630
+ * carry domain subdirs (e.g. `Document/getDocumentInfo`). No escape guard: jsx
631
+ * names come from trusted in-process code (a module or a plugin's own source),
632
+ * which already runs arbitrary JS, so a guard would add no boundary.
633
+ */
634
+ private resolvePath;
635
+ /**
636
+ * Prime the default ExtendScript engine with ES polyfills. Reads every
637
+ * `*.js` file under `polyfillsDir` (recursively, sorted by relative path for
638
+ * deterministic concatenation order), concatenates them into `polyfillsCache`,
639
+ * and evaluates the bundle once. Must be awaited before any `execute` call
640
+ * that depends on the polyfills; `PsBridgeHost.onInit` does this before
641
+ * `server.listen`.
642
+ *
643
+ * Missing dir -> throw (packaging bug). Empty dir -> no-op. Injection
644
+ * returning `"Error:…"` or rejecting -> throw, so a broken polyfill surfaces
645
+ * at startup rather than as a runtime `find is not a function`.
646
+ */
647
+ init(): Promise<void>;
648
+ /**
649
+ * Run the jsx registered under `name` in the built-in `jsx/` tree (domain
650
+ * subdirs included, e.g. `Document/getDocumentInfo`). `params` are inlined into
651
+ * the script; `sharedEngineSafe` opts into Photoshop's shared script engine.
652
+ * The root runner's own scope *is* the built-in tree, so `execute` and
653
+ * `executeBuiltin` coincide here; a plugin's scoped view (see `forPlugin`)
654
+ * splits them apart.
655
+ */
656
+ execute<T = unknown>(name: string, params?: Record<string, unknown>, sharedEngineSafe?: boolean): Promise<T>;
657
+ /**
658
+ * Alias of `execute` on the root runner — always the built-in tree. Present so
659
+ * the root satisfies `JsxRunnerApi` alongside the scoped view; the scoped view
660
+ * overrides `execute` (plugin dir) while delegating `executeBuiltin` here.
661
+ */
662
+ executeBuiltin<T = unknown>(name: string, params?: Record<string, unknown>, sharedEngineSafe?: boolean): Promise<T>;
663
+ /**
664
+ * Run the jsx under `name` resolved against `baseDir`. The seam through which
665
+ * both the root runner (built-in tree) and the scoped view (a plugin's dir)
666
+ * reach Photoshop; keeps path resolution + result normalization in one place.
667
+ *
668
+ * @internal Server-internal — not on `JsxRunnerApi`, not reachable by plugins.
669
+ * Public only so the sibling `ScopedJsx` can delegate to it.
670
+ */
671
+ executeIn<T = unknown>(baseDir: string, name: string, params?: Record<string, unknown>, sharedEngineSafe?: boolean): Promise<T>;
672
+ /**
673
+ * Evaluate an arbitrary jsx string in the default ExtendScript engine (the
674
+ * same engine `init()` primed with polyfills). No `sharedEngineSafe` opt-out:
675
+ * polyfills live only in the default engine, so a shared-engine variant would
676
+ * be silently un-polyfilled. Return value follows the same convention as
677
+ * `run` — verbatim, with `"Error:"`-prefixed strings turned into thrown
678
+ * Errors.
679
+ */
680
+ run<T = unknown>(script: string): Promise<T>;
681
+ /**
682
+ * Open a built-in packaged jsx file via the low-level `_sendJSXFile` channel
683
+ * and return a typed handle to its in-flight evaluation. A root-runner-only
684
+ * seam (not on `JsxRunnerApi`): the only caller is `ImageModule.getPixmap`.
685
+ * Plugins reach pixmaps through `plugin.modules.image`, not this channel.
686
+ *
687
+ * Unlike `run` (which
688
+ * awaits a single resolved value), this exposes the raw progress stream so
689
+ * the caller can collect the multi-message responses Photoshop emits for
690
+ * pixmap-producing scripts (bounds + pixmap + optional ICC profile). The
691
+ * caller owns completion: it must call `channel.resolve()` once it has
692
+ * received every message it expected, or `channel.reject(err)` on failure.
693
+ *
694
+ * `sharedEngineSafe` defaults to `true` to match the pixmap protocol
695
+ * (generator-core's `getPixmap` / `getDocumentPixmap` both use the shared
696
+ * engine).
697
+ */
698
+ openJSXFile(name: string, params?: Record<string, unknown>, sharedEngineSafe?: boolean): JsxChannel;
699
+ /**
700
+ * Shared result normalization for `run` and `execute`: a string starting with
701
+ * `"Error:"` becomes a thrown `Error` carrying the remainder; everything else
702
+ * is returned verbatim (no `JSON.parse` — `T` is a labelling convenience).
703
+ */
704
+ private normalizeJsxResult;
705
+ /**
706
+ * Recursively list `*.js` files under `polyfillsDir`, sorted by relative path
707
+ * (POSIX-normalized) so concatenation order is stable across platforms and
708
+ * polyfills that depend on each other load in a fixed sequence. Throws if the
709
+ * directory itself is missing.
710
+ */
711
+ private collectPolyfillFiles;
712
+ }
713
+
714
+ /**
715
+ * Layer/document bounds in pixels. The console logged this as `[Object]` (not
716
+ * expanded); this is the standard generator bounds shape.
717
+ */
718
+ interface Bounds {
719
+ top: number;
720
+ left: number;
721
+ bottom: number;
722
+ right: number;
723
+ }
724
+ /** One entry in `imageChanged.layers` — a layer touched by the change. */
725
+ interface ImageChangedLayer {
726
+ /** Layer id (stable across the session). */
727
+ id: number;
728
+ /** Present (true) when the layer's pixels changed. */
729
+ pixels?: boolean;
730
+ /** Present (true) when the layer was removed in this change. */
731
+ removed?: boolean;
732
+ /** Layer bounds; present on geometry/pixel changes. */
733
+ bounds?: Bounds;
734
+ }
735
+ /**
736
+ * Payload of the `imageChanged` event. Fields are a union of everything PS may
737
+ * send; only `version` / `timeStamp` / `count` / `id` are always present. A
738
+ * single event carries *either* metadata flags (`metaDataOnly`, `selection`),
739
+ * *or* `layers`, *or* document-level flags (`active` / `file` / `closed`).
740
+ */
741
+ interface ImageChangedEvent {
742
+ /** Generator protocol version, e.g. "1.6.1". */
743
+ version: string;
744
+ /** Seconds since epoch (float), e.g. 1782455135.936. */
745
+ timeStamp: number;
746
+ /** Per-document monotonically increasing change counter (resets per doc id). */
747
+ count: number;
748
+ /** Document id this change belongs to. */
749
+ id: number;
750
+ /** True on the first event for a doc / on activation. */
751
+ active?: boolean;
752
+ /** Document title or full path, e.g. "Test-恢复的.psd" or "C:\\...\\Test.psd". */
753
+ file?: string;
754
+ /** True when the document was closed. */
755
+ closed?: boolean;
756
+ /** True when only metadata changed (no pixel/layer body). */
757
+ metaDataOnly?: boolean;
758
+ /** Selected layer indices; empty array when the selection is cleared. */
759
+ selection?: number[];
760
+ /** Layers touched by this change (pixel edits, bounds, removals). */
761
+ layers?: ImageChangedLayer[];
762
+ }
763
+ /**
764
+ * Map of Photoshop event name -> payload type passed to the listener. Shapes
765
+ * marked "observed" were confirmed from live PS output; the rest are inferred
766
+ * from the generator protocol docs.
767
+ */
768
+ interface PhotoshopEventMap {
769
+ /** [workspace display name] (inferred). */
770
+ workspaceChanged: string;
771
+ /** Tool name, e.g. "paintbrushTool" / "moveTool" (observed). */
772
+ toolChanged: string;
773
+ /** "enter" | "exit" (inferred). */
774
+ quickMaskStateChanged: string;
775
+ /** Document id (observed). */
776
+ documentChanged: number;
777
+ /** Document id of the closed document (observed). */
778
+ closedDocument: number;
779
+ /** Document id (inferred). */
780
+ newDocumentViewCreated: number;
781
+ /** Document id (inferred). */
782
+ activeViewChanged: number;
783
+ /** Document id of the now-current document (observed). */
784
+ currentDocumentChanged: number;
785
+ /** Color as 6-character hex value (inferred). */
786
+ backgroundColorChanged: string;
787
+ /** Color as 6-character hex value (inferred). */
788
+ foregroundColorChanged: string;
789
+ /** Image/document change descriptor (observed). */
790
+ imageChanged: ImageChangedEvent;
791
+ }
792
+ /** Listener for a given Photoshop event key. */
793
+ type PhotoshopEventListener<K extends keyof PhotoshopEventMap> = (payload: PhotoshopEventMap[K]) => void;
794
+ /**
795
+ * Listen-only typed surface a Plugin reaches through `plugin.events` /
796
+ * `this.events`. `EventManager` (an `EventEmitter`) `implements` this; `emit` is
797
+ * deliberately excluded — a Plugin subscribes, it never dispatches Photoshop
798
+ * events.
799
+ */
800
+ interface PhotoshopEvents {
801
+ on<K extends keyof PhotoshopEventMap>(event: K, listener: PhotoshopEventListener<K>): this;
802
+ once<K extends keyof PhotoshopEventMap>(event: K, listener: PhotoshopEventListener<K>): this;
803
+ off<K extends keyof PhotoshopEventMap>(event: K, listener: PhotoshopEventListener<K>): this;
804
+ }
805
+ type Listener<K extends keyof PhotoshopEventMap> = PhotoshopEventListener<K>;
806
+ /**
807
+ * Owns the plugin's Photoshop event subscriptions, exposed as a typed
808
+ * `EventEmitter`. Held by `PsBridgeHost` (see `plugin.events`).
809
+ *
810
+ * Subscriptions are lazy: the manager only calls `generator.onPhotoshopEvent`
811
+ * the first time a caller listens to an event, and `removePhotoshopEventListener`
812
+ * once the last listener for that event goes away. Reference counting rides on
813
+ * the `newListener` / `removeListener` meta-events so every add/remove path
814
+ * (`on` / `once` / `off` / `removeAllListeners`) is covered.
815
+ *
816
+ * The `on` / `once` / `off` / `emit` signatures are narrowed to
817
+ * `PhotoshopEventMap`, so only confirmed events can be listened to at compile
818
+ * time; unknown names throw at runtime.
819
+ */
820
+ declare class EventManager extends EventEmitter implements PhotoshopEvents {
821
+ private readonly generator;
822
+ private readonly bridges;
823
+ constructor(generator: PsGenerator);
824
+ on<K extends keyof PhotoshopEventMap>(event: K, listener: Listener<K>): this;
825
+ once<K extends keyof PhotoshopEventMap>(event: K, listener: Listener<K>): this;
826
+ addListener<K extends keyof PhotoshopEventMap>(event: K, listener: Listener<K>): this;
827
+ off<K extends keyof PhotoshopEventMap>(event: K, listener: Listener<K>): this;
828
+ removeListener<K extends keyof PhotoshopEventMap>(event: K, listener: Listener<K>): this;
829
+ /** Dispatch a payload to listeners. Fired by the PS bridge; not for external use. */
830
+ emit<K extends keyof PhotoshopEventMap>(event: K, payload: PhotoshopEventMap[K]): boolean;
831
+ emit(event: string | symbol, ...args: unknown[]): boolean;
832
+ /** First listener for a PS event -> subscribe upstream once. */
833
+ private onAdd;
834
+ /** Last listener for a PS event removed -> detach the upstream bridge. */
835
+ private onRemove;
836
+ }
837
+
838
+ /**
839
+ * Action-domain feature module (ADR 0006). Exposes the `Action:*` WS Request
840
+ * methods, each backed by a packaged `jsx/Action/<name>.jsx` script run through
841
+ * the plugin's `JsxRunner` (ADR 0008). A jsx failure follows the `"Error:"`
842
+ * prefix convention: `JsxRunner` throws, and `Registry.dispatch` turns it into an
843
+ * INTERNAL response — the methods themselves do not catch.
844
+ *
845
+ * Migrated from LightAi's `ActionManager`. The `@McpTool` metadata did not carry
846
+ * over (no MCP runtime here — only the `@ws` WS path), but the human-facing
847
+ * descriptions are preserved as method JSDoc.
848
+ */
849
+ /**
850
+ * The Action module surface a Plugin reaches through `plugin.modules.action`
851
+ * (RFC 0003). `ActionModule implements` this; the SDK re-exports it via
852
+ * src/contract.ts.
853
+ */
854
+ interface ActionModuleApi {
855
+ autoCutout(): Promise<boolean>;
856
+ removeBackground(): Promise<{
857
+ success: boolean;
858
+ }>;
859
+ }
860
+ declare class ActionModule extends BaseModule implements ActionModuleApi {
861
+ constructor(plugin: PsBridgeHost);
862
+ /**
863
+ * Automatically create a selection for the main subject of the current layer.
864
+ * Runs `jsx/Action/autoCutout.jsx`. The jsx return value is not consulted:
865
+ * success is implicit, and a failure surfaces as a thrown error (hence an
866
+ * INTERNAL response), so this always resolves to `true` on the happy path.
867
+ */
868
+ autoCutout(): Promise<boolean>;
869
+ /**
870
+ * Remove the background of the current layer. Runs `jsx/Action/removeBackground.jsx`
871
+ * and wraps the jsx's boolean result as `{ success }`.
872
+ */
873
+ removeBackground(): Promise<{
874
+ success: boolean;
875
+ }>;
876
+ }
877
+
878
+ type PsDocument = {
879
+ id: number;
880
+ name: string;
881
+ width: number;
882
+ height: number;
883
+ resolution: number;
884
+ isDirty: boolean;
885
+ filePath?: string;
886
+ };
887
+ /**
888
+ * The Document module surface a Plugin reaches through `plugin.modules.document`
889
+ * (RFC 0003). `DocumentModule implements` this; the SDK re-exports it via
890
+ * src/contract.ts.
891
+ */
892
+ interface DocumentModuleApi {
893
+ getCurrentDocument(): Promise<PsDocument>;
894
+ exportDocument(params: Record<string, unknown>): Promise<unknown>;
895
+ saveDocument(params: {
896
+ savePath?: string;
897
+ }): Promise<unknown>;
898
+ }
899
+ declare class DocumentModule extends BaseModule implements DocumentModuleApi {
900
+ constructor(plugin: PsBridgeHost);
901
+ currentDocument: PsDocument | null;
902
+ getCurrentDocument(): Promise<PsDocument>;
903
+ exportDocument(params: Record<string, any>): Promise<unknown>;
904
+ saveDocument(params: {
905
+ savePath?: string;
906
+ }): Promise<unknown>;
907
+ }
908
+
909
+ /**
910
+ * Single-layer pixmap export + preview, isolated from the buggy
911
+ * `generator.getPixmap` (generator-core's version omits the
912
+ * `includeAdjustors/Children/ClipBase/Clipped` flags and passes a stray
913
+ * `settings.thread` field). `getPixmap` here is a faithful port of
914
+ * LightAi's `LayerManager.getPixmap` (index.ts:364-482): it calls the plugin's
915
+ * own `Layer/getLayerPixmap.jsx` over the progress channel and collects the
916
+ * bounds + pixmap + ICC profile messages Photoshop streams back.
917
+ *
918
+ * Whole-document export is handled separately by generator-core's
919
+ * `getDocumentPixmap` (to be wired up later); this module only deals with
920
+ * explicit layer specs, which is what `Layer/getLayerPixmap.jsx` requires.
921
+ *
922
+ * Encoding (raw RGBA -> PNG) goes through `sharp`, externalized from the bundle
923
+ * and resolved from node_modules at runtime inside Photoshop's Node.
924
+ */
925
+ /**
926
+ * The Image module surface a Plugin reaches through `plugin.modules.image`
927
+ * (RFC 0003). `ImageModule implements` this; the SDK re-exports it via
928
+ * src/contract.ts. `settings` is widened to `Record<string, unknown>` so the
929
+ * plugin contract does not drag the generator-core `GetPixmapSettings` namespace
930
+ * into the SDK; `buffer` is `Uint8Array` (not `Buffer`) so the SDK stays
931
+ * Node-free.
932
+ */
933
+ interface ImageModuleApi {
934
+ exportImage(options: {
935
+ documentId?: number;
936
+ layerSpec: LayerSpec;
937
+ settings?: Record<string, unknown>;
938
+ }): Promise<ImageResult>;
939
+ getPreview(options: {
940
+ documentId?: number;
941
+ layerSpec: number;
942
+ }): Promise<ImageResult>;
943
+ exportDocument(options: {
944
+ documentId?: number;
945
+ settings?: Record<string, unknown>;
946
+ }): Promise<ImageResult>;
947
+ }
948
+ declare class ImageModule extends BaseModule implements ImageModuleApi {
949
+ constructor(plugin: PsBridgeHost);
950
+ /**
951
+ * Export a single layer as a PNG buffer plus its bounds and pixel
952
+ * dimensions. `settings` carries the `GetPixmapSettings` Photoshop accepts;
953
+ * the four `include*` flags default to `true` when unspecified (the fix over
954
+ * generator-core). `layerSpec` is required — the underlying jsx needs an
955
+ * explicit layer id or index range.
956
+ */
957
+ exportImage(options: {
958
+ documentId?: number;
959
+ layerSpec: LayerSpec;
960
+ settings?: PsGenerator.GetPixmapSettings;
961
+ }): Promise<ImageResult>;
962
+ /**
963
+ * Export a downscaled preview of a single layer. The scale is computed so
964
+ * the longer edge lands near 300px; scaling is done by Photoshop via
965
+ * `scaleX/scaleY`, not by `sharp`. `includeClipped/ClipBase/Adjustors` are
966
+ * forced to `false` to fetch only the body layer's pixels. `layerSpec` is
967
+ * required (a layer id; the layer's `rect` drives the scale).
968
+ */
969
+ getPreview(options: {
970
+ documentId?: number;
971
+ layerSpec: number;
972
+ }): Promise<ImageResult>;
973
+ /**
974
+ * Export the whole document (its current visibility state, flattened) as a PNG
975
+ * buffer plus bounds and pixel dimensions. Unlike `exportImage` (a single layer
976
+ * via the `Layer/getLayerPixmap` jsx protocol), this uses generator-core's
977
+ * built-in `getDocumentPixmap`, which returns an already-parsed `PsPixmap`.
978
+ * `documentId` defaults to the current document; `settings` carries the
979
+ * `GetPixmapSettings` Photoshop accepts (e.g. `scaleX`/`scaleY`).
980
+ */
981
+ exportDocument(options: {
982
+ documentId?: number;
983
+ settings?: PsGenerator.GetPixmapSettings;
984
+ }): Promise<ImageResult>;
985
+ /**
986
+ * `@ws` wrapper over {@link exportImage} (RFC 0008). Returns a wire-friendly
987
+ * {@link WsImageResult}: when `plugin.cos` is enabled the PNG is uploaded and
988
+ * `data` is an https URL, otherwise `data` is a base64 data URI. A COS upload
989
+ * failure throws (no base64 fallback) — a configured channel must be used.
990
+ */
991
+ exportLayerWs(options: {
992
+ documentId?: number;
993
+ layerSpec: LayerSpec;
994
+ settings?: PsGenerator.GetPixmapSettings;
995
+ }): Promise<WsImageResult>;
996
+ /**
997
+ * `@ws` wrapper over {@link getPreview} (RFC 0008). Always returns base64 —
998
+ * previews are high-frequency, downscaled thumbnails not worth a COS round-trip,
999
+ * so this never uploads even when `plugin.cos` is enabled.
1000
+ */
1001
+ getPreviewWs(options: {
1002
+ documentId?: number;
1003
+ layerSpec: number;
1004
+ }): Promise<WsImageResult>;
1005
+ /**
1006
+ * `@ws` wrapper over {@link exportDocument} (RFC 0008). Uploads to COS when
1007
+ * enabled (https URL), else base64; a COS failure throws.
1008
+ */
1009
+ exportDocumentWs(options: {
1010
+ documentId?: number;
1011
+ settings?: PsGenerator.GetPixmapSettings;
1012
+ }): Promise<WsImageResult>;
1013
+ /**
1014
+ * Turn a module-internal {@link ImageResult} (raw PNG `buffer`) into the
1015
+ * wire-friendly {@link WsImageResult} (`data` string). With `upload` set and
1016
+ * `plugin.cos` enabled, the buffer is uploaded and `data` is the signed URL;
1017
+ * otherwise `data` is a `data:image/png;base64,...` URI. Both forms drop
1018
+ * straight into an `<img src>`.
1019
+ */
1020
+ private toWsResult;
1021
+ /**
1022
+ * Resolve a layer's name for the COS object key. A numeric `layerSpec` is
1023
+ * looked up via the layer module; an index-range spec has no single name, so it
1024
+ * falls back to "layers". Lookup failures degrade to `layer-{id}` rather than
1025
+ * failing the export.
1026
+ */
1027
+ private resolveLayerName;
1028
+ /**
1029
+ * Resolve a document's name for the COS object key. Uses the current document's
1030
+ * name when the target is the current document; otherwise falls back to
1031
+ * `doc-{id}` rather than spending a jsx round-trip to name an off-screen doc.
1032
+ */
1033
+ private resolveDocumentName;
1034
+ /**
1035
+ * Faithful port of LightAi `LayerManager.getPixmap` (index.ts:364-482).
1036
+ * Builds the params (dropping generator-core's stray `settings.thread` and
1037
+ * defaulting the four `include*` flags to `true`), opens the pixmap jsx over
1038
+ * the progress channel, and resolves three native promises as the
1039
+ * bounds/pixmap/iccProfile messages arrive. Once all expected messages are
1040
+ * in, signals the channel to settle and constructs a `Pixmap`.
1041
+ */
1042
+ private getPixmap;
1043
+ /**
1044
+ * Resolve the document id: explicit override, else the document module's
1045
+ * current document, else fail loud (matches LightAi's "No document opened").
1046
+ */
1047
+ private resolveDocumentId;
1048
+ /**
1049
+ * Convert a pixmap's raw pixels into a tightly-packed RGBA buffer. Handles both
1050
+ * the single-layer protocol (`getLayerPixmap`: 4-channel, no row padding) and
1051
+ * generator-core's `getDocumentPixmap` (which may return 3-channel pixmaps and
1052
+ * rows padded to `rowBytes`). 4-channel is Photoshop's `[A,R,G,B]` layout;
1053
+ * 3-channel is `[R,G,B]` and gets an opaque alpha. Rows are walked by
1054
+ * `rowBytes` so any per-row padding is skipped rather than misaligning the image.
1055
+ */
1056
+ private parseRawPixels;
1057
+ private encodePng;
1058
+ }
1059
+ /**
1060
+ * Result of an image export / preview: PNG bytes plus geometry metadata. The
1061
+ * bytes are typed as `Uint8Array` (the implementation returns a `Buffer`, which
1062
+ * is a `Uint8Array` subtype) so this type can cross into the Node-free SDK
1063
+ * contract unchanged.
1064
+ */
1065
+ interface ImageResult {
1066
+ buffer: Uint8Array;
1067
+ bounds: PsBounds$1;
1068
+ width: number;
1069
+ height: number;
1070
+ }
1071
+
1072
+ /**
1073
+ * Plugin-facing COS contract (RFC 0008). The minimal slice modules and plugins
1074
+ * reach through `plugin.cos`: upload in-memory bytes or a local file and get back
1075
+ * a ready-to-use signed URL. Params use `Uint8Array`/path strings (never `Buffer`
1076
+ * or the COS SDK's own types) so the SDK's re-export of this interface stays
1077
+ * Node-free, mirroring how `ImageModuleApi` is exposed.
1078
+ */
1079
+ interface CosServiceApi {
1080
+ /** Upload raw bytes, returning a signed URL. `name` labels the object key. */
1081
+ uploadObject(data: Uint8Array, name?: string): Promise<string>;
1082
+ /** Upload a local file by path, returning a signed URL. */
1083
+ uploadFile(dir: string, name?: string): Promise<string>;
1084
+ }
1085
+ /** Permanent-key COS config, read from the environment by {@link CosService.fromEnv}. */
1086
+ interface CosConfig {
1087
+ secretId: string;
1088
+ secretKey: string;
1089
+ bucket: string;
1090
+ region: string;
1091
+ keyPrefix: string;
1092
+ urlExpires: number;
1093
+ }
1094
+ /**
1095
+ * Optional object-storage upload unit (RFC 0008). Enabled only when the four
1096
+ * `PS_BRIDGE_COS_*` env fields are present; otherwise the host leaves `plugin.cos`
1097
+ * undefined and image exports fall back to base64. Uses a permanent key pair and
1098
+ * returns signed URLs without an attachment disposition, so the image they point
1099
+ * at stays inline-displayable.
1100
+ */
1101
+ declare class CosService implements CosServiceApi {
1102
+ private readonly config;
1103
+ private readonly logger;
1104
+ private readonly cos;
1105
+ constructor(config: CosConfig, logger: Logger);
1106
+ /**
1107
+ * Build a CosService from the environment, or return undefined when COS is not
1108
+ * configured. All four `PS_BRIDGE_COS_SECRET_ID/SECRET_KEY/BUCKET/REGION` must be
1109
+ * present and non-empty — a missing field means "not enabled", decided once at
1110
+ * startup rather than failing loudly on the first upload.
1111
+ */
1112
+ static fromEnv(logger: Logger): CosService | undefined;
1113
+ uploadObject(data: Uint8Array, name?: string): Promise<string>;
1114
+ uploadFile(dir: string, name?: string): Promise<string>;
1115
+ /**
1116
+ * Compose the object key `{keyPrefix}/{name}-{ts}{ext}` (keyPrefix is env-
1117
+ * configurable, default `ps-bridge/exports`). `name` (a layer/document name) is
1118
+ * kept verbatim including non-ASCII (e.g. Chinese); only path separators and
1119
+ * whitespace are replaced — they would nest the key or break the URL — and the
1120
+ * label is length-capped. Uniqueness rides on the timestamp, not the label.
1121
+ */
1122
+ private buildKey;
1123
+ private sanitizeName;
1124
+ private putObject;
1125
+ private putFile;
1126
+ private signedUrl;
1127
+ }
1128
+
1129
+ /** Host config handed in by generator-core (self._config[name]). */
1130
+ interface PluginConfig {
1131
+ port?: number;
1132
+ /**
1133
+ * Directory whose direct child folders are loaded as plugin packages
1134
+ * (each a `package.json` with a `main` entry; see the plugin loader).
1135
+ * Defaults to `<generator-package>/plugins` — i.e. `packages/generator/plugins`,
1136
+ * a symlink to the repo-root `/plugins` in development.
1137
+ */
1138
+ pluginsDir?: string;
1139
+ [key: string]: unknown;
1140
+ }
1141
+ /**
1142
+ * Test-only overrides for `JsxRunner` construction. Production callers pass
1143
+ * nothing; tests point `polyfillsDir` at the source `jsx/polyfills` tree so
1144
+ * `init()` reads real files instead of the bundler's `dist/jsx/polyfills`
1145
+ * (which `__dirname`-based resolution can't reach under vitest's source
1146
+ * runtime).
1147
+ */
1148
+ interface JsxRunnerOverrides {
1149
+ polyfillsDir?: string;
1150
+ }
1151
+ /**
1152
+ * The host generator-core loads. Registers a menu item and starts the server's
1153
+ * own WebSocket service (ADR 0003). Every call into Photoshop goes through the
1154
+ * injected `generator`, which makes the whole init path observable from a test
1155
+ * mock (see test/fakeGenerator.ts).
1156
+ */
1157
+ declare class PsBridgeHost implements PluginHost {
1158
+ readonly generator: PsGenerator;
1159
+ readonly config: PluginConfig;
1160
+ readonly logger: Logger;
1161
+ private server;
1162
+ /** Feature modules, reached by short key (`host.modules.layer`, ADR 0009). */
1163
+ readonly modules: {
1164
+ layer: LayerModule;
1165
+ document: DocumentModule;
1166
+ action: ActionModule;
1167
+ image: ImageModule;
1168
+ };
1169
+ private plugins;
1170
+ private readonly _jsx;
1171
+ private readonly _events;
1172
+ /**
1173
+ * Optional object-storage upload service (RFC 0008). Set from the environment
1174
+ * at construction — present only when the `PS_BRIDGE_COS_*` fields are configured;
1175
+ * otherwise undefined. Reached by modules and plugins through `plugin.cos`.
1176
+ */
1177
+ readonly cos?: CosService;
1178
+ private constructor();
1179
+ /** Run packaged jsx by name (ADR 0008). Used by modules and other server callers. */
1180
+ get jsx(): JsxRunner;
1181
+ /** Photoshop event subscriptions owned by the host. */
1182
+ get events(): EventManager;
1183
+ /**
1184
+ * Build the host contract for one plugin (RFC 0005). A shallow view that shares
1185
+ * the host's `modules` and `events` (both global-singleton semantics — they do
1186
+ * not split per plugin) but swaps in a `jsx` scoped to `<pluginDir>/jsx`, so the
1187
+ * plugin's `jsx.execute("x")` resolves to its own files while `executeBuiltin`
1188
+ * still reaches the built-in tree. Passed to `loadPlugins` as the `hostFor`
1189
+ * factory; the plugin never sees the concrete `PsBridgeHost`.
1190
+ */
1191
+ private hostFor;
1192
+ /** Entry point: construct the host and run its async initialization. */
1193
+ static init(generator: PsGenerator, config: PluginConfig, logger: Logger, overrides?: JsxRunnerOverrides): Promise<PsBridgeHost>;
1194
+ private onInit;
1195
+ private createMenuItem;
1196
+ private handleMenuClicked;
1197
+ /** Stop the WebSocket service (used by tests; PS teardown is process exit). */
1198
+ close(): Promise<void>;
1199
+ }
1200
+
1201
+ /**
1202
+ * Base class for feature modules (ADR 0006). A module reaches every dependency
1203
+ * through `this.plugin` — `this.plugin.generator` for Photoshop, `this.plugin.logger`,
1204
+ * and `this.plugin.emit` / `this.plugin.broadcast` to push Events. Methods are
1205
+ * exposed via the `@ws` / `@api` decorators and wired up by `bootstrap`.
1206
+ */
1207
+ declare abstract class BaseModule {
1208
+ readonly name: string;
1209
+ readonly plugin: PsBridgeHost;
1210
+ constructor(name: string, plugin: PsBridgeHost);
1211
+ }
1212
+
1213
+ declare class PsLayer {
1214
+ id: number;
1215
+ index: number;
1216
+ name: string;
1217
+ type: number;
1218
+ visible: boolean;
1219
+ bounds: PsBounds$1;
1220
+ rect: PsRect;
1221
+ clip: boolean;
1222
+ children?: PsLayer[];
1223
+ constructor(init: Partial<PsLayer>);
1224
+ }
1225
+ /**
1226
+ * The Layer module surface a Plugin reaches through `plugin.modules.layer`
1227
+ * (RFC 0003). `LayerModule implements` this, so the plugin contract tracks the
1228
+ * implementation by compiler force; the SDK re-exports it via src/contract.ts.
1229
+ */
1230
+ interface LayerModuleApi {
1231
+ getLayerInfo(options?: {
1232
+ id?: number;
1233
+ index?: number;
1234
+ getChildren?: boolean;
1235
+ getGeneratorSettings?: boolean;
1236
+ }): Promise<PsLayer>;
1237
+ getLayerInfoByID(layerID: number, options?: {
1238
+ getChildren: boolean;
1239
+ }): Promise<PsLayer>;
1240
+ getLayerInfoByIndex(layerIndex: number, options?: {
1241
+ getChildren: boolean;
1242
+ }): Promise<PsLayer>;
1243
+ }
1244
+ declare class LayerModule extends BaseModule implements LayerModuleApi {
1245
+ constructor(plugin: PsBridgeHost);
1246
+ getLayerInfo(options?: {
1247
+ id?: number;
1248
+ index?: number;
1249
+ getChildren?: boolean;
1250
+ getGeneratorSettings?: boolean;
1251
+ }): Promise<PsLayer>;
1252
+ getLayerInfoByID(layerIDOrParams: number | {
1253
+ layerID: number;
1254
+ options?: {
1255
+ getChildren: boolean;
1256
+ };
1257
+ }, options?: {
1258
+ getChildren: boolean;
1259
+ }): Promise<PsLayer>;
1260
+ getLayerInfoByIndex(layerIndexOrParams: number | {
1261
+ layerIndex: number;
1262
+ options?: {
1263
+ getChildren: boolean;
1264
+ };
1265
+ }, options?: {
1266
+ getChildren: boolean;
1267
+ }): Promise<PsLayer>;
1268
+ }
1269
+
1270
+ export { type ActionModuleApi as A, type Bounds as B, type CosServiceApi as C, type DocumentModuleApi as D, type ImageChangedEvent as I, JsxRunner as J, type LayerModuleApi as L, PsGenerator as P, type PluginConfig as a, PsBridgeHost as b, type ImageChangedLayer as c, type ImageModuleApi as d, type ImageResult as e, type JsxRunnerApi as f, type LayerSpec as g, type PhotoshopEventListener as h, type PhotoshopEventMap as i, type PhotoshopEvents as j, type PsBounds$1 as k, type PsDocument as l, PsLayer as m, type PsRect as n };