wizards 0.5.6 → 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 (113) 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 +9 -7
  5. package/apps/runtime/dist/assistant/knows.js +10 -9
  6. package/apps/runtime/dist/auth/index.js +7 -0
  7. package/apps/runtime/dist/connectors/external.js +3 -3
  8. package/apps/runtime/dist/connectors/index.js +23 -23
  9. package/apps/runtime/dist/connectors/via-account.js +6 -6
  10. package/apps/runtime/dist/documents/parse.js +3 -3
  11. package/apps/runtime/dist/engine/input.js +11 -11
  12. package/apps/runtime/dist/engine/requirements.js +7 -8
  13. package/apps/runtime/dist/engine/runner.js +10 -14
  14. package/apps/runtime/dist/engine/steps.js +6 -6
  15. package/apps/runtime/dist/engine/surface.js +2 -2
  16. package/apps/runtime/dist/film/step.js +5 -5
  17. package/apps/runtime/dist/harness/apple.js +2 -1
  18. package/apps/runtime/dist/harness/claude.js +2 -2
  19. package/apps/runtime/dist/harness/codex.js +2 -2
  20. package/apps/runtime/dist/harness/model.js +4 -5
  21. package/apps/runtime/dist/harness/terminal.js +4 -4
  22. package/apps/runtime/dist/media/conversation.js +18 -35
  23. package/apps/runtime/dist/media/transcribe.js +2 -1
  24. package/apps/runtime/dist/model-errors.js +1 -1
  25. package/apps/runtime/dist/models.js +32 -40
  26. package/apps/runtime/dist/plugins/runs.js +3 -5
  27. package/apps/runtime/dist/routes/api.js +9 -9
  28. package/apps/runtime/dist/routes/lang.js +14 -9
  29. package/apps/runtime/dist/routes/marketplace.js +6 -1
  30. package/apps/runtime/dist/routes/runs.js +5 -5
  31. package/apps/runtime/dist/routes/studio.js +38 -37
  32. package/apps/runtime/dist/services/access.js +4 -4
  33. package/apps/runtime/dist/services/architect.js +3 -12
  34. package/apps/runtime/dist/services/assistant-chats.js +2 -2
  35. package/apps/runtime/dist/services/brand.js +3 -3
  36. package/apps/runtime/dist/services/cloud.js +5 -5
  37. package/apps/runtime/dist/services/errors.js +2 -2
  38. package/apps/runtime/dist/services/knowledge-model.js +3 -3
  39. package/apps/runtime/dist/services/marketplace.js +18 -8
  40. package/apps/runtime/dist/services/package-file.js +9 -11
  41. package/apps/runtime/dist/services/package.js +8 -9
  42. package/apps/runtime/dist/services/profile.js +2 -1
  43. package/apps/runtime/dist/services/project-files.js +13 -13
  44. package/apps/runtime/dist/services/projects.js +7 -11
  45. package/apps/runtime/dist/services/runners.js +4 -4
  46. package/apps/runtime/dist/services/runs.js +7 -10
  47. package/apps/runtime/dist/services/shares.js +2 -2
  48. package/apps/runtime/dist/services/site.js +8 -11
  49. package/apps/runtime/dist/services/space-categories.js +7 -7
  50. package/apps/runtime/dist/services/space-data.js +20 -20
  51. package/apps/runtime/dist/services/spaces.js +7 -9
  52. package/apps/runtime/dist/services/wizards.js +11 -13
  53. package/apps/runtime/dist/store/index.js +2 -2
  54. package/apps/runtime/dist/texts.js +195 -0
  55. package/apps/runtime/dist/tools/plugin.js +7 -3
  56. package/apps/runtime/dist/widgets/step.js +2 -2
  57. package/apps/web/dist/assets/AboutDialog-BcTtA42P.js +1 -0
  58. package/apps/web/dist/assets/{ChangelogDialog-sdSC9eZj.js → ChangelogDialog-DMOWV_Iu.js} +1 -1
  59. package/apps/web/dist/assets/ChatView-CrnrYHjp.js +10 -0
  60. package/apps/web/dist/assets/EditorPage-DpEw3eOu.js +3 -0
  61. package/apps/web/dist/assets/{FlowPreview-B4-ipMdE.js → FlowPreview-CzmH4JUQ.js} +1 -1
  62. package/apps/web/dist/assets/{Harness-DwI4wFqj.js → Harness-DipH0Vec.js} +2 -2
  63. package/apps/web/dist/assets/{NewWizardChannels-DkI648G-.js → NewWizardChannels-DShu0PP6.js} +1 -1
  64. package/apps/web/dist/assets/NewWizardPage-HSa6PNJs.js +7 -0
  65. package/apps/web/dist/assets/OnboardingPage-CPADMs4c.js +1 -0
  66. package/apps/web/dist/assets/ProjectPage-DMPcrhgt.js +1 -0
  67. package/apps/web/dist/assets/PublicRunner-DZ5sWmtg.js +1 -0
  68. package/apps/web/dist/assets/{ReadOnly-Jvzgyelg.js → ReadOnly-B9xaq_M3.js} +1 -1
  69. package/apps/web/dist/assets/RunDrawer-9nMGhbSm.js +4 -0
  70. package/apps/web/dist/assets/{RunnerView-DN4sMYS6.js → RunnerView-NSSP3tc6.js} +1 -1
  71. package/apps/web/dist/assets/SettingsPage-Dy9MbwQh.js +1 -0
  72. package/apps/web/dist/assets/SetupPage-kemgI7v5.js +1 -0
  73. package/apps/web/dist/assets/{SharePage-CmowpiJG.js → SharePage-CQfIdcwy.js} +1 -1
  74. package/apps/web/dist/assets/{changelog-Cz8fZ-jG.js → changelog-BW40vzI6.js} +1 -1
  75. package/apps/web/dist/assets/{esm-J06O1nN_.js → esm-BF1nonbH.js} +1 -1
  76. package/apps/web/dist/assets/index-CnJ4Yzkf.css +1 -0
  77. package/apps/web/dist/assets/index-DQ9qAB4v.js +14 -0
  78. package/apps/web/dist/assets/{lock-ChDlFFHO.js → lock-DVvM6IsA.js} +1 -1
  79. package/apps/web/dist/assets/{phone-Cr7jz_kf.js → phone-3fG1okFs.js} +1 -1
  80. package/apps/web/dist/assets/{settings-menu-BAwHMXCR.js → settings-menu-D9fbi1Z0.js} +1 -1
  81. package/apps/web/dist/assets/ui-EuR3jezP.js +134 -0
  82. package/apps/web/dist/assets/{upload-DcAIMe8d.js → upload-BsMUhEfX.js} +1 -1
  83. package/apps/web/dist/index.html +3 -3
  84. package/apps/web/dist/mcp/flow.html +78 -78
  85. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +26 -9
  86. package/node_modules/@engenty-wizards/shared/dist/definition.js +125 -3
  87. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +1 -1
  88. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +9 -8
  89. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +6 -6
  90. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +78 -205
  91. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +167 -68
  92. package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +3 -6
  93. package/node_modules/@engenty-wizards/shared/dist/projects.js +9 -9
  94. package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +33 -7
  95. package/node_modules/@engenty-wizards/shared/dist/providers.js +88 -37
  96. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +298 -365
  97. package/node_modules/@engenty-wizards/shared/dist/run.js +582 -240
  98. package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +36 -0
  99. package/node_modules/@engenty-wizards/shared/dist/texts.js +46 -0
  100. package/package.json +1 -1
  101. package/apps/web/dist/assets/AboutDialog-Ck6_TSVJ.js +0 -1
  102. package/apps/web/dist/assets/ChatView-S1H9Pcih.js +0 -10
  103. package/apps/web/dist/assets/EditorPage-BSfJXx4W.js +0 -3
  104. package/apps/web/dist/assets/NewWizardPage-dumNiSqM.js +0 -7
  105. package/apps/web/dist/assets/OnboardingPage-BAfHLLNT.js +0 -1
  106. package/apps/web/dist/assets/ProjectPage-fneLQr6R.js +0 -1
  107. package/apps/web/dist/assets/PublicRunner-CuxDAXK7.js +0 -1
  108. package/apps/web/dist/assets/RunDrawer-C_T1I77g.js +0 -4
  109. package/apps/web/dist/assets/SettingsPage-DjFvKMRz.js +0 -1
  110. package/apps/web/dist/assets/SetupPage-CzYwVUvh.js +0 -1
  111. package/apps/web/dist/assets/index-CZFfDGHy.css +0 -1
  112. package/apps/web/dist/assets/index-DZTDv2pA.js +0 -14
  113. package/apps/web/dist/assets/ui-C5_NcAmG.js +0 -134
@@ -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"));
@@ -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";
@@ -251,12 +251,22 @@ export async function searchMarketplace(search) {
251
251
  facets: addFacets(listed.facets, found.facets),
252
252
  };
253
253
  }
254
+ /**
255
+ * The gallery for someone not signed in (the studio's start page for guests): what the
256
+ * marketplace lists, without a tenant's stars and plugins. An entry that needs a plugin says so.
257
+ */
258
+ export function searchMarketplaceAsGuest(search) {
259
+ return searchListed(search, true);
260
+ }
254
261
  /** Searches the marketplace; without an answer, what is kept here, scored the same way. */
255
- async function searchListed(search) {
262
+ async function searchListed(search, guest = false) {
256
263
  const { q = "", lang, starred: onlyStarred, limit = 60, offset = 0, ...filters } = search;
257
- const stars = await starredIds();
264
+ const stars = guest ? [] : await starredIds();
258
265
  const starred = new Set(stars);
259
- const unavailable = await missingCapabilities();
266
+ const unavailable = guest ? new Set() : await missingCapabilities();
267
+ const plugged = (entries) => guest
268
+ ? entries.map((e) => (e.plugins?.length ? { ...e, needs: e.plugins } : e))
269
+ : withPlugins(entries);
260
270
  const params = new URLSearchParams({ lang, limit: String(limit), offset: String(offset) });
261
271
  if (onlyStarred) {
262
272
  params.set("ids", stars.join(","));
@@ -278,7 +288,7 @@ async function searchListed(search) {
278
288
  const optional = await keptOptional(remote.entries.map((e) => e.id), lang, unavailable);
279
289
  return {
280
290
  ...remote,
281
- entries: await withPlugins(remote.entries.map((e) => asEntry(e, starred, unavailable, optional.get(e.id)))),
291
+ entries: await plugged(remote.entries.map((e) => asEntry(e, starred, unavailable, optional.get(e.id)))),
282
292
  offline: false,
283
293
  unavailable: [...unavailable],
284
294
  };
@@ -290,7 +300,7 @@ async function searchListed(search) {
290
300
  const optional = await keptOptional(page.entries.map((e) => e.id), lang, unavailable);
291
301
  return {
292
302
  ...page,
293
- entries: await withPlugins(page.entries.map((e) => asEntry(e, starred, unavailable, optional.get(e.id)))),
303
+ entries: await plugged(page.entries.map((e) => asEntry(e, starred, unavailable, optional.get(e.id)))),
294
304
  offline: true,
295
305
  unavailable: [...unavailable],
296
306
  };
@@ -404,13 +414,13 @@ export async function marketplaceWizard(id, lang) {
404
414
  return null;
405
415
  }
406
416
  if (!readable(found.definition)) {
407
- 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"));
408
418
  }
409
419
  // A wizard whose plugins are not here would stop at its first step that uses them.
410
420
  const have = await pluginIdsOf(currentTenant());
411
421
  const needs = pluginsOf(wizardSchema.parse(found.definition)).filter((p) => !have.has(p));
412
422
  if (needs.length) {
413
- 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 }));
414
424
  }
415
425
  return {
416
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");