mulmoclaude 2.6.0 → 2.8.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.
Files changed (86) hide show
  1. package/client/assets/Graph-ChAxkn9o.js +1 -0
  2. package/client/assets/{JsonEditor-FKfzzMkt.js → JsonEditor-COQcmjLQ.js} +1 -1
  3. package/client/assets/dist-Bij44LdC.js +2 -0
  4. package/client/assets/echarts-GV3U9sVf.js +1 -0
  5. package/client/assets/echarts-gl-BXx4On0Y.js +4282 -0
  6. package/client/assets/echarts-wordcloud-_9uQUAfp.js +1 -0
  7. package/client/assets/emscripten-module-Q67P5WYC-BU3Hlttz.js +2160 -0
  8. package/client/assets/ffi-4V2b7xu3.js +1 -0
  9. package/client/assets/{html2canvas-D0m9Y8pD-y1tDAhR3.js → html2canvas-NBfz6Rb9-CIdEBoH2.js} +1 -1
  10. package/client/assets/{index-gnbD2OHU.js → index-D6xzCZhP.js} +605 -604
  11. package/client/assets/index-DzMsXDnj.css +2 -0
  12. package/client/assets/{index.es-HSMDtZ_a-CYtNwG_G.js → index.es-STZX6pYP-BGvt3MI2.js} +3 -3
  13. package/client/assets/install-C1moNcrn.js +15 -0
  14. package/client/assets/japan-CyDc-cz7-BgORDJsl.js +1 -0
  15. package/client/assets/{marp-BVojbGre.js → marp-Du5Sym5b.js} +57 -57
  16. package/client/assets/module-ES6BEMUI-CDTIpMiA.js +6 -0
  17. package/client/assets/purify.es-DkTnAgLw-BZxk_wWO.js +3 -0
  18. package/client/assets/style-BZwVK1dO-BFtilmZh.js +1 -0
  19. package/client/assets/vue-i18n-YREco4q1.js +3 -0
  20. package/client/assets/world-R-tMZ4nZ-B1c4T9X2.js +1 -0
  21. package/client/index.html +6 -4
  22. package/package.json +13 -12
  23. package/server/api/routes/plugins.ts +9 -59
  24. package/server/build/mcp-server.mjs +806 -714
  25. package/server/index.ts +1 -0
  26. package/server/plugins/confined-file-ops.ts +56 -0
  27. package/server/plugins/spreadsheet-builtin.ts +26 -0
  28. package/server/plugins/spreadsheet-save-outcome.ts +7 -0
  29. package/server/utils/files/store-resolvers.ts +1 -1
  30. package/src/lang/de.ts +0 -20
  31. package/src/lang/en.ts +0 -20
  32. package/src/lang/es.ts +0 -20
  33. package/src/lang/fr.ts +0 -20
  34. package/src/lang/ja.ts +0 -20
  35. package/src/lang/ko.ts +0 -20
  36. package/src/lang/pt-BR.ts +0 -20
  37. package/src/lang/zh.ts +0 -20
  38. package/src/plugins/spreadsheet/definition.ts +9 -127
  39. package/src/plugins/spreadsheet/index.ts +7 -4
  40. package/src/plugins/spreadsheet/meta.ts +2 -4
  41. package/client/assets/index-BA0fpWIZ.css +0 -2
  42. package/client/assets/purify.es-BaSjDiWD-Dbm5oOcm.js +0 -3
  43. package/client/assets/style-CCV5GAnU-D55tgtzm.js +0 -1
  44. package/client/assets/vue-i18n-2bgWpywp.js +0 -3
  45. package/server/utils/files/spreadsheet-store.ts +0 -26
  46. package/src/plugins/spreadsheet/Preview.vue +0 -31
  47. package/src/plugins/spreadsheet/View.vue +0 -1007
  48. package/src/plugins/spreadsheet/cellHighlights.ts +0 -78
  49. package/src/plugins/spreadsheet/engine/calculator.ts +0 -427
  50. package/src/plugins/spreadsheet/engine/cellBuilder.ts +0 -89
  51. package/src/plugins/spreadsheet/engine/cellEmpty.ts +0 -26
  52. package/src/plugins/spreadsheet/engine/cellFormatting.ts +0 -59
  53. package/src/plugins/spreadsheet/engine/coerce-boolean.ts +0 -26
  54. package/src/plugins/spreadsheet/engine/condition.ts +0 -182
  55. package/src/plugins/spreadsheet/engine/date-locale.ts +0 -47
  56. package/src/plugins/spreadsheet/engine/date-parser.ts +0 -234
  57. package/src/plugins/spreadsheet/engine/date-utils.ts +0 -73
  58. package/src/plugins/spreadsheet/engine/datedif.ts +0 -93
  59. package/src/plugins/spreadsheet/engine/engine.ts +0 -177
  60. package/src/plugins/spreadsheet/engine/evaluator.ts +0 -494
  61. package/src/plugins/spreadsheet/engine/financial-math.ts +0 -114
  62. package/src/plugins/spreadsheet/engine/formatter.ts +0 -158
  63. package/src/plugins/spreadsheet/engine/formulaError.ts +0 -67
  64. package/src/plugins/spreadsheet/engine/formulaRefs.ts +0 -202
  65. package/src/plugins/spreadsheet/engine/functions/date.ts +0 -212
  66. package/src/plugins/spreadsheet/engine/functions/financial.ts +0 -244
  67. package/src/plugins/spreadsheet/engine/functions/index.ts +0 -16
  68. package/src/plugins/spreadsheet/engine/functions/logical.ts +0 -221
  69. package/src/plugins/spreadsheet/engine/functions/lookup-math.ts +0 -28
  70. package/src/plugins/spreadsheet/engine/functions/lookup.ts +0 -257
  71. package/src/plugins/spreadsheet/engine/functions/mathematical.ts +0 -283
  72. package/src/plugins/spreadsheet/engine/functions/statistical-math.ts +0 -91
  73. package/src/plugins/spreadsheet/engine/functions/statistical.ts +0 -290
  74. package/src/plugins/spreadsheet/engine/functions/text.ts +0 -398
  75. package/src/plugins/spreadsheet/engine/index.ts +0 -34
  76. package/src/plugins/spreadsheet/engine/jsonCellLocator.ts +0 -111
  77. package/src/plugins/spreadsheet/engine/math-ops.ts +0 -98
  78. package/src/plugins/spreadsheet/engine/numericCoercion.ts +0 -61
  79. package/src/plugins/spreadsheet/engine/parser.ts +0 -40
  80. package/src/plugins/spreadsheet/engine/registry.ts +0 -201
  81. package/src/plugins/spreadsheet/engine/responseDecoder.ts +0 -68
  82. package/src/plugins/spreadsheet/engine/spreadsheet-errors.ts +0 -82
  83. package/src/plugins/spreadsheet/engine/textFormat.ts +0 -108
  84. package/src/plugins/spreadsheet/engine/translateFormula.ts +0 -69
  85. package/src/plugins/spreadsheet/engine/types.ts +0 -81
  86. package/src/plugins/spreadsheet/keyboardNav.ts +0 -38
package/server/index.ts CHANGED
@@ -55,6 +55,7 @@ import "./plugins/markdown-builtin.js";
55
55
  // Side-effect: registers the built-in "html" dispatch handler so the
56
56
  // presentHtml View's useRuntime().dispatch({ kind }) resolves (phase 2).
57
57
  import "./plugins/html-builtin.js";
58
+ import "./plugins/spreadsheet-builtin.js";
58
59
  // Side-effect: registers the built-in "shapescript" dispatch handler so the
59
60
  // presentShapeScript View's useRuntime().dispatch({ kind }) resolves — the
60
61
  // source editor's load / save against artifacts/shapes/.
@@ -0,0 +1,56 @@
1
+ // FileOps that refuse a path whose real location (symlinks followed) leaves the scope root.
2
+ // `makeArtifactsFileOps` folds `..` lexically only, so a symlink inside the root still reaches out.
3
+
4
+ import path from "node:path";
5
+ import { mkdir, readFile, readdir, realpath, stat as fsStat, unlink as fsUnlink } from "node:fs/promises";
6
+ import type { FileOps } from "gui-chat-protocol";
7
+ import { writeFileAtomic } from "../utils/files/atomic.js";
8
+ import { resolveWithinRoot, resolveWriteWithinRoot } from "../utils/files/safe.js";
9
+ import { workspacePath } from "../workspace/paths.js";
10
+
11
+ /** An escape reads as a missing file, so callers that treat ENOENT as "not there" stay closed. */
12
+ const notInScope = (rel: string): Error & { code: string } => Object.assign(new Error(`not inside the scope: ${rel}`), { code: "ENOENT" });
13
+
14
+ const realRoot = async (scopeRoot: string): Promise<string> => {
15
+ await mkdir(scopeRoot, { recursive: true });
16
+ return realpath(scopeRoot);
17
+ };
18
+
19
+ const existingPath = async (scopeRoot: string, rel: string): Promise<string | null> => resolveWithinRoot(await realRoot(scopeRoot), rel);
20
+
21
+ const requireExisting = async (scopeRoot: string, rel: string): Promise<string> => {
22
+ const resolved = await existingPath(scopeRoot, rel);
23
+ if (resolved === null) throw notInScope(rel);
24
+ return resolved;
25
+ };
26
+
27
+ export function makeConfinedFileOps(scopeRoot: string): FileOps {
28
+ return {
29
+ read: async (rel) => readFile(await requireExisting(scopeRoot, rel), "utf-8"),
30
+ readBytes: async (rel) => {
31
+ const buf = await readFile(await requireExisting(scopeRoot, rel));
32
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
33
+ },
34
+ write: async (rel, content) => {
35
+ const target = await resolveWriteWithinRoot(await realRoot(scopeRoot), rel);
36
+ if (target === null) throw new Error(`refusing to write outside the scope: ${rel}`);
37
+ await writeFileAtomic(target, content);
38
+ },
39
+ readDir: async (rel) => {
40
+ const resolved = await existingPath(scopeRoot, rel);
41
+ return resolved === null ? [] : readdir(resolved);
42
+ },
43
+ stat: async (rel) => {
44
+ const { mtimeMs, size } = await fsStat(await requireExisting(scopeRoot, rel));
45
+ return { mtimeMs, size };
46
+ },
47
+ exists: async (rel) => (await existingPath(scopeRoot, rel)) !== null,
48
+ unlink: async (rel) => {
49
+ const resolved = await existingPath(scopeRoot, rel);
50
+ if (resolved !== null) await fsUnlink(resolved);
51
+ },
52
+ };
53
+ }
54
+
55
+ /** The workspace `artifacts/` dir, confined as above. */
56
+ export const makeConfinedArtifactsFileOps = (): FileOps => makeConfinedFileOps(path.join(workspacePath, "artifacts"));
@@ -0,0 +1,26 @@
1
+ // MulmoClaude's host wiring for the presentSpreadsheet View's dispatch channel.
2
+ // The @gui-chat-plugin/spreadsheet View loads and saves its workbook through
3
+ // `useRuntime().dispatch({ kind: "loadSpreadsheet" | "saveSpreadsheet", … })`;
4
+ // this routes those calls to the package's `executeSpreadsheetDispatch` against
5
+ // the workspace `artifacts/` (confined by realpath, so a symlink cannot lead out), then publishes a file-change event
6
+ // after a save that wrote the file. Imported for side effect at boot (server/index.ts).
7
+
8
+ import { executeSpreadsheetDispatch, isSpreadsheetDispatchArgs } from "@gui-chat-plugin/spreadsheet";
9
+ import { makeConfinedArtifactsFileOps } from "./confined-file-ops.js";
10
+ import { publishFileChange } from "../events/file-change.js";
11
+ import { describeKind, registerBuiltinDispatch } from "./builtin-dispatch.js";
12
+ import { wroteSpreadsheetFile } from "./spreadsheet-save-outcome.js";
13
+
14
+ /** Matches `wrapWithScope("spreadsheet", …)` in `src/plugins/spreadsheet/index.ts`. */
15
+ const SPREADSHEET_SCOPE = "spreadsheet";
16
+
17
+ registerBuiltinDispatch(SPREADSHEET_SCOPE, async (args) => {
18
+ if (!isSpreadsheetDispatchArgs(args)) {
19
+ throw new Error(`spreadsheet plugin: unrecognised dispatch payload (kind=${describeKind(args)})`);
20
+ }
21
+ const result = await executeSpreadsheetDispatch(makeConfinedArtifactsFileOps(), args);
22
+ if (wroteSpreadsheetFile(args, result.data)) {
23
+ void publishFileChange(args.path);
24
+ }
25
+ return result;
26
+ });
@@ -0,0 +1,7 @@
1
+ // Whether a spreadsheet dispatch changed the file on disk, so the host should announce it.
2
+ // A save refused for a conflict (the file moved on since the View's baseVersion) wrote nothing.
3
+
4
+ import type { SpreadsheetDispatchArgs, SpreadsheetFileData } from "@gui-chat-plugin/spreadsheet";
5
+
6
+ export const wroteSpreadsheetFile = (args: SpreadsheetDispatchArgs, data: SpreadsheetFileData | undefined): boolean =>
7
+ args.kind === "saveSpreadsheet" && data !== undefined && data.conflict !== true;
@@ -1,5 +1,5 @@
1
1
  // Shared resolver factory for file-store modules (attachment-store,
2
- // image-store, spreadsheet-store). Each store used to define its own
2
+ // image-store). Each store used to define its own
3
3
  // `safeResolve(relativePath)` wrapper of identical shape: ensure the
4
4
  // root dir exists, cache its realpath, strip the leading store
5
5
  // prefix, hand off to `resolveWithinRoot`. That left three name-cloned
package/src/lang/de.ts CHANGED
@@ -1099,26 +1099,6 @@ const deMessages = {
1099
1099
  pluginSkill: {
1100
1100
  noDescription: "(keine Beschreibung)",
1101
1101
  },
1102
- pluginSpreadsheet: {
1103
- previewUntitled: "Tabelle",
1104
- previewSheets: "{count} Blatt | {count} Blätter",
1105
- untitled: "Tabelle",
1106
- excel: "Excel",
1107
- valuePlaceholder: "Wert",
1108
- valueOrFormulaPlaceholder: "Wert oder Formel (z. B. 100 oder SUM(B2:B11))",
1109
- formatPlaceholder: "Format (z. B. $#,##0.00)",
1110
- loading: "Tabelle wird geladen...",
1111
- noData: "Keine Tabellendaten verfügbar",
1112
- editData: "Tabellendaten bearbeiten",
1113
- applyChanges: "Änderungen übernehmen",
1114
- dataMustBeArray: "Daten müssen ein Array von Blättern sein",
1115
- loadFailed: "Laden der Tabelle fehlgeschlagen: {error}",
1116
- invalidJsonAlert: "Ungültiges JSON-Format: {error}",
1117
- unknownError: "Unbekannter Fehler",
1118
- update: "Aktualisieren",
1119
- stringType: "Zeichenkette",
1120
- formulaType: "Formel",
1121
- },
1122
1102
  app: {
1123
1103
  startConversation: "Gespräch beginnen",
1124
1104
  thinking: "Denkt nach…",
package/src/lang/en.ts CHANGED
@@ -1109,26 +1109,6 @@ const enMessages = {
1109
1109
  pluginSkill: {
1110
1110
  noDescription: "(no description)",
1111
1111
  },
1112
- pluginSpreadsheet: {
1113
- previewUntitled: "Spreadsheet",
1114
- previewSheets: "{count} sheet | {count} sheets",
1115
- untitled: "Spreadsheet",
1116
- excel: "Excel",
1117
- valuePlaceholder: "Value",
1118
- valueOrFormulaPlaceholder: "Value or Formula (e.g., 100 or SUM(B2:B11))",
1119
- formatPlaceholder: "Format (e.g., $#,##0.00)",
1120
- loading: "Loading spreadsheet...",
1121
- noData: "No spreadsheet data available",
1122
- editData: "Edit Spreadsheet Data",
1123
- applyChanges: "Apply Changes",
1124
- dataMustBeArray: "Data must be an array of sheets",
1125
- loadFailed: "Failed to load spreadsheet: {error}",
1126
- invalidJsonAlert: "Invalid JSON format: {error}",
1127
- unknownError: "Unknown error",
1128
- update: "Update",
1129
- stringType: "String",
1130
- formulaType: "Formula",
1131
- },
1132
1112
  app: {
1133
1113
  startConversation: "Start a conversation",
1134
1114
  thinking: "Thinking…",
package/src/lang/es.ts CHANGED
@@ -1097,26 +1097,6 @@ const esMessages = {
1097
1097
  pluginSkill: {
1098
1098
  noDescription: "(sin descripción)",
1099
1099
  },
1100
- pluginSpreadsheet: {
1101
- previewUntitled: "Hoja de cálculo",
1102
- previewSheets: "{count} hoja | {count} hojas",
1103
- untitled: "Hoja de cálculo",
1104
- excel: "Excel",
1105
- valuePlaceholder: "Valor",
1106
- valueOrFormulaPlaceholder: "Valor o Fórmula (ej. 100 o SUM(B2:B11))",
1107
- formatPlaceholder: "Formato (ej. $#,##0.00)",
1108
- loading: "Cargando la hoja de cálculo...",
1109
- noData: "No hay datos de hoja de cálculo disponibles",
1110
- editData: "Editar datos de la hoja de cálculo",
1111
- applyChanges: "Aplicar cambios",
1112
- dataMustBeArray: "Los datos deben ser una matriz de hojas",
1113
- loadFailed: "Error al cargar la hoja de cálculo: {error}",
1114
- invalidJsonAlert: "Formato JSON no válido: {error}",
1115
- unknownError: "Error desconocido",
1116
- update: "Actualizar",
1117
- stringType: "Texto",
1118
- formulaType: "Fórmula",
1119
- },
1120
1100
  app: {
1121
1101
  startConversation: "Inicia una conversación",
1122
1102
  thinking: "Pensando…",
package/src/lang/fr.ts CHANGED
@@ -1089,26 +1089,6 @@ const frMessages = {
1089
1089
  pluginSkill: {
1090
1090
  noDescription: "(aucune description)",
1091
1091
  },
1092
- pluginSpreadsheet: {
1093
- previewUntitled: "Tableur",
1094
- previewSheets: "{count} feuille | {count} feuilles",
1095
- untitled: "Tableur",
1096
- excel: "Excel",
1097
- valuePlaceholder: "Valeur",
1098
- valueOrFormulaPlaceholder: "Valeur ou formule (ex. 100 ou SUM(B2:B11))",
1099
- formatPlaceholder: "Format (ex. $#,##0.00)",
1100
- loading: "Chargement du tableur...",
1101
- noData: "Aucune donnée de tableur disponible",
1102
- editData: "Modifier les données du tableur",
1103
- applyChanges: "Appliquer les modifications",
1104
- dataMustBeArray: "Les données doivent être un tableau de feuilles",
1105
- loadFailed: "Échec du chargement du tableur : {error}",
1106
- invalidJsonAlert: "Format JSON non valide : {error}",
1107
- unknownError: "Erreur inconnue",
1108
- update: "Mettre à jour",
1109
- stringType: "Texte",
1110
- formulaType: "Formule",
1111
- },
1112
1092
  app: {
1113
1093
  startConversation: "Démarrer une conversation",
1114
1094
  thinking: "Réflexion…",
package/src/lang/ja.ts CHANGED
@@ -1083,26 +1083,6 @@ const jaMessages = {
1083
1083
  pluginSkill: {
1084
1084
  noDescription: "(説明なし)",
1085
1085
  },
1086
- pluginSpreadsheet: {
1087
- previewUntitled: "スプレッドシート",
1088
- previewSheets: "{count} シート",
1089
- untitled: "スプレッドシート",
1090
- excel: "Excel",
1091
- valuePlaceholder: "値",
1092
- valueOrFormulaPlaceholder: "値または数式(例: 100, SUM(B2:B11))",
1093
- formatPlaceholder: "書式(例: $#,##0.00)",
1094
- loading: "スプレッドシート読み込み中...",
1095
- noData: "スプレッドシートのデータがありません",
1096
- editData: "スプレッドシートデータを編集",
1097
- applyChanges: "変更を適用",
1098
- dataMustBeArray: "データはシートの配列である必要があります",
1099
- loadFailed: "スプレッドシートの読み込みに失敗: {error}",
1100
- invalidJsonAlert: "不正な JSON 形式です: {error}",
1101
- unknownError: "不明なエラー",
1102
- update: "更新",
1103
- stringType: "文字列",
1104
- formulaType: "数式",
1105
- },
1106
1086
  app: {
1107
1087
  startConversation: "会話を開始してください",
1108
1088
  thinking: "考え中…",
package/src/lang/ko.ts CHANGED
@@ -1081,26 +1081,6 @@ const koMessages = {
1081
1081
  pluginSkill: {
1082
1082
  noDescription: "(설명 없음)",
1083
1083
  },
1084
- pluginSpreadsheet: {
1085
- previewUntitled: "스프레드시트",
1086
- previewSheets: "{count}개 시트",
1087
- untitled: "스프레드시트",
1088
- excel: "Excel",
1089
- valuePlaceholder: "값",
1090
- valueOrFormulaPlaceholder: "값 또는 수식 (예: 100 또는 SUM(B2:B11))",
1091
- formatPlaceholder: "형식 (예: $#,##0.00)",
1092
- loading: "스프레드시트를 불러오는 중...",
1093
- noData: "사용 가능한 스프레드시트 데이터가 없습니다",
1094
- editData: "스프레드시트 데이터 편집",
1095
- applyChanges: "변경 사항 적용",
1096
- dataMustBeArray: "데이터는 시트 배열이어야 합니다",
1097
- loadFailed: "스프레드시트 불러오기 실패: {error}",
1098
- invalidJsonAlert: "잘못된 JSON 형식: {error}",
1099
- unknownError: "알 수 없는 오류",
1100
- update: "업데이트",
1101
- stringType: "문자열",
1102
- formulaType: "수식",
1103
- },
1104
1084
  app: {
1105
1085
  startConversation: "대화 시작",
1106
1086
  thinking: "생각 중…",
package/src/lang/pt-BR.ts CHANGED
@@ -1085,26 +1085,6 @@ const ptBRMessages = {
1085
1085
  pluginSkill: {
1086
1086
  noDescription: "(sem descrição)",
1087
1087
  },
1088
- pluginSpreadsheet: {
1089
- previewUntitled: "Planilha",
1090
- previewSheets: "{count} aba | {count} abas",
1091
- untitled: "Planilha",
1092
- excel: "Excel",
1093
- valuePlaceholder: "Valor",
1094
- valueOrFormulaPlaceholder: "Valor ou Fórmula (ex.: 100 ou SUM(B2:B11))",
1095
- formatPlaceholder: "Formato (ex.: $#,##0.00)",
1096
- loading: "Carregando planilha...",
1097
- noData: "Nenhum dado de planilha disponível",
1098
- editData: "Editar dados da planilha",
1099
- applyChanges: "Aplicar alterações",
1100
- dataMustBeArray: "Os dados devem ser uma matriz de planilhas",
1101
- loadFailed: "Falha ao carregar a planilha: {error}",
1102
- invalidJsonAlert: "Formato JSON inválido: {error}",
1103
- unknownError: "Erro desconhecido",
1104
- update: "Atualizar",
1105
- stringType: "Texto",
1106
- formulaType: "Fórmula",
1107
- },
1108
1088
  app: {
1109
1089
  startConversation: "Iniciar uma conversa",
1110
1090
  thinking: "Pensando…",
package/src/lang/zh.ts CHANGED
@@ -1065,26 +1065,6 @@ const zhMessages = {
1065
1065
  pluginSkill: {
1066
1066
  noDescription: "(无描述)",
1067
1067
  },
1068
- pluginSpreadsheet: {
1069
- previewUntitled: "电子表格",
1070
- previewSheets: "{count} 个工作表",
1071
- untitled: "电子表格",
1072
- excel: "Excel",
1073
- valuePlaceholder: "值",
1074
- valueOrFormulaPlaceholder: "值或公式(例如 100 或 SUM(B2:B11))",
1075
- formatPlaceholder: "格式(例如 $#,##0.00)",
1076
- loading: "正在加载电子表格...",
1077
- noData: "没有可用的电子表格数据",
1078
- editData: "编辑电子表格数据",
1079
- applyChanges: "应用更改",
1080
- dataMustBeArray: "数据必须是工作表数组",
1081
- loadFailed: "加载电子表格失败: {error}",
1082
- invalidJsonAlert: "无效的 JSON 格式: {error}",
1083
- unknownError: "未知错误",
1084
- update: "更新",
1085
- stringType: "字符串",
1086
- formulaType: "公式",
1087
- },
1088
1068
  app: {
1089
1069
  startConversation: "开始对话",
1090
1070
  thinking: "思考中…",
@@ -1,137 +1,19 @@
1
+ import { TOOL_DEFINITION as PACKAGE_TOOL_DEFINITION } from "@gui-chat-plugin/spreadsheet";
1
2
  import type { ToolDefinition } from "gui-chat-protocol";
2
3
  import { META } from "./meta";
3
4
  import type { ResolvedRoute } from "../meta-types";
4
- import { errorMessage } from "../../utils/errors";
5
5
 
6
+ // presentSpreadsheet's schema, validation, engine and View live in
7
+ // @gui-chat-plugin/spreadsheet (also consumed by MulmoTerminal). This built-in
8
+ // keeps MulmoClaude's routing META and the prompt, which points the agent at
9
+ // the workspace help file for cell formats and functions.
6
10
  export const TOOL_NAME = META.toolName;
7
- export type SpreadsheetEndpoints = { readonly [K in keyof typeof META.apiRoutes]: ResolvedRoute };
8
-
9
- /** Mirrors the engine's `StoredCellValue`. Declaring it narrower than what the
10
- * engine stores and renders is what forced the View to cast its decoded
11
- * sheets back to this type. */
12
- export type SpreadsheetCellValue = string | number | boolean;
13
-
14
- export interface SpreadsheetCell {
15
- v: SpreadsheetCellValue;
16
- f?: string;
17
- }
18
-
19
- /** A cell as a stored workbook actually holds it. The engine's `normalizeData`
20
- * folds all four forms, and models and hand edits write all four, so a
21
- * workbook read back off disk cannot promise `{ v }` for every cell. The tool
22
- * SCHEMA is deliberately stricter — it says what a caller should emit, not
23
- * what an existing file may contain. */
24
- export type StorableCell = SpreadsheetCell | { v?: null; f?: string } | SpreadsheetCellValue | null;
25
11
 
26
- export interface SpreadsheetSheet {
27
- name: string;
28
- data: StorableCell[][];
29
- }
30
-
31
- export interface SpreadsheetToolData {
32
- sheets: SpreadsheetSheet[] | string;
33
- }
34
-
35
- export interface SpreadsheetArgs {
36
- title: string;
37
- sheets: SpreadsheetSheet[];
38
- }
12
+ export type SpreadsheetEndpoints = { readonly [K in keyof typeof META.apiRoutes]: ResolvedRoute };
39
13
 
40
- const toolDefinition: ToolDefinition = {
41
- type: "function",
42
- name: TOOL_NAME,
43
- description: "Display an Excel-like spreadsheet with formulas and calculations.",
14
+ export const TOOL_DEFINITION: ToolDefinition = {
15
+ ...PACKAGE_TOOL_DEFINITION,
44
16
  prompt: `Use ${TOOL_NAME} when the user asks for a spreadsheet, table with calculations, or what-if analysis. Use formulas and cell references instead of pre-calculated values so the spreadsheet stays interactive. For cell format details and available functions, read \`config/helps/spreadsheet.md\` in the workspace.`,
45
- parameters: {
46
- type: "object",
47
- properties: {
48
- title: {
49
- type: "string",
50
- description: "Title for the spreadsheet",
51
- },
52
- sheets: {
53
- type: "array",
54
- description: "Sheets to render as spreadsheet tabs. Each sheet includes a name and 2D array of cells (rows x columns).",
55
- items: {
56
- type: "object",
57
- properties: {
58
- name: {
59
- type: "string",
60
- description: "Sheet name (e.g., 'Sales Q1', 'Summary')",
61
- },
62
- data: {
63
- type: "array",
64
- description:
65
- 'Rows of cells. Each cell is an object with \'v\' (value) and \'f\' (format). Use Excel-style A1 notation in formulas: columns are letters (A, B, C...), rows are 1-based numbers (1, 2, 3...). Values can be text, numbers, dates, or formulas. Examples: [{"v": "Product"}, {"v": 2024, "f": "#,##0"}, {"v": "01/15/2025", "f": "MM/DD/YYYY"}, {"v": "=B2*1.05", "f": "$#,##0.00"}]. Format codes: \'$#,##0.00\' (currency), \'#,##0\' (integer), \'0.00%\' (percent), \'0.00\' (decimal), \'MM/DD/YYYY\' (date), \'DD-MMM-YYYY\' (date), \'YYYY-MM-DD\' (ISO date).',
66
- items: {
67
- type: "array",
68
- description: "Row of cells. Each cell is an object with value and format.",
69
- items: {
70
- type: "object",
71
- description: "Cell object with value and optional format. If value is a string starting with '=', it's treated as a formula.",
72
- properties: {
73
- v: {
74
- oneOf: [{ type: "string" }, { type: "number" }, { type: "boolean" }],
75
- description:
76
- "Cell value. Can be text, number, boolean, date, or formula (string starting with '='). Examples: 'Revenue', 1500000, true, '01/15/2025', '=SUM(A1:A10)', '=B2-TODAY()'. Date strings like '01/15/2025' are automatically parsed to date serial numbers.",
77
- },
78
- f: {
79
- type: "string",
80
- description:
81
- "Optional format code for displaying the value. Common formats: '$#,##0.00' (currency), '#,##0' (integer), '0.00%' (percent), '0.00' (decimal), 'MM/DD/YYYY' (date), 'DD-MMM-YYYY' (date), 'YYYY-MM-DD' (ISO date)",
82
- },
83
- },
84
- required: ["v"],
85
- },
86
- },
87
- },
88
- },
89
- required: ["name", "data"],
90
- },
91
- },
92
- },
93
- required: ["title", "sheets"],
94
- },
95
17
  };
96
18
 
97
- export default toolDefinition;
98
-
99
- export const executeSpreadsheet = async (
100
- args: SpreadsheetArgs,
101
- ): Promise<{
102
- message: string;
103
- title: string;
104
- data: SpreadsheetToolData;
105
- instructions: string;
106
- }> => {
107
- const { title } = args;
108
- let { sheets } = args;
109
-
110
- // Handle case where LLM accidentally stringifies the sheets array
111
- if (typeof sheets === "string") {
112
- try {
113
- sheets = JSON.parse(sheets);
114
- } catch (error) {
115
- throw new Error(`Invalid sheets format: sheets must be an array, not a string. Parse error: ${errorMessage(error)}`);
116
- }
117
- }
118
-
119
- // Validate that sheets are provided
120
- if (!Array.isArray(sheets) || sheets.length === 0) {
121
- throw new Error("At least one sheet is required. Sheets must be an array of sheet objects.");
122
- }
123
-
124
- // Validate each sheet has data
125
- for (const sheet of sheets) {
126
- if (!sheet.name || !sheet.data || sheet.data.length === 0) {
127
- throw new Error(`Invalid sheet: ${sheet.name || "unnamed"}. Each sheet must have a name and data array.`);
128
- }
129
- }
130
-
131
- return {
132
- message: `Created spreadsheet: ${title}`,
133
- title,
134
- data: { sheets },
135
- instructions: "Acknowledge that the spreadsheet has been created and is displayed to the user.",
136
- };
137
- };
19
+ export default TOOL_DEFINITION;
@@ -1,12 +1,15 @@
1
1
  import type { PluginRegistration, ToolPlugin } from "../../tools/types";
2
- import toolDefinition, { TOOL_NAME, type SpreadsheetEndpoints, type SpreadsheetToolData } from "./definition";
2
+ import { View, Preview, type SpreadsheetToolData } from "@gui-chat-plugin/spreadsheet/vue";
3
+ // Vite lib mode does not inject the package's stylesheet; the consumer imports it.
4
+ import "@gui-chat-plugin/spreadsheet/style.css";
5
+ import { TOOL_DEFINITION, TOOL_NAME, type SpreadsheetEndpoints } from "./definition";
3
6
  import { makeRouteExecute } from "../execute";
4
7
  import { wrapWithScope } from "../scope";
5
- import View from "./View.vue";
6
- import Preview from "./Preview.vue";
7
8
 
9
+ // wrapWithScope provides the runtime the package View needs: dispatch for
10
+ // loading and saving the workbook, and the host locale for its strings.
8
11
  const spreadsheetPlugin: ToolPlugin<SpreadsheetToolData> = {
9
- toolDefinition,
12
+ toolDefinition: TOOL_DEFINITION,
10
13
 
11
14
  execute: makeRouteExecute<SpreadsheetEndpoints, SpreadsheetToolData>("spreadsheet", "create", TOOL_NAME),
12
15
 
@@ -4,11 +4,9 @@ export const META = definePluginMeta({
4
4
  toolName: "presentSpreadsheet",
5
5
  apiNamespace: "spreadsheet",
6
6
  apiRoutes: {
7
- /** POST /api/spreadsheet — create a new spreadsheet. */
7
+ /** POST /api/spreadsheet — create a new spreadsheet. Cell edits are
8
+ * saved through the View's dispatch (server/plugins/spreadsheet-builtin.ts). */
8
9
  create: { method: "POST", path: "" },
9
- /** PUT /api/spreadsheet/update — overwrite an existing
10
- * spreadsheet. Body carries the workspace-relative path. */
11
- update: { method: "PUT", path: "/update" },
12
10
  },
13
11
  mcpDispatch: "create",
14
12
  });