mulmoclaude 1.2.0 → 1.3.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 (75) hide show
  1. package/client/assets/{PluginScopedRoot-CjiZ2FJI.js → PluginScopedRoot-B53YSjaC.js} +1 -1
  2. package/client/assets/index-D3czxq1I.css +2 -0
  3. package/client/assets/{index-yQkLdtMk.js → index-DUdqhWg6.js} +42 -43
  4. package/client/assets/{marp-Bs_qwoKZ.js → marp-CyWxL3bc.js} +12 -3
  5. package/client/index.html +3 -3
  6. package/package.json +6 -5
  7. package/server/agent/attachmentConverter.ts +0 -25
  8. package/server/api/auth/viewToken.ts +10 -8
  9. package/server/api/routes/agent.ts +35 -10
  10. package/server/api/routes/collections.ts +150 -96
  11. package/server/api/routes/files.ts +57 -54
  12. package/server/api/routes/mulmo-script.ts +257 -895
  13. package/server/api/routes/wiki/history.ts +17 -19
  14. package/server/events/collection-change.ts +0 -5
  15. package/server/events/file-change.ts +1 -6
  16. package/server/index.ts +25 -76
  17. package/server/plugins/diagnostics.ts +0 -6
  18. package/server/plugins/mulmoscript-server.ts +58 -0
  19. package/server/prompts/system/system.md +2 -2
  20. package/server/remoteHost/handlers/getCollection.ts +5 -4
  21. package/server/remoteHost/handlers/googleCalendar.ts +50 -3
  22. package/server/remoteHost/handlers/index.ts +3 -1
  23. package/server/utils/files/html-store.ts +2 -16
  24. package/server/utils/files/journal-io.ts +0 -12
  25. package/server/utils/files/plugins-io.ts +2 -11
  26. package/server/utils/files/safe.ts +39 -0
  27. package/server/utils/httpError.ts +6 -0
  28. package/server/utils/time.ts +0 -3
  29. package/server/workspace/collections/index.ts +1 -0
  30. package/server/workspace/collections/remoteView.ts +13 -4
  31. package/server/workspace/journal/state.ts +1 -9
  32. package/server/workspace/memory/topic-index-hook.ts +0 -9
  33. package/server/workspace/memory/topic-io.ts +0 -4
  34. package/server/workspace/skills/external/catalog.ts +24 -13
  35. package/server/workspace/skills/writer.ts +20 -16
  36. package/server/workspace/wiki-pages/snapshot.ts +13 -12
  37. package/src/composables/useFileDropZone.ts +3 -15
  38. package/src/config/apiRoutes.ts +6 -0
  39. package/src/config/roles.ts +0 -4
  40. package/src/lang/de.ts +0 -30
  41. package/src/lang/en.ts +0 -30
  42. package/src/lang/es.ts +0 -30
  43. package/src/lang/fr.ts +0 -30
  44. package/src/lang/ja.ts +0 -30
  45. package/src/lang/ko.ts +0 -30
  46. package/src/lang/pt-BR.ts +0 -30
  47. package/src/lang/zh.ts +0 -30
  48. package/src/plugins/accounting/index.ts +0 -2
  49. package/src/plugins/api.ts +0 -6
  50. package/src/plugins/canvas/index.ts +0 -2
  51. package/src/plugins/chart/index.ts +0 -2
  52. package/src/plugins/editImages/index.ts +0 -2
  53. package/src/plugins/generateImage/index.ts +0 -2
  54. package/src/plugins/manageSkills/index.ts +0 -2
  55. package/src/plugins/markdown/index.ts +0 -2
  56. package/src/plugins/photoLocations/index.ts +0 -2
  57. package/src/plugins/presentCollection/index.ts +0 -2
  58. package/src/plugins/presentForm/index.ts +0 -2
  59. package/src/plugins/presentHtml/index.ts +0 -2
  60. package/src/plugins/presentMulmoScript/definition.ts +11 -116
  61. package/src/plugins/presentMulmoScript/index.ts +62 -12
  62. package/src/plugins/presentSVG/index.ts +0 -2
  63. package/src/plugins/skill/index.ts +0 -3
  64. package/src/plugins/spreadsheet/engine/parser.ts +0 -103
  65. package/src/plugins/spreadsheet/index.ts +0 -2
  66. package/src/plugins/textResponse/index.ts +0 -3
  67. package/src/plugins/wiki/index.ts +0 -2
  68. package/src/types/notification.ts +0 -10
  69. package/src/utils/api.ts +0 -4
  70. package/client/assets/index-D5nJbMO1.css +0 -2
  71. package/server/api/routes/mulmoScriptValidate.ts +0 -101
  72. package/server/utils/mulmoErrorCapture.ts +0 -93
  73. package/src/plugins/presentMulmoScript/Preview.vue +0 -23
  74. package/src/plugins/presentMulmoScript/View.vue +0 -1949
  75. package/src/plugins/presentMulmoScript/helpers.ts +0 -206
@@ -23,12 +23,13 @@ import {
23
23
  } from "@mulmoclaude/core/remote-view";
24
24
  import { enrichItems } from "@mulmoclaude/core/collection/server";
25
25
  import {
26
+ collectionWritable,
26
27
  deleteItem,
27
- listItems,
28
28
  readCustomViewHtml,
29
29
  readCustomViewI18n,
30
30
  readItem,
31
31
  safeRecordId,
32
+ storeFor,
32
33
  writeItem,
33
34
  type CollectionCustomView,
34
35
  type CollectionItem,
@@ -108,6 +109,7 @@ export type MutateRemoteViewResult =
108
109
  | { kind: "view-not-found"; viewId: string }
109
110
  | { kind: "not-mobile"; viewId: string }
110
111
  | { kind: "not-writable"; viewId: string }
112
+ | { kind: "read-only-collection" }
111
113
  | { kind: "field-not-editable"; field: string }
112
114
  | { kind: "delete-not-allowed" }
113
115
  | { kind: "invalid-patch" }
@@ -129,6 +131,9 @@ export const createMutateRemoteView =
129
131
  const view = (collection.schema.views ?? []).find((entry) => entry.id === viewId);
130
132
  if (!view) return { kind: "view-not-found", viewId };
131
133
  if (view.target !== "mobile") return { kind: "not-mobile", viewId };
134
+ // A dataSource collection is read-only regardless of what write surface
135
+ // the view declares — the collection-level rule outranks the view's.
136
+ if (!collectionWritable(collection)) return { kind: "read-only-collection" };
132
137
  if (!isWritableView(view)) return { kind: "not-writable", viewId };
133
138
  return request.op === "delete" ? deleteViaView(deps, collection, view.allowDelete === true, request.id) : updateViaView(deps, collection, view, request);
134
139
  };
@@ -213,7 +218,9 @@ export type RemoteViewItemsResult =
213
218
  | { kind: "too-large"; bytes: number };
214
219
 
215
220
  export interface RemoteViewItemsDeps {
216
- listItems: typeof listItems;
221
+ /** Load every record of the collection — store-aware (file records or a
222
+ * `dataSource` CSV's rows), unlike a raw dataDir `listItems`. */
223
+ listRecords: (collection: LoadedCollection) => Promise<CollectionItem[]>;
217
224
  enrichItems: typeof enrichItems;
218
225
  resolveThumbnail: typeof resolveThumbnail;
219
226
  }
@@ -271,7 +278,7 @@ export const createRemoteViewItems =
271
278
  // formulas evaluated with a full ref cache (`ticker.price`, `shares * ticker.price`
272
279
  // resolve), toggles projected, embeds resolved. The phone gets plain resolved
273
280
  // scalars — no network, no dataUrl — so mobile numbers match desktop exactly.
274
- const items = await deps.listItems(collection.dataDir);
281
+ const items = await deps.listRecords(collection);
275
282
  const derived = (await deps.enrichItems(collection, items)) as RemoteViewItem[];
276
283
  const page = pageFromItems(derived, request, collection.schema.primaryKey);
277
284
  // Resolving an `embed` column attaches a whole target record per row, so the
@@ -289,7 +296,7 @@ export const createRemoteViewItems =
289
296
  return { kind: "ok", page, inlined, omitted };
290
297
  };
291
298
 
292
- export const remoteViewItems = createRemoteViewItems({ listItems, enrichItems, resolveThumbnail });
299
+ export const remoteViewItems = createRemoteViewItems({ listRecords: (collection) => storeFor(collection).list(), enrichItems, resolveThumbnail });
293
300
 
294
301
  /** Message per non-ok item-page kind — shared by the channel handler (throws)
295
302
  * and the HTTP route (sends with the matching status). */
@@ -307,6 +314,8 @@ export function mutateRemoteViewFailureMessage(result: Exclude<MutateRemoteViewR
307
314
  if (result.kind === "not-mobile") return `custom view '${result.viewId}' is not a mobile view — declare target: "mobile" in its views[] entry`;
308
315
  if (result.kind === "not-writable")
309
316
  return `mobile view '${result.viewId}' is read-only — declare editableFields and/or allowDelete in its views[] entry to allow writes`;
317
+ if (result.kind === "read-only-collection")
318
+ return `collection '${slug}' is read-only (backed by an external dataSource) — update the data file itself instead`;
310
319
  if (result.kind === "field-not-editable")
311
320
  return `field '${result.field}' is not editable from this view — add it to the view's editableFields (the primary key is never editable)`;
312
321
  if (result.kind === "delete-not-allowed") return `this view may not delete records — set allowDelete: true in its views[] entry`;
@@ -1,8 +1,4 @@
1
- import {
2
- readJournalState as readJournalStateRaw,
3
- writeJournalState as writeJournalStateRaw,
4
- journalStateExists as journalStateExistsRaw,
5
- } from "../../utils/files/journal-io.js";
1
+ import { readJournalState as readJournalStateRaw, writeJournalState as writeJournalStateRaw } from "../../utils/files/journal-io.js";
6
2
  import { ONE_HOUR_MS, ONE_DAY_MS } from "../../utils/time.js";
7
3
  import { log } from "../../system/logger/index.js";
8
4
  import { isRecord } from "../../utils/types.js";
@@ -106,7 +102,3 @@ export async function readState(workspaceRoot: string): Promise<JournalState> {
106
102
  export async function writeState(workspaceRoot: string, state: JournalState): Promise<void> {
107
103
  await writeJournalStateRaw(state, workspaceRoot);
108
104
  }
109
-
110
- export function stateFileExists(workspaceRoot: string): boolean {
111
- return journalStateExistsRaw(workspaceRoot);
112
- }
@@ -117,12 +117,3 @@ export async function maybeRegenerateTopicIndex(relativePath: string): Promise<b
117
117
  return false;
118
118
  }
119
119
  }
120
-
121
- /** Test-only — drop the per-workspace regen chain so each test
122
- * starts with a fresh queue. Without this, a chain rejection from
123
- * one test could carry into the next (the .catch in `chainRegen`
124
- * swallows it for the next caller, but the test runner sees the
125
- * unhandled-rejection from the original promise). */
126
- export function _resetTopicIndexRegenChainForTesting(): void {
127
- regenChains.clear();
128
- }
@@ -33,10 +33,6 @@ export function topicMemoryIndexPath(workspaceRoot: string): string {
33
33
  return path.join(workspaceRoot, WORKSPACE_FILES.memoryIndex);
34
34
  }
35
35
 
36
- export function topicFilePath(workspaceRoot: string, type: MemoryType, topic: string): string {
37
- return path.join(topicMemoryRoot(workspaceRoot), type, `${topic}.md`);
38
- }
39
-
40
36
  // Both loaders walk types in `MEMORY_TYPES` order (a stable
41
37
  // constant) and sort filenames within each type by name. This pins
42
38
  // the order of entries in the agent's system prompt so a `readdir`
@@ -198,19 +198,33 @@ async function resolveSource(repoIdRaw: string, skillFolderRaw: string, workspac
198
198
  return { repoId, skillFolder, sourceDir, url: meta.url };
199
199
  }
200
200
 
201
+ // Resolve a catalog source, or classify why it failed: a bad-shape id
202
+ // (rejected upstream) → `invalid-id`, otherwise missing-on-disk →
203
+ // `not-found`. The detail-read and star routes share this preamble; the
204
+ // error variants are a subset of both routes' result types.
205
+ type SourceError = { kind: "invalid-id" } | { kind: "not-found"; repoId: string; skillFolder: string };
206
+
207
+ async function resolveSourceOrError(
208
+ repoIdRaw: string,
209
+ skillFolderRaw: string,
210
+ workspaceRoot: string,
211
+ ): Promise<{ ok: true; resolved: ResolvedSource } | { ok: false; error: SourceError }> {
212
+ const resolved = await resolveSource(repoIdRaw, skillFolderRaw, workspaceRoot);
213
+ if (resolved) return { ok: true, resolved };
214
+ if (!isSafeRepoId(repoIdRaw)) return { ok: false, error: { kind: "invalid-id" } };
215
+ if (skillFolderRaw !== "." && safeFolderName(skillFolderRaw) === null) return { ok: false, error: { kind: "invalid-id" } };
216
+ return { ok: false, error: { kind: "not-found", repoId: repoIdRaw, skillFolder: skillFolderRaw } };
217
+ }
218
+
201
219
  export async function readExternalCatalogDetail(
202
220
  repoIdRaw: string,
203
221
  skillFolderRaw: string,
204
222
  opts: ExternalCatalogOptions = {},
205
223
  ): Promise<ExternalCatalogDetailResult> {
206
224
  const workspaceRoot = opts.workspaceRoot ?? workspacePath;
207
- const resolved = await resolveSource(repoIdRaw, skillFolderRaw, workspaceRoot);
208
- if (!resolved) {
209
- // Distinguish bad shape (rejected upstream) from missing-on-disk.
210
- if (!isSafeRepoId(repoIdRaw)) return { kind: "invalid-id" };
211
- if (skillFolderRaw !== "." && safeFolderName(skillFolderRaw) === null) return { kind: "invalid-id" };
212
- return { kind: "not-found", repoId: repoIdRaw, skillFolder: skillFolderRaw };
213
- }
225
+ const source = await resolveSourceOrError(repoIdRaw, skillFolderRaw, workspaceRoot);
226
+ if (!source.ok) return source.error;
227
+ const { resolved } = source;
214
228
  const skillMd = path.join(resolved.sourceDir, "SKILL.md");
215
229
  let raw: string;
216
230
  try {
@@ -265,12 +279,9 @@ export type ExternalStarResult =
265
279
 
266
280
  export async function starExternalCatalogEntry(repoIdRaw: string, skillFolderRaw: string, opts: ExternalCatalogOptions = {}): Promise<ExternalStarResult> {
267
281
  const workspaceRoot = opts.workspaceRoot ?? workspacePath;
268
- const resolved = await resolveSource(repoIdRaw, skillFolderRaw, workspaceRoot);
269
- if (!resolved) {
270
- if (!isSafeRepoId(repoIdRaw)) return { kind: "invalid-id" };
271
- if (skillFolderRaw !== "." && safeFolderName(skillFolderRaw) === null) return { kind: "invalid-id" };
272
- return { kind: "not-found", repoId: repoIdRaw, skillFolder: skillFolderRaw };
273
- }
282
+ const source = await resolveSourceOrError(repoIdRaw, skillFolderRaw, workspaceRoot);
283
+ if (!source.ok) return source.error;
284
+ const { resolved } = source;
274
285
  const activeId = deriveActiveId(resolved.url, resolved.skillFolder === "." ? null : resolved.skillFolder);
275
286
  if (!activeId) return { kind: "invalid-id" };
276
287
  const activeSlugDir = path.join(activeDir(workspaceRoot), activeId);
@@ -35,21 +35,31 @@ export type SaveResult =
35
35
  | { kind: "missing-field"; field: "description" | "body" }
36
36
  | { kind: "exists"; name: string };
37
37
 
38
+ // The slug + required-field checks shared by save and update. Returns
39
+ // the error variant to return verbatim, or null when the input is
40
+ // valid. Its variants are a subset of both SaveResult and UpdateResult.
41
+ type SkillInputProblem = { kind: "invalid-slug"; slug: string } | { kind: "missing-field"; field: "description" | "body" };
42
+
43
+ function validateSkillInput(input: SaveSkillInput): SkillInputProblem | null {
44
+ if (!isValidSlug(input.name)) return { kind: "invalid-slug", slug: input.name };
45
+ if (typeof input.description !== "string" || input.description.trim().length === 0) {
46
+ return { kind: "missing-field", field: "description" };
47
+ }
48
+ if (typeof input.body !== "string") {
49
+ return { kind: "missing-field", field: "body" };
50
+ }
51
+ return null;
52
+ }
53
+
38
54
  /**
39
55
  * Write a new SKILL.md atomically. Refuses to overwrite — if the
40
56
  * skill already exists at either scope, returns `kind: "exists"`.
41
57
  */
42
58
  export async function saveProjectSkill(input: SaveSkillInput): Promise<SaveResult> {
59
+ const problem = validateSkillInput(input);
60
+ if (problem) return problem;
43
61
  const { workspaceRoot, name, description, body } = input;
44
62
 
45
- if (!isValidSlug(name)) return { kind: "invalid-slug", slug: name };
46
- if (typeof description !== "string" || description.trim().length === 0) {
47
- return { kind: "missing-field", field: "description" };
48
- }
49
- if (typeof body !== "string") {
50
- return { kind: "missing-field", field: "body" };
51
- }
52
-
53
63
  // Conflict check across BOTH scopes — we don't want to shadow a
54
64
  // user-scope skill with the same name (project would silently
55
65
  // override it via the precedence rule).
@@ -87,16 +97,10 @@ export type UpdateResult =
87
97
  * user-scope skills and rejects names that don't exist.
88
98
  */
89
99
  export async function updateProjectSkill(input: SaveSkillInput): Promise<UpdateResult> {
100
+ const problem = validateSkillInput(input);
101
+ if (problem) return problem;
90
102
  const { workspaceRoot, name, description, body } = input;
91
103
 
92
- if (!isValidSlug(name)) return { kind: "invalid-slug", slug: name };
93
- if (typeof description !== "string" || description.trim().length === 0) {
94
- return { kind: "missing-field", field: "description" };
95
- }
96
- if (typeof body !== "string") {
97
- return { kind: "missing-field", field: "body" };
98
- }
99
-
100
104
  const existing = await discoverSkills({ workspaceRoot });
101
105
  const skill = existing.find((candidate) => candidate.name === name);
102
106
  if (!skill) return { kind: "not-found", name };
@@ -219,12 +219,7 @@ export async function gcSnapshots(slug: string, now: Date, opts: SnapshotPathOpt
219
219
  // lexicographically sortable because it's zero-padded ISO with
220
220
  // colons swapped. Same-millisecond writes resolve via the
221
221
  // shortId tail in `stamp` for tie-break consistency.
222
- entries.sort((left, right) => {
223
- if (left.filenameStamp !== right.filenameStamp) {
224
- return left.filenameStamp < right.filenameStamp ? 1 : -1;
225
- }
226
- return left.stamp < right.stamp ? 1 : left.stamp > right.stamp ? -1 : 0;
227
- });
222
+ entries.sort(compareSnapshotsNewestFirst);
228
223
 
229
224
  const cutoffMs = now.getTime() - SNAPSHOT_RETAIN_DAYS * ONE_DAY_MS;
230
225
 
@@ -252,6 +247,17 @@ interface SnapshotEntry {
252
247
  fileName: string;
253
248
  }
254
249
 
250
+ // Newest-first ordering: by `filenameStamp` (zero-padded ISO, so
251
+ // lexicographically sortable), then by the shortId-tailed `stamp` to
252
+ // break same-millisecond ties consistently. The one ordering rule shared
253
+ // by every snapshot listing.
254
+ function compareSnapshotsNewestFirst(left: SnapshotEntry, right: SnapshotEntry): number {
255
+ if (left.filenameStamp !== right.filenameStamp) {
256
+ return left.filenameStamp < right.filenameStamp ? 1 : -1;
257
+ }
258
+ return left.stamp < right.stamp ? 1 : left.stamp > right.stamp ? -1 : 0;
259
+ }
260
+
255
261
  async function readSnapshotEntries(dir: string): Promise<SnapshotEntry[]> {
256
262
  // Defence in depth: refuse to read if the directory itself is a
257
263
  // symlink (codex review iter-3 / iter-4 #917). See historyDirIsSafe.
@@ -326,12 +332,7 @@ function entryEditor(meta: Record<string, unknown>): WikiPageEditor {
326
332
  export async function listSnapshots(slug: string, opts: SnapshotPathOptions = {}): Promise<SnapshotSummary[]> {
327
333
  const dir = historyDir(slug, opts);
328
334
  const entries = await readSnapshotEntries(dir);
329
- entries.sort((left, right) => {
330
- if (left.filenameStamp !== right.filenameStamp) {
331
- return left.filenameStamp < right.filenameStamp ? 1 : -1;
332
- }
333
- return left.stamp < right.stamp ? 1 : left.stamp > right.stamp ? -1 : 0;
334
- });
335
+ entries.sort(compareSnapshotsNewestFirst);
335
336
 
336
337
  const summaries: SnapshotSummary[] = [];
337
338
  for (const entry of entries) {
@@ -36,10 +36,9 @@ export interface UseFileDropZoneOptions {
36
36
  }
37
37
 
38
38
  // Module-scope so the install-once contract holds across multiple
39
- // `useFileDropZone` consumers. The handler reference is retained
40
- // so `_resetFileDropZoneForTests` can actually remove the listeners
41
- // (a naive reset that only flipped the flag would leak handlers
42
- // across test runs — Sourcery review on PR #1331).
39
+ // `useFileDropZone` consumers. The handler reference is retained so the
40
+ // listeners it installs can be removed again (a flag-only guard would leak
41
+ // handlers Sourcery review on PR #1331).
43
42
  let windowGuardHandler: ((event: DragEvent) => void) | null = null;
44
43
 
45
44
  function isFileDrag(event: DragEvent): boolean {
@@ -132,14 +131,3 @@ export function useFileDropZone(opts: UseFileDropZoneOptions): UseFileDropZoneRe
132
131
 
133
132
  return { isDragging, onDragenter, onDragover, onDragleave, onDrop };
134
133
  }
135
-
136
- /** Test-only reset. Removes the window listeners installed by
137
- * `installWindowDefaultGuard` so a test can verify the install-once
138
- * contract without leaking handlers across cases. */
139
- export function _resetFileDropZoneForTests(): void {
140
- if (windowGuardHandler !== null && typeof window !== "undefined") {
141
- window.removeEventListener("dragover", windowGuardHandler);
142
- window.removeEventListener("drop", windowGuardHandler);
143
- }
144
- windowGuardHandler = null;
145
- }
@@ -357,6 +357,12 @@ const HOST_API_ROUTES = {
357
357
  * capability on the view token; mutate kind ONLY — a view token must
358
358
  * never be able to start LLM work (chat/agent stay bearer-guarded). */
359
359
  viewDataAction: "/api/collections/:slug/view-data/actions/:actionId",
360
+ /** POST → run a structured aggregation query (body: { query }) over a
361
+ * dataSource collection's WHOLE data file → { collection, count, rows }.
362
+ * Requires only the `read` capability on the view token — the DSL is
363
+ * read-only by construction (see core/queryZ.ts); 400 on a file-backed
364
+ * collection (no query engine). Backs chart-drawing custom views. */
365
+ viewDataQuery: "/api/collections/:slug/view-data/query",
360
366
  /** DELETE → remove one custom view: drop it from schema.json `views[]` and
361
367
  * unlink its `views/<file>.html` (global-bearer auth) → { deleted, viewId }.
362
368
  * Source-aware; refuses user-scope + preset collections. */
@@ -452,7 +452,3 @@ export const BUILTIN_ROLE_IDS = {
452
452
  export type BuiltInRoleId = (typeof BUILTIN_ROLE_IDS)[keyof typeof BUILTIN_ROLE_IDS];
453
453
 
454
454
  export const DEFAULT_ROLE_ID: BuiltInRoleId = BUILTIN_ROLE_IDS.general;
455
-
456
- export function getRole(roleId: string): Role {
457
- return ROLES.find((role) => role.id === roleId) ?? ROLES[0];
458
- }
package/src/lang/de.ts CHANGED
@@ -969,36 +969,6 @@ const deMessages = {
969
969
  pluginUiImage: {
970
970
  promptLabel: "{label}:",
971
971
  },
972
- pluginMulmoScript: {
973
- beatCount: "{count} Beat | {count} Beats",
974
- movie: "Video",
975
- generating: "Wird generiert…",
976
- rendering: "Wird gerendert…",
977
- saving: "Wird gespeichert…",
978
- update: "Aktualisieren",
979
- characters: "Charaktere",
980
- drop: "Ablegen",
981
- gen: "Generieren",
982
- play: "▶ Abspielen",
983
- stop: "■ Stoppen",
984
- playPresentation: "Präsentation abspielen",
985
- regenerateMovie: "Video neu generieren",
986
- movieGenerationFailed: "Videoerstellung fehlgeschlagen",
987
- pdf: "PDF",
988
- regeneratePdf: "PDF neu generieren",
989
- generatingPdf: "PDF wird erstellt…",
990
- retry: "Erneut versuchen",
991
- errPrefix: "⚠ Fehler",
992
- noBeats: "Keine Beats im Skript gefunden",
993
- editSource: "Skript-Quelle bearbeiten",
994
- applyChanges: "Änderungen übernehmen",
995
- generateAll: "Alle generieren",
996
- orDropImage: "oder Bild ablegen",
997
- generate: "Generieren",
998
- generateAudio: "♪ Generieren",
999
- saveErrorInvalidJson: "⚠ Ungültiges JSON: {error}",
1000
- saveErrorSaveFailed: "⚠ Speichern fehlgeschlagen: {error}",
1001
- },
1002
972
  pluginMarkdown: {
1003
973
  loading: "Dokument wird geladen...",
1004
974
  loadFailed: "⚠ Laden des Dokuments fehlgeschlagen: {error}",
package/src/lang/en.ts CHANGED
@@ -980,36 +980,6 @@ const enMessages = {
980
980
  pluginUiImage: {
981
981
  promptLabel: "{label}:",
982
982
  },
983
- pluginMulmoScript: {
984
- beatCount: "{count} beat | {count} beats",
985
- movie: "Movie",
986
- generating: "Generating…",
987
- rendering: "Rendering…",
988
- saving: "Saving…",
989
- update: "Update",
990
- characters: "Characters",
991
- drop: "Drop",
992
- gen: "Gen",
993
- play: "▶ Play",
994
- stop: "■ Stop",
995
- playPresentation: "Play presentation",
996
- regenerateMovie: "Regenerate movie",
997
- movieGenerationFailed: "Movie generation failed",
998
- pdf: "PDF",
999
- regeneratePdf: "Regenerate PDF",
1000
- generatingPdf: "Generating PDF…",
1001
- retry: "Retry",
1002
- errPrefix: "⚠ Error",
1003
- noBeats: "No beats found in script",
1004
- editSource: "Edit Script Source",
1005
- applyChanges: "Apply Changes",
1006
- generateAll: "Generate All",
1007
- orDropImage: "or drop image",
1008
- generate: "Generate",
1009
- generateAudio: "♪ Generate",
1010
- saveErrorInvalidJson: "⚠ Invalid JSON: {error}",
1011
- saveErrorSaveFailed: "⚠ Save failed: {error}",
1012
- },
1013
983
  pluginMarkdown: {
1014
984
  loading: "Loading document...",
1015
985
  loadFailed: "⚠ Failed to load document: {error}",
package/src/lang/es.ts CHANGED
@@ -967,36 +967,6 @@ const esMessages = {
967
967
  pluginUiImage: {
968
968
  promptLabel: "{label}:",
969
969
  },
970
- pluginMulmoScript: {
971
- beatCount: "{count} beat | {count} beats",
972
- movie: "Vídeo",
973
- generating: "Generando…",
974
- rendering: "Renderizando…",
975
- saving: "Guardando…",
976
- update: "Actualizar",
977
- characters: "Personajes",
978
- drop: "Soltar",
979
- gen: "Generar",
980
- play: "▶ Reproducir",
981
- stop: "■ Detener",
982
- playPresentation: "Reproducir presentación",
983
- regenerateMovie: "Regenerar vídeo",
984
- movieGenerationFailed: "Error al generar el vídeo",
985
- pdf: "PDF",
986
- regeneratePdf: "Regenerar PDF",
987
- generatingPdf: "Generando PDF…",
988
- retry: "Reintentar",
989
- errPrefix: "⚠ Error",
990
- noBeats: "No se encontraron beats en el script",
991
- editSource: "Editar fuente del script",
992
- applyChanges: "Aplicar cambios",
993
- generateAll: "Generar todo",
994
- orDropImage: "o arrastra una imagen",
995
- generate: "Generar",
996
- generateAudio: "♪ Generar",
997
- saveErrorInvalidJson: "⚠ JSON no válido: {error}",
998
- saveErrorSaveFailed: "⚠ Error al guardar: {error}",
999
- },
1000
970
  pluginMarkdown: {
1001
971
  loading: "Cargando documento...",
1002
972
  loadFailed: "⚠ Error al cargar el documento: {error}",
package/src/lang/fr.ts CHANGED
@@ -958,36 +958,6 @@ const frMessages = {
958
958
  pluginUiImage: {
959
959
  promptLabel: "{label} :",
960
960
  },
961
- pluginMulmoScript: {
962
- beatCount: "{count} beat | {count} beats",
963
- movie: "Film",
964
- generating: "Génération…",
965
- rendering: "Rendu…",
966
- saving: "Enregistrement…",
967
- update: "Mettre à jour",
968
- characters: "Personnages",
969
- drop: "Déposer",
970
- gen: "Générer",
971
- play: "▶ Lire",
972
- stop: "■ Arrêter",
973
- playPresentation: "Lire la présentation",
974
- regenerateMovie: "Régénérer la vidéo",
975
- movieGenerationFailed: "Échec de la génération de la vidéo",
976
- pdf: "PDF",
977
- regeneratePdf: "Régénérer le PDF",
978
- generatingPdf: "Génération du PDF…",
979
- retry: "Réessayer",
980
- errPrefix: "⚠ Erreur",
981
- noBeats: "Aucun beat trouvé dans le script",
982
- editSource: "Modifier la source du script",
983
- applyChanges: "Appliquer les modifications",
984
- generateAll: "Tout générer",
985
- orDropImage: "ou déposez une image",
986
- generate: "Générer",
987
- generateAudio: "♪ Générer",
988
- saveErrorInvalidJson: "⚠ JSON invalide : {error}",
989
- saveErrorSaveFailed: "⚠ Échec de la sauvegarde : {error}",
990
- },
991
961
  pluginMarkdown: {
992
962
  loading: "Chargement du document...",
993
963
  loadFailed: "⚠ Échec du chargement du document : {error}",
package/src/lang/ja.ts CHANGED
@@ -954,36 +954,6 @@ const jaMessages = {
954
954
  pluginUiImage: {
955
955
  promptLabel: "{label}:",
956
956
  },
957
- pluginMulmoScript: {
958
- beatCount: "{count} ビート",
959
- movie: "動画",
960
- generating: "生成中…",
961
- rendering: "レンダリング中…",
962
- saving: "保存中…",
963
- update: "更新",
964
- characters: "キャラクター",
965
- drop: "ドロップ",
966
- gen: "生成",
967
- play: "▶ 再生",
968
- stop: "■ 停止",
969
- playPresentation: "プレゼンテーション再生",
970
- regenerateMovie: "動画を再生成",
971
- movieGenerationFailed: "動画の生成に失敗しました",
972
- pdf: "PDF",
973
- regeneratePdf: "PDF を再生成",
974
- generatingPdf: "PDF を生成中…",
975
- retry: "再試行",
976
- errPrefix: "⚠ エラー",
977
- noBeats: "スクリプトにビートが見つかりません",
978
- editSource: "スクリプトソースを編集",
979
- applyChanges: "変更を適用",
980
- generateAll: "すべて生成",
981
- orDropImage: "画像をドロップ",
982
- generate: "生成",
983
- generateAudio: "♪ 生成",
984
- saveErrorInvalidJson: "⚠ 不正な JSON: {error}",
985
- saveErrorSaveFailed: "⚠ 保存失敗: {error}",
986
- },
987
957
  pluginMarkdown: {
988
958
  loading: "ドキュメント読み込み中...",
989
959
  loadFailed: "⚠ ドキュメントの読み込みに失敗: {error}",
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,
@@ -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 = {