@pinepaper.studio/mcp-server 1.6.7 → 1.6.9

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 (174) hide show
  1. package/README.de.md +9 -5
  2. package/README.es.md +9 -5
  3. package/README.fr.md +9 -5
  4. package/README.hi.md +8 -5
  5. package/README.ja.md +8 -5
  6. package/README.ko.md +8 -4
  7. package/README.md +256 -17
  8. package/README.pt-BR.md +9 -5
  9. package/README.zh-CN.md +6 -4
  10. package/dist/browser/index.d.ts +1 -1
  11. package/dist/browser/index.d.ts.map +1 -1
  12. package/dist/browser/index.js +1 -1
  13. package/dist/browser/index.js.map +1 -1
  14. package/dist/browser/puppeteer-controller.d.ts +86 -2
  15. package/dist/browser/puppeteer-controller.d.ts.map +1 -1
  16. package/dist/browser/puppeteer-controller.js +143 -60
  17. package/dist/browser/puppeteer-controller.js.map +1 -1
  18. package/dist/character/data/concepts.json +1 -0
  19. package/dist/character/data/depictions.json +1 -0
  20. package/dist/character/data/styles.json +1 -0
  21. package/dist/character/duration-scale.d.ts +13 -0
  22. package/dist/character/duration-scale.d.ts.map +1 -0
  23. package/dist/character/duration-scale.js +13 -0
  24. package/dist/character/duration-scale.js.map +1 -0
  25. package/dist/character/path-bbox.d.ts +82 -0
  26. package/dist/character/path-bbox.d.ts.map +1 -0
  27. package/dist/character/path-bbox.js +496 -0
  28. package/dist/character/path-bbox.js.map +1 -0
  29. package/dist/character/perform.d.ts +149 -0
  30. package/dist/character/perform.d.ts.map +1 -0
  31. package/dist/character/perform.js +626 -0
  32. package/dist/character/perform.js.map +1 -0
  33. package/dist/character/resolve.d.ts +51 -0
  34. package/dist/character/resolve.d.ts.map +1 -0
  35. package/dist/character/resolve.js +239 -0
  36. package/dist/character/resolve.js.map +1 -0
  37. package/dist/cli.js +12 -0
  38. package/dist/cli.js.map +1 -1
  39. package/dist/design/design-systems.d.ts +224 -0
  40. package/dist/design/design-systems.d.ts.map +1 -0
  41. package/dist/design/design-systems.js +434 -0
  42. package/dist/design/design-systems.js.map +1 -0
  43. package/dist/execution/canvas-state.d.ts +7 -0
  44. package/dist/execution/canvas-state.d.ts.map +1 -1
  45. package/dist/execution/canvas-state.js +20 -8
  46. package/dist/execution/canvas-state.js.map +1 -1
  47. package/dist/index.d.ts +20 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +12 -4
  50. package/dist/index.js.map +1 -1
  51. package/dist/ontology/design-graph.d.ts +9 -0
  52. package/dist/ontology/design-graph.d.ts.map +1 -1
  53. package/dist/ontology/design-graph.js +98 -3
  54. package/dist/ontology/design-graph.js.map +1 -1
  55. package/dist/ontology/types.d.ts +42 -0
  56. package/dist/ontology/types.d.ts.map +1 -1
  57. package/dist/ontology/vocabulary.d.ts.map +1 -1
  58. package/dist/ontology/vocabulary.js +20 -0
  59. package/dist/ontology/vocabulary.js.map +1 -1
  60. package/dist/tools/compact-descriptions.d.ts.map +1 -1
  61. package/dist/tools/compact-descriptions.js +25 -0
  62. package/dist/tools/compact-descriptions.js.map +1 -1
  63. package/dist/tools/definitions.d.ts +1 -1
  64. package/dist/tools/definitions.d.ts.map +1 -1
  65. package/dist/tools/definitions.js +404 -25
  66. package/dist/tools/definitions.js.map +1 -1
  67. package/dist/tools/engine-methods.d.ts +18 -0
  68. package/dist/tools/engine-methods.d.ts.map +1 -0
  69. package/dist/tools/engine-methods.js +241 -0
  70. package/dist/tools/engine-methods.js.map +1 -0
  71. package/dist/tools/handlers/character.d.ts +121 -0
  72. package/dist/tools/handlers/character.d.ts.map +1 -0
  73. package/dist/tools/handlers/character.js +315 -0
  74. package/dist/tools/handlers/character.js.map +1 -0
  75. package/dist/tools/handlers.d.ts +2 -0
  76. package/dist/tools/handlers.d.ts.map +1 -1
  77. package/dist/tools/handlers.js +522 -18
  78. package/dist/tools/handlers.js.map +1 -1
  79. package/dist/tools/minimal-descriptions.d.ts.map +1 -1
  80. package/dist/tools/minimal-descriptions.js +15 -3
  81. package/dist/tools/minimal-descriptions.js.map +1 -1
  82. package/dist/tools/toolkits.d.ts.map +1 -1
  83. package/dist/tools/toolkits.js +8 -0
  84. package/dist/tools/toolkits.js.map +1 -1
  85. package/dist/types/code-generator.d.ts +106 -1
  86. package/dist/types/code-generator.d.ts.map +1 -1
  87. package/dist/types/code-generator.js +1273 -18
  88. package/dist/types/code-generator.js.map +1 -1
  89. package/dist/types/schemas.d.ts +6805 -124
  90. package/dist/types/schemas.d.ts.map +1 -1
  91. package/dist/types/schemas.js +627 -17
  92. package/dist/types/schemas.js.map +1 -1
  93. package/dist/vendor/design/art-deco-generator.d.ts +52 -0
  94. package/dist/vendor/design/art-deco-generator.d.ts.map +1 -0
  95. package/dist/vendor/design/art-deco-generator.js +144 -0
  96. package/dist/vendor/design/art-deco-generator.js.map +1 -0
  97. package/dist/vendor/design/art-nouveau-generator.d.ts +46 -0
  98. package/dist/vendor/design/art-nouveau-generator.d.ts.map +1 -0
  99. package/dist/vendor/design/art-nouveau-generator.js +134 -0
  100. package/dist/vendor/design/art-nouveau-generator.js.map +1 -0
  101. package/dist/vendor/design/bauhaus-generator.d.ts +53 -0
  102. package/dist/vendor/design/bauhaus-generator.d.ts.map +1 -0
  103. package/dist/vendor/design/bauhaus-generator.js +126 -0
  104. package/dist/vendor/design/bauhaus-generator.js.map +1 -0
  105. package/dist/vendor/design/brutalist-generator.d.ts +53 -0
  106. package/dist/vendor/design/brutalist-generator.d.ts.map +1 -0
  107. package/dist/vendor/design/brutalist-generator.js +148 -0
  108. package/dist/vendor/design/brutalist-generator.js.map +1 -0
  109. package/dist/vendor/design/claymorphism-generator.d.ts +46 -0
  110. package/dist/vendor/design/claymorphism-generator.d.ts.map +1 -0
  111. package/dist/vendor/design/claymorphism-generator.js +135 -0
  112. package/dist/vendor/design/claymorphism-generator.js.map +1 -0
  113. package/dist/vendor/design/corporate-memphis-generator.d.ts +47 -0
  114. package/dist/vendor/design/corporate-memphis-generator.d.ts.map +1 -0
  115. package/dist/vendor/design/corporate-memphis-generator.js +144 -0
  116. package/dist/vendor/design/corporate-memphis-generator.js.map +1 -0
  117. package/dist/vendor/design/cyberpunk-generator.d.ts +52 -0
  118. package/dist/vendor/design/cyberpunk-generator.d.ts.map +1 -0
  119. package/dist/vendor/design/cyberpunk-generator.js +162 -0
  120. package/dist/vendor/design/cyberpunk-generator.js.map +1 -0
  121. package/dist/vendor/design/de-stijl-generator.d.ts +50 -0
  122. package/dist/vendor/design/de-stijl-generator.d.ts.map +1 -0
  123. package/dist/vendor/design/de-stijl-generator.js +154 -0
  124. package/dist/vendor/design/de-stijl-generator.js.map +1 -0
  125. package/dist/vendor/design/design-style.d.ts +3 -0
  126. package/dist/vendor/design/design-style.d.ts.map +1 -0
  127. package/dist/vendor/design/design-style.js +12 -0
  128. package/dist/vendor/design/design-style.js.map +1 -0
  129. package/dist/vendor/design/design-systems-dtcg.d.ts +59 -0
  130. package/dist/vendor/design/design-systems-dtcg.d.ts.map +1 -0
  131. package/dist/vendor/design/design-systems-dtcg.js +571 -0
  132. package/dist/vendor/design/design-systems-dtcg.js.map +1 -0
  133. package/dist/vendor/design/frutiger-aero-generator.d.ts +46 -0
  134. package/dist/vendor/design/frutiger-aero-generator.d.ts.map +1 -0
  135. package/dist/vendor/design/frutiger-aero-generator.js +133 -0
  136. package/dist/vendor/design/frutiger-aero-generator.js.map +1 -0
  137. package/dist/vendor/design/memphis-generator.d.ts +56 -0
  138. package/dist/vendor/design/memphis-generator.d.ts.map +1 -0
  139. package/dist/vendor/design/memphis-generator.js +158 -0
  140. package/dist/vendor/design/memphis-generator.js.map +1 -0
  141. package/dist/vendor/design/mid-century-upa-generator.d.ts +46 -0
  142. package/dist/vendor/design/mid-century-upa-generator.d.ts.map +1 -0
  143. package/dist/vendor/design/mid-century-upa-generator.js +137 -0
  144. package/dist/vendor/design/mid-century-upa-generator.js.map +1 -0
  145. package/dist/vendor/design/neumorphic-generator.d.ts +53 -0
  146. package/dist/vendor/design/neumorphic-generator.d.ts.map +1 -0
  147. package/dist/vendor/design/neumorphic-generator.js +182 -0
  148. package/dist/vendor/design/neumorphic-generator.js.map +1 -0
  149. package/dist/vendor/design/op-art-generator.d.ts +45 -0
  150. package/dist/vendor/design/op-art-generator.d.ts.map +1 -0
  151. package/dist/vendor/design/op-art-generator.js +116 -0
  152. package/dist/vendor/design/op-art-generator.js.map +1 -0
  153. package/dist/vendor/design/pixel-art-generator.d.ts +45 -0
  154. package/dist/vendor/design/pixel-art-generator.d.ts.map +1 -0
  155. package/dist/vendor/design/pixel-art-generator.js +143 -0
  156. package/dist/vendor/design/pixel-art-generator.js.map +1 -0
  157. package/dist/vendor/design/pop-art-generator.d.ts +53 -0
  158. package/dist/vendor/design/pop-art-generator.d.ts.map +1 -0
  159. package/dist/vendor/design/pop-art-generator.js +164 -0
  160. package/dist/vendor/design/pop-art-generator.js.map +1 -0
  161. package/dist/vendor/design/risograph-generator.d.ts +53 -0
  162. package/dist/vendor/design/risograph-generator.d.ts.map +1 -0
  163. package/dist/vendor/design/risograph-generator.js +170 -0
  164. package/dist/vendor/design/risograph-generator.js.map +1 -0
  165. package/dist/vendor/design/swiss-style-generator.d.ts +55 -0
  166. package/dist/vendor/design/swiss-style-generator.d.ts.map +1 -0
  167. package/dist/vendor/design/swiss-style-generator.js +147 -0
  168. package/dist/vendor/design/swiss-style-generator.js.map +1 -0
  169. package/dist/vendor/design/vaporwave-generator.d.ts +51 -0
  170. package/dist/vendor/design/vaporwave-generator.d.ts.map +1 -0
  171. package/dist/vendor/design/vaporwave-generator.js +139 -0
  172. package/dist/vendor/design/vaporwave-generator.js.map +1 -0
  173. package/manifest.json +33 -1
  174. package/package.json +13 -5
package/README.zh-CN.md CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  PinePaper MCP 服务器让 AI 助手通过 MCP 协议在 [PinePaper Studio](https://pinepaper.studio) 中创建和驱动图形动画。兼容任何支持 MCP 工具调用的 AI(Claude、GPT、Gemini、本地模型等)。
16
16
 
17
- 服务器提供 **144 个工具**,覆盖绘图、动画、图表、地图、排版、物理、图像编辑、数据可视化与导出:
17
+ 服务器提供 **152 个工具**,覆盖绘图、动画、图表、地图、排版、物理、图像编辑、数据可视化与导出:
18
18
 
19
19
  - 创建文本、形状、几何图形与复杂图稿
20
20
  - 用行为驱动的**关系(relations)**做动画,而不是逐帧关键帧
@@ -77,9 +77,11 @@ npm install -g @pinepaper.studio/mcp-server
77
77
  服务器** —— 相同的工具、相同的版本,通过 HTTP 提供,无需安装,也不需要在你的
78
78
  机器上运行浏览器。
79
79
 
80
- 托管需要成本,因此是付费的:**Starter 每月 $4.99**(每天 500 次请求)与
81
- **Pro 每月 $14.99**(每天 5,000 次,另含动画模板库)。如果你能在本地运行,
82
- 就在本地运行 —— 这样不会损失任何功能。
80
+ 托管需要成本,因此是付费的,按额度(credits)运行。当前价格请见
81
+ [cloud.pinepaper.studio](https://cloud.pinepaper.studio)。能在本地运行就在本地运行,
82
+ 但两者并不完全相同:托管服务使用经过测试的 LLM,并在生成的代码进入画布前
83
+ 进行修复 —— 这是本地服务器自身无法做到的。哪个模型更合适,你可以自己验证:基准测试用同一个提示词在多个模型上运行,
84
+ 并排展示结果,任意一次运行都可分享。目前为邀请制测试版。
83
85
 
84
86
  ## 更多
85
87
 
@@ -4,5 +4,5 @@
4
4
  * Exports for browser-side usage of PinePaper MCP tools.
5
5
  */
6
6
  export { PinePaperBridge, PinePaperStudioHandler, createBridge, createStudioHandler, type BridgeMessage, type BridgeConfig, } from './bridge.js';
7
- export { PinePaperBrowserController, getBrowserController, resetBrowserController, resetBrowserControllerSync, type BrowserControllerConfig, type ExecuteResult, type AgentConnectOptions, type BatchExecuteResult, } from './puppeteer-controller.js';
7
+ export { PinePaperBrowserController, getBrowserController, resetBrowserController, resetBrowserControllerSync, PAGE_WAIT_UNTIL, type BrowserControllerConfig, type ExecuteCodeOptions, type PageWaitUntil, type ExecuteResult, type AgentConnectOptions, type BatchExecuteResult, } from './puppeteer-controller.js';
8
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,YAAY,EACZ,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,0BAA0B,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,YAAY,EACZ,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,0BAA0B,EAC1B,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,2BAA2B,CAAC"}
@@ -4,5 +4,5 @@
4
4
  * Exports for browser-side usage of PinePaper MCP tools.
5
5
  */
6
6
  export { PinePaperBridge, PinePaperStudioHandler, createBridge, createStudioHandler, } from './bridge.js';
7
- export { PinePaperBrowserController, getBrowserController, resetBrowserController, resetBrowserControllerSync, } from './puppeteer-controller.js';
7
+ export { PinePaperBrowserController, getBrowserController, resetBrowserController, resetBrowserControllerSync, PAGE_WAIT_UNTIL, } from './puppeteer-controller.js';
8
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,YAAY,EACZ,mBAAmB,GAGpB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,0BAA0B,GAK3B,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,YAAY,EACZ,mBAAmB,GAGpB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,0BAA0B,EAC1B,eAAe,GAOhB,MAAM,2BAA2B,CAAC"}
@@ -4,6 +4,13 @@
4
4
  * Controls a real browser instance running PinePaper Studio.
5
5
  * Executes code directly via page.evaluate() and provides screenshots.
6
6
  */
7
+ /**
8
+ * What a navigation waits for. Puppeteer's own union, spelled out here so the
9
+ * env var can be validated against it without importing puppeteer — which is
10
+ * an optional peer, absent on most installs (see connect()).
11
+ */
12
+ export type PageWaitUntil = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2';
13
+ export declare const PAGE_WAIT_UNTIL: readonly PageWaitUntil[];
7
14
  export interface BrowserControllerConfig {
8
15
  /** PinePaper Studio URL (default: https://pinepaper.studio) */
9
16
  studioUrl?: string;
@@ -17,6 +24,45 @@ export interface BrowserControllerConfig {
17
24
  timeout?: number;
18
25
  /** Enable agent mode with ?agent=1 URL parameter */
19
26
  agentMode?: boolean;
27
+ /**
28
+ * What navigations wait for (default: 'domcontentloaded').
29
+ *
30
+ * Every goto/reload used to be networkidle2, which asks for two-or-fewer
31
+ * connections held for half a second — a bar Studio never clears behind a
32
+ * proxy, or with analytics and polling sockets open. Connect then burned the
33
+ * entire timeout and failed against a page that had been usable for seconds.
34
+ * Readiness is waitForPinePaper()'s job; a navigation only has to hand us a
35
+ * document.
36
+ */
37
+ waitUntil?: PageWaitUntil;
38
+ /**
39
+ * Chrome --proxy-server value (e.g. 'http://127.0.0.1:8080'). Setting it also
40
+ * passes --ignore-certificate-errors, because a proxy that intercepts TLS
41
+ * presents its own certificate and Chrome would otherwise refuse every
42
+ * origin. Unset → neither flag, and certificate checking stays on.
43
+ */
44
+ proxy?: string;
45
+ /**
46
+ * Run code through FxTool's governor (app.runGenerated) when the build has
47
+ * one. Default true. False raw-evals instead — see ExecuteCodeOptions.
48
+ */
49
+ governor?: boolean;
50
+ }
51
+ export interface ExecuteCodeOptions {
52
+ /**
53
+ * Governor budget for this run, in ms. FxTool's runGenerated defaults to
54
+ * 10_000 (RUN_DEFAULTS.timeoutMs, an async-tail Promise.race): a sane guard
55
+ * against a runaway script, and the wrong number for an export — the one
56
+ * operation that legitimately runs for minutes.
57
+ */
58
+ governorTimeoutMs?: number;
59
+ /**
60
+ * Skip app.runGenerated entirely and raw-eval the code. That gives up the
61
+ * governor report, the structured error codes and the seeded PRNG, so it is
62
+ * the escape hatch for a deployed build whose governor ignores the budget
63
+ * above — never a fast path. Defaults to the controller's `governor: false`.
64
+ */
65
+ bypassGovernor?: boolean;
20
66
  }
21
67
  export interface AgentConnectOptions {
22
68
  /** Run browser in headless mode (default: true for agent workflows) */
@@ -62,6 +108,29 @@ export interface ExecuteResult {
62
108
  */
63
109
  canvasReset?: boolean;
64
110
  }
111
+ /**
112
+ * Is PinePaper Studio actually usable? Evaluated IN THE PAGE, so it closes over
113
+ * nothing.
114
+ *
115
+ * The old test was `app !== undefined || pinepaper !== undefined || paper !==
116
+ * undefined`, hand-copied into four places. `window.paper` is Paper.js, which
117
+ * attaches the moment the canvas script loads — long before Studio has built
118
+ * its API — so all four sites could report ready against a page that would
119
+ * throw on the first call.
120
+ *
121
+ * The test is FxTool's own, mirrored rather than invented: js/app.js polls
122
+ * `app && app.itemRegistry && typeof app.create === 'function'` under the
123
+ * comment "No global ready event exists — poll for the app surface the executor
124
+ * needs". Two ordering details make both halves earn their place:
125
+ * `window.PinePaper` is first assigned the CLASS at module load (no `create` —
126
+ * create() is an instance method) and only later the instance, and the instance
127
+ * assigns itself to the global from INSIDE its own constructor.
128
+ *
129
+ * Every candidate global is tested, not just the first truthy one: picking
130
+ * `window.app` because it exists and then failing it would trade a false ready
131
+ * for a false not-ready, which costs the whole timeout.
132
+ */
133
+ export declare function pinePaperIsReady(): boolean;
65
134
  /**
66
135
  * Controls PinePaper Studio via Puppeteer.
67
136
  *
@@ -78,6 +147,13 @@ export declare class PinePaperBrowserController {
78
147
  private isAgentMode;
79
148
  private connectingPromise;
80
149
  constructor(config?: BrowserControllerConfig);
150
+ /**
151
+ * Chrome flags shared by both launch paths, so a proxy configured once does
152
+ * not go missing from whichever path the caller happens to take.
153
+ */
154
+ private launchArgs;
155
+ /** Launch options shared by both paths — see MIN_PROTOCOL_TIMEOUT_MS. */
156
+ private launchOptions;
81
157
  /**
82
158
  * Get URL with agent mode parameter
83
159
  */
@@ -102,6 +178,14 @@ export declare class PinePaperBrowserController {
102
178
  * Whether the browser is running in headless mode
103
179
  */
104
180
  get isHeadless(): boolean;
181
+ /**
182
+ * The resolved connection settings. Read-only, and a copy: these are what the
183
+ * env vars and the caller's config actually worked out to, which is otherwise
184
+ * only observable by launching a browser.
185
+ */
186
+ get connectionSettings(): Readonly<Required<BrowserControllerConfig>>;
187
+ /** The Chrome flags this controller would launch with. Exposed for tests. */
188
+ chromeArgs(): string[];
105
189
  /**
106
190
  * Launch browser and navigate to PinePaper Studio.
107
191
  * When agentMode is enabled (default), this automatically uses connectAgent().
@@ -134,7 +218,7 @@ export declare class PinePaperBrowserController {
134
218
  /**
135
219
  * Execute JavaScript code in PinePaper Studio
136
220
  */
137
- executeCode(code: string, takeScreenshot?: boolean): Promise<ExecuteResult>;
221
+ executeCode(code: string, takeScreenshot?: boolean, options?: ExecuteCodeOptions): Promise<ExecuteResult>;
138
222
  /**
139
223
  * Take a screenshot of the current canvas
140
224
  */
@@ -180,7 +264,7 @@ export declare class PinePaperBrowserController {
180
264
  /**
181
265
  * Execute code with a timeout
182
266
  */
183
- executeWithTimeout(code: string, timeoutMs: number, takeScreenshot?: boolean): Promise<ExecuteResult>;
267
+ executeWithTimeout(code: string, timeoutMs: number, takeScreenshot?: boolean, options?: ExecuteCodeOptions): Promise<ExecuteResult>;
184
268
  /**
185
269
  * Quick reset: Clear canvas without full page refresh
186
270
  */
@@ -1 +1 @@
1
- {"version":3,"file":"puppeteer-controller.d.ts","sourceRoot":"","sources":["../../src/browser/puppeteer-controller.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"puppeteer-controller.d.ts","sourceRoot":"","sources":["../../src/browser/puppeteer-controller.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,41 @@
4
4
  * Controls a real browser instance running PinePaper Studio.
5
5
  * Executes code directly via page.evaluate() and provides screenshots.
6
6
  */
7
+ export const PAGE_WAIT_UNTIL = ['load', 'domcontentloaded', 'networkidle0', 'networkidle2'];
8
+ /**
9
+ * Is PinePaper Studio actually usable? Evaluated IN THE PAGE, so it closes over
10
+ * nothing.
11
+ *
12
+ * The old test was `app !== undefined || pinepaper !== undefined || paper !==
13
+ * undefined`, hand-copied into four places. `window.paper` is Paper.js, which
14
+ * attaches the moment the canvas script loads — long before Studio has built
15
+ * its API — so all four sites could report ready against a page that would
16
+ * throw on the first call.
17
+ *
18
+ * The test is FxTool's own, mirrored rather than invented: js/app.js polls
19
+ * `app && app.itemRegistry && typeof app.create === 'function'` under the
20
+ * comment "No global ready event exists — poll for the app surface the executor
21
+ * needs". Two ordering details make both halves earn their place:
22
+ * `window.PinePaper` is first assigned the CLASS at module load (no `create` —
23
+ * create() is an instance method) and only later the instance, and the instance
24
+ * assigns itself to the global from INSIDE its own constructor.
25
+ *
26
+ * Every candidate global is tested, not just the first truthy one: picking
27
+ * `window.app` because it exists and then failing it would trade a false ready
28
+ * for a false not-ready, which costs the whole timeout.
29
+ */
30
+ export function pinePaperIsReady() {
31
+ const w = window;
32
+ return [w.app, w.PinePaper, w.pinepaper].some((api) => !!api && typeof api.create === 'function' && !!api.itemRegistry);
33
+ }
34
+ /**
35
+ * Floor for how long CDP waits on a single call. Puppeteer's default is 180s,
36
+ * which is shorter than a long video export's governor budget (handlers.ts,
37
+ * EXPORT_GOVERNOR_DEFAULT_MS — this number, deliberately) — the protocol must
38
+ * not be the thing that kills an export first, or the error blames the wrong
39
+ * layer. PINEPAPER_TIMEOUT raises it past this floor.
40
+ */
41
+ const MIN_PROTOCOL_TIMEOUT_MS = 300_000;
7
42
  // =============================================================================
8
43
  // BROWSER CONTROLLER CLASS
9
44
  // =============================================================================
@@ -42,11 +77,48 @@ export class PinePaperBrowserController {
42
77
  viewportHeight: config.viewportHeight || 800,
43
78
  timeout: config.timeout || 30000,
44
79
  agentMode: true, // MCP server always uses agent mode
80
+ waitUntil: config.waitUntil || 'domcontentloaded',
81
+ proxy: config.proxy || '',
82
+ governor: config.governor ?? true,
45
83
  };
46
84
  console.error(`[PinePaper] Controller initialized:`);
47
85
  console.error(`[PinePaper] Original URL: ${originalUrl}`);
48
86
  console.error(`[PinePaper] Agent URL: ${studioUrl}`);
49
87
  console.error(`[PinePaper] Headless: ${this.config.headless}`);
88
+ console.error(`[PinePaper] waitUntil: ${this.config.waitUntil}, timeout: ${this.config.timeout}ms`);
89
+ if (this.config.proxy) {
90
+ console.error(`[PinePaper] Proxy: ${this.config.proxy} — certificate errors ignored for this session`);
91
+ }
92
+ if (!this.config.governor) {
93
+ console.error('[PinePaper] Governor: OFF — raw eval, no report, no seeded determinism');
94
+ }
95
+ }
96
+ /**
97
+ * Chrome flags shared by both launch paths, so a proxy configured once does
98
+ * not go missing from whichever path the caller happens to take.
99
+ */
100
+ launchArgs(viewportWidth, viewportHeight, extra = []) {
101
+ const args = [
102
+ '--no-sandbox',
103
+ '--disable-setuid-sandbox',
104
+ `--window-size=${viewportWidth},${viewportHeight}`,
105
+ ...extra,
106
+ ];
107
+ // Strictly gated on an explicitly configured proxy. Relaxing certificate
108
+ // checking for every run would be a silent downgrade; here it is the
109
+ // consequence of a flag the operator set for their own interception proxy.
110
+ if (this.config.proxy) {
111
+ args.push(`--proxy-server=${this.config.proxy}`, '--ignore-certificate-errors');
112
+ }
113
+ return args;
114
+ }
115
+ /** Launch options shared by both paths — see MIN_PROTOCOL_TIMEOUT_MS. */
116
+ launchOptions(headless, args) {
117
+ return {
118
+ headless,
119
+ args,
120
+ protocolTimeout: Math.max(this.config.timeout, MIN_PROTOCOL_TIMEOUT_MS),
121
+ };
50
122
  }
51
123
  /**
52
124
  * Get URL with agent mode parameter
@@ -87,6 +159,18 @@ export class PinePaperBrowserController {
87
159
  get isHeadless() {
88
160
  return this.config.headless;
89
161
  }
162
+ /**
163
+ * The resolved connection settings. Read-only, and a copy: these are what the
164
+ * env vars and the caller's config actually worked out to, which is otherwise
165
+ * only observable by launching a browser.
166
+ */
167
+ get connectionSettings() {
168
+ return { ...this.config };
169
+ }
170
+ /** The Chrome flags this controller would launch with. Exposed for tests. */
171
+ chromeArgs() {
172
+ return this.launchArgs(this.config.viewportWidth, this.config.viewportHeight);
173
+ }
90
174
  /**
91
175
  * Launch browser and navigate to PinePaper Studio.
92
176
  * When agentMode is enabled (default), this automatically uses connectAgent().
@@ -144,14 +228,7 @@ export class PinePaperBrowserController {
144
228
  throw new Error('The browser tools need puppeteer, which is an optional install: run `npm i puppeteer` (or `npm i -g puppeteer` for a global CLI) and retry. It is kept out of the default dependency tree for supply-chain hygiene.');
145
229
  }
146
230
  console.error('[PinePaper] Launching browser...');
147
- this.browser = await puppeteer.default.launch({
148
- headless: this.config.headless,
149
- args: [
150
- '--no-sandbox',
151
- '--disable-setuid-sandbox',
152
- `--window-size=${this.config.viewportWidth},${this.config.viewportHeight}`,
153
- ],
154
- });
231
+ this.browser = await puppeteer.default.launch(this.launchOptions(this.config.headless, this.launchArgs(this.config.viewportWidth, this.config.viewportHeight)));
155
232
  // Get existing pages first, reuse if available (avoid creating multiple tabs)
156
233
  const pages = await this.browser.pages();
157
234
  if (pages.length > 0) {
@@ -170,17 +247,10 @@ export class PinePaperBrowserController {
170
247
  });
171
248
  console.error(`[PinePaper] Navigating to ${this.config.studioUrl}...`);
172
249
  await this.page.goto(this.config.studioUrl, {
173
- waitUntil: 'networkidle2',
250
+ waitUntil: this.config.waitUntil,
174
251
  timeout: this.config.timeout,
175
252
  });
176
- // Wait for PinePaper to be ready (check for app object)
177
- console.error('[PinePaper] Waiting for PinePaper Studio to initialize...');
178
- await this.page.waitForFunction(() => {
179
- // Check if PinePaper's global app or API is available
180
- return (typeof window.app !== 'undefined' ||
181
- typeof window.pinepaper !== 'undefined' ||
182
- typeof window.paper !== 'undefined');
183
- }, { timeout: this.config.timeout });
253
+ await this.waitForPinePaper();
184
254
  this.isConnected = true;
185
255
  console.error('[PinePaper] Connected to PinePaper Studio');
186
256
  }
@@ -272,7 +342,7 @@ export class PinePaperBrowserController {
272
342
  /**
273
343
  * Execute JavaScript code in PinePaper Studio
274
344
  */
275
- async executeCode(code, takeScreenshot = true) {
345
+ async executeCode(code, takeScreenshot = true, options = {}) {
276
346
  if (!this.page || !this.isConnected) {
277
347
  return {
278
348
  success: false,
@@ -285,11 +355,17 @@ export class PinePaperBrowserController {
285
355
  // budgets, bulk-create perf, and a machine-readable report. It returns the
286
356
  // code's trailing-expression value as result.value (FxTool captures it).
287
357
  // Fall back to raw eval on older builds that lack runGenerated.
288
- const result = await this.page.evaluate(async (codeToRun) => {
358
+ const runOptions = {
359
+ bypass: options.bypassGovernor ?? !this.config.governor,
360
+ timeoutMs: options.governorTimeoutMs,
361
+ };
362
+ const result = await this.page.evaluate(async (codeToRun, opts) => {
289
363
  const app = window.app || window.PinePaper;
290
- if (app && typeof app.runGenerated === 'function') {
364
+ if (!opts.bypass && app && typeof app.runGenerated === 'function') {
291
365
  try {
292
- const run = await app.runGenerated(codeToRun, { source: 'agent' });
366
+ // timeoutMs is runGenerated's own option (RUN_DEFAULTS.timeoutMs,
367
+ // 10s) — passing undefined leaves the default in place.
368
+ const run = await app.runGenerated(codeToRun, { source: 'agent', timeoutMs: opts.timeoutMs });
293
369
  if (run && run.ok === false) {
294
370
  return {
295
371
  success: false,
@@ -305,7 +381,9 @@ export class PinePaperBrowserController {
305
381
  return { success: false, error: e instanceof Error ? e.message : 'Execution error', executedVia: 'governed' };
306
382
  }
307
383
  }
308
- // Fallback: raw eval (pre-governor FxTool build).
384
+ // Fallback: raw eval — a pre-governor FxTool build, or an explicit
385
+ // bypass. Both generated-code shapes are `(`-led expression statements,
386
+ // so eval returns the same trailing value runGenerated captures.
309
387
  try {
310
388
  // eslint-disable-next-line no-eval
311
389
  const evalResult = eval(codeToRun);
@@ -321,7 +399,7 @@ export class PinePaperBrowserController {
321
399
  executedVia: 'eval',
322
400
  };
323
401
  }
324
- }, code);
402
+ }, code, runOptions);
325
403
  // Take screenshot if requested
326
404
  let screenshot;
327
405
  if (takeScreenshot && result.success) {
@@ -339,7 +417,10 @@ export class PinePaperBrowserController {
339
417
  // the canvas. Re-bind to the live page and retry ONCE instead.
340
418
  if (PinePaperBrowserController.isStaleFrameError(error) && await this.reacquirePage()) {
341
419
  try {
342
- const retry = await this.executeCode(code, takeScreenshot);
420
+ // Forward `options`: a recovered export that silently reverted to the
421
+ // 10s governor budget would die at the same place the retry exists to
422
+ // get past.
423
+ const retry = await this.executeCode(code, takeScreenshot, options);
343
424
  // The page reloaded under us, so the scene may be gone even though
344
425
  // execution now succeeds. Say so explicitly — silently continuing
345
426
  // against vanished item ids is how this bug wasted a whole rebuild.
@@ -407,7 +488,7 @@ export class PinePaperBrowserController {
407
488
  throw new Error('Not connected to browser');
408
489
  }
409
490
  await this.page.goto(url, {
410
- waitUntil: 'networkidle2',
491
+ waitUntil: this.config.waitUntil,
411
492
  timeout: this.config.timeout,
412
493
  });
413
494
  }
@@ -419,7 +500,7 @@ export class PinePaperBrowserController {
419
500
  throw new Error('Not connected to browser');
420
501
  }
421
502
  await this.page.reload({
422
- waitUntil: 'networkidle2',
503
+ waitUntil: this.config.waitUntil,
423
504
  timeout: this.config.timeout,
424
505
  });
425
506
  }
@@ -433,16 +514,11 @@ export class PinePaperBrowserController {
433
514
  }
434
515
  console.error('[PinePaper] Refreshing page...');
435
516
  await this.page.reload({
436
- waitUntil: 'networkidle2',
517
+ waitUntil: this.config.waitUntil,
437
518
  timeout: this.config.timeout,
438
519
  });
439
520
  // Wait for PinePaper to be ready again
440
- console.error('[PinePaper] Waiting for PinePaper Studio to reinitialize...');
441
- await this.page.waitForFunction(() => {
442
- return (typeof window.app !== 'undefined' ||
443
- typeof window.pinepaper !== 'undefined' ||
444
- typeof window.paper !== 'undefined');
445
- }, { timeout: this.config.timeout });
521
+ await this.waitForPinePaper();
446
522
  console.error('[PinePaper] Page refreshed and ready');
447
523
  }
448
524
  /**
@@ -453,11 +529,7 @@ export class PinePaperBrowserController {
453
529
  return false;
454
530
  }
455
531
  try {
456
- return await this.page.evaluate(() => {
457
- return (typeof window.app !== 'undefined' ||
458
- typeof window.pinepaper !== 'undefined' ||
459
- typeof window.paper !== 'undefined');
460
- });
532
+ return await this.page.evaluate(pinePaperIsReady);
461
533
  }
462
534
  catch {
463
535
  return false;
@@ -481,7 +553,7 @@ export class PinePaperBrowserController {
481
553
  const agentUrl = this.getAgentUrl(this.config.studioUrl);
482
554
  console.error(`[PinePaper] Switching to agent mode: ${agentUrl}`);
483
555
  await this.page.goto(agentUrl, {
484
- waitUntil: 'networkidle2',
556
+ waitUntil: this.config.waitUntil,
485
557
  timeout: this.config.timeout,
486
558
  });
487
559
  await this.waitForPinePaper();
@@ -524,18 +596,12 @@ export class PinePaperBrowserController {
524
596
  const viewportWidth = options.viewportWidth ?? this.config.viewportWidth;
525
597
  const viewportHeight = options.viewportHeight ?? this.config.viewportHeight;
526
598
  console.error(`[PinePaper] Launching browser in agent mode (headless: ${useHeadless})...`);
527
- this.browser = await puppeteer.default.launch({
528
- headless: useHeadless,
529
- args: [
530
- '--no-sandbox',
531
- '--disable-setuid-sandbox',
532
- `--window-size=${viewportWidth},${viewportHeight}`,
533
- // Additional optimization flags for agent mode
534
- '--disable-gpu',
535
- '--disable-dev-shm-usage',
536
- '--disable-extensions',
537
- ],
538
- });
599
+ this.browser = await puppeteer.default.launch(this.launchOptions(useHeadless, this.launchArgs(viewportWidth, viewportHeight, [
600
+ // Additional optimization flags for agent mode
601
+ '--disable-gpu',
602
+ '--disable-dev-shm-usage',
603
+ '--disable-extensions',
604
+ ])));
539
605
  // Get existing pages first, reuse if available (avoid creating multiple tabs)
540
606
  const pages = await this.browser.pages();
541
607
  // Use the first page (about:blank) and close ALL others
@@ -565,7 +631,7 @@ export class PinePaperBrowserController {
565
631
  console.error(`[PinePaper] Navigating to agent URL: ${targetUrl}`);
566
632
  // Navigate the existing page to PinePaper (converts about:blank to PinePaper)
567
633
  await this.page.goto(targetUrl, {
568
- waitUntil: 'networkidle2',
634
+ waitUntil: this.config.waitUntil,
569
635
  timeout: this.config.timeout,
570
636
  });
571
637
  // Wait for PinePaper to be ready
@@ -586,11 +652,7 @@ export class PinePaperBrowserController {
586
652
  if (!this.page)
587
653
  return;
588
654
  console.error('[PinePaper] Waiting for PinePaper Studio to initialize...');
589
- await this.page.waitForFunction(() => {
590
- return (typeof window.app !== 'undefined' ||
591
- typeof window.pinepaper !== 'undefined' ||
592
- typeof window.paper !== 'undefined');
593
- }, { timeout: this.config.timeout });
655
+ await this.page.waitForFunction(pinePaperIsReady, { timeout: this.config.timeout });
594
656
  }
595
657
  /**
596
658
  * Execute multiple code blocks as a batch for optimal performance.
@@ -695,11 +757,16 @@ ${codes.map((code, i) => `
695
757
  /**
696
758
  * Execute code with a timeout
697
759
  */
698
- async executeWithTimeout(code, timeoutMs, takeScreenshot = false) {
760
+ async executeWithTimeout(code, timeoutMs, takeScreenshot = false, options = {}) {
699
761
  const timeoutPromise = new Promise((_, reject) => {
700
762
  setTimeout(() => reject(new Error('Execution timeout')), timeoutMs);
701
763
  });
702
- const executePromise = this.executeCode(code, takeScreenshot);
764
+ // The caller's outer deadline is the governor's too unless they said
765
+ // otherwise — a governor that fires first would report the wrong cause.
766
+ const executePromise = this.executeCode(code, takeScreenshot, {
767
+ governorTimeoutMs: timeoutMs,
768
+ ...options,
769
+ });
703
770
  try {
704
771
  return await Promise.race([executePromise, timeoutPromise]);
705
772
  }
@@ -775,12 +842,28 @@ let globalController = null;
775
842
  */
776
843
  export function getBrowserController(config) {
777
844
  if (!globalController) {
778
- // Resolve headless: explicit config > env var > default (true)
845
+ // Env resolution lives here, not in the controller: explicit config wins,
846
+ // then the env var, then the default — the shape PINEPAPER_HEADLESS and
847
+ // PINEPAPER_STUDIO_URL already use.
779
848
  const envHeadless = process.env.PINEPAPER_HEADLESS;
780
849
  const headless = config?.headless ?? (envHeadless !== undefined ? envHeadless !== 'false' : true);
850
+ const envWaitUntil = process.env.PINEPAPER_WAIT_UNTIL?.trim();
851
+ if (envWaitUntil && !PAGE_WAIT_UNTIL.includes(envWaitUntil)) {
852
+ // Loudly, because a typo here silently reinstates the hang it was set to
853
+ // avoid — and a 30s connect failure looks nothing like a bad env var.
854
+ console.error(`[PinePaper] PINEPAPER_WAIT_UNTIL="${envWaitUntil}" is not one of ${PAGE_WAIT_UNTIL.join(', ')} — ignoring it.`);
855
+ }
856
+ const envTimeout = Number(process.env.PINEPAPER_TIMEOUT);
857
+ if (process.env.PINEPAPER_TIMEOUT && !Number.isFinite(envTimeout)) {
858
+ console.error(`[PinePaper] PINEPAPER_TIMEOUT="${process.env.PINEPAPER_TIMEOUT}" is not a number — ignoring it.`);
859
+ }
781
860
  const mcpConfig = {
782
861
  ...config,
783
862
  headless,
863
+ waitUntil: config?.waitUntil ?? (envWaitUntil && PAGE_WAIT_UNTIL.includes(envWaitUntil) ? envWaitUntil : undefined),
864
+ timeout: config?.timeout ?? (Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : undefined),
865
+ proxy: config?.proxy ?? process.env.PINEPAPER_PROXY?.trim(),
866
+ governor: config?.governor ?? (process.env.PINEPAPER_GOVERNOR?.trim().toLowerCase() !== 'off'),
784
867
  };
785
868
  globalController = new PinePaperBrowserController(mcpConfig);
786
869
  }