@pinepaper.studio/mcp-server 1.6.24 → 1.6.25

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 (158) hide show
  1. package/README.md +130 -14
  2. package/dist/browser/puppeteer-controller.d.ts +6 -0
  3. package/dist/browser/puppeteer-controller.d.ts.map +1 -1
  4. package/dist/browser/puppeteer-controller.js +97 -69
  5. package/dist/browser/puppeteer-controller.js.map +1 -1
  6. package/dist/build-info.json +2 -2
  7. package/dist/cli.js +7 -5
  8. package/dist/cli.js.map +1 -1
  9. package/dist/design/design-systems.js +2 -2
  10. package/dist/design/design-systems.js.map +1 -1
  11. package/dist/i18n/locales/en.js +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +51 -11
  15. package/dist/index.js.map +1 -1
  16. package/dist/mcp/index.d.ts +5 -0
  17. package/dist/mcp/index.d.ts.map +1 -0
  18. package/dist/mcp/index.js +5 -0
  19. package/dist/mcp/index.js.map +1 -0
  20. package/dist/mcp/server.d.ts +86 -0
  21. package/dist/mcp/server.d.ts.map +1 -0
  22. package/dist/mcp/server.js +159 -0
  23. package/dist/mcp/server.js.map +1 -0
  24. package/dist/mcp/stdio.d.ts +20 -0
  25. package/dist/mcp/stdio.d.ts.map +1 -0
  26. package/dist/mcp/stdio.js +82 -0
  27. package/dist/mcp/stdio.js.map +1 -0
  28. package/dist/mcp/types.d.ts +598 -0
  29. package/dist/mcp/types.d.ts.map +1 -0
  30. package/dist/mcp/types.js +140 -0
  31. package/dist/mcp/types.js.map +1 -0
  32. package/dist/metrics/performance-tracker.d.ts.map +1 -1
  33. package/dist/metrics/performance-tracker.js +4 -3
  34. package/dist/metrics/performance-tracker.js.map +1 -1
  35. package/dist/ontology/hints.d.ts.map +1 -1
  36. package/dist/ontology/hints.js +3 -1
  37. package/dist/ontology/hints.js.map +1 -1
  38. package/dist/ontology/upstream-vocabulary.d.ts +2 -2
  39. package/dist/ontology/upstream-vocabulary.d.ts.map +1 -1
  40. package/dist/ontology/upstream-vocabulary.js +8 -2
  41. package/dist/ontology/upstream-vocabulary.js.map +1 -1
  42. package/dist/server/index.d.ts +1 -1
  43. package/dist/server/index.d.ts.map +1 -1
  44. package/dist/server/index.js +1 -1
  45. package/dist/server/index.js.map +1 -1
  46. package/dist/tools/batch-macros.d.ts.map +1 -1
  47. package/dist/tools/batch-macros.js +2 -1
  48. package/dist/tools/batch-macros.js.map +1 -1
  49. package/dist/tools/compact-descriptions.d.ts.map +1 -1
  50. package/dist/tools/compact-descriptions.js +7 -6
  51. package/dist/tools/compact-descriptions.js.map +1 -1
  52. package/dist/tools/craft-tools.d.ts +1 -1
  53. package/dist/tools/craft-tools.d.ts.map +1 -1
  54. package/dist/tools/create-known-keys.d.ts +2 -0
  55. package/dist/tools/create-known-keys.d.ts.map +1 -0
  56. package/dist/tools/create-known-keys.js +24 -0
  57. package/dist/tools/create-known-keys.js.map +1 -0
  58. package/dist/tools/definitions.d.ts +3 -1
  59. package/dist/tools/definitions.d.ts.map +1 -1
  60. package/dist/tools/definitions.js +43 -22
  61. package/dist/tools/definitions.js.map +1 -1
  62. package/dist/tools/effect-check.d.ts +1 -1
  63. package/dist/tools/effect-check.d.ts.map +1 -1
  64. package/dist/tools/effect-check.js +4 -0
  65. package/dist/tools/effect-check.js.map +1 -1
  66. package/dist/tools/engine-lists.d.ts +4 -1
  67. package/dist/tools/engine-lists.d.ts.map +1 -1
  68. package/dist/tools/engine-lists.js +9 -1
  69. package/dist/tools/engine-lists.js.map +1 -1
  70. package/dist/tools/engine-methods.d.ts.map +1 -1
  71. package/dist/tools/engine-methods.js +1 -0
  72. package/dist/tools/engine-methods.js.map +1 -1
  73. package/dist/tools/engine-surface.d.ts +1 -1
  74. package/dist/tools/engine-surface.d.ts.map +1 -1
  75. package/dist/tools/engine-surface.js +18 -3
  76. package/dist/tools/engine-surface.js.map +1 -1
  77. package/dist/tools/export-file-receipt.d.ts +12 -0
  78. package/dist/tools/export-file-receipt.d.ts.map +1 -1
  79. package/dist/tools/export-file-receipt.js +21 -1
  80. package/dist/tools/export-file-receipt.js.map +1 -1
  81. package/dist/tools/handlers/beats.d.ts +1 -1
  82. package/dist/tools/handlers/beats.d.ts.map +1 -1
  83. package/dist/tools/handlers/camera.d.ts +1 -1
  84. package/dist/tools/handlers/camera.d.ts.map +1 -1
  85. package/dist/tools/handlers/export.d.ts +1 -1
  86. package/dist/tools/handlers/export.d.ts.map +1 -1
  87. package/dist/tools/handlers/export.js +3 -2
  88. package/dist/tools/handlers/export.js.map +1 -1
  89. package/dist/tools/handlers/font.d.ts +1 -1
  90. package/dist/tools/handlers/font.d.ts.map +1 -1
  91. package/dist/tools/handlers/font.js +8 -7
  92. package/dist/tools/handlers/font.js.map +1 -1
  93. package/dist/tools/handlers/generate.d.ts +1 -1
  94. package/dist/tools/handlers/generate.d.ts.map +1 -1
  95. package/dist/tools/handlers/generate.js +4 -3
  96. package/dist/tools/handlers/generate.js.map +1 -1
  97. package/dist/tools/handlers/maps.d.ts +1 -1
  98. package/dist/tools/handlers/maps.d.ts.map +1 -1
  99. package/dist/tools/handlers/ontology.d.ts +1 -1
  100. package/dist/tools/handlers/ontology.d.ts.map +1 -1
  101. package/dist/tools/handlers/tool-guide.d.ts +1 -1
  102. package/dist/tools/handlers/tool-guide.d.ts.map +1 -1
  103. package/dist/tools/handlers.d.ts +15 -3
  104. package/dist/tools/handlers.d.ts.map +1 -1
  105. package/dist/tools/handlers.js +71 -31
  106. package/dist/tools/handlers.js.map +1 -1
  107. package/dist/tools/lane.d.ts +11 -6
  108. package/dist/tools/lane.d.ts.map +1 -1
  109. package/dist/tools/lane.js +46 -9
  110. package/dist/tools/lane.js.map +1 -1
  111. package/dist/tools/minimal-descriptions.js +1 -1
  112. package/dist/tools/minimal-descriptions.js.map +1 -1
  113. package/dist/tools/p5-compat/p5-helpers.d.ts +1 -1
  114. package/dist/tools/p5-compat/p5-helpers.js +1 -1
  115. package/dist/tools/shape-params.d.ts +2 -2
  116. package/dist/tools/shape-params.d.ts.map +1 -1
  117. package/dist/tools/shape-params.js +8 -2
  118. package/dist/tools/shape-params.js.map +1 -1
  119. package/dist/tools/stick-vocabulary.d.ts +2 -2
  120. package/dist/tools/stick-vocabulary.js +2 -2
  121. package/dist/tools/timeline.d.ts +2 -2
  122. package/dist/tools/timeline.d.ts.map +1 -1
  123. package/dist/tools/timeline.js +6 -1
  124. package/dist/tools/timeline.js.map +1 -1
  125. package/dist/tools/toolkits.d.ts +22 -7
  126. package/dist/tools/toolkits.d.ts.map +1 -1
  127. package/dist/tools/toolkits.js +102 -20
  128. package/dist/tools/toolkits.js.map +1 -1
  129. package/dist/types/code-generator.d.ts +25 -0
  130. package/dist/types/code-generator.d.ts.map +1 -1
  131. package/dist/types/code-generator.js +402 -157
  132. package/dist/types/code-generator.js.map +1 -1
  133. package/dist/types/schemas.d.ts +52 -47
  134. package/dist/types/schemas.d.ts.map +1 -1
  135. package/dist/types/schemas.js +7 -5
  136. package/dist/types/schemas.js.map +1 -1
  137. package/dist/utils/asset-paths.d.ts.map +1 -1
  138. package/dist/utils/asset-paths.js +4 -4
  139. package/dist/utils/asset-paths.js.map +1 -1
  140. package/dist/utils/env.d.ts +9 -0
  141. package/dist/utils/env.d.ts.map +1 -0
  142. package/dist/utils/env.js +11 -0
  143. package/dist/utils/env.js.map +1 -0
  144. package/dist/utils/export-dir.d.ts +36 -0
  145. package/dist/utils/export-dir.d.ts.map +1 -0
  146. package/dist/utils/export-dir.js +86 -0
  147. package/dist/utils/export-dir.js.map +1 -0
  148. package/dist/utils/js-literal.d.ts +4 -0
  149. package/dist/utils/js-literal.d.ts.map +1 -0
  150. package/dist/utils/js-literal.js +25 -0
  151. package/dist/utils/js-literal.js.map +1 -0
  152. package/dist/utils/mcp-log.d.ts +1 -1
  153. package/dist/utils/mcp-log.d.ts.map +1 -1
  154. package/dist/version.d.ts.map +1 -1
  155. package/dist/version.js +5 -12
  156. package/dist/version.js.map +1 -1
  157. package/manifest.json +42 -63
  158. package/package.json +8 -6
@@ -13,7 +13,7 @@ import { CreateItemInputSchema, ModifyItemInputSchema, DeleteItemInputSchema, Ad
13
13
  // Diagram schemas
14
14
  CreateDiagramShapeInputSchema, ConnectInputSchema, ConnectPortsInputSchema, AddPortsInputSchema, AutoLayoutInputSchema, GetDiagramShapesInputSchema, UpdateConnectorInputSchema, RemoveConnectorInputSchema, DiagramModeInputSchema,
15
15
  // Agent flow mode schemas
16
- AgentStartJobInputSchema, AgentEndJobInputSchema, AgentResetInputSchema, AgentBatchExecuteInputSchema, AgentExportInputSchema, ExportStoreInputSchema, AgentAnalyzeInputSchema,
16
+ AgentStartJobInputSchema, AgentEndJobInputSchema, AgentResetInputSchema, AgentBatchExecuteInputSchema, AgentBatchOperationSchema, AgentExportInputSchema, ExportStoreInputSchema, AgentAnalyzeInputSchema,
17
17
  // Letter collage schemas
18
18
  CreateLetterCollageInputSchema, AnimateLetterCollageInputSchema,
19
19
  // Map schemas
@@ -34,11 +34,10 @@ SelectionInputSchema, TransformInputSchema, HistoryInputSchema, ImageFilterInput
34
34
  import { ZodError, z } from 'zod';
35
35
  import { AsyncLocalStorage } from 'node:async_hooks';
36
36
  import { effectClass } from './effect-class.js';
37
- import { exportFileReceipt } from './export-file-receipt.js';
37
+ import { exportFileReceiptOrReason, receiptErrorLine } from './export-file-receipt.js';
38
38
  import { ExportStepError, errorText, verifySavedFile, writeStep, bytesForSave } from './export-file-check.js';
39
39
  import { EFFECT_BEFORE_JS, effectAfterJs, namedItemIds, idMayBeGone, NO_CHANGE_NOTE } from './effect-check.js';
40
40
  import { writeFile, mkdir, appendFile, unlink, readFile, stat } from 'node:fs/promises';
41
- import { tmpdir } from 'node:os';
42
41
  import { join, extname } from 'node:path';
43
42
  import { fileURLToPath } from 'node:url';
44
43
  import { getBrowserController, resetBrowserController, } from '../browser/puppeteer-controller.js';
@@ -59,12 +58,15 @@ import { exportHandlers } from './handlers/export.js';
59
58
  import { generateHandlers } from './handlers/generate.js';
60
59
  import { beatHandlers } from './handlers/beats.js';
61
60
  import { planCharacter, generateCharacterCode } from './handlers/character.js';
61
+ import { jsLiteral } from '../utils/js-literal.js';
62
62
  import { buildZip } from '../utils/zip.js';
63
63
  import { buildScc } from '../utils/scc.js';
64
64
  import { checkContrast, checkFlashes, parseCssColor } from '../utils/a11y.js';
65
65
  import { buildHtml5Ad, buildPlayable, externalRequests, pinSceneBackground } from '../utils/ad-package.js';
66
66
  import { safeFetch, checkUrl, UnsafeUrlError } from '../utils/safe-fetch.js';
67
67
  import { confineAssetPath } from '../utils/asset-paths.js';
68
+ import { env } from '../utils/env.js';
69
+ import { getExportDir, exportFileLink } from '../utils/export-dir.js';
68
70
  /**
69
71
  * Registry of per-domain handler maps. Tools listed here short-circuit the
70
72
  * main switch at dispatch time (see handleToolCallInner). New domains are
@@ -87,7 +89,7 @@ function getDomainHandlers() {
87
89
  * Defaults to 'on_request' for optimal performance.
88
90
  */
89
91
  export function getScreenshotMode() {
90
- const mode = process.env.PINEPAPER_SCREENSHOT_MODE?.toLowerCase();
92
+ const mode = env('PINEPAPER_SCREENSHOT_MODE')?.toLowerCase();
91
93
  if (mode === 'always' || mode === 'never' || mode === 'on_request') {
92
94
  return mode;
93
95
  }
@@ -97,9 +99,8 @@ export function getScreenshotMode() {
97
99
  // =============================================================================
98
100
  // EXPORT FILE SAVE HELPERS
99
101
  // =============================================================================
100
- export function getExportDir() {
101
- return process.env.PINEPAPER_EXPORT_DIR || join(tmpdir(), 'pinepaper-exports');
102
- }
102
+ // The export folder, one definition for the writers and for path confinement (utils/export-dir.ts).
103
+ export { getExportDir };
103
104
  // wav joins them: a minute of 48kHz 16-bit is ~5.8 MB of base64 and the ten
104
105
  // minutes the schema allows is ~77 MB. Deliverable across the bridge, useless
105
106
  // pasted into a response.
@@ -135,10 +136,10 @@ const SAVE_THRESHOLD_BYTES = 180_000; // ~180KB base64 ≈ 135KB decoded
135
136
  */
136
137
  const EXPORT_GOVERNOR_DEFAULT_MS = 300_000;
137
138
  function exportGovernorTimeoutMs() {
138
- const raw = Number(process.env.PINEPAPER_EXPORT_TIMEOUT);
139
+ const raw = Number(env('PINEPAPER_EXPORT_TIMEOUT'));
139
140
  if (!Number.isFinite(raw) || raw <= 0)
140
141
  return EXPORT_GOVERNOR_DEFAULT_MS;
141
- const protocolFloor = Math.max(Number(process.env.PINEPAPER_TIMEOUT) || 0, EXPORT_GOVERNOR_DEFAULT_MS);
142
+ const protocolFloor = Math.max(Number(env('PINEPAPER_TIMEOUT')) || 0, EXPORT_GOVERNOR_DEFAULT_MS);
142
143
  if (raw > protocolFloor) {
143
144
  console.error(`[PinePaper] PINEPAPER_EXPORT_TIMEOUT=${raw}ms is above the browser's protocol timeout (${protocolFloor}ms) — ` +
144
145
  'raise PINEPAPER_TIMEOUT to match, or an export past that point fails as a protocol error rather than a governor timeout.');
@@ -420,7 +421,7 @@ async function saveAdExport(r, input, platform) {
420
421
  content: [{
421
422
  type: 'text',
422
423
  text: `Ad saved:\n\n${Object.entries(files).map(([k, v]) => `${k}: ${v}`).join('\n')}\nFormat: ${r.format}\nSize: ${r.adSize.width}x${r.adSize.height}, upload ${(uploadBytes / 1024).toFixed(1)} KB\n\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
423
- }],
424
+ }, ...Object.values(files).filter((f) => typeof f === 'string').map((f) => exportFileLink(f))],
424
425
  };
425
426
  }
426
427
  async function saveExportToFile(data, format, platform) {
@@ -601,7 +602,7 @@ async function streamRetainedExportToFile(controller, retained, platform) {
601
602
  * Defaults to 'puppeteer' for live execution.
602
603
  */
603
604
  export function getExecutionMode() {
604
- const mode = process.env.PINEPAPER_EXECUTION_MODE?.toLowerCase();
605
+ const mode = env('PINEPAPER_EXECUTION_MODE')?.toLowerCase();
605
606
  if (mode === 'puppeteer' || mode === 'code') {
606
607
  return mode;
607
608
  }
@@ -792,7 +793,7 @@ function executedResult(code, result, screenshot, description, report) {
792
793
  const content = [
793
794
  { type: 'text', text: JSON.stringify(resultObj, null, 2) },
794
795
  ];
795
- if (process.env.PINEPAPER_ECHO_CODE === '1') {
796
+ if (env('PINEPAPER_ECHO_CODE') === '1') {
796
797
  content.push({ type: 'text', text: `Generated code:\n\`\`\`javascript\n${code}\n\`\`\`` });
797
798
  }
798
799
  if (screenshot) {
@@ -822,7 +823,7 @@ function screenshotResult(screenshot) {
822
823
  * error code, not a program, and stripping those would take information away.
823
824
  */
824
825
  function redactGeneratedCode(details) {
825
- if (process.env.PINEPAPER_ECHO_CODE === '1')
826
+ if (env('PINEPAPER_ECHO_CODE') === '1')
826
827
  return details;
827
828
  if (!details || typeof details !== 'object' || Array.isArray(details))
828
829
  return details;
@@ -1085,9 +1086,7 @@ export async function customCodeRollback(controller, error) {
1085
1086
  removed = v.removed ?? 0;
1086
1087
  left = typeof v.left === 'number' ? v.left : -1;
1087
1088
  }
1088
- catch {
1089
- rolledBack = false;
1090
- }
1089
+ catch { /* rolledBack stays false: nothing was confirmed removed */ }
1091
1090
  const scene = rolledBack
1092
1091
  ? (removed > 0
1093
1092
  ? `The ${removed} item(s) this call created before it stopped were removed. Changes it made to items that already existed are not reverted.`
@@ -1189,7 +1188,7 @@ async function writeResultDataToFile(out, ext) {
1189
1188
  const { data: _d, ...rest } = r;
1190
1189
  void _d;
1191
1190
  obj.result = { ...rest, filePath, bytes: bytes.length };
1192
- return { ...out, content: [{ type: 'text', text: JSON.stringify(obj, null, 2) }, ...out.content.slice(1)] };
1191
+ return { ...out, content: [{ type: 'text', text: JSON.stringify(obj, null, 2) }, ...out.content.slice(1), exportFileLink(filePath, bytes.length)] };
1193
1192
  }
1194
1193
  catch (e) {
1195
1194
  const err = e instanceof ExportStepError ? new ExportStepError(e.step, e.path ?? filePath, e.reason)
@@ -1569,7 +1568,7 @@ export function unwrapLint(run) {
1569
1568
  if (!r || typeof r !== 'object' || Array.isArray(r) || !('__ppLint' in r))
1570
1569
  return [];
1571
1570
  const lint = Array.isArray(r.__ppLint) ? r.__ppLint : [];
1572
- if ('__ppValue' in r)
1571
+ if ('__ppValue' in r || r.__ppWrapped === true)
1573
1572
  run.result = r.__ppValue;
1574
1573
  else {
1575
1574
  const { __ppLint: _l, ...rest } = r;
@@ -1847,7 +1846,46 @@ async function nestedIgnoredFor(toolName, args) {
1847
1846
  if (!tool)
1848
1847
  return [];
1849
1848
  const hidden = new Set(NESTED_HIDDEN[toolName] ?? []);
1850
- return nestedIgnoredPaths(tool.inputSchema, args).filter((p) => !hidden.has(p.replace(/\[\d+\]/g, '[]')));
1849
+ const paths = nestedIgnoredPaths(tool.inputSchema, args).filter((p) => !hidden.has(p.replace(/\[\d+\]/g, '[]')));
1850
+ return toolName === 'pinepaper_agent_batch_execute' ? [...paths, ...batchOpIgnoredPaths(args)] : paths;
1851
+ }
1852
+ /**
1853
+ * Batch op keys the batch reads that AgentBatchOperationSchema does not list. The op schema is passthrough (a create
1854
+ * takes shape properties flat), so these arrive intact; batch-op-keys.test.ts fails when the batch code reads a key
1855
+ * that is in neither list, so a working key is never reported. Shrink-only.
1856
+ */
1857
+ export const BATCH_OP_HIDDEN = {
1858
+ '*': ['_step', 'id'],
1859
+ keyframe_animate: ['params', 'timeUnits', 'timeOffset', 'clipInPoint', 'clipOutPoint'],
1860
+ timeline: ['repeat', 'yoyo', 'anchor'],
1861
+ };
1862
+ /** Hints for the slips that land on a key another op type reads. */
1863
+ const BATCH_OP_HINTS = {
1864
+ 'relation.params': 'a relation op takes its parameters as relationOptions',
1865
+ 'modify.x': 'a modify op takes changes inside properties', 'modify.y': 'a modify op takes changes inside properties',
1866
+ };
1867
+ /**
1868
+ * A key on a batch op that nothing reads (the passthrough schema let it in, so the nested walk cannot see it). A
1869
+ * `create` folds every op-level key into its properties, which create_item's own property report covers, so only the
1870
+ * other op types are checked here.
1871
+ */
1872
+ export function batchOpIgnoredPaths(args) {
1873
+ const ops = Array.isArray(args.operations) ? args.operations : [];
1874
+ const shape = Object.keys(AgentBatchOperationSchema.shape);
1875
+ const out = [];
1876
+ ops.forEach((op, i) => {
1877
+ const type = op && typeof op === 'object' && !Array.isArray(op) ? String(op.type) : 'create';
1878
+ if (type === 'create')
1879
+ return;
1880
+ const known = new Set([...shape, ...BATCH_OP_HIDDEN['*'], ...(BATCH_OP_HIDDEN[type] ?? [])]);
1881
+ for (const k of Object.keys(op)) {
1882
+ if (known.has(k))
1883
+ continue;
1884
+ const hint = BATCH_OP_HINTS[`${type}.${k}`];
1885
+ out.push(`operations[${i}].${k}${hint ? ` (${hint})` : ''}`);
1886
+ }
1887
+ });
1888
+ return out.slice(0, 50);
1851
1889
  }
1852
1890
  export const IGNORED_ARGUMENTS_META_KEY = 'pinepaper.studio/ignoredArguments';
1853
1891
  export async function handleToolCall(toolName, args, options = {}) {
@@ -2591,7 +2629,7 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
2591
2629
  const controller = options.browserController || getBrowserController();
2592
2630
  if (!controller.connected)
2593
2631
  await controller.connect();
2594
- const r = await controller.executeCode(`(function() { const ids = ${JSON.stringify(itemIds)}; return ids.filter(function(id) { const e = app.itemRegistry && app.itemRegistry.get(id); return !(e && e.item); }); })();`, false);
2632
+ const r = await controller.executeCode(`(function() { const ids = ${jsLiteral(itemIds)}; return ids.filter(function(id) { const e = app.itemRegistry && app.itemRegistry.get(id); return !(e && e.item); }); })();`, false);
2595
2633
  if (r.success && Array.isArray(r.result))
2596
2634
  missingItems = r.result;
2597
2635
  }
@@ -3035,7 +3073,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
3035
3073
  // Falls back to the PINEPAPER_STUDIO_URL env var, else PuppeteerController's
3036
3074
  // built-in prod default. "/editor" + agent params are appended by the controller.
3037
3075
  const rawUrl = typeof args.url === 'string' ? args.url.trim() : '';
3038
- const studioUrl = (rawUrl || (process.env.PINEPAPER_STUDIO_URL || '').trim()) || undefined;
3076
+ const studioUrl = (rawUrl || (env('PINEPAPER_STUDIO_URL') || '').trim()) || undefined;
3039
3077
  // Only http(s) is supported — reject file://, data://, javascript:, etc.
3040
3078
  if (studioUrl) {
3041
3079
  const v = validateStudioUrl(studioUrl);
@@ -3188,7 +3226,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
3188
3226
  version: SERVER_VERSION,
3189
3227
  toolkit: detectToolkit(),
3190
3228
  verbosity: detectVerbosity(),
3191
- locale: process.env.PINEPAPER_LOCALE || 'en',
3229
+ locale: env('PINEPAPER_LOCALE') || 'en',
3192
3230
  executionMode: options.executionMode || getExecutionMode(),
3193
3231
  screenshotMode: options.screenshotMode || getScreenshotMode(),
3194
3232
  };
@@ -3290,6 +3328,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
3290
3328
  })),
3291
3329
  profiles: {
3292
3330
  'full+verbose': fullTokens,
3331
+ 'core+compact': Math.ceil(Buffer.byteLength(JSON.stringify(getToolsForToolkit(getToolsForVerbosity('compact'), 'core')), 'utf-8') / 4),
3293
3332
  'agent+compact': agentCompactTokens,
3294
3333
  'agent+minimal': agentMinimalTokens,
3295
3334
  },
@@ -3302,7 +3341,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
3302
3341
  const recs = toolDefsSection.recommendations;
3303
3342
  const alternatives = [];
3304
3343
  for (const v of ['compact', 'minimal']) {
3305
- for (const k of ['full', 'agent', 'minimal']) {
3344
+ for (const k of ['full', 'agent', 'core', 'minimal']) {
3306
3345
  if (k === toolkit && v === verbosity)
3307
3346
  continue;
3308
3347
  const t = Math.ceil(Buffer.byteLength(JSON.stringify(getToolsForToolkit(getToolsForVerbosity(v), k)), 'utf-8') / 4);
@@ -3744,7 +3783,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
3744
3783
  type: 'text',
3745
3784
  // The script is not part of the answer here either — same reason
3746
3785
  // errors no longer carry it. PINEPAPER_ECHO_CODE=1 brings it back.
3747
- text: (process.env.PINEPAPER_ECHO_CODE === '1'
3786
+ text: (env('PINEPAPER_ECHO_CODE') === '1'
3748
3787
  ? `Executed PinePaper code:\n\n\`\`\`javascript\n${code}\n\`\`\`\n\n`
3749
3788
  : '')
3750
3789
  + `Full screenshot saved to: ${filePath} (${(fileSize / 1024).toFixed(1)} KB)\n\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
@@ -4017,13 +4056,13 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
4017
4056
  format,
4018
4057
  chunkBytes: Number(exportResult.chunkBytes) || undefined,
4019
4058
  }, input.platform || 'auto');
4020
- const receipt = await exportFileReceipt(filePath, exportResult);
4059
+ const receipt = await exportFileReceiptOrReason(filePath, exportResult);
4021
4060
  const cleanResult = { ...exportResult, data: undefined, filePath, fileSize, chunks, file: receipt };
4022
4061
  return withEngineReport({
4023
4062
  content: [{
4024
4063
  type: 'text',
4025
- text: `Export saved to file:\n\nFile: ${filePath}\nFormat: ${format}\nSize: ${(fileSize / 1024).toFixed(1)} KB\nPlatform: ${input.platform}\nPaged out of the studio's export store in ${chunks} chunk${chunks === 1 ? '' : 's'}\n${probeLines(receipt)}${lintLines(exportResult)}\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
4026
- }],
4064
+ text: `Export saved to file:\n\nFile: ${filePath}\nFormat: ${format}\nSize: ${(fileSize / 1024).toFixed(1)} KB\nPlatform: ${input.platform}\nPaged out of the studio's export store in ${chunks} chunk${chunks === 1 ? '' : 's'}\n${receiptErrorLine(receipt)}${'receiptError' in receipt ? '' : probeLines(receipt)}${lintLines(exportResult)}\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
4065
+ }, exportFileLink(filePath, fileSize)],
4027
4066
  }, exportBrowserResult.report);
4028
4067
  }
4029
4068
  catch (streamError) {
@@ -4056,7 +4095,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
4056
4095
  const { sccCues: _c, ...rest } = exportResult;
4057
4096
  void _c;
4058
4097
  const cleanResult = { ...rest, filePath, fileSize, frameRate: '29.97 drop-frame', fidelity: { warnings } };
4059
- return withEngineReport({ content: [{ type: 'text', text: `Export saved to file:\n\nFile: ${filePath}\nFormat: scc (CEA-608, CC1, pop-on)\nSize: ${fileSize} bytes\n\nResult: ${JSON.stringify(cleanResult, null, 2)}` }] }, exportBrowserResult.report);
4098
+ return withEngineReport({ content: [{ type: 'text', text: `Export saved to file:\n\nFile: ${filePath}\nFormat: scc (CEA-608, CC1, pop-on)\nSize: ${fileSize} bytes\n\nResult: ${JSON.stringify(cleanResult, null, 2)}` }, exportFileLink(filePath, fileSize)] }, exportBrowserResult.report);
4060
4099
  }
4061
4100
  if ((format === 'html5-ad' || format === 'playable') && typeof exportResult?.adPage === 'string') {
4062
4101
  try {
@@ -4079,7 +4118,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
4079
4118
  if (shouldSaveToFile) {
4080
4119
  try {
4081
4120
  const { filePath, fileSize } = await saveExportToFile(data, format, input.platform || 'auto');
4082
- const receipt = await exportFileReceipt(filePath, exportResult);
4121
+ const receipt = await exportFileReceiptOrReason(filePath, exportResult);
4083
4122
  const cleanResult = { ...exportResult, data: undefined, filePath, fileSize, file: receipt };
4084
4123
  // A mix that dropped sounds wrote a perfectly good file that is
4085
4124
  // missing tracks. It rides the SUCCESS path, which is exactly why
@@ -4090,10 +4129,11 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
4090
4129
  type: 'text',
4091
4130
  text: `Export saved to file:\n\nFile: ${filePath}\nFormat: ${format}\nSize: ${(fileSize / 1024).toFixed(1)} KB\nPlatform: ${input.platform}\n`
4092
4131
  + (dropNote ? `DROPPED: ${dropNote}\n` : '')
4093
- + probeLines(receipt)
4132
+ + receiptErrorLine(receipt)
4133
+ + ('receiptError' in receipt ? '' : probeLines(receipt))
4094
4134
  + lintLines(exportResult)
4095
4135
  + `\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
4096
- }],
4136
+ }, exportFileLink(filePath, fileSize)],
4097
4137
  }, exportBrowserResult.report);
4098
4138
  }
4099
4139
  catch (saveError) {