@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.
- package/assets/docs/api/index.d.ts +9 -3
- package/assets/docs/guide/SKILL.md +2 -2
- package/assets/docs/guide/api/node/classes/creativeengine.md +13 -13
- package/assets/docs/guide/edit-image/transform/scale.md +1 -3
- package/assets/docs/guide/filters-and-effects/duotone.md +1 -1
- package/assets/docs/guide/get-started/bun.md +0 -2
- package/assets/docs/guide/get-started/deno.md +0 -1
- package/assets/docs/guide/get-started/vanilla.md +0 -2
- package/assets/docs/guide/import-media/default-assets.md +20 -24
- package/assets/docs/guide/plugins/print-ready-pdf.md +0 -2
- package/assets/docs/guide/serve-assets.md +80 -23
- package/assets/docs/handbook/SKILL.md +6 -7
- package/dist/assets/inline.generated.js +12 -12
- package/dist/assets/inline.generated.js.map +1 -1
- package/dist/cli/http.d.ts.map +1 -1
- package/dist/cli/http.js +17 -3
- package/dist/cli/http.js.map +1 -1
- package/dist/cli/stdio.d.ts.map +1 -1
- package/dist/cli/stdio.js +44 -15
- package/dist/cli/stdio.js.map +1 -1
- package/dist/cli/trial-license.generated.d.ts +1 -1
- package/dist/cli/trial-license.generated.js +1 -1
- package/dist/cli/trial-license.generated.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/diagnostics/bundle.d.ts +3 -0
- package/dist/diagnostics/bundle.d.ts.map +1 -1
- package/dist/diagnostics/bundle.js +25 -22
- package/dist/diagnostics/bundle.js.map +1 -1
- package/dist/diagnostics/prompt.d.ts +3 -0
- package/dist/diagnostics/prompt.d.ts.map +1 -0
- package/dist/diagnostics/prompt.js +21 -0
- package/dist/diagnostics/prompt.js.map +1 -0
- package/dist/diagnostics/router.d.ts +12 -0
- package/dist/diagnostics/router.d.ts.map +1 -0
- package/dist/diagnostics/router.js +64 -0
- package/dist/diagnostics/router.js.map +1 -0
- package/dist/diagnostics/spa-assets.generated.d.ts +2 -0
- package/dist/diagnostics/spa-assets.generated.d.ts.map +1 -0
- package/dist/diagnostics/spa-assets.generated.js +1231 -0
- package/dist/diagnostics/spa-assets.generated.js.map +1 -0
- package/dist/diagnostics/status.d.ts +38 -0
- package/dist/diagnostics/status.d.ts.map +1 -0
- package/dist/diagnostics/status.js +53 -0
- package/dist/diagnostics/status.js.map +1 -0
- package/dist/diagnostics/summary.d.ts +48 -0
- package/dist/diagnostics/summary.d.ts.map +1 -0
- package/dist/diagnostics/summary.js +116 -0
- package/dist/diagnostics/summary.js.map +1 -0
- package/dist/diagnostics/tool.d.ts +16 -0
- package/dist/diagnostics/tool.d.ts.map +1 -0
- package/dist/diagnostics/tool.js +90 -0
- package/dist/diagnostics/tool.js.map +1 -0
- package/dist/engine/asset-sources.d.ts +8 -0
- package/dist/engine/asset-sources.d.ts.map +1 -1
- package/dist/engine/asset-sources.js +9 -2
- package/dist/engine/asset-sources.js.map +1 -1
- package/dist/engine/uri-resolver.js.map +1 -1
- package/dist/server.d.ts +25 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +24 -4
- package/dist/server.js.map +1 -1
- package/dist/tools/engine.d.ts +79 -14
- package/dist/tools/engine.d.ts.map +1 -1
- package/dist/tools/engine.js +119 -20
- package/dist/tools/engine.js.map +1 -1
- package/dist/tools/export.d.ts.map +1 -1
- package/dist/tools/export.js +34 -4
- package/dist/tools/export.js.map +1 -1
- package/dist/tools/index.js +2 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/load.d.ts.map +1 -1
- package/dist/tools/load.js +26 -19
- package/dist/tools/load.js.map +1 -1
- package/dist/tools/preview.d.ts.map +1 -1
- package/dist/tools/preview.js +60 -40
- package/dist/tools/preview.js.map +1 -1
- package/dist/tools/{open-editor.d.ts → view.d.ts} +3 -3
- package/dist/tools/view.d.ts.map +1 -0
- package/dist/tools/{open-editor.js → view.js} +20 -18
- package/dist/tools/view.js.map +1 -0
- package/dist/viewer/listener.d.ts +2 -0
- package/dist/viewer/listener.d.ts.map +1 -1
- package/dist/viewer/listener.js +2 -0
- package/dist/viewer/listener.js.map +1 -1
- package/dist/viewer/presigned-r2-url-builder.d.ts +1 -1
- package/dist/viewer/presigned-r2-url-builder.js +1 -1
- package/dist/viewer/presigned-r2-url-builder.js.map +1 -1
- package/dist/viewer/router.d.ts.map +1 -1
- package/dist/viewer/router.js +8 -17
- package/dist/viewer/router.js.map +1 -1
- package/dist/viewer/session-mount.js.map +1 -1
- package/dist/viewer/spa-assets.generated.d.ts.map +1 -1
- package/dist/viewer/spa-assets.generated.js +750 -869
- package/dist/viewer/spa-assets.generated.js.map +1 -1
- package/dist/viewer/url-builder.d.ts +4 -3
- package/dist/viewer/url-builder.d.ts.map +1 -1
- package/dist/viewer/url-builder.js +4 -4
- package/dist/viewer/url-builder.js.map +1 -1
- package/package.json +6 -5
- package/dist/tools/open-editor.d.ts.map +0 -1
- package/dist/tools/open-editor.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/tools/preview.ts"],"names":[],"mappings":"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","sourcesContent":["// packages/codesign-mcp/src/tools/preview.ts\n//\n// `preview` — renders a block to an inline image. Adds an optional `revision`\n// (spec §6.1): when supplied and the engine slot is at a different revision,\n// the session loads that revision before exporting. Without `revision`, renders\n// the engine's current state (uses `session.currentSlot()` — side-effect-free).\n\nimport { performance } from 'node:perf_hooks';\nimport type { Mutex } from '../engine/mutex.js';\nimport type { EngineSession, EngineSlot } from '../engine/session.js';\nimport type { RevisionStore } from '../revisions/store.js';\nimport type { CodesignMcpToolRegistration } from '../server.js';\nimport {\n SpanRecorder,\n NOOP_TELEMETRY_SINK,\n summarizeResult,\n toErrorShape,\n type TelemetrySink\n} from './spans.js';\n\nconst MAX_DIMENSION_PX = 1800;\nconst DEFAULT_TARGET_WIDTH = 512;\n\ninterface MinimalEngine {\n block: {\n export(blockId: number, mimeType: string, options?: unknown): Promise<Blob>;\n };\n}\n\ninterface PreviewArgs {\n blockId: number;\n revision?: string;\n format?: 'webp' | 'png' | 'jpeg';\n width?: number;\n height?: number;\n}\n\nconst DESCRIPTION = [\n 'Render a block (almost always a page) to an inline image so you can see the result.',\n '',\n \"`blockId` is REQUIRED — preview the page you're working on, not the scene root.\",\n '',\n \"Optional `revision`: if supplied, loads that revision into the engine before rendering. Without it, renders the engine's current state (almost always what you want directly after `edit`).\",\n '',\n \"Defaults: format='webp', width=512. Width/height clamped at 1800 px (Anthropic many-image limit).\"\n].join('\\n');\n\nconst PREVIEW_INPUT_SCHEMA = {\n type: 'object' as const,\n properties: {\n blockId: { type: 'integer' as const },\n revision: {\n type: 'string' as const,\n description: \"Revision id; defaults to the engine's current state.\"\n },\n format: { enum: ['webp', 'png', 'jpeg'] },\n width: { type: 'integer' as const, minimum: 1 },\n height: { type: 'integer' as const, minimum: 1 }\n },\n required: ['blockId'],\n additionalProperties: false\n};\n\nexport interface CreatePreviewToolOptions<E = unknown> {\n store: RevisionStore;\n session: EngineSession<E>;\n /** Guards the shared engine — load-then-export must not interleave with another session's edit (see engine/mutex.ts). */\n engineLock: Mutex;\n /** Diagnostics telemetry port. Absent → no-op (no timing overhead beyond a few clocks). */\n telemetry?: TelemetrySink;\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n if (typeof Buffer !== 'undefined')\n return Buffer.from(bytes).toString('base64');\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);\n return btoa(s);\n}\n\nexport function createPreviewTool<E = unknown>(\n opts: CreatePreviewToolOptions<E>\n): CodesignMcpToolRegistration {\n const { store, session, engineLock } = opts;\n const telemetry = opts.telemetry ?? NOOP_TELEMETRY_SINK;\n return {\n name: 'preview',\n description: DESCRIPTION,\n inputSchema: PREVIEW_INPUT_SCHEMA,\n call: async (rawArgs) => {\n const rec = new SpanRecorder();\n const startedAt = performance.now();\n type CallResult = Awaited<\n ReturnType<CodesignMcpToolRegistration['call']>\n >;\n let result: CallResult | undefined;\n let threw: unknown;\n let revision: string | undefined;\n try {\n result = await (async (): Promise<CallResult> => {\n const args = (rawArgs ?? {}) as PreviewArgs;\n if (typeof args.blockId !== 'number') {\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: 'preview: `blockId` (integer) is required.'\n }\n ]\n };\n }\n // Load (if a revision was given) and export read the shared engine across\n // awaits — serialize against any concurrent engine-touching tool call.\n const beforeLock = performance.now();\n return engineLock.runExclusive(async () => {\n rec.add('lock_wait', performance.now() - beforeLock);\n let slot: EngineSlot<E>;\n if (args.revision !== undefined) {\n const revId = args.revision;\n revision = revId;\n const rev = await rec.time('workspace_read', () =>\n store.read(revId)\n );\n slot = await rec.time('engine_scene_load', async () => {\n await session.populate(revId, rev.scene);\n return session.acquire(revId);\n });\n } else {\n // No revision: render the current engine state via the side-effect-free\n // currentSlot accessor (does NOT reset). If no slot is warm, error.\n const current = session.currentSlot();\n if (!current) {\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: 'preview: no engine state to render. Run `edit` first, or pass `revision` to load a specific revision.'\n }\n ]\n };\n }\n slot = current;\n }\n const eng = slot.engine as MinimalEngine;\n const format = args.format ?? 'webp';\n const mimeType = `image/${format}`;\n const targetWidth = Math.min(\n args.width ?? DEFAULT_TARGET_WIDTH,\n MAX_DIMENSION_PX\n );\n const exportOpts: Record<string, unknown> = { targetWidth };\n if (args.height !== undefined)\n exportOpts.targetHeight = Math.min(args.height, MAX_DIMENSION_PX);\n const blob = await rec.time('engine_execute', () =>\n eng.block.export(args.blockId, mimeType, exportOpts)\n );\n const bytes = new Uint8Array(await blob.arrayBuffer());\n return {\n content: [\n { type: 'image' as const, data: bytesToBase64(bytes), mimeType }\n ]\n };\n });\n })();\n return result;\n } catch (err) {\n threw = err;\n throw err;\n } finally {\n telemetry.record({\n tool: 'preview',\n succeeded: threw === undefined && result?.isError !== true,\n totalMs: performance.now() - startedAt,\n spans: rec.list(),\n args: rawArgs,\n ...(result !== undefined ? { result: summarizeResult(result) } : {}),\n ...(revision ? { revision } : {}),\n ...(threw ? { error: toErrorShape(threw) } : {})\n });\n }\n }\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/tools/preview.ts"],"names":[],"mappings":"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","sourcesContent":["// packages/codesign-mcp/src/tools/preview.ts\n//\n// `preview` — renders a block to an inline image. `revision` is REQUIRED\n// (CODE-76): the session loads that revision before exporting, so `blockId`\n// always resolves against the scene it belongs to — never against a different\n// design a concurrent agent may have loaded into the shared engine.\n\nimport { performance } from 'node:perf_hooks';\nimport type { Mutex } from '../engine/mutex.js';\nimport type { EngineSession } from '../engine/session.js';\nimport type { RevisionStore } from '../revisions/store.js';\nimport type { CodesignMcpToolRegistration } from '../server.js';\nimport {\n SpanRecorder,\n NOOP_TELEMETRY_SINK,\n summarizeResult,\n toErrorShape,\n type TelemetrySink\n} from './spans.js';\n\nconst MAX_DIMENSION_PX = 1800;\nconst DEFAULT_TARGET_WIDTH = 512;\n\ninterface MinimalEngine {\n block: {\n export(blockId: number, mimeType: string, options?: unknown): Promise<Blob>;\n };\n}\n\ninterface PreviewArgs {\n blockId: number;\n revision: string;\n format?: 'webp' | 'png' | 'jpeg';\n width?: number;\n height?: number;\n}\n\nconst DESCRIPTION = [\n 'Render a block (almost always a page) to an inline image so you can see the result.',\n '',\n \"`blockId` and `revision` are BOTH REQUIRED. Preview the page you're working on (not the scene root), and pass the `revision` the block belongs to — the id `edit` returned.\",\n '',\n 'The given `revision` is loaded before rendering, so `blockId` always resolves against the right scene — never against a different design another agent may have loaded on a shared server.',\n '',\n \"Defaults: format='webp', width=512. Width/height clamped at 1800 px (Anthropic many-image limit).\"\n].join('\\n');\n\nconst PREVIEW_INPUT_SCHEMA = {\n type: 'object' as const,\n properties: {\n blockId: { type: 'integer' as const },\n revision: {\n type: 'string' as const,\n description:\n 'Revision id the block belongs to (from `edit`). Loaded before rendering so `blockId` resolves against the right scene.'\n },\n format: { enum: ['webp', 'png', 'jpeg'] },\n width: { type: 'integer' as const, minimum: 1 },\n height: { type: 'integer' as const, minimum: 1 }\n },\n required: ['blockId', 'revision'],\n additionalProperties: false\n};\n\nexport interface CreatePreviewToolOptions<E = unknown> {\n store: RevisionStore;\n session: EngineSession<E>;\n /** Guards the shared engine — load-then-export must not interleave with another session's edit (see engine/mutex.ts). */\n engineLock: Mutex;\n /** Diagnostics telemetry port. Absent → no-op (no timing overhead beyond a few clocks). */\n telemetry?: TelemetrySink;\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n if (typeof Buffer !== 'undefined')\n return Buffer.from(bytes).toString('base64');\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);\n return btoa(s);\n}\n\nexport function createPreviewTool<E = unknown>(\n opts: CreatePreviewToolOptions<E>\n): CodesignMcpToolRegistration {\n const { store, session, engineLock } = opts;\n const telemetry = opts.telemetry ?? NOOP_TELEMETRY_SINK;\n return {\n name: 'preview',\n description: DESCRIPTION,\n inputSchema: PREVIEW_INPUT_SCHEMA,\n call: async (rawArgs) => {\n const rec = new SpanRecorder();\n const startedAt = performance.now();\n type CallResult = Awaited<\n ReturnType<CodesignMcpToolRegistration['call']>\n >;\n let result: CallResult | undefined;\n let threw: unknown;\n let revision: string | undefined;\n let exportError: unknown;\n try {\n result = await (async (): Promise<CallResult> => {\n const args = (rawArgs ?? {}) as PreviewArgs;\n if (typeof args.blockId !== 'number') {\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: 'preview: `blockId` (integer) is required.'\n }\n ]\n };\n }\n if (typeof args.revision !== 'string') {\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: 'preview: `revision` (string) is required — pass the revision the block belongs to (the id `edit` returned). It is loaded before rendering so `blockId` always resolves against the right scene.'\n }\n ]\n };\n }\n const revId = args.revision;\n revision = revId;\n // Load the requested revision, then export — both read the shared engine\n // across awaits, so serialize against any concurrent engine-touching tool\n // call (see engine/mutex.ts).\n const beforeLock = performance.now();\n return engineLock.runExclusive(async () => {\n rec.add('lock_wait', performance.now() - beforeLock);\n const rev = await rec.time('workspace_read', () =>\n store.read(revId)\n );\n const slot = await rec.time('engine_scene_load', async () => {\n await session.populate(revId, rev.scene);\n return session.acquire(revId);\n });\n const eng = slot.engine as MinimalEngine;\n const format = args.format ?? 'webp';\n const mimeType = `image/${format}`;\n const targetWidth = Math.min(\n args.width ?? DEFAULT_TARGET_WIDTH,\n MAX_DIMENSION_PX\n );\n const exportOpts: Record<string, unknown> = { targetWidth };\n if (args.height !== undefined)\n exportOpts.targetHeight = Math.min(args.height, MAX_DIMENSION_PX);\n // We loaded `revId` under the lock immediately above, so the correct\n // scene is guaranteed to be current here. If export still throws,\n // `blockId` genuinely isn't in this revision's scene — a wrong id, or\n // a block removed in this revision. Return a legible tool error the\n // model can act on — mirroring `edit`'s dirty-on-throw path — instead\n // of rethrowing into an opaque MCP -32603 the model can't reason about.\n let bytes: Uint8Array;\n try {\n const blob = await rec.time('engine_execute', () =>\n eng.block.export(args.blockId, mimeType, exportOpts)\n );\n bytes = new Uint8Array(await blob.arrayBuffer());\n } catch (err) {\n exportError = err;\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: `preview: could not render block ${args.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. ${\n err instanceof Error\n ? (err.stack ?? err.message)\n : String(err)\n }`\n }\n ]\n };\n }\n return {\n content: [\n { type: 'image' as const, data: bytesToBase64(bytes), mimeType }\n ]\n };\n });\n })();\n return result;\n } catch (err) {\n threw = err;\n throw err;\n } finally {\n telemetry.record({\n tool: 'preview',\n succeeded: threw === undefined && result?.isError !== true,\n totalMs: performance.now() - startedAt,\n spans: rec.list(),\n args: rawArgs,\n ...(result !== undefined ? { result: summarizeResult(result) } : {}),\n ...(revision ? { revision } : {}),\n ...(threw\n ? { error: toErrorShape(threw) }\n : exportError\n ? { error: toErrorShape(exportError) }\n : {})\n });\n }\n }\n };\n}\n"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { RevisionStore } from '../revisions/store.js';
|
|
2
2
|
import type { CodesignMcpToolRegistration } from '../server.js';
|
|
3
3
|
import type { ViewerUrlBuilder } from '../viewer/url-builder.js';
|
|
4
|
-
export interface
|
|
4
|
+
export interface CreateViewToolOptions {
|
|
5
5
|
urlBuilder: ViewerUrlBuilder;
|
|
6
6
|
/** Resolves a revision to its design root (the root revision id). */
|
|
7
7
|
store: RevisionStore;
|
|
8
8
|
}
|
|
9
|
-
export declare function
|
|
10
|
-
//# sourceMappingURL=
|
|
9
|
+
export declare function createViewTool(opts: CreateViewToolOptions): CodesignMcpToolRegistration;
|
|
10
|
+
//# sourceMappingURL=view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/tools/view.ts"],"names":[],"mappings":"AAAA,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;AAEjE,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,gBAAgB,CAAC;IAC7B,qEAAqE;IACrE,KAAK,EAAE,aAAa,CAAC;CACtB;AASD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,qBAAqB,GAC1B,2BAA2B,CA2D7B"}
|
|
@@ -1,39 +1,41 @@
|
|
|
1
|
-
const DESCRIPTION = "Get a URL the human can open to view the design in CE.SDK.
|
|
1
|
+
const DESCRIPTION = "Get a URL the human can open to view the design in CE.SDK. Returns the design's " +
|
|
2
2
|
"stable URL — the viewer keeps it live, re-rendering as you `edit`, so print it ONCE and don't re-emit " +
|
|
3
3
|
"a fresh URL every turn. Add `pin: true` for a frozen permalink to that exact revision (for 'look at " +
|
|
4
|
-
"exactly this' or comparing revisions).
|
|
5
|
-
'The
|
|
6
|
-
export function
|
|
4
|
+
"exactly this' or comparing revisions). " +
|
|
5
|
+
'The page is a full editor; edits made in the browser stay local to that tab (export-only) — agent edits remain authoritative.';
|
|
6
|
+
export function createViewTool(opts) {
|
|
7
7
|
return {
|
|
8
|
-
name: '
|
|
8
|
+
name: 'view',
|
|
9
9
|
description: DESCRIPTION,
|
|
10
10
|
inputSchema: {
|
|
11
11
|
type: 'object',
|
|
12
12
|
properties: {
|
|
13
13
|
revision: {
|
|
14
14
|
type: 'string',
|
|
15
|
-
description: 'Any revision in the design
|
|
15
|
+
description: 'Any revision in the design; resolved to its root and opened at the design head.'
|
|
16
16
|
},
|
|
17
17
|
pin: {
|
|
18
18
|
type: 'boolean',
|
|
19
19
|
description: 'When true, return a frozen permalink to this exact revision instead of the live design URL.'
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
|
+
required: ['revision'],
|
|
22
23
|
additionalProperties: false
|
|
23
24
|
},
|
|
24
25
|
call: async (rawArgs) => {
|
|
25
26
|
const args = (rawArgs ?? {});
|
|
27
|
+
if (typeof args.revision !== 'string') {
|
|
28
|
+
return {
|
|
29
|
+
isError: true,
|
|
30
|
+
content: [
|
|
31
|
+
{
|
|
32
|
+
type: 'text',
|
|
33
|
+
text: 'view: `revision` (string) is required.'
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
}
|
|
26
38
|
try {
|
|
27
|
-
if (args.revision === undefined) {
|
|
28
|
-
return {
|
|
29
|
-
content: [
|
|
30
|
-
{
|
|
31
|
-
type: 'text',
|
|
32
|
-
text: JSON.stringify({ url: opts.urlBuilder.forStudio() })
|
|
33
|
-
}
|
|
34
|
-
]
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
39
|
// historyOf returns the chain root-first; [0] is the design root.
|
|
38
40
|
const chain = await opts.store.historyOf(args.revision);
|
|
39
41
|
const root = chain[0].id;
|
|
@@ -53,10 +55,10 @@ export function createOpenEditorTool(opts) {
|
|
|
53
55
|
: String(err);
|
|
54
56
|
return {
|
|
55
57
|
isError: true,
|
|
56
|
-
content: [{ type: 'text', text: `
|
|
58
|
+
content: [{ type: 'text', text: `view: ${msg}` }]
|
|
57
59
|
};
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
|
-
//# sourceMappingURL=
|
|
64
|
+
//# sourceMappingURL=view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/tools/view.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,GACf,kFAAkF;IAClF,wGAAwG;IACxG,sGAAsG;IACtG,yCAAyC;IACzC,+HAA+H,CAAC;AAElI,MAAM,UAAU,cAAc,CAC5B,IAA2B;IAE3B,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,WAAW;QACxB,WAAW,EAAE;YACX,IAAI,EAAE,QAAiB;YACvB,UAAU,EAAE;gBACV,QAAQ,EAAE;oBACR,IAAI,EAAE,QAAiB;oBACvB,WAAW,EACT,iFAAiF;iBACpF;gBACD,GAAG,EAAE;oBACH,IAAI,EAAE,SAAkB;oBACxB,WAAW,EACT,6FAA6F;iBAChG;aACF;YACD,QAAQ,EAAE,CAAC,UAAU,CAAC;YACtB,oBAAoB,EAAE,KAAK;SAC5B;QACD,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACtB,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAyC,CAAC;YACrE,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACtC,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,wCAAwC;yBAC/C;qBACF;iBACF,CAAC;YACJ,CAAC;YACD,IAAI,CAAC;gBACH,kEAAkE;gBAClE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACxD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG;oBAClB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC;oBACxD,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;gBACpC,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;iBACpE,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,IAAI,GAAI,GAAyB,CAAC,IAAI,CAAC;gBAC7C,MAAM,GAAG,GACP,IAAI,KAAK,QAAQ;oBACf,CAAC,CAAC,oBAAoB,IAAI,CAAC,QAAQ,EAAE;oBACrC,CAAC,CAAC,GAAG,YAAY,KAAK;wBACpB,CAAC,CAAC,GAAG,CAAC,OAAO;wBACb,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACpB,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,SAAS,GAAG,EAAE,EAAE,CAAC;iBAC3D,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { RevisionStore } from '../revisions/store.js';\nimport type { CodesignMcpToolRegistration } from '../server.js';\nimport type { ViewerUrlBuilder } from '../viewer/url-builder.js';\n\nexport interface CreateViewToolOptions {\n urlBuilder: ViewerUrlBuilder;\n /** Resolves a revision to its design root (the root revision id). */\n store: RevisionStore;\n}\n\nconst DESCRIPTION =\n \"Get a URL the human can open to view the design in CE.SDK. Returns the design's \" +\n \"stable URL — the viewer keeps it live, re-rendering as you `edit`, so print it ONCE and don't re-emit \" +\n \"a fresh URL every turn. Add `pin: true` for a frozen permalink to that exact revision (for 'look at \" +\n \"exactly this' or comparing revisions). \" +\n 'The page is a full editor; edits made in the browser stay local to that tab (export-only) — agent edits remain authoritative.';\n\nexport function createViewTool(\n opts: CreateViewToolOptions\n): CodesignMcpToolRegistration {\n return {\n name: 'view',\n description: DESCRIPTION,\n inputSchema: {\n type: 'object' as const,\n properties: {\n revision: {\n type: 'string' as const,\n description:\n 'Any revision in the design; resolved to its root and opened at the design head.'\n },\n pin: {\n type: 'boolean' as const,\n description:\n 'When true, return a frozen permalink to this exact revision instead of the live design URL.'\n }\n },\n required: ['revision'],\n additionalProperties: false\n },\n call: async (rawArgs) => {\n const args = (rawArgs ?? {}) as { revision?: string; pin?: boolean };\n if (typeof args.revision !== 'string') {\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: 'view: `revision` (string) is required.'\n }\n ]\n };\n }\n try {\n // historyOf returns the chain root-first; [0] is the design root.\n const chain = await opts.store.historyOf(args.revision);\n const root = chain[0].id;\n const url = args.pin\n ? opts.urlBuilder.forPinnedRevision(root, args.revision)\n : opts.urlBuilder.forDesign(root);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ url }) }]\n };\n } catch (err) {\n const code = (err as { code?: string }).code;\n const msg =\n code === 'ENOENT'\n ? `unknown revision ${args.revision}`\n : err instanceof Error\n ? err.message\n : String(err);\n return {\n isError: true,\n content: [{ type: 'text' as const, text: `view: ${msg}` }]\n };\n }\n }\n };\n}\n"]}
|
|
@@ -9,6 +9,8 @@ export interface ViewerListener {
|
|
|
9
9
|
}
|
|
10
10
|
export interface StartViewerListenerOptions {
|
|
11
11
|
router: Router;
|
|
12
|
+
/** Additional routers mounted after the main router (e.g. the diagnostics router). */
|
|
13
|
+
extraRouters?: Router[];
|
|
12
14
|
host?: string;
|
|
13
15
|
port?: number;
|
|
14
16
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../src/viewer/listener.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACtC,OAAgB,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,0BAA0B,GAC/B,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../src/viewer/listener.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACtC,OAAgB,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,0BAA0B,GAC/B,OAAO,CAAC,cAAc,CAAC,CAwBzB"}
|
package/dist/viewer/listener.js
CHANGED
|
@@ -8,6 +8,8 @@ export async function startViewerListener(opts) {
|
|
|
8
8
|
const host = opts.host ?? '127.0.0.1';
|
|
9
9
|
const app = express();
|
|
10
10
|
app.use(opts.router);
|
|
11
|
+
for (const extra of opts.extraRouters ?? [])
|
|
12
|
+
app.use(extra);
|
|
11
13
|
const server = app.listen(opts.port ?? 0, host);
|
|
12
14
|
await new Promise((resolve, reject) => {
|
|
13
15
|
server.once('listening', () => resolve());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listener.js","sourceRoot":"","sources":["../../src/viewer/listener.ts"],"names":[],"mappings":"AAAA,EAAE;AACF,0EAA0E;AAC1E,4EAA4E;AAC5E,yEAAyE;AACzE,sBAAsB;AAItB,OAAO,OAAyB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"listener.js","sourceRoot":"","sources":["../../src/viewer/listener.ts"],"names":[],"mappings":"AAAA,EAAE;AACF,0EAA0E;AAC1E,4EAA4E;AAC5E,yEAAyE;AACzE,sBAAsB;AAItB,OAAO,OAAyB,MAAM,SAAS,CAAC;AAiBhD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAgC;IAEhC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC;IACtC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,YAAY,IAAI,EAAE;QAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IAChD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;IAC9B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QACnC,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,MAAM,IAAI,GAAG,UAAU,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;IAC3C,OAAO;QACL,GAAG;QACH,MAAM;QACN,IAAI;QACJ,KAAK,EAAE,GAAG,EAAE,CACV,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9B,MAAM,CAAC,mBAAmB,EAAE,EAAE,CAAC;QACjC,CAAC,CAAC;KACL,CAAC;AACJ,CAAC","sourcesContent":["//\n// Ephemeral localhost HTTP listener used by stdio mode. Mounts the viewer\n// router; selects an OS-assigned free port (port: 0); reports the base URL.\n// In http mode the same router mounts onto the transport's express app —\n// no listener needed.\n\nimport type { Server } from 'node:http';\nimport type { Router } from 'express';\nimport express, { type Express } from 'express';\n\nexport interface ViewerListener {\n app: Express;\n server: Server;\n base: string;\n close(): Promise<void>;\n}\n\nexport interface StartViewerListenerOptions {\n router: Router;\n /** Additional routers mounted after the main router (e.g. the diagnostics router). */\n extraRouters?: Router[];\n host?: string;\n port?: number;\n}\n\nexport async function startViewerListener(\n opts: StartViewerListenerOptions\n): Promise<ViewerListener> {\n const host = opts.host ?? '127.0.0.1';\n const app = express();\n app.use(opts.router);\n for (const extra of opts.extraRouters ?? []) app.use(extra);\n const server = app.listen(opts.port ?? 0, host);\n await new Promise<void>((resolve, reject) => {\n server.once('listening', () => resolve());\n server.once('error', (err) => reject(err));\n });\n const addr = server.address();\n if (!addr || typeof addr === 'string')\n throw new Error('viewer listener: failed to read bound address');\n const base = `http://${host}:${addr.port}`;\n return {\n app,\n server,\n base,\n close: () =>\n new Promise<void>((resolve) => {\n server.close(() => resolve());\n server.closeAllConnections?.();\n })\n };\n}\n"]}
|
|
@@ -12,7 +12,7 @@ export interface PresignedR2UrlBuilderOptions extends LocalUrlBuilderOptions {
|
|
|
12
12
|
* URL builder for S3/R2-backed hosted deployments.
|
|
13
13
|
*
|
|
14
14
|
* Inherits all sync viewer URL methods from LocalViewerUrlBuilder
|
|
15
|
-
* (forDesign / forPinnedRevision /
|
|
15
|
+
* (forDesign / forPinnedRevision / forResource), so the
|
|
16
16
|
* synchronous CE.SDK `workspace://` URI resolver callback continues to work
|
|
17
17
|
* unchanged. Only `forDownload` is overridden to return a presigned GET URL
|
|
18
18
|
* so the MCP client can download artifacts directly from R2 rather than
|
|
@@ -11,7 +11,7 @@ import { LocalViewerUrlBuilder } from './url-builder.js';
|
|
|
11
11
|
* URL builder for S3/R2-backed hosted deployments.
|
|
12
12
|
*
|
|
13
13
|
* Inherits all sync viewer URL methods from LocalViewerUrlBuilder
|
|
14
|
-
* (forDesign / forPinnedRevision /
|
|
14
|
+
* (forDesign / forPinnedRevision / forResource), so the
|
|
15
15
|
* synchronous CE.SDK `workspace://` URI resolver callback continues to work
|
|
16
16
|
* unchanged. Only `forDownload` is overridden to return a presigned GET URL
|
|
17
17
|
* so the MCP client can download artifacts directly from R2 rather than
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presigned-r2-url-builder.js","sourceRoot":"","sources":["../../src/viewer/presigned-r2-url-builder.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,iFAAiF;AACjF,oEAAoE;AACpE,OAAO,EAAE,gBAAgB,EAAiB,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EACL,qBAAqB,EAEtB,MAAM,kBAAkB,CAAC;AAW1B;;;;;;;;;;GAUG;AACH,MAAM,OAAO,qBAAsB,SAAQ,qBAAqB;IAM9D,YAAY,IAAkC;QAC5C,KAAK,CAAC,IAAI,CAAC,CAAC;QACZ,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACpC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;IAC1C,CAAC;IAEQ,KAAK,CAAC,WAAW,CAAC,OAAe;QACxC,2EAA2E;QAC3E,8EAA8E;QAC9E,6EAA6E;QAC7E,2EAA2E;QAC3E,2EAA2E;QAC3E,mBAAmB;QACnB,MAAM,GAAG,GAAG,cAAc,IAAI,CAAC,WAAW,IAAI,OAAO,EAAE,CAAC;QACxD,OAAO,YAAY,CACjB,IAAI,CAAC,MAAM,EACX,IAAI,gBAAgB,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EACvD,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAC9B,CAAC;IACJ,CAAC;CACF","sourcesContent":["// packages/codesign-mcp/src/viewer/presigned-r2-url-builder.ts\n//\n// Node-only. NOT exported from viewer/index.ts or the main package entrypoint\n// — keeps @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner out of the\n// browser bundle (mirrors the pattern used by S3WorkspaceFS in workspace/s3.ts).\n// Imported directly by cli/http.ts for the S3/R2 hosted deployment.\nimport { GetObjectCommand, type S3Client } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport {\n LocalViewerUrlBuilder,\n type LocalUrlBuilderOptions\n} from './url-builder.js';\n\nexport interface PresignedR2UrlBuilderOptions extends LocalUrlBuilderOptions {\n client: S3Client;\n bucket: string;\n /** The session's workspace id — the R2 key is `workspaces/<workspaceId>/<relpath>`. */\n workspaceId: string;\n /** Presigned URL lifetime in seconds. Default 3600. */\n expiresIn?: number;\n}\n\n/**\n * URL builder for S3/R2-backed hosted deployments.\n *\n * Inherits all sync viewer URL methods from LocalViewerUrlBuilder\n * (forDesign / forPinnedRevision /
|
|
1
|
+
{"version":3,"file":"presigned-r2-url-builder.js","sourceRoot":"","sources":["../../src/viewer/presigned-r2-url-builder.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,iFAAiF;AACjF,oEAAoE;AACpE,OAAO,EAAE,gBAAgB,EAAiB,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EACL,qBAAqB,EAEtB,MAAM,kBAAkB,CAAC;AAW1B;;;;;;;;;;GAUG;AACH,MAAM,OAAO,qBAAsB,SAAQ,qBAAqB;IAM9D,YAAY,IAAkC;QAC5C,KAAK,CAAC,IAAI,CAAC,CAAC;QACZ,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACpC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;IAC1C,CAAC;IAEQ,KAAK,CAAC,WAAW,CAAC,OAAe;QACxC,2EAA2E;QAC3E,8EAA8E;QAC9E,6EAA6E;QAC7E,2EAA2E;QAC3E,2EAA2E;QAC3E,mBAAmB;QACnB,MAAM,GAAG,GAAG,cAAc,IAAI,CAAC,WAAW,IAAI,OAAO,EAAE,CAAC;QACxD,OAAO,YAAY,CACjB,IAAI,CAAC,MAAM,EACX,IAAI,gBAAgB,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EACvD,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAC9B,CAAC;IACJ,CAAC;CACF","sourcesContent":["// packages/codesign-mcp/src/viewer/presigned-r2-url-builder.ts\n//\n// Node-only. NOT exported from viewer/index.ts or the main package entrypoint\n// — keeps @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner out of the\n// browser bundle (mirrors the pattern used by S3WorkspaceFS in workspace/s3.ts).\n// Imported directly by cli/http.ts for the S3/R2 hosted deployment.\nimport { GetObjectCommand, type S3Client } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport {\n LocalViewerUrlBuilder,\n type LocalUrlBuilderOptions\n} from './url-builder.js';\n\nexport interface PresignedR2UrlBuilderOptions extends LocalUrlBuilderOptions {\n client: S3Client;\n bucket: string;\n /** The session's workspace id — the R2 key is `workspaces/<workspaceId>/<relpath>`. */\n workspaceId: string;\n /** Presigned URL lifetime in seconds. Default 3600. */\n expiresIn?: number;\n}\n\n/**\n * URL builder for S3/R2-backed hosted deployments.\n *\n * Inherits all sync viewer URL methods from LocalViewerUrlBuilder\n * (forDesign / forPinnedRevision / forResource), so the\n * synchronous CE.SDK `workspace://` URI resolver callback continues to work\n * unchanged. Only `forDownload` is overridden to return a presigned GET URL\n * so the MCP client can download artifacts directly from R2 rather than\n * going through the `/r/` Express route (which is not mounted on the hosted\n * Cloudflare deployment).\n */\nexport class PresignedR2UrlBuilder extends LocalViewerUrlBuilder {\n private readonly client: S3Client;\n private readonly bucket: string;\n private readonly workspaceId: string;\n private readonly expiresIn: number;\n\n constructor(opts: PresignedR2UrlBuilderOptions) {\n super(opts);\n this.client = opts.client;\n this.bucket = opts.bucket;\n this.workspaceId = opts.workspaceId;\n this.expiresIn = opts.expiresIn ?? 3600;\n }\n\n override async forDownload(relpath: string): Promise<string> {\n // workspaceId is used raw (no sanitize here) on purpose: it is the SAME id\n // ScopedWorkspaceFS used to store the artifact, and ScopedWorkspaceFS already\n // sanitised it (rejecting traversal) before any bytes were written. So a key\n // we can presign always corresponds to a stored object, and sanitize is an\n // identity on accepted ids — re-sanitising here would only risk drift from\n // the storage key.\n const key = `workspaces/${this.workspaceId}/${relpath}`;\n return getSignedUrl(\n this.client,\n new GetObjectCommand({ Bucket: this.bucket, Key: key }),\n { expiresIn: this.expiresIn }\n );\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/viewer/router.ts"],"names":[],"mappings":"AAKA,OAAO,EAIL,MAAM,EACP,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/viewer/router.ts"],"names":[],"mappings":"AAKA,OAAO,EAIL,MAAM,EACP,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAKvD,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,aAAa,CAAC;IACrB,EAAE,EAAE,WAAW,CAAC;IAChB,WAAW,EAAE,cAAc,CAAC;IAC5B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gHAAgH;IAChH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,yBAAyB,GAAG,MAAM,CAwI1E"}
|
package/dist/viewer/router.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
//
|
|
2
|
-
// Express router serving the
|
|
3
|
-
//
|
|
2
|
+
// Express router serving the editor SPA + JSON snapshot API + SSE
|
|
3
|
+
// channel. Stateless: every request reads through the supplied
|
|
4
4
|
// store/fs. Same router mounts on the stdio listener and on the HTTP
|
|
5
5
|
// transport's express app (one impl, no fork).
|
|
6
6
|
import { Router } from 'express';
|
|
7
|
+
import { defaultAssetsBaseUrl } from '../engine/asset-sources.js';
|
|
7
8
|
import { createResourceRoute } from './resource-route.js';
|
|
8
9
|
import { SPA_ASSETS } from './spa-assets.generated.js';
|
|
9
10
|
import { cesdkCdnUrl, installedCesdkVersion } from './cesdk-cdn.js';
|
|
@@ -25,16 +26,14 @@ export function createViewerRouter(opts) {
|
|
|
25
26
|
res.json({
|
|
26
27
|
license: opts.cesdkLicense ?? '',
|
|
27
28
|
resourceBase: `${req.baseUrl}/r/`,
|
|
28
|
-
cesdkEngineUrl
|
|
29
|
+
cesdkEngineUrl,
|
|
30
|
+
defaultAssetsBaseUrl: defaultAssetsBaseUrl()
|
|
29
31
|
});
|
|
30
32
|
});
|
|
31
33
|
router.use(createResourceRoute({ fs, workspaceRoot: opts.workspaceRoot ?? '' }));
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// `/d/<root>` (LIVE) and `/d/<root>/<rev>` (PINNED) both serve the viewer
|
|
36
|
-
// SPA; viewer.js reads the path depth to choose the mode. The old
|
|
37
|
-
// per-revision `/s/<rev>` route is gone — `open_editor` hands out `/d/`.
|
|
34
|
+
// `/d/<root>` (LIVE) and `/d/<root>/<rev>` (PINNED) both serve the editor
|
|
35
|
+
// SPA; editor.js reads the path depth to choose the mode. The old detached
|
|
36
|
+
// "Edit a copy" route (`/e/<rev>`) is gone — the surface is editable itself.
|
|
38
37
|
const serveViewer = (req, res) => {
|
|
39
38
|
res.type('html').send(renderHtml(SPA_ASSETS['index.html'], req.baseUrl));
|
|
40
39
|
};
|
|
@@ -96,14 +95,6 @@ export function createViewerRouter(opts) {
|
|
|
96
95
|
router.get('/sse/:root', (req, res) => {
|
|
97
96
|
broadcaster.subscribe(req.params.root, res);
|
|
98
97
|
});
|
|
99
|
-
router.get('/api/designs', async (_req, res, next) => {
|
|
100
|
-
try {
|
|
101
|
-
res.json(await store.listRoots());
|
|
102
|
-
}
|
|
103
|
-
catch (err) {
|
|
104
|
-
next(err);
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
98
|
router.get('/api/design/:root', async (req, res, next) => {
|
|
108
99
|
try {
|
|
109
100
|
const roots = await store.listRoots();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/viewer/router.ts"],"names":[],"mappings":"AAAA,EAAE;AACF,4EAA4E;AAC5E,yEAAyE;AACzE,qEAAqE;AACrE,+CAA+C;AAC/C,OAAO,EAIL,MAAM,EACP,MAAM,SAAS,CAAC;AAIjB,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAuBpE,MAAM,UAAU,kBAAkB,CAAC,IAA+B;IAChE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IACxC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,MAAM,cAAc,GAClB,IAAI,CAAC,cAAc,IAAI,WAAW,CAAC,qBAAqB,EAAE,CAAC,CAAC;IAE9D,4EAA4E;IAC5E,yEAAyE;IACzE,6EAA6E;IAC7E,wEAAwE;IACxE,2EAA2E;IAC3E,yEAAyE;IACzE,iEAAiE;IACjE,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,OAAe,EAAU,EAAE,CAC3D,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;IAE9C,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACrC,yEAAyE;QACzE,sEAAsE;QACtE,GAAG,CAAC,IAAI,CAAC;YACP,OAAO,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE;YAChC,YAAY,EAAE,GAAG,GAAG,CAAC,OAAO,KAAK;YACjC,cAAc;SACf,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CACR,mBAAmB,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC,CACrE,CAAC;IAEF,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC3B,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,0EAA0E;IAC1E,kEAAkE;IAClE,yEAAyE;IACzE,MAAM,WAAW,GAAG,CAAC,GAAY,EAAE,GAAa,EAAQ,EAAE;QACxD,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3E,CAAC,CAAC;IACF,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IACpC,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;IAEzC,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC7C,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;QAC7C,MAAM,KAAK,GACT,GAAG,KAAK,MAAM;YACZ,CAAC,CAAC,WAAW;YACb,CAAC,CAAC,GAAG,KAAK,IAAI;gBACZ,CAAC,CAAC,wBAAwB;gBAC1B,CAAC,CAAC,YAAY,CAAC;QACrB,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,sEAAsE;IACtE,mEAAmE;IACnE,MAAM,CAAC,GAAG,CAAC,0BAA0B,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC9D,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;YAC3B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC1B,MAAM,OAAO,GAAG,WAAW,KAAK,IAAI,IAAI,eAAe,CAAC;YACxD,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAAE,OAAO,IAAI,EAAE,CAAC;YAC/C,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACzC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACpD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAClD,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACpD,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACjC,GAAG,CAAC,IAAI,CAAC;gBACP,QAAQ,EAAE,QAAQ,CAAC,EAAE;gBACrB,YAAY;gBACZ,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,KAAK,EAAE,MAAM,KAAK,CAAC,YAAY,CAAC,YAAY,CAAC;gBAC7C,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI;gBACxB,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS;aACnC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAAyB,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACjD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBACpD,OAAO;YACT,CAAC;YACD,kEAAkE;YAClE,4DAA4D;YAC5D,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACpC,WAAW,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACnD,IAAI,CAAC;YACH,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CAAC,mBAAmB,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACvD,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACrE,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBAClD,OAAO;YACT,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CAAC,6BAA6B,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACjE,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACjD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBAClD,OAAO;YACT,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,4EAA4E;IAC5E,uEAAuE;IACvE,wEAAwE;IACxE,+CAA+C;IAC/C,MAAM,CAAC,GAAG,CACR,CAAC,IAAa,EAAE,IAAa,EAAE,GAAa,EAAE,KAAmB,EAAE,EAAE;QACnE,IAAI,GAAG,CAAC,WAAW;YAAE,OAAO;QAC5B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACtD,CAAC,CACF,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["//\n// Express router serving the read-only viewer + mini-studio + JSON snapshot\n// API + SSE channel. Stateless: every request reads through the supplied\n// store/fs. Same router mounts on the stdio listener and on the HTTP\n// transport's express app (one impl, no fork).\nimport {\n type NextFunction,\n type Request,\n type Response,\n Router\n} from 'express';\nimport type { RevisionStore } from '../revisions/store.js';\nimport type { WorkspaceFS } from '../workspace/types.js';\nimport type { SseBroadcaster } from './broadcaster.js';\nimport { createResourceRoute } from './resource-route.js';\nimport { SPA_ASSETS } from './spa-assets.generated.js';\nimport { cesdkCdnUrl, installedCesdkVersion } from './cesdk-cdn.js';\n\nexport interface CreateViewerRouterOptions {\n store: RevisionStore;\n fs: WorkspaceFS;\n broadcaster: SseBroadcaster;\n /**\n * CE.SDK license served to the browser viewer via `/api/config`.\n * Without it, the SPA can't initialise CreativeEditorSDK and shows a\n * license error. The viewer requires a browser-shape license (which may\n * be a separate SKU from the @cesdk/node license used by the agent).\n */\n cesdkLicense?: string;\n /** Absolute workspace root, forwarded to the `/r/` route for disk streaming. Empty on object-store backends. */\n workspaceRoot?: string;\n /**\n * Full cdn.img.ly URL the browser viewer imports the CE.SDK engine from.\n * Defaults to the installed engine version so the preview can never drift\n * from the node runtime. Overridable for tests.\n */\n cesdkEngineUrl?: string;\n}\n\nexport function createViewerRouter(opts: CreateViewerRouterOptions): Router {\n const { store, fs, broadcaster } = opts;\n const router = Router();\n const cesdkEngineUrl =\n opts.cesdkEngineUrl ?? cesdkCdnUrl(installedCesdkVersion());\n\n // The SPA is mounted at root on the stdio listener (req.baseUrl === \"\") and\n // under \"/viewer\" on the HTTP transport (req.baseUrl === \"/viewer\"). Its\n // HTML/JS address assets and API routes by path, so we inject the live mount\n // prefix at serve time: the `{{VIEWER_BASE}}` token in the HTML becomes\n // req.baseUrl, and the inline `window.__VIEWER_BASE__` global it sets lets\n // the SPA's JS build matching URLs at runtime. Without this the absolute\n // paths resolve against the origin root and 404 under \"/viewer\".\n const renderHtml = (html: string, baseUrl: string): string =>\n html.replaceAll('{{VIEWER_BASE}}', baseUrl);\n\n router.get('/api/config', (req, res) => {\n // `resourceBase` is where the browser viewer resolves workspace:// asset\n // URIs. `req.baseUrl` is \"\" on the stdio listener, \"/viewer\" on HTTP.\n res.json({\n license: opts.cesdkLicense ?? '',\n resourceBase: `${req.baseUrl}/r/`,\n cesdkEngineUrl\n });\n });\n\n router.use(\n createResourceRoute({ fs, workspaceRoot: opts.workspaceRoot ?? '' })\n );\n\n router.get('/', (req, res) => {\n res.type('html').send(renderHtml(SPA_ASSETS['studio.html'], req.baseUrl));\n });\n\n // `/d/<root>` (LIVE) and `/d/<root>/<rev>` (PINNED) both serve the viewer\n // SPA; viewer.js reads the path depth to choose the mode. The old\n // per-revision `/s/<rev>` route is gone — `open_editor` hands out `/d/`.\n const serveViewer = (req: Request, res: Response): void => {\n res.type('html').send(renderHtml(SPA_ASSETS['index.html'], req.baseUrl));\n };\n router.get('/d/:root', serveViewer);\n router.get('/d/:root/:rev', serveViewer);\n\n router.get('/static/:name', (req, res, next) => {\n const body = SPA_ASSETS[req.params.name];\n if (!body) return next();\n const ext = req.params.name.split('.').pop();\n const ctype =\n ext === 'html'\n ? 'text/html'\n : ext === 'js'\n ? 'application/javascript'\n : 'text/plain';\n res.type(ctype).send(body);\n });\n\n // NOTE: thumbnail MUST be registered before /snapshot/:rev so Express\n // matches it first (route registration order is resolution order).\n router.get('/snapshot/:rev/thumbnail', async (req, res, next) => {\n try {\n const rev = req.params.rev;\n const shard = rev.slice(0, 2);\n const rest = rev.slice(2);\n const relPath = `exports/${shard}/${rest}/preview.webp`;\n if (!(await fs.exists(relPath))) return next();\n const bytes = await fs.readFile(relPath);\n res.type('image/webp').send(Buffer.from(bytes));\n } catch (err) {\n next(err);\n }\n });\n\n router.get('/snapshot/:rev', async (req, res, next) => {\n try {\n const revision = await store.read(req.params.rev);\n const chain = await store.historyOf(req.params.rev);\n const rootRevision = chain[0].id;\n res.json({\n revision: revision.id,\n rootRevision,\n scene: revision.scene,\n title: await store.currentTitle(rootRevision),\n note: revision.meta.note,\n createdAt: revision.meta.createdAt\n });\n } catch (err) {\n if ((err as { code?: string }).code === 'ENOENT') {\n res.status(404).json({ error: 'unknown revision' });\n return;\n }\n // Non-ENOENT errors carry fs paths in their message — hand to the\n // sanitising error handler rather than echoing err.message.\n next(err);\n }\n });\n\n router.get('/sse/:root', (req, res) => {\n broadcaster.subscribe(req.params.root, res);\n });\n\n router.get('/api/designs', async (_req, res, next) => {\n try {\n res.json(await store.listRoots());\n } catch (err) {\n next(err);\n }\n });\n\n router.get('/api/design/:root', async (req, res, next) => {\n try {\n const roots = await store.listRoots();\n const design = roots.find((d) => d.rootRevision === req.params.root);\n if (!design) {\n res.status(404).json({ error: 'unknown design' });\n return;\n }\n res.json(design);\n } catch (err) {\n next(err);\n }\n });\n\n router.get('/api/design/:root/revisions', async (req, res, next) => {\n try {\n const tree = await store.treeOf(req.params.root);\n if (tree.length === 0) {\n res.status(404).json({ error: 'unknown design' });\n return;\n }\n res.json(tree);\n } catch (err) {\n next(err);\n }\n });\n\n // Sanitising error handler — fs errors embed the absolute workspace path in\n // their message; convert every error to a generic response so the path\n // never reaches a client. MUST be last, and MUST take all four args for\n // Express to recognise it as an error handler.\n router.use(\n (_err: unknown, _req: Request, res: Response, _next: NextFunction) => {\n if (res.headersSent) return;\n res.status(500).type('text').send('internal error');\n }\n );\n\n return router;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/viewer/router.ts"],"names":[],"mappings":"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","sourcesContent":["//\n// Express router serving the editor SPA + JSON snapshot API + SSE\n// channel. Stateless: every request reads through the supplied\n// store/fs. Same router mounts on the stdio listener and on the HTTP\n// transport's express app (one impl, no fork).\nimport {\n type NextFunction,\n type Request,\n type Response,\n Router\n} from 'express';\nimport type { RevisionStore } from '../revisions/store.js';\nimport type { WorkspaceFS } from '../workspace/types.js';\nimport { defaultAssetsBaseUrl } from '../engine/asset-sources.js';\nimport type { SseBroadcaster } from './broadcaster.js';\nimport { createResourceRoute } from './resource-route.js';\nimport { SPA_ASSETS } from './spa-assets.generated.js';\nimport { cesdkCdnUrl, installedCesdkVersion } from './cesdk-cdn.js';\n\nexport interface CreateViewerRouterOptions {\n store: RevisionStore;\n fs: WorkspaceFS;\n broadcaster: SseBroadcaster;\n /**\n * CE.SDK license served to the browser viewer via `/api/config`.\n * Without it, the SPA can't initialise CreativeEditorSDK and shows a\n * license error. The viewer requires a browser-shape license (which may\n * be a separate SKU from the @cesdk/node license used by the agent).\n */\n cesdkLicense?: string;\n /** Absolute workspace root, forwarded to the `/r/` route for disk streaming. Empty on object-store backends. */\n workspaceRoot?: string;\n /**\n * Full cdn.img.ly URL the browser viewer imports the CE.SDK engine from.\n * Defaults to the installed engine version so the preview can never drift\n * from the node runtime. Overridable for tests.\n */\n cesdkEngineUrl?: string;\n}\n\nexport function createViewerRouter(opts: CreateViewerRouterOptions): Router {\n const { store, fs, broadcaster } = opts;\n const router = Router();\n const cesdkEngineUrl =\n opts.cesdkEngineUrl ?? cesdkCdnUrl(installedCesdkVersion());\n\n // The SPA is mounted at root on the stdio listener (req.baseUrl === \"\") and\n // under \"/viewer\" on the HTTP transport (req.baseUrl === \"/viewer\"). Its\n // HTML/JS address assets and API routes by path, so we inject the live mount\n // prefix at serve time: the `{{VIEWER_BASE}}` token in the HTML becomes\n // req.baseUrl, and the inline `window.__VIEWER_BASE__` global it sets lets\n // the SPA's JS build matching URLs at runtime. Without this the absolute\n // paths resolve against the origin root and 404 under \"/viewer\".\n const renderHtml = (html: string, baseUrl: string): string =>\n html.replaceAll('{{VIEWER_BASE}}', baseUrl);\n\n router.get('/api/config', (req, res) => {\n // `resourceBase` is where the browser viewer resolves workspace:// asset\n // URIs. `req.baseUrl` is \"\" on the stdio listener, \"/viewer\" on HTTP.\n res.json({\n license: opts.cesdkLicense ?? '',\n resourceBase: `${req.baseUrl}/r/`,\n cesdkEngineUrl,\n defaultAssetsBaseUrl: defaultAssetsBaseUrl()\n });\n });\n\n router.use(\n createResourceRoute({ fs, workspaceRoot: opts.workspaceRoot ?? '' })\n );\n\n // `/d/<root>` (LIVE) and `/d/<root>/<rev>` (PINNED) both serve the editor\n // SPA; editor.js reads the path depth to choose the mode. The old detached\n // \"Edit a copy\" route (`/e/<rev>`) is gone — the surface is editable itself.\n const serveViewer = (req: Request, res: Response): void => {\n res.type('html').send(renderHtml(SPA_ASSETS['index.html'], req.baseUrl));\n };\n router.get('/d/:root', serveViewer);\n router.get('/d/:root/:rev', serveViewer);\n\n router.get('/static/:name', (req, res, next) => {\n const body = SPA_ASSETS[req.params.name];\n if (!body) return next();\n const ext = req.params.name.split('.').pop();\n const ctype =\n ext === 'html'\n ? 'text/html'\n : ext === 'js'\n ? 'application/javascript'\n : 'text/plain';\n res.type(ctype).send(body);\n });\n\n // NOTE: thumbnail MUST be registered before /snapshot/:rev so Express\n // matches it first (route registration order is resolution order).\n router.get('/snapshot/:rev/thumbnail', async (req, res, next) => {\n try {\n const rev = req.params.rev;\n const shard = rev.slice(0, 2);\n const rest = rev.slice(2);\n const relPath = `exports/${shard}/${rest}/preview.webp`;\n if (!(await fs.exists(relPath))) return next();\n const bytes = await fs.readFile(relPath);\n res.type('image/webp').send(Buffer.from(bytes));\n } catch (err) {\n next(err);\n }\n });\n\n router.get('/snapshot/:rev', async (req, res, next) => {\n try {\n const revision = await store.read(req.params.rev);\n const chain = await store.historyOf(req.params.rev);\n const rootRevision = chain[0].id;\n res.json({\n revision: revision.id,\n rootRevision,\n scene: revision.scene,\n title: await store.currentTitle(rootRevision),\n note: revision.meta.note,\n createdAt: revision.meta.createdAt\n });\n } catch (err) {\n if ((err as { code?: string }).code === 'ENOENT') {\n res.status(404).json({ error: 'unknown revision' });\n return;\n }\n // Non-ENOENT errors carry fs paths in their message — hand to the\n // sanitising error handler rather than echoing err.message.\n next(err);\n }\n });\n\n router.get('/sse/:root', (req, res) => {\n broadcaster.subscribe(req.params.root, res);\n });\n\n router.get('/api/design/:root', async (req, res, next) => {\n try {\n const roots = await store.listRoots();\n const design = roots.find((d) => d.rootRevision === req.params.root);\n if (!design) {\n res.status(404).json({ error: 'unknown design' });\n return;\n }\n res.json(design);\n } catch (err) {\n next(err);\n }\n });\n\n router.get('/api/design/:root/revisions', async (req, res, next) => {\n try {\n const tree = await store.treeOf(req.params.root);\n if (tree.length === 0) {\n res.status(404).json({ error: 'unknown design' });\n return;\n }\n res.json(tree);\n } catch (err) {\n next(err);\n }\n });\n\n // Sanitising error handler — fs errors embed the absolute workspace path in\n // their message; convert every error to a generic response so the path\n // never reaches a client. MUST be last, and MUST take all four args for\n // Express to recognise it as an error handler.\n router.use(\n (_err: unknown, _req: Request, res: Response, _next: NextFunction) => {\n if (res.headersSent) return;\n res.status(500).type('text').send('internal error');\n }\n );\n\n return router;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-mount.js","sourceRoot":"","sources":["../../src/viewer/session-mount.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAQjD,MAAM,UAAU,kBAAkB,CAChC,GAAY,EACZ,IAA+B;IAE/B,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACrE,IAAI,QAA2B,CAAC;QAChC,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,oEAAoE;YACpE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC/C,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,kBAAkB,CAAC;YAChC,KAAK,EAAE,IAAI,aAAa,CAAC,QAAQ,CAAC;YAClC,EAAE,EAAE,QAAQ;YACZ,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["// packages/codesign-mcp/src/viewer/session-mount.ts\n//\n// Multi-tenant viewer mount for `session` workspace mode: GET /v/<sid>/...\n// serves the same
|
|
1
|
+
{"version":3,"file":"session-mount.js","sourceRoot":"","sources":["../../src/viewer/session-mount.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAQjD,MAAM,UAAU,kBAAkB,CAChC,GAAY,EACZ,IAA+B;IAE/B,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACrE,IAAI,QAA2B,CAAC;QAChC,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,oEAAoE;YACpE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC/C,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,kBAAkB,CAAC;YAChC,KAAK,EAAE,IAAI,aAAa,CAAC,QAAQ,CAAC;YAClC,EAAE,EAAE,QAAQ;YACZ,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["// packages/codesign-mcp/src/viewer/session-mount.ts\n//\n// Multi-tenant viewer mount for `session` workspace mode: GET /v/<sid>/...\n// serves the same editor router, scoped to that session's workspace\n// `s/<sid>` — the SAME derivation makeWorkspaceResolver applies on /mcp, so a\n// session always finds its own designs. The sid in the URL is a bearer\n// capability mirroring the /mcp trust posture (unguessable session id); real\n// authentication is a follow-up. The router is built per request: construction\n// is cheap and stateless, and caching by sid would be an unbounded,\n// attacker-fillable map on a public endpoint.\nimport type { Express, NextFunction, Request, Response } from 'express';\nimport { RevisionStore } from '../revisions/store.js';\nimport { ScopedWorkspaceFS } from '../workspace/scoped.js';\nimport type { WorkspaceFS } from '../workspace/types.js';\nimport type { SseBroadcaster } from './broadcaster.js';\nimport { createViewerRouter } from './router.js';\n\nexport interface MountSessionViewerOptions {\n rawFs: WorkspaceFS;\n broadcaster: SseBroadcaster;\n cesdkLicense: string | undefined;\n}\n\nexport function mountSessionViewer(\n app: Express,\n opts: MountSessionViewerOptions\n): void {\n app.use('/v/:sid', (req: Request, res: Response, next: NextFunction) => {\n let scopedFs: ScopedWorkspaceFS;\n try {\n scopedFs = new ScopedWorkspaceFS(opts.rawFs, `s/${req.params.sid}`);\n } catch {\n // sanitizeWorkspaceId rejected the sid (traversal / control chars).\n res.status(404).type('text').send('not found');\n return;\n }\n const router = createViewerRouter({\n store: new RevisionStore(scopedFs),\n fs: scopedFs,\n broadcaster: opts.broadcaster,\n ...(opts.cesdkLicense ? { cesdkLicense: opts.cesdkLicense } : {})\n });\n router(req, res, next);\n });\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spa-assets.generated.d.ts","sourceRoot":"","sources":["../../src/viewer/spa-assets.generated.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"spa-assets.generated.d.ts","sourceRoot":"","sources":["../../src/viewer/spa-assets.generated.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAk2B7C,CAAC"}
|