wizards 0.5.7 → 0.5.8

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 (108) 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-BcTtA42P.js +1 -0
  56. package/apps/web/dist/assets/{ChangelogDialog-rAtYZjxz.js → ChangelogDialog-DMOWV_Iu.js} +1 -1
  57. package/apps/web/dist/assets/ChatView-CrnrYHjp.js +10 -0
  58. package/apps/web/dist/assets/EditorPage-DpEw3eOu.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-DipH0Vec.js} +2 -2
  61. package/apps/web/dist/assets/{NewWizardChannels-BxrBhC9U.js → NewWizardChannels-DShu0PP6.js} +1 -1
  62. package/apps/web/dist/assets/NewWizardPage-HSa6PNJs.js +7 -0
  63. package/apps/web/dist/assets/{OnboardingPage-DPdH9sSj.js → OnboardingPage-CPADMs4c.js} +1 -1
  64. package/apps/web/dist/assets/ProjectPage-DMPcrhgt.js +1 -0
  65. package/apps/web/dist/assets/{PublicRunner-Bv3TMKw4.js → PublicRunner-DZ5sWmtg.js} +1 -1
  66. package/apps/web/dist/assets/{ReadOnly-CmqIrOAR.js → ReadOnly-B9xaq_M3.js} +1 -1
  67. package/apps/web/dist/assets/RunDrawer-9nMGhbSm.js +4 -0
  68. package/apps/web/dist/assets/{RunnerView-Cd1VAlIA.js → RunnerView-NSSP3tc6.js} +1 -1
  69. package/apps/web/dist/assets/SettingsPage-Dy9MbwQh.js +1 -0
  70. package/apps/web/dist/assets/SetupPage-kemgI7v5.js +1 -0
  71. package/apps/web/dist/assets/{SharePage-BT80wI1I.js → SharePage-CQfIdcwy.js} +1 -1
  72. package/apps/web/dist/assets/changelog-BW40vzI6.js +1 -0
  73. package/apps/web/dist/assets/{esm-BTVd6EFo.js → esm-BF1nonbH.js} +1 -1
  74. package/apps/web/dist/assets/index-DQ9qAB4v.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/node_modules/@engenty-wizards/shared/dist/definition.d.ts +26 -9
  83. package/node_modules/@engenty-wizards/shared/dist/definition.js +125 -3
  84. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +1 -1
  85. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +9 -8
  86. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +6 -6
  87. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +78 -205
  88. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +167 -68
  89. package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +3 -6
  90. package/node_modules/@engenty-wizards/shared/dist/projects.js +9 -9
  91. package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +33 -7
  92. package/node_modules/@engenty-wizards/shared/dist/providers.js +88 -37
  93. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +298 -365
  94. package/node_modules/@engenty-wizards/shared/dist/run.js +582 -240
  95. package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +36 -0
  96. package/node_modules/@engenty-wizards/shared/dist/texts.js +46 -0
  97. package/package.json +1 -1
  98. package/apps/web/dist/assets/AboutDialog-t9bayaIs.js +0 -1
  99. package/apps/web/dist/assets/ChatView-C3VqLT98.js +0 -10
  100. package/apps/web/dist/assets/EditorPage-D3Ef24b9.js +0 -3
  101. package/apps/web/dist/assets/NewWizardPage-C1_hsQPr.js +0 -7
  102. package/apps/web/dist/assets/ProjectPage-P40R1cPE.js +0 -1
  103. package/apps/web/dist/assets/RunDrawer-9qG1G-OY.js +0 -4
  104. package/apps/web/dist/assets/SettingsPage-DFz4UMfo.js +0 -1
  105. package/apps/web/dist/assets/SetupPage-B0UBNMB9.js +0 -1
  106. package/apps/web/dist/assets/changelog-BpSBMjpm.js +0 -1
  107. package/apps/web/dist/assets/index-D8vwgldH.js +0 -14
  108. package/apps/web/dist/assets/ui-4bQx9I1f.js +0 -134
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { wizardLang } from "@engenty-wizards/shared/definition";
2
+ import { LANGS, wizardLang } from "@engenty-wizards/shared/definition";
3
3
  import { noteText } from "@engenty-wizards/shared/run";
4
4
  import { and, asc, eq, inArray, isNotNull } from "drizzle-orm";
5
5
  import { db, schema, withTenant } from "../db/client.js";
@@ -67,9 +67,7 @@ export function runsApiFor(pluginId) {
67
67
  throw new ServiceError("refused", errorText(err, wizardLang(version.definition)));
68
68
  }
69
69
  if (err instanceof NoCreditsError) {
70
- throw new ServiceError("no_credits", wizardLang(version.definition) === "de"
71
- ? "Dieser Wizard hat gerade kein Guthaben mehr. Versuch es später noch einmal."
72
- : "This wizard has run out of credits for now. Try again later.");
70
+ throw new ServiceError("no_credits", noteText({ code: "wizardRunOutCredits" }, wizardLang(version.definition)));
73
71
  }
74
72
  throw err;
75
73
  });
@@ -157,7 +155,7 @@ export function runsApiFor(pluginId) {
157
155
  note: event.note
158
156
  ? {
159
157
  code: event.note.code,
160
- text: { de: noteText(event.note, "de"), en: noteText(event.note, "en") },
158
+ text: Object.fromEntries(LANGS.map((lang) => [lang, noteText(event.note, lang)])),
161
159
  }
162
160
  : null,
163
161
  }
@@ -1,4 +1,4 @@
1
- import { bilingual } from "@engenty-wizards/shared/run";
1
+ import { note } from "@engenty-wizards/shared/run";
2
2
  import { PACKAGE_MIME } from "@engenty-wizards/shared/wizard-package";
3
3
  import { Hono } from "hono";
4
4
  import { bodyLimit } from "hono/body-limit";
@@ -20,7 +20,7 @@ import { siteJob, siteSchema, startSiteJob } from "../services/site.js";
20
20
  import { checkOnServer, checkSchema, listSyncedSpaces, patchSyncedWizard, removeSyncedSpace, removeSyncedWizard, rotateSyncedLink, syncDataSchema, syncSettingsSchema, syncSpaceData, syncWizard, syncWizardSchema, } from "../services/spaces.js";
21
21
  import { createWizard, ownedWizard, publishWizard, writeDraft } from "../services/wizards.js";
22
22
  import { codeOf, tenantStatus } from "../tenants/control.js";
23
- import { inLang, notFoundAnswer } from "./lang.js";
23
+ import { notFoundAnswer, say } from "./lang.js";
24
24
  import { dispatchAs } from "./plugins.js";
25
25
  const importSchema = z.object({
26
26
  /** An earlier import's wizard: it is updated instead of a new one being made. */
@@ -61,7 +61,7 @@ async function caller(c, scopes) {
61
61
  }
62
62
  if ((await tenantStatus(who.tenantId)) === "suspended") {
63
63
  return c.json({
64
- error: inLang(c, "Dieses Konto ist gesperrt.", "This account is suspended."),
64
+ error: say(c, "accountSuspended"),
65
65
  code: "tenant_suspended",
66
66
  }, 403);
67
67
  }
@@ -72,7 +72,7 @@ function withinSyncLimit(c, who) {
72
72
  return allowed(`sync:${who.tenantId}`, 240, HOUR)
73
73
  ? null
74
74
  : c.json({
75
- error: inLang(c, "Zu viele Übertragungen. Versuch es später noch einmal.", "Too many transfers. Try again later."),
75
+ error: say(c, "tooManyTransfersTry"),
76
76
  code: "rate_limited",
77
77
  }, 429);
78
78
  }
@@ -90,7 +90,7 @@ async function pluginCall(c) {
90
90
  if (!(await pluginIdsOf(who.tenantId)).has(id)) {
91
91
  return loadedPlugin(id) || PRO_PLUGINS.includes(id)
92
92
  ? c.json({
93
- error: inLang(c, "Dieses Plugin gehört nicht zu deinem Paket. Mit Pro ist es dabei.", "This plugin is not part of your plan. It comes with Pro."),
93
+ error: say(c, "pluginNotPartPlan"),
94
94
  code: "plugin",
95
95
  plugin: id,
96
96
  }, 403)
@@ -180,7 +180,7 @@ export const apiRoutes = new Hono()
180
180
  });
181
181
  if (!found) {
182
182
  return c.json({
183
- error: inLang(c, "Der Wizard ist noch nicht veröffentlicht.", "The wizard is not published yet."),
183
+ error: say(c, "wizardNotPublishedYet"),
184
184
  code: "not_found",
185
185
  }, 404);
186
186
  }
@@ -290,7 +290,7 @@ export const apiRoutes = new Hono()
290
290
  }
291
291
  if (!allowed(`site:${who.tenantId}`, 20, HOUR)) {
292
292
  return c.json({
293
- error: inLang(c, "Zu oft hintereinander. Versuch es später noch einmal.", "Too many times in a row. Try again later."),
293
+ error: say(c, "tooManyTimesRow"),
294
294
  code: "rate_limited",
295
295
  }, 429);
296
296
  }
@@ -396,7 +396,7 @@ export const apiRoutes = new Hono()
396
396
  }
397
397
  if ((await tenantStatus(who.tenantId)) === "suspended") {
398
398
  return c.json({
399
- error: inLang(c, "Dieses Konto ist gesperrt.", "This account is suspended."),
399
+ error: say(c, "accountSuspended"),
400
400
  code: "tenant_suspended",
401
401
  }, 403);
402
402
  }
@@ -427,7 +427,7 @@ export const apiRoutes = new Hono()
427
427
  return c.json({ wizardId, revision: written.revision, issues: written.issues });
428
428
  }
429
429
  if (written.issues.length) {
430
- throw new ServiceError("has_issues", bilingual("Der Wizard hat noch Fehler.", "The wizard still has errors."), {
430
+ throw new ServiceError("has_issues", note("wizardStillErrors"), {
431
431
  issues: written.issues,
432
432
  wizardId,
433
433
  });
@@ -1,20 +1,25 @@
1
+ import { localLang } from "@engenty-wizards/shared/definition";
2
+ import { serverText } from "../texts.js";
1
3
  /**
2
- * The language a visitor's request asks for: what the runner around it speaks, the browser's
3
- * first language (apps/web: `navigator.language`) — German for "de…", else English; English
4
- * without one, as the runner starts.
4
+ * The language a visitor's request asks for, as the studio starts in it (`localLang`): the
5
+ * first of the browser's languages engenty speaks other than English, else English. The
6
+ * browser's `Accept-Language` names them in its order; the server has no time zone to go by.
5
7
  */
6
8
  export function requestLang(c) {
7
- const asked = c.req.header("accept-language")?.trim().toLowerCase();
8
- return asked?.startsWith("de") ? "de" : "en";
9
+ const asked = (c.req.header("accept-language") ?? "")
10
+ .split(",")
11
+ .map((part) => part.split(";")[0]?.trim() ?? "")
12
+ .filter(Boolean);
13
+ return localLang(asked) ?? "en";
9
14
  }
10
- /** A line the person reads, in the language their request asks for. */
11
- export function inLang(c, de, en) {
12
- return requestLang(c) === "de" ? de : en;
15
+ /** A line of the server's table the person reads, in the language their request asks for. */
16
+ export function say(c, key, params) {
17
+ return serverText(requestLang(c), key, params);
13
18
  }
14
19
  /** A 404 the person may read: a sentence in their language, the machine code beside it. */
15
20
  export function notFoundAnswer(c) {
16
21
  return {
17
- error: inLang(c, "Das gibt es nicht oder nicht mehr.", "This doesn’t exist or was deleted."),
22
+ error: say(c, "notFound"),
18
23
  code: "not_found",
19
24
  };
20
25
  }
@@ -1,4 +1,4 @@
1
- import { FORMATS, LIST_FORMATS, TEXT_CLASSES } from "@engenty-wizards/shared/definition";
1
+ import { FORMATS, LANGS, LIST_FORMATS, TEXT_CLASSES } from "@engenty-wizards/shared/definition";
2
2
  import { TableColumnValueError } from "@engenty-wizards/shared/engenty/data-tables";
3
3
  import { noteText } from "@engenty-wizards/shared/run";
4
4
  import { getConnInfo } from "@hono/node-server/conninfo";
@@ -39,7 +39,7 @@ import { isIconFile, wizardAvatarSvg, wizardIcon } from "../services/wizard-icon
39
39
  import { clearStore, deleteRows, listRows, listSecrets, readStoreFile, StoreError, saveRows, scopeOf, storeFiles, updateRow, } from "../store/index.js";
40
40
  import { tenantOfLink, tenantOfRun, tenantStatus, tokenOfCode, visitorOverLimit, } from "../tenants/control.js";
41
41
  import { bareMime, byteRange, inlineSafe, uploadLimit, uploadMime, wizardManifest, } from "./delivery.js";
42
- import { inLang, notFoundAnswer, requestLang } from "./lang.js";
42
+ import { notFoundAnswer, requestLang, say } from "./lang.js";
43
43
  const VISITOR_COOKIE = "wz_vid";
44
44
  /** Kept files a review shows inline. */
45
45
  const INLINE_TYPES = new Set(["application/pdf", "image/png", "image/jpeg", "image/webp"]);
@@ -205,7 +205,7 @@ export const publicRoutes = new Hono()
205
205
  const ip = clientIp(c);
206
206
  if (codeLookupBlocked(ip)) {
207
207
  return c.json({
208
- error: inLang(c, "Zu viele Versuche. Warte kurz und versuch es noch einmal.", "Too many tries. Wait a moment and try again."),
208
+ error: say(c, "tooManyTriesWait"),
209
209
  code: "too_many_tries",
210
210
  }, 429);
211
211
  }
@@ -633,7 +633,7 @@ export const runRoutes = new Hono()
633
633
  .object({
634
634
  lat: z.number().min(-90).max(90),
635
635
  lng: z.number().min(-180).max(180),
636
- language: z.enum(["de", "en"]).default("en"),
636
+ language: z.enum(LANGS).default("en"),
637
637
  })
638
638
  .parse(await c.req.json());
639
639
  return c.json({ label: await reverseGeocode(lat, lng, language) });
@@ -1012,7 +1012,7 @@ export const runRoutes = new Hono()
1012
1012
  .object({
1013
1013
  token: z.string().min(8).max(512),
1014
1014
  platform: z.enum(["ios", "android"]),
1015
- lang: z.enum(["en", "de"]).default("en"),
1015
+ lang: z.enum(LANGS).default("en"),
1016
1016
  })
1017
1017
  .parse(await c.req.json());
1018
1018
  await setPushDevice(run.id, body);
@@ -1,11 +1,11 @@
1
1
  import { mkdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { ASSISTANT_LAYOUTS, ASSISTANT_PAGE_BYTES, assistantSignalSchema, assistantTurnSchema, } from "@engenty-wizards/shared/assistant";
4
- import { MODEL_CLASSES, TEXT_CLASSES, } from "@engenty-wizards/shared/definition";
4
+ import { LANGS, MODEL_CLASSES, TEXT_CLASSES, } from "@engenty-wizards/shared/definition";
5
5
  import { CATEGORY_TYPES, whereSchema } from "@engenty-wizards/shared/knowledge";
6
6
  import { PROJECT_FILE_KINDS } from "@engenty-wizards/shared/projects";
7
7
  import { isCreditsRef } from "@engenty-wizards/shared/providers";
8
- import { bilingual } from "@engenty-wizards/shared/run";
8
+ import { note } from "@engenty-wizards/shared/run";
9
9
  import { PACKAGE_EXTENSION, PACKAGE_MAX_BYTES, PACKAGE_MIME, } from "@engenty-wizards/shared/wizard-package";
10
10
  import { generateText } from "ai";
11
11
  import { Hono } from "hono";
@@ -63,9 +63,10 @@ import { createWizard, deleteWizard, draftChanges, duplicateWizard, listWizards,
63
63
  import { readSetting, writeSetting } from "../settings.js";
64
64
  import { codeOf } from "../tenants/control.js";
65
65
  import { currentTenant } from "../tenants/tenant.js";
66
+ import { serverText } from "../texts.js";
66
67
  import { applyUpdate, updateStatus } from "../update.js";
67
68
  import { byteRange } from "./delivery.js";
68
- import { inLang, requestLang } from "./lang.js";
69
+ import { requestLang, say } from "./lang.js";
69
70
  /** The model sources a test call has worked on: the setup is done for those, not for the others. */
70
71
  async function testedSources() {
71
72
  const tested = await readSetting("setup-done");
@@ -282,7 +283,7 @@ export const studio = new Hono()
282
283
  .get("/team", async (c) => {
283
284
  if (!managed) {
284
285
  return c.json({
285
- error: inLang(c, "Das gibt es nur in der Cloud.", "This is only available in the cloud."),
286
+ error: say(c, "onlyAvailableCloud"),
286
287
  code: "not_managed",
287
288
  }, 404);
288
289
  }
@@ -304,14 +305,14 @@ export const studio = new Hono()
304
305
  // `?fresh=1` asks GitHub now instead of within the hour.
305
306
  .get("/update", async (c) => managed
306
307
  ? c.json({
307
- error: inLang(c, "Das gibt es nur auf deinem Computer.", "This is only available on your computer."),
308
+ error: say(c, "onlyAvailableComputer"),
308
309
  code: "not_local",
309
310
  }, 404)
310
311
  : c.json(await updateStatus(c.req.query("fresh") === "1")))
311
312
  .post("/update", async (c) => {
312
313
  if (managed) {
313
314
  return c.json({
314
- error: inLang(c, "Das gibt es nur auf deinem Computer.", "This is only available on your computer."),
315
+ error: say(c, "onlyAvailableComputer"),
315
316
  code: "not_local",
316
317
  }, 404);
317
318
  }
@@ -320,7 +321,7 @@ export const studio = new Hono()
320
321
  })
321
322
  .put("/profile", async (c) => c.json(await saveProfile(c.get("user"), profileSchema.parse(await c.req.json()))))
322
323
  .put("/lang", async (c) => {
323
- const { lang } = z.object({ lang: z.enum(["de", "en"]) }).parse(await c.req.json());
324
+ const { lang } = z.object({ lang: z.enum(LANGS) }).parse(await c.req.json());
324
325
  await saveUserLang(c.get("user"), lang);
325
326
  return c.json({ lang });
326
327
  })
@@ -342,10 +343,10 @@ export const studio = new Hono()
342
343
  .post("/assistant/speak", async (c) => {
343
344
  const { text } = z.object({ text: z.string().min(1).max(4000) }).parse(await c.req.json());
344
345
  if ((await classWays()).speech.kind === "none") {
345
- throw new ServiceError("refused", bilingual("Hier ist kein Sprachmodell eingerichtet.", "No language model is set up here."));
346
+ throw new ServiceError("refused", note("noLanguageModelSet"));
346
347
  }
347
348
  if (!(await canSpend())) {
348
- throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
349
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
349
350
  }
350
351
  const voice = await generateSpeechMedia({ text, abortSignal: c.req.raw.signal });
351
352
  return c.body(new Uint8Array(voice.bytes), 200, { "content-type": voice.mime });
@@ -365,10 +366,10 @@ export const studio = new Hono()
365
366
  .post("/assistant/turn", async (c) => {
366
367
  const user = c.get("user");
367
368
  const body = assistantTurnSchema
368
- .extend({ lang: z.enum(["de", "en"]).default("en") })
369
+ .extend({ lang: z.enum(LANGS).default("en") })
369
370
  .parse(await c.req.json());
370
371
  if (!(await canSpend())) {
371
- throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
372
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
372
373
  }
373
374
  const project = body.projectId ? await ownedProject(user.id, body.projectId) : null;
374
375
  const writable = project ? await projectWritable(project) : false;
@@ -454,7 +455,7 @@ export const studio = new Hono()
454
455
  const left = takeSignals(runId);
455
456
  if (!left.length || abort.signal.aborted) {
456
457
  changed ||= result.changed;
457
- reply = result.reply || (changed ? (body.lang === "de" ? "Erledigt." : "Done.") : "");
458
+ reply = result.reply || (changed ? serverText(body.lang, "done") : "");
458
459
  const stored = await addMessage(chat.id, {
459
460
  role: "assistant",
460
461
  content: reply,
@@ -480,9 +481,7 @@ export const studio = new Hono()
480
481
  await send("error", {
481
482
  message: err instanceof ServiceError || err instanceof ModelUnavailableError
482
483
  ? errorText(err, body.lang)
483
- : body.lang === "de"
484
- ? "Da ist etwas schiefgelaufen. Versuch es noch einmal."
485
- : "Something went wrong. Try again.",
484
+ : serverText(body.lang, "somethingWrong"),
486
485
  });
487
486
  }
488
487
  endRun(runId);
@@ -530,7 +529,7 @@ export const studio = new Hono()
530
529
  const kind = z.enum(PROJECT_FILE_KINDS).parse(c.req.query("kind"));
531
530
  const file = (await c.req.formData()).get("file");
532
531
  if (!(file instanceof File)) {
533
- throw new ServiceError("invalid", bilingual("Wähl eine Datei.", "Choose a file."));
532
+ throw new ServiceError("invalid", note("chooseFile"));
534
533
  }
535
534
  const row = await addProjectFile(project.id, {
536
535
  kind,
@@ -563,7 +562,7 @@ export const studio = new Hono()
563
562
  await ownedProject(c.get("user").id, c.req.param("id"));
564
563
  const { url } = z.object({ url: z.string().min(1).max(2000) }).parse(await c.req.json());
565
564
  const colors = await scanColors(url).catch(() => {
566
- throw new ServiceError("invalid", bilingual("Die Seite ist nicht erreichbar.", "The page can’t be reached."));
565
+ throw new ServiceError("invalid", note("pageNotReached"));
567
566
  });
568
567
  return c.json({ colors });
569
568
  })
@@ -579,7 +578,7 @@ export const studio = new Hono()
579
578
  })
580
579
  .parse(await c.req.json());
581
580
  if (!(await canSpend())) {
582
- throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
581
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
583
582
  }
584
583
  try {
585
584
  return c.json({
@@ -597,7 +596,7 @@ export const studio = new Hono()
597
596
  await ownedProject(c.get("user").id, c.req.param("id"));
598
597
  const { url } = z.object({ url: z.string().min(1).max(2000) }).parse(await c.req.json());
599
598
  const fonts = await scanFonts(url).catch(() => {
600
- throw new ServiceError("invalid", bilingual("Die Seite ist nicht erreichbar.", "The page can’t be reached."));
599
+ throw new ServiceError("invalid", note("pageNotReached"));
601
600
  });
602
601
  return c.json({ fonts });
603
602
  })
@@ -839,7 +838,7 @@ export const studio = new Hono()
839
838
  ? { fileId: id }
840
839
  : null;
841
840
  if (!ref || key[1] !== ":") {
842
- throw new ServiceError("invalid", bilingual("Unbekannter Eintrag.", "Unknown entry."));
841
+ throw new ServiceError("invalid", note("unknownEntry"));
843
842
  }
844
843
  const { values } = z
845
844
  .object({ values: z.array(z.union([z.string().max(300), z.number(), z.boolean()])).max(50) })
@@ -953,7 +952,7 @@ export const studio = new Hono()
953
952
  .post("/projects/:id/wizards/import", async (c) => {
954
953
  const file = (await c.req.formData()).get("file");
955
954
  if (!(file instanceof File) || !file.size || file.size > PACKAGE_MAX_BYTES) {
956
- throw new ServiceError("invalid", bilingual(`Wähl eine Wizard-Datei (${PACKAGE_EXTENSION}) bis 30 MB.`, `Choose a wizard file (${PACKAGE_EXTENSION}) of 30 MB or less.`));
955
+ throw new ServiceError("invalid", note("chooseWizardFileMb", { PACKAGE_EXTENSION }));
957
956
  }
958
957
  return c.json(await importWizard(c.get("user").id, c.req.param("id"), new Uint8Array(await file.arrayBuffer()), file.name, requestLang(c)));
959
958
  })
@@ -964,7 +963,7 @@ export const studio = new Hono()
964
963
  });
965
964
  if (!found) {
966
965
  return c.json({
967
- error: inLang(c, "Der Wizard ist noch nicht veröffentlicht.", "The wizard is not published yet."),
966
+ error: say(c, "wizardNotPublishedYet"),
968
967
  }, 404);
969
968
  }
970
969
  const { name, zip } = found;
@@ -1116,7 +1115,7 @@ export const studio = new Hono()
1116
1115
  .parse(await c.req.json());
1117
1116
  await writableWizard(user.id, wizardId);
1118
1117
  if (!(await canSpend())) {
1119
- throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
1118
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
1120
1119
  }
1121
1120
  return streamSSE(c, async (stream) => {
1122
1121
  const abort = new AbortController();
@@ -1146,7 +1145,7 @@ export const studio = new Hono()
1146
1145
  data: JSON.stringify({
1147
1146
  message: err instanceof ServiceError
1148
1147
  ? errorText(err, requestLang(c))
1149
- : inLang(c, "Da ist etwas schiefgelaufen. Versuch es noch einmal.", "Something went wrong. Try again."),
1148
+ : say(c, "somethingWentWrongTry"),
1150
1149
  }),
1151
1150
  });
1152
1151
  }
@@ -1156,10 +1155,10 @@ export const studio = new Hono()
1156
1155
  .post("/transcribe", async (c) => {
1157
1156
  const file = (await c.req.formData()).get("file");
1158
1157
  if (!(file instanceof File) || !file.size || file.size > 10_000_000) {
1159
- throw new ServiceError("invalid", bilingual("Die Aufnahme darf höchstens 10 MB groß sein.", "The recording can be 10 MB at most."));
1158
+ throw new ServiceError("invalid", note("recordingMbMost"));
1160
1159
  }
1161
1160
  if (!(await canSpend())) {
1162
- throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
1161
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
1163
1162
  }
1164
1163
  try {
1165
1164
  const { text } = await transcribeAudio({
@@ -1230,7 +1229,7 @@ export const studio = new Hono()
1230
1229
  const { connect } = z.object({ connect: z.boolean() }).parse(await c.req.json());
1231
1230
  const [done] = await connectApps(layout(), [c.req.param("id")], !connect);
1232
1231
  if (!done) {
1233
- return c.json({ error: inLang(c, "Diese App gibt es nicht.", "Unknown app."), code: "not_found" }, 404);
1232
+ return c.json({ error: say(c, "unknownApp"), code: "not_found" }, 404);
1234
1233
  }
1235
1234
  // Not done is an answer too: the app's file is no plain JSON, here is what to paste.
1236
1235
  return c.json(done.outcome);
@@ -1251,7 +1250,7 @@ export const studio = new Hono()
1251
1250
  .put("/models", async (c) => {
1252
1251
  if (!mayChangeModels(c.get("user"))) {
1253
1252
  return c.json({
1254
- error: inLang(c, "Nur Admins des Teams ändern seine Modelle.", "Only team admins can change its models."),
1253
+ error: say(c, "onlyTeamAdminsChange"),
1255
1254
  code: "forbidden",
1256
1255
  }, 403);
1257
1256
  }
@@ -1262,7 +1261,7 @@ export const studio = new Hono()
1262
1261
  input.ollamaUrl ||
1263
1262
  Object.values(input.bindings ?? {}).some((ref) => ref && !cloudBinding(ref)))) {
1264
1263
  return c.json({
1265
- error: inLang(c, "Das gibt es nicht in der Cloud.", "This isn’t available in the cloud."),
1264
+ error: say(c, "notAvailableCloud"),
1266
1265
  code: "not_local",
1267
1266
  }, 400);
1268
1267
  }
@@ -1273,7 +1272,9 @@ export const studio = new Hono()
1273
1272
  Object.values(input.bindings ?? {}).some((ref) => ref && !isCreditsRef(ref));
1274
1273
  if (own && info && !feature("ownKeys", info.features)) {
1275
1274
  return c.json({
1276
- error: inLang(c, `Eigene API-Keys gibt es nicht im Paket ${info.plan?.name ?? "des Teams"}.`, `Your own API keys aren’t part of the ${info.plan?.name ?? "team’s"} plan.`),
1275
+ error: info.plan?.name
1276
+ ? say(c, "ownKeysNotInPlan", { plan: info.plan.name })
1277
+ : say(c, "ownKeysNotInTeamPlan"),
1277
1278
  code: "plan",
1278
1279
  }, 403);
1279
1280
  }
@@ -1291,7 +1292,7 @@ export const studio = new Hono()
1291
1292
  if (!value) {
1292
1293
  return c.json({
1293
1294
  ok: false,
1294
- message: inLang(c, "Kein API-Key hinterlegt.", "No API key is set."),
1295
+ message: say(c, "noApiKeySet"),
1295
1296
  });
1296
1297
  }
1297
1298
  return c.json(await checkKey(provider, value, requestLang(c)));
@@ -1301,12 +1302,12 @@ export const studio = new Hono()
1301
1302
  .post("/models/test", async (c) => {
1302
1303
  if (!mayChangeModels(c.get("user"))) {
1303
1304
  return c.json({
1304
- error: inLang(c, "Nur Admins des Teams testen seine Modelle.", "Only team admins can test its models."),
1305
+ error: say(c, "onlyTeamAdminsTest"),
1305
1306
  code: "forbidden",
1306
1307
  }, 403);
1307
1308
  }
1308
1309
  const { cls, lang } = z
1309
- .object({ cls: z.enum(MODEL_CLASSES), lang: z.enum(["de", "en"]).default("en") })
1310
+ .object({ cls: z.enum(MODEL_CLASSES), lang: z.enum(LANGS).default("en") })
1310
1311
  .parse(await c.req.json());
1311
1312
  return c.json(await testClass(cls, lang));
1312
1313
  })
@@ -1461,7 +1462,7 @@ export const studio = new Hono()
1461
1462
  .put("/team", async (c) => {
1462
1463
  if (!managed) {
1463
1464
  return c.json({
1464
- error: inLang(c, "Das gibt es nur in der Cloud.", "This is only available in the cloud."),
1465
+ error: say(c, "onlyAvailableCloud"),
1465
1466
  code: "not_managed",
1466
1467
  }, 404);
1467
1468
  }
@@ -1473,7 +1474,7 @@ export const studio = new Hono()
1473
1474
  .post("/onboarding/done", async (c) => {
1474
1475
  if (!managed) {
1475
1476
  return c.json({
1476
- error: inLang(c, "Das gibt es nur in der Cloud.", "This is only available in the cloud."),
1477
+ error: say(c, "onlyAvailableCloud"),
1477
1478
  code: "not_managed",
1478
1479
  }, 404);
1479
1480
  }
@@ -1490,12 +1491,12 @@ export const studio = new Hono()
1490
1491
  .post("/install-mail", async (c) => {
1491
1492
  if (!managed) {
1492
1493
  return c.json({
1493
- error: inLang(c, "Das gibt es nur in der Cloud.", "This is only available in the cloud."),
1494
+ error: say(c, "onlyAvailableCloud"),
1494
1495
  code: "not_managed",
1495
1496
  }, 404);
1496
1497
  }
1497
1498
  const { lang } = z
1498
- .object({ lang: z.enum(["de", "en"]).default("en") })
1499
+ .object({ lang: z.enum(LANGS).default("en") })
1499
1500
  .parse(await c.req.json().catch(() => ({})));
1500
1501
  await installMail(c.get("user").email, lang);
1501
1502
  return c.json({ ok: true });
@@ -1,5 +1,5 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { and, eq } from "drizzle-orm";
4
4
  import { db, schema } from "../db/client.js";
5
5
  import { managed, tenantInfo } from "../manage.js";
@@ -31,7 +31,7 @@ export function currentRole() {
31
31
  return actor.getStore() ?? "owner";
32
32
  }
33
33
  export const isAdminRole = (role) => role === "owner" || role === "admin";
34
- export const MEMBERS_EDIT_ONLY = bilingual("Das dürfen nur Admins des Teams. Als Mitglied bearbeitest du die Wizards, die es schon gibt.", "Only team admins can do that. As a member, you can edit the wizards that already exist.");
34
+ export const MEMBERS_EDIT_ONLY = note("onlyTeamAdmins");
35
35
  /** Refuses a member: making and deleting is for the owner and the admins of the team. */
36
36
  export async function requireAdmin() {
37
37
  if (syncing() || !managed || isAdminRole(currentRole())) {
@@ -52,8 +52,8 @@ export async function mayBuild() {
52
52
  // An older Manage-App does not say: then it builds. One that cannot be reached: nothing changes.
53
53
  return info ? info.limits.build !== false : false;
54
54
  }
55
- export const READ_ONLY_PROJECT = bilingual("Diesen Space bearbeitest du in engenty wizards auf deinem Computer, nicht hier.", "You edit this space in engenty wizards on your computer, not here.");
56
- export const READ_ONLY_TENANT = bilingual("Hier laufen die Wizards, die du in engenty wizards auf deinem Computer veröffentlichst. Gebaut und bearbeitet wird dort.", "Wizards run here once you publish them in engenty wizards on your computer. You build and edit them there.");
55
+ export const READ_ONLY_PROJECT = note("spaceEditedLocally");
56
+ export const READ_ONLY_TENANT = note("wizardsRunOncePublished");
57
57
  /** Refuses when the tenant builds nothing here. */
58
58
  export async function requireBuild() {
59
59
  if (syncing() || (await mayBuild())) {
@@ -5,6 +5,7 @@ import { listConnectors } from "../connectors/external.js";
5
5
  import { db, schema } from "../db/client.js";
6
6
  import { managed } from "../manage.js";
7
7
  import { hasTextModel } from "../models.js";
8
+ import { serverText } from "../texts.js";
8
9
  import { draftFiles, readFile } from "./files.js";
9
10
  import { ownedProject } from "./projects.js";
10
11
  import { addMessage, draftIssues, ownedWizard, wizardMessages, writableWizard } from "./wizards.js";
@@ -72,19 +73,9 @@ lang, io) {
72
73
  images,
73
74
  lang,
74
75
  });
75
- const de = lang === "de";
76
76
  const reply = result.unfinished
77
- ? `${result.reply ? `${result.reply}\n\n` : ""}${de
78
- ? "Ich bin noch nicht ganz fertig geworden. Schreib „weiter“, dann mache ich genau dort weiter."
79
- : "I have not quite finished. Write “continue” and I will carry on right there."}`
80
- : result.reply ||
81
- (result.changed
82
- ? de
83
- ? "Erledigt."
84
- : "Done."
85
- : de
86
- ? "Das habe ich nicht verstanden – magst du es anders formulieren?"
87
- : "I did not understand that – could you put it differently?");
77
+ ? `${result.reply ? `${result.reply}\n\n` : ""}${serverText(lang, "notFinished")}`
78
+ : result.reply || serverText(lang, result.changed ? "done" : "notUnderstood");
88
79
  await addMessage(w.id, { role: "assistant", content: reply, changed: result.changed });
89
80
  const after = await ownedWizard(userId, wizardId);
90
81
  return {
@@ -1,5 +1,5 @@
1
1
  import { ASSISTANT_LAYOUTS } from "@engenty-wizards/shared/assistant";
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, desc, eq, isNotNull, isNull, sql } from "drizzle-orm";
5
5
  import { nanoid } from "nanoid";
@@ -253,7 +253,7 @@ export async function readChat(user, chatId) {
253
253
  where: and(mine(user), eq(schema.assistantChat.id, chatId)),
254
254
  });
255
255
  if (!chat) {
256
- throw new ServiceError("not_found", bilingual("Diesen Chat gibt es nicht.", "This chat doesn’t exist."));
256
+ throw new ServiceError("not_found", note("chatNotExist"));
257
257
  }
258
258
  return {
259
259
  id: chat.id,
@@ -1,5 +1,5 @@
1
1
  import { GRADIENT_TYPES, gradientCss, mainLogo, } 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, Output } from "ai";
4
4
  import { and, asc, eq } from "drizzle-orm";
5
5
  import { z } from "zod";
@@ -66,14 +66,14 @@ export async function describeGradient(brand, text, current) {
66
66
  answer = result.output;
67
67
  }
68
68
  catch {
69
- throw new ServiceError("invalid", bilingual("Daraus ließ sich kein Verlauf machen. Beschreib ihn anders.", "Couldn’t make a gradient from that. Try describing it differently."));
69
+ throw new ServiceError("invalid", note("gradientFailed"));
70
70
  }
71
71
  const stops = answer.stops
72
72
  .filter((s) => HEX.test(s.color))
73
73
  .slice(0, 12)
74
74
  .map((s) => ({ color: s.color.toLowerCase(), at: Math.min(100, Math.max(0, s.at)) }));
75
75
  if (stops.length < 2) {
76
- throw new ServiceError("invalid", bilingual("Daraus ließ sich kein Verlauf machen. Beschreib ihn anders.", "Couldn’t make a gradient from that. Try describing it differently."));
76
+ throw new ServiceError("invalid", note("gradientFailed"));
77
77
  }
78
78
  return {
79
79
  name: answer.name.trim().slice(0, 80),
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { and, asc, eq, inArray, isNotNull, isNull, like } from "drizzle-orm";
4
4
  import { accountToken, linkedAccount } from "../auth/account.js";
5
5
  import { control, controlDb, db, schema } from "../db/client.js";
@@ -26,7 +26,7 @@ export async function cloudState(wizardId) {
26
26
  async function call(method, path, body, timeoutMs = 30_000) {
27
27
  const token = await accountToken();
28
28
  if (!token) {
29
- throw new ServiceError("refused", bilingual("Das Konto ist abgemeldet. Melde dich neu an.", "The account is signed out. Sign in again."), {
29
+ throw new ServiceError("refused", note("accountSignedOut"), {
30
30
  reason: "signed_out",
31
31
  });
32
32
  }
@@ -40,7 +40,7 @@ async function call(method, path, body, timeoutMs = 30_000) {
40
40
  signal: AbortSignal.timeout(timeoutMs),
41
41
  }).catch(() => null);
42
42
  if (!res) {
43
- throw new ServiceError("refused", bilingual("Die Cloud ist gerade nicht erreichbar.", "The cloud can’t be reached right now."), {
43
+ throw new ServiceError("refused", note("cloudNotReachedRight"), {
44
44
  reason: "unreachable",
45
45
  });
46
46
  }
@@ -50,7 +50,7 @@ async function call(method, path, body, timeoutMs = 30_000) {
50
50
  function refusal(answer, fallback) {
51
51
  const { error, reason, ...rest } = answer.body;
52
52
  const message = answer.status === 401
53
- ? bilingual("Das Konto ist abgemeldet. Melde dich neu an.", "The account is signed out. Sign in again.")
53
+ ? note("accountSignedOut")
54
54
  : typeof error === "string" && error
55
55
  ? error
56
56
  : fallback;
@@ -214,7 +214,7 @@ export async function syncToCloud(userId, wizardId) {
214
214
  const w = await ownedWizard(userId, wizardId);
215
215
  const version = await publishedVersion(w);
216
216
  if (!version) {
217
- throw new ServiceError("refused", bilingual("Veröffentliche den Wizard zuerst.", "Publish the wizard first."));
217
+ throw new ServiceError("refused", note("publishWizardFirst"));
218
218
  }
219
219
  const before = await cloudState(w.id);
220
220
  try {
@@ -1,4 +1,4 @@
1
- import { bilingual } from "@engenty-wizards/shared/run";
1
+ import { note } from "@engenty-wizards/shared/run";
2
2
  import { errorText, NotedError } from "../model-errors.js";
3
3
  const STATUS = {
4
4
  not_found: 404,
@@ -26,4 +26,4 @@ export class ServiceError extends NotedError {
26
26
  return { error: errorText(this, lang), code: this.code, ...this.data };
27
27
  }
28
28
  }
29
- export const notFound = () => new ServiceError("not_found", bilingual("Das gibt es nicht oder nicht mehr.", "This doesn’t exist or was deleted."));
29
+ export const notFound = () => new ServiceError("not_found", note("notFound"));