wizards 0.5.7 → 0.5.9

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 (109) hide show
  1. package/apps/runtime/dist/agents/architect.js +13 -26
  2. package/apps/runtime/dist/agents/studio-assistant.js +19 -22
  3. package/apps/runtime/dist/agents/subscription.js +10 -9
  4. package/apps/runtime/dist/app.js +6 -6
  5. package/apps/runtime/dist/assistant/knows.js +10 -9
  6. package/apps/runtime/dist/connectors/external.js +3 -3
  7. package/apps/runtime/dist/connectors/index.js +23 -23
  8. package/apps/runtime/dist/connectors/via-account.js +6 -6
  9. package/apps/runtime/dist/documents/parse.js +3 -3
  10. package/apps/runtime/dist/engine/input.js +11 -11
  11. package/apps/runtime/dist/engine/requirements.js +7 -8
  12. package/apps/runtime/dist/engine/runner.js +10 -14
  13. package/apps/runtime/dist/engine/steps.js +6 -6
  14. package/apps/runtime/dist/engine/surface.js +2 -2
  15. package/apps/runtime/dist/film/step.js +5 -5
  16. package/apps/runtime/dist/harness/apple.js +2 -1
  17. package/apps/runtime/dist/harness/claude.js +2 -2
  18. package/apps/runtime/dist/harness/codex.js +2 -2
  19. package/apps/runtime/dist/harness/model.js +4 -5
  20. package/apps/runtime/dist/harness/terminal.js +4 -4
  21. package/apps/runtime/dist/media/conversation.js +18 -35
  22. package/apps/runtime/dist/media/transcribe.js +2 -1
  23. package/apps/runtime/dist/model-errors.js +1 -1
  24. package/apps/runtime/dist/models.js +32 -40
  25. package/apps/runtime/dist/plugins/runs.js +3 -5
  26. package/apps/runtime/dist/routes/api.js +9 -9
  27. package/apps/runtime/dist/routes/lang.js +14 -9
  28. package/apps/runtime/dist/routes/runs.js +5 -5
  29. package/apps/runtime/dist/routes/studio.js +38 -37
  30. package/apps/runtime/dist/services/access.js +4 -4
  31. package/apps/runtime/dist/services/architect.js +3 -12
  32. package/apps/runtime/dist/services/assistant-chats.js +2 -2
  33. package/apps/runtime/dist/services/brand.js +3 -3
  34. package/apps/runtime/dist/services/cloud.js +5 -5
  35. package/apps/runtime/dist/services/errors.js +2 -2
  36. package/apps/runtime/dist/services/knowledge-model.js +3 -3
  37. package/apps/runtime/dist/services/marketplace.js +3 -3
  38. package/apps/runtime/dist/services/package-file.js +9 -11
  39. package/apps/runtime/dist/services/package.js +8 -9
  40. package/apps/runtime/dist/services/profile.js +2 -1
  41. package/apps/runtime/dist/services/project-files.js +13 -13
  42. package/apps/runtime/dist/services/projects.js +7 -11
  43. package/apps/runtime/dist/services/runners.js +4 -4
  44. package/apps/runtime/dist/services/runs.js +7 -10
  45. package/apps/runtime/dist/services/shares.js +2 -2
  46. package/apps/runtime/dist/services/site.js +8 -11
  47. package/apps/runtime/dist/services/space-categories.js +7 -7
  48. package/apps/runtime/dist/services/space-data.js +20 -20
  49. package/apps/runtime/dist/services/spaces.js +7 -9
  50. package/apps/runtime/dist/services/wizards.js +11 -13
  51. package/apps/runtime/dist/store/index.js +2 -2
  52. package/apps/runtime/dist/texts.js +195 -0
  53. package/apps/runtime/dist/tools/plugin.js +7 -3
  54. package/apps/runtime/dist/widgets/step.js +2 -2
  55. package/apps/web/dist/assets/AboutDialog-CpI-cGpz.js +1 -0
  56. package/apps/web/dist/assets/{ChangelogDialog-rAtYZjxz.js → ChangelogDialog-BW56Y5UR.js} +1 -1
  57. package/apps/web/dist/assets/ChatView-CH8U-tRy.js +10 -0
  58. package/apps/web/dist/assets/EditorPage-Be5J_y4F.js +3 -0
  59. package/apps/web/dist/assets/{FlowPreview--3-yI51e.js → FlowPreview-CzmH4JUQ.js} +1 -1
  60. package/apps/web/dist/assets/{Harness-B9tvFe0U.js → Harness-CJKWO3AK.js} +2 -2
  61. package/apps/web/dist/assets/{NewWizardChannels-BxrBhC9U.js → NewWizardChannels-Ca2niMTY.js} +1 -1
  62. package/apps/web/dist/assets/NewWizardPage-D5gi4TYE.js +7 -0
  63. package/apps/web/dist/assets/{OnboardingPage-DPdH9sSj.js → OnboardingPage-Bm-c-D21.js} +1 -1
  64. package/apps/web/dist/assets/ProjectPage-C8nJCBZP.js +1 -0
  65. package/apps/web/dist/assets/{PublicRunner-Bv3TMKw4.js → PublicRunner-anxg3Rxz.js} +1 -1
  66. package/apps/web/dist/assets/{ReadOnly-CmqIrOAR.js → ReadOnly-i-6WBMSE.js} +1 -1
  67. package/apps/web/dist/assets/RunDrawer-4yvfW_2c.js +4 -0
  68. package/apps/web/dist/assets/{RunnerView-Cd1VAlIA.js → RunnerView-BkWQBa_M.js} +1 -1
  69. package/apps/web/dist/assets/SettingsPage-NL8r9S6R.js +1 -0
  70. package/apps/web/dist/assets/SetupPage-Crcfx4-p.js +1 -0
  71. package/apps/web/dist/assets/{SharePage-BT80wI1I.js → SharePage-CDEAtmBC.js} +1 -1
  72. package/apps/web/dist/assets/changelog-BGBuldNF.js +1 -0
  73. package/apps/web/dist/assets/{esm-BTVd6EFo.js → esm-BF1nonbH.js} +1 -1
  74. package/apps/web/dist/assets/index-C74LSJ-W.js +14 -0
  75. package/apps/web/dist/assets/{lock-ByHFI96G.js → lock-DVvM6IsA.js} +1 -1
  76. package/apps/web/dist/assets/{phone-KPwxAM1R.js → phone-3fG1okFs.js} +1 -1
  77. package/apps/web/dist/assets/{settings-menu-K5bg3QE3.js → settings-menu-D9fbi1Z0.js} +1 -1
  78. package/apps/web/dist/assets/ui-EuR3jezP.js +134 -0
  79. package/apps/web/dist/assets/{upload-BsIL8tNR.js → upload-BsMUhEfX.js} +1 -1
  80. package/apps/web/dist/index.html +2 -2
  81. package/apps/web/dist/mcp/flow.html +77 -77
  82. package/bin/wizards.mjs +15 -2
  83. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +26 -9
  84. package/node_modules/@engenty-wizards/shared/dist/definition.js +125 -3
  85. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +1 -1
  86. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +9 -8
  87. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +6 -6
  88. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +78 -205
  89. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +167 -68
  90. package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +3 -6
  91. package/node_modules/@engenty-wizards/shared/dist/projects.js +9 -9
  92. package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +33 -7
  93. package/node_modules/@engenty-wizards/shared/dist/providers.js +88 -37
  94. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +298 -365
  95. package/node_modules/@engenty-wizards/shared/dist/run.js +582 -240
  96. package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +36 -0
  97. package/node_modules/@engenty-wizards/shared/dist/texts.js +46 -0
  98. package/package.json +5 -1
  99. package/apps/web/dist/assets/AboutDialog-t9bayaIs.js +0 -1
  100. package/apps/web/dist/assets/ChatView-C3VqLT98.js +0 -10
  101. package/apps/web/dist/assets/EditorPage-D3Ef24b9.js +0 -3
  102. package/apps/web/dist/assets/NewWizardPage-C1_hsQPr.js +0 -7
  103. package/apps/web/dist/assets/ProjectPage-P40R1cPE.js +0 -1
  104. package/apps/web/dist/assets/RunDrawer-9qG1G-OY.js +0 -4
  105. package/apps/web/dist/assets/SettingsPage-DFz4UMfo.js +0 -1
  106. package/apps/web/dist/assets/SetupPage-B0UBNMB9.js +0 -1
  107. package/apps/web/dist/assets/changelog-BpSBMjpm.js +0 -1
  108. package/apps/web/dist/assets/index-D8vwgldH.js +0 -14
  109. package/apps/web/dist/assets/ui-4bQx9I1f.js +0 -134
@@ -1,5 +1,5 @@
1
1
  import { dayOf, slugOf } from "@engenty-wizards/shared/knowledge";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { columnIdOf } from "@engenty-wizards/shared/space-data";
4
4
  import { generateText, Output } from "ai";
5
5
  import { and, eq, inArray, isNull } from "drizzle-orm";
@@ -504,12 +504,12 @@ export async function writeSummary(valueId) {
504
504
  where: eq(schema.spaceCategoryValue.id, valueId),
505
505
  });
506
506
  if (!value) {
507
- throw new ServiceError("not_found", bilingual("Diesen Wert gibt es nicht.", "This value doesn’t exist."));
507
+ throw new ServiceError("not_found", note("valueNotExist"));
508
508
  }
509
509
  const category = await categoryRow(value.categoryId);
510
510
  const { items, total } = await itemsOfValue(value.id);
511
511
  if (!items.length) {
512
- throw new ServiceError("invalid", bilingual("Unter diesem Wert steht noch nichts.", "Nothing is filed under this value yet."));
512
+ throw new ServiceError("invalid", note("nothingFiledUnderValue"));
513
513
  }
514
514
  const model = await textModel("standard");
515
515
  const result = await generateText({
@@ -4,7 +4,7 @@ import { DEFINITION_VERSION, readable, wizardSchema, } from "@engenty-wizards/sh
4
4
  import { capabilitiesOf, costTierOf, effortOf, formatsOf, MARKETPLACE_LANGS, pluginsOf, } from "@engenty-wizards/shared/marketplace";
5
5
  import { wizardOutline } from "@engenty-wizards/shared/marketplace-entry";
6
6
  import { searchEntries } from "@engenty-wizards/shared/marketplace-search";
7
- import { bilingual } from "@engenty-wizards/shared/run";
7
+ import { note } from "@engenty-wizards/shared/run";
8
8
  import { and, eq, inArray, notInArray } from "drizzle-orm";
9
9
  import { formulaEstimate } from "../credits/estimate.js";
10
10
  import { control, controlDb, db, schema, withTenant } from "../db/client.js";
@@ -414,13 +414,13 @@ export async function marketplaceWizard(id, lang) {
414
414
  return null;
415
415
  }
416
416
  if (!readable(found.definition)) {
417
- throw new ServiceError("refused", bilingual("Diese Vorlage braucht eine neuere Version von engenty wizards. Aktualisiere zuerst engenty wizards.", "This template needs a newer version of engenty wizards. Update engenty wizards first."));
417
+ throw new ServiceError("refused", note("templateNeedsNewerVersion"));
418
418
  }
419
419
  // A wizard whose plugins are not here would stop at its first step that uses them.
420
420
  const have = await pluginIdsOf(currentTenant());
421
421
  const needs = pluginsOf(wizardSchema.parse(found.definition)).filter((p) => !have.has(p));
422
422
  if (needs.length) {
423
- throw new ServiceError("refused", bilingual(`Diese Vorlage braucht ${needs.length === 1 ? "das Plugin" : "die Plugins"} ${needs.join(", ")}: bei Pro auf engenty.ai dabei.`, `This template needs the ${needs.length === 1 ? "plugin" : "plugins"} ${needs.join(", ")}: included with Pro on engenty.ai.`));
423
+ throw new ServiceError("refused", note("templateNeedsPlugins", { count: needs.length, plugins: needs }));
424
424
  }
425
425
  return {
426
426
  id: found.id,
@@ -1,4 +1,4 @@
1
- import { bilingual } from "@engenty-wizards/shared/run";
1
+ import { note } from "@engenty-wizards/shared/run";
2
2
  import { manifestSchema, PACKAGE_FILES, PACKAGE_FORMAT, PACKAGE_MANIFEST, PACKAGE_MAX_BYTES, } from "@engenty-wizards/shared/wizard-package";
3
3
  import { cleanPath, WORKSPACE_LIMITS } from "@engenty-wizards/shared/workspace";
4
4
  import { strFromU8, unzipSync } from "fflate";
@@ -13,7 +13,7 @@ export function json(data) {
13
13
  return JSON.parse(strFromU8(data));
14
14
  }
15
15
  catch {
16
- throw invalid(bilingual("Das ist keine Wizard-Datei: Die wizard.json darin lässt sich nicht lesen.", "This isn’t a wizard file: its wizard.json can’t be read."));
16
+ throw invalid(note("wizardJsonUnreadable"));
17
17
  }
18
18
  }
19
19
  /** A manifest, or a bare definition: a wizard without workspace is fully told by its JSON. */
@@ -23,7 +23,7 @@ export function readManifest(raw) {
23
23
  }
24
24
  const parsed = manifestSchema.safeParse(raw);
25
25
  if (!parsed.success) {
26
- throw invalid(bilingual("Diese Wizard-Datei stammt aus einer neueren Version oder ist beschädigt. Aktualisiere engenty wizards oder exportiere den Wizard neu.", "This wizard file comes from a newer version, or it’s damaged. Update engenty wizards or export the wizard again."));
26
+ throw invalid(note("wizardFileComesNewer"));
27
27
  }
28
28
  return parsed.data;
29
29
  }
@@ -46,26 +46,24 @@ export function readPackage(data) {
46
46
  total += file.originalSize;
47
47
  count += 1;
48
48
  if (file.originalSize > WORKSPACE_LIMITS.fileBytes) {
49
- throw invalid(bilingual(`${file.name} ist größer als 5 MB.`, `${file.name} is larger than 5 MB.`));
49
+ throw invalid(note("fileOver5Mb", { name: file.name }));
50
50
  }
51
51
  if (count > WORKSPACE_LIMITS.files + 1 || total > PACKAGE_MAX_BYTES) {
52
- throw invalid(bilingual("Die Wizard-Datei ist zu groß: Ein Arbeitsbereich fasst höchstens 200 Dateien und 25 MB.", "The wizard file is too large: a workspace holds at most 200 files and 25 MB."));
52
+ throw invalid(note("wizardFileTooLarge"));
53
53
  }
54
54
  return true;
55
55
  },
56
56
  });
57
57
  }
58
58
  catch (err) {
59
- throw err instanceof ServiceError
60
- ? err
61
- : invalid(bilingual("Die Wizard-Datei lässt sich nicht öffnen. Exportiere den Wizard neu und versuche es noch einmal.", "The wizard file can’t be opened. Export the wizard again and try once more."));
59
+ throw err instanceof ServiceError ? err : invalid(note("wizardFileNotOpened"));
62
60
  }
63
61
  // A package unpacked and zipped again often sits in a folder of its own.
64
62
  const manifestPath = Object.keys(entries)
65
63
  .filter((name) => name === PACKAGE_MANIFEST || name.endsWith(`/${PACKAGE_MANIFEST}`))
66
64
  .sort((a, b) => a.length - b.length)[0];
67
65
  if (manifestPath === undefined) {
68
- throw invalid(bilingual("Das ist keine Wizard-Datei: Die wizard.json fehlt.", "This isn’t a wizard file: wizard.json is missing."));
66
+ throw invalid(note("wizardJsonMissing"));
69
67
  }
70
68
  const root = manifestPath.slice(0, -PACKAGE_MANIFEST.length);
71
69
  const files = Object.entries(entries)
@@ -73,12 +71,12 @@ export function readPackage(data) {
73
71
  .map(([name, content]) => {
74
72
  const path = cleanPath(name.slice(root.length + PACKAGE_FILES.length));
75
73
  if (!path) {
76
- throw invalid(bilingual(`„${name}“ ist kein gültiger Dateipfad.`, `“${name}” isn’t a valid file path.`));
74
+ throw invalid(note("notValidFilePath", { name }));
77
75
  }
78
76
  return { path, data: content };
79
77
  });
80
78
  if (files.reduce((n, f) => n + f.data.byteLength, 0) > WORKSPACE_LIMITS.totalBytes) {
81
- throw invalid(bilingual("Der Arbeitsbereich in der Wizard-Datei ist größer als 25 MB.", "The workspace in the wizard file is larger than 25 MB."));
79
+ throw invalid(note("workspaceWizardFileLarger"));
82
80
  }
83
81
  return { manifest: readManifest(json(entries[manifestPath])), files };
84
82
  }
@@ -4,6 +4,7 @@ import { strToU8, zipSync } from "fflate";
4
4
  import { listConnectors } from "../connectors/external.js";
5
5
  import { db, schema } from "../db/client.js";
6
6
  import { getBlob } from "../files/blobs.js";
7
+ import { serverText } from "../texts.js";
7
8
  import { requireAdmin } from "./access.js";
8
9
  import { draftFiles, writeFile } from "./files.js";
9
10
  import { pageTree } from "./knowledge.js";
@@ -144,16 +145,14 @@ lang = "en") {
144
145
  // The shape is checked before anything is written; a wrong one leaves no wizard behind.
145
146
  const { draft } = parseDraft(manifest.definition, files.map((f) => f.path));
146
147
  const lacks = await missing(userId, projectId, draft, manifest);
147
- const de = lang === "de";
148
+ const counts = {
149
+ name,
150
+ steps: serverText(lang, "stepCount", { count: draft.steps.length }),
151
+ files: serverText(lang, "fileCount", { count: files.length }),
152
+ };
148
153
  const note = [
149
- de
150
- ? `Importiert${name ? ` aus „${name}“` : ""}: ${draft.steps.length} Schritte, ${files.length} ${files.length === 1 ? "Datei" : "Dateien"}.`
151
- : `Imported${name ? ` from “${name}”` : ""}: ${draft.steps.length} steps, ${files.length} ${files.length === 1 ? "file" : "files"}.`,
152
- lacks.length
153
- ? de
154
- ? `Diesem Space fehlt noch: ${lacks.join(", ")}. Füge es unter Einstellungen → Connectors hinzu.`
155
- : `This space still needs: ${lacks.join(", ")}. Add it under Settings → Connectors.`
156
- : "",
154
+ serverText(lang, name ? "importedFrom" : "imported", counts),
155
+ lacks.length ? serverText(lang, "spaceStillNeeds", { needs: lacks }) : "",
157
156
  ]
158
157
  .filter(Boolean)
159
158
  .join("\n\n");
@@ -1,3 +1,4 @@
1
+ import { isLang } from "@engenty-wizards/shared/definition";
1
2
  import { z } from "zod";
2
3
  import { linkedAccount } from "../auth/account.js";
3
4
  import { managed } from "../manage.js";
@@ -34,7 +35,7 @@ const langKey = (user) => `lang:${user.tenantId}:${user.id}`;
34
35
  */
35
36
  export async function userLang(user) {
36
37
  const lang = await readSetting(langKey(user));
37
- return lang === "de" || lang === "en" ? lang : null;
38
+ return isLang(lang) ? lang : null;
38
39
  }
39
40
  export async function saveUserLang(user, lang) {
40
41
  await writeSetting(langKey(user), lang);
@@ -1,5 +1,5 @@
1
1
  import { PROJECT_LIMITS, projectFileLimit, } from "@engenty-wizards/shared/projects";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { generateText } from "ai";
4
4
  import { and, asc, count, eq, max } from "drizzle-orm";
5
5
  import { db, schema } from "../db/client.js";
@@ -79,14 +79,14 @@ export async function addProjectFile(projectId, input) {
79
79
  const mime = documentMime(input.name, input.mime) || "application/octet-stream";
80
80
  if (!accepts(input.kind, mime)) {
81
81
  throw new ServiceError("invalid", input.kind === "logo"
82
- ? bilingual("Ein Logo ist ein Bild (PNG, SVG, JPEG, WebP).", "A logo is an image (PNG, SVG, JPEG, WebP).")
82
+ ? note("logoImagePngSvg")
83
83
  : input.kind === "pattern"
84
- ? bilingual("Ein Muster ist ein Bild (SVG, PNG, JPEG, WebP).", "A pattern is an image (SVG, PNG, JPEG, WebP).")
85
- : bilingual("Hierher gehören Bilder, Videos, Audio, PDFs, Schriften, Lottie und 3D-Modelle. Texte zum Nachschlagen gehören ins Wissen.", "Images, videos, audio, PDFs, fonts, Lottie and 3D models go here. Texts to look things up in go under knowledge."));
84
+ ? note("patternImageSvgPng")
85
+ : note("filesGoHere"));
86
86
  }
87
87
  const limit = projectFileLimit(input.kind, mime);
88
88
  if (!input.data.byteLength || input.data.byteLength > limit) {
89
- throw new ServiceError("invalid", bilingual(`Wähl eine Datei bis ${Math.round(limit / 1_000_000)} MB.`, `Choose a file of ${Math.round(limit / 1_000_000)} MB or less.`));
89
+ throw new ServiceError("invalid", note("chooseFileUpTo", { mb: Math.round(limit / 1_000_000) }));
90
90
  }
91
91
  const scope = and(eq(schema.projectFile.projectId, projectId), eq(schema.projectFile.kind, input.kind));
92
92
  const [{ n, last }] = await db
@@ -94,7 +94,7 @@ export async function addProjectFile(projectId, input) {
94
94
  .from(schema.projectFile)
95
95
  .where(scope);
96
96
  if (n >= PROJECT_LIMITS.files[input.kind]) {
97
- throw new ServiceError("refused", bilingual(`Hier haben höchstens ${PROJECT_LIMITS.files[input.kind]} Dateien Platz.`, `There’s room for at most ${PROJECT_LIMITS.files[input.kind]} files here.`));
97
+ throw new ServiceError("refused", note("roomMostFiles", { count: PROJECT_LIMITS.files[input.kind] }));
98
98
  }
99
99
  const name = input.name.slice(0, 120) || "datei";
100
100
  const ref = await saveAsset({
@@ -150,21 +150,21 @@ export async function addProjectFileFromUrl(projectId, input) {
150
150
  headers: { "user-agent": "Mozilla/5.0 (compatible; engenty-wizards/0.1)" },
151
151
  signal: AbortSignal.any([AbortSignal.timeout(60_000), ...(signal ? [signal] : [])]),
152
152
  }).catch(() => {
153
- throw new ServiceError("invalid", bilingual("Die Adresse ist nicht erreichbar.", "The address can’t be reached."));
153
+ throw new ServiceError("invalid", note("addressNotReached"));
154
154
  });
155
155
  if (!res.ok) {
156
- throw new ServiceError("invalid", bilingual(`Die Adresse antwortete mit ${res.status}.`, `The address responded with ${res.status}.`));
156
+ throw new ServiceError("invalid", note("addressResponded", { status: res.status }));
157
157
  }
158
158
  const mime = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
159
159
  if (mime === "text/html" && kind !== "document") {
160
160
  await res.body?.cancel();
161
- throw new ServiceError("invalid", bilingual("Unter der Adresse steht eine Webseite, kein Bild.", "The address leads to a web page, not an image."), {
161
+ throw new ServiceError("invalid", note("addressLeadsWebPage"), {
162
162
  reason: "page",
163
163
  url: res.url,
164
164
  });
165
165
  }
166
166
  if (Number(res.headers.get("content-length") ?? 0) > projectFileLimit(kind, mime)) {
167
- throw new ServiceError("invalid", bilingual("Die Datei ist zu groß.", "The file is too large."));
167
+ throw new ServiceError("invalid", note("addressFileTooLarge"));
168
168
  }
169
169
  const data = new Uint8Array(await res.arrayBuffer());
170
170
  const name = decodeURIComponent(new URL(res.url).pathname.split("/").pop() || kind);
@@ -305,7 +305,7 @@ export async function reindexProjectFile(projectId, fileId) {
305
305
  await requireWritableProject(projectId);
306
306
  const row = await projectFile(projectId, fileId);
307
307
  if (row.kind !== "document") {
308
- throw new ServiceError("invalid", bilingual("Nur Dokumente stehen im Index.", "Only documents are in the index."));
308
+ throw new ServiceError("invalid", note("onlyDocumentsIndex"));
309
309
  }
310
310
  const [pending] = await db
311
311
  .update(schema.projectFile)
@@ -380,7 +380,7 @@ export async function putOriginFile(origin, input) {
380
380
  });
381
381
  const mime = documentMime(input.name, input.mime) || "application/octet-stream";
382
382
  if (!input.data.byteLength || input.data.byteLength > projectFileLimit("document", mime)) {
383
- throw new ServiceError("invalid", bilingual(`„${input.name}“ ist leer oder zu groß für ein Dokument.`, `“${input.name}” is empty or too large for a document.`));
383
+ throw new ServiceError("invalid", note("emptyTooLargeDocument", { name: input.name }));
384
384
  }
385
385
  const name = input.name.slice(0, 120) || "datei";
386
386
  const values = {
@@ -407,7 +407,7 @@ export async function putOriginFile(origin, input) {
407
407
  .from(schema.projectFile)
408
408
  .where(and(eq(schema.projectFile.projectId, input.space), eq(schema.projectFile.kind, "document")));
409
409
  if (n >= PROJECT_LIMITS.files.document) {
410
- throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${PROJECT_LIMITS.files.document} Dokumente.`, `A space can have at most ${PROJECT_LIMITS.files.document} documents.`));
410
+ throw new ServiceError("refused", note("spaceMostDocuments", { count: PROJECT_LIMITS.files.document }));
411
411
  }
412
412
  const ref = await saveAsset({ kind: "project", mime, name, data: input.data });
413
413
  id = ref.id;
@@ -1,5 +1,5 @@
1
1
  import { COLOR_SECTION_KINDS, cleanAssetSections, cleanLogoSections, FACT_TYPES, FONT_USES, FONT_WEIGHTS, GRADIENT_TYPES, orderColors, PROJECT_LIMITS, RULE_MARKS, } from "@engenty-wizards/shared/projects";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { and, count, eq, isNull } from "drizzle-orm";
4
4
  import { nanoid } from "nanoid";
5
5
  import { z } from "zod";
@@ -8,6 +8,7 @@ import { env } from "../env.js";
8
8
  import { managed, tenantInfo } from "../manage.js";
9
9
  import { spaceChanged } from "../plugins/events.js";
10
10
  import { currentTenant } from "../tenants/tenant.js";
11
+ import { serverText } from "../texts.js";
11
12
  import { mayBuild, requireAdmin, requireBuild, requireWritable } from "./access.js";
12
13
  import { notFound, ServiceError } from "./errors.js";
13
14
  import { forgetWizardLinks } from "./links.js";
@@ -148,10 +149,7 @@ const madeHere = () => and(eq(schema.project.tenantId, currentTenant()), isNull(
148
149
  /** The tenant's projects a local install synced. */
149
150
  const synced = () => and(eq(schema.project.tenantId, currentTenant()), eq(schema.project.origin, "local"));
150
151
  /** What the first space is called, in the language of whoever first opens the studio. */
151
- const FIRST_SPACE = {
152
- cloud: { de: "Meine Wizards", en: "My wizards" },
153
- local: { de: "Mein Computer", en: "My computer" },
154
- };
152
+ const FIRST_SPACE = { cloud: "firstSpaceCloud", local: "firstSpaceLocal" };
155
153
  /**
156
154
  * A tenant that builds here always has a project of its own: the first one is made when its
157
155
  * studio is first opened, named in that request's language.
@@ -163,7 +161,7 @@ async function ensureProject(lang = "en") {
163
161
  await db.insert(schema.project).values({
164
162
  id,
165
163
  tenantId: currentTenant(),
166
- name: FIRST_SPACE[env.manage.url ? "cloud" : "local"][lang],
164
+ name: serverText(lang, FIRST_SPACE[env.manage.url ? "cloud" : "local"]),
167
165
  });
168
166
  spaceChanged("space.created", id);
169
167
  }
@@ -230,9 +228,7 @@ export async function createProject(_userId, name) {
230
228
  .where(eq(schema.project.tenantId, currentTenant()));
231
229
  const limit = await projectLimit();
232
230
  if (n >= limit) {
233
- throw new ServiceError("refused", limit === 1
234
- ? bilingual("Hier ist nur ein Space möglich.", "Only one space is allowed here.")
235
- : bilingual(`Hier sind höchstens ${limit} Spaces möglich.`, `You can have at most ${limit} spaces here.`));
231
+ throw new ServiceError("refused", limit === 1 ? note("onlyOneSpaceAllowed") : note("mostSpaces", { count: limit }));
236
232
  }
237
233
  const id = nanoid(12);
238
234
  await db
@@ -256,7 +252,7 @@ export async function updateProject(userId, projectId, patch) {
256
252
  return { ...s, headers };
257
253
  });
258
254
  if (patch.facts && new Set(patch.facts.map((f) => f.key)).size !== patch.facts.length) {
259
- throw new ServiceError("invalid", bilingual("Zwei Einträge haben denselben Schlüssel.", "Two entries have the same key."));
255
+ throw new ServiceError("invalid", note("twoEntriesSameKey"));
260
256
  }
261
257
  await db
262
258
  .update(schema.project)
@@ -370,7 +366,7 @@ export async function deleteProject(userId, projectId) {
370
366
  await requireAdmin();
371
367
  const all = await db.query.project.findMany({ where: madeHere() });
372
368
  if (all.length <= 1) {
373
- throw new ServiceError("refused", bilingual("Der letzte Space lässt sich nicht löschen.", "You can’t delete the last space."));
369
+ throw new ServiceError("refused", note("notDeleteLastSpace"));
374
370
  }
375
371
  await removeProject(projectId);
376
372
  }
@@ -1,5 +1,5 @@
1
1
  import { wizardLang } from "@engenty-wizards/shared/definition";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { BUILT_IN_RUNNERS, CONVERSATION_RUNNERS, DEFAULT_RUNNER, DEFAULT_RUNNER_SETTINGS, runnerFit, } from "@engenty-wizards/shared/runners";
4
4
  import { env } from "../env.js";
5
5
  import { conversationAccess, conversationAvailable } from "../models.js";
@@ -72,14 +72,14 @@ export async function checkRunnerSettings(settings) {
72
72
  const ids = new Set(available.map((r) => r.id));
73
73
  const unknown = settings.enabled.find((id) => !ids.has(id));
74
74
  if (unknown) {
75
- throw new ServiceError("invalid", bilingual(`Einen Runner „${unknown}“ gibt es hier nicht.`, `There’s no runner “${unknown}” here.`));
75
+ throw new ServiceError("invalid", note("unknownRunner", { unknown }));
76
76
  }
77
77
  const enabled = [...new Set(settings.enabled)];
78
78
  if (!enabled.length) {
79
- throw new ServiceError("invalid", bilingual("Mindestens ein Runner muss eingeschaltet bleiben.", "At least one runner has to stay on."));
79
+ throw new ServiceError("invalid", note("leastOneRunnerStay"));
80
80
  }
81
81
  if (!enabled.includes(settings.default)) {
82
- throw new ServiceError("invalid", bilingual("Der Link muss einen eingeschalteten Runner öffnen.", "The link has to open a runner that’s turned on."));
82
+ throw new ServiceError("invalid", note("linkOpenRunnerTurned"));
83
83
  }
84
84
  return { default: settings.default, enabled };
85
85
  }
@@ -1,5 +1,5 @@
1
1
  import { FIELD_WAYS, formatsFor, isDecidedField, shownFields, slotText, wizardLang, } from "@engenty-wizards/shared/definition";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { and, count, desc, eq, gte, inArray, max, or, sql } from "drizzle-orm";
4
4
  import { canSpend, MICROS_PER_CREDIT } from "../credits/credits.js";
5
5
  import { db, schema } from "../db/client.js";
@@ -18,7 +18,7 @@ export async function startTestRun(userId, wizardId, autopilot) {
18
18
  const w = await ownedWizard(userId, wizardId);
19
19
  const { definition, files } = await requireClean(w);
20
20
  if (!(await canSpend())) {
21
- throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
21
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
22
22
  }
23
23
  const runId = await createRun({
24
24
  wizardId: w.id,
@@ -80,13 +80,10 @@ function fly(runId, answers, acceptReviews) {
80
80
  } while (again);
81
81
  }
82
82
  catch (err) {
83
- const answers = (lang) => err instanceof RunInputError
84
- ? fieldErrorsIn(err.errors, lang)
85
- .map((e) => `${e.field} – ${e.message}`)
86
- .join("; ")
87
- : "";
88
83
  const message = err instanceof RunInputError
89
- ? bilingual(`Antworten fehlen oder passen nicht: ${answers("de")}`, `Answers are missing or don’t fit: ${answers("en")}`)
84
+ ? note("answersMissing", {
85
+ answers: err.errors.map((e) => note("fieldError", { field: e.field, message: e.message })),
86
+ })
90
87
  : saidOf(err);
91
88
  await emitEvent(runId, null, "error", message).catch(() => undefined);
92
89
  land();
@@ -377,7 +374,7 @@ export async function startRun(userId, wizardId, answers = {}) {
377
374
  throw notFound();
378
375
  }
379
376
  if (!(await canSpend())) {
380
- throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
377
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
381
378
  }
382
379
  const runId = await createRun({
383
380
  wizardId: w.id,
@@ -391,7 +388,7 @@ export async function startRun(userId, wizardId, answers = {}) {
391
388
  throw new ServiceError("refused", saidOf(err));
392
389
  }
393
390
  if (err instanceof NoCreditsError) {
394
- throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
391
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
395
392
  }
396
393
  throw err;
397
394
  });
@@ -1,4 +1,4 @@
1
- import { bilingual } from "@engenty-wizards/shared/run";
1
+ import { note } from "@engenty-wizards/shared/run";
2
2
  import { and, eq, isNotNull, lt } from "drizzle-orm";
3
3
  import { nanoid } from "nanoid";
4
4
  import { db, schema } from "../db/client.js";
@@ -13,7 +13,7 @@ export const shareUrlFor = (token) => `${env.publicUrl}/s/${token}`;
13
13
  /** Shares a finished run. Idempotent: the same run keeps the same link. */
14
14
  export async function shareRun(run) {
15
15
  if (run.status !== "done") {
16
- throw new ServiceError("refused", bilingual("Nur fertige Ergebnisse lassen sich teilen.", "Only finished results can be shared."));
16
+ throw new ServiceError("refused", note("onlyFinishedResultsShared"));
17
17
  }
18
18
  let token = run.shareToken;
19
19
  if (!token) {
@@ -1,11 +1,12 @@
1
1
  import { factKey, PROJECT_LIMITS } from "@engenty-wizards/shared/projects";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { nanoid } from "nanoid";
4
4
  import { z } from "zod";
5
5
  import { runStudioAssistant } from "../agents/studio-assistant.js";
6
6
  import { canSpend } from "../credits/credits.js";
7
7
  import { errorText } from "../model-errors.js";
8
8
  import { currentTenant } from "../tenants/tenant.js";
9
+ import { serverText } from "../texts.js";
9
10
  import { projectWritable, requireWritable } from "./access.js";
10
11
  import { apiSpace, apiSpaceDetails } from "./api.js";
11
12
  import { openChat, turnHistory } from "./assistant-chats.js";
@@ -167,15 +168,13 @@ function assistMessage(input) {
167
168
  }
168
169
  async function work(job, projectId, input) {
169
170
  try {
170
- const de = siteLang(input) === "de";
171
- job.activity = de
172
- ? "Übernimmt die Angaben der Website …"
173
- : "Taking over what the website says …";
171
+ const lang = siteLang(input);
172
+ job.activity = serverText(lang, "siteTakingOver");
174
173
  await fillFromSite(job.userId, projectId, input);
175
- job.activity = de ? "Schreibt die Seiten in Wissen …" : "Writing the pages into Knowledge …";
174
+ job.activity = serverText(lang, "siteWritingPages");
176
175
  job.pages = await writePages(projectId, input);
177
176
  if (input.assist && (await canSpend())) {
178
- job.activity = de ? "Liest die Angaben …" : "Reading the details …";
177
+ job.activity = serverText(lang, "siteReading");
179
178
  const project = await ownedProject(job.userId, projectId);
180
179
  // The person's open assistant chat gives the context; the turn is not stored in it.
181
180
  const chat = await openChat(job.user);
@@ -210,9 +209,7 @@ async function work(job, projectId, input) {
210
209
  job.error =
211
210
  err instanceof ServiceError
212
211
  ? errorText(err, siteLang(input))
213
- : siteLang(input) === "de"
214
- ? "Die Website ließ sich nicht übernehmen."
215
- : "The website could not be taken over.";
212
+ : serverText(siteLang(input), "siteFailed");
216
213
  console.error("[site]", err);
217
214
  }
218
215
  }
@@ -267,7 +264,7 @@ function siteJobView(job) {
267
264
  export function siteJob(jobId) {
268
265
  const job = jobs.get(jobId);
269
266
  if (!job || job.tenantId !== currentTenant()) {
270
- throw new ServiceError("not_found", bilingual("Diesen Vorgang gibt es nicht, oder er ist schon beendet.", "This job doesn’t exist or has already finished."));
267
+ throw new ServiceError("not_found", note("jobNotExistAlready"));
271
268
  }
272
269
  return siteJobView(job);
273
270
  }
@@ -1,5 +1,5 @@
1
1
  import { CATEGORY_TYPES, dayOf, } from "@engenty-wizards/shared/knowledge";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { and, asc, eq, inArray, isNotNull, or, sql } from "drizzle-orm";
4
4
  import { nanoid } from "nanoid";
5
5
  import { db, schema } from "../db/client.js";
@@ -273,10 +273,10 @@ const MAX_CATEGORIES = 60;
273
273
  function checkDraft(draft) {
274
274
  const name = draft.name.replace(/\s+/g, " ").trim().slice(0, 80);
275
275
  if (!name) {
276
- throw new ServiceError("invalid", bilingual("Eine Kategorie braucht einen Namen.", "A category needs a name."));
276
+ throw new ServiceError("invalid", note("categoryNeedsName"));
277
277
  }
278
278
  if (!CATEGORY_TYPES.includes(draft.type)) {
279
- throw new ServiceError("invalid", bilingual(`Unbekannter Typ „${draft.type}“.`, `Unknown type “${draft.type}”.`));
279
+ throw new ServiceError("invalid", note("unknownCategoryType", { type: draft.type }));
280
280
  }
281
281
  return name;
282
282
  }
@@ -330,7 +330,7 @@ export async function putCategory(projectId, draft, opts = {}) {
330
330
  return known;
331
331
  }
332
332
  if (rows.length >= MAX_CATEGORIES) {
333
- throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${MAX_CATEGORIES} Kategorien.`, `A space can have at most ${MAX_CATEGORIES} categories.`));
333
+ throw new ServiceError("refused", note("spaceMostCategories", { count: MAX_CATEGORIES }));
334
334
  }
335
335
  const id = nanoid(12);
336
336
  const [row] = await db
@@ -409,7 +409,7 @@ export async function updateCategory(categoryId, patch) {
409
409
  if (name !== row.name) {
410
410
  const clash = byName(await categoryRows(row.projectId), name);
411
411
  if (clash && clash.id !== row.id) {
412
- throw new ServiceError("invalid", bilingual(`Die Kategorie „${name}“ gibt es schon.`, `The category “${name}” already exists.`));
412
+ throw new ServiceError("invalid", note("categoryAlreadyExists", { name }));
413
413
  }
414
414
  }
415
415
  await db
@@ -459,7 +459,7 @@ export async function addCategoryValue(categoryId, value) {
459
459
  const category = await categoryRow(categoryId);
460
460
  await requireWritableProject(category.projectId);
461
461
  if (category.type !== "choice") {
462
- throw new ServiceError("invalid", bilingual("Werte gibt es nur bei Kategorien vom Typ Auswahl.", "Only choice categories have values."));
462
+ throw new ServiceError("invalid", note("onlyChoiceCategoriesValues"));
463
463
  }
464
464
  const ids = await addValues(category.id, [value]);
465
465
  return { id: [...ids.values()].at(-1) };
@@ -489,7 +489,7 @@ export async function mergeCategoryValues(fromId, intoId) {
489
489
  const from = await valueRow(fromId);
490
490
  const into = await valueRow(intoId);
491
491
  if (from.category.id !== into.category.id || fromId === intoId) {
492
- throw new ServiceError("invalid", bilingual("Nur Werte einer Kategorie lassen sich zusammenführen.", "Only values of one category can be merged."));
492
+ throw new ServiceError("invalid", note("onlyValuesOneCategory"));
493
493
  }
494
494
  await requireWritableProject(from.category.projectId);
495
495
  const t = schema.spaceItemCategory;