@mulmoclaude/mulmoscript-plugin 1.1.4 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contract-DxKVCRQk.cjs.map +1 -1
- package/dist/contract-vY0niHkh.js.map +1 -1
- package/dist/core/contract.d.ts +1 -1
- package/dist/core/contract.d.ts.map +1 -1
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/plugin-BLp8tSYt.cjs.map +1 -1
- package/dist/plugin-CXhOP_xU.js.map +1 -1
- package/dist/server/ops.d.ts +5 -5
- package/dist/server/ops.d.ts.map +1 -1
- package/dist/server/types.d.ts +8 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server.cjs +13 -7
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +12 -6
- package/dist/server.js.map +1 -1
- package/dist/style.css +18 -18
- package/dist/vue/View.vue.d.ts +1 -1
- package/dist/vue/View.vue.d.ts.map +1 -1
- package/dist/vue/components/BeatLightbox.vue.d.ts +4 -4
- package/dist/vue/components/CharacterStrip.vue.d.ts +6 -6
- package/dist/vue/components/MulmoScriptToolbar.vue.d.ts +5 -5
- package/dist/vue/viewTypes.d.ts +2 -2
- package/dist/vue/viewTypes.d.ts.map +1 -1
- package/dist/vue.cjs +4 -4
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +4 -4
- package/dist/vue.js.map +1 -1
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-DxKVCRQk.cjs","names":[],"sources":["../src/core/contract.ts"],"sourcesContent":["// Host-agnostic dispatch envelope for the presentMulmoScript View. The Vue\n// View is decoupled from any one host's REST surface: it calls\n// `useRuntime().dispatch({ kind, … })`, the host routes that to its\n// mulmoScript dispatch handler, and every response is an `{ ok: … }`\n// envelope so failures travel as data (no HTTP-status coupling, no\n// \"dispatch failed (500)\" prefixes in user-facing errors).\n//\n// Long-running generation (movie / PDF) is a single long-held dispatch that\n// resolves when the pipeline finishes; per-beat progress arrives on the\n// plugin pubsub channel (`GENERATION_EVENT`) instead of an SSE stream, which\n// also covers generations started elsewhere (background autoGenerateMovie,\n// another tab, the agent).\n\n/** One in-flight or per-beat generation notice, published on the plugin\n * pubsub `generation` channel and returned by the `pendingGenerations`\n * snapshot. Value strings mirror @mulmobridge/protocol's GENERATION_KINDS\n * so MulmoClaude's host bridge maps 1:1 without a lookup table. */\nexport interface MulmoScriptGenerationEvent {\n kind: \"beatImage\" | \"beatAudio\" | \"characterImage\" | \"movie\" | \"pdf\";\n /** Wire `stories/…` path of the script the generation belongs to. */\n filePath: string;\n /** beatIndex (as string) for beat*, character key for characterImage, \"\" for movie/pdf. */\n key: string;\n /** false = started, true = finished (reload the asset off disk). */\n done: boolean;\n /** Only set on done=true when the work failed. */\n error?: string;\n}\n\n/** Plugin pubsub event name the host publishes generation events on\n * (full channel: `plugin:<scope>:generation`). */\nexport const GENERATION_EVENT = \"generation\";\n\ninterface BeatRef {\n filePath: string;\n beatIndex: number;\n}\n\ninterface CharacterRef {\n filePath: string;\n key: string;\n}\n\n/** Session tag for hosts that surface per-session generation indicators\n * (MulmoClaude's sidebar). Optional everywhere; hosts without sessions\n * ignore it. */\ninterface SessionTag {\n chatSessionId?: string;\n}\n\nexport type MulmoScriptDispatchArgs =\n | ({ kind: \"save\" } & { filePath?: string; script?: unknown; filename?: string })\n | { kind: \"updateBeat\"; filePath: string; beatIndex: number; beat: unknown }\n | { kind: \"updateScript\"; filePath: string; script: unknown }\n | ({ kind: \"beatImage\" } & BeatRef)\n | ({ kind: \"beatAudio\" } & BeatRef)\n | ({ kind: \"beatMovie\" } & BeatRef)\n | ({ kind: \"renderBeat\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"generateBeatAudio\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadBeatImage\" } & BeatRef & { imageData: string })\n | ({ kind: \"characterImage\" } & CharacterRef)\n | ({ kind: \"renderCharacter\" } & CharacterRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadCharacterImage\" } & CharacterRef & { imageData: string })\n | ({ kind: \"movieStatus\" } & { filePath: string })\n | ({ kind: \"pdfStatus\" } & { filePath: string })\n | ({ kind: \"generateMovie\" } & { filePath: string } & SessionTag)\n | ({ kind: \"generatePdf\" } & { filePath: string } & SessionTag)\n | { kind: \"pendingGenerations\"; filePath: string };\n\nexport type MulmoScriptDispatchKind = MulmoScriptDispatchArgs[\"kind\"];\n\n/** Failure half of every dispatch response. `code` mirrors the phase-1\n * outcome codes so a host can log/telemetry on it; the View only reads\n * `error`. */\nexport interface DispatchFailure {\n ok: false;\n code?: \"bad_request\" | \"not_found\" | \"server_error\";\n error: string;\n}\n\nexport type DispatchEnvelope<T> = ({ ok: true } & T) | DispatchFailure;\n\n/** Maps a dispatch `kind` to its success payload so the View's transport\n * can call `dispatch` without casts at every site. */\nexport interface MulmoScriptDispatchResult {\n save: { script: Record<string, unknown>; filePath: string; message: string };\n updateBeat: Record<string, never>;\n updateScript: Record<string, never>;\n beatImage: { image: string | null };\n beatAudio: { audio: string | null };\n beatMovie: { moviePath: string | null };\n renderBeat: { image: string };\n generateBeatAudio: { audio: string };\n uploadBeatImage: { image: string };\n characterImage: { image: string | null };\n renderCharacter: { image: string };\n uploadCharacterImage: { image: string };\n movieStatus: { moviePath: string | null };\n pdfStatus: { pdfPath: string | null };\n generateMovie: { moviePath: string };\n generatePdf: { pdfPath: string };\n pendingGenerations: { pending: MulmoScriptGenerationEvent[] };\n}\n"],"mappings":";;;AA+BA,IAAa,mBAAmB"}
|
|
1
|
+
{"version":3,"file":"contract-DxKVCRQk.cjs","names":[],"sources":["../src/core/contract.ts"],"sourcesContent":["// Host-agnostic dispatch envelope for the presentMulmoScript View. The Vue\n// View is decoupled from any one host's REST surface: it calls\n// `useRuntime().dispatch({ kind, … })`, the host routes that to its\n// mulmoScript dispatch handler, and every response is an `{ ok: … }`\n// envelope so failures travel as data (no HTTP-status coupling, no\n// \"dispatch failed (500)\" prefixes in user-facing errors).\n//\n// Long-running generation (movie / PDF) is a single long-held dispatch that\n// resolves when the pipeline finishes; per-beat progress arrives on the\n// plugin pubsub channel (`GENERATION_EVENT`) instead of an SSE stream, which\n// also covers generations started elsewhere (background autoGenerateMovie,\n// another tab, the agent).\n\n/** One in-flight or per-beat generation notice, published on the plugin\n * pubsub `generation` channel and returned by the `pendingGenerations`\n * snapshot. Value strings mirror @mulmobridge/protocol's GENERATION_KINDS\n * so MulmoClaude's host bridge maps 1:1 without a lookup table. */\nexport interface MulmoScriptGenerationEvent {\n kind: \"beatImage\" | \"beatAudio\" | \"characterImage\" | \"movie\" | \"pdf\";\n /** Wire `stories/…` path of the script the generation belongs to. */\n filePath: string;\n /** beatIndex (as string) for beat*, character key for characterImage, \"\" for movie/pdf. */\n key: string;\n /** false = started, true = finished (reload the asset off disk). */\n done: boolean;\n /** Only set on done=true when the work failed. */\n error?: string;\n}\n\n/** Plugin pubsub event name the host publishes generation events on\n * (full channel: `plugin:<scope>:generation`). */\nexport const GENERATION_EVENT = \"generation\";\n\ninterface BeatRef {\n filePath: string;\n beatIndex: number;\n}\n\ninterface CharacterRef {\n filePath: string;\n key: string;\n}\n\n/** Session tag for hosts that surface per-session generation indicators\n * (MulmoClaude's sidebar). Optional everywhere; hosts without sessions\n * ignore it. */\ninterface SessionTag {\n chatSessionId?: string | undefined;\n}\n\nexport type MulmoScriptDispatchArgs =\n | ({ kind: \"save\" } & { filePath?: string; script?: unknown; filename?: string })\n | { kind: \"updateBeat\"; filePath: string; beatIndex: number; beat: unknown }\n | { kind: \"updateScript\"; filePath: string; script: unknown }\n | ({ kind: \"beatImage\" } & BeatRef)\n | ({ kind: \"beatAudio\" } & BeatRef)\n | ({ kind: \"beatMovie\" } & BeatRef)\n | ({ kind: \"renderBeat\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"generateBeatAudio\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadBeatImage\" } & BeatRef & { imageData: string })\n | ({ kind: \"characterImage\" } & CharacterRef)\n | ({ kind: \"renderCharacter\" } & CharacterRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadCharacterImage\" } & CharacterRef & { imageData: string })\n | ({ kind: \"movieStatus\" } & { filePath: string })\n | ({ kind: \"pdfStatus\" } & { filePath: string })\n | ({ kind: \"generateMovie\" } & { filePath: string } & SessionTag)\n | ({ kind: \"generatePdf\" } & { filePath: string } & SessionTag)\n | { kind: \"pendingGenerations\"; filePath: string };\n\nexport type MulmoScriptDispatchKind = MulmoScriptDispatchArgs[\"kind\"];\n\n/** Failure half of every dispatch response. `code` mirrors the phase-1\n * outcome codes so a host can log/telemetry on it; the View only reads\n * `error`. */\nexport interface DispatchFailure {\n ok: false;\n code?: \"bad_request\" | \"not_found\" | \"server_error\";\n error: string;\n}\n\nexport type DispatchEnvelope<T> = ({ ok: true } & T) | DispatchFailure;\n\n/** Maps a dispatch `kind` to its success payload so the View's transport\n * can call `dispatch` without casts at every site. */\nexport interface MulmoScriptDispatchResult {\n save: { script: Record<string, unknown>; filePath: string; message: string };\n updateBeat: Record<string, never>;\n updateScript: Record<string, never>;\n beatImage: { image: string | null };\n beatAudio: { audio: string | null };\n beatMovie: { moviePath: string | null };\n renderBeat: { image: string };\n generateBeatAudio: { audio: string };\n uploadBeatImage: { image: string };\n characterImage: { image: string | null };\n renderCharacter: { image: string };\n uploadCharacterImage: { image: string };\n movieStatus: { moviePath: string | null };\n pdfStatus: { pdfPath: string | null };\n generateMovie: { moviePath: string };\n generatePdf: { pdfPath: string };\n pendingGenerations: { pending: MulmoScriptGenerationEvent[] };\n}\n"],"mappings":";;;AA+BA,IAAa,mBAAmB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-vY0niHkh.js","names":[],"sources":["../src/core/contract.ts"],"sourcesContent":["// Host-agnostic dispatch envelope for the presentMulmoScript View. The Vue\n// View is decoupled from any one host's REST surface: it calls\n// `useRuntime().dispatch({ kind, … })`, the host routes that to its\n// mulmoScript dispatch handler, and every response is an `{ ok: … }`\n// envelope so failures travel as data (no HTTP-status coupling, no\n// \"dispatch failed (500)\" prefixes in user-facing errors).\n//\n// Long-running generation (movie / PDF) is a single long-held dispatch that\n// resolves when the pipeline finishes; per-beat progress arrives on the\n// plugin pubsub channel (`GENERATION_EVENT`) instead of an SSE stream, which\n// also covers generations started elsewhere (background autoGenerateMovie,\n// another tab, the agent).\n\n/** One in-flight or per-beat generation notice, published on the plugin\n * pubsub `generation` channel and returned by the `pendingGenerations`\n * snapshot. Value strings mirror @mulmobridge/protocol's GENERATION_KINDS\n * so MulmoClaude's host bridge maps 1:1 without a lookup table. */\nexport interface MulmoScriptGenerationEvent {\n kind: \"beatImage\" | \"beatAudio\" | \"characterImage\" | \"movie\" | \"pdf\";\n /** Wire `stories/…` path of the script the generation belongs to. */\n filePath: string;\n /** beatIndex (as string) for beat*, character key for characterImage, \"\" for movie/pdf. */\n key: string;\n /** false = started, true = finished (reload the asset off disk). */\n done: boolean;\n /** Only set on done=true when the work failed. */\n error?: string;\n}\n\n/** Plugin pubsub event name the host publishes generation events on\n * (full channel: `plugin:<scope>:generation`). */\nexport const GENERATION_EVENT = \"generation\";\n\ninterface BeatRef {\n filePath: string;\n beatIndex: number;\n}\n\ninterface CharacterRef {\n filePath: string;\n key: string;\n}\n\n/** Session tag for hosts that surface per-session generation indicators\n * (MulmoClaude's sidebar). Optional everywhere; hosts without sessions\n * ignore it. */\ninterface SessionTag {\n chatSessionId?: string;\n}\n\nexport type MulmoScriptDispatchArgs =\n | ({ kind: \"save\" } & { filePath?: string; script?: unknown; filename?: string })\n | { kind: \"updateBeat\"; filePath: string; beatIndex: number; beat: unknown }\n | { kind: \"updateScript\"; filePath: string; script: unknown }\n | ({ kind: \"beatImage\" } & BeatRef)\n | ({ kind: \"beatAudio\" } & BeatRef)\n | ({ kind: \"beatMovie\" } & BeatRef)\n | ({ kind: \"renderBeat\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"generateBeatAudio\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadBeatImage\" } & BeatRef & { imageData: string })\n | ({ kind: \"characterImage\" } & CharacterRef)\n | ({ kind: \"renderCharacter\" } & CharacterRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadCharacterImage\" } & CharacterRef & { imageData: string })\n | ({ kind: \"movieStatus\" } & { filePath: string })\n | ({ kind: \"pdfStatus\" } & { filePath: string })\n | ({ kind: \"generateMovie\" } & { filePath: string } & SessionTag)\n | ({ kind: \"generatePdf\" } & { filePath: string } & SessionTag)\n | { kind: \"pendingGenerations\"; filePath: string };\n\nexport type MulmoScriptDispatchKind = MulmoScriptDispatchArgs[\"kind\"];\n\n/** Failure half of every dispatch response. `code` mirrors the phase-1\n * outcome codes so a host can log/telemetry on it; the View only reads\n * `error`. */\nexport interface DispatchFailure {\n ok: false;\n code?: \"bad_request\" | \"not_found\" | \"server_error\";\n error: string;\n}\n\nexport type DispatchEnvelope<T> = ({ ok: true } & T) | DispatchFailure;\n\n/** Maps a dispatch `kind` to its success payload so the View's transport\n * can call `dispatch` without casts at every site. */\nexport interface MulmoScriptDispatchResult {\n save: { script: Record<string, unknown>; filePath: string; message: string };\n updateBeat: Record<string, never>;\n updateScript: Record<string, never>;\n beatImage: { image: string | null };\n beatAudio: { audio: string | null };\n beatMovie: { moviePath: string | null };\n renderBeat: { image: string };\n generateBeatAudio: { audio: string };\n uploadBeatImage: { image: string };\n characterImage: { image: string | null };\n renderCharacter: { image: string };\n uploadCharacterImage: { image: string };\n movieStatus: { moviePath: string | null };\n pdfStatus: { pdfPath: string | null };\n generateMovie: { moviePath: string };\n generatePdf: { pdfPath: string };\n pendingGenerations: { pending: MulmoScriptGenerationEvent[] };\n}\n"],"mappings":";;;AA+BA,IAAa,mBAAmB"}
|
|
1
|
+
{"version":3,"file":"contract-vY0niHkh.js","names":[],"sources":["../src/core/contract.ts"],"sourcesContent":["// Host-agnostic dispatch envelope for the presentMulmoScript View. The Vue\n// View is decoupled from any one host's REST surface: it calls\n// `useRuntime().dispatch({ kind, … })`, the host routes that to its\n// mulmoScript dispatch handler, and every response is an `{ ok: … }`\n// envelope so failures travel as data (no HTTP-status coupling, no\n// \"dispatch failed (500)\" prefixes in user-facing errors).\n//\n// Long-running generation (movie / PDF) is a single long-held dispatch that\n// resolves when the pipeline finishes; per-beat progress arrives on the\n// plugin pubsub channel (`GENERATION_EVENT`) instead of an SSE stream, which\n// also covers generations started elsewhere (background autoGenerateMovie,\n// another tab, the agent).\n\n/** One in-flight or per-beat generation notice, published on the plugin\n * pubsub `generation` channel and returned by the `pendingGenerations`\n * snapshot. Value strings mirror @mulmobridge/protocol's GENERATION_KINDS\n * so MulmoClaude's host bridge maps 1:1 without a lookup table. */\nexport interface MulmoScriptGenerationEvent {\n kind: \"beatImage\" | \"beatAudio\" | \"characterImage\" | \"movie\" | \"pdf\";\n /** Wire `stories/…` path of the script the generation belongs to. */\n filePath: string;\n /** beatIndex (as string) for beat*, character key for characterImage, \"\" for movie/pdf. */\n key: string;\n /** false = started, true = finished (reload the asset off disk). */\n done: boolean;\n /** Only set on done=true when the work failed. */\n error?: string;\n}\n\n/** Plugin pubsub event name the host publishes generation events on\n * (full channel: `plugin:<scope>:generation`). */\nexport const GENERATION_EVENT = \"generation\";\n\ninterface BeatRef {\n filePath: string;\n beatIndex: number;\n}\n\ninterface CharacterRef {\n filePath: string;\n key: string;\n}\n\n/** Session tag for hosts that surface per-session generation indicators\n * (MulmoClaude's sidebar). Optional everywhere; hosts without sessions\n * ignore it. */\ninterface SessionTag {\n chatSessionId?: string | undefined;\n}\n\nexport type MulmoScriptDispatchArgs =\n | ({ kind: \"save\" } & { filePath?: string; script?: unknown; filename?: string })\n | { kind: \"updateBeat\"; filePath: string; beatIndex: number; beat: unknown }\n | { kind: \"updateScript\"; filePath: string; script: unknown }\n | ({ kind: \"beatImage\" } & BeatRef)\n | ({ kind: \"beatAudio\" } & BeatRef)\n | ({ kind: \"beatMovie\" } & BeatRef)\n | ({ kind: \"renderBeat\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"generateBeatAudio\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadBeatImage\" } & BeatRef & { imageData: string })\n | ({ kind: \"characterImage\" } & CharacterRef)\n | ({ kind: \"renderCharacter\" } & CharacterRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadCharacterImage\" } & CharacterRef & { imageData: string })\n | ({ kind: \"movieStatus\" } & { filePath: string })\n | ({ kind: \"pdfStatus\" } & { filePath: string })\n | ({ kind: \"generateMovie\" } & { filePath: string } & SessionTag)\n | ({ kind: \"generatePdf\" } & { filePath: string } & SessionTag)\n | { kind: \"pendingGenerations\"; filePath: string };\n\nexport type MulmoScriptDispatchKind = MulmoScriptDispatchArgs[\"kind\"];\n\n/** Failure half of every dispatch response. `code` mirrors the phase-1\n * outcome codes so a host can log/telemetry on it; the View only reads\n * `error`. */\nexport interface DispatchFailure {\n ok: false;\n code?: \"bad_request\" | \"not_found\" | \"server_error\";\n error: string;\n}\n\nexport type DispatchEnvelope<T> = ({ ok: true } & T) | DispatchFailure;\n\n/** Maps a dispatch `kind` to its success payload so the View's transport\n * can call `dispatch` without casts at every site. */\nexport interface MulmoScriptDispatchResult {\n save: { script: Record<string, unknown>; filePath: string; message: string };\n updateBeat: Record<string, never>;\n updateScript: Record<string, never>;\n beatImage: { image: string | null };\n beatAudio: { audio: string | null };\n beatMovie: { moviePath: string | null };\n renderBeat: { image: string };\n generateBeatAudio: { audio: string };\n uploadBeatImage: { image: string };\n characterImage: { image: string | null };\n renderCharacter: { image: string };\n uploadCharacterImage: { image: string };\n movieStatus: { moviePath: string | null };\n pdfStatus: { pdfPath: string | null };\n generateMovie: { moviePath: string };\n generatePdf: { pdfPath: string };\n pendingGenerations: { pending: MulmoScriptGenerationEvent[] };\n}\n"],"mappings":";;;AA+BA,IAAa,mBAAmB"}
|
package/dist/core/contract.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ interface CharacterRef {
|
|
|
28
28
|
* (MulmoClaude's sidebar). Optional everywhere; hosts without sessions
|
|
29
29
|
* ignore it. */
|
|
30
30
|
interface SessionTag {
|
|
31
|
-
chatSessionId?: string;
|
|
31
|
+
chatSessionId?: string | undefined;
|
|
32
32
|
}
|
|
33
33
|
export type MulmoScriptDispatchArgs = ({
|
|
34
34
|
kind: "save";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/core/contract.ts"],"names":[],"mappings":"AAaA;;;oEAGoE;AACpE,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,WAAW,GAAG,WAAW,GAAG,gBAAgB,GAAG,OAAO,GAAG,KAAK,CAAC;IACrE,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,2FAA2F;IAC3F,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAC;IACd,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;mDACmD;AACnD,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAE7C,UAAU,OAAO;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,YAAY;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;iBAEiB;AACjB,UAAU,UAAU;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/core/contract.ts"],"names":[],"mappings":"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"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -6,9 +6,9 @@ import type { MulmoScript } from "@mulmocast/types";
|
|
|
6
6
|
* by hosts that have a movie backend (the package core ignores it). */
|
|
7
7
|
export interface SaveMulmoScriptArgs {
|
|
8
8
|
script?: unknown;
|
|
9
|
-
filename?: string;
|
|
10
|
-
filePath?: string;
|
|
11
|
-
autoGenerateMovie?: boolean;
|
|
9
|
+
filename?: string | undefined;
|
|
10
|
+
filePath?: string | undefined;
|
|
11
|
+
autoGenerateMovie?: boolean | undefined;
|
|
12
12
|
}
|
|
13
13
|
/** Result payload that drives the View. `filePath` is the historical
|
|
14
14
|
* `stories/<name>.json` wire form every mulmoScript endpoint keys on. */
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;wEAGwE;AACxE,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;wEAGwE;AACxE,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,iBAAiB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC;AAED;0EAC0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;iEAIiE;AACjE,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAC/B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-BLp8tSYt.cjs","names":["isRecord"],"sources":["../src/core/definition.ts","../../../common/dist/index.js","../src/core/paths.ts","../src/core/validate.ts","../src/core/plugin.ts"],"sourcesContent":["import type { ToolDefinition } from \"gui-chat-protocol\";\n\nexport const TOOL_NAME = \"presentMulmoScript\";\n\n// Single source of truth for the presentMulmoScript tool schema, shared by\n// MulmoClaude (host built-in shim re-exports this) and MulmoTerminal.\n// (Extracted byte-identical from the host definition; evolves here with the\n// package version.)\nexport const TOOL_DEFINITION: ToolDefinition = {\n type: \"function\",\n name: TOOL_NAME,\n description: `Save and present a MulmoScript story or presentation as a visual storyboard in the canvas.\n\nTwo modes — provide EXACTLY ONE of \\`script\\` or \\`filePath\\`:\n\n1. **Create new** — pass \\`script\\` (full MulmoScript JSON). Server saves it to disk and presents it.\n2. **Re-display existing** — pass \\`filePath\\` (the \\`filePath\\` returned by a previous call, e.g. \"stories/my-story-1700000000000.json\"; it is resolved against the workspace's \\`artifacts/\\` directory, NOT the workspace root). Much cheaper than re-sending the full script. Use whenever the user wants to revisit a presentation that was already created in this workspace.\n\nOptional \\`autoGenerateMovie: true\\` kicks off movie generation in the background, so the final video is ready by the time the user opens the canvas. Movie generation is expensive (multiple image + audio API calls + video encoding) — only set this when the user has explicitly asked for the movie. Default \\`false\\`.\n\nProvider rules for new scripts:\n- \\`speechParams.speakers.<name>.provider\\`: \\`\"gemini\"\\` — pairs with Gemini voices like \\`\"Kore\"\\`, \\`\"Aoede\"\\`, \\`\"Puck\"\\`. Do NOT use \\`\"google\"\\` here — that routes to Google Cloud TTS, where Gemini-class voices fail with \"This voice requires a model name to be specified.\" unless an explicit \\`model\\` is set.\n- \\`imageParams.provider\\`: \\`\"google\"\\`\n- \\`movieParams.provider\\`: \\`\"google\"\\`\n- Do NOT add a top-level \\`provider\\` field to \\`speechParams\\` — provider belongs per-speaker only.\n\nRequired structure:\n\n{\n \"$mulmocast\": { \"version\": \"1.1\" },\n \"title\": \"The Life of a Star\",\n \"description\": \"A short educational explainer about stellar evolution\",\n \"lang\": \"en\",\n \"speechParams\": {\n \"speakers\": {\n \"Presenter\": {\n \"provider\": \"gemini\",\n \"voiceId\": \"Kore\",\n \"displayName\": { \"en\": \"Presenter\" }\n }\n }\n },\n \"imageParams\": { \"provider\": \"google\", \"model\": \"gemini-3.1-flash-image-preview\" },\n \"movieParams\": { \"provider\": \"google\", \"model\": \"veo-3.1-generate\" },\n \"beats\": [\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Narration spoken aloud for this beat.\",\n \"imagePrompt\": \"Detailed description — AI generates the image\"\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Bullet point beat.\",\n \"image\": { \"type\": \"textSlide\", \"slide\": { \"title\": \"Slide Title\", \"bullets\": [\"Point one\", \"Point two\"] } }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Markdown beat.\",\n \"image\": { \"type\": \"markdown\", \"markdown\": \"## Heading\\\\n\\\\nBody text here.\" }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Chart beat — use for data, comparisons, trends.\",\n \"image\": { \"type\": \"chart\", \"title\": \"Chart Title\", \"chartData\": { \"type\": \"bar\", \"data\": { \"labels\": [\"A\", \"B\", \"C\"], \"datasets\": [{ \"label\": \"Series\", \"data\": [10, 20, 30] }] } } }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Diagram beat — use for flows, architectures, relationships.\",\n \"image\": { \"type\": \"mermaid\", \"title\": \"Diagram Title\", \"code\": { \"kind\": \"text\", \"text\": \"graph TD\\\\n A[Start] --> B[Process] --> C[End]\" } }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Rich interactive beat — use for custom layouts, animations, or anything that benefits from HTML/CSS.\",\n \"image\": { \"type\": \"html_tailwind\", \"html\": \"<div class=\\\\\"flex items-center justify-center h-full text-4xl font-bold text-blue-600\\\\\">Hello World</div>\" }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"AI video beat.\",\n \"moviePrompt\": \"Detailed description — AI generates the video clip\"\n }\n ]\n}\n\nBeat visual options (choose one per beat):\n- \"imagePrompt\": \"...\" → top-level string field — AI generates an image from the prompt\n- \"moviePrompt\": \"...\" → top-level string field — AI generates a video clip from the prompt\n- \"image\": { \"type\": \"textSlide\", \"slide\": { \"title\", \"subtitle\"?, \"bullets\"? } }\n- \"image\": { \"type\": \"markdown\", \"markdown\": \"...\" }\n- \"image\": { \"type\": \"chart\", \"title\": \"...\", \"chartData\": { \"type\": \"bar\"|\"line\"|\"pie\"|..., \"data\": { \"labels\": [...], \"datasets\": [...] } } } ← PREFER for data/numbers/comparisons. chartData is a full Chart.js config: labels/datasets go under \"data\", not at the top level.\n- \"image\": { \"type\": \"mermaid\", \"title\": \"...\", \"code\": { \"kind\": \"text\", \"text\": \"...\" } } ← PREFER for flows/diagrams/relationships\n- \"image\": { \"type\": \"html_tailwind\", \"html\": \"...\", \"script\"?: \"...\" } ← PREFER for rich layouts, animations, custom visuals\n\nIMPORTANT: \"imagePrompt\" and \"moviePrompt\" are plain string fields on the beat, NOT nested under \"image\".`,\n parameters: {\n type: \"object\",\n properties: {\n script: {\n type: \"object\",\n description:\n \"Complete MulmoScript JSON for a NEW presentation. Must include $mulmocast, speechParams, imageParams, movieParams, and beats array. Always populate the top-level 'description' field with a concise 1–2 sentence summary of the presentation. Do NOT pass alongside `filePath`.\",\n additionalProperties: true,\n },\n filename: {\n type: \"string\",\n description:\n \"Optional filename without extension. Defaults to a slug of the script title. Only meaningful with `script`; ignored when `filePath` is given.\",\n },\n filePath: {\n type: \"string\",\n description:\n \"Path of an EXISTING MulmoScript JSON file, as returned by a previous call (e.g. 'stories/my-story-1700000000000.json'). Resolved against the workspace's `artifacts/` directory, not the workspace root ('artifacts/stories/…' is also accepted). Use this to re-display a script previously saved in this workspace, instead of resending the full JSON. Do NOT pass alongside `script`.\",\n },\n autoGenerateMovie: {\n type: \"boolean\",\n description:\n \"When true, the server starts movie generation in the background after save/load. The user does NOT need to open the canvas — progress streams via the existing session channel. Default false. Only set true when the user has explicitly asked for the movie; generation is expensive.\",\n },\n },\n required: [],\n },\n};\n","// General-purpose runtime type guards, shared across the MulmoClaude host,\n// bridges, and plugins. This is a leaf package — pure and dependency-free — so\n// any tier can import it without creating an uphill edge.\n//\n// These originated as `server/utils/types.ts` (#504), which centralised 40+\n// hand-written inline `typeof x === \"object\"` checks. They are promoted here so\n// the same guards stop being re-hand-written in every bridge and plugin too.\n/** Narrow `unknown` to a plain object (not null, not array). */\nexport function isRecord(value) {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n/** Narrow `unknown` to any object (not null, arrays allowed).\n * Use `isRecord` when you need to access string keys. */\nexport function isObj(value) {\n return typeof value === \"object\" && value !== null;\n}\n/** Non-empty string after trimming whitespace. */\nexport function isNonEmptyString(value) {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n/** Record whose values are all strings. */\nexport function isStringRecord(value) {\n if (!isRecord(value))\n return false;\n return Object.values(value).every((val) => typeof val === \"string\");\n}\n/** String array (every element is a string). */\nexport function isStringArray(value) {\n return Array.isArray(value) && value.every((val) => typeof val === \"string\");\n}\n/** An array of unknowns. Prefer this over a bare `Array.isArray` in typed code:\n * `Array.isArray(x: unknown)` narrows to `any[]`, silently reintroducing\n * `any`, whereas this keeps the element type `unknown`. */\nexport function isUnknownArray(value) {\n return Array.isArray(value);\n}\n/** Error-like object with a `code` property (e.g. Node.js fs errors). */\nexport function isErrorWithCode(value) {\n return isRecord(value) && typeof value.code === \"string\";\n}\n/** Check that a record has a specific key with a string value. */\nexport function hasStringProp(value, key) {\n return isRecord(value) && typeof value[key] === \"string\";\n}\n/** Check that a record has a specific key with a number value. */\nexport function hasNumberProp(value, key) {\n return isRecord(value) && typeof value[key] === \"number\";\n}\n/** Split a comma-separated env value into trimmed, non-empty entries.\n * `lowercase` folds case for identifiers compared case-insensitively\n * (JIDs, email addresses, hex pubkeys). Absent/empty input → empty list. */\nexport function parseCsvList(raw, opts) {\n return (raw ?? \"\")\n .split(\",\")\n .map((entry) => (opts?.lowercase ? entry.trim().toLowerCase() : entry.trim()))\n .filter(Boolean);\n}\n/** A comma-separated env value as a Set — the canonical allowlist shape,\n * where an empty set is the \"allow all\" sentinel (`set.size === 0`). */\nexport function parseCsvSet(raw, opts) {\n return new Set(parseCsvList(raw, opts));\n}\n/** Normalise an unknown thrown value into a human-readable string. Isomorphic\n * (host, bridges, plugins, Vue) — this is the single home for the helper that\n * #2217 could only consolidate for server code, since `@mulmoclaude/core/utils`\n * is server-only.\n *\n * A non-Error object with a non-empty string `details` (gRPC convention) or\n * `message` field surfaces that field — `details` wins — instead of the\n * `[object Object]` a bare `String(err)` would print; an empty-string field\n * falls through. `fallback` covers the error-boundary idiom where a thrown\n * non-Error should read as a descriptive message rather than `String(err)`\n * noise; omit it in logging contexts where `String(err)` is fine. */\nexport function errorMessage(err, fallback) {\n if (err instanceof Error)\n return err.message;\n if (hasStringProp(err, \"details\") && err.details)\n return err.details;\n if (hasStringProp(err, \"message\") && err.message)\n return err.message;\n if (fallback !== undefined)\n return fallback;\n return String(err);\n}\n/** `Date` → `YYYY-MM-DD` in UTC — for dates that must not shift with the\n * host's local timezone (tool-trace search dirs, API date keys). Isomorphic\n * single source (#2480): the host re-exports it from `server/utils/date.ts`,\n * x-plugin imports it directly. The `@receptron/task-scheduler` copy stays\n * local on purpose — that leaf package is published independently and kept\n * dependency-free. Wall-clock questions use the host's `toLocalIsoDate`. */\nexport function toUtcIsoDate(timestamp) {\n const year = timestamp.getUTCFullYear();\n const month = String(timestamp.getUTCMonth() + 1).padStart(2, \"0\");\n const day = String(timestamp.getUTCDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n// A Map, not an object literal: `{}[char]` reads through the prototype chain,\n// so a future caller widening the regex would silently get `[object Object]`\n// for keys like `constructor`.\nconst HTML_ESCAPES = new Map([\n [\"&\", \"&\"],\n [\"<\", \"<\"],\n [\">\", \">\"],\n ['\"', \""\"],\n [\"'\", \"'\"],\n]);\n/** HTML-escape text destined for markup or an attribute value — the fixed\n * five-character map, nothing more. Lives here rather than in\n * `@mulmoclaude/core/wiki` (#2483) because `@mulmoclaude/markdown-utils` is a\n * leaf that core depends on and so cannot import back up; core/wiki\n * re-exports this, keeping its consumers' import path unchanged.\n *\n * Escaping `&` first is what makes a single pass safe — the entities this\n * introduces contain none of the other four characters, so nothing is\n * double-escaped. Not a sanitiser: it neither strips tags nor validates URLs. */\nexport function escapeHtml(value) {\n return value.replace(/[&<>\"']/g, (char) => HTML_ESCAPES.get(char) ?? char);\n}\nexport { scanEnvOptions, snakeToLowerCamel } from \"./envScan.js\";\n","// Path helpers for MulmoScript story artifacts. The generic build primitives\n// (slug + the `\"\"`/`.`/`..` traversal guard) live in the shared, browser-safe\n// `@mulmoclaude/core/artifacts` (#2405); only the story-specific wire-path\n// rules stay here.\n//\n// Path model: the stories directory lives at `<workspace>/artifacts/stories`\n// and the FileOps scope root is `<workspace>/artifacts`, so the\n// FileOps-relative path and the historical `stories/<name>.json` wire form\n// (which every mulmoScript endpoint keys on) are the SAME string. Stories are\n// NOT `YYYY/MM`-partitioned, so `storyFilePath` opts out of partitioning.\n\nimport { ARTIFACTS_ROOT, buildArtifactRelPath, hasUnsafePathSegment, slugifyArtifact } from \"@mulmoclaude/core/artifacts\";\n\nconst STORIES_DIR = \"stories\";\nconst STORY_FALLBACK_SLUG = \"story\";\n\n/** Lowercase-hyphen slug, capped, leading/trailing hyphens stripped; falls back\n * to `fallback` for empty/undefined/non-ASCII input. */\nexport function slugify(title: string | undefined, fallback = STORY_FALLBACK_SLUG): string {\n return slugifyArtifact(title, fallback);\n}\n\n/** Build a fresh, collision-safe story path for a new script —\n * `stories/<slug>-<epoch-ms>.json`, valid as both the FileOps-relative\n * write path and the wire `filePath`. */\nexport function storyFilePath(slugSource: string, now: Date = new Date()): string {\n return buildArtifactRelPath({ dir: STORIES_DIR, title: slugSource, ext: \".json\", fallback: STORY_FALLBACK_SLUG, now, partitioned: false });\n}\n\n/**\n * Normalize a caller-supplied wire path to the canonical\n * `stories/<rel>` form, or null when it can't be trusted. Accepts the\n * canonical `stories/foo.json` convention, bare `foo.json` (the host route\n * historically allowed either), and the workspace-relative spelling\n * `artifacts/stories/foo.json` — the tool description called `filePath`\n * \"workspace-relative\" for a long time, so agents legitimately send it.\n * A leading `artifacts` segment is dropped only when `stories` follows;\n * a bare `artifacts/foo.json` keeps its historical meaning (a file named\n * `artifacts/foo.json` under the stories dir). Rejects absolute paths,\n * backslashes, and any empty / `.` / `..` segment — the lexical guard\n * before every `files.artifacts` read/write (FileOps re-checks\n * containment as defence-in-depth).\n */\nexport function normalizeStoryPath(filePath: string): string | null {\n if (filePath.length === 0 || filePath.includes(\"\\\\\")) return null;\n // Absolute POSIX path or Windows drive prefix.\n if (filePath.startsWith(\"/\") || /^[A-Za-z]:/.test(filePath)) return null;\n if (hasUnsafePathSegment(filePath)) return null;\n const segments = filePath.split(\"/\");\n const trimmed = segments[0] === ARTIFACTS_ROOT && segments[1] === STORIES_DIR ? segments.slice(1) : segments;\n const rest = trimmed[0] === STORIES_DIR ? trimmed.slice(1) : trimmed;\n if (rest.length === 0) return null;\n return [STORIES_DIR, ...rest].join(\"/\");\n}\n","// Body validators for the mulmoScript update endpoints, moved verbatim from\n// the host's `server/api/routes/mulmoScriptValidate.ts` so MulmoClaude and\n// MulmoTerminal share one definition of a \"valid script\" / \"valid beat\".\n//\n// The `@mulmocast/types` package exports zod schemas that mirror the\n// canonical MulmoScript / MulmoBeat shapes. The same schemas back client-side\n// edit-time validation in the presentMulmoScript View.\n\nimport { mulmoBeatSchema, mulmoScriptSchema } from \"@mulmocast/types\";\n\nexport type ValidationResult<T> = { ok: true; value: T } | { ok: false; error: string };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction formatZodIssues(\n // Zod's `$ZodIssue.path` is `PropertyKey[]` (includes `symbol`).\n // Accept the wider type so callers can pass `safeParse().error.issues`\n // directly; stringify any non-string/number segments at format time.\n issues: readonly { message: string; path: readonly PropertyKey[] }[],\n): string {\n if (issues.length === 0) return \"invalid shape\";\n const head = issues\n .slice(0, 3)\n .map((i) => {\n const pathStr = i.path.length > 0 ? i.path.map((seg) => String(seg)).join(\".\") : \"<root>\";\n return `${pathStr}: ${i.message}`;\n })\n .join(\"; \");\n return issues.length > 3 ? `${head} (+${issues.length - 3} more)` : head;\n}\n\n/** Shared prelude: require an object body carrying a non-empty\n * `filePath`. On success returns the narrowed record + filePath so the\n * caller reads the remaining fields without re-narrowing. */\nfunction validateFilePathBody(body: unknown): ValidationResult<{ record: Record<string, unknown>; filePath: string }> {\n if (!isRecord(body)) {\n return { ok: false, error: \"body must be an object\" };\n }\n if (typeof body.filePath !== \"string\" || body.filePath === \"\") {\n return { ok: false, error: \"filePath must be a non-empty string\" };\n }\n return { ok: true, value: { record: body, filePath: body.filePath } };\n}\n\n/**\n * Validate the `update-script` request body. Returns the parsed,\n * schema-conformant script on success, or a human-readable error\n * suitable for sending back as a 400 response.\n */\nexport function validateUpdateScriptBody(body: unknown): ValidationResult<{\n filePath: string;\n script: unknown;\n}> {\n const base = validateFilePathBody(body);\n if (!base.ok) return base;\n const { record, filePath } = base.value;\n if (record.script === undefined) {\n return { ok: false, error: \"script is required\" };\n }\n const parsed = mulmoScriptSchema.safeParse(record.script);\n if (!parsed.success) {\n return {\n ok: false,\n error: `invalid script: ${formatZodIssues(parsed.error.issues)}`,\n };\n }\n return {\n ok: true,\n value: { filePath, script: parsed.data },\n };\n}\n\n/**\n * Validate the `update-beat` request body. `beatIndex` is allowed\n * to be any non-negative integer; the handler still bounds-checks\n * against the actual script length after reading the file.\n */\nexport function validateUpdateBeatBody(body: unknown): ValidationResult<{\n filePath: string;\n beatIndex: number;\n beat: unknown;\n}> {\n const base = validateFilePathBody(body);\n if (!base.ok) return base;\n const { record, filePath } = base.value;\n const beatIndex = record.beatIndex;\n if (typeof beatIndex !== \"number\" || !Number.isInteger(beatIndex) || beatIndex < 0) {\n return { ok: false, error: \"beatIndex must be a non-negative integer\" };\n }\n if (record.beat === undefined) {\n return { ok: false, error: \"beat is required\" };\n }\n const parsed = mulmoBeatSchema.safeParse(record.beat);\n if (!parsed.success) {\n return {\n ok: false,\n error: `invalid beat: ${formatZodIssues(parsed.error.issues)}`,\n };\n }\n return {\n ok: true,\n value: {\n filePath,\n beatIndex,\n beat: parsed.data,\n },\n };\n}\n","import type { ToolPluginCore, ToolResult } from \"gui-chat-protocol\";\nimport { mulmoScriptSchema } from \"@mulmocast/types\";\nimport { errorMessage } from \"@mulmoclaude/common\";\nimport { TOOL_DEFINITION } from \"./definition\";\nimport { normalizeStoryPath, storyFilePath } from \"./paths\";\nimport { validateUpdateBeatBody, validateUpdateScriptBody } from \"./validate\";\nimport type { MulmoScriptData, MulmoScriptExecuteContext, SaveMulmoScriptArgs } from \"./types\";\n\n/** Failure half of every outcome below. `code` preserves the hosts'\n * HTTP contract (bad_request → 400, not_found → 404) without the package\n * knowing anything about HTTP. */\nexport interface MulmoScriptFailure {\n ok: false;\n code: \"bad_request\" | \"not_found\";\n error: string;\n}\n\nexport type SaveMulmoScriptOutcome = ({ ok: true; message: string } & MulmoScriptData) | MulmoScriptFailure;\n\nexport type UpdateMulmoScriptOutcome = { ok: true } | MulmoScriptFailure;\n\nfunction badRequest(error: string): MulmoScriptFailure {\n return { ok: false, code: \"bad_request\", error };\n}\n\nfunction notFound(error: string): MulmoScriptFailure {\n return { ok: false, code: \"not_found\", error };\n}\n\nfunction stringifyScript(script: unknown): string {\n // 2-space indent matches the hosts' writeJsonAtomic convention so a\n // package-written script diffs cleanly against host-written ones.\n return JSON.stringify(script, null, 2);\n}\n\n/** Persist a new, schema-validated script under a fresh `stories/…` path. */\nasync function saveNewScript(context: MulmoScriptExecuteContext, script: unknown, filename: string | undefined, now: Date): Promise<SaveMulmoScriptOutcome> {\n const validation = mulmoScriptSchema.safeParse(script);\n if (!validation.success) {\n return badRequest(\"script is not a valid MulmoScript\");\n }\n const validatedScript = validation.data;\n // slugify drops `/`, `\\`, and `..`, so a hostile `filename` like\n // \"../../etc/passwd\" can never escape the stories dir — defense in\n // depth on top of FileOps' own containment check.\n const slugSource = filename ? filename.replace(/\\.json$/i, \"\") : validatedScript.title || \"untitled\";\n const filePath = storyFilePath(slugSource, now);\n await context.files.artifacts.write(filePath, stringifyScript(validatedScript));\n return { ok: true, script: validatedScript, filePath, message: `Saved MulmoScript to ${filePath}` };\n}\n\n/** Re-open an existing script: containment guard, existence, JSON parse,\n * schema validation — same acceptance rules as the save path so a script\n * this package saved can never be one it later refuses to load. */\nasync function loadExistingScript(context: MulmoScriptExecuteContext, filePath: string): Promise<SaveMulmoScriptOutcome> {\n if (!filePath.toLowerCase().endsWith(\".json\")) {\n return badRequest(\"filePath must point to a .json file\");\n }\n const storyPath = normalizeStoryPath(filePath);\n if (!storyPath) {\n return badRequest(\"Invalid filePath\");\n }\n if (!(await context.files.artifacts.exists(storyPath))) {\n return notFound(`File not found: ${filePath}`);\n }\n const raw = await context.files.artifacts.read(storyPath);\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n return badRequest(`Invalid JSON: ${errorMessage(err)}`);\n }\n const validation = mulmoScriptSchema.safeParse(parsed);\n if (!validation.success) {\n return badRequest(\"File is not a valid MulmoScript\");\n }\n return { ok: true, script: validation.data, filePath: storyPath, message: `Loaded MulmoScript from ${storyPath}` };\n}\n\n/**\n * Unified save-or-reopen for the presentMulmoScript tool call. `script`\n * (create new) and `filePath` (existing) are mutually exclusive. Never\n * throws on bad input — validation failures come back as discriminated\n * failures so host routes stay thin adapters. `autoGenerateMovie` is NOT\n * handled here: movie generation needs host backends (mulmocast/ffmpeg),\n * so hosts that support it trigger it from the returned `filePath`.\n */\nexport async function executeMulmoScriptSave(\n context: MulmoScriptExecuteContext,\n args: SaveMulmoScriptArgs,\n now: Date = new Date(),\n): Promise<SaveMulmoScriptOutcome> {\n const { script, filename, filePath } = args ?? {};\n const hasScript = script !== undefined && script !== null;\n const hasFilePath = typeof filePath === \"string\" && filePath !== \"\";\n if (hasScript === hasFilePath) {\n return badRequest(\n hasScript ? \"Provide either `script` or `filePath`, not both.\" : \"Provide either `script` (new presentation) or `filePath` (existing presentation).\",\n );\n }\n return hasFilePath\n ? loadExistingScript(context, filePath as string)\n : saveNewScript(context, script, typeof filename === \"string\" ? filename : undefined, now);\n}\n\n/** Resolve + guard a wire path for the update endpoints. */\nasync function resolveExistingStory(context: MulmoScriptExecuteContext, filePath: string): Promise<{ storyPath: string } | MulmoScriptFailure> {\n const storyPath = normalizeStoryPath(filePath);\n if (!storyPath) return badRequest(\"Invalid filePath\");\n if (!(await context.files.artifacts.exists(storyPath))) {\n return notFound(`File not found: ${filePath}`);\n }\n return { storyPath };\n}\n\n/** Overwrite one beat of an existing script (the View's per-beat source\n * editor). Validates the body shape + beat schema, bounds-checks the index\n * against the script on disk, and writes the whole file back. */\nexport async function executeUpdateBeat(context: MulmoScriptExecuteContext, body: unknown): Promise<UpdateMulmoScriptOutcome> {\n const validation = validateUpdateBeatBody(body);\n if (!validation.ok) return badRequest(validation.error);\n const { filePath, beatIndex, beat } = validation.value;\n\n const resolved = await resolveExistingStory(context, filePath);\n if (\"ok\" in resolved) return resolved;\n\n let script: { beats?: unknown[] };\n try {\n script = JSON.parse(await context.files.artifacts.read(resolved.storyPath));\n } catch (err) {\n return badRequest(`Invalid JSON: ${errorMessage(err)}`);\n }\n if (!Array.isArray(script.beats) || beatIndex >= script.beats.length) {\n return badRequest(\"Invalid beatIndex\");\n }\n script.beats[beatIndex] = beat;\n await context.files.artifacts.write(resolved.storyPath, stringifyScript(script));\n return { ok: true };\n}\n\n/** Overwrite the whole script (the View's full-source editor / deck-editor\n * auto-save). The body's `script` is schema-validated by\n * `validateUpdateScriptBody` before the write. */\nexport async function executeUpdateScript(context: MulmoScriptExecuteContext, body: unknown): Promise<UpdateMulmoScriptOutcome> {\n const validation = validateUpdateScriptBody(body);\n if (!validation.ok) return badRequest(validation.error);\n const { filePath, script } = validation.value;\n\n const resolved = await resolveExistingStory(context, filePath);\n if (\"ok\" in resolved) return resolved;\n\n await context.files.artifacts.write(resolved.storyPath, stringifyScript(script));\n return { ok: true };\n}\n\n/** ToolResult-shaped wrapper over `executeMulmoScriptSave` for runtime hosts\n * (e.g. MulmoTerminal's package loader), where the tool call resolves to a\n * ToolResult rather than an HTTP response. Failures are narrate-only\n * (message, no data) so the agent can self-correct. */\nexport async function executeMulmoScript(context: MulmoScriptExecuteContext, args: SaveMulmoScriptArgs): Promise<ToolResult<MulmoScriptData>> {\n const outcome = await executeMulmoScriptSave(context, args);\n if (!outcome.ok) {\n return {\n message: outcome.error,\n instructions: \"Acknowledge the error and retry with a valid `script` (new) or an existing `filePath`.\",\n };\n }\n // The tool schema advertises `autoGenerateMovie`, but movie generation\n // needs host backends (mulmocast/ffmpeg) this generic execute path\n // doesn't have — MulmoClaude honours the flag in its own save route.\n // Say so in the result rather than silently dropping the option, so the\n // agent doesn't tell the user a movie is on its way.\n const ignoredMovieNote = args.autoGenerateMovie === true ? \" (autoGenerateMovie is not supported by this host and was ignored)\" : \"\";\n return {\n message: `${outcome.message}${ignoredMovieNote}`,\n data: { script: outcome.script, filePath: outcome.filePath },\n instructions: \"Display the storyboard to the user.\",\n };\n}\n\n/** Non-Vue plugin core for runtime hosts that register the package directly.\n * MulmoClaude consumes only TOOL_DEFINITION + the execute functions in its\n * own routes, so it doesn't use this. */\nexport const pluginCore: ToolPluginCore<MulmoScriptData, MulmoScriptData, SaveMulmoScriptArgs> = {\n toolDefinition: TOOL_DEFINITION,\n execute: executeMulmoScript as unknown as ToolPluginCore<MulmoScriptData, MulmoScriptData, SaveMulmoScriptArgs>[\"execute\"],\n generatingMessage: \"Generating MulmoScript storyboard…\",\n isEnabled: () => true,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin-BLp8tSYt.cjs","names":["isRecord"],"sources":["../src/core/definition.ts","../../../common/dist/index.js","../src/core/paths.ts","../src/core/validate.ts","../src/core/plugin.ts"],"sourcesContent":["import type { ToolDefinition } from \"gui-chat-protocol\";\n\nexport const TOOL_NAME = \"presentMulmoScript\";\n\n// Single source of truth for the presentMulmoScript tool schema, shared by\n// MulmoClaude (host built-in shim re-exports this) and MulmoTerminal.\n// (Extracted byte-identical from the host definition; evolves here with the\n// package version.)\nexport const TOOL_DEFINITION: ToolDefinition = {\n type: \"function\",\n name: TOOL_NAME,\n description: `Save and present a MulmoScript story or presentation as a visual storyboard in the canvas.\n\nTwo modes — provide EXACTLY ONE of \\`script\\` or \\`filePath\\`:\n\n1. **Create new** — pass \\`script\\` (full MulmoScript JSON). Server saves it to disk and presents it.\n2. **Re-display existing** — pass \\`filePath\\` (the \\`filePath\\` returned by a previous call, e.g. \"stories/my-story-1700000000000.json\"; it is resolved against the workspace's \\`artifacts/\\` directory, NOT the workspace root). Much cheaper than re-sending the full script. Use whenever the user wants to revisit a presentation that was already created in this workspace.\n\nOptional \\`autoGenerateMovie: true\\` kicks off movie generation in the background, so the final video is ready by the time the user opens the canvas. Movie generation is expensive (multiple image + audio API calls + video encoding) — only set this when the user has explicitly asked for the movie. Default \\`false\\`.\n\nProvider rules for new scripts:\n- \\`speechParams.speakers.<name>.provider\\`: \\`\"gemini\"\\` — pairs with Gemini voices like \\`\"Kore\"\\`, \\`\"Aoede\"\\`, \\`\"Puck\"\\`. Do NOT use \\`\"google\"\\` here — that routes to Google Cloud TTS, where Gemini-class voices fail with \"This voice requires a model name to be specified.\" unless an explicit \\`model\\` is set.\n- \\`imageParams.provider\\`: \\`\"google\"\\`\n- \\`movieParams.provider\\`: \\`\"google\"\\`\n- Do NOT add a top-level \\`provider\\` field to \\`speechParams\\` — provider belongs per-speaker only.\n\nRequired structure:\n\n{\n \"$mulmocast\": { \"version\": \"1.1\" },\n \"title\": \"The Life of a Star\",\n \"description\": \"A short educational explainer about stellar evolution\",\n \"lang\": \"en\",\n \"speechParams\": {\n \"speakers\": {\n \"Presenter\": {\n \"provider\": \"gemini\",\n \"voiceId\": \"Kore\",\n \"displayName\": { \"en\": \"Presenter\" }\n }\n }\n },\n \"imageParams\": { \"provider\": \"google\", \"model\": \"gemini-3.1-flash-image-preview\" },\n \"movieParams\": { \"provider\": \"google\", \"model\": \"veo-3.1-generate\" },\n \"beats\": [\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Narration spoken aloud for this beat.\",\n \"imagePrompt\": \"Detailed description — AI generates the image\"\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Bullet point beat.\",\n \"image\": { \"type\": \"textSlide\", \"slide\": { \"title\": \"Slide Title\", \"bullets\": [\"Point one\", \"Point two\"] } }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Markdown beat.\",\n \"image\": { \"type\": \"markdown\", \"markdown\": \"## Heading\\\\n\\\\nBody text here.\" }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Chart beat — use for data, comparisons, trends.\",\n \"image\": { \"type\": \"chart\", \"title\": \"Chart Title\", \"chartData\": { \"type\": \"bar\", \"data\": { \"labels\": [\"A\", \"B\", \"C\"], \"datasets\": [{ \"label\": \"Series\", \"data\": [10, 20, 30] }] } } }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Diagram beat — use for flows, architectures, relationships.\",\n \"image\": { \"type\": \"mermaid\", \"title\": \"Diagram Title\", \"code\": { \"kind\": \"text\", \"text\": \"graph TD\\\\n A[Start] --> B[Process] --> C[End]\" } }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Rich interactive beat — use for custom layouts, animations, or anything that benefits from HTML/CSS.\",\n \"image\": { \"type\": \"html_tailwind\", \"html\": \"<div class=\\\\\"flex items-center justify-center h-full text-4xl font-bold text-blue-600\\\\\">Hello World</div>\" }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"AI video beat.\",\n \"moviePrompt\": \"Detailed description — AI generates the video clip\"\n }\n ]\n}\n\nBeat visual options (choose one per beat):\n- \"imagePrompt\": \"...\" → top-level string field — AI generates an image from the prompt\n- \"moviePrompt\": \"...\" → top-level string field — AI generates a video clip from the prompt\n- \"image\": { \"type\": \"textSlide\", \"slide\": { \"title\", \"subtitle\"?, \"bullets\"? } }\n- \"image\": { \"type\": \"markdown\", \"markdown\": \"...\" }\n- \"image\": { \"type\": \"chart\", \"title\": \"...\", \"chartData\": { \"type\": \"bar\"|\"line\"|\"pie\"|..., \"data\": { \"labels\": [...], \"datasets\": [...] } } } ← PREFER for data/numbers/comparisons. chartData is a full Chart.js config: labels/datasets go under \"data\", not at the top level.\n- \"image\": { \"type\": \"mermaid\", \"title\": \"...\", \"code\": { \"kind\": \"text\", \"text\": \"...\" } } ← PREFER for flows/diagrams/relationships\n- \"image\": { \"type\": \"html_tailwind\", \"html\": \"...\", \"script\"?: \"...\" } ← PREFER for rich layouts, animations, custom visuals\n\nIMPORTANT: \"imagePrompt\" and \"moviePrompt\" are plain string fields on the beat, NOT nested under \"image\".`,\n parameters: {\n type: \"object\",\n properties: {\n script: {\n type: \"object\",\n description:\n \"Complete MulmoScript JSON for a NEW presentation. Must include $mulmocast, speechParams, imageParams, movieParams, and beats array. Always populate the top-level 'description' field with a concise 1–2 sentence summary of the presentation. Do NOT pass alongside `filePath`.\",\n additionalProperties: true,\n },\n filename: {\n type: \"string\",\n description:\n \"Optional filename without extension. Defaults to a slug of the script title. Only meaningful with `script`; ignored when `filePath` is given.\",\n },\n filePath: {\n type: \"string\",\n description:\n \"Path of an EXISTING MulmoScript JSON file, as returned by a previous call (e.g. 'stories/my-story-1700000000000.json'). Resolved against the workspace's `artifacts/` directory, not the workspace root ('artifacts/stories/…' is also accepted). Use this to re-display a script previously saved in this workspace, instead of resending the full JSON. Do NOT pass alongside `script`.\",\n },\n autoGenerateMovie: {\n type: \"boolean\",\n description:\n \"When true, the server starts movie generation in the background after save/load. The user does NOT need to open the canvas — progress streams via the existing session channel. Default false. Only set true when the user has explicitly asked for the movie; generation is expensive.\",\n },\n },\n required: [],\n },\n};\n","// General-purpose runtime type guards, shared across the MulmoClaude host,\n// bridges, and plugins. This is a leaf package — pure and dependency-free — so\n// any tier can import it without creating an uphill edge.\n//\n// These originated as `server/utils/types.ts` (#504), which centralised 40+\n// hand-written inline `typeof x === \"object\"` checks. They are promoted here so\n// the same guards stop being re-hand-written in every bridge and plugin too.\n/** Narrow `unknown` to a plain object (not null, not array). */\nexport function isRecord(value) {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n/** Narrow `unknown` to any object (not null, arrays allowed).\n * Use `isRecord` when you need to access string keys. */\nexport function isObj(value) {\n return typeof value === \"object\" && value !== null;\n}\n/** Non-empty string after trimming whitespace. */\nexport function isNonEmptyString(value) {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n/** Record whose values are all strings. */\nexport function isStringRecord(value) {\n if (!isRecord(value))\n return false;\n return Object.values(value).every((val) => typeof val === \"string\");\n}\n/** String array (every element is a string). */\nexport function isStringArray(value) {\n return Array.isArray(value) && value.every((val) => typeof val === \"string\");\n}\n/** An array of unknowns. Prefer this over a bare `Array.isArray` in typed code:\n * `Array.isArray(x: unknown)` narrows to `any[]`, silently reintroducing\n * `any`, whereas this keeps the element type `unknown`. */\nexport function isUnknownArray(value) {\n return Array.isArray(value);\n}\n/** Error-like object with a `code` property (e.g. Node.js fs errors). */\nexport function isErrorWithCode(value) {\n return isRecord(value) && typeof value.code === \"string\";\n}\n/** Check that a record has a specific key with a string value. */\nexport function hasStringProp(value, key) {\n return isRecord(value) && typeof value[key] === \"string\";\n}\n/** Check that a record has a specific key with a number value. */\nexport function hasNumberProp(value, key) {\n return isRecord(value) && typeof value[key] === \"number\";\n}\n/** Split a comma-separated env value into trimmed, non-empty entries.\n * `lowercase` folds case for identifiers compared case-insensitively\n * (JIDs, email addresses, hex pubkeys). Absent/empty input → empty list. */\nexport function parseCsvList(raw, opts) {\n return (raw ?? \"\")\n .split(\",\")\n .map((entry) => (opts?.lowercase ? entry.trim().toLowerCase() : entry.trim()))\n .filter(Boolean);\n}\n/** A comma-separated env value as a Set — the canonical allowlist shape,\n * where an empty set is the \"allow all\" sentinel (`set.size === 0`). */\nexport function parseCsvSet(raw, opts) {\n return new Set(parseCsvList(raw, opts));\n}\n/** Normalise an unknown thrown value into a human-readable string. Isomorphic\n * (host, bridges, plugins, Vue) — this is the single home for the helper that\n * #2217 could only consolidate for server code, since `@mulmoclaude/core/utils`\n * is server-only.\n *\n * A non-Error object with a non-empty string `details` (gRPC convention) or\n * `message` field surfaces that field — `details` wins — instead of the\n * `[object Object]` a bare `String(err)` would print; an empty-string field\n * falls through. `fallback` covers the error-boundary idiom where a thrown\n * non-Error should read as a descriptive message rather than `String(err)`\n * noise; omit it in logging contexts where `String(err)` is fine. */\nexport function errorMessage(err, fallback) {\n if (err instanceof Error)\n return err.message;\n if (hasStringProp(err, \"details\") && err.details)\n return err.details;\n if (hasStringProp(err, \"message\") && err.message)\n return err.message;\n if (fallback !== undefined)\n return fallback;\n return String(err);\n}\n/** `Date` → `YYYY-MM-DD` in UTC — for dates that must not shift with the\n * host's local timezone (tool-trace search dirs, API date keys). Isomorphic\n * single source (#2480): the host re-exports it from `server/utils/date.ts`,\n * x-plugin imports it directly. The `@receptron/task-scheduler` copy stays\n * local on purpose — that leaf package is published independently and kept\n * dependency-free. Wall-clock questions use the host's `toLocalIsoDate`. */\nexport function toUtcIsoDate(timestamp) {\n const year = timestamp.getUTCFullYear();\n const month = String(timestamp.getUTCMonth() + 1).padStart(2, \"0\");\n const day = String(timestamp.getUTCDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n// A Map, not an object literal: `{}[char]` reads through the prototype chain,\n// so a future caller widening the regex would silently get `[object Object]`\n// for keys like `constructor`.\nconst HTML_ESCAPES = new Map([\n [\"&\", \"&\"],\n [\"<\", \"<\"],\n [\">\", \">\"],\n ['\"', \""\"],\n [\"'\", \"'\"],\n]);\n/** HTML-escape text destined for markup or an attribute value — the fixed\n * five-character map, nothing more. Lives here rather than in\n * `@mulmoclaude/core/wiki` (#2483) because `@mulmoclaude/markdown-utils` is a\n * leaf that core depends on and so cannot import back up; core/wiki\n * re-exports this, keeping its consumers' import path unchanged.\n *\n * Escaping `&` first is what makes a single pass safe — the entities this\n * introduces contain none of the other four characters, so nothing is\n * double-escaped. Not a sanitiser: it neither strips tags nor validates URLs. */\nexport function escapeHtml(value) {\n return value.replace(/[&<>\"']/g, (char) => HTML_ESCAPES.get(char) ?? char);\n}\n/** Split a JWS compact serialization into its three segments, or `null` when the\n * token isn't well-formed. Pure string work, so the Node bridges and the\n * Cloudflare Workers relay — which decode the segments differently (`Buffer` vs\n * `atob`) — can still share this one guard.\n *\n * A JWS compact serialization is EXACTLY three segments. Both a short token and\n * a longer one (a five-segment JWE, or an attacker appending `.junk`) are\n * rejected outright — never parsed from their first three segments, which would\n * let the signed input disagree with the token. */\nexport function splitJwtSegments(token) {\n const [headerSegment, payloadSegment, signatureSegment, ...extraSegments] = token.split(\".\");\n if (headerSegment === undefined || payloadSegment === undefined || signatureSegment === undefined)\n return null;\n if (extraSegments.length > 0)\n return null;\n return { headerSegment, payloadSegment, signatureSegment };\n}\nexport { scanEnvOptions, snakeToLowerCamel } from \"./envScan.js\";\n","// Path helpers for MulmoScript story artifacts. The generic build primitives\n// (slug + the `\"\"`/`.`/`..` traversal guard) live in the shared, browser-safe\n// `@mulmoclaude/core/artifacts` (#2405); only the story-specific wire-path\n// rules stay here.\n//\n// Path model: the stories directory lives at `<workspace>/artifacts/stories`\n// and the FileOps scope root is `<workspace>/artifacts`, so the\n// FileOps-relative path and the historical `stories/<name>.json` wire form\n// (which every mulmoScript endpoint keys on) are the SAME string. Stories are\n// NOT `YYYY/MM`-partitioned, so `storyFilePath` opts out of partitioning.\n\nimport { ARTIFACTS_ROOT, buildArtifactRelPath, hasUnsafePathSegment, slugifyArtifact } from \"@mulmoclaude/core/artifacts\";\n\nconst STORIES_DIR = \"stories\";\nconst STORY_FALLBACK_SLUG = \"story\";\n\n/** Lowercase-hyphen slug, capped, leading/trailing hyphens stripped; falls back\n * to `fallback` for empty/undefined/non-ASCII input. */\nexport function slugify(title: string | undefined, fallback = STORY_FALLBACK_SLUG): string {\n return slugifyArtifact(title, fallback);\n}\n\n/** Build a fresh, collision-safe story path for a new script —\n * `stories/<slug>-<epoch-ms>.json`, valid as both the FileOps-relative\n * write path and the wire `filePath`. */\nexport function storyFilePath(slugSource: string, now: Date = new Date()): string {\n return buildArtifactRelPath({ dir: STORIES_DIR, title: slugSource, ext: \".json\", fallback: STORY_FALLBACK_SLUG, now, partitioned: false });\n}\n\n/**\n * Normalize a caller-supplied wire path to the canonical\n * `stories/<rel>` form, or null when it can't be trusted. Accepts the\n * canonical `stories/foo.json` convention, bare `foo.json` (the host route\n * historically allowed either), and the workspace-relative spelling\n * `artifacts/stories/foo.json` — the tool description called `filePath`\n * \"workspace-relative\" for a long time, so agents legitimately send it.\n * A leading `artifacts` segment is dropped only when `stories` follows;\n * a bare `artifacts/foo.json` keeps its historical meaning (a file named\n * `artifacts/foo.json` under the stories dir). Rejects absolute paths,\n * backslashes, and any empty / `.` / `..` segment — the lexical guard\n * before every `files.artifacts` read/write (FileOps re-checks\n * containment as defence-in-depth).\n */\nexport function normalizeStoryPath(filePath: string): string | null {\n if (filePath.length === 0 || filePath.includes(\"\\\\\")) return null;\n // Absolute POSIX path or Windows drive prefix.\n if (filePath.startsWith(\"/\") || /^[A-Za-z]:/.test(filePath)) return null;\n if (hasUnsafePathSegment(filePath)) return null;\n const segments = filePath.split(\"/\");\n const trimmed = segments[0] === ARTIFACTS_ROOT && segments[1] === STORIES_DIR ? segments.slice(1) : segments;\n const rest = trimmed[0] === STORIES_DIR ? trimmed.slice(1) : trimmed;\n if (rest.length === 0) return null;\n return [STORIES_DIR, ...rest].join(\"/\");\n}\n","// Body validators for the mulmoScript update endpoints, moved verbatim from\n// the host's `server/api/routes/mulmoScriptValidate.ts` so MulmoClaude and\n// MulmoTerminal share one definition of a \"valid script\" / \"valid beat\".\n//\n// The `@mulmocast/types` package exports zod schemas that mirror the\n// canonical MulmoScript / MulmoBeat shapes. The same schemas back client-side\n// edit-time validation in the presentMulmoScript View.\n\nimport { mulmoBeatSchema, mulmoScriptSchema } from \"@mulmocast/types\";\n\nexport type ValidationResult<T> = { ok: true; value: T } | { ok: false; error: string };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction formatZodIssues(\n // Zod's `$ZodIssue.path` is `PropertyKey[]` (includes `symbol`).\n // Accept the wider type so callers can pass `safeParse().error.issues`\n // directly; stringify any non-string/number segments at format time.\n issues: readonly { message: string; path: readonly PropertyKey[] }[],\n): string {\n if (issues.length === 0) return \"invalid shape\";\n const head = issues\n .slice(0, 3)\n .map((i) => {\n const pathStr = i.path.length > 0 ? i.path.map((seg) => String(seg)).join(\".\") : \"<root>\";\n return `${pathStr}: ${i.message}`;\n })\n .join(\"; \");\n return issues.length > 3 ? `${head} (+${issues.length - 3} more)` : head;\n}\n\n/** Shared prelude: require an object body carrying a non-empty\n * `filePath`. On success returns the narrowed record + filePath so the\n * caller reads the remaining fields without re-narrowing. */\nfunction validateFilePathBody(body: unknown): ValidationResult<{ record: Record<string, unknown>; filePath: string }> {\n if (!isRecord(body)) {\n return { ok: false, error: \"body must be an object\" };\n }\n if (typeof body.filePath !== \"string\" || body.filePath === \"\") {\n return { ok: false, error: \"filePath must be a non-empty string\" };\n }\n return { ok: true, value: { record: body, filePath: body.filePath } };\n}\n\n/**\n * Validate the `update-script` request body. Returns the parsed,\n * schema-conformant script on success, or a human-readable error\n * suitable for sending back as a 400 response.\n */\nexport function validateUpdateScriptBody(body: unknown): ValidationResult<{\n filePath: string;\n script: unknown;\n}> {\n const base = validateFilePathBody(body);\n if (!base.ok) return base;\n const { record, filePath } = base.value;\n if (record.script === undefined) {\n return { ok: false, error: \"script is required\" };\n }\n const parsed = mulmoScriptSchema.safeParse(record.script);\n if (!parsed.success) {\n return {\n ok: false,\n error: `invalid script: ${formatZodIssues(parsed.error.issues)}`,\n };\n }\n return {\n ok: true,\n value: { filePath, script: parsed.data },\n };\n}\n\n/**\n * Validate the `update-beat` request body. `beatIndex` is allowed\n * to be any non-negative integer; the handler still bounds-checks\n * against the actual script length after reading the file.\n */\nexport function validateUpdateBeatBody(body: unknown): ValidationResult<{\n filePath: string;\n beatIndex: number;\n beat: unknown;\n}> {\n const base = validateFilePathBody(body);\n if (!base.ok) return base;\n const { record, filePath } = base.value;\n const beatIndex = record.beatIndex;\n if (typeof beatIndex !== \"number\" || !Number.isInteger(beatIndex) || beatIndex < 0) {\n return { ok: false, error: \"beatIndex must be a non-negative integer\" };\n }\n if (record.beat === undefined) {\n return { ok: false, error: \"beat is required\" };\n }\n const parsed = mulmoBeatSchema.safeParse(record.beat);\n if (!parsed.success) {\n return {\n ok: false,\n error: `invalid beat: ${formatZodIssues(parsed.error.issues)}`,\n };\n }\n return {\n ok: true,\n value: {\n filePath,\n beatIndex,\n beat: parsed.data,\n },\n };\n}\n","import type { ToolPluginCore, ToolResult } from \"gui-chat-protocol\";\nimport { mulmoScriptSchema } from \"@mulmocast/types\";\nimport { errorMessage } from \"@mulmoclaude/common\";\nimport { TOOL_DEFINITION } from \"./definition\";\nimport { normalizeStoryPath, storyFilePath } from \"./paths\";\nimport { validateUpdateBeatBody, validateUpdateScriptBody } from \"./validate\";\nimport type { MulmoScriptData, MulmoScriptExecuteContext, SaveMulmoScriptArgs } from \"./types\";\n\n/** Failure half of every outcome below. `code` preserves the hosts'\n * HTTP contract (bad_request → 400, not_found → 404) without the package\n * knowing anything about HTTP. */\nexport interface MulmoScriptFailure {\n ok: false;\n code: \"bad_request\" | \"not_found\";\n error: string;\n}\n\nexport type SaveMulmoScriptOutcome = ({ ok: true; message: string } & MulmoScriptData) | MulmoScriptFailure;\n\nexport type UpdateMulmoScriptOutcome = { ok: true } | MulmoScriptFailure;\n\nfunction badRequest(error: string): MulmoScriptFailure {\n return { ok: false, code: \"bad_request\", error };\n}\n\nfunction notFound(error: string): MulmoScriptFailure {\n return { ok: false, code: \"not_found\", error };\n}\n\nfunction stringifyScript(script: unknown): string {\n // 2-space indent matches the hosts' writeJsonAtomic convention so a\n // package-written script diffs cleanly against host-written ones.\n return JSON.stringify(script, null, 2);\n}\n\n/** Persist a new, schema-validated script under a fresh `stories/…` path. */\nasync function saveNewScript(context: MulmoScriptExecuteContext, script: unknown, filename: string | undefined, now: Date): Promise<SaveMulmoScriptOutcome> {\n const validation = mulmoScriptSchema.safeParse(script);\n if (!validation.success) {\n return badRequest(\"script is not a valid MulmoScript\");\n }\n const validatedScript = validation.data;\n // slugify drops `/`, `\\`, and `..`, so a hostile `filename` like\n // \"../../etc/passwd\" can never escape the stories dir — defense in\n // depth on top of FileOps' own containment check.\n const slugSource = filename ? filename.replace(/\\.json$/i, \"\") : validatedScript.title || \"untitled\";\n const filePath = storyFilePath(slugSource, now);\n await context.files.artifacts.write(filePath, stringifyScript(validatedScript));\n return { ok: true, script: validatedScript, filePath, message: `Saved MulmoScript to ${filePath}` };\n}\n\n/** Re-open an existing script: containment guard, existence, JSON parse,\n * schema validation — same acceptance rules as the save path so a script\n * this package saved can never be one it later refuses to load. */\nasync function loadExistingScript(context: MulmoScriptExecuteContext, filePath: string): Promise<SaveMulmoScriptOutcome> {\n if (!filePath.toLowerCase().endsWith(\".json\")) {\n return badRequest(\"filePath must point to a .json file\");\n }\n const storyPath = normalizeStoryPath(filePath);\n if (!storyPath) {\n return badRequest(\"Invalid filePath\");\n }\n if (!(await context.files.artifacts.exists(storyPath))) {\n return notFound(`File not found: ${filePath}`);\n }\n const raw = await context.files.artifacts.read(storyPath);\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n return badRequest(`Invalid JSON: ${errorMessage(err)}`);\n }\n const validation = mulmoScriptSchema.safeParse(parsed);\n if (!validation.success) {\n return badRequest(\"File is not a valid MulmoScript\");\n }\n return { ok: true, script: validation.data, filePath: storyPath, message: `Loaded MulmoScript from ${storyPath}` };\n}\n\n/**\n * Unified save-or-reopen for the presentMulmoScript tool call. `script`\n * (create new) and `filePath` (existing) are mutually exclusive. Never\n * throws on bad input — validation failures come back as discriminated\n * failures so host routes stay thin adapters. `autoGenerateMovie` is NOT\n * handled here: movie generation needs host backends (mulmocast/ffmpeg),\n * so hosts that support it trigger it from the returned `filePath`.\n */\nexport async function executeMulmoScriptSave(\n context: MulmoScriptExecuteContext,\n args: SaveMulmoScriptArgs,\n now: Date = new Date(),\n): Promise<SaveMulmoScriptOutcome> {\n const { script, filename, filePath } = args ?? {};\n const hasScript = script !== undefined && script !== null;\n const hasFilePath = typeof filePath === \"string\" && filePath !== \"\";\n if (hasScript === hasFilePath) {\n return badRequest(\n hasScript ? \"Provide either `script` or `filePath`, not both.\" : \"Provide either `script` (new presentation) or `filePath` (existing presentation).\",\n );\n }\n return hasFilePath\n ? loadExistingScript(context, filePath as string)\n : saveNewScript(context, script, typeof filename === \"string\" ? filename : undefined, now);\n}\n\n/** Resolve + guard a wire path for the update endpoints. */\nasync function resolveExistingStory(context: MulmoScriptExecuteContext, filePath: string): Promise<{ storyPath: string } | MulmoScriptFailure> {\n const storyPath = normalizeStoryPath(filePath);\n if (!storyPath) return badRequest(\"Invalid filePath\");\n if (!(await context.files.artifacts.exists(storyPath))) {\n return notFound(`File not found: ${filePath}`);\n }\n return { storyPath };\n}\n\n/** Overwrite one beat of an existing script (the View's per-beat source\n * editor). Validates the body shape + beat schema, bounds-checks the index\n * against the script on disk, and writes the whole file back. */\nexport async function executeUpdateBeat(context: MulmoScriptExecuteContext, body: unknown): Promise<UpdateMulmoScriptOutcome> {\n const validation = validateUpdateBeatBody(body);\n if (!validation.ok) return badRequest(validation.error);\n const { filePath, beatIndex, beat } = validation.value;\n\n const resolved = await resolveExistingStory(context, filePath);\n if (\"ok\" in resolved) return resolved;\n\n let script: { beats?: unknown[] };\n try {\n script = JSON.parse(await context.files.artifacts.read(resolved.storyPath));\n } catch (err) {\n return badRequest(`Invalid JSON: ${errorMessage(err)}`);\n }\n if (!Array.isArray(script.beats) || beatIndex >= script.beats.length) {\n return badRequest(\"Invalid beatIndex\");\n }\n script.beats[beatIndex] = beat;\n await context.files.artifacts.write(resolved.storyPath, stringifyScript(script));\n return { ok: true };\n}\n\n/** Overwrite the whole script (the View's full-source editor / deck-editor\n * auto-save). The body's `script` is schema-validated by\n * `validateUpdateScriptBody` before the write. */\nexport async function executeUpdateScript(context: MulmoScriptExecuteContext, body: unknown): Promise<UpdateMulmoScriptOutcome> {\n const validation = validateUpdateScriptBody(body);\n if (!validation.ok) return badRequest(validation.error);\n const { filePath, script } = validation.value;\n\n const resolved = await resolveExistingStory(context, filePath);\n if (\"ok\" in resolved) return resolved;\n\n await context.files.artifacts.write(resolved.storyPath, stringifyScript(script));\n return { ok: true };\n}\n\n/** ToolResult-shaped wrapper over `executeMulmoScriptSave` for runtime hosts\n * (e.g. MulmoTerminal's package loader), where the tool call resolves to a\n * ToolResult rather than an HTTP response. Failures are narrate-only\n * (message, no data) so the agent can self-correct. */\nexport async function executeMulmoScript(context: MulmoScriptExecuteContext, args: SaveMulmoScriptArgs): Promise<ToolResult<MulmoScriptData>> {\n const outcome = await executeMulmoScriptSave(context, args);\n if (!outcome.ok) {\n return {\n message: outcome.error,\n instructions: \"Acknowledge the error and retry with a valid `script` (new) or an existing `filePath`.\",\n };\n }\n // The tool schema advertises `autoGenerateMovie`, but movie generation\n // needs host backends (mulmocast/ffmpeg) this generic execute path\n // doesn't have — MulmoClaude honours the flag in its own save route.\n // Say so in the result rather than silently dropping the option, so the\n // agent doesn't tell the user a movie is on its way.\n const ignoredMovieNote = args.autoGenerateMovie === true ? \" (autoGenerateMovie is not supported by this host and was ignored)\" : \"\";\n return {\n message: `${outcome.message}${ignoredMovieNote}`,\n data: { script: outcome.script, filePath: outcome.filePath },\n instructions: \"Display the storyboard to the user.\",\n };\n}\n\n/** Non-Vue plugin core for runtime hosts that register the package directly.\n * MulmoClaude consumes only TOOL_DEFINITION + the execute functions in its\n * own routes, so it doesn't use this. */\nexport const pluginCore: ToolPluginCore<MulmoScriptData, MulmoScriptData, SaveMulmoScriptArgs> = {\n toolDefinition: TOOL_DEFINITION,\n execute: executeMulmoScript as unknown as ToolPluginCore<MulmoScriptData, MulmoScriptData, SaveMulmoScriptArgs>[\"execute\"],\n generatingMessage: \"Generating MulmoScript storyboard…\",\n isEnabled: () => true,\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-CXhOP_xU.js","names":["isRecord"],"sources":["../src/core/definition.ts","../../../common/dist/index.js","../src/core/paths.ts","../src/core/validate.ts","../src/core/plugin.ts"],"sourcesContent":["import type { ToolDefinition } from \"gui-chat-protocol\";\n\nexport const TOOL_NAME = \"presentMulmoScript\";\n\n// Single source of truth for the presentMulmoScript tool schema, shared by\n// MulmoClaude (host built-in shim re-exports this) and MulmoTerminal.\n// (Extracted byte-identical from the host definition; evolves here with the\n// package version.)\nexport const TOOL_DEFINITION: ToolDefinition = {\n type: \"function\",\n name: TOOL_NAME,\n description: `Save and present a MulmoScript story or presentation as a visual storyboard in the canvas.\n\nTwo modes — provide EXACTLY ONE of \\`script\\` or \\`filePath\\`:\n\n1. **Create new** — pass \\`script\\` (full MulmoScript JSON). Server saves it to disk and presents it.\n2. **Re-display existing** — pass \\`filePath\\` (the \\`filePath\\` returned by a previous call, e.g. \"stories/my-story-1700000000000.json\"; it is resolved against the workspace's \\`artifacts/\\` directory, NOT the workspace root). Much cheaper than re-sending the full script. Use whenever the user wants to revisit a presentation that was already created in this workspace.\n\nOptional \\`autoGenerateMovie: true\\` kicks off movie generation in the background, so the final video is ready by the time the user opens the canvas. Movie generation is expensive (multiple image + audio API calls + video encoding) — only set this when the user has explicitly asked for the movie. Default \\`false\\`.\n\nProvider rules for new scripts:\n- \\`speechParams.speakers.<name>.provider\\`: \\`\"gemini\"\\` — pairs with Gemini voices like \\`\"Kore\"\\`, \\`\"Aoede\"\\`, \\`\"Puck\"\\`. Do NOT use \\`\"google\"\\` here — that routes to Google Cloud TTS, where Gemini-class voices fail with \"This voice requires a model name to be specified.\" unless an explicit \\`model\\` is set.\n- \\`imageParams.provider\\`: \\`\"google\"\\`\n- \\`movieParams.provider\\`: \\`\"google\"\\`\n- Do NOT add a top-level \\`provider\\` field to \\`speechParams\\` — provider belongs per-speaker only.\n\nRequired structure:\n\n{\n \"$mulmocast\": { \"version\": \"1.1\" },\n \"title\": \"The Life of a Star\",\n \"description\": \"A short educational explainer about stellar evolution\",\n \"lang\": \"en\",\n \"speechParams\": {\n \"speakers\": {\n \"Presenter\": {\n \"provider\": \"gemini\",\n \"voiceId\": \"Kore\",\n \"displayName\": { \"en\": \"Presenter\" }\n }\n }\n },\n \"imageParams\": { \"provider\": \"google\", \"model\": \"gemini-3.1-flash-image-preview\" },\n \"movieParams\": { \"provider\": \"google\", \"model\": \"veo-3.1-generate\" },\n \"beats\": [\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Narration spoken aloud for this beat.\",\n \"imagePrompt\": \"Detailed description — AI generates the image\"\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Bullet point beat.\",\n \"image\": { \"type\": \"textSlide\", \"slide\": { \"title\": \"Slide Title\", \"bullets\": [\"Point one\", \"Point two\"] } }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Markdown beat.\",\n \"image\": { \"type\": \"markdown\", \"markdown\": \"## Heading\\\\n\\\\nBody text here.\" }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Chart beat — use for data, comparisons, trends.\",\n \"image\": { \"type\": \"chart\", \"title\": \"Chart Title\", \"chartData\": { \"type\": \"bar\", \"data\": { \"labels\": [\"A\", \"B\", \"C\"], \"datasets\": [{ \"label\": \"Series\", \"data\": [10, 20, 30] }] } } }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Diagram beat — use for flows, architectures, relationships.\",\n \"image\": { \"type\": \"mermaid\", \"title\": \"Diagram Title\", \"code\": { \"kind\": \"text\", \"text\": \"graph TD\\\\n A[Start] --> B[Process] --> C[End]\" } }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Rich interactive beat — use for custom layouts, animations, or anything that benefits from HTML/CSS.\",\n \"image\": { \"type\": \"html_tailwind\", \"html\": \"<div class=\\\\\"flex items-center justify-center h-full text-4xl font-bold text-blue-600\\\\\">Hello World</div>\" }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"AI video beat.\",\n \"moviePrompt\": \"Detailed description — AI generates the video clip\"\n }\n ]\n}\n\nBeat visual options (choose one per beat):\n- \"imagePrompt\": \"...\" → top-level string field — AI generates an image from the prompt\n- \"moviePrompt\": \"...\" → top-level string field — AI generates a video clip from the prompt\n- \"image\": { \"type\": \"textSlide\", \"slide\": { \"title\", \"subtitle\"?, \"bullets\"? } }\n- \"image\": { \"type\": \"markdown\", \"markdown\": \"...\" }\n- \"image\": { \"type\": \"chart\", \"title\": \"...\", \"chartData\": { \"type\": \"bar\"|\"line\"|\"pie\"|..., \"data\": { \"labels\": [...], \"datasets\": [...] } } } ← PREFER for data/numbers/comparisons. chartData is a full Chart.js config: labels/datasets go under \"data\", not at the top level.\n- \"image\": { \"type\": \"mermaid\", \"title\": \"...\", \"code\": { \"kind\": \"text\", \"text\": \"...\" } } ← PREFER for flows/diagrams/relationships\n- \"image\": { \"type\": \"html_tailwind\", \"html\": \"...\", \"script\"?: \"...\" } ← PREFER for rich layouts, animations, custom visuals\n\nIMPORTANT: \"imagePrompt\" and \"moviePrompt\" are plain string fields on the beat, NOT nested under \"image\".`,\n parameters: {\n type: \"object\",\n properties: {\n script: {\n type: \"object\",\n description:\n \"Complete MulmoScript JSON for a NEW presentation. Must include $mulmocast, speechParams, imageParams, movieParams, and beats array. Always populate the top-level 'description' field with a concise 1–2 sentence summary of the presentation. Do NOT pass alongside `filePath`.\",\n additionalProperties: true,\n },\n filename: {\n type: \"string\",\n description:\n \"Optional filename without extension. Defaults to a slug of the script title. Only meaningful with `script`; ignored when `filePath` is given.\",\n },\n filePath: {\n type: \"string\",\n description:\n \"Path of an EXISTING MulmoScript JSON file, as returned by a previous call (e.g. 'stories/my-story-1700000000000.json'). Resolved against the workspace's `artifacts/` directory, not the workspace root ('artifacts/stories/…' is also accepted). Use this to re-display a script previously saved in this workspace, instead of resending the full JSON. Do NOT pass alongside `script`.\",\n },\n autoGenerateMovie: {\n type: \"boolean\",\n description:\n \"When true, the server starts movie generation in the background after save/load. The user does NOT need to open the canvas — progress streams via the existing session channel. Default false. Only set true when the user has explicitly asked for the movie; generation is expensive.\",\n },\n },\n required: [],\n },\n};\n","// General-purpose runtime type guards, shared across the MulmoClaude host,\n// bridges, and plugins. This is a leaf package — pure and dependency-free — so\n// any tier can import it without creating an uphill edge.\n//\n// These originated as `server/utils/types.ts` (#504), which centralised 40+\n// hand-written inline `typeof x === \"object\"` checks. They are promoted here so\n// the same guards stop being re-hand-written in every bridge and plugin too.\n/** Narrow `unknown` to a plain object (not null, not array). */\nexport function isRecord(value) {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n/** Narrow `unknown` to any object (not null, arrays allowed).\n * Use `isRecord` when you need to access string keys. */\nexport function isObj(value) {\n return typeof value === \"object\" && value !== null;\n}\n/** Non-empty string after trimming whitespace. */\nexport function isNonEmptyString(value) {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n/** Record whose values are all strings. */\nexport function isStringRecord(value) {\n if (!isRecord(value))\n return false;\n return Object.values(value).every((val) => typeof val === \"string\");\n}\n/** String array (every element is a string). */\nexport function isStringArray(value) {\n return Array.isArray(value) && value.every((val) => typeof val === \"string\");\n}\n/** An array of unknowns. Prefer this over a bare `Array.isArray` in typed code:\n * `Array.isArray(x: unknown)` narrows to `any[]`, silently reintroducing\n * `any`, whereas this keeps the element type `unknown`. */\nexport function isUnknownArray(value) {\n return Array.isArray(value);\n}\n/** Error-like object with a `code` property (e.g. Node.js fs errors). */\nexport function isErrorWithCode(value) {\n return isRecord(value) && typeof value.code === \"string\";\n}\n/** Check that a record has a specific key with a string value. */\nexport function hasStringProp(value, key) {\n return isRecord(value) && typeof value[key] === \"string\";\n}\n/** Check that a record has a specific key with a number value. */\nexport function hasNumberProp(value, key) {\n return isRecord(value) && typeof value[key] === \"number\";\n}\n/** Split a comma-separated env value into trimmed, non-empty entries.\n * `lowercase` folds case for identifiers compared case-insensitively\n * (JIDs, email addresses, hex pubkeys). Absent/empty input → empty list. */\nexport function parseCsvList(raw, opts) {\n return (raw ?? \"\")\n .split(\",\")\n .map((entry) => (opts?.lowercase ? entry.trim().toLowerCase() : entry.trim()))\n .filter(Boolean);\n}\n/** A comma-separated env value as a Set — the canonical allowlist shape,\n * where an empty set is the \"allow all\" sentinel (`set.size === 0`). */\nexport function parseCsvSet(raw, opts) {\n return new Set(parseCsvList(raw, opts));\n}\n/** Normalise an unknown thrown value into a human-readable string. Isomorphic\n * (host, bridges, plugins, Vue) — this is the single home for the helper that\n * #2217 could only consolidate for server code, since `@mulmoclaude/core/utils`\n * is server-only.\n *\n * A non-Error object with a non-empty string `details` (gRPC convention) or\n * `message` field surfaces that field — `details` wins — instead of the\n * `[object Object]` a bare `String(err)` would print; an empty-string field\n * falls through. `fallback` covers the error-boundary idiom where a thrown\n * non-Error should read as a descriptive message rather than `String(err)`\n * noise; omit it in logging contexts where `String(err)` is fine. */\nexport function errorMessage(err, fallback) {\n if (err instanceof Error)\n return err.message;\n if (hasStringProp(err, \"details\") && err.details)\n return err.details;\n if (hasStringProp(err, \"message\") && err.message)\n return err.message;\n if (fallback !== undefined)\n return fallback;\n return String(err);\n}\n/** `Date` → `YYYY-MM-DD` in UTC — for dates that must not shift with the\n * host's local timezone (tool-trace search dirs, API date keys). Isomorphic\n * single source (#2480): the host re-exports it from `server/utils/date.ts`,\n * x-plugin imports it directly. The `@receptron/task-scheduler` copy stays\n * local on purpose — that leaf package is published independently and kept\n * dependency-free. Wall-clock questions use the host's `toLocalIsoDate`. */\nexport function toUtcIsoDate(timestamp) {\n const year = timestamp.getUTCFullYear();\n const month = String(timestamp.getUTCMonth() + 1).padStart(2, \"0\");\n const day = String(timestamp.getUTCDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n// A Map, not an object literal: `{}[char]` reads through the prototype chain,\n// so a future caller widening the regex would silently get `[object Object]`\n// for keys like `constructor`.\nconst HTML_ESCAPES = new Map([\n [\"&\", \"&\"],\n [\"<\", \"<\"],\n [\">\", \">\"],\n ['\"', \""\"],\n [\"'\", \"'\"],\n]);\n/** HTML-escape text destined for markup or an attribute value — the fixed\n * five-character map, nothing more. Lives here rather than in\n * `@mulmoclaude/core/wiki` (#2483) because `@mulmoclaude/markdown-utils` is a\n * leaf that core depends on and so cannot import back up; core/wiki\n * re-exports this, keeping its consumers' import path unchanged.\n *\n * Escaping `&` first is what makes a single pass safe — the entities this\n * introduces contain none of the other four characters, so nothing is\n * double-escaped. Not a sanitiser: it neither strips tags nor validates URLs. */\nexport function escapeHtml(value) {\n return value.replace(/[&<>\"']/g, (char) => HTML_ESCAPES.get(char) ?? char);\n}\nexport { scanEnvOptions, snakeToLowerCamel } from \"./envScan.js\";\n","// Path helpers for MulmoScript story artifacts. The generic build primitives\n// (slug + the `\"\"`/`.`/`..` traversal guard) live in the shared, browser-safe\n// `@mulmoclaude/core/artifacts` (#2405); only the story-specific wire-path\n// rules stay here.\n//\n// Path model: the stories directory lives at `<workspace>/artifacts/stories`\n// and the FileOps scope root is `<workspace>/artifacts`, so the\n// FileOps-relative path and the historical `stories/<name>.json` wire form\n// (which every mulmoScript endpoint keys on) are the SAME string. Stories are\n// NOT `YYYY/MM`-partitioned, so `storyFilePath` opts out of partitioning.\n\nimport { ARTIFACTS_ROOT, buildArtifactRelPath, hasUnsafePathSegment, slugifyArtifact } from \"@mulmoclaude/core/artifacts\";\n\nconst STORIES_DIR = \"stories\";\nconst STORY_FALLBACK_SLUG = \"story\";\n\n/** Lowercase-hyphen slug, capped, leading/trailing hyphens stripped; falls back\n * to `fallback` for empty/undefined/non-ASCII input. */\nexport function slugify(title: string | undefined, fallback = STORY_FALLBACK_SLUG): string {\n return slugifyArtifact(title, fallback);\n}\n\n/** Build a fresh, collision-safe story path for a new script —\n * `stories/<slug>-<epoch-ms>.json`, valid as both the FileOps-relative\n * write path and the wire `filePath`. */\nexport function storyFilePath(slugSource: string, now: Date = new Date()): string {\n return buildArtifactRelPath({ dir: STORIES_DIR, title: slugSource, ext: \".json\", fallback: STORY_FALLBACK_SLUG, now, partitioned: false });\n}\n\n/**\n * Normalize a caller-supplied wire path to the canonical\n * `stories/<rel>` form, or null when it can't be trusted. Accepts the\n * canonical `stories/foo.json` convention, bare `foo.json` (the host route\n * historically allowed either), and the workspace-relative spelling\n * `artifacts/stories/foo.json` — the tool description called `filePath`\n * \"workspace-relative\" for a long time, so agents legitimately send it.\n * A leading `artifacts` segment is dropped only when `stories` follows;\n * a bare `artifacts/foo.json` keeps its historical meaning (a file named\n * `artifacts/foo.json` under the stories dir). Rejects absolute paths,\n * backslashes, and any empty / `.` / `..` segment — the lexical guard\n * before every `files.artifacts` read/write (FileOps re-checks\n * containment as defence-in-depth).\n */\nexport function normalizeStoryPath(filePath: string): string | null {\n if (filePath.length === 0 || filePath.includes(\"\\\\\")) return null;\n // Absolute POSIX path or Windows drive prefix.\n if (filePath.startsWith(\"/\") || /^[A-Za-z]:/.test(filePath)) return null;\n if (hasUnsafePathSegment(filePath)) return null;\n const segments = filePath.split(\"/\");\n const trimmed = segments[0] === ARTIFACTS_ROOT && segments[1] === STORIES_DIR ? segments.slice(1) : segments;\n const rest = trimmed[0] === STORIES_DIR ? trimmed.slice(1) : trimmed;\n if (rest.length === 0) return null;\n return [STORIES_DIR, ...rest].join(\"/\");\n}\n","// Body validators for the mulmoScript update endpoints, moved verbatim from\n// the host's `server/api/routes/mulmoScriptValidate.ts` so MulmoClaude and\n// MulmoTerminal share one definition of a \"valid script\" / \"valid beat\".\n//\n// The `@mulmocast/types` package exports zod schemas that mirror the\n// canonical MulmoScript / MulmoBeat shapes. The same schemas back client-side\n// edit-time validation in the presentMulmoScript View.\n\nimport { mulmoBeatSchema, mulmoScriptSchema } from \"@mulmocast/types\";\n\nexport type ValidationResult<T> = { ok: true; value: T } | { ok: false; error: string };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction formatZodIssues(\n // Zod's `$ZodIssue.path` is `PropertyKey[]` (includes `symbol`).\n // Accept the wider type so callers can pass `safeParse().error.issues`\n // directly; stringify any non-string/number segments at format time.\n issues: readonly { message: string; path: readonly PropertyKey[] }[],\n): string {\n if (issues.length === 0) return \"invalid shape\";\n const head = issues\n .slice(0, 3)\n .map((i) => {\n const pathStr = i.path.length > 0 ? i.path.map((seg) => String(seg)).join(\".\") : \"<root>\";\n return `${pathStr}: ${i.message}`;\n })\n .join(\"; \");\n return issues.length > 3 ? `${head} (+${issues.length - 3} more)` : head;\n}\n\n/** Shared prelude: require an object body carrying a non-empty\n * `filePath`. On success returns the narrowed record + filePath so the\n * caller reads the remaining fields without re-narrowing. */\nfunction validateFilePathBody(body: unknown): ValidationResult<{ record: Record<string, unknown>; filePath: string }> {\n if (!isRecord(body)) {\n return { ok: false, error: \"body must be an object\" };\n }\n if (typeof body.filePath !== \"string\" || body.filePath === \"\") {\n return { ok: false, error: \"filePath must be a non-empty string\" };\n }\n return { ok: true, value: { record: body, filePath: body.filePath } };\n}\n\n/**\n * Validate the `update-script` request body. Returns the parsed,\n * schema-conformant script on success, or a human-readable error\n * suitable for sending back as a 400 response.\n */\nexport function validateUpdateScriptBody(body: unknown): ValidationResult<{\n filePath: string;\n script: unknown;\n}> {\n const base = validateFilePathBody(body);\n if (!base.ok) return base;\n const { record, filePath } = base.value;\n if (record.script === undefined) {\n return { ok: false, error: \"script is required\" };\n }\n const parsed = mulmoScriptSchema.safeParse(record.script);\n if (!parsed.success) {\n return {\n ok: false,\n error: `invalid script: ${formatZodIssues(parsed.error.issues)}`,\n };\n }\n return {\n ok: true,\n value: { filePath, script: parsed.data },\n };\n}\n\n/**\n * Validate the `update-beat` request body. `beatIndex` is allowed\n * to be any non-negative integer; the handler still bounds-checks\n * against the actual script length after reading the file.\n */\nexport function validateUpdateBeatBody(body: unknown): ValidationResult<{\n filePath: string;\n beatIndex: number;\n beat: unknown;\n}> {\n const base = validateFilePathBody(body);\n if (!base.ok) return base;\n const { record, filePath } = base.value;\n const beatIndex = record.beatIndex;\n if (typeof beatIndex !== \"number\" || !Number.isInteger(beatIndex) || beatIndex < 0) {\n return { ok: false, error: \"beatIndex must be a non-negative integer\" };\n }\n if (record.beat === undefined) {\n return { ok: false, error: \"beat is required\" };\n }\n const parsed = mulmoBeatSchema.safeParse(record.beat);\n if (!parsed.success) {\n return {\n ok: false,\n error: `invalid beat: ${formatZodIssues(parsed.error.issues)}`,\n };\n }\n return {\n ok: true,\n value: {\n filePath,\n beatIndex,\n beat: parsed.data,\n },\n };\n}\n","import type { ToolPluginCore, ToolResult } from \"gui-chat-protocol\";\nimport { mulmoScriptSchema } from \"@mulmocast/types\";\nimport { errorMessage } from \"@mulmoclaude/common\";\nimport { TOOL_DEFINITION } from \"./definition\";\nimport { normalizeStoryPath, storyFilePath } from \"./paths\";\nimport { validateUpdateBeatBody, validateUpdateScriptBody } from \"./validate\";\nimport type { MulmoScriptData, MulmoScriptExecuteContext, SaveMulmoScriptArgs } from \"./types\";\n\n/** Failure half of every outcome below. `code` preserves the hosts'\n * HTTP contract (bad_request → 400, not_found → 404) without the package\n * knowing anything about HTTP. */\nexport interface MulmoScriptFailure {\n ok: false;\n code: \"bad_request\" | \"not_found\";\n error: string;\n}\n\nexport type SaveMulmoScriptOutcome = ({ ok: true; message: string } & MulmoScriptData) | MulmoScriptFailure;\n\nexport type UpdateMulmoScriptOutcome = { ok: true } | MulmoScriptFailure;\n\nfunction badRequest(error: string): MulmoScriptFailure {\n return { ok: false, code: \"bad_request\", error };\n}\n\nfunction notFound(error: string): MulmoScriptFailure {\n return { ok: false, code: \"not_found\", error };\n}\n\nfunction stringifyScript(script: unknown): string {\n // 2-space indent matches the hosts' writeJsonAtomic convention so a\n // package-written script diffs cleanly against host-written ones.\n return JSON.stringify(script, null, 2);\n}\n\n/** Persist a new, schema-validated script under a fresh `stories/…` path. */\nasync function saveNewScript(context: MulmoScriptExecuteContext, script: unknown, filename: string | undefined, now: Date): Promise<SaveMulmoScriptOutcome> {\n const validation = mulmoScriptSchema.safeParse(script);\n if (!validation.success) {\n return badRequest(\"script is not a valid MulmoScript\");\n }\n const validatedScript = validation.data;\n // slugify drops `/`, `\\`, and `..`, so a hostile `filename` like\n // \"../../etc/passwd\" can never escape the stories dir — defense in\n // depth on top of FileOps' own containment check.\n const slugSource = filename ? filename.replace(/\\.json$/i, \"\") : validatedScript.title || \"untitled\";\n const filePath = storyFilePath(slugSource, now);\n await context.files.artifacts.write(filePath, stringifyScript(validatedScript));\n return { ok: true, script: validatedScript, filePath, message: `Saved MulmoScript to ${filePath}` };\n}\n\n/** Re-open an existing script: containment guard, existence, JSON parse,\n * schema validation — same acceptance rules as the save path so a script\n * this package saved can never be one it later refuses to load. */\nasync function loadExistingScript(context: MulmoScriptExecuteContext, filePath: string): Promise<SaveMulmoScriptOutcome> {\n if (!filePath.toLowerCase().endsWith(\".json\")) {\n return badRequest(\"filePath must point to a .json file\");\n }\n const storyPath = normalizeStoryPath(filePath);\n if (!storyPath) {\n return badRequest(\"Invalid filePath\");\n }\n if (!(await context.files.artifacts.exists(storyPath))) {\n return notFound(`File not found: ${filePath}`);\n }\n const raw = await context.files.artifacts.read(storyPath);\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n return badRequest(`Invalid JSON: ${errorMessage(err)}`);\n }\n const validation = mulmoScriptSchema.safeParse(parsed);\n if (!validation.success) {\n return badRequest(\"File is not a valid MulmoScript\");\n }\n return { ok: true, script: validation.data, filePath: storyPath, message: `Loaded MulmoScript from ${storyPath}` };\n}\n\n/**\n * Unified save-or-reopen for the presentMulmoScript tool call. `script`\n * (create new) and `filePath` (existing) are mutually exclusive. Never\n * throws on bad input — validation failures come back as discriminated\n * failures so host routes stay thin adapters. `autoGenerateMovie` is NOT\n * handled here: movie generation needs host backends (mulmocast/ffmpeg),\n * so hosts that support it trigger it from the returned `filePath`.\n */\nexport async function executeMulmoScriptSave(\n context: MulmoScriptExecuteContext,\n args: SaveMulmoScriptArgs,\n now: Date = new Date(),\n): Promise<SaveMulmoScriptOutcome> {\n const { script, filename, filePath } = args ?? {};\n const hasScript = script !== undefined && script !== null;\n const hasFilePath = typeof filePath === \"string\" && filePath !== \"\";\n if (hasScript === hasFilePath) {\n return badRequest(\n hasScript ? \"Provide either `script` or `filePath`, not both.\" : \"Provide either `script` (new presentation) or `filePath` (existing presentation).\",\n );\n }\n return hasFilePath\n ? loadExistingScript(context, filePath as string)\n : saveNewScript(context, script, typeof filename === \"string\" ? filename : undefined, now);\n}\n\n/** Resolve + guard a wire path for the update endpoints. */\nasync function resolveExistingStory(context: MulmoScriptExecuteContext, filePath: string): Promise<{ storyPath: string } | MulmoScriptFailure> {\n const storyPath = normalizeStoryPath(filePath);\n if (!storyPath) return badRequest(\"Invalid filePath\");\n if (!(await context.files.artifacts.exists(storyPath))) {\n return notFound(`File not found: ${filePath}`);\n }\n return { storyPath };\n}\n\n/** Overwrite one beat of an existing script (the View's per-beat source\n * editor). Validates the body shape + beat schema, bounds-checks the index\n * against the script on disk, and writes the whole file back. */\nexport async function executeUpdateBeat(context: MulmoScriptExecuteContext, body: unknown): Promise<UpdateMulmoScriptOutcome> {\n const validation = validateUpdateBeatBody(body);\n if (!validation.ok) return badRequest(validation.error);\n const { filePath, beatIndex, beat } = validation.value;\n\n const resolved = await resolveExistingStory(context, filePath);\n if (\"ok\" in resolved) return resolved;\n\n let script: { beats?: unknown[] };\n try {\n script = JSON.parse(await context.files.artifacts.read(resolved.storyPath));\n } catch (err) {\n return badRequest(`Invalid JSON: ${errorMessage(err)}`);\n }\n if (!Array.isArray(script.beats) || beatIndex >= script.beats.length) {\n return badRequest(\"Invalid beatIndex\");\n }\n script.beats[beatIndex] = beat;\n await context.files.artifacts.write(resolved.storyPath, stringifyScript(script));\n return { ok: true };\n}\n\n/** Overwrite the whole script (the View's full-source editor / deck-editor\n * auto-save). The body's `script` is schema-validated by\n * `validateUpdateScriptBody` before the write. */\nexport async function executeUpdateScript(context: MulmoScriptExecuteContext, body: unknown): Promise<UpdateMulmoScriptOutcome> {\n const validation = validateUpdateScriptBody(body);\n if (!validation.ok) return badRequest(validation.error);\n const { filePath, script } = validation.value;\n\n const resolved = await resolveExistingStory(context, filePath);\n if (\"ok\" in resolved) return resolved;\n\n await context.files.artifacts.write(resolved.storyPath, stringifyScript(script));\n return { ok: true };\n}\n\n/** ToolResult-shaped wrapper over `executeMulmoScriptSave` for runtime hosts\n * (e.g. MulmoTerminal's package loader), where the tool call resolves to a\n * ToolResult rather than an HTTP response. Failures are narrate-only\n * (message, no data) so the agent can self-correct. */\nexport async function executeMulmoScript(context: MulmoScriptExecuteContext, args: SaveMulmoScriptArgs): Promise<ToolResult<MulmoScriptData>> {\n const outcome = await executeMulmoScriptSave(context, args);\n if (!outcome.ok) {\n return {\n message: outcome.error,\n instructions: \"Acknowledge the error and retry with a valid `script` (new) or an existing `filePath`.\",\n };\n }\n // The tool schema advertises `autoGenerateMovie`, but movie generation\n // needs host backends (mulmocast/ffmpeg) this generic execute path\n // doesn't have — MulmoClaude honours the flag in its own save route.\n // Say so in the result rather than silently dropping the option, so the\n // agent doesn't tell the user a movie is on its way.\n const ignoredMovieNote = args.autoGenerateMovie === true ? \" (autoGenerateMovie is not supported by this host and was ignored)\" : \"\";\n return {\n message: `${outcome.message}${ignoredMovieNote}`,\n data: { script: outcome.script, filePath: outcome.filePath },\n instructions: \"Display the storyboard to the user.\",\n };\n}\n\n/** Non-Vue plugin core for runtime hosts that register the package directly.\n * MulmoClaude consumes only TOOL_DEFINITION + the execute functions in its\n * own routes, so it doesn't use this. */\nexport const pluginCore: ToolPluginCore<MulmoScriptData, MulmoScriptData, SaveMulmoScriptArgs> = {\n toolDefinition: TOOL_DEFINITION,\n execute: executeMulmoScript as unknown as ToolPluginCore<MulmoScriptData, MulmoScriptData, SaveMulmoScriptArgs>[\"execute\"],\n generatingMessage: \"Generating MulmoScript storyboard…\",\n isEnabled: () => true,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin-CXhOP_xU.js","names":["isRecord"],"sources":["../src/core/definition.ts","../../../common/dist/index.js","../src/core/paths.ts","../src/core/validate.ts","../src/core/plugin.ts"],"sourcesContent":["import type { ToolDefinition } from \"gui-chat-protocol\";\n\nexport const TOOL_NAME = \"presentMulmoScript\";\n\n// Single source of truth for the presentMulmoScript tool schema, shared by\n// MulmoClaude (host built-in shim re-exports this) and MulmoTerminal.\n// (Extracted byte-identical from the host definition; evolves here with the\n// package version.)\nexport const TOOL_DEFINITION: ToolDefinition = {\n type: \"function\",\n name: TOOL_NAME,\n description: `Save and present a MulmoScript story or presentation as a visual storyboard in the canvas.\n\nTwo modes — provide EXACTLY ONE of \\`script\\` or \\`filePath\\`:\n\n1. **Create new** — pass \\`script\\` (full MulmoScript JSON). Server saves it to disk and presents it.\n2. **Re-display existing** — pass \\`filePath\\` (the \\`filePath\\` returned by a previous call, e.g. \"stories/my-story-1700000000000.json\"; it is resolved against the workspace's \\`artifacts/\\` directory, NOT the workspace root). Much cheaper than re-sending the full script. Use whenever the user wants to revisit a presentation that was already created in this workspace.\n\nOptional \\`autoGenerateMovie: true\\` kicks off movie generation in the background, so the final video is ready by the time the user opens the canvas. Movie generation is expensive (multiple image + audio API calls + video encoding) — only set this when the user has explicitly asked for the movie. Default \\`false\\`.\n\nProvider rules for new scripts:\n- \\`speechParams.speakers.<name>.provider\\`: \\`\"gemini\"\\` — pairs with Gemini voices like \\`\"Kore\"\\`, \\`\"Aoede\"\\`, \\`\"Puck\"\\`. Do NOT use \\`\"google\"\\` here — that routes to Google Cloud TTS, where Gemini-class voices fail with \"This voice requires a model name to be specified.\" unless an explicit \\`model\\` is set.\n- \\`imageParams.provider\\`: \\`\"google\"\\`\n- \\`movieParams.provider\\`: \\`\"google\"\\`\n- Do NOT add a top-level \\`provider\\` field to \\`speechParams\\` — provider belongs per-speaker only.\n\nRequired structure:\n\n{\n \"$mulmocast\": { \"version\": \"1.1\" },\n \"title\": \"The Life of a Star\",\n \"description\": \"A short educational explainer about stellar evolution\",\n \"lang\": \"en\",\n \"speechParams\": {\n \"speakers\": {\n \"Presenter\": {\n \"provider\": \"gemini\",\n \"voiceId\": \"Kore\",\n \"displayName\": { \"en\": \"Presenter\" }\n }\n }\n },\n \"imageParams\": { \"provider\": \"google\", \"model\": \"gemini-3.1-flash-image-preview\" },\n \"movieParams\": { \"provider\": \"google\", \"model\": \"veo-3.1-generate\" },\n \"beats\": [\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Narration spoken aloud for this beat.\",\n \"imagePrompt\": \"Detailed description — AI generates the image\"\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Bullet point beat.\",\n \"image\": { \"type\": \"textSlide\", \"slide\": { \"title\": \"Slide Title\", \"bullets\": [\"Point one\", \"Point two\"] } }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Markdown beat.\",\n \"image\": { \"type\": \"markdown\", \"markdown\": \"## Heading\\\\n\\\\nBody text here.\" }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Chart beat — use for data, comparisons, trends.\",\n \"image\": { \"type\": \"chart\", \"title\": \"Chart Title\", \"chartData\": { \"type\": \"bar\", \"data\": { \"labels\": [\"A\", \"B\", \"C\"], \"datasets\": [{ \"label\": \"Series\", \"data\": [10, 20, 30] }] } } }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Diagram beat — use for flows, architectures, relationships.\",\n \"image\": { \"type\": \"mermaid\", \"title\": \"Diagram Title\", \"code\": { \"kind\": \"text\", \"text\": \"graph TD\\\\n A[Start] --> B[Process] --> C[End]\" } }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"Rich interactive beat — use for custom layouts, animations, or anything that benefits from HTML/CSS.\",\n \"image\": { \"type\": \"html_tailwind\", \"html\": \"<div class=\\\\\"flex items-center justify-center h-full text-4xl font-bold text-blue-600\\\\\">Hello World</div>\" }\n },\n {\n \"speaker\": \"Presenter\",\n \"text\": \"AI video beat.\",\n \"moviePrompt\": \"Detailed description — AI generates the video clip\"\n }\n ]\n}\n\nBeat visual options (choose one per beat):\n- \"imagePrompt\": \"...\" → top-level string field — AI generates an image from the prompt\n- \"moviePrompt\": \"...\" → top-level string field — AI generates a video clip from the prompt\n- \"image\": { \"type\": \"textSlide\", \"slide\": { \"title\", \"subtitle\"?, \"bullets\"? } }\n- \"image\": { \"type\": \"markdown\", \"markdown\": \"...\" }\n- \"image\": { \"type\": \"chart\", \"title\": \"...\", \"chartData\": { \"type\": \"bar\"|\"line\"|\"pie\"|..., \"data\": { \"labels\": [...], \"datasets\": [...] } } } ← PREFER for data/numbers/comparisons. chartData is a full Chart.js config: labels/datasets go under \"data\", not at the top level.\n- \"image\": { \"type\": \"mermaid\", \"title\": \"...\", \"code\": { \"kind\": \"text\", \"text\": \"...\" } } ← PREFER for flows/diagrams/relationships\n- \"image\": { \"type\": \"html_tailwind\", \"html\": \"...\", \"script\"?: \"...\" } ← PREFER for rich layouts, animations, custom visuals\n\nIMPORTANT: \"imagePrompt\" and \"moviePrompt\" are plain string fields on the beat, NOT nested under \"image\".`,\n parameters: {\n type: \"object\",\n properties: {\n script: {\n type: \"object\",\n description:\n \"Complete MulmoScript JSON for a NEW presentation. Must include $mulmocast, speechParams, imageParams, movieParams, and beats array. Always populate the top-level 'description' field with a concise 1–2 sentence summary of the presentation. Do NOT pass alongside `filePath`.\",\n additionalProperties: true,\n },\n filename: {\n type: \"string\",\n description:\n \"Optional filename without extension. Defaults to a slug of the script title. Only meaningful with `script`; ignored when `filePath` is given.\",\n },\n filePath: {\n type: \"string\",\n description:\n \"Path of an EXISTING MulmoScript JSON file, as returned by a previous call (e.g. 'stories/my-story-1700000000000.json'). Resolved against the workspace's `artifacts/` directory, not the workspace root ('artifacts/stories/…' is also accepted). Use this to re-display a script previously saved in this workspace, instead of resending the full JSON. Do NOT pass alongside `script`.\",\n },\n autoGenerateMovie: {\n type: \"boolean\",\n description:\n \"When true, the server starts movie generation in the background after save/load. The user does NOT need to open the canvas — progress streams via the existing session channel. Default false. Only set true when the user has explicitly asked for the movie; generation is expensive.\",\n },\n },\n required: [],\n },\n};\n","// General-purpose runtime type guards, shared across the MulmoClaude host,\n// bridges, and plugins. This is a leaf package — pure and dependency-free — so\n// any tier can import it without creating an uphill edge.\n//\n// These originated as `server/utils/types.ts` (#504), which centralised 40+\n// hand-written inline `typeof x === \"object\"` checks. They are promoted here so\n// the same guards stop being re-hand-written in every bridge and plugin too.\n/** Narrow `unknown` to a plain object (not null, not array). */\nexport function isRecord(value) {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n/** Narrow `unknown` to any object (not null, arrays allowed).\n * Use `isRecord` when you need to access string keys. */\nexport function isObj(value) {\n return typeof value === \"object\" && value !== null;\n}\n/** Non-empty string after trimming whitespace. */\nexport function isNonEmptyString(value) {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n/** Record whose values are all strings. */\nexport function isStringRecord(value) {\n if (!isRecord(value))\n return false;\n return Object.values(value).every((val) => typeof val === \"string\");\n}\n/** String array (every element is a string). */\nexport function isStringArray(value) {\n return Array.isArray(value) && value.every((val) => typeof val === \"string\");\n}\n/** An array of unknowns. Prefer this over a bare `Array.isArray` in typed code:\n * `Array.isArray(x: unknown)` narrows to `any[]`, silently reintroducing\n * `any`, whereas this keeps the element type `unknown`. */\nexport function isUnknownArray(value) {\n return Array.isArray(value);\n}\n/** Error-like object with a `code` property (e.g. Node.js fs errors). */\nexport function isErrorWithCode(value) {\n return isRecord(value) && typeof value.code === \"string\";\n}\n/** Check that a record has a specific key with a string value. */\nexport function hasStringProp(value, key) {\n return isRecord(value) && typeof value[key] === \"string\";\n}\n/** Check that a record has a specific key with a number value. */\nexport function hasNumberProp(value, key) {\n return isRecord(value) && typeof value[key] === \"number\";\n}\n/** Split a comma-separated env value into trimmed, non-empty entries.\n * `lowercase` folds case for identifiers compared case-insensitively\n * (JIDs, email addresses, hex pubkeys). Absent/empty input → empty list. */\nexport function parseCsvList(raw, opts) {\n return (raw ?? \"\")\n .split(\",\")\n .map((entry) => (opts?.lowercase ? entry.trim().toLowerCase() : entry.trim()))\n .filter(Boolean);\n}\n/** A comma-separated env value as a Set — the canonical allowlist shape,\n * where an empty set is the \"allow all\" sentinel (`set.size === 0`). */\nexport function parseCsvSet(raw, opts) {\n return new Set(parseCsvList(raw, opts));\n}\n/** Normalise an unknown thrown value into a human-readable string. Isomorphic\n * (host, bridges, plugins, Vue) — this is the single home for the helper that\n * #2217 could only consolidate for server code, since `@mulmoclaude/core/utils`\n * is server-only.\n *\n * A non-Error object with a non-empty string `details` (gRPC convention) or\n * `message` field surfaces that field — `details` wins — instead of the\n * `[object Object]` a bare `String(err)` would print; an empty-string field\n * falls through. `fallback` covers the error-boundary idiom where a thrown\n * non-Error should read as a descriptive message rather than `String(err)`\n * noise; omit it in logging contexts where `String(err)` is fine. */\nexport function errorMessage(err, fallback) {\n if (err instanceof Error)\n return err.message;\n if (hasStringProp(err, \"details\") && err.details)\n return err.details;\n if (hasStringProp(err, \"message\") && err.message)\n return err.message;\n if (fallback !== undefined)\n return fallback;\n return String(err);\n}\n/** `Date` → `YYYY-MM-DD` in UTC — for dates that must not shift with the\n * host's local timezone (tool-trace search dirs, API date keys). Isomorphic\n * single source (#2480): the host re-exports it from `server/utils/date.ts`,\n * x-plugin imports it directly. The `@receptron/task-scheduler` copy stays\n * local on purpose — that leaf package is published independently and kept\n * dependency-free. Wall-clock questions use the host's `toLocalIsoDate`. */\nexport function toUtcIsoDate(timestamp) {\n const year = timestamp.getUTCFullYear();\n const month = String(timestamp.getUTCMonth() + 1).padStart(2, \"0\");\n const day = String(timestamp.getUTCDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n// A Map, not an object literal: `{}[char]` reads through the prototype chain,\n// so a future caller widening the regex would silently get `[object Object]`\n// for keys like `constructor`.\nconst HTML_ESCAPES = new Map([\n [\"&\", \"&\"],\n [\"<\", \"<\"],\n [\">\", \">\"],\n ['\"', \""\"],\n [\"'\", \"'\"],\n]);\n/** HTML-escape text destined for markup or an attribute value — the fixed\n * five-character map, nothing more. Lives here rather than in\n * `@mulmoclaude/core/wiki` (#2483) because `@mulmoclaude/markdown-utils` is a\n * leaf that core depends on and so cannot import back up; core/wiki\n * re-exports this, keeping its consumers' import path unchanged.\n *\n * Escaping `&` first is what makes a single pass safe — the entities this\n * introduces contain none of the other four characters, so nothing is\n * double-escaped. Not a sanitiser: it neither strips tags nor validates URLs. */\nexport function escapeHtml(value) {\n return value.replace(/[&<>\"']/g, (char) => HTML_ESCAPES.get(char) ?? char);\n}\n/** Split a JWS compact serialization into its three segments, or `null` when the\n * token isn't well-formed. Pure string work, so the Node bridges and the\n * Cloudflare Workers relay — which decode the segments differently (`Buffer` vs\n * `atob`) — can still share this one guard.\n *\n * A JWS compact serialization is EXACTLY three segments. Both a short token and\n * a longer one (a five-segment JWE, or an attacker appending `.junk`) are\n * rejected outright — never parsed from their first three segments, which would\n * let the signed input disagree with the token. */\nexport function splitJwtSegments(token) {\n const [headerSegment, payloadSegment, signatureSegment, ...extraSegments] = token.split(\".\");\n if (headerSegment === undefined || payloadSegment === undefined || signatureSegment === undefined)\n return null;\n if (extraSegments.length > 0)\n return null;\n return { headerSegment, payloadSegment, signatureSegment };\n}\nexport { scanEnvOptions, snakeToLowerCamel } from \"./envScan.js\";\n","// Path helpers for MulmoScript story artifacts. The generic build primitives\n// (slug + the `\"\"`/`.`/`..` traversal guard) live in the shared, browser-safe\n// `@mulmoclaude/core/artifacts` (#2405); only the story-specific wire-path\n// rules stay here.\n//\n// Path model: the stories directory lives at `<workspace>/artifacts/stories`\n// and the FileOps scope root is `<workspace>/artifacts`, so the\n// FileOps-relative path and the historical `stories/<name>.json` wire form\n// (which every mulmoScript endpoint keys on) are the SAME string. Stories are\n// NOT `YYYY/MM`-partitioned, so `storyFilePath` opts out of partitioning.\n\nimport { ARTIFACTS_ROOT, buildArtifactRelPath, hasUnsafePathSegment, slugifyArtifact } from \"@mulmoclaude/core/artifacts\";\n\nconst STORIES_DIR = \"stories\";\nconst STORY_FALLBACK_SLUG = \"story\";\n\n/** Lowercase-hyphen slug, capped, leading/trailing hyphens stripped; falls back\n * to `fallback` for empty/undefined/non-ASCII input. */\nexport function slugify(title: string | undefined, fallback = STORY_FALLBACK_SLUG): string {\n return slugifyArtifact(title, fallback);\n}\n\n/** Build a fresh, collision-safe story path for a new script —\n * `stories/<slug>-<epoch-ms>.json`, valid as both the FileOps-relative\n * write path and the wire `filePath`. */\nexport function storyFilePath(slugSource: string, now: Date = new Date()): string {\n return buildArtifactRelPath({ dir: STORIES_DIR, title: slugSource, ext: \".json\", fallback: STORY_FALLBACK_SLUG, now, partitioned: false });\n}\n\n/**\n * Normalize a caller-supplied wire path to the canonical\n * `stories/<rel>` form, or null when it can't be trusted. Accepts the\n * canonical `stories/foo.json` convention, bare `foo.json` (the host route\n * historically allowed either), and the workspace-relative spelling\n * `artifacts/stories/foo.json` — the tool description called `filePath`\n * \"workspace-relative\" for a long time, so agents legitimately send it.\n * A leading `artifacts` segment is dropped only when `stories` follows;\n * a bare `artifacts/foo.json` keeps its historical meaning (a file named\n * `artifacts/foo.json` under the stories dir). Rejects absolute paths,\n * backslashes, and any empty / `.` / `..` segment — the lexical guard\n * before every `files.artifacts` read/write (FileOps re-checks\n * containment as defence-in-depth).\n */\nexport function normalizeStoryPath(filePath: string): string | null {\n if (filePath.length === 0 || filePath.includes(\"\\\\\")) return null;\n // Absolute POSIX path or Windows drive prefix.\n if (filePath.startsWith(\"/\") || /^[A-Za-z]:/.test(filePath)) return null;\n if (hasUnsafePathSegment(filePath)) return null;\n const segments = filePath.split(\"/\");\n const trimmed = segments[0] === ARTIFACTS_ROOT && segments[1] === STORIES_DIR ? segments.slice(1) : segments;\n const rest = trimmed[0] === STORIES_DIR ? trimmed.slice(1) : trimmed;\n if (rest.length === 0) return null;\n return [STORIES_DIR, ...rest].join(\"/\");\n}\n","// Body validators for the mulmoScript update endpoints, moved verbatim from\n// the host's `server/api/routes/mulmoScriptValidate.ts` so MulmoClaude and\n// MulmoTerminal share one definition of a \"valid script\" / \"valid beat\".\n//\n// The `@mulmocast/types` package exports zod schemas that mirror the\n// canonical MulmoScript / MulmoBeat shapes. The same schemas back client-side\n// edit-time validation in the presentMulmoScript View.\n\nimport { mulmoBeatSchema, mulmoScriptSchema } from \"@mulmocast/types\";\n\nexport type ValidationResult<T> = { ok: true; value: T } | { ok: false; error: string };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction formatZodIssues(\n // Zod's `$ZodIssue.path` is `PropertyKey[]` (includes `symbol`).\n // Accept the wider type so callers can pass `safeParse().error.issues`\n // directly; stringify any non-string/number segments at format time.\n issues: readonly { message: string; path: readonly PropertyKey[] }[],\n): string {\n if (issues.length === 0) return \"invalid shape\";\n const head = issues\n .slice(0, 3)\n .map((i) => {\n const pathStr = i.path.length > 0 ? i.path.map((seg) => String(seg)).join(\".\") : \"<root>\";\n return `${pathStr}: ${i.message}`;\n })\n .join(\"; \");\n return issues.length > 3 ? `${head} (+${issues.length - 3} more)` : head;\n}\n\n/** Shared prelude: require an object body carrying a non-empty\n * `filePath`. On success returns the narrowed record + filePath so the\n * caller reads the remaining fields without re-narrowing. */\nfunction validateFilePathBody(body: unknown): ValidationResult<{ record: Record<string, unknown>; filePath: string }> {\n if (!isRecord(body)) {\n return { ok: false, error: \"body must be an object\" };\n }\n if (typeof body.filePath !== \"string\" || body.filePath === \"\") {\n return { ok: false, error: \"filePath must be a non-empty string\" };\n }\n return { ok: true, value: { record: body, filePath: body.filePath } };\n}\n\n/**\n * Validate the `update-script` request body. Returns the parsed,\n * schema-conformant script on success, or a human-readable error\n * suitable for sending back as a 400 response.\n */\nexport function validateUpdateScriptBody(body: unknown): ValidationResult<{\n filePath: string;\n script: unknown;\n}> {\n const base = validateFilePathBody(body);\n if (!base.ok) return base;\n const { record, filePath } = base.value;\n if (record.script === undefined) {\n return { ok: false, error: \"script is required\" };\n }\n const parsed = mulmoScriptSchema.safeParse(record.script);\n if (!parsed.success) {\n return {\n ok: false,\n error: `invalid script: ${formatZodIssues(parsed.error.issues)}`,\n };\n }\n return {\n ok: true,\n value: { filePath, script: parsed.data },\n };\n}\n\n/**\n * Validate the `update-beat` request body. `beatIndex` is allowed\n * to be any non-negative integer; the handler still bounds-checks\n * against the actual script length after reading the file.\n */\nexport function validateUpdateBeatBody(body: unknown): ValidationResult<{\n filePath: string;\n beatIndex: number;\n beat: unknown;\n}> {\n const base = validateFilePathBody(body);\n if (!base.ok) return base;\n const { record, filePath } = base.value;\n const beatIndex = record.beatIndex;\n if (typeof beatIndex !== \"number\" || !Number.isInteger(beatIndex) || beatIndex < 0) {\n return { ok: false, error: \"beatIndex must be a non-negative integer\" };\n }\n if (record.beat === undefined) {\n return { ok: false, error: \"beat is required\" };\n }\n const parsed = mulmoBeatSchema.safeParse(record.beat);\n if (!parsed.success) {\n return {\n ok: false,\n error: `invalid beat: ${formatZodIssues(parsed.error.issues)}`,\n };\n }\n return {\n ok: true,\n value: {\n filePath,\n beatIndex,\n beat: parsed.data,\n },\n };\n}\n","import type { ToolPluginCore, ToolResult } from \"gui-chat-protocol\";\nimport { mulmoScriptSchema } from \"@mulmocast/types\";\nimport { errorMessage } from \"@mulmoclaude/common\";\nimport { TOOL_DEFINITION } from \"./definition\";\nimport { normalizeStoryPath, storyFilePath } from \"./paths\";\nimport { validateUpdateBeatBody, validateUpdateScriptBody } from \"./validate\";\nimport type { MulmoScriptData, MulmoScriptExecuteContext, SaveMulmoScriptArgs } from \"./types\";\n\n/** Failure half of every outcome below. `code` preserves the hosts'\n * HTTP contract (bad_request → 400, not_found → 404) without the package\n * knowing anything about HTTP. */\nexport interface MulmoScriptFailure {\n ok: false;\n code: \"bad_request\" | \"not_found\";\n error: string;\n}\n\nexport type SaveMulmoScriptOutcome = ({ ok: true; message: string } & MulmoScriptData) | MulmoScriptFailure;\n\nexport type UpdateMulmoScriptOutcome = { ok: true } | MulmoScriptFailure;\n\nfunction badRequest(error: string): MulmoScriptFailure {\n return { ok: false, code: \"bad_request\", error };\n}\n\nfunction notFound(error: string): MulmoScriptFailure {\n return { ok: false, code: \"not_found\", error };\n}\n\nfunction stringifyScript(script: unknown): string {\n // 2-space indent matches the hosts' writeJsonAtomic convention so a\n // package-written script diffs cleanly against host-written ones.\n return JSON.stringify(script, null, 2);\n}\n\n/** Persist a new, schema-validated script under a fresh `stories/…` path. */\nasync function saveNewScript(context: MulmoScriptExecuteContext, script: unknown, filename: string | undefined, now: Date): Promise<SaveMulmoScriptOutcome> {\n const validation = mulmoScriptSchema.safeParse(script);\n if (!validation.success) {\n return badRequest(\"script is not a valid MulmoScript\");\n }\n const validatedScript = validation.data;\n // slugify drops `/`, `\\`, and `..`, so a hostile `filename` like\n // \"../../etc/passwd\" can never escape the stories dir — defense in\n // depth on top of FileOps' own containment check.\n const slugSource = filename ? filename.replace(/\\.json$/i, \"\") : validatedScript.title || \"untitled\";\n const filePath = storyFilePath(slugSource, now);\n await context.files.artifacts.write(filePath, stringifyScript(validatedScript));\n return { ok: true, script: validatedScript, filePath, message: `Saved MulmoScript to ${filePath}` };\n}\n\n/** Re-open an existing script: containment guard, existence, JSON parse,\n * schema validation — same acceptance rules as the save path so a script\n * this package saved can never be one it later refuses to load. */\nasync function loadExistingScript(context: MulmoScriptExecuteContext, filePath: string): Promise<SaveMulmoScriptOutcome> {\n if (!filePath.toLowerCase().endsWith(\".json\")) {\n return badRequest(\"filePath must point to a .json file\");\n }\n const storyPath = normalizeStoryPath(filePath);\n if (!storyPath) {\n return badRequest(\"Invalid filePath\");\n }\n if (!(await context.files.artifacts.exists(storyPath))) {\n return notFound(`File not found: ${filePath}`);\n }\n const raw = await context.files.artifacts.read(storyPath);\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n return badRequest(`Invalid JSON: ${errorMessage(err)}`);\n }\n const validation = mulmoScriptSchema.safeParse(parsed);\n if (!validation.success) {\n return badRequest(\"File is not a valid MulmoScript\");\n }\n return { ok: true, script: validation.data, filePath: storyPath, message: `Loaded MulmoScript from ${storyPath}` };\n}\n\n/**\n * Unified save-or-reopen for the presentMulmoScript tool call. `script`\n * (create new) and `filePath` (existing) are mutually exclusive. Never\n * throws on bad input — validation failures come back as discriminated\n * failures so host routes stay thin adapters. `autoGenerateMovie` is NOT\n * handled here: movie generation needs host backends (mulmocast/ffmpeg),\n * so hosts that support it trigger it from the returned `filePath`.\n */\nexport async function executeMulmoScriptSave(\n context: MulmoScriptExecuteContext,\n args: SaveMulmoScriptArgs,\n now: Date = new Date(),\n): Promise<SaveMulmoScriptOutcome> {\n const { script, filename, filePath } = args ?? {};\n const hasScript = script !== undefined && script !== null;\n const hasFilePath = typeof filePath === \"string\" && filePath !== \"\";\n if (hasScript === hasFilePath) {\n return badRequest(\n hasScript ? \"Provide either `script` or `filePath`, not both.\" : \"Provide either `script` (new presentation) or `filePath` (existing presentation).\",\n );\n }\n return hasFilePath\n ? loadExistingScript(context, filePath as string)\n : saveNewScript(context, script, typeof filename === \"string\" ? filename : undefined, now);\n}\n\n/** Resolve + guard a wire path for the update endpoints. */\nasync function resolveExistingStory(context: MulmoScriptExecuteContext, filePath: string): Promise<{ storyPath: string } | MulmoScriptFailure> {\n const storyPath = normalizeStoryPath(filePath);\n if (!storyPath) return badRequest(\"Invalid filePath\");\n if (!(await context.files.artifacts.exists(storyPath))) {\n return notFound(`File not found: ${filePath}`);\n }\n return { storyPath };\n}\n\n/** Overwrite one beat of an existing script (the View's per-beat source\n * editor). Validates the body shape + beat schema, bounds-checks the index\n * against the script on disk, and writes the whole file back. */\nexport async function executeUpdateBeat(context: MulmoScriptExecuteContext, body: unknown): Promise<UpdateMulmoScriptOutcome> {\n const validation = validateUpdateBeatBody(body);\n if (!validation.ok) return badRequest(validation.error);\n const { filePath, beatIndex, beat } = validation.value;\n\n const resolved = await resolveExistingStory(context, filePath);\n if (\"ok\" in resolved) return resolved;\n\n let script: { beats?: unknown[] };\n try {\n script = JSON.parse(await context.files.artifacts.read(resolved.storyPath));\n } catch (err) {\n return badRequest(`Invalid JSON: ${errorMessage(err)}`);\n }\n if (!Array.isArray(script.beats) || beatIndex >= script.beats.length) {\n return badRequest(\"Invalid beatIndex\");\n }\n script.beats[beatIndex] = beat;\n await context.files.artifacts.write(resolved.storyPath, stringifyScript(script));\n return { ok: true };\n}\n\n/** Overwrite the whole script (the View's full-source editor / deck-editor\n * auto-save). The body's `script` is schema-validated by\n * `validateUpdateScriptBody` before the write. */\nexport async function executeUpdateScript(context: MulmoScriptExecuteContext, body: unknown): Promise<UpdateMulmoScriptOutcome> {\n const validation = validateUpdateScriptBody(body);\n if (!validation.ok) return badRequest(validation.error);\n const { filePath, script } = validation.value;\n\n const resolved = await resolveExistingStory(context, filePath);\n if (\"ok\" in resolved) return resolved;\n\n await context.files.artifacts.write(resolved.storyPath, stringifyScript(script));\n return { ok: true };\n}\n\n/** ToolResult-shaped wrapper over `executeMulmoScriptSave` for runtime hosts\n * (e.g. MulmoTerminal's package loader), where the tool call resolves to a\n * ToolResult rather than an HTTP response. Failures are narrate-only\n * (message, no data) so the agent can self-correct. */\nexport async function executeMulmoScript(context: MulmoScriptExecuteContext, args: SaveMulmoScriptArgs): Promise<ToolResult<MulmoScriptData>> {\n const outcome = await executeMulmoScriptSave(context, args);\n if (!outcome.ok) {\n return {\n message: outcome.error,\n instructions: \"Acknowledge the error and retry with a valid `script` (new) or an existing `filePath`.\",\n };\n }\n // The tool schema advertises `autoGenerateMovie`, but movie generation\n // needs host backends (mulmocast/ffmpeg) this generic execute path\n // doesn't have — MulmoClaude honours the flag in its own save route.\n // Say so in the result rather than silently dropping the option, so the\n // agent doesn't tell the user a movie is on its way.\n const ignoredMovieNote = args.autoGenerateMovie === true ? \" (autoGenerateMovie is not supported by this host and was ignored)\" : \"\";\n return {\n message: `${outcome.message}${ignoredMovieNote}`,\n data: { script: outcome.script, filePath: outcome.filePath },\n instructions: \"Display the storyboard to the user.\",\n };\n}\n\n/** Non-Vue plugin core for runtime hosts that register the package directly.\n * MulmoClaude consumes only TOOL_DEFINITION + the execute functions in its\n * own routes, so it doesn't use this. */\nexport const pluginCore: ToolPluginCore<MulmoScriptData, MulmoScriptData, SaveMulmoScriptArgs> = {\n toolDefinition: TOOL_DEFINITION,\n execute: executeMulmoScript as unknown as ToolPluginCore<MulmoScriptData, MulmoScriptData, SaveMulmoScriptArgs>[\"execute\"],\n generatingMessage: \"Generating MulmoScript storyboard…\",\n isEnabled: () => true,\n};\n"],"mappings":"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"}
|
package/dist/server/ops.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MulmoBeat, MulmoStudioContext } from "@mulmocast/types";
|
|
2
2
|
import type { MulmoScriptGenerationEvent } from "../core/contract";
|
|
3
|
-
import type {
|
|
3
|
+
import type { GenerateOpArgsWith, MovieGenerationResult, MovieProgressEvent, MulmoScriptServerBackend, OpFailure, OpResult, PdfGenerationResult } from "./types";
|
|
4
4
|
type GenerationKind = MulmoScriptGenerationEvent["kind"];
|
|
5
5
|
export declare const PDF_MODE: "slide";
|
|
6
6
|
export declare const PDF_SIZE: "a4";
|
|
@@ -14,7 +14,7 @@ export interface RunStoryOpDeps {
|
|
|
14
14
|
buildContext?: (absoluteFilePath: string, force?: boolean) => Promise<StoryContext | undefined>;
|
|
15
15
|
}
|
|
16
16
|
export interface RunStoryOpOptions<T> {
|
|
17
|
-
force?: boolean;
|
|
17
|
+
force?: boolean | undefined;
|
|
18
18
|
/**
|
|
19
19
|
* Op-specific tag included in the failure log so dashboards can
|
|
20
20
|
* distinguish which op is failing (e.g. `"generate-beat-audio"`).
|
|
@@ -69,13 +69,13 @@ export declare function createMulmoScriptServerOps(backend: MulmoScriptServerBac
|
|
|
69
69
|
pdfStatusOp: (filePath: string) => Promise<OpResult<{
|
|
70
70
|
pdfPath: string | null;
|
|
71
71
|
}>>;
|
|
72
|
-
renderBeatOp: (args:
|
|
72
|
+
renderBeatOp: (args: GenerateOpArgsWith<"filePath" | "beatIndex">) => Promise<OpResult<{
|
|
73
73
|
image: string;
|
|
74
74
|
}>>;
|
|
75
|
-
generateBeatAudioOp: (args:
|
|
75
|
+
generateBeatAudioOp: (args: GenerateOpArgsWith<"filePath" | "beatIndex">) => Promise<OpResult<{
|
|
76
76
|
audio: string;
|
|
77
77
|
}>>;
|
|
78
|
-
renderCharacterOp: (args:
|
|
78
|
+
renderCharacterOp: (args: GenerateOpArgsWith<"filePath" | "key">) => Promise<OpResult<{
|
|
79
79
|
image: string;
|
|
80
80
|
}>>;
|
|
81
81
|
uploadBeatImageOp: (filePath: string, beatIndex: number, imageData: string) => Promise<OpResult<{
|
package/dist/server/ops.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops.d.ts","sourceRoot":"","sources":["../../src/server/ops.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,SAAS,EAAyB,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC7F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAMnE,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"ops.d.ts","sourceRoot":"","sources":["../../src/server/ops.ts"],"names":[],"mappings":"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"}
|
package/dist/server/types.d.ts
CHANGED
|
@@ -18,6 +18,14 @@ export interface GenerateOpArgs {
|
|
|
18
18
|
force?: boolean | undefined;
|
|
19
19
|
chatSessionId?: string | undefined;
|
|
20
20
|
}
|
|
21
|
+
/** `GenerateOpArgs` with `K` promoted to genuinely required. `Required<Pick<…>>`
|
|
22
|
+
* does NOT work here: under `exactOptionalPropertyTypes` the `-?` modifier drops
|
|
23
|
+
* only the `?`, leaving the explicitly declared `| undefined` in place, so the
|
|
24
|
+
* op body still sees `T | undefined`. `Omit` for the rest, because intersecting
|
|
25
|
+
* the whole interface would re-introduce the optional declaration. */
|
|
26
|
+
export type GenerateOpArgsWith<K extends keyof GenerateOpArgs> = {
|
|
27
|
+
[P in K]-?: Exclude<GenerateOpArgs[P], undefined>;
|
|
28
|
+
} & Omit<GenerateOpArgs, K>;
|
|
21
29
|
export type MovieGenerationResult = {
|
|
22
30
|
ok: true;
|
|
23
31
|
outputPath: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAEnE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,KAAK,CAAC;IACV;6CACyC;IACzC,IAAI,EAAE,aAAa,GAAG,WAAW,GAAG,cAAc,GAAG,aAAa,CAAC;IACnE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,MAAM,MAAM,qBAAqB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AACpG,MAAM,MAAM,mBAAmB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,mIAAmI;AACnI,MAAM,MAAM,oBAAoB,GAAG,aAAa,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC;mEAC+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB;yEACqE;IACrE,SAAS,EAAE,OAAO,CAAC;IACnB;gEAC4D;IAC5D,eAAe,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF;;8DAE0D;IAC1D,iBAAiB,CAAC,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC;IAC9C;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACnG,GAAG,CAAC,EAAE,oBAAoB,CAAC;CAC5B"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAEnE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,KAAK,CAAC;IACV;6CACyC;IACzC,IAAI,EAAE,aAAa,GAAG,WAAW,GAAG,cAAc,GAAG,aAAa,CAAC;IACnE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED;;;;uEAIuE;AACvE,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,MAAM,cAAc,IAAI;KAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC;CAAE,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;AAEjJ,MAAM,MAAM,qBAAqB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AACpG,MAAM,MAAM,mBAAmB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,mIAAmI;AACnI,MAAM,MAAM,oBAAoB,GAAG,aAAa,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC;mEAC+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB;yEACqE;IACrE,SAAS,EAAE,OAAO,CAAC;IACnB;gEAC4D;IAC5D,eAAe,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF;;8DAE0D;IAC1D,iBAAiB,CAAC,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC;IAC9C;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACnG,GAAG,CAAC,EAAE,oBAAoB,CAAC;CAC5B"}
|