mulmoclaude 1.2.0 → 1.4.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/README.md +1 -1
- package/client/assets/{PluginScopedRoot-CjiZ2FJI.js → PluginScopedRoot-DYk4nRpW.js} +1 -1
- package/client/assets/index-7QnbsZZr.css +2 -0
- package/client/assets/{index-yQkLdtMk.js → index-C3SRVT2p.js} +84 -85
- package/client/assets/{marp-Bs_qwoKZ.js → marp-DxctLEy1.js} +12 -3
- package/client/index.html +3 -3
- package/package.json +7 -6
- package/server/agent/attachmentConverter.ts +0 -25
- package/server/agent/mcp-server.ts +27 -8
- package/server/api/auth/viewToken.ts +16 -8
- package/server/api/routes/agent.ts +49 -11
- package/server/api/routes/collections.ts +274 -102
- package/server/api/routes/files.ts +57 -54
- package/server/api/routes/mulmo-script.ts +257 -895
- package/server/api/routes/wiki/history.ts +17 -19
- package/server/build/dispatcher.mjs +2 -2
- package/server/events/collection-change.ts +1 -5
- package/server/events/file-change.ts +1 -6
- package/server/events/pub-sub/index.ts +15 -2
- package/server/events/relay-client.ts +6 -1
- package/server/events/session-store/index.ts +11 -2
- package/server/index.ts +51 -78
- package/server/plugins/diagnostics.ts +0 -6
- package/server/plugins/mulmoscript-server.ts +58 -0
- package/server/prompts/system/system.md +2 -3
- package/server/remoteHost/handlers/getCollection.ts +5 -4
- package/server/remoteHost/handlers/getFeed.ts +10 -5
- package/server/remoteHost/handlers/googleCalendar.ts +50 -3
- package/server/remoteHost/handlers/index.ts +3 -1
- package/server/services/translation/index.ts +10 -3
- package/server/utils/errors.ts +7 -21
- package/server/utils/files/html-store.ts +2 -16
- package/server/utils/files/journal-io.ts +0 -12
- package/server/utils/files/plugins-io.ts +2 -11
- package/server/utils/files/safe.ts +39 -0
- package/server/utils/httpError.ts +6 -0
- package/server/utils/text.ts +7 -23
- package/server/utils/time.ts +0 -3
- package/server/workspace/collections/index.ts +6 -4
- package/server/workspace/collections/remoteView.ts +31 -22
- package/server/workspace/collections/watcher.ts +1 -0
- package/server/workspace/custom-dirs.ts +16 -6
- package/server/workspace/journal/state.ts +1 -9
- package/server/workspace/memory/topic-index-hook.ts +0 -9
- package/server/workspace/memory/topic-io.ts +0 -4
- package/server/workspace/reference-dirs.ts +8 -3
- package/server/workspace/skills/external/catalog.ts +24 -13
- package/server/workspace/skills/writer.ts +20 -16
- package/server/workspace/wiki-pages/snapshot.ts +13 -12
- package/src/components/SettingsGoogleTab.vue +4 -1
- package/src/components/StackView.vue +12 -1
- package/src/composables/collections/uiHost.ts +2 -1
- package/src/composables/useChatScroll.ts +18 -6
- package/src/composables/useFileDropZone.ts +3 -15
- package/src/composables/useFileSelection.ts +4 -1
- package/src/composables/useStickToBottom.ts +51 -0
- package/src/config/apiRoutes.ts +20 -0
- package/src/config/roles.ts +0 -4
- package/src/lang/de.ts +0 -30
- package/src/lang/en.ts +0 -30
- package/src/lang/es.ts +0 -30
- package/src/lang/fr.ts +0 -30
- package/src/lang/ja.ts +0 -30
- package/src/lang/ko.ts +0 -30
- package/src/lang/pt-BR.ts +0 -30
- package/src/lang/zh.ts +0 -30
- package/src/plugins/accounting/index.ts +0 -2
- package/src/plugins/api.ts +0 -6
- package/src/plugins/canvas/index.ts +0 -2
- package/src/plugins/chart/index.ts +0 -2
- package/src/plugins/editImages/index.ts +0 -2
- package/src/plugins/generateImage/index.ts +0 -2
- package/src/plugins/manageSkills/index.ts +0 -2
- package/src/plugins/markdown/index.ts +0 -2
- package/src/plugins/photoLocations/index.ts +0 -2
- package/src/plugins/presentCollection/index.ts +0 -2
- package/src/plugins/presentForm/index.ts +0 -2
- package/src/plugins/presentHtml/index.ts +0 -2
- package/src/plugins/presentMulmoScript/definition.ts +11 -116
- package/src/plugins/presentMulmoScript/index.ts +62 -12
- package/src/plugins/presentSVG/index.ts +0 -2
- package/src/plugins/skill/index.ts +0 -3
- package/src/plugins/spreadsheet/engine/parser.ts +0 -103
- package/src/plugins/spreadsheet/index.ts +0 -2
- package/src/plugins/textResponse/index.ts +0 -3
- package/src/plugins/wiki/index.ts +0 -2
- package/src/types/notification.ts +0 -10
- package/src/utils/api.ts +0 -4
- package/src/utils/dom/scrollable.ts +15 -0
- package/src/utils/errors.ts +7 -38
- package/client/assets/index-D5nJbMO1.css +0 -2
- package/server/api/routes/mulmoScriptValidate.ts +0 -101
- package/server/utils/mulmoErrorCapture.ts +0 -93
- package/src/plugins/presentMulmoScript/Preview.vue +0 -23
- package/src/plugins/presentMulmoScript/View.vue +0 -1949
- package/src/plugins/presentMulmoScript/helpers.ts +0 -206
package/src/lang/ko.ts
CHANGED
|
@@ -952,36 +952,6 @@ const koMessages = {
|
|
|
952
952
|
pluginUiImage: {
|
|
953
953
|
promptLabel: "{label}:",
|
|
954
954
|
},
|
|
955
|
-
pluginMulmoScript: {
|
|
956
|
-
beatCount: "{count}개 비트",
|
|
957
|
-
movie: "영상",
|
|
958
|
-
generating: "생성 중…",
|
|
959
|
-
rendering: "렌더링 중…",
|
|
960
|
-
saving: "저장 중…",
|
|
961
|
-
update: "업데이트",
|
|
962
|
-
characters: "캐릭터",
|
|
963
|
-
drop: "드롭",
|
|
964
|
-
gen: "생성",
|
|
965
|
-
play: "▶ 재생",
|
|
966
|
-
stop: "■ 정지",
|
|
967
|
-
playPresentation: "프레젠테이션 재생",
|
|
968
|
-
regenerateMovie: "동영상 재생성",
|
|
969
|
-
movieGenerationFailed: "동영상 생성에 실패했습니다",
|
|
970
|
-
pdf: "PDF",
|
|
971
|
-
regeneratePdf: "PDF 재생성",
|
|
972
|
-
generatingPdf: "PDF 생성 중…",
|
|
973
|
-
retry: "다시 시도",
|
|
974
|
-
errPrefix: "⚠ 오류",
|
|
975
|
-
noBeats: "스크립트에서 비트를 찾을 수 없습니다",
|
|
976
|
-
editSource: "스크립트 원본 편집",
|
|
977
|
-
applyChanges: "변경 사항 적용",
|
|
978
|
-
generateAll: "전체 생성",
|
|
979
|
-
orDropImage: "또는 이미지 드롭",
|
|
980
|
-
generate: "생성",
|
|
981
|
-
generateAudio: "♪ 생성",
|
|
982
|
-
saveErrorInvalidJson: "⚠ 잘못된 JSON: {error}",
|
|
983
|
-
saveErrorSaveFailed: "⚠ 저장 실패: {error}",
|
|
984
|
-
},
|
|
985
955
|
pluginMarkdown: {
|
|
986
956
|
loading: "문서를 불러오는 중...",
|
|
987
957
|
loadFailed: "⚠ 문서 불러오기 실패: {error}",
|
package/src/lang/pt-BR.ts
CHANGED
|
@@ -955,36 +955,6 @@ const ptBRMessages = {
|
|
|
955
955
|
pluginUiImage: {
|
|
956
956
|
promptLabel: "{label}:",
|
|
957
957
|
},
|
|
958
|
-
pluginMulmoScript: {
|
|
959
|
-
beatCount: "{count} beat | {count} beats",
|
|
960
|
-
movie: "Vídeo",
|
|
961
|
-
generating: "Gerando…",
|
|
962
|
-
rendering: "Renderizando…",
|
|
963
|
-
saving: "Salvando…",
|
|
964
|
-
update: "Atualizar",
|
|
965
|
-
characters: "Personagens",
|
|
966
|
-
drop: "Soltar",
|
|
967
|
-
gen: "Gerar",
|
|
968
|
-
play: "▶ Reproduzir",
|
|
969
|
-
stop: "■ Parar",
|
|
970
|
-
playPresentation: "Reproduzir apresentação",
|
|
971
|
-
regenerateMovie: "Regenerar vídeo",
|
|
972
|
-
movieGenerationFailed: "Falha ao gerar o vídeo",
|
|
973
|
-
pdf: "PDF",
|
|
974
|
-
regeneratePdf: "Regenerar PDF",
|
|
975
|
-
generatingPdf: "Gerando PDF…",
|
|
976
|
-
retry: "Tentar novamente",
|
|
977
|
-
errPrefix: "⚠ Erro",
|
|
978
|
-
noBeats: "Nenhum beat encontrado no script",
|
|
979
|
-
editSource: "Editar fonte do script",
|
|
980
|
-
applyChanges: "Aplicar alterações",
|
|
981
|
-
generateAll: "Gerar tudo",
|
|
982
|
-
orDropImage: "ou solte uma imagem",
|
|
983
|
-
generate: "Gerar",
|
|
984
|
-
generateAudio: "♪ Gerar",
|
|
985
|
-
saveErrorInvalidJson: "⚠ JSON inválido: {error}",
|
|
986
|
-
saveErrorSaveFailed: "⚠ Falha ao salvar: {error}",
|
|
987
|
-
},
|
|
988
958
|
pluginMarkdown: {
|
|
989
959
|
loading: "Carregando documento...",
|
|
990
960
|
loadFailed: "⚠ Falha ao carregar o documento: {error}",
|
package/src/lang/zh.ts
CHANGED
|
@@ -939,36 +939,6 @@ const zhMessages = {
|
|
|
939
939
|
pluginUiImage: {
|
|
940
940
|
promptLabel: "{label}:",
|
|
941
941
|
},
|
|
942
|
-
pluginMulmoScript: {
|
|
943
|
-
beatCount: "{count} 个 beat",
|
|
944
|
-
movie: "视频",
|
|
945
|
-
generating: "生成中…",
|
|
946
|
-
rendering: "渲染中…",
|
|
947
|
-
saving: "保存中…",
|
|
948
|
-
update: "更新",
|
|
949
|
-
characters: "角色",
|
|
950
|
-
drop: "拖放",
|
|
951
|
-
gen: "生成",
|
|
952
|
-
play: "▶ 播放",
|
|
953
|
-
stop: "■ 停止",
|
|
954
|
-
playPresentation: "播放演示",
|
|
955
|
-
regenerateMovie: "重新生成视频",
|
|
956
|
-
movieGenerationFailed: "视频生成失败",
|
|
957
|
-
pdf: "PDF",
|
|
958
|
-
regeneratePdf: "重新生成 PDF",
|
|
959
|
-
generatingPdf: "生成 PDF…",
|
|
960
|
-
retry: "重试",
|
|
961
|
-
errPrefix: "⚠ 错误",
|
|
962
|
-
noBeats: "脚本中没有找到 beat",
|
|
963
|
-
editSource: "编辑脚本源",
|
|
964
|
-
applyChanges: "应用更改",
|
|
965
|
-
generateAll: "全部生成",
|
|
966
|
-
orDropImage: "或拖入图片",
|
|
967
|
-
generate: "生成",
|
|
968
|
-
generateAudio: "♪ 生成",
|
|
969
|
-
saveErrorInvalidJson: "⚠ JSON 无效: {error}",
|
|
970
|
-
saveErrorSaveFailed: "⚠ 保存失败: {error}",
|
|
971
|
-
},
|
|
972
942
|
pluginMarkdown: {
|
|
973
943
|
loading: "正在加载文档...",
|
|
974
944
|
loadFailed: "⚠ 加载文档失败: {error}",
|
|
@@ -44,8 +44,6 @@ const accountingPlugin: ToolPlugin<AccountingActionData> = {
|
|
|
44
44
|
previewComponent: wrapWithScope("accounting", AccountingPreview),
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
export default accountingPlugin;
|
|
48
|
-
|
|
49
47
|
export const REGISTRATION: PluginRegistration = {
|
|
50
48
|
toolName: META.toolName,
|
|
51
49
|
entry: accountingPlugin,
|
package/src/plugins/api.ts
CHANGED
|
@@ -117,9 +117,3 @@ export function pluginPageRoute(name: string): string {
|
|
|
117
117
|
export function pluginAllPluginNames(): readonly string[] {
|
|
118
118
|
return requireContext().getAllPluginNames();
|
|
119
119
|
}
|
|
120
|
-
|
|
121
|
-
/** Test-only: drop the installed context so the next `installHostContext`
|
|
122
|
-
* starts from a clean slate. Production code should not call this. */
|
|
123
|
-
export function resetHostContextForTest(): void {
|
|
124
|
-
installedContext = null;
|
|
125
|
-
}
|
|
@@ -34,8 +34,6 @@ const canvasPlugin: ToolPlugin<ImageToolData> = {
|
|
|
34
34
|
viewComponent: wrapWithScope("canvas", View),
|
|
35
35
|
previewComponent: wrapWithScope("canvas", Preview),
|
|
36
36
|
};
|
|
37
|
-
|
|
38
|
-
export default canvasPlugin;
|
|
39
37
|
export { TOOL_NAME };
|
|
40
38
|
|
|
41
39
|
export const REGISTRATION: PluginRegistration = {
|
|
@@ -46,8 +46,6 @@ const chartPlugin: ToolPlugin<PresentChartData> = {
|
|
|
46
46
|
viewComponent: wrapWithScope("chart", View as unknown as Component),
|
|
47
47
|
previewComponent: wrapWithScope("chart", Preview as unknown as Component),
|
|
48
48
|
};
|
|
49
|
-
|
|
50
|
-
export default chartPlugin;
|
|
51
49
|
export { TOOL_NAME };
|
|
52
50
|
|
|
53
51
|
export const REGISTRATION: PluginRegistration = {
|
|
@@ -33,8 +33,6 @@ const editImagesPlugin: ToolPlugin<ImageToolData> = {
|
|
|
33
33
|
viewComponent: wrapWithScope("image", View),
|
|
34
34
|
previewComponent: wrapWithScope("image", Preview),
|
|
35
35
|
};
|
|
36
|
-
|
|
37
|
-
export default editImagesPlugin;
|
|
38
36
|
export { TOOL_NAME };
|
|
39
37
|
|
|
40
38
|
export const REGISTRATION: PluginRegistration = {
|
|
@@ -55,8 +55,6 @@ const generateImagePlugin: ToolPlugin<ImageToolData> = {
|
|
|
55
55
|
viewComponent: wrapWithScope("image", View),
|
|
56
56
|
previewComponent: wrapWithScope("image", Preview),
|
|
57
57
|
};
|
|
58
|
-
|
|
59
|
-
export default generateImagePlugin;
|
|
60
58
|
export { TOOL_NAME };
|
|
61
59
|
|
|
62
60
|
export const REGISTRATION: PluginRegistration = {
|
|
@@ -47,8 +47,6 @@ const manageSkillsPlugin: ToolPlugin<ManageSkillsData> = {
|
|
|
47
47
|
viewComponent: wrapWithScope("skills", View),
|
|
48
48
|
previewComponent: wrapWithScope("skills", Preview),
|
|
49
49
|
};
|
|
50
|
-
|
|
51
|
-
export default manageSkillsPlugin;
|
|
52
50
|
export { TOOL_NAME };
|
|
53
51
|
|
|
54
52
|
export const REGISTRATION: PluginRegistration = {
|
|
@@ -52,8 +52,6 @@ const markdownPlugin: ToolPlugin<MarkdownToolData> = {
|
|
|
52
52
|
viewComponent: wrapWithScope("markdown", View as unknown as Component),
|
|
53
53
|
previewComponent: wrapWithScope("markdown", Preview as unknown as Component),
|
|
54
54
|
};
|
|
55
|
-
|
|
56
|
-
export default markdownPlugin;
|
|
57
55
|
export { TOOL_NAME };
|
|
58
56
|
|
|
59
57
|
export const REGISTRATION: PluginRegistration = {
|
|
@@ -30,8 +30,6 @@ const photoLocationsPlugin: ToolPlugin<PhotoLocationsActionData> = {
|
|
|
30
30
|
viewComponent: wrapWithScope("photoLocations", View),
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export default photoLocationsPlugin;
|
|
34
|
-
|
|
35
33
|
export const REGISTRATION: PluginRegistration = {
|
|
36
34
|
toolName: META.toolName,
|
|
37
35
|
entry: photoLocationsPlugin,
|
|
@@ -17,8 +17,6 @@ const presentCollectionPlugin: ToolPlugin<PresentCollectionData, PresentCollecti
|
|
|
17
17
|
viewComponent: wrapWithScope("presentCollection", PresentCollectionView),
|
|
18
18
|
previewComponent: wrapWithScope("presentCollection", PresentCollectionPreview),
|
|
19
19
|
};
|
|
20
|
-
|
|
21
|
-
export default presentCollectionPlugin;
|
|
22
20
|
export { TOOL_NAME };
|
|
23
21
|
|
|
24
22
|
export const REGISTRATION: PluginRegistration = {
|
|
@@ -24,8 +24,6 @@ const presentFormPlugin: ToolPlugin<FormData, FormData, FormArgs> = {
|
|
|
24
24
|
viewComponent: wrapWithScope("form", pkg.viewComponent),
|
|
25
25
|
previewComponent: wrapWithScope("form", pkg.previewComponent),
|
|
26
26
|
};
|
|
27
|
-
|
|
28
|
-
export default presentFormPlugin;
|
|
29
27
|
export { TOOL_NAME };
|
|
30
28
|
|
|
31
29
|
export const REGISTRATION: PluginRegistration = {
|
|
@@ -51,8 +51,6 @@ const presentHtmlPlugin: ToolPlugin<PresentHtmlData> = {
|
|
|
51
51
|
viewComponent: wrapWithScope("html", View as unknown as Component),
|
|
52
52
|
previewComponent: wrapWithScope("html", Preview as unknown as Component),
|
|
53
53
|
};
|
|
54
|
-
|
|
55
|
-
export default presentHtmlPlugin;
|
|
56
54
|
export { TOOL_NAME };
|
|
57
55
|
|
|
58
56
|
export const REGISTRATION: PluginRegistration = {
|
|
@@ -1,122 +1,17 @@
|
|
|
1
|
-
import type { ToolDefinition } from "gui-chat-protocol";
|
|
2
1
|
import { META } from "./meta";
|
|
3
2
|
import type { ResolvedRoute } from "../meta-types";
|
|
3
|
+
import { TOOL_DEFINITION } from "@mulmoclaude/mulmoscript-plugin";
|
|
4
4
|
|
|
5
|
+
// presentMulmoScript's tool schema and save/reopen/update logic now live in
|
|
6
|
+
// the shared @mulmoclaude/mulmoscript-plugin package (single source of truth,
|
|
7
|
+
// also consumable by MulmoTerminal — plans/feat-mulmoscript-plugin.md). This
|
|
8
|
+
// built-in is a thin host adapter: it keeps MulmoClaude's host-specific
|
|
9
|
+
// routing META and View while sourcing the schema from the package.
|
|
5
10
|
export const TOOL_NAME = META.toolName;
|
|
6
|
-
export type MulmoScriptEndpoints = { readonly [K in keyof typeof META.apiRoutes]: ResolvedRoute };
|
|
7
|
-
|
|
8
|
-
const toolDefinition: ToolDefinition = {
|
|
9
|
-
type: "function",
|
|
10
|
-
name: META.toolName,
|
|
11
|
-
description: `Save and present a MulmoScript story or presentation as a visual storyboard in the canvas.
|
|
12
|
-
|
|
13
|
-
Two modes — provide EXACTLY ONE of \`script\` or \`filePath\`:
|
|
14
|
-
|
|
15
|
-
1. **Create new** — pass \`script\` (full MulmoScript JSON). Server saves it to disk and presents it.
|
|
16
|
-
2. **Re-display existing** — pass \`filePath\` (workspace-relative path returned by a previous call, e.g. "stories/my-story-1700000000000.json"). Much cheaper than re-sending the full script. Use whenever the user wants to revisit a presentation that was already created in this workspace.
|
|
17
|
-
|
|
18
|
-
Optional \`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\`.
|
|
19
11
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- \`movieParams.provider\`: \`"google"\`
|
|
24
|
-
- Do NOT add a top-level \`provider\` field to \`speechParams\` — provider belongs per-speaker only.
|
|
25
|
-
|
|
26
|
-
Required structure:
|
|
27
|
-
|
|
28
|
-
{
|
|
29
|
-
"$mulmocast": { "version": "1.1" },
|
|
30
|
-
"title": "The Life of a Star",
|
|
31
|
-
"description": "A short educational explainer about stellar evolution",
|
|
32
|
-
"lang": "en",
|
|
33
|
-
"speechParams": {
|
|
34
|
-
"speakers": {
|
|
35
|
-
"Presenter": {
|
|
36
|
-
"provider": "gemini",
|
|
37
|
-
"voiceId": "Kore",
|
|
38
|
-
"displayName": { "en": "Presenter" }
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
"imageParams": { "provider": "google", "model": "gemini-3.1-flash-image-preview" },
|
|
43
|
-
"movieParams": { "provider": "google", "model": "veo-3.1-generate" },
|
|
44
|
-
"beats": [
|
|
45
|
-
{
|
|
46
|
-
"speaker": "Presenter",
|
|
47
|
-
"text": "Narration spoken aloud for this beat.",
|
|
48
|
-
"imagePrompt": "Detailed description — AI generates the image"
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
"speaker": "Presenter",
|
|
52
|
-
"text": "Bullet point beat.",
|
|
53
|
-
"image": { "type": "textSlide", "slide": { "title": "Slide Title", "bullets": ["Point one", "Point two"] } }
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"speaker": "Presenter",
|
|
57
|
-
"text": "Markdown beat.",
|
|
58
|
-
"image": { "type": "markdown", "markdown": "## Heading\\n\\nBody text here." }
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
"speaker": "Presenter",
|
|
62
|
-
"text": "Chart beat — use for data, comparisons, trends.",
|
|
63
|
-
"image": { "type": "chart", "title": "Chart Title", "chartData": { "type": "bar", "data": { "labels": ["A", "B", "C"], "datasets": [{ "label": "Series", "data": [10, 20, 30] }] } } }
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"speaker": "Presenter",
|
|
67
|
-
"text": "Diagram beat — use for flows, architectures, relationships.",
|
|
68
|
-
"image": { "type": "mermaid", "title": "Diagram Title", "code": { "kind": "text", "text": "graph TD\\n A[Start] --> B[Process] --> C[End]" } }
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
"speaker": "Presenter",
|
|
72
|
-
"text": "Rich interactive beat — use for custom layouts, animations, or anything that benefits from HTML/CSS.",
|
|
73
|
-
"image": { "type": "html_tailwind", "html": "<div class=\\"flex items-center justify-center h-full text-4xl font-bold text-blue-600\\">Hello World</div>" }
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
"speaker": "Presenter",
|
|
77
|
-
"text": "AI video beat.",
|
|
78
|
-
"moviePrompt": "Detailed description — AI generates the video clip"
|
|
79
|
-
}
|
|
80
|
-
]
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
Beat visual options (choose one per beat):
|
|
84
|
-
- "imagePrompt": "..." → top-level string field — AI generates an image from the prompt
|
|
85
|
-
- "moviePrompt": "..." → top-level string field — AI generates a video clip from the prompt
|
|
86
|
-
- "image": { "type": "textSlide", "slide": { "title", "subtitle"?, "bullets"? } }
|
|
87
|
-
- "image": { "type": "markdown", "markdown": "..." }
|
|
88
|
-
- "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.
|
|
89
|
-
- "image": { "type": "mermaid", "title": "...", "code": { "kind": "text", "text": "..." } } ← PREFER for flows/diagrams/relationships
|
|
90
|
-
- "image": { "type": "html_tailwind", "html": "...", "script"?: "..." } ← PREFER for rich layouts, animations, custom visuals
|
|
91
|
-
|
|
92
|
-
IMPORTANT: "imagePrompt" and "moviePrompt" are plain string fields on the beat, NOT nested under "image".`,
|
|
93
|
-
parameters: {
|
|
94
|
-
type: "object",
|
|
95
|
-
properties: {
|
|
96
|
-
script: {
|
|
97
|
-
type: "object",
|
|
98
|
-
description:
|
|
99
|
-
"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`.",
|
|
100
|
-
additionalProperties: true,
|
|
101
|
-
},
|
|
102
|
-
filename: {
|
|
103
|
-
type: "string",
|
|
104
|
-
description:
|
|
105
|
-
"Optional filename without extension. Defaults to a slug of the script title. Only meaningful with `script`; ignored when `filePath` is given.",
|
|
106
|
-
},
|
|
107
|
-
filePath: {
|
|
108
|
-
type: "string",
|
|
109
|
-
description:
|
|
110
|
-
"Workspace-relative path to an EXISTING MulmoScript JSON file (e.g. 'stories/my-story-1700000000000.json'). Use this to re-display a script previously saved in this workspace, instead of resending the full JSON. Do NOT pass alongside `script`.",
|
|
111
|
-
},
|
|
112
|
-
autoGenerateMovie: {
|
|
113
|
-
type: "boolean",
|
|
114
|
-
description:
|
|
115
|
-
"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.",
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
required: [],
|
|
119
|
-
},
|
|
120
|
-
};
|
|
12
|
+
/** Resolved-URL view of the mulmoScript plugin's routes. Plugin code reads
|
|
13
|
+
* `endpoints.<route>.{method, url}` to drive `apiCall`. Auto-derived from META. */
|
|
14
|
+
export type MulmoScriptEndpoints = { readonly [K in keyof typeof META.apiRoutes]: ResolvedRoute };
|
|
121
15
|
|
|
122
|
-
export
|
|
16
|
+
export { TOOL_DEFINITION };
|
|
17
|
+
export default TOOL_DEFINITION;
|
|
@@ -1,21 +1,73 @@
|
|
|
1
|
+
// MulmoClaude's thin built-in adapter for the shared mulmoscript plugin
|
|
2
|
+
// (plans/feat-mulmoscript-plugin.md phase 2). View / Preview /
|
|
3
|
+
// TOOL_DEFINITION come from @mulmoclaude/mulmoscript-plugin; the View
|
|
4
|
+
// reaches host backends via useRuntime().dispatch → the built-in
|
|
5
|
+
// "mulmoScript" dispatch handler (server/plugins/mulmoscript-server.ts)
|
|
6
|
+
// and hears generation progress on the plugin pubsub channel. This adapter
|
|
7
|
+
// keeps MulmoClaude's existing tool-call create path (POST
|
|
8
|
+
// /api/mulmoScript/save) and injects the two host-transport capabilities
|
|
9
|
+
// the generic runtime can't carry: the active chat session id (sidebar
|
|
10
|
+
// generation indicator) and the bearer-authenticated media download.
|
|
11
|
+
import { computed, defineComponent, h, markRaw, provide, type Component } from "vue";
|
|
1
12
|
import type { PluginRegistration, ToolPlugin } from "../../tools/types";
|
|
2
13
|
import type { ToolResult } from "gui-chat-protocol";
|
|
3
|
-
import type
|
|
14
|
+
import { View, Preview, MULMOSCRIPT_HOST_ADAPTER_KEY, type MulmoScriptData, type MulmoScriptHostAdapter } from "@mulmoclaude/mulmoscript-plugin/vue";
|
|
15
|
+
// Lib mode doesn't auto-inject the package's compiled styles; the consumer
|
|
16
|
+
// must import them — same as @mulmoclaude/{markdown,form,chart,html}-plugin.
|
|
17
|
+
import "@mulmoclaude/mulmoscript-plugin/style.css";
|
|
4
18
|
import toolDefinition, { TOOL_NAME, type MulmoScriptEndpoints } from "./definition";
|
|
5
19
|
import { pluginEndpoints } from "../api";
|
|
6
20
|
import { wrapWithScope } from "../scope";
|
|
7
|
-
import
|
|
8
|
-
import Preview from "./Preview.vue";
|
|
9
|
-
import { apiCall } from "../../utils/api";
|
|
21
|
+
import { apiCall, apiFetchRaw } from "../../utils/api";
|
|
10
22
|
import { makeUuid } from "../../utils/id";
|
|
23
|
+
import { useActiveSession } from "../../composables/useActiveSession";
|
|
11
24
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
// Re-exported from the shared package so anything importing the result-data
|
|
26
|
+
// shape from "./index" keeps working while the type stays single-sourced.
|
|
27
|
+
export type { MulmoScriptData };
|
|
28
|
+
|
|
29
|
+
// Bearer-authenticated media download over the host's existing
|
|
30
|
+
// download routes. A plain <video src> / <a href download> can't attach
|
|
31
|
+
// the Authorization header, and the routes stay behind the standard
|
|
32
|
+
// /api/* bearer guard by explicit review decision — so the package View
|
|
33
|
+
// fetches bytes through this injected capability instead.
|
|
34
|
+
async function fetchMediaBlob(query: { moviePath?: string; pdfPath?: string }): Promise<Blob> {
|
|
35
|
+
const endpoints = pluginEndpoints<MulmoScriptEndpoints>("mulmoScript");
|
|
36
|
+
const target = query.pdfPath
|
|
37
|
+
? { url: endpoints.downloadPdf.url, query: { pdfPath: query.pdfPath } }
|
|
38
|
+
: { url: endpoints.downloadMovie.url, query: { moviePath: query.moviePath ?? "" } };
|
|
39
|
+
const res = await apiFetchRaw(target.url, { method: "GET", query: target.query });
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
throw new Error(`HTTP ${res.status}`);
|
|
42
|
+
}
|
|
43
|
+
return res.blob();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Provide the package's host-adapter injection around the View —
|
|
47
|
+
* mounted INSIDE wrapWithScope's PluginScopedRoot, forwarding every
|
|
48
|
+
* prop / attr / slot through verbatim (same shape as wrapWithScope). */
|
|
49
|
+
function withHostAdapter<TInner extends Component>(inner: TInner): TInner {
|
|
50
|
+
return markRaw(
|
|
51
|
+
defineComponent({
|
|
52
|
+
name: "MulmoScriptHostAdapter",
|
|
53
|
+
inheritAttrs: false,
|
|
54
|
+
setup(_props, { attrs, slots }) {
|
|
55
|
+
const activeSessionRef = useActiveSession();
|
|
56
|
+
const adapter: MulmoScriptHostAdapter = {
|
|
57
|
+
chatSessionId: computed(() => activeSessionRef?.value?.id),
|
|
58
|
+
fetchMediaBlob,
|
|
59
|
+
};
|
|
60
|
+
provide(MULMOSCRIPT_HOST_ADAPTER_KEY, adapter);
|
|
61
|
+
return () => h(inner, attrs, slots);
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
) as unknown as TInner;
|
|
15
65
|
}
|
|
16
66
|
|
|
17
67
|
const presentMulmoScriptPlugin: ToolPlugin<MulmoScriptData> = {
|
|
18
|
-
|
|
68
|
+
// gui-chat-protocol type is externalized but yarn-4's dual-@vue can
|
|
69
|
+
// make the package's nominal types distinct; coerce once here.
|
|
70
|
+
toolDefinition: toolDefinition as ToolPlugin<MulmoScriptData>["toolDefinition"],
|
|
19
71
|
|
|
20
72
|
// Pass-through: the agent (MCP) and GUI dispatcher both end up at the
|
|
21
73
|
// same backend route, which dispatches between create-new (`script`)
|
|
@@ -42,12 +94,10 @@ const presentMulmoScriptPlugin: ToolPlugin<MulmoScriptData> = {
|
|
|
42
94
|
|
|
43
95
|
isEnabled: () => true,
|
|
44
96
|
generatingMessage: "Generating MulmoScript storyboard…",
|
|
45
|
-
viewComponent: wrapWithScope("mulmoScript", View),
|
|
46
|
-
previewComponent: wrapWithScope("mulmoScript", Preview),
|
|
97
|
+
viewComponent: wrapWithScope("mulmoScript", withHostAdapter(View as unknown as Component)),
|
|
98
|
+
previewComponent: wrapWithScope("mulmoScript", Preview as unknown as Component),
|
|
47
99
|
};
|
|
48
100
|
|
|
49
|
-
export default presentMulmoScriptPlugin;
|
|
50
|
-
|
|
51
101
|
export const REGISTRATION: PluginRegistration = {
|
|
52
102
|
toolName: TOOL_NAME,
|
|
53
103
|
entry: presentMulmoScriptPlugin,
|
|
@@ -39,8 +39,6 @@ const presentSvgPlugin: ToolPlugin<PresentSvgData> = {
|
|
|
39
39
|
viewComponent: wrapWithScope("svg", View),
|
|
40
40
|
previewComponent: wrapWithScope("svg", Preview),
|
|
41
41
|
};
|
|
42
|
-
|
|
43
|
-
export default presentSvgPlugin;
|
|
44
42
|
export { TOOL_NAME };
|
|
45
43
|
|
|
46
44
|
export const REGISTRATION: PluginRegistration = {
|
|
@@ -38,106 +38,3 @@ export function indexToColumn(index: number): string {
|
|
|
38
38
|
}
|
|
39
39
|
return col;
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Parse a cell reference to its components
|
|
44
|
-
* Supports: A1, $A$1, $A1, A$1, Sheet1!A1, 'My Sheet'!A1
|
|
45
|
-
*
|
|
46
|
-
* @param ref - Cell reference string
|
|
47
|
-
* @returns Parsed cell reference object
|
|
48
|
-
*/
|
|
49
|
-
export function parseCellRef(ref: string): CellRef {
|
|
50
|
-
let cellRef = ref;
|
|
51
|
-
let sheetName: string | undefined;
|
|
52
|
-
|
|
53
|
-
// Check for cross-sheet reference (e.g., 'Sheet Name'!B2 or Sheet1!B2)
|
|
54
|
-
const sheetMatch = ref.match(/^(?:'([^']+)'|([^!]+))!(.+)$/);
|
|
55
|
-
if (sheetMatch) {
|
|
56
|
-
sheetName = sheetMatch[1] || sheetMatch[2]; // Quoted or unquoted sheet name
|
|
57
|
-
cellRef = sheetMatch[3]; // Cell reference part
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Parse absolute references ($A$1)
|
|
61
|
-
const absoluteRow = cellRef.includes("$") && cellRef.match(/\$\d+/);
|
|
62
|
-
const absoluteCol = cellRef.includes("$") && cellRef.match(/\$[A-Z]+/);
|
|
63
|
-
|
|
64
|
-
// Remove $ symbols
|
|
65
|
-
const cleanRef = cellRef.replace(/\$/g, "");
|
|
66
|
-
const match = cleanRef.match(/^([A-Z]+)(\d+)$/);
|
|
67
|
-
|
|
68
|
-
if (!match) {
|
|
69
|
-
throw new Error(`Invalid cell reference: ${ref}`);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const col = columnToIndex(match[1]);
|
|
73
|
-
const row = parseInt(match[2]) - 1; // 1-indexed to 0-indexed
|
|
74
|
-
|
|
75
|
-
const result: CellRef = { row, col };
|
|
76
|
-
|
|
77
|
-
if (sheetName) {
|
|
78
|
-
result.sheet = sheetName;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (absoluteRow || absoluteCol) {
|
|
82
|
-
result.absolute = {
|
|
83
|
-
row: !!absoluteRow,
|
|
84
|
-
col: !!absoluteCol,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Parse a range reference to its components
|
|
93
|
-
* Supports: A1:B10, $A$1:$B$10, Sheet1!A1:B10
|
|
94
|
-
*
|
|
95
|
-
* @param range - Range reference string
|
|
96
|
-
* @returns Parsed range reference object
|
|
97
|
-
*/
|
|
98
|
-
export function parseRangeRef(range: string): RangeRef {
|
|
99
|
-
// Use non-greedy match to improve performance
|
|
100
|
-
const colonIndex = range.lastIndexOf(":");
|
|
101
|
-
if (colonIndex === -1) {
|
|
102
|
-
throw new Error(`Invalid range reference: ${range}`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const start = parseCellRef(range.substring(0, colonIndex));
|
|
106
|
-
const end = parseCellRef(range.substring(colonIndex + 1));
|
|
107
|
-
|
|
108
|
-
return { start, end };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Convert a cell reference object back to A1 notation
|
|
113
|
-
*
|
|
114
|
-
* @param ref - Cell reference object
|
|
115
|
-
* @returns A1 notation string
|
|
116
|
-
*/
|
|
117
|
-
export function cellRefToA1(ref: CellRef): string {
|
|
118
|
-
const col = indexToColumn(ref.col);
|
|
119
|
-
const row = ref.row + 1; // 0-based to 1-based
|
|
120
|
-
|
|
121
|
-
let result = "";
|
|
122
|
-
|
|
123
|
-
if (ref.absolute?.col) {
|
|
124
|
-
result += "$";
|
|
125
|
-
}
|
|
126
|
-
result += col;
|
|
127
|
-
|
|
128
|
-
if (ref.absolute?.row) {
|
|
129
|
-
result += "$";
|
|
130
|
-
}
|
|
131
|
-
result += row;
|
|
132
|
-
|
|
133
|
-
if (ref.sheet) {
|
|
134
|
-
// Quote sheet name if it contains spaces
|
|
135
|
-
if (ref.sheet.includes(" ")) {
|
|
136
|
-
result = `'${ref.sheet}'!${result}`;
|
|
137
|
-
} else {
|
|
138
|
-
result = `${ref.sheet}!${result}`;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return result;
|
|
143
|
-
}
|
|
@@ -34,8 +34,6 @@ const spreadsheetPlugin: ToolPlugin<SpreadsheetToolData> = {
|
|
|
34
34
|
viewComponent: wrapWithScope("spreadsheet", View),
|
|
35
35
|
previewComponent: wrapWithScope("spreadsheet", Preview),
|
|
36
36
|
};
|
|
37
|
-
|
|
38
|
-
export default spreadsheetPlugin;
|
|
39
37
|
export { TOOL_NAME };
|
|
40
38
|
|
|
41
39
|
export const REGISTRATION: PluginRegistration = {
|
|
@@ -25,16 +25,6 @@ export const NOTIFICATION_KINDS = {
|
|
|
25
25
|
|
|
26
26
|
export type NotificationKind = (typeof NOTIFICATION_KINDS)[keyof typeof NOTIFICATION_KINDS];
|
|
27
27
|
|
|
28
|
-
export const NOTIFICATION_ICONS: Record<NotificationKind, string> = {
|
|
29
|
-
todo: "check_circle",
|
|
30
|
-
scheduler: "event",
|
|
31
|
-
agent: "smart_toy",
|
|
32
|
-
journal: "auto_stories",
|
|
33
|
-
push: "notifications",
|
|
34
|
-
bridge: "chat",
|
|
35
|
-
system: "warning",
|
|
36
|
-
};
|
|
37
|
-
|
|
38
28
|
export const NOTIFICATION_ACTION_TYPES = {
|
|
39
29
|
navigate: "navigate",
|
|
40
30
|
none: "none",
|
package/src/utils/api.ts
CHANGED
|
@@ -226,10 +226,6 @@ export function apiPut<T = unknown>(path: string, body?: unknown, extra: Omit<Ap
|
|
|
226
226
|
return apiCall<T>(path, { ...extra, method: "PUT", body });
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
export function apiPatch<T = unknown>(path: string, body?: unknown, extra: Omit<ApiOptions, "method" | "body"> = {}): Promise<ApiResult<T>> {
|
|
230
|
-
return apiCall<T>(path, { ...extra, method: "PATCH", body });
|
|
231
|
-
}
|
|
232
|
-
|
|
233
229
|
export function apiDelete<T = unknown>(path: string, body?: unknown, extra: Omit<ApiOptions, "method" | "body"> = {}): Promise<ApiResult<T>> {
|
|
234
230
|
return apiCall<T>(path, { ...extra, method: "DELETE", body });
|
|
235
231
|
}
|