wizards 0.5.10 → 0.5.12

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 (59) hide show
  1. package/apps/runtime/dist/browser/fast/action-space.js +0 -2
  2. package/apps/runtime/dist/cli/mcp.js +2 -15
  3. package/apps/runtime/dist/cli/report.js +2 -15
  4. package/apps/runtime/dist/cli/start.js +28 -14
  5. package/apps/runtime/dist/cli/status.js +1 -1
  6. package/apps/runtime/dist/credits/credits.js +0 -3
  7. package/apps/runtime/dist/db/client.js +0 -7
  8. package/apps/runtime/dist/docs/user/build-a-wizard.md +1 -1
  9. package/apps/runtime/dist/engine/runner.js +12 -7
  10. package/apps/runtime/dist/harness/index.js +0 -4
  11. package/apps/runtime/dist/manage.js +7 -7
  12. package/apps/runtime/dist/plugins/pro.js +0 -6
  13. package/apps/runtime/dist/render/convert.js +0 -3
  14. package/apps/runtime/dist/routes/studio.js +42 -6
  15. package/apps/runtime/dist/services/api.js +8 -6
  16. package/apps/runtime/dist/store/index.js +0 -11
  17. package/apps/runtime/dist/texts.js +45 -39
  18. package/apps/web/dist/assets/{AboutDialog-DsRwnfPV.js → AboutDialog-DvkE9J4h.js} +1 -1
  19. package/apps/web/dist/assets/{ChangelogDialog-IlNqQnnS.js → ChangelogDialog-BZ8i3wt_.js} +1 -1
  20. package/apps/web/dist/assets/{ChatView-DtiFinUD.js → ChatView-DIDIR0mb.js} +1 -1
  21. package/apps/web/dist/assets/{EditorPage-DUNtNSK2.js → EditorPage-BYysznX3.js} +1 -1
  22. package/apps/web/dist/assets/{FlowPreview-CIzjgZuD.js → FlowPreview-DG23aI89.js} +1 -1
  23. package/apps/web/dist/assets/{Harness-JP1WcYyl.js → Harness-CFh04TZ3.js} +1 -1
  24. package/apps/web/dist/assets/{NewWizardChannels-DRenAibu.js → NewWizardChannels-Bmo5yRCv.js} +1 -1
  25. package/apps/web/dist/assets/{NewWizardPage-2OPw3sgZ.js → NewWizardPage-4n9-ZmHt.js} +2 -2
  26. package/apps/web/dist/assets/OnboardingPage-DpZv9qEO.js +1 -0
  27. package/apps/web/dist/assets/{ProjectPage-Bwvgr0wn.js → ProjectPage-IQb9RFlm.js} +1 -1
  28. package/apps/web/dist/assets/{PublicRunner-C2BBulT4.js → PublicRunner-Da0klKj-.js} +1 -1
  29. package/apps/web/dist/assets/{ReadOnly--S0kHt6O.js → ReadOnly-C_jD7KjW.js} +1 -1
  30. package/apps/web/dist/assets/{RunDrawer-DMsGnCo4.js → RunDrawer-FwLK67Yn.js} +1 -1
  31. package/apps/web/dist/assets/{RunnerView-DKPMblvD.js → RunnerView-BBuphCkK.js} +1 -1
  32. package/apps/web/dist/assets/{SettingsPage-C_frXK1B.js → SettingsPage-CZx52Ubk.js} +1 -1
  33. package/apps/web/dist/assets/{SetupPage-BAuR4VsB.js → SetupPage-B-o7VhbI.js} +1 -1
  34. package/apps/web/dist/assets/{SharePage-t4nlY7Y9.js → SharePage-k2OMGOWU.js} +1 -1
  35. package/apps/web/dist/assets/changelog-9oSWVuP-.js +1 -0
  36. package/apps/web/dist/assets/{esm-Bp2lKzWx.js → esm-Dzqe3TT4.js} +1 -1
  37. package/apps/web/dist/assets/{index-CC09ypna.js → index-93fPRTxr.js} +4 -4
  38. package/apps/web/dist/assets/index-DaKjB676.css +1 -0
  39. package/apps/web/dist/assets/{lock-heat0zQ3.js → lock-a8rDnyg5.js} +1 -1
  40. package/apps/web/dist/assets/{phone-DlicJ0f_.js → phone-CitA6oZn.js} +1 -1
  41. package/apps/web/dist/assets/{settings-menu-BpPXMKjQ.js → settings-menu-COgM8CgB.js} +1 -1
  42. package/apps/web/dist/assets/{ui-DWaP9zdL.js → ui-CeZNNuaK.js} +2 -2
  43. package/apps/web/dist/assets/{upload-BcENTlhM.js → upload-CcKFwoPd.js} +1 -1
  44. package/apps/web/dist/index.html +3 -3
  45. package/apps/web/dist/mcp/flow.html +2 -2
  46. package/node_modules/@engenty-wizards/plugin-sdk/dist/thread.js +34 -34
  47. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +0 -6
  48. package/node_modules/@engenty-wizards/shared/dist/definition.js +0 -10
  49. package/node_modules/@engenty-wizards/shared/dist/features.d.ts +0 -1
  50. package/node_modules/@engenty-wizards/shared/dist/features.js +0 -1
  51. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +0 -5
  52. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +0 -5
  53. package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +1 -1
  54. package/node_modules/@engenty-wizards/shared/dist/providers.js +1 -1
  55. package/node_modules/@engenty-wizards/shared/dist/run.js +1 -1
  56. package/package.json +1 -1
  57. package/apps/web/dist/assets/OnboardingPage-3r1Wl95W.js +0 -1
  58. package/apps/web/dist/assets/changelog-BFj5SeNY.js +0 -1
  59. package/apps/web/dist/assets/index-5gXip3QC.css +0 -1
@@ -7,8 +7,6 @@
7
7
  // the answer is validated against exactly the ids that were offered. Ported
8
8
  // from engenty-pro's fast loop.
9
9
  import { NEXT_ACTION, TARGET } from "./instructions.js";
10
- /** What the classifier may pick besides an element operation. */
11
- export const CONTROL_OPERATIONS = ["DONE", "BLOCKED"];
12
10
  /** The operation head's id for "interact with one element". */
13
11
  export const ACT = "ACT";
14
12
  /** The single target head's key in the questions and the answers. */
@@ -1,8 +1,7 @@
1
- import { readFileSync } from "node:fs";
2
1
  import { join } from "node:path";
3
2
  import { createInterface } from "node:readline";
4
3
  import { LOCAL_CLIENT_HEADER, LOCAL_MCP_HEADER, mintLocalTicket } from "../auth/local-ticket.js";
5
- import { runningRuntime, settings, startInBackground } from "./start.js";
4
+ import { readSecret, runningRuntime, settings, startInBackground } from "./start.js";
6
5
  import { tilde } from "./ui.js";
7
6
  /** The JSON-RPC messages of a response: a JSON body (one or a batch) or a server-sent stream. */
8
7
  export function messagesOf(contentType, body) {
@@ -26,18 +25,6 @@ export function messagesOf(contentType, body) {
26
25
  const parsed = JSON.parse(body);
27
26
  return Array.isArray(parsed) ? parsed : [parsed];
28
27
  }
29
- function readSecret(paths, dataDir) {
30
- const given = process.env.APP_SECRET?.trim() || settings(paths).fileEnv.APP_SECRET?.trim();
31
- if (given) {
32
- return given;
33
- }
34
- try {
35
- return readFileSync(join(dataDir, "secret"), "utf8").trim();
36
- }
37
- catch {
38
- return null;
39
- }
40
- }
41
28
  export async function mcp(paths) {
42
29
  const { dataDir } = settings(paths);
43
30
  let running = null;
@@ -63,7 +50,7 @@ export async function mcp(paths) {
63
50
  };
64
51
  const post = async (message) => {
65
52
  const at = await runtime();
66
- const secret = readSecret(paths, dataDir);
53
+ const secret = readSecret(paths);
67
54
  if (!secret) {
68
55
  throw new Error("No secret in the data folder yet. Start engenty wizards once first.");
69
56
  }
@@ -1,7 +1,6 @@
1
- import { readFileSync } from "node:fs";
2
1
  import { join } from "node:path";
3
2
  import { LOCAL_MCP_HEADER, mintLocalTicket } from "../auth/local-ticket.js";
4
- import { runningRuntime, settings } from "./start.js";
3
+ import { readSecret, runningRuntime, settings } from "./start.js";
5
4
  import { bad, cmd, dim, ok, tilde } from "./ui.js";
6
5
  /**
7
6
  * `wizards logs` shows the last lines the running runtime printed; `wizards report` sends its
@@ -9,25 +8,13 @@ import { bad, cmd, dim, ok, tilde } from "./ui.js";
9
8
  * (docs/plans/remote-debug.md). Both talk to the running process, signed with the data folder's
10
9
  * secret as `wizards mcp` does; neither sends anything by itself.
11
10
  */
12
- function readSecret(paths, dataDir) {
13
- const given = process.env.APP_SECRET?.trim() || settings(paths).fileEnv.APP_SECRET?.trim();
14
- if (given) {
15
- return given;
16
- }
17
- try {
18
- return readFileSync(join(dataDir, "secret"), "utf8").trim();
19
- }
20
- catch {
21
- return null;
22
- }
23
- }
24
11
  async function ask(paths, method, path) {
25
12
  const { dataDir } = settings(paths);
26
13
  const running = await runningRuntime(dataDir);
27
14
  if (!running) {
28
15
  return `engenty wizards isn’t running. To start it: ${cmd("start")}`;
29
16
  }
30
- const secret = readSecret(paths, dataDir);
17
+ const secret = readSecret(paths);
31
18
  if (!secret) {
32
19
  return `Couldn’t read the data folder’s secret (${tilde(join(dataDir, "secret"))}).`;
33
20
  }
@@ -23,6 +23,25 @@ export function settings(paths, env = process.env) {
23
23
  appUrl: value("APP_URL").replace(/\/$/, ""),
24
24
  };
25
25
  }
26
+ /**
27
+ * The secret the runtime this command starts signs with, read the way it reads it (env.ts): its
28
+ * APP_SECRET, else the one it keeps in the data folder; null before it made one. The runtime
29
+ * never sees the terminal's APP_SECRET (cli/environment.ts), only its folder's `.env.local` and
30
+ * `.env`, so the terminal's is left out here too: a ticket signed with it would be refused.
31
+ */
32
+ export function readSecret(paths, env = process.env) {
33
+ const { fileEnv, dataDir } = settings(paths, env);
34
+ const given = readEnvFile(join(paths.home, ".env.local")).APP_SECRET?.trim() || fileEnv.APP_SECRET?.trim();
35
+ if (given) {
36
+ return given;
37
+ }
38
+ try {
39
+ return readFileSync(join(dataDir, "secret"), "utf8").trim() || null;
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
26
45
  export async function healthy(port) {
27
46
  try {
28
47
  const response = await fetch(`http://127.0.0.1:${port}/api/health`, {
@@ -55,16 +74,11 @@ export async function runningRuntime(dataDir) {
55
74
  const running = readRunning(dataDir);
56
75
  return running && (await healthy(running.port)) ? running : null;
57
76
  }
58
- /** A link that lets this browser into a running runtime: a ticket signed with the data folder's secret. */
59
- export function entryUrl(running, dataDir) {
60
- try {
61
- const secret = readFileSync(join(dataDir, "secret"), "utf8").trim();
62
- return `${running.url}/api/local/enter?t=${mintLocalTicket(secret)}`;
63
- }
64
- catch {
65
- // APP_SECRET is set elsewhere: the browser gets in if it was let in before.
66
- return `${running.url}/`;
67
- }
77
+ /** A link that lets this browser into a running runtime: a ticket signed with the runtime's secret. */
78
+ export function entryUrl(running, paths, env = process.env) {
79
+ const secret = readSecret(paths, env);
80
+ // No secret to be read here: the browser gets in if it was let in before.
81
+ return secret ? `${running.url}/api/local/enter?t=${mintLocalTicket(secret)}` : `${running.url}/`;
68
82
  }
69
83
  /** Opens an address in the person's browser; false when this machine has none to open it with. */
70
84
  export function openBrowser(url) {
@@ -145,7 +159,7 @@ export async function start(paths, options) {
145
159
  mkdirSync(paths.logs, { recursive: true });
146
160
  const running = await runningRuntime(dataDir);
147
161
  if (running) {
148
- show(running.url, entryUrl(running, dataDir), options.open);
162
+ show(running.url, entryUrl(running, paths), options.open);
149
163
  console.log(dim(` It was already running. To stop it: ${cmd("stop")}`));
150
164
  return 0;
151
165
  }
@@ -155,7 +169,7 @@ export async function start(paths, options) {
155
169
  console.error(`engenty wizards didn’t start. Its log is in ${tilde(join(paths.logs, "runtime.log"))}.`);
156
170
  return 1;
157
171
  }
158
- show(started.url, entryUrl(started, dataDir), options.open);
172
+ show(started.url, entryUrl(started, paths), options.open);
159
173
  console.log(dim(` It’s running in the background. To stop it: ${cmd("stop")}`));
160
174
  console.log(dim(` Its log is in ${tilde(join(paths.logs, "runtime.log"))}.`));
161
175
  return 0;
@@ -224,7 +238,7 @@ export async function studio(paths) {
224
238
  const { dataDir } = settings(paths);
225
239
  const running = await runningRuntime(dataDir);
226
240
  if (running) {
227
- show(running.url, entryUrl(running, dataDir), true);
241
+ show(running.url, entryUrl(running, paths), true);
228
242
  return 0;
229
243
  }
230
244
  return start(paths, { open: true, background: true });
@@ -237,7 +251,7 @@ export async function open(paths, print) {
237
251
  console.error(`engenty wizards isn’t running. To start it: ${cmd("start")}`);
238
252
  return 1;
239
253
  }
240
- const entry = entryUrl(running, dataDir);
254
+ const entry = entryUrl(running, paths);
241
255
  if (print || !openBrowser(entry)) {
242
256
  console.log(entry);
243
257
  }
@@ -135,7 +135,7 @@ export async function restart(paths) {
135
135
  }
136
136
  console.log(`${ok} ${running ? "Restarted" : "Started"}: ${cyan(started.url)} ${dim(`pid ${started.pid}, in the background`)}`);
137
137
  if (!running) {
138
- console.log(` Open the studio: ${cyan(entryUrl(started, dataDir))}`);
138
+ console.log(` Open the studio: ${cyan(entryUrl(started, paths))}`);
139
139
  }
140
140
  return 0;
141
141
  }
@@ -11,9 +11,6 @@ export const MICROS_PER_CREDIT = 1_000_000;
11
11
  export function usdToMicros(usd) {
12
12
  return Math.ceil(usd * 100 * MICROS_PER_CREDIT);
13
13
  }
14
- export function microsToCredits(micros) {
15
- return Math.round(micros / MICROS_PER_CREDIT);
16
- }
17
14
  /** At least one credit left — enough to start something. */
18
15
  export async function canSpend() {
19
16
  if (!managed) {
@@ -92,13 +92,6 @@ export function tenantDb(id) {
92
92
  }
93
93
  return db;
94
94
  }
95
- /** Records where the Manage-App created the tenant's database, before it is first opened. */
96
- export async function registerTenant(id, dbUrl) {
97
- await controlDb
98
- .insert(control.tenant)
99
- .values({ id, dbUrl })
100
- .onConflictDoUpdate({ target: control.tenant.id, set: { dbUrl } });
101
- }
102
95
  const ready = new Map();
103
96
  /** Closes and forgets an open tenant database (the tenant was deleted or moved). */
104
97
  export function closeTenantDb(id) {
@@ -52,7 +52,7 @@ You can change everything afterwards in the chat.
52
52
  | The diagram, left | The steps as a flow with their branches. Click a step to open it |
53
53
  | Chat | Say what should change. The assistant edits the flow and tells you what it did |
54
54
  | Steps | One step at a time: its texts, fields, tools and model class. Move it up or down, or delete it |
55
- | Files | What the wizard needs on every run: widget code, libraries, price lists. Upload your own or let the assistant write them |
55
+ | Files | Files the wizard uses on every run, like widget code, libraries or price lists. Upload your own or let the assistant write them |
56
56
  | Memory | What the wizard knows across all its runs: the notes that AI steps with the “Pages” tool read and add to, and the tables of its shared lists |
57
57
  | Runs | The test runs and live runs of this wizard |
58
58
 
@@ -59,7 +59,11 @@ async function concurrencyLimit(tenant) {
59
59
  if (!managed) {
60
60
  return env.limits.concurrentRuns;
61
61
  }
62
- const info = await tenantInfo(tenant).catch(() => null);
62
+ // Out of reach, the Manage-App doesn't hold the line up: the runtime's own limit counts.
63
+ const info = await tenantInfo(tenant).catch((err) => {
64
+ console.warn(`[line] limit of tenant ${tenant}:`, err);
65
+ return null;
66
+ });
63
67
  return info?.limits.concurrentRuns || env.limits.concurrentRuns;
64
68
  }
65
69
  async function loadRun(runId) {
@@ -206,14 +210,14 @@ async function take(runId, tenant) {
206
210
  active.set(runId, abort);
207
211
  // The heartbeat keeps the claim and hears of a cancel the web wrote while a step works.
208
212
  const beat = setInterval(() => {
209
- void heartbeatRuns([runId]).catch(() => undefined);
213
+ void heartbeatRuns([runId]).catch((err) => console.warn(`[run ${runId}] heartbeat:`, err));
210
214
  void withTenant(tenant, () => loadRun(runId))
211
215
  .then((run) => {
212
216
  if (run && run.status !== "running") {
213
217
  abort.abort();
214
218
  }
215
219
  })
216
- .catch(() => undefined);
220
+ .catch((err) => console.warn(`[run ${runId}] cancel check:`, err));
217
221
  }, HEARTBEAT_MS);
218
222
  beat.unref();
219
223
  await withTenant(tenant, () => drive(runId, abort.signal))
@@ -223,17 +227,18 @@ async function take(runId, tenant) {
223
227
  const run = await loadRun(runId);
224
228
  const lang = run ? wizardLang(run.definition) : "en";
225
229
  await updateRun(runId, { status: "failed", error: saidText(friendly(err), lang) });
226
- }).catch(() => undefined);
230
+ }).catch((e) => console.error(`[run ${runId}] marking it failed:`, e));
227
231
  })
228
232
  .finally(async () => {
229
233
  clearInterval(beat);
230
234
  active.delete(runId);
231
- await withTenant(tenant, () => releaseResources(runId));
235
+ // What fails here leaves the rest of the cleanup to run.
236
+ await withTenant(tenant, () => releaseResources(runId)).catch((err) => console.error(`[run ${runId}] releasing its resources:`, err));
232
237
  // A run the shutdown stopped goes back in line for the next runner; the rest is done for now.
233
238
  if (!stopping) {
234
- await restRun(runId).catch(() => undefined);
239
+ await restRun(runId).catch((err) => console.error(`[run ${runId}] rest:`, err));
235
240
  }
236
- withTenant(tenant, () => signalChanged(runId));
241
+ void withTenant(tenant, () => signalChanged(runId)).catch((err) => console.error(`[run ${runId}] signal:`, err));
237
242
  // A slot came free: the next run of the line need not wait for the poll.
238
243
  void lookAtLine();
239
244
  });
@@ -65,10 +65,6 @@ export async function detectHarness(id, force = false) {
65
65
  export function detectHarnesses(force = false) {
66
66
  return Promise.all(HARNESSES.map((h) => detectHarness(h.id, force)));
67
67
  }
68
- /** The clients installed on this machine. */
69
- export async function installedHarnesses() {
70
- return (await detectHarnesses()).filter((h) => h.version !== null);
71
- }
72
68
  /**
73
69
  * What the inline terminal runs to install a client: the command the terminal setup runs, shown
74
70
  * first. A client from npm goes into the install's own prefix with the npm of its own Node.
@@ -153,6 +153,13 @@ export async function renameTenant(id, name) {
153
153
  tenants.set(id, { at: Date.now(), value });
154
154
  return value;
155
155
  }
156
+ export function saveTeamProfile(tenantId, userId, lang, profile) {
157
+ return call("PUT", `/v1/tenants/${encodeURIComponent(tenantId)}/profile`, {
158
+ ...profile,
159
+ userId,
160
+ lang,
161
+ });
162
+ }
156
163
  /** The Manage-App mails the install command and the first steps to one of the team's people. */
157
164
  export function installMail(email, lang) {
158
165
  return call("POST", "/v1/install-mail", { email, lang });
@@ -184,9 +191,6 @@ export async function publicPlans(base) {
184
191
  }
185
192
  return (await res.json());
186
193
  }
187
- export function bookUsage(input) {
188
- return call("POST", "/v1/usage", input);
189
- }
190
194
  const channels = new Map();
191
195
  /** The tenant's numbers, cached for a minute. */
192
196
  export async function tenantChannels(id) {
@@ -198,10 +202,6 @@ export async function tenantChannels(id) {
198
202
  channels.set(id, { at: Date.now(), value });
199
203
  return value;
200
204
  }
201
- /** Forgets what the Manage-App said of the tenant's numbers: the next `tenantChannels` asks. */
202
- export function forgetChannels(id) {
203
- channels.delete(id);
204
- }
205
205
  /** A person of the tenant asks for its number; what the Manage-App answers with is the request. */
206
206
  export async function requestChannelNumber(id, input) {
207
207
  channels.delete(id);
@@ -308,9 +308,3 @@ export async function proState() {
308
308
  modules,
309
309
  };
310
310
  }
311
- /** For tests: forget what the account said. */
312
- export function resetProForTests() {
313
- listing = null;
314
- lastError = null;
315
- setEntitlement(null);
316
- }
@@ -146,9 +146,6 @@ export async function tablesToXlsx(tables) {
146
146
  }
147
147
  return new Uint8Array(await wb.xlsx.writeBuffer());
148
148
  }
149
- export function tableToXlsx(rows) {
150
- return tablesToXlsx([{ name: "Data", rows }]);
151
- }
152
149
  export function jsonToMarkdown(value) {
153
150
  const rows = firstTable(value);
154
151
  if (rows?.length) {
@@ -31,7 +31,7 @@ import { appleStatus } from "../harness/apple.js";
31
31
  import { freshAuth, loginEnv } from "../harness/env.js";
32
32
  import { detectHarness, detectHarnesses, HARNESS_IDS, harness, installSpec, } from "../harness/index.js";
33
33
  import { closeTerminal, openTerminal, resizeTerminal, subscribeTerminal, writeTerminal, } from "../harness/terminal.js";
34
- import { installMail, managed, renameTenant, tenantInfo, tenantMembers, userInfo, } from "../manage.js";
34
+ import { installMail, managed, renameTenant, saveTeamProfile, tenantInfo, tenantMembers, userInfo, } from "../manage.js";
35
35
  import { seenApps } from "../mcp/seen.js";
36
36
  import { generateImageMedia, generateSpeechMedia, generateVideoMedia } from "../media/generate.js";
37
37
  import { transcribeAudio } from "../media/transcribe.js";
@@ -1266,11 +1266,21 @@ export const studio = new Hono()
1266
1266
  }, 400);
1267
1267
  }
1268
1268
  // Own keys and bindings to a provider's model are a feature of the team's plan.
1269
- if (managed) {
1270
- const info = await tenantInfo(c.get("user").tenantId).catch(() => null);
1271
- const own = Object.values(input.keys ?? {}).some((key) => key?.trim()) ||
1272
- Object.values(input.bindings ?? {}).some((ref) => ref && !isCreditsRef(ref));
1273
- if (own && info && !feature("ownKeys", info.features)) {
1269
+ // When the Manage-App can't say what the plan holds, own keys wait: nothing is taken unchecked.
1270
+ const own = Object.values(input.keys ?? {}).some((key) => key?.trim()) ||
1271
+ Object.values(input.bindings ?? {}).some((ref) => ref && !isCreditsRef(ref));
1272
+ if (managed && own) {
1273
+ const info = await tenantInfo(c.get("user").tenantId).catch((err) => {
1274
+ console.error("[models] plan of the tenant:", err);
1275
+ return null;
1276
+ });
1277
+ if (!info) {
1278
+ return c.json({
1279
+ error: say(c, "planUnavailable"),
1280
+ code: "manage_unavailable",
1281
+ }, 503);
1282
+ }
1283
+ if (!feature("ownKeys", info.features)) {
1274
1284
  return c.json({
1275
1285
  error: info.plan?.name
1276
1286
  ? say(c, "ownKeysNotInPlan", { plan: info.plan.name })
@@ -1470,6 +1480,32 @@ export const studio = new Hono()
1470
1480
  const { name } = z.object({ name: z.string().trim().min(1).max(80) }).parse(await c.req.json());
1471
1481
  const info = await renameTenant(c.get("user").tenantId, name);
1472
1482
  return c.json({ name: info.name });
1483
+ })
1484
+ // What the team uses engenty for, and the questions that fit: kept at the Manage-App.
1485
+ .put("/team/profile", async (c) => {
1486
+ if (!managed) {
1487
+ return c.json({
1488
+ error: say(c, "onlyAvailableCloud"),
1489
+ code: "not_managed",
1490
+ }, 404);
1491
+ }
1492
+ await requireAdmin();
1493
+ const field = (max) => z.string().trim().max(max).nullable().optional();
1494
+ const profile = z
1495
+ .object({
1496
+ kind: z.enum(["personal", "club", "small", "corp"]),
1497
+ size: field(40),
1498
+ sector: field(120),
1499
+ role: field(120),
1500
+ need: field(2000),
1501
+ legalName: field(200),
1502
+ phone: field(40),
1503
+ website: field(400),
1504
+ })
1505
+ .parse(await c.req.json());
1506
+ const user = c.get("user");
1507
+ await saveTeamProfile(user.tenantId, user.id, requestLang(c) === "de" ? "de" : "en", profile);
1508
+ return c.json({ ok: true });
1473
1509
  })
1474
1510
  .post("/onboarding/done", async (c) => {
1475
1511
  if (!managed) {
@@ -1,4 +1,4 @@
1
- import { wizardLang } from "@engenty-wizards/shared/definition";
1
+ import { wizardLang, } from "@engenty-wizards/shared/definition";
2
2
  import { standaloneEntry } from "@engenty-wizards/shared/fit";
3
3
  import { pluginsOf } from "@engenty-wizards/shared/marketplace";
4
4
  import { noteText } from "@engenty-wizards/shared/run";
@@ -9,6 +9,7 @@ import { env } from "../env.js";
9
9
  import { wizardUnavailable } from "../limits.js";
10
10
  import { codeOf } from "../tenants/control.js";
11
11
  import { currentTenant } from "../tenants/tenant.js";
12
+ import { serverText } from "../texts.js";
12
13
  import { mayBuild, projectWritable, READ_ONLY_TENANT } from "./access.js";
13
14
  import { mainLogoId } from "./brand.js";
14
15
  import { notFound, ServiceError } from "./errors.js";
@@ -111,20 +112,20 @@ export async function apiWizards(userId, spaceId) {
111
112
  };
112
113
  }));
113
114
  }
114
- /** A value as one line of text: what the person typed or picked. */
115
- function answerText(field, value) {
115
+ /** A value as one line of text, in the wizard's language: what the person typed or picked. */
116
+ function answerText(field, value, lang) {
116
117
  if (value === undefined || value === null || value === "") {
117
118
  return "";
118
119
  }
119
120
  if (typeof value === "boolean") {
120
- return value ? "Ja" : "Nein";
121
+ return serverText(lang, value ? "yes" : "no");
121
122
  }
122
123
  if (typeof value === "string" || typeof value === "number") {
123
124
  return String(value);
124
125
  }
125
126
  if (Array.isArray(value)) {
126
127
  if (["file", "image", "audio", "signature"].includes(field.kind)) {
127
- return value.length === 1 ? "1 Datei" : `${value.length} Dateien`;
128
+ return serverText(lang, "fileCount", { count: value.length });
128
129
  }
129
130
  return value
130
131
  .map((v) => (typeof v === "object" && v ? Object.values(v).join(" ") : String(v)))
@@ -140,12 +141,13 @@ function answerText(field, value) {
140
141
  /** What the person answered on the run's pages, by the fields' labels, in the order asked. */
141
142
  function answersOf(run) {
142
143
  const answers = [];
144
+ const lang = wizardLang(run.definition);
143
145
  for (const step of run.definition.steps) {
144
146
  if (step.type !== "page") {
145
147
  continue;
146
148
  }
147
149
  for (const field of step.fields) {
148
- const value = answerText(field, run.state.values[field.id]);
150
+ const value = answerText(field, run.state.values[field.id], lang);
149
151
  if (value) {
150
152
  answers.push({ field: field.id, label: field.label, kind: field.kind, value });
151
153
  }
@@ -238,17 +238,6 @@ export async function writeStoreFile(scope, rawPath, content, meta = {}) {
238
238
  updatedAt: updatedAt.toISOString(),
239
239
  };
240
240
  }
241
- export async function deleteStoreFile(scope, rawPath) {
242
- const path = cleanPath(rawPath);
243
- if (!path) {
244
- return false;
245
- }
246
- const gone = await db
247
- .delete(schema.storeFile)
248
- .where(and(ownFiles(scope), eq(schema.storeFile.path, path)))
249
- .returning({ path: schema.storeFile.path });
250
- return gone.length > 0;
251
- }
252
241
  // --- secrets -----------------------------------------------------------------
253
242
  const ownSecrets = (scope) => and(eq(schema.storeSecret.wizardId, scope.wizardId), eq(schema.storeSecret.holder, scope.holder));
254
243
  export async function getSecret(scope, slot) {
@@ -12,38 +12,40 @@ const EN = {
12
12
  imported: "Imported: {steps}, {files}.",
13
13
  stepCount: { one: "{count} step", other: "{count} steps" },
14
14
  fileCount: { one: "{count} file", other: "{count} files" },
15
+ yes: "Yes",
16
+ no: "No",
15
17
  spaceStillNeeds: "This space still needs: {needs}. Add it under Settings → Connectors.",
16
18
  notFinished: "I have not quite finished. Write “continue” and I will carry on right there.",
17
19
  done: "Done.",
18
20
  notUnderstood: "I did not understand that – could you put it differently?",
19
- siteTakingOver: "Taking over what the website says …",
20
- siteWritingPages: "Writing the pages into Knowledge …",
21
- siteReading: "Reading the details …",
21
+ siteTakingOver: "Taking over what the website says…",
22
+ siteWritingPages: "Writing the pages into Knowledge…",
23
+ siteReading: "Reading the details…",
22
24
  siteFailed: "The website could not be taken over.",
23
25
  faqQuestion: "Question",
24
26
  faqAnswer: "Answer",
25
27
  tableTitle: "Table",
26
28
  newWizard: "New wizard",
27
- newWizardStart: "Let's start",
29
+ newWizardStart: "Let’s start",
28
30
  newWizardAsk: "What is it about?",
29
31
  newWizardDone: "Done",
30
32
  templateSetUp: "I have set up the template “{title}” for you. Test it at the top right – or tell me what should be different.",
31
- imapHint: "Gmail, iCloud, GMX and web.de need an app password (in your account's security settings), not your normal password.",
32
- doingSaveLogo: "Fetching the logo …",
33
- doingSaveFile: "Fetching a file …",
34
- doingSetLogoSections: "Grouping the logos …",
35
- doingSetAssetSections: "Grouping the assets …",
36
- doingSetTypography: "Entering the fonts …",
37
- doingSetGradients: "Entering the gradients …",
38
- doingSetRules: "Entering rules …",
39
- doingSetProfile: "Writing title and description …",
40
- doingSetFacts: "Entering facts …",
41
- doingSetColors: "Entering the colours …",
42
- doingLookAtFile: "Looking at a file …",
43
- doingDescribeFile: "Describing a file …",
44
- doingWebSearch: "Searching the web …",
45
- doingReadSite: "Reading {host} …",
46
- doingReadWebsite: "Reading the website …",
33
+ imapHint: "Gmail, iCloud, GMX and web.de need an app password (in your account’s security settings), not your normal password.",
34
+ doingSaveLogo: "Fetching the logo…",
35
+ doingSaveFile: "Fetching a file…",
36
+ doingSetLogoSections: "Grouping the logos…",
37
+ doingSetAssetSections: "Grouping the assets…",
38
+ doingSetTypography: "Entering the fonts…",
39
+ doingSetGradients: "Entering the gradients…",
40
+ doingSetRules: "Entering rules…",
41
+ doingSetProfile: "Writing title and description…",
42
+ doingSetFacts: "Entering facts…",
43
+ doingSetColors: "Entering the colours…",
44
+ doingLookAtFile: "Looking at a file…",
45
+ doingDescribeFile: "Describing a file…",
46
+ doingWebSearch: "Searching the web…",
47
+ doingReadSite: "Reading {host}…",
48
+ doingReadWebsite: "Reading the website…",
47
49
  doingGuide: "Reading the building guide",
48
50
  doingTemplate: "Looking at a template",
49
51
  doingReadWizard: "Reading the wizard",
@@ -53,24 +55,24 @@ const EN = {
53
55
  doingTestWizard: "Testing the wizard",
54
56
  doingFindService: "Looking for a service",
55
57
  doingWork: "Working",
56
- doingFlow: "Building the flow …",
57
- doingWriteFile: "Writing {path} …",
58
- doingWriteAFile: "Writing a file …",
59
- doingDownload: "Downloading data …",
60
- doingReadWeb: "Reading the web …",
61
- doingTestWidget: "Testing the widget …",
62
- doingPrepare: "Preparing {output} …",
63
- doingCalculate: "Calculating …",
64
- doingLookFiles: "Looking at the files …",
65
- doingTidyFiles: "Tidying up files …",
66
- doingReadGuide: "Reading the guide …",
67
- doingAuthoringGuide: "Reading the authoring guide …",
68
- doingGallery: "Searching the gallery …",
69
- doingPlans: "Looking up the plans …",
70
- doingAccount: "Looking up the account …",
71
- doingUsage: "Looking up the usage …",
72
- doingModels: "Looking up the models …",
73
- doingEarlierChats: "Searching earlier chats …",
58
+ doingFlow: "Building the flow…",
59
+ doingWriteFile: "Writing {path}…",
60
+ doingWriteAFile: "Writing a file…",
61
+ doingDownload: "Downloading data…",
62
+ doingReadWeb: "Reading the web…",
63
+ doingTestWidget: "Testing the widget…",
64
+ doingPrepare: "Preparing {output}…",
65
+ doingCalculate: "Calculating…",
66
+ doingLookFiles: "Looking at the files…",
67
+ doingTidyFiles: "Tidying up files…",
68
+ doingReadGuide: "Reading the guide…",
69
+ doingAuthoringGuide: "Reading the authoring guide…",
70
+ doingGallery: "Searching the gallery…",
71
+ doingPlans: "Looking up the plans…",
72
+ doingAccount: "Looking up the account…",
73
+ doingUsage: "Looking up the usage…",
74
+ doingModels: "Looking up the models…",
75
+ doingEarlierChats: "Searching earlier chats…",
74
76
  somethingWrong: "Something went wrong. Try again.",
75
77
  firstSpaceCloud: "My wizards",
76
78
  firstSpaceLocal: "My computer",
@@ -95,6 +97,7 @@ const EN = {
95
97
  notFound: "This doesn’t exist or was deleted.",
96
98
  ownKeysNotInPlan: "Your own API keys aren’t part of the {plan} plan.",
97
99
  ownKeysNotInTeamPlan: "Your own API keys aren’t part of the team’s plan.",
100
+ planUnavailable: "The team’s plan can’t be checked right now. Try again later.",
98
101
  };
99
102
  const DE = {
100
103
  keyUnknown: "Der Anbieter kennt diesen API-Key nicht.",
@@ -104,6 +107,8 @@ const DE = {
104
107
  imported: "Importiert: {steps}, {files}.",
105
108
  stepCount: { one: "{count} Schritt", other: "{count} Schritte" },
106
109
  fileCount: { one: "{count} Datei", other: "{count} Dateien" },
110
+ yes: "Ja",
111
+ no: "Nein",
107
112
  spaceStillNeeds: "Diesem Space fehlt noch: {needs}. Füge es unter Einstellungen → Connectors hinzu.",
108
113
  notFinished: "Ich bin noch nicht ganz fertig geworden. Schreib „weiter“, dann mache ich genau dort weiter.",
109
114
  done: "Erledigt.",
@@ -116,7 +121,7 @@ const DE = {
116
121
  faqAnswer: "Antwort",
117
122
  tableTitle: "Tabelle",
118
123
  newWizard: "Neuer Wizard",
119
- newWizardStart: "Los geht's",
124
+ newWizardStart: "Los geht’s",
120
125
  newWizardAsk: "Worum geht es?",
121
126
  newWizardDone: "Fertig",
122
127
  templateSetUp: "Ich habe die Vorlage „{title}“ für dich angelegt. Teste sie rechts oben – oder sag mir, was anders sein soll.",
@@ -187,6 +192,7 @@ const DE = {
187
192
  notFound: "Das gibt es nicht oder nicht mehr.",
188
193
  ownKeysNotInPlan: "Eigene API-Keys gibt es nicht im Paket {plan}.",
189
194
  ownKeysNotInTeamPlan: "Eigene API-Keys gibt es nicht im Paket des Teams.",
195
+ planUnavailable: "Das Paket des Teams lässt sich gerade nicht prüfen. Versuch es später noch einmal.",
190
196
  };
191
197
  export const SERVER_TEXTS = { en: EN, de: DE };
192
198
  /** A line of the server's table in a language; English where the language lacks it. */