@imgly/codesign-mcp 0.1.7 → 0.1.8

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 (104) hide show
  1. package/assets/docs/api/index.d.ts +9 -3
  2. package/assets/docs/guide/SKILL.md +2 -2
  3. package/assets/docs/guide/api/node/classes/creativeengine.md +13 -13
  4. package/assets/docs/guide/edit-image/transform/scale.md +1 -3
  5. package/assets/docs/guide/filters-and-effects/duotone.md +1 -1
  6. package/assets/docs/guide/get-started/bun.md +0 -2
  7. package/assets/docs/guide/get-started/deno.md +0 -1
  8. package/assets/docs/guide/get-started/vanilla.md +0 -2
  9. package/assets/docs/guide/import-media/default-assets.md +20 -24
  10. package/assets/docs/guide/plugins/print-ready-pdf.md +0 -2
  11. package/assets/docs/guide/serve-assets.md +80 -23
  12. package/assets/docs/handbook/SKILL.md +6 -7
  13. package/dist/assets/inline.generated.js +12 -12
  14. package/dist/assets/inline.generated.js.map +1 -1
  15. package/dist/cli/http.d.ts.map +1 -1
  16. package/dist/cli/http.js +17 -3
  17. package/dist/cli/http.js.map +1 -1
  18. package/dist/cli/stdio.d.ts.map +1 -1
  19. package/dist/cli/stdio.js +44 -15
  20. package/dist/cli/stdio.js.map +1 -1
  21. package/dist/cli/trial-license.generated.d.ts +1 -1
  22. package/dist/cli/trial-license.generated.js +1 -1
  23. package/dist/cli/trial-license.generated.js.map +1 -1
  24. package/dist/config.d.ts +6 -0
  25. package/dist/config.d.ts.map +1 -1
  26. package/dist/config.js +8 -0
  27. package/dist/config.js.map +1 -1
  28. package/dist/diagnostics/bundle.d.ts +3 -0
  29. package/dist/diagnostics/bundle.d.ts.map +1 -1
  30. package/dist/diagnostics/bundle.js +25 -22
  31. package/dist/diagnostics/bundle.js.map +1 -1
  32. package/dist/diagnostics/prompt.d.ts +3 -0
  33. package/dist/diagnostics/prompt.d.ts.map +1 -0
  34. package/dist/diagnostics/prompt.js +21 -0
  35. package/dist/diagnostics/prompt.js.map +1 -0
  36. package/dist/diagnostics/router.d.ts +12 -0
  37. package/dist/diagnostics/router.d.ts.map +1 -0
  38. package/dist/diagnostics/router.js +64 -0
  39. package/dist/diagnostics/router.js.map +1 -0
  40. package/dist/diagnostics/spa-assets.generated.d.ts +2 -0
  41. package/dist/diagnostics/spa-assets.generated.d.ts.map +1 -0
  42. package/dist/diagnostics/spa-assets.generated.js +1231 -0
  43. package/dist/diagnostics/spa-assets.generated.js.map +1 -0
  44. package/dist/diagnostics/status.d.ts +38 -0
  45. package/dist/diagnostics/status.d.ts.map +1 -0
  46. package/dist/diagnostics/status.js +53 -0
  47. package/dist/diagnostics/status.js.map +1 -0
  48. package/dist/diagnostics/summary.d.ts +48 -0
  49. package/dist/diagnostics/summary.d.ts.map +1 -0
  50. package/dist/diagnostics/summary.js +116 -0
  51. package/dist/diagnostics/summary.js.map +1 -0
  52. package/dist/diagnostics/tool.d.ts +16 -0
  53. package/dist/diagnostics/tool.d.ts.map +1 -0
  54. package/dist/diagnostics/tool.js +90 -0
  55. package/dist/diagnostics/tool.js.map +1 -0
  56. package/dist/engine/asset-sources.d.ts +8 -0
  57. package/dist/engine/asset-sources.d.ts.map +1 -1
  58. package/dist/engine/asset-sources.js +9 -2
  59. package/dist/engine/asset-sources.js.map +1 -1
  60. package/dist/engine/uri-resolver.js.map +1 -1
  61. package/dist/server.d.ts +25 -0
  62. package/dist/server.d.ts.map +1 -1
  63. package/dist/server.js +24 -4
  64. package/dist/server.js.map +1 -1
  65. package/dist/tools/engine.d.ts +79 -14
  66. package/dist/tools/engine.d.ts.map +1 -1
  67. package/dist/tools/engine.js +119 -20
  68. package/dist/tools/engine.js.map +1 -1
  69. package/dist/tools/export.d.ts.map +1 -1
  70. package/dist/tools/export.js +34 -4
  71. package/dist/tools/export.js.map +1 -1
  72. package/dist/tools/index.js +2 -2
  73. package/dist/tools/index.js.map +1 -1
  74. package/dist/tools/load.d.ts.map +1 -1
  75. package/dist/tools/load.js +26 -19
  76. package/dist/tools/load.js.map +1 -1
  77. package/dist/tools/preview.d.ts.map +1 -1
  78. package/dist/tools/preview.js +60 -40
  79. package/dist/tools/preview.js.map +1 -1
  80. package/dist/tools/{open-editor.d.ts → view.d.ts} +3 -3
  81. package/dist/tools/view.d.ts.map +1 -0
  82. package/dist/tools/{open-editor.js → view.js} +20 -18
  83. package/dist/tools/view.js.map +1 -0
  84. package/dist/viewer/listener.d.ts +2 -0
  85. package/dist/viewer/listener.d.ts.map +1 -1
  86. package/dist/viewer/listener.js +2 -0
  87. package/dist/viewer/listener.js.map +1 -1
  88. package/dist/viewer/presigned-r2-url-builder.d.ts +1 -1
  89. package/dist/viewer/presigned-r2-url-builder.js +1 -1
  90. package/dist/viewer/presigned-r2-url-builder.js.map +1 -1
  91. package/dist/viewer/router.d.ts.map +1 -1
  92. package/dist/viewer/router.js +8 -17
  93. package/dist/viewer/router.js.map +1 -1
  94. package/dist/viewer/session-mount.js.map +1 -1
  95. package/dist/viewer/spa-assets.generated.d.ts.map +1 -1
  96. package/dist/viewer/spa-assets.generated.js +750 -869
  97. package/dist/viewer/spa-assets.generated.js.map +1 -1
  98. package/dist/viewer/url-builder.d.ts +4 -3
  99. package/dist/viewer/url-builder.d.ts.map +1 -1
  100. package/dist/viewer/url-builder.js +4 -4
  101. package/dist/viewer/url-builder.js.map +1 -1
  102. package/package.json +6 -5
  103. package/dist/tools/open-editor.d.ts.map +0 -1
  104. package/dist/tools/open-editor.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"uri-resolver.js","sourceRoot":"","sources":["../../src/engine/uri-resolver.ts"],"names":[],"mappings":"AAOA,MAAM,oBAAoB,GAAG,cAAc,CAAC;AAc5C;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAA2B,EAC3B,UAA4B;IAE5B,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,GAAG,EAAE,eAAe,EAAE,EAAE,CACpD,GAAG,CAAC,UAAU,CAAC,oBAAoB,CAAC;QAClC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAChE,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CACzB,CAAC;AACJ,CAAC","sourcesContent":["//\n// Registers a CE.SDK URI resolver that rewrites `workspace://<relpath>` asset\n// URIs (stored durably in scenes) to the HTTP `/r/` URL the engine can fetch.\n// Everything else delegates to CE.SDK's default resolver. Used by the headless\n// node engine; the browser viewer mirrors this logic in viewer.js.\nimport type { ViewerUrlBuilder } from '../viewer/url-builder.js';\n\nconst WORKSPACE_URI_PREFIX = 'workspace://';\n\n/** The minimal slice of CreativeEngine this module needs — keeps it testable. */\nexport interface UriResolvableEngine {\n editor: {\n setURIResolver(\n resolver: (\n uri: string,\n defaultResolver: (uri: string) => string\n ) => string\n ): void;\n };\n}\n\n/**\n * Make the engine resolve `workspace://…` asset URIs to the `/r/` HTTP route.\n * Scenes persist the durable `workspace://` form; this rewrites it to a live\n * fetch URL at load time, so a design survives a process restart / rewind.\n */\nexport function registerWorkspaceUriResolver(\n engine: UriResolvableEngine,\n urlBuilder: ViewerUrlBuilder\n): void {\n engine.editor.setURIResolver((uri, defaultResolver) =>\n uri.startsWith(WORKSPACE_URI_PREFIX)\n ? urlBuilder.forResource(uri.slice(WORKSPACE_URI_PREFIX.length))\n : defaultResolver(uri)\n );\n}\n"]}
1
+ {"version":3,"file":"uri-resolver.js","sourceRoot":"","sources":["../../src/engine/uri-resolver.ts"],"names":[],"mappings":"AAOA,MAAM,oBAAoB,GAAG,cAAc,CAAC;AAc5C;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAA2B,EAC3B,UAA4B;IAE5B,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,GAAG,EAAE,eAAe,EAAE,EAAE,CACpD,GAAG,CAAC,UAAU,CAAC,oBAAoB,CAAC;QAClC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAChE,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CACzB,CAAC;AACJ,CAAC","sourcesContent":["//\n// Registers a CE.SDK URI resolver that rewrites `workspace://<relpath>` asset\n// URIs (stored durably in scenes) to the HTTP `/r/` URL the engine can fetch.\n// Everything else delegates to CE.SDK's default resolver. Used by the headless\n// node engine; the browser editor mirrors this logic in editor.js.\nimport type { ViewerUrlBuilder } from '../viewer/url-builder.js';\n\nconst WORKSPACE_URI_PREFIX = 'workspace://';\n\n/** The minimal slice of CreativeEngine this module needs — keeps it testable. */\nexport interface UriResolvableEngine {\n editor: {\n setURIResolver(\n resolver: (\n uri: string,\n defaultResolver: (uri: string) => string\n ) => string\n ): void;\n };\n}\n\n/**\n * Make the engine resolve `workspace://…` asset URIs to the `/r/` HTTP route.\n * Scenes persist the durable `workspace://` form; this rewrites it to a live\n * fetch URL at load time, so a design survives a process restart / rewind.\n */\nexport function registerWorkspaceUriResolver(\n engine: UriResolvableEngine,\n urlBuilder: ViewerUrlBuilder\n): void {\n engine.editor.setURIResolver((uri, defaultResolver) =>\n uri.startsWith(WORKSPACE_URI_PREFIX)\n ? urlBuilder.forResource(uri.slice(WORKSPACE_URI_PREFIX.length))\n : defaultResolver(uri)\n );\n}\n"]}
package/dist/server.d.ts CHANGED
@@ -22,6 +22,29 @@ export interface CodesignMcpToolRegistration {
22
22
  isError?: boolean;
23
23
  }>;
24
24
  }
25
+ /**
26
+ * One prompt (slash command) the server exposes via prompts/list +
27
+ * prompts/get. Mirrors `CodesignMcpToolRegistration`'s caller-supplied shape.
28
+ */
29
+ export interface CodesignMcpPromptRegistration {
30
+ name: string;
31
+ description?: string;
32
+ arguments?: Array<{
33
+ name: string;
34
+ description?: string;
35
+ required?: boolean;
36
+ }>;
37
+ get: (args: Record<string, string> | undefined) => {
38
+ description?: string;
39
+ messages: Array<{
40
+ role: 'user' | 'assistant';
41
+ content: {
42
+ type: 'text';
43
+ text: string;
44
+ };
45
+ }>;
46
+ };
47
+ }
25
48
  export interface CodesignMcpServerOptions {
26
49
  /**
27
50
  * Asset bundle. Keys are forward-slash paths under one of:
@@ -34,6 +57,8 @@ export interface CodesignMcpServerOptions {
34
57
  resourceScheme?: string;
35
58
  /** Tools to expose via tools/list + tools/call. */
36
59
  tools?: CodesignMcpToolRegistration[];
60
+ /** MCP prompts (slash commands). Advertised only when at least one is passed. */
61
+ prompts?: CodesignMcpPromptRegistration[];
37
62
  /**
38
63
  * Build identity reported as `serverInfo.version` on `initialize`. A client
39
64
  * (the deploy smoketest) can assert it to confirm it's talking to the
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAYnE;;;;;GAKG;AACH,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;QAC/B,OAAO,EAAE,KAAK,CACV;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,GAC9B;YAAE,IAAI,EAAE,OAAO,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CACpD,CAAC;QACF,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,KAAK,CAAC,EAAE,2BAA2B,EAAE,CAAC;IACtC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,wBAAwB,GAC7B,MAAM,CAmIR"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAcnE;;;;;GAKG;AACH,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;QAC/B,OAAO,EAAE,KAAK,CACV;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,GAC9B;YAAE,IAAI,EAAE,OAAO,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CACpD,CAAC;QACF,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC,CAAC;CACJ;AAED;;;GAGG;AACH,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC,CAAC;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,KAAK;QACjD,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,KAAK,CAAC;YACd,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;YAC3B,OAAO,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAA;aAAE,CAAC;SACzC,CAAC,CAAC;KACJ,CAAC;CACH;AAED,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,KAAK,CAAC,EAAE,2BAA2B,EAAE,CAAC;IACtC,iFAAiF;IACjF,OAAO,CAAC,EAAE,6BAA6B,EAAE,CAAC;IAC1C;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,wBAAwB,GAC7B,MAAM,CA2JR"}
package/dist/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
2
+ import { CallToolRequestSchema, ErrorCode, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
3
3
  import { rewriteSkillLinks } from './cross-ref.js';
4
4
  import { buildSkillReadTools } from './skill-tools.js';
5
5
  const DOC_FILE = /^docs\/([^/]+)\/(.+)$/;
@@ -31,7 +31,7 @@ export function createCodesignMcpServer(opts) {
31
31
  '',
32
32
  'Design tools (after reading the handbook):',
33
33
  '- `edit({ parent, code, title?, note? })` — mutate the canvas; `parent` is REQUIRED (revision id, or null for a new design).',
34
- '- `preview({ blockId, revision? })` — render a block to an inline image.',
34
+ '- `preview({ blockId, revision })` — render a block to an inline image. `revision` is required (the id `edit` returned).',
35
35
  '- `export({ target, format, revision, blockId })` — render a block to pdf/image and write it into the workspace.',
36
36
  '- `save({ revision, outPath? })` — write a self-contained `.zip` archive (scene + assets) of a revision into the workspace.',
37
37
  '',
@@ -40,7 +40,7 @@ export function createCodesignMcpServer(opts) {
40
40
  '- `history({ revision })` — walk back to the root; how you re-ground after a rewind.',
41
41
  '- `inspect({ revision })` — read one revision in full, including the code that produced it (patterns, not block ids).',
42
42
  '- `rename({ revision, title })` — rename a design.',
43
- '- `open_editor({ revision? })` — URL the human can open to view the design.',
43
+ '- `view({ revision })` — URL the human can open to view the design.',
44
44
  '- `asset_search({ sourceId?, query? })` — browse asset sources; apply results in `edit`.'
45
45
  ]
46
46
  .concat(hasAssetGenerate
@@ -64,7 +64,11 @@ export function createCodesignMcpServer(opts) {
64
64
  rewrittenContent.set(uri, rewriteSkillLinks(content, doc, scheme));
65
65
  }
66
66
  const server = new Server({ name: 'codesign', version: opts.buildId ?? 'dev' }, {
67
- capabilities: { resources: {}, tools: {} },
67
+ capabilities: {
68
+ resources: {},
69
+ tools: {},
70
+ ...(opts.prompts && opts.prompts.length > 0 ? { prompts: {} } : {})
71
+ },
68
72
  instructions
69
73
  });
70
74
  // `resources/list` advertises only the per-skill SKILL.md (the entry
@@ -114,6 +118,22 @@ export function createCodesignMcpServer(opts) {
114
118
  }
115
119
  return await t.call(req.params.arguments ?? {});
116
120
  });
121
+ if (opts.prompts && opts.prompts.length > 0) {
122
+ const prompts = new Map(opts.prompts.map((p) => [p.name, p]));
123
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
124
+ prompts: Array.from(prompts.values()).map((p) => ({
125
+ name: p.name,
126
+ ...(p.description ? { description: p.description } : {}),
127
+ ...(p.arguments ? { arguments: p.arguments } : {})
128
+ }))
129
+ }));
130
+ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
131
+ const p = prompts.get(req.params.name);
132
+ if (!p)
133
+ throw new McpError(ErrorCode.InvalidParams, `Unknown prompt: ${req.params.name}`);
134
+ return p.get(req.params.arguments);
135
+ });
136
+ }
117
137
  return server;
118
138
  }
119
139
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequestSchema,\n ErrorCode,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ReadResourceRequestSchema\n} from '@modelcontextprotocol/sdk/types.js';\nimport { rewriteSkillLinks } from './cross-ref.js';\nimport { buildSkillReadTools } from './skill-tools.js';\n\n/**\n * One tool the server exposes via tools/list + tools/call. Caller-supplied so\n * the engine-bound implementations live near the surface that owns the engine\n * (the demo passes its existing `createDesignTools` output adapted to this\n * shape; an external bootstrap could pass server-side equivalents).\n */\nexport interface CodesignMcpToolRegistration {\n name: string;\n description?: string;\n /** JSON Schema describing the tool's input arguments. */\n inputSchema: unknown;\n call: (args: unknown) => Promise<{\n content: Array<\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string }\n >;\n isError?: boolean;\n }>;\n}\n\nexport interface CodesignMcpServerOptions {\n /**\n * Asset bundle. Keys are forward-slash paths under one of:\n * \"docs/<doc>/SKILL.md\" — entry point for handbook / api / guide\n * \"docs/<doc>/<file>\" — sub-files reachable via the doc tools\n * Values are file contents.\n */\n assets: Record<string, string>;\n /** URI scheme for resources. Default: \"codesign\". */\n resourceScheme?: string;\n /** Tools to expose via tools/list + tools/call. */\n tools?: CodesignMcpToolRegistration[];\n /**\n * Build identity reported as `serverInfo.version` on `initialize`. A client\n * (the deploy smoketest) can assert it to confirm it's talking to the\n * just-deployed build, not a stale container. Default `'dev'`.\n */\n buildId?: string;\n}\n\nconst DOC_FILE = /^docs\\/([^/]+)\\/(.+)$/;\n\nexport function createCodesignMcpServer(\n opts: CodesignMcpServerOptions\n): Server {\n const scheme = opts.resourceScheme ?? 'codesign';\n const assets = opts.assets;\n\n // `instructions` is intentionally short — it tells the model what the\n // server is and STRONGLY directs it to call `codesign_read_codesign_system`\n // before its first edit. Skills are now first-class tools (not MCP\n // resources buried in an XML block), so discovery happens via tools/list\n // on every host without needing ReadMcpResourceTool support.\n //\n // Why short: Claude Code wraps MCP `instructions` in a <system-reminder>\n // and truncates long ones mid-stream. Keeping `instructions` under ~1.5 KB\n // ensures the whole pointer survives any sane truncation.\n //\n // The asset-generation hint is included only when an `asset_generate` tool\n // is actually registered (Local hides it without an API_KEY), so we never\n // advertise a tool the model can't see in tools/list.\n const hasAssetGenerate = (opts.tools ?? []).some(\n (t) => t.name === 'asset_generate'\n );\n const instructions = [\n 'This is the **codesign** MCP server. It hosts a CE.SDK CreativeEngine and a persistent design workspace (revision DAG) for vector design + print-quality PDF output.',\n '',\n '**Required first step** — before any `edit`, call `handbook` (no args). It teaches the mandatory `parent` argument, dirty-on-throw safety, rewind recovery via `list`/`history`, the typeface schema setFont requires, and the runtime quirks that bite first attempts. Skipping it produces broken output.',\n '',\n 'Reference docs, on demand:',\n '- `api({ path?, offset?, limit? })` — CE.SDK TypeScript signatures.',\n '- `guide({ path? })` — CE.SDK Vanilla JS reference docs / topic guides.',\n '',\n 'Design tools (after reading the handbook):',\n '- `edit({ parent, code, title?, note? })` — mutate the canvas; `parent` is REQUIRED (revision id, or null for a new design).',\n '- `preview({ blockId, revision? })` — render a block to an inline image.',\n '- `export({ target, format, revision, blockId })` — render a block to pdf/image and write it into the workspace.',\n '- `save({ revision, outPath? })` — write a self-contained `.zip` archive (scene + assets) of a revision into the workspace.',\n '',\n 'Workspace tools (for resuming + navigating across designs):',\n '- `list()` — every design in the workspace, newest first.',\n '- `history({ revision })` — walk back to the root; how you re-ground after a rewind.',\n '- `inspect({ revision })` — read one revision in full, including the code that produced it (patterns, not block ids).',\n '- `rename({ revision, title })` — rename a design.',\n '- `open_editor({ revision? })` — URL the human can open to view the design.',\n '- `asset_search({ sourceId?, query? })` — browse asset sources; apply results in `edit`.'\n ]\n .concat(\n hasAssetGenerate\n ? [\n '',\n 'Asset generation:',\n \"- `asset_generate({ kind: 'image', prompt, width?, height? })` — opt-in (Local needs API_KEY); Hosted is cost-gated.\"\n ]\n : []\n )\n .join('\\n');\n\n // Build the resource map up-front: URI → rewritten markdown body. Cross-refs\n // inside doc content are rewritten to absolute MCP URIs so the model never\n // has to resolve a relative path against a doc dir.\n const rewrittenContent = new Map<string, string>();\n for (const [key, content] of Object.entries(assets)) {\n const m = key.match(DOC_FILE);\n if (!m) continue;\n const [, doc, rest] = m;\n const uri = `${scheme}://docs/${doc}/${rest}`;\n rewrittenContent.set(uri, rewriteSkillLinks(content, doc, scheme));\n }\n\n const server = new Server(\n { name: 'codesign', version: opts.buildId ?? 'dev' },\n {\n capabilities: { resources: {}, tools: {} },\n instructions\n }\n );\n\n // `resources/list` advertises only the per-skill SKILL.md (the entry\n // point), not the 392 nested cesdk-guide files. Sub-files stay readable\n // via `resources/read` against any cached URI, but skill content discovery\n // happens through the codesign_read_* tools — those are the universal\n // surface across MCP hosts.\n server.setRequestHandler(ListResourcesRequestSchema, async () => ({\n resources: Array.from(rewrittenContent.keys())\n .filter((uri) => uri.endsWith('/SKILL.md'))\n .map((uri) => ({\n uri,\n mimeType: 'text/markdown',\n name: uri.replace(`${scheme}://`, '')\n }))\n }));\n\n server.setRequestHandler(ReadResourceRequestSchema, async (req) => {\n const text = rewrittenContent.get(req.params.uri);\n if (text === undefined) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown resource: ${req.params.uri}`\n );\n }\n return {\n contents: [{ uri: req.params.uri, mimeType: 'text/markdown', text }]\n };\n });\n\n // Tools registered with the server: caller-supplied design tools + the\n // auto-generated skill-read tools (one per skill in the asset bundle).\n // The skill tools always come first so Level 1 wording in the design tool\n // descriptions (\"call codesign_read_codesign_system first\") points at\n // something the model already sees in tools/list.\n const tools = new Map<string, CodesignMcpToolRegistration>();\n for (const t of buildSkillReadTools(assets)) tools.set(t.name, t);\n for (const t of opts.tools ?? []) tools.set(t.name, t);\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: Array.from(tools.values()).map((t) => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema as never\n }))\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const t = tools.get(req.params.name);\n if (!t) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown tool: ${req.params.name}`\n );\n }\n return await t.call(req.params.arguments ?? {});\n });\n\n return server;\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EACL,qBAAqB,EACrB,SAAS,EACT,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,QAAQ,EACR,yBAAyB,EAC1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAiEvD,MAAM,QAAQ,GAAG,uBAAuB,CAAC;AAEzC,MAAM,UAAU,uBAAuB,CACrC,IAA8B;IAE9B,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,IAAI,UAAU,CAAC;IACjD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAE3B,sEAAsE;IACtE,4EAA4E;IAC5E,mEAAmE;IACnE,yEAAyE;IACzE,6DAA6D;IAC7D,EAAE;IACF,yEAAyE;IACzE,2EAA2E;IAC3E,0DAA0D;IAC1D,EAAE;IACF,2EAA2E;IAC3E,0EAA0E;IAC1E,sDAAsD;IACtD,MAAM,gBAAgB,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAC9C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,gBAAgB,CACnC,CAAC;IACF,MAAM,YAAY,GAAG;QACnB,sKAAsK;QACtK,EAAE;QACF,6SAA6S;QAC7S,EAAE;QACF,4BAA4B;QAC5B,qEAAqE;QACrE,yEAAyE;QACzE,EAAE;QACF,4CAA4C;QAC5C,8HAA8H;QAC9H,0HAA0H;QAC1H,kHAAkH;QAClH,6HAA6H;QAC7H,EAAE;QACF,6DAA6D;QAC7D,2DAA2D;QAC3D,sFAAsF;QACtF,uHAAuH;QACvH,oDAAoD;QACpD,qEAAqE;QACrE,0FAA0F;KAC3F;SACE,MAAM,CACL,gBAAgB;QACd,CAAC,CAAC;YACE,EAAE;YACF,mBAAmB;YACnB,sHAAsH;SACvH;QACH,CAAC,CAAC,EAAE,CACP;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,6EAA6E;IAC7E,2EAA2E;IAC3E,oDAAoD;IACpD,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAC;IACnD,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACpD,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,MAAM,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,MAAM,GAAG,GAAG,GAAG,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC;QAC9C,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,iBAAiB,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK,EAAE,EACpD;QACE,YAAY,EAAE;YACZ,SAAS,EAAE,EAAE;YACb,KAAK,EAAE,EAAE;YACT,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpE;QACD,YAAY;KACb,CACF,CAAC;IAEF,qEAAqE;IACrE,wEAAwE;IACxE,2EAA2E;IAC3E,sEAAsE;IACtE,4BAA4B;IAC5B,MAAM,CAAC,iBAAiB,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAChE,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;aAC3C,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;aAC1C,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACb,GAAG;YACH,QAAQ,EAAE,eAAe;YACzB,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,MAAM,KAAK,EAAE,EAAE,CAAC;SACtC,CAAC,CAAC;KACN,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,yBAAyB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAChE,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,aAAa,EACvB,qBAAqB,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CACtC,CAAC;QACJ,CAAC;QACD,OAAO;YACL,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;SACrE,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,uEAAuE;IACvE,uEAAuE;IACvE,0EAA0E;IAC1E,sEAAsE;IACtE,kDAAkD;IAClD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAuC,CAAC;IAC7D,KAAK,MAAM,CAAC,IAAI,mBAAmB,CAAC,MAAM,CAAC;QAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAClE,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE;QAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAEvD,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5C,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,WAAW,EAAE,CAAC,CAAC,WAAoB;SACpC,CAAC,CAAC;KACJ,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAC5D,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,aAAa,EACvB,iBAAiB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CACnC,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9D,MAAM,CAAC,iBAAiB,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;YAC9D,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAChD,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACxD,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnD,CAAC,CAAC;SACJ,CAAC,CAAC,CAAC;QACJ,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC7D,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,CAAC,CAAC;gBACJ,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,aAAa,EACvB,mBAAmB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CACrC,CAAC;YACJ,OAAO,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequestSchema,\n ErrorCode,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ReadResourceRequestSchema\n} from '@modelcontextprotocol/sdk/types.js';\nimport { rewriteSkillLinks } from './cross-ref.js';\nimport { buildSkillReadTools } from './skill-tools.js';\n\n/**\n * One tool the server exposes via tools/list + tools/call. Caller-supplied so\n * the engine-bound implementations live near the surface that owns the engine\n * (the demo passes its existing `createDesignTools` output adapted to this\n * shape; an external bootstrap could pass server-side equivalents).\n */\nexport interface CodesignMcpToolRegistration {\n name: string;\n description?: string;\n /** JSON Schema describing the tool's input arguments. */\n inputSchema: unknown;\n call: (args: unknown) => Promise<{\n content: Array<\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string }\n >;\n isError?: boolean;\n }>;\n}\n\n/**\n * One prompt (slash command) the server exposes via prompts/list +\n * prompts/get. Mirrors `CodesignMcpToolRegistration`'s caller-supplied shape.\n */\nexport interface CodesignMcpPromptRegistration {\n name: string;\n description?: string;\n arguments?: Array<{\n name: string;\n description?: string;\n required?: boolean;\n }>;\n get: (args: Record<string, string> | undefined) => {\n description?: string;\n messages: Array<{\n role: 'user' | 'assistant';\n content: { type: 'text'; text: string };\n }>;\n };\n}\n\nexport interface CodesignMcpServerOptions {\n /**\n * Asset bundle. Keys are forward-slash paths under one of:\n * \"docs/<doc>/SKILL.md\" — entry point for handbook / api / guide\n * \"docs/<doc>/<file>\" — sub-files reachable via the doc tools\n * Values are file contents.\n */\n assets: Record<string, string>;\n /** URI scheme for resources. Default: \"codesign\". */\n resourceScheme?: string;\n /** Tools to expose via tools/list + tools/call. */\n tools?: CodesignMcpToolRegistration[];\n /** MCP prompts (slash commands). Advertised only when at least one is passed. */\n prompts?: CodesignMcpPromptRegistration[];\n /**\n * Build identity reported as `serverInfo.version` on `initialize`. A client\n * (the deploy smoketest) can assert it to confirm it's talking to the\n * just-deployed build, not a stale container. Default `'dev'`.\n */\n buildId?: string;\n}\n\nconst DOC_FILE = /^docs\\/([^/]+)\\/(.+)$/;\n\nexport function createCodesignMcpServer(\n opts: CodesignMcpServerOptions\n): Server {\n const scheme = opts.resourceScheme ?? 'codesign';\n const assets = opts.assets;\n\n // `instructions` is intentionally short — it tells the model what the\n // server is and STRONGLY directs it to call `codesign_read_codesign_system`\n // before its first edit. Skills are now first-class tools (not MCP\n // resources buried in an XML block), so discovery happens via tools/list\n // on every host without needing ReadMcpResourceTool support.\n //\n // Why short: Claude Code wraps MCP `instructions` in a <system-reminder>\n // and truncates long ones mid-stream. Keeping `instructions` under ~1.5 KB\n // ensures the whole pointer survives any sane truncation.\n //\n // The asset-generation hint is included only when an `asset_generate` tool\n // is actually registered (Local hides it without an API_KEY), so we never\n // advertise a tool the model can't see in tools/list.\n const hasAssetGenerate = (opts.tools ?? []).some(\n (t) => t.name === 'asset_generate'\n );\n const instructions = [\n 'This is the **codesign** MCP server. It hosts a CE.SDK CreativeEngine and a persistent design workspace (revision DAG) for vector design + print-quality PDF output.',\n '',\n '**Required first step** — before any `edit`, call `handbook` (no args). It teaches the mandatory `parent` argument, dirty-on-throw safety, rewind recovery via `list`/`history`, the typeface schema setFont requires, and the runtime quirks that bite first attempts. Skipping it produces broken output.',\n '',\n 'Reference docs, on demand:',\n '- `api({ path?, offset?, limit? })` — CE.SDK TypeScript signatures.',\n '- `guide({ path? })` — CE.SDK Vanilla JS reference docs / topic guides.',\n '',\n 'Design tools (after reading the handbook):',\n '- `edit({ parent, code, title?, note? })` — mutate the canvas; `parent` is REQUIRED (revision id, or null for a new design).',\n '- `preview({ blockId, revision })` — render a block to an inline image. `revision` is required (the id `edit` returned).',\n '- `export({ target, format, revision, blockId })` — render a block to pdf/image and write it into the workspace.',\n '- `save({ revision, outPath? })` — write a self-contained `.zip` archive (scene + assets) of a revision into the workspace.',\n '',\n 'Workspace tools (for resuming + navigating across designs):',\n '- `list()` — every design in the workspace, newest first.',\n '- `history({ revision })` — walk back to the root; how you re-ground after a rewind.',\n '- `inspect({ revision })` — read one revision in full, including the code that produced it (patterns, not block ids).',\n '- `rename({ revision, title })` — rename a design.',\n '- `view({ revision })` — URL the human can open to view the design.',\n '- `asset_search({ sourceId?, query? })` — browse asset sources; apply results in `edit`.'\n ]\n .concat(\n hasAssetGenerate\n ? [\n '',\n 'Asset generation:',\n \"- `asset_generate({ kind: 'image', prompt, width?, height? })` — opt-in (Local needs API_KEY); Hosted is cost-gated.\"\n ]\n : []\n )\n .join('\\n');\n\n // Build the resource map up-front: URI → rewritten markdown body. Cross-refs\n // inside doc content are rewritten to absolute MCP URIs so the model never\n // has to resolve a relative path against a doc dir.\n const rewrittenContent = new Map<string, string>();\n for (const [key, content] of Object.entries(assets)) {\n const m = key.match(DOC_FILE);\n if (!m) continue;\n const [, doc, rest] = m;\n const uri = `${scheme}://docs/${doc}/${rest}`;\n rewrittenContent.set(uri, rewriteSkillLinks(content, doc, scheme));\n }\n\n const server = new Server(\n { name: 'codesign', version: opts.buildId ?? 'dev' },\n {\n capabilities: {\n resources: {},\n tools: {},\n ...(opts.prompts && opts.prompts.length > 0 ? { prompts: {} } : {})\n },\n instructions\n }\n );\n\n // `resources/list` advertises only the per-skill SKILL.md (the entry\n // point), not the 392 nested cesdk-guide files. Sub-files stay readable\n // via `resources/read` against any cached URI, but skill content discovery\n // happens through the codesign_read_* tools — those are the universal\n // surface across MCP hosts.\n server.setRequestHandler(ListResourcesRequestSchema, async () => ({\n resources: Array.from(rewrittenContent.keys())\n .filter((uri) => uri.endsWith('/SKILL.md'))\n .map((uri) => ({\n uri,\n mimeType: 'text/markdown',\n name: uri.replace(`${scheme}://`, '')\n }))\n }));\n\n server.setRequestHandler(ReadResourceRequestSchema, async (req) => {\n const text = rewrittenContent.get(req.params.uri);\n if (text === undefined) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown resource: ${req.params.uri}`\n );\n }\n return {\n contents: [{ uri: req.params.uri, mimeType: 'text/markdown', text }]\n };\n });\n\n // Tools registered with the server: caller-supplied design tools + the\n // auto-generated skill-read tools (one per skill in the asset bundle).\n // The skill tools always come first so Level 1 wording in the design tool\n // descriptions (\"call codesign_read_codesign_system first\") points at\n // something the model already sees in tools/list.\n const tools = new Map<string, CodesignMcpToolRegistration>();\n for (const t of buildSkillReadTools(assets)) tools.set(t.name, t);\n for (const t of opts.tools ?? []) tools.set(t.name, t);\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: Array.from(tools.values()).map((t) => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema as never\n }))\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const t = tools.get(req.params.name);\n if (!t) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown tool: ${req.params.name}`\n );\n }\n return await t.call(req.params.arguments ?? {});\n });\n\n if (opts.prompts && opts.prompts.length > 0) {\n const prompts = new Map(opts.prompts.map((p) => [p.name, p]));\n server.setRequestHandler(ListPromptsRequestSchema, async () => ({\n prompts: Array.from(prompts.values()).map((p) => ({\n name: p.name,\n ...(p.description ? { description: p.description } : {}),\n ...(p.arguments ? { arguments: p.arguments } : {})\n }))\n }));\n server.setRequestHandler(GetPromptRequestSchema, async (req) => {\n const p = prompts.get(req.params.name);\n if (!p)\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown prompt: ${req.params.name}`\n );\n return p.get(req.params.arguments);\n });\n }\n\n return server;\n}\n"]}
@@ -1,12 +1,83 @@
1
1
  import type CreativeEngine from '@cesdk/engine';
2
+ /**
3
+ * The shape both engine packages expose: `module.exports` is the CreativeEngine
4
+ * class itself, with `.default` aliased to the same class.
5
+ */
6
+ type EngineModule = {
7
+ default?: typeof CreativeEngine;
8
+ };
9
+ /** A CJS-style `require`, injected so the selection logic is unit-testable. */
10
+ type Loader = (pkg: string) => unknown;
11
+ /** Which engine package actually loaded, and via what path. */
12
+ export type EngineFlavor = 'native' | 'wasm' | 'wasm-fallback';
13
+ /**
14
+ * Which engine flavor actually loaded on the first `ensureHeadlessEngine`
15
+ * call — `undefined` until the engine has booted at least once. Surfaced by
16
+ * the `diagnostics` tool (`status.config.engineResolved`) so a user can tell
17
+ * whether they're getting native GPU rasterization or the WASM fallback.
18
+ */
19
+ export declare function resolvedEngineFlavor(): EngineFlavor | undefined;
20
+ /**
21
+ * Choose and load the engine package per `IMGLY_CESDK_ENGINE`, with
22
+ * context-aware fallback:
23
+ *
24
+ * - unset / "" (default) → WASM (TEMPORARY — native is NOT the default because
25
+ * registering a URI resolver breaks all resource
26
+ * loading on @cesdk/node-native, and the MCP server
27
+ * always registers one). Remove `isDefault ||` in the
28
+ * body to restore the native default (CODE-7) once the
29
+ * engine fixes the resolver; the native-load and
30
+ * WASM-fallback paths below stay in place, dormant.
31
+ * - "native" (explicit) → native; if it fails to load, throw — a deliberate
32
+ * request must not silently degrade to slow WASM.
33
+ * - "wasm" (explicit) → WASM; native is never attempted.
34
+ * - any other value → throw (guards typos like "natve").
35
+ *
36
+ * Native ships prebuilt binaries for darwin-arm64/darwin-x64/linux-x64 only.
37
+ *
38
+ * `load` is injected (the real `createRequire(...)` in production, a stub in
39
+ * tests). `warn` defaults to `console.warn`, which is stderr-safe: in stdio mode
40
+ * `redirectConsoleToStderr()` remaps it to stderr so it never corrupts the
41
+ * JSON-RPC channel; in http mode Node's console.warn already writes to stderr.
42
+ */
43
+ export declare function resolveEngineModule(env: string | undefined, load: Loader, warn?: (msg: string) => void): {
44
+ module: EngineModule;
45
+ flavor: EngineFlavor;
46
+ };
47
+ /**
48
+ * Resolve the engine module, then initialize it — adding init-time fallback on
49
+ * top of `resolveEngineModule`'s load-time fallback.
50
+ *
51
+ * The native addon (`@cesdk/node-native`) dlopens its GStreamer/GL dependencies
52
+ * LAZILY, inside `init()` — not at `require()` time. So a native module can
53
+ * resolve successfully (flavor 'native') and still fail to boot, e.g. on a
54
+ * Linux host missing system libraries. `resolveEngineModule` alone can't catch
55
+ * that; this function does, applying the same context-aware policy:
56
+ *
57
+ * - default (env unset/'') + native resolved but init rejects → warn, load
58
+ * and init WASM instead, flavor 'wasm-fallback'. If WASM init ALSO
59
+ * rejects, that rejection propagates as-is (no further fallback).
60
+ * - explicit "native" + init rejects → rejects with a clearer wrapped
61
+ * message + `{ cause }`, mirroring `resolveEngineModule`'s load-time throw.
62
+ * - explicit "wasm", or already-fallback WASM (flavor !== 'native') + init
63
+ * rejects → propagates unchanged.
64
+ *
65
+ * `load` and `initEngine` are injected so this stays unit-testable without a
66
+ * real engine binary; `ensureHeadlessEngine` supplies the real `createRequire`
67
+ * loader and a real `Ctor.init({ license })` call.
68
+ */
69
+ export declare function initEngineModule(env: string | undefined, load: Loader, initEngine: (mod: EngineModule) => Promise<CreativeEngine>, warn?: (msg: string) => void): Promise<{
70
+ engine: CreativeEngine;
71
+ flavor: EngineFlavor;
72
+ }>;
2
73
  /**
3
74
  * Initialize a headless CreativeEngine once and share it across calls.
4
75
  *
5
76
  * Only ever called on node — the browser surface passes `cesdk.engine` directly
6
77
  * to the design tools, so this code path is dead in the browser bundle. To keep
7
- * it that way, both `node:module` and `@cesdk/node` are loaded via async
8
- * dynamic import inside the function — Vite leaves dynamic imports of node
9
- * builtins as runtime errors only if executed, not at module init.
78
+ * it that way, `node:module` is loaded via async dynamic import inside the
79
+ * function — Vite leaves dynamic imports of node builtins as runtime errors only
80
+ * if executed, not at module init.
10
81
  *
11
82
  * Both `@cesdk/node` (WASM) and `@cesdk/node-native` (N-API) are loaded through
12
83
  * `createRequire` (CJS-shaped) rather than ESM dynamic import. `@cesdk/node`'s
@@ -15,17 +86,10 @@ import type CreativeEngine from '@cesdk/engine';
15
86
  * `ReferenceError: Worker is not defined`. The native package is also CJS-shaped
16
87
  * so the same load strategy works for both.
17
88
  *
18
- * Set `IMGLY_CESDK_ENGINE=native` to select the N-API native engine
19
- * (`@cesdk/node-native`). The default is the WASM engine (`@cesdk/node`).
20
- *
21
- * Native vs WASM init API:
22
- * - Both packages export `CreativeEngine` as `module.exports` (the class
23
- * itself) with `module.exports.default` aliased to the same class.
24
- * - Both expose `static init(config?: Partial<Configuration>): Promise<Engine>`
25
- * and accept `{ license }` as config.
26
- * - The native `Configuration` adds an optional `device: 'auto'|'cpu'|'gpu'`
27
- * field (ignored by WASM). We pass only `{ license }` here so both paths
28
- * default to `device: 'auto'` (Metal/EGL → CPU fallback on native).
89
+ * Both packages expose `static init(config?): Promise<Engine>` and accept
90
+ * `{ license }`. The native `Configuration` adds an optional
91
+ * `device: 'auto'|'cpu'|'gpu'` field (ignored by WASM); we pass only `{ license }`
92
+ * so both default to `device: 'auto'` (Metal/EGL → CPU fallback on native).
29
93
  */
30
94
  export declare function ensureHeadlessEngine(license: string): Promise<CreativeEngine>;
31
95
  /**
@@ -41,4 +105,5 @@ export declare function ensureHeadlessEngine(license: string): Promise<CreativeE
41
105
  * it may await an in-flight `init()`.
42
106
  */
43
107
  export declare function disposeHeadlessEngine(): Promise<void>;
108
+ export {};
44
109
  //# sourceMappingURL=engine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/tools/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,cAAc,MAAM,eAAe,CAAC;AAIhD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAmC7E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAa3D"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/tools/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,cAAc,MAAM,eAAe,CAAC;AAIhD;;;GAGG;AACH,KAAK,YAAY,GAAG;IAClB,OAAO,CAAC,EAAE,OAAO,cAAc,CAAC;CACjC,CAAC;AAEF,+EAA+E;AAC/E,KAAK,MAAM,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;AAEvC,+DAA+D;AAC/D,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,eAAe,CAAC;AAO/D;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,YAAY,GAAG,SAAS,CAE/D;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAmB,GACzC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,CAkChD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,cAAc,CAAC,EAC1D,IAAI,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAmB,GACzC,OAAO,CAAC;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC,CA2B3D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAmC7E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAa3D"}
@@ -1,12 +1,118 @@
1
1
  let cached;
2
+ const NATIVE_PKG = '@cesdk/node-native';
3
+ const WASM_PKG = '@cesdk/node';
4
+ let resolvedFlavor;
5
+ /**
6
+ * Which engine flavor actually loaded on the first `ensureHeadlessEngine`
7
+ * call — `undefined` until the engine has booted at least once. Surfaced by
8
+ * the `diagnostics` tool (`status.config.engineResolved`) so a user can tell
9
+ * whether they're getting native GPU rasterization or the WASM fallback.
10
+ */
11
+ export function resolvedEngineFlavor() {
12
+ return resolvedFlavor;
13
+ }
14
+ /**
15
+ * Choose and load the engine package per `IMGLY_CESDK_ENGINE`, with
16
+ * context-aware fallback:
17
+ *
18
+ * - unset / "" (default) → WASM (TEMPORARY — native is NOT the default because
19
+ * registering a URI resolver breaks all resource
20
+ * loading on @cesdk/node-native, and the MCP server
21
+ * always registers one). Remove `isDefault ||` in the
22
+ * body to restore the native default (CODE-7) once the
23
+ * engine fixes the resolver; the native-load and
24
+ * WASM-fallback paths below stay in place, dormant.
25
+ * - "native" (explicit) → native; if it fails to load, throw — a deliberate
26
+ * request must not silently degrade to slow WASM.
27
+ * - "wasm" (explicit) → WASM; native is never attempted.
28
+ * - any other value → throw (guards typos like "natve").
29
+ *
30
+ * Native ships prebuilt binaries for darwin-arm64/darwin-x64/linux-x64 only.
31
+ *
32
+ * `load` is injected (the real `createRequire(...)` in production, a stub in
33
+ * tests). `warn` defaults to `console.warn`, which is stderr-safe: in stdio mode
34
+ * `redirectConsoleToStderr()` remaps it to stderr so it never corrupts the
35
+ * JSON-RPC channel; in http mode Node's console.warn already writes to stderr.
36
+ */
37
+ export function resolveEngineModule(env, load, warn = console.warn) {
38
+ const isDefault = env === undefined || env === '';
39
+ if (!isDefault && env !== 'native' && env !== 'wasm') {
40
+ throw new Error(`Invalid IMGLY_CESDK_ENGINE "${env}": expected "native" or "wasm"`);
41
+ }
42
+ // Default (unset/empty) or explicit "wasm": load WASM. The `isDefault ||` is a
43
+ // temporary revert of the native default — native breaks all resource loading
44
+ // under a URI resolver (see the doc comment). Remove it to make native the
45
+ // default again (CODE-7) once the engine fixes the resolver.
46
+ if (isDefault || env === 'wasm') {
47
+ return { module: load(WASM_PKG), flavor: 'wasm' };
48
+ }
49
+ // Explicit "native" only (the default no longer reaches here): try native.
50
+ const explicitNative = env === 'native';
51
+ try {
52
+ return { module: load(NATIVE_PKG), flavor: 'native' };
53
+ }
54
+ catch (err) {
55
+ const detail = err instanceof Error ? err.message : String(err);
56
+ if (explicitNative) {
57
+ throw new Error(`IMGLY_CESDK_ENGINE=native but the native engine failed to load: ${detail}`, { cause: err });
58
+ }
59
+ warn(`native engine (${NATIVE_PKG}) unavailable on this platform; ` +
60
+ `falling back to WASM (${WASM_PKG}): ${detail}`);
61
+ return { module: load(WASM_PKG), flavor: 'wasm-fallback' };
62
+ }
63
+ }
64
+ /**
65
+ * Resolve the engine module, then initialize it — adding init-time fallback on
66
+ * top of `resolveEngineModule`'s load-time fallback.
67
+ *
68
+ * The native addon (`@cesdk/node-native`) dlopens its GStreamer/GL dependencies
69
+ * LAZILY, inside `init()` — not at `require()` time. So a native module can
70
+ * resolve successfully (flavor 'native') and still fail to boot, e.g. on a
71
+ * Linux host missing system libraries. `resolveEngineModule` alone can't catch
72
+ * that; this function does, applying the same context-aware policy:
73
+ *
74
+ * - default (env unset/'') + native resolved but init rejects → warn, load
75
+ * and init WASM instead, flavor 'wasm-fallback'. If WASM init ALSO
76
+ * rejects, that rejection propagates as-is (no further fallback).
77
+ * - explicit "native" + init rejects → rejects with a clearer wrapped
78
+ * message + `{ cause }`, mirroring `resolveEngineModule`'s load-time throw.
79
+ * - explicit "wasm", or already-fallback WASM (flavor !== 'native') + init
80
+ * rejects → propagates unchanged.
81
+ *
82
+ * `load` and `initEngine` are injected so this stays unit-testable without a
83
+ * real engine binary; `ensureHeadlessEngine` supplies the real `createRequire`
84
+ * loader and a real `Ctor.init({ license })` call.
85
+ */
86
+ export async function initEngineModule(env, load, initEngine, warn = console.warn) {
87
+ const { module: mod, flavor } = resolveEngineModule(env, load, warn);
88
+ try {
89
+ return { engine: await initEngine(mod), flavor };
90
+ }
91
+ catch (err) {
92
+ const detail = err instanceof Error ? err.message : String(err);
93
+ const isDefault = env === undefined || env === '';
94
+ if (flavor === 'native' && !isDefault) {
95
+ throw new Error(`IMGLY_CESDK_ENGINE=native but the native engine failed to initialize: ${detail}`, { cause: err });
96
+ }
97
+ if (flavor !== 'native') {
98
+ throw err; // explicit wasm, or already-fallback wasm: no further fallback
99
+ }
100
+ // Default mode, native resolved but failed to INIT: fall back to WASM,
101
+ // same breadcrumb-and-degrade policy as a native load-time failure.
102
+ warn(`native engine loaded but failed to initialize; falling back to WASM (${WASM_PKG}): ${detail}` +
103
+ ` — if this is Linux, the native engine needs system libraries (see @cesdk/node-native README, 'Linux runtime dependencies')`);
104
+ const wasmMod = load(WASM_PKG);
105
+ return { engine: await initEngine(wasmMod), flavor: 'wasm-fallback' };
106
+ }
107
+ }
2
108
  /**
3
109
  * Initialize a headless CreativeEngine once and share it across calls.
4
110
  *
5
111
  * Only ever called on node — the browser surface passes `cesdk.engine` directly
6
112
  * to the design tools, so this code path is dead in the browser bundle. To keep
7
- * it that way, both `node:module` and `@cesdk/node` are loaded via async
8
- * dynamic import inside the function — Vite leaves dynamic imports of node
9
- * builtins as runtime errors only if executed, not at module init.
113
+ * it that way, `node:module` is loaded via async dynamic import inside the
114
+ * function — Vite leaves dynamic imports of node builtins as runtime errors only
115
+ * if executed, not at module init.
10
116
  *
11
117
  * Both `@cesdk/node` (WASM) and `@cesdk/node-native` (N-API) are loaded through
12
118
  * `createRequire` (CJS-shaped) rather than ESM dynamic import. `@cesdk/node`'s
@@ -15,29 +121,22 @@ let cached;
15
121
  * `ReferenceError: Worker is not defined`. The native package is also CJS-shaped
16
122
  * so the same load strategy works for both.
17
123
  *
18
- * Set `IMGLY_CESDK_ENGINE=native` to select the N-API native engine
19
- * (`@cesdk/node-native`). The default is the WASM engine (`@cesdk/node`).
20
- *
21
- * Native vs WASM init API:
22
- * - Both packages export `CreativeEngine` as `module.exports` (the class
23
- * itself) with `module.exports.default` aliased to the same class.
24
- * - Both expose `static init(config?: Partial<Configuration>): Promise<Engine>`
25
- * and accept `{ license }` as config.
26
- * - The native `Configuration` adds an optional `device: 'auto'|'cpu'|'gpu'`
27
- * field (ignored by WASM). We pass only `{ license }` here so both paths
28
- * default to `device: 'auto'` (Metal/EGL → CPU fallback on native).
124
+ * Both packages expose `static init(config?): Promise<Engine>` and accept
125
+ * `{ license }`. The native `Configuration` adds an optional
126
+ * `device: 'auto'|'cpu'|'gpu'` field (ignored by WASM); we pass only `{ license }`
127
+ * so both default to `device: 'auto'` (Metal/EGL → CPU fallback on native).
29
128
  */
30
129
  export function ensureHeadlessEngine(license) {
31
130
  if (!cached) {
32
131
  cached = (async () => {
33
132
  const { createRequire } = await import('node:module');
34
133
  const require = createRequire(import.meta.url);
35
- const pkg = process.env.IMGLY_CESDK_ENGINE === 'native'
36
- ? '@cesdk/node-native'
37
- : '@cesdk/node';
38
- const mod = require(pkg);
39
- const Ctor = mod.default ?? mod;
40
- const engine = await Ctor.init({ license });
134
+ const initEngine = (mod) => {
135
+ const Ctor = mod.default ?? mod;
136
+ return Ctor.init({ license });
137
+ };
138
+ const { engine, flavor } = await initEngineModule(process.env.IMGLY_CESDK_ENGINE, require, initEngine);
139
+ resolvedFlavor = flavor;
41
140
  // CE.SDK's default design unit is "Inch", which makes setTextFontSize
42
141
  // + setWidth/Height with numeric pixel-looking values render at ~2.5×
43
142
  // the agent's expected size. Patch scene.create so every new scene
@@ -1 +1 @@
1
- {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/tools/engine.ts"],"names":[],"mappings":"AAEA,IAAI,MAA2C,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;YACnB,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;YACtD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC/C,MAAM,GAAG,GACP,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,QAAQ;gBACzC,CAAC,CAAC,oBAAoB;gBACtB,CAAC,CAAC,aAAa,CAAC;YACpB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAGtB,CAAC;YACF,MAAM,IAAI,GACR,GAAG,CAAC,OAAO,IAAK,GAAwC,CAAC;YAC3D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;YAC5C,sEAAsE;YACtE,sEAAsE;YACtE,mEAAmE;YACnE,qDAAqD;YACrD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACzD,MAAM,CAAC,KAAyD,CAAC,MAAM;gBACtE,CAAC,CAAC,MAAgB,EAAE,EAAE;oBACpB,MAAM,OAAO,GAAG,UAAU,CACxB,MAA0C,CAC3C,CAAC;oBACF,gEAAgE;oBAChE,8DAA8D;oBAC9D,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;oBACpC,OAAO,OAAO,CAAC;gBACjB,CAAC,CAA+B,CAAC;YACnC,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC,EAAE,CAAC;IACP,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,MAAM,OAAO,GAAG,MAAM,CAAC;IACvB,IAAI,CAAC,OAAO;QAAE,OAAO;IACrB,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,GAAG,SAAS,CAAC;IACnB,IAAI,MAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,OAAO,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,qDAAqD;IAC/D,CAAC;IACD,MAAM,CAAC,OAAO,EAAE,CAAC;AACnB,CAAC","sourcesContent":["import type CreativeEngine from '@cesdk/engine';\n\nlet cached: Promise<CreativeEngine> | undefined;\n\n/**\n * Initialize a headless CreativeEngine once and share it across calls.\n *\n * Only ever called on node — the browser surface passes `cesdk.engine` directly\n * to the design tools, so this code path is dead in the browser bundle. To keep\n * it that way, both `node:module` and `@cesdk/node` are loaded via async\n * dynamic import inside the function — Vite leaves dynamic imports of node\n * builtins as runtime errors only if executed, not at module init.\n *\n * Both `@cesdk/node` (WASM) and `@cesdk/node-native` (N-API) are loaded through\n * `createRequire` (CJS-shaped) rather than ESM dynamic import. `@cesdk/node`'s\n * emscripten/wasm runtime spawns Workers that resolve relative to the package's\n * `__dirname`; ESM dynamic import + vitest pre-bundling break that path with\n * `ReferenceError: Worker is not defined`. The native package is also CJS-shaped\n * so the same load strategy works for both.\n *\n * Set `IMGLY_CESDK_ENGINE=native` to select the N-API native engine\n * (`@cesdk/node-native`). The default is the WASM engine (`@cesdk/node`).\n *\n * Native vs WASM init API:\n * - Both packages export `CreativeEngine` as `module.exports` (the class\n * itself) with `module.exports.default` aliased to the same class.\n * - Both expose `static init(config?: Partial<Configuration>): Promise<Engine>`\n * and accept `{ license }` as config.\n * - The native `Configuration` adds an optional `device: 'auto'|'cpu'|'gpu'`\n * field (ignored by WASM). We pass only `{ license }` here so both paths\n * default to `device: 'auto'` (Metal/EGL → CPU fallback on native).\n */\nexport function ensureHeadlessEngine(license: string): Promise<CreativeEngine> {\n if (!cached) {\n cached = (async () => {\n const { createRequire } = await import('node:module');\n const require = createRequire(import.meta.url);\n const pkg =\n process.env.IMGLY_CESDK_ENGINE === 'native'\n ? '@cesdk/node-native'\n : '@cesdk/node';\n const mod = require(pkg) as {\n default?: typeof CreativeEngine;\n init?: typeof CreativeEngine.init;\n };\n const Ctor: typeof CreativeEngine =\n mod.default ?? (mod as unknown as typeof CreativeEngine);\n const engine = await Ctor.init({ license });\n // CE.SDK's default design unit is \"Inch\", which makes setTextFontSize\n // + setWidth/Height with numeric pixel-looking values render at ~2.5×\n // the agent's expected size. Patch scene.create so every new scene\n // starts in Pixel — the agent never has to remember.\n const origCreate = engine.scene.create.bind(engine.scene);\n (engine.scene as unknown as { create(layout?: unknown): number }).create =\n ((layout?: unknown) => {\n const sceneId = origCreate(\n layout as Parameters<typeof origCreate>[0]\n );\n // @cesdk/node setDesignUnit takes only the unit; it operates on\n // the current scene, which is the one create() just returned.\n engine.scene.setDesignUnit('Pixel');\n return sceneId;\n }) as typeof engine.scene.create;\n return engine;\n })();\n }\n return cached;\n}\n\n/**\n * Dispose the process-wide headless engine if one was created, and reset the\n * cache so a subsequent `ensureHeadlessEngine` re-initializes from scratch.\n *\n * Idempotent and safe to call when no engine was ever initialized (a no-op that\n * never imports `@cesdk/node`) or while an init is still in flight — it awaits\n * the pending init first so we dispose a fully-constructed engine, never a\n * half-built one. A rejected init produced no engine, so that case is swallowed.\n *\n * `CreativeEngine.dispose()` is synchronous; this wrapper is async only because\n * it may await an in-flight `init()`.\n */\nexport async function disposeHeadlessEngine(): Promise<void> {\n const pending = cached;\n if (!pending) return;\n // Clear the cache first so no concurrent caller receives the engine we are\n // about to dispose; a later `ensureHeadlessEngine` builds a fresh one.\n cached = undefined;\n let engine: CreativeEngine;\n try {\n engine = await pending;\n } catch {\n return; // init never produced an engine — nothing to dispose\n }\n engine.dispose();\n}\n"]}
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/tools/engine.ts"],"names":[],"mappings":"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","sourcesContent":["import type CreativeEngine from '@cesdk/engine';\n\nlet cached: Promise<CreativeEngine> | undefined;\n\n/**\n * The shape both engine packages expose: `module.exports` is the CreativeEngine\n * class itself, with `.default` aliased to the same class.\n */\ntype EngineModule = {\n default?: typeof CreativeEngine;\n};\n\n/** A CJS-style `require`, injected so the selection logic is unit-testable. */\ntype Loader = (pkg: string) => unknown;\n\n/** Which engine package actually loaded, and via what path. */\nexport type EngineFlavor = 'native' | 'wasm' | 'wasm-fallback';\n\nconst NATIVE_PKG = '@cesdk/node-native';\nconst WASM_PKG = '@cesdk/node';\n\nlet resolvedFlavor: EngineFlavor | undefined;\n\n/**\n * Which engine flavor actually loaded on the first `ensureHeadlessEngine`\n * call — `undefined` until the engine has booted at least once. Surfaced by\n * the `diagnostics` tool (`status.config.engineResolved`) so a user can tell\n * whether they're getting native GPU rasterization or the WASM fallback.\n */\nexport function resolvedEngineFlavor(): EngineFlavor | undefined {\n return resolvedFlavor;\n}\n\n/**\n * Choose and load the engine package per `IMGLY_CESDK_ENGINE`, with\n * context-aware fallback:\n *\n * - unset / \"\" (default) → WASM (TEMPORARY — native is NOT the default because\n * registering a URI resolver breaks all resource\n * loading on @cesdk/node-native, and the MCP server\n * always registers one). Remove `isDefault ||` in the\n * body to restore the native default (CODE-7) once the\n * engine fixes the resolver; the native-load and\n * WASM-fallback paths below stay in place, dormant.\n * - \"native\" (explicit) → native; if it fails to load, throw — a deliberate\n * request must not silently degrade to slow WASM.\n * - \"wasm\" (explicit) → WASM; native is never attempted.\n * - any other value → throw (guards typos like \"natve\").\n *\n * Native ships prebuilt binaries for darwin-arm64/darwin-x64/linux-x64 only.\n *\n * `load` is injected (the real `createRequire(...)` in production, a stub in\n * tests). `warn` defaults to `console.warn`, which is stderr-safe: in stdio mode\n * `redirectConsoleToStderr()` remaps it to stderr so it never corrupts the\n * JSON-RPC channel; in http mode Node's console.warn already writes to stderr.\n */\nexport function resolveEngineModule(\n env: string | undefined,\n load: Loader,\n warn: (msg: string) => void = console.warn\n): { module: EngineModule; flavor: EngineFlavor } {\n const isDefault = env === undefined || env === '';\n if (!isDefault && env !== 'native' && env !== 'wasm') {\n throw new Error(\n `Invalid IMGLY_CESDK_ENGINE \"${env}\": expected \"native\" or \"wasm\"`\n );\n }\n\n // Default (unset/empty) or explicit \"wasm\": load WASM. The `isDefault ||` is a\n // temporary revert of the native default — native breaks all resource loading\n // under a URI resolver (see the doc comment). Remove it to make native the\n // default again (CODE-7) once the engine fixes the resolver.\n if (isDefault || env === 'wasm') {\n return { module: load(WASM_PKG) as EngineModule, flavor: 'wasm' };\n }\n\n // Explicit \"native\" only (the default no longer reaches here): try native.\n const explicitNative = env === 'native';\n try {\n return { module: load(NATIVE_PKG) as EngineModule, flavor: 'native' };\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n if (explicitNative) {\n throw new Error(\n `IMGLY_CESDK_ENGINE=native but the native engine failed to load: ${detail}`,\n { cause: err }\n );\n }\n warn(\n `native engine (${NATIVE_PKG}) unavailable on this platform; ` +\n `falling back to WASM (${WASM_PKG}): ${detail}`\n );\n return { module: load(WASM_PKG) as EngineModule, flavor: 'wasm-fallback' };\n }\n}\n\n/**\n * Resolve the engine module, then initialize it — adding init-time fallback on\n * top of `resolveEngineModule`'s load-time fallback.\n *\n * The native addon (`@cesdk/node-native`) dlopens its GStreamer/GL dependencies\n * LAZILY, inside `init()` — not at `require()` time. So a native module can\n * resolve successfully (flavor 'native') and still fail to boot, e.g. on a\n * Linux host missing system libraries. `resolveEngineModule` alone can't catch\n * that; this function does, applying the same context-aware policy:\n *\n * - default (env unset/'') + native resolved but init rejects → warn, load\n * and init WASM instead, flavor 'wasm-fallback'. If WASM init ALSO\n * rejects, that rejection propagates as-is (no further fallback).\n * - explicit \"native\" + init rejects → rejects with a clearer wrapped\n * message + `{ cause }`, mirroring `resolveEngineModule`'s load-time throw.\n * - explicit \"wasm\", or already-fallback WASM (flavor !== 'native') + init\n * rejects → propagates unchanged.\n *\n * `load` and `initEngine` are injected so this stays unit-testable without a\n * real engine binary; `ensureHeadlessEngine` supplies the real `createRequire`\n * loader and a real `Ctor.init({ license })` call.\n */\nexport async function initEngineModule(\n env: string | undefined,\n load: Loader,\n initEngine: (mod: EngineModule) => Promise<CreativeEngine>,\n warn: (msg: string) => void = console.warn\n): Promise<{ engine: CreativeEngine; flavor: EngineFlavor }> {\n const { module: mod, flavor } = resolveEngineModule(env, load, warn);\n try {\n return { engine: await initEngine(mod), flavor };\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n const isDefault = env === undefined || env === '';\n\n if (flavor === 'native' && !isDefault) {\n throw new Error(\n `IMGLY_CESDK_ENGINE=native but the native engine failed to initialize: ${detail}`,\n { cause: err }\n );\n }\n if (flavor !== 'native') {\n throw err; // explicit wasm, or already-fallback wasm: no further fallback\n }\n\n // Default mode, native resolved but failed to INIT: fall back to WASM,\n // same breadcrumb-and-degrade policy as a native load-time failure.\n warn(\n `native engine loaded but failed to initialize; falling back to WASM (${WASM_PKG}): ${detail}` +\n ` — if this is Linux, the native engine needs system libraries (see @cesdk/node-native README, 'Linux runtime dependencies')`\n );\n const wasmMod = load(WASM_PKG) as EngineModule;\n return { engine: await initEngine(wasmMod), flavor: 'wasm-fallback' };\n }\n}\n\n/**\n * Initialize a headless CreativeEngine once and share it across calls.\n *\n * Only ever called on node — the browser surface passes `cesdk.engine` directly\n * to the design tools, so this code path is dead in the browser bundle. To keep\n * it that way, `node:module` is loaded via async dynamic import inside the\n * function — Vite leaves dynamic imports of node builtins as runtime errors only\n * if executed, not at module init.\n *\n * Both `@cesdk/node` (WASM) and `@cesdk/node-native` (N-API) are loaded through\n * `createRequire` (CJS-shaped) rather than ESM dynamic import. `@cesdk/node`'s\n * emscripten/wasm runtime spawns Workers that resolve relative to the package's\n * `__dirname`; ESM dynamic import + vitest pre-bundling break that path with\n * `ReferenceError: Worker is not defined`. The native package is also CJS-shaped\n * so the same load strategy works for both.\n *\n * Both packages expose `static init(config?): Promise<Engine>` and accept\n * `{ license }`. The native `Configuration` adds an optional\n * `device: 'auto'|'cpu'|'gpu'` field (ignored by WASM); we pass only `{ license }`\n * so both default to `device: 'auto'` (Metal/EGL → CPU fallback on native).\n */\nexport function ensureHeadlessEngine(license: string): Promise<CreativeEngine> {\n if (!cached) {\n cached = (async () => {\n const { createRequire } = await import('node:module');\n const require = createRequire(import.meta.url);\n const initEngine = (mod: EngineModule): Promise<CreativeEngine> => {\n const Ctor: typeof CreativeEngine =\n mod.default ?? (mod as unknown as typeof CreativeEngine);\n return Ctor.init({ license });\n };\n const { engine, flavor } = await initEngineModule(\n process.env.IMGLY_CESDK_ENGINE,\n require,\n initEngine\n );\n resolvedFlavor = flavor;\n // CE.SDK's default design unit is \"Inch\", which makes setTextFontSize\n // + setWidth/Height with numeric pixel-looking values render at ~2.5×\n // the agent's expected size. Patch scene.create so every new scene\n // starts in Pixel — the agent never has to remember.\n const origCreate = engine.scene.create.bind(engine.scene);\n (engine.scene as unknown as { create(layout?: unknown): number }).create =\n ((layout?: unknown) => {\n const sceneId = origCreate(\n layout as Parameters<typeof origCreate>[0]\n );\n // @cesdk/node setDesignUnit takes only the unit; it operates on\n // the current scene, which is the one create() just returned.\n engine.scene.setDesignUnit('Pixel');\n return sceneId;\n }) as typeof engine.scene.create;\n return engine;\n })();\n }\n return cached;\n}\n\n/**\n * Dispose the process-wide headless engine if one was created, and reset the\n * cache so a subsequent `ensureHeadlessEngine` re-initializes from scratch.\n *\n * Idempotent and safe to call when no engine was ever initialized (a no-op that\n * never imports `@cesdk/node`) or while an init is still in flight — it awaits\n * the pending init first so we dispose a fully-constructed engine, never a\n * half-built one. A rejected init produced no engine, so that case is swallowed.\n *\n * `CreativeEngine.dispose()` is synchronous; this wrapper is async only because\n * it may await an in-flight `init()`.\n */\nexport async function disposeHeadlessEngine(): Promise<void> {\n const pending = cached;\n if (!pending) return;\n // Clear the cache first so no concurrent caller receives the engine we are\n // about to dispose; a later `ensureHeadlessEngine` builds a fresh one.\n cached = undefined;\n let engine: CreativeEngine;\n try {\n engine = await pending;\n } catch {\n return; // init never produced an engine — nothing to dispose\n }\n engine.dispose();\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/tools/export.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAoEpB,MAAM,WAAW,uBAAuB,CAAC,CAAC,GAAG,OAAO;IAClD,EAAE,EAAE,WAAW,CAAC;IAChB,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC1B,8EAA8E;IAC9E,UAAU,EAAE,gBAAgB,CAAC;IAC7B,gFAAgF;IAChF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yHAAyH;IACzH,UAAU,EAAE,KAAK,CAAC;IAClB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAC1C,IAAI,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAC/B,2BAA2B,CAyK7B"}
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/tools/export.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAoEpB,MAAM,WAAW,uBAAuB,CAAC,CAAC,GAAG,OAAO;IAClD,EAAE,EAAE,WAAW,CAAC;IAChB,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC1B,8EAA8E;IAC9E,UAAU,EAAE,gBAAgB,CAAC;IAC7B,gFAAgF;IAChF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yHAAyH;IACzH,UAAU,EAAE,KAAK,CAAC;IAClB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAC1C,IAAI,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAC/B,2BAA2B,CA0M7B"}
@@ -70,6 +70,7 @@ export function createExportTool(opts) {
70
70
  let result;
71
71
  let threw;
72
72
  let revision;
73
+ let exportError;
73
74
  try {
74
75
  result = await (async () => {
75
76
  const args = (rawArgs ?? {});
@@ -141,16 +142,41 @@ export function createExportTool(opts) {
141
142
  // load-into-engine then export reads the shared engine across awaits —
142
143
  // serialize against any concurrent engine-touching tool call.
143
144
  const beforeLock = performance.now();
144
- const bytes = await engineLock.runExclusive(async () => {
145
+ const rendered = await engineLock.runExclusive(async () => {
145
146
  rec.add('lock_wait', performance.now() - beforeLock);
146
147
  const slot = await rec.time('engine_scene_load', async () => {
147
148
  await session.populate(revId, sceneBytes);
148
149
  return session.acquire(revId);
149
150
  });
150
151
  const eng = slot.engine;
151
- const blob = await rec.time('engine_execute', () => eng.block.export(blockId, mime, {}));
152
- return new Uint8Array(await blob.arrayBuffer());
152
+ // We loaded `revId` under the lock just above, so the correct scene
153
+ // is current. If the render throws, `blockId` genuinely isn't in
154
+ // this revision's scene (a wrong or removed id) — return a legible
155
+ // tool error the model can act on (mirroring `edit`'s dirty-on-throw
156
+ // path) instead of rethrowing into an opaque MCP -32603.
157
+ try {
158
+ const blob = await rec.time('engine_execute', () => eng.block.export(blockId, mime, {}));
159
+ return { bytes: new Uint8Array(await blob.arrayBuffer()) };
160
+ }
161
+ catch (err) {
162
+ return { error: err };
163
+ }
153
164
  });
165
+ if ('error' in rendered) {
166
+ exportError = rendered.error;
167
+ return {
168
+ isError: true,
169
+ content: [
170
+ {
171
+ type: 'text',
172
+ text: `export: could not render block ${blockId} in revision ${revId}. That block id isn't in this revision's scene — double-check the id (use \`inspect\` to see the revision's structure), or it may have been removed in this revision. ${rendered.error instanceof Error
173
+ ? (rendered.error.stack ?? rendered.error.message)
174
+ : String(rendered.error)}`
175
+ }
176
+ ]
177
+ };
178
+ }
179
+ const bytes = rendered.bytes;
154
180
  const shard = args.revision.slice(0, 2);
155
181
  const rest = args.revision.slice(2);
156
182
  const ext = FORMAT_EXT[args.format];
@@ -208,7 +234,11 @@ export function createExportTool(opts) {
208
234
  args: rawArgs,
209
235
  ...(result !== undefined ? { result: summarizeResult(result) } : {}),
210
236
  ...(revision ? { revision } : {}),
211
- ...(threw ? { error: toErrorShape(threw) } : {})
237
+ ...(threw
238
+ ? { error: toErrorShape(threw) }
239
+ : exportError
240
+ ? { error: toErrorShape(exportError) }
241
+ : {})
212
242
  });
213
243
  }
214
244
  }