wizards 0.5.0 → 0.5.1

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 (158) hide show
  1. package/README.md +45 -42
  2. package/apps/runtime/dist/agents/studio-assistant.js +3 -3
  3. package/apps/runtime/dist/app.js +7 -4
  4. package/apps/runtime/dist/auth/index.js +2 -2
  5. package/apps/runtime/dist/authoring/guide.js +1 -1
  6. package/apps/runtime/dist/cli/account.js +31 -28
  7. package/apps/runtime/dist/cli/autostart.js +5 -3
  8. package/apps/runtime/dist/cli/clients.js +1 -1
  9. package/apps/runtime/dist/cli/connect.js +24 -24
  10. package/apps/runtime/dist/cli/features.js +6 -6
  11. package/apps/runtime/dist/cli/main.js +51 -39
  12. package/apps/runtime/dist/cli/mcp.js +5 -4
  13. package/apps/runtime/dist/cli/menu.js +32 -30
  14. package/apps/runtime/dist/cli/report.js +7 -9
  15. package/apps/runtime/dist/cli/setup.js +42 -38
  16. package/apps/runtime/dist/cli/start.js +16 -15
  17. package/apps/runtime/dist/cli/status.js +20 -20
  18. package/apps/runtime/dist/cli/ui.js +9 -2
  19. package/apps/runtime/dist/cli/update.js +6 -6
  20. package/apps/runtime/dist/cli/validate.js +1 -1
  21. package/apps/runtime/dist/connectors/external.js +3 -2
  22. package/apps/runtime/dist/connectors/index.js +5 -5
  23. package/apps/runtime/dist/connectors/plugin-connections.js +2 -2
  24. package/apps/runtime/dist/connectors/via-account.js +5 -5
  25. package/apps/runtime/dist/db/control-schema.js +14 -0
  26. package/apps/runtime/dist/db/migrations/control/0009_plugin_claim.sql +9 -0
  27. package/apps/runtime/dist/db/migrations/control/meta/0009_snapshot.json +561 -0
  28. package/apps/runtime/dist/db/migrations/control/meta/_journal.json +7 -0
  29. package/apps/runtime/dist/docs/dev/index.md +16 -14
  30. package/apps/runtime/dist/docs/dev/plugins/connections.md +27 -24
  31. package/apps/runtime/dist/docs/dev/plugins/events.md +19 -17
  32. package/apps/runtime/dist/docs/dev/plugins/examples.md +13 -12
  33. package/apps/runtime/dist/docs/dev/plugins/index.md +29 -24
  34. package/apps/runtime/dist/docs/dev/plugins/layout.md +20 -19
  35. package/apps/runtime/dist/docs/dev/plugins/quick-start.md +16 -15
  36. package/apps/runtime/dist/docs/dev/plugins/reference.md +52 -40
  37. package/apps/runtime/dist/docs/dev/plugins/reloading.md +24 -23
  38. package/apps/runtime/dist/docs/dev/plugins/routes.md +26 -22
  39. package/apps/runtime/dist/docs/dev/plugins/runners.md +49 -44
  40. package/apps/runtime/dist/docs/dev/plugins/server.md +95 -43
  41. package/apps/runtime/dist/docs/dev/plugins/shipping.md +19 -16
  42. package/apps/runtime/dist/docs/dev/plugins/space.md +75 -69
  43. package/apps/runtime/dist/docs/dev/plugins/starters.md +22 -17
  44. package/apps/runtime/dist/docs/dev/plugins/studio.md +80 -37
  45. package/apps/runtime/dist/docs/dev/plugins/styling.md +44 -41
  46. package/apps/runtime/dist/docs/dev/plugins/tables.md +11 -11
  47. package/apps/runtime/dist/docs/dev/plugins/tools.md +26 -25
  48. package/apps/runtime/dist/docs/dev/wizard-format.md +35 -23
  49. package/apps/runtime/dist/docs/user/ai-apps.md +46 -43
  50. package/apps/runtime/dist/docs/user/assistant.md +41 -36
  51. package/apps/runtime/dist/docs/user/build-a-wizard.md +64 -39
  52. package/apps/runtime/dist/docs/user/command-line.md +47 -44
  53. package/apps/runtime/dist/docs/user/connectors.md +40 -35
  54. package/apps/runtime/dist/docs/user/create-account.md +27 -22
  55. package/apps/runtime/dist/docs/user/data.md +29 -29
  56. package/apps/runtime/dist/docs/user/first-start.md +26 -28
  57. package/apps/runtime/dist/docs/user/index.md +16 -17
  58. package/apps/runtime/dist/docs/user/install.md +28 -27
  59. package/apps/runtime/dist/docs/user/local-only.md +21 -17
  60. package/apps/runtime/dist/docs/user/models.md +92 -84
  61. package/apps/runtime/dist/docs/user/plugins.md +28 -23
  62. package/apps/runtime/dist/docs/user/run.md +51 -50
  63. package/apps/runtime/dist/docs/user/server.md +9 -9
  64. package/apps/runtime/dist/docs/user/space.md +87 -65
  65. package/apps/runtime/dist/docs/user/steps.md +73 -69
  66. package/apps/runtime/dist/docs/user/test-and-share.md +127 -105
  67. package/apps/runtime/dist/docs/user/troubleshooting.md +40 -38
  68. package/apps/runtime/dist/docs/user/wordpress.md +62 -54
  69. package/apps/runtime/dist/documents/parse.js +2 -2
  70. package/apps/runtime/dist/engenty/shims/web-ingest.js +17 -1
  71. package/apps/runtime/dist/engine/input.js +4 -4
  72. package/apps/runtime/dist/engine/push.js +2 -2
  73. package/apps/runtime/dist/engine/runner.js +8 -8
  74. package/apps/runtime/dist/engine/steps.js +2 -2
  75. package/apps/runtime/dist/engine/surface.js +1 -1
  76. package/apps/runtime/dist/film/step.js +3 -3
  77. package/apps/runtime/dist/harness/claude.js +1 -1
  78. package/apps/runtime/dist/harness/codex.js +1 -1
  79. package/apps/runtime/dist/harness/model.js +2 -2
  80. package/apps/runtime/dist/harness/terminal.js +3 -3
  81. package/apps/runtime/dist/mcp/handler.js +2 -2
  82. package/apps/runtime/dist/mcp/tools.js +9 -9
  83. package/apps/runtime/dist/models.js +15 -15
  84. package/apps/runtime/dist/plugins/claims.js +56 -0
  85. package/apps/runtime/dist/plugins/loader.js +2 -0
  86. package/apps/runtime/dist/plugins/public.js +2 -3
  87. package/apps/runtime/dist/plugins/runs.js +13 -8
  88. package/apps/runtime/dist/render/chromium.js +24 -11
  89. package/apps/runtime/dist/routes/api.js +15 -9
  90. package/apps/runtime/dist/routes/debug.js +1 -1
  91. package/apps/runtime/dist/routes/delivery.js +46 -0
  92. package/apps/runtime/dist/routes/lang.js +7 -0
  93. package/apps/runtime/dist/routes/marketplace.js +2 -1
  94. package/apps/runtime/dist/routes/plugin-public.js +2 -1
  95. package/apps/runtime/dist/routes/plugins.js +13 -14
  96. package/apps/runtime/dist/routes/runs.js +59 -49
  97. package/apps/runtime/dist/routes/studio.js +53 -23
  98. package/apps/runtime/dist/services/access.js +3 -3
  99. package/apps/runtime/dist/services/assistant-chats.js +1 -1
  100. package/apps/runtime/dist/services/brand.js +2 -2
  101. package/apps/runtime/dist/services/cloud.js +5 -4
  102. package/apps/runtime/dist/services/errors.js +2 -1
  103. package/apps/runtime/dist/services/knowledge-model.js +2 -2
  104. package/apps/runtime/dist/services/marketplace.js +2 -2
  105. package/apps/runtime/dist/services/package-file.js +12 -9
  106. package/apps/runtime/dist/services/package.js +2 -2
  107. package/apps/runtime/dist/services/project-files.js +10 -12
  108. package/apps/runtime/dist/services/projects.js +13 -8
  109. package/apps/runtime/dist/services/runners.js +4 -3
  110. package/apps/runtime/dist/services/runs.js +3 -3
  111. package/apps/runtime/dist/services/site.js +2 -2
  112. package/apps/runtime/dist/services/space-categories.js +4 -4
  113. package/apps/runtime/dist/services/space-data.js +13 -13
  114. package/apps/runtime/dist/services/space-origin.js +1 -1
  115. package/apps/runtime/dist/services/spaces.js +7 -7
  116. package/apps/runtime/dist/services/wizards.js +5 -5
  117. package/apps/runtime/dist/tools/net-guard.js +135 -24
  118. package/apps/runtime/dist/tools/plugin.js +1 -1
  119. package/apps/runtime/dist/tools/store.js +1 -1
  120. package/apps/runtime/dist/widgets/step.js +1 -1
  121. package/apps/web/dist/assets/AboutDialog-DaYwk84X.js +1 -0
  122. package/apps/web/dist/assets/{ChangelogDialog-BXpkgatF.js → ChangelogDialog-CsYbEayb.js} +1 -1
  123. package/apps/web/dist/assets/{ChatView-t7z44uFN.js → ChatView-BRD3fKZU.js} +1 -1
  124. package/apps/web/dist/assets/{EditorPage-4qxNJgKr.js → EditorPage-ocPlZD3E.js} +3 -3
  125. package/apps/web/dist/assets/{FlowPreview-WpS8DCPl.js → FlowPreview-Bm7sv746.js} +1 -1
  126. package/apps/web/dist/assets/{Harness-DL7Bse40.js → Harness-DKE-YNA5.js} +1 -1
  127. package/apps/web/dist/assets/{NewWizardChannels-Crt6PzH2.js → NewWizardChannels-CnWckqjO.js} +1 -1
  128. package/apps/web/dist/assets/{NewWizardPage-COiVmNsl.js → NewWizardPage-BXTpbHVM.js} +1 -1
  129. package/apps/web/dist/assets/{OnboardingPage-BwluEqXq.js → OnboardingPage-3Rz5uOsa.js} +1 -1
  130. package/apps/web/dist/assets/{ProjectPage-BIRU_AlN.js → ProjectPage-QdSJwm3k.js} +1 -1
  131. package/apps/web/dist/assets/{PublicRunner-BxMb4gEt.js → PublicRunner-DEpBer5g.js} +1 -1
  132. package/apps/web/dist/assets/{ReadOnly-D9r5ze5S.js → ReadOnly-rr_RJD-a.js} +1 -1
  133. package/apps/web/dist/assets/{RunDrawer-DmJl2QOJ.js → RunDrawer-BsZt0tL9.js} +1 -1
  134. package/apps/web/dist/assets/{RunnerView-CF_1rC-7.js → RunnerView-BbRukEdE.js} +1 -1
  135. package/apps/web/dist/assets/SettingsPage-qkWsQIMN.js +1 -0
  136. package/apps/web/dist/assets/{SetupPage-BGKtbS7j.js → SetupPage-PzvPPLfM.js} +1 -1
  137. package/apps/web/dist/assets/{SharePage-C4MqXKDA.js → SharePage-7vdDWG1n.js} +1 -1
  138. package/apps/web/dist/assets/changelog-BFdAYD28.js +1 -0
  139. package/apps/web/dist/assets/{esm-CsY8TkyE.js → esm-g0kaGp8t.js} +1 -1
  140. package/apps/web/dist/assets/{index-BjQvGQp_.js → index-BzPid1oQ.js} +4 -4
  141. package/apps/web/dist/assets/{lock-CI6TpWL-.js → lock-BK9RYIft.js} +1 -1
  142. package/apps/web/dist/assets/oss-credits-BJrR7BR1.js +1 -0
  143. package/apps/web/dist/assets/{phone-BrtQz-fG.js → phone-DtkY0NfU.js} +1 -1
  144. package/apps/web/dist/assets/{settings-menu-BtEKL72n.js → settings-menu-7EJQRFOi.js} +1 -1
  145. package/apps/web/dist/assets/{ui-4IaTD7LB.js → ui-B6S5hYPd.js} +3 -3
  146. package/apps/web/dist/assets/{upload-CWJAtPRo.js → upload-rYCAy7ox.js} +1 -1
  147. package/apps/web/dist/index.html +2 -2
  148. package/apps/web/dist/mcp/flow.html +3 -3
  149. package/bin/wizards.mjs +1 -1
  150. package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +21 -2
  151. package/node_modules/@engenty-wizards/shared/dist/projects.js +1 -1
  152. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +48 -48
  153. package/node_modules/@engenty-wizards/shared/dist/run.js +48 -48
  154. package/package.json +5 -4
  155. package/apps/web/dist/assets/AboutDialog-A_ILyeue.js +0 -1
  156. package/apps/web/dist/assets/SettingsPage-3xSInE-A.js +0 -1
  157. package/apps/web/dist/assets/changelog-CM_GdIVI.js +0 -1
  158. package/apps/web/dist/assets/oss-credits-BQQX0kvk.js +0 -1
@@ -5,7 +5,7 @@ import { delimiter, dirname, join } from "node:path";
5
5
  import { promisify } from "node:util";
6
6
  import { installedByScript, packageRoot } from "./home.js";
7
7
  import { findOnPath } from "./machine.js";
8
- import { bad, dim, ok, tilde } from "./ui.js";
8
+ import { bad, cmd, dim, ok, tilde } from "./ui.js";
9
9
  const run = promisify(execFile);
10
10
  /**
11
11
  * The wizards in the person's own AI apps: `wizards connect` writes this install's MCP
@@ -214,7 +214,7 @@ function jsonApp(def) {
214
214
  const running = () => def.ownsFileWhileRunning?.()
215
215
  ? {
216
216
  ok: false,
217
- message: `${def.name} is running and would overwrite its config. Quit it (Cmd+Q), then run this again.`,
217
+ message: `${def.name} is running and would overwrite its settings. Quit it completely, then try again.`,
218
218
  }
219
219
  : null;
220
220
  const snippet = (server) => JSON.stringify(def.at.reduceRight((inner, key) => ({ [key]: inner }), {
@@ -236,17 +236,17 @@ function jsonApp(def) {
236
236
  if (next === null) {
237
237
  return {
238
238
  ok: false,
239
- message: `${def.file} is not plain JSON (comments?); add this to it:`,
239
+ message: `${tilde(def.file)} isn’t plain JSON (comments?). Add this to it yourself:`,
240
240
  snippet: snippet(server),
241
241
  };
242
242
  }
243
243
  save(def.file, next);
244
- return { ok: true, message: `added to ${def.file}` };
244
+ return { ok: true, message: `added to ${tilde(def.file)}` };
245
245
  },
246
246
  async disconnect() {
247
247
  const text = read(def.file);
248
248
  if (!text || !jsonHasServer(text, def.at)) {
249
- return { ok: true, message: "was not connected" };
249
+ return { ok: true, message: "wasn’t connected" };
250
250
  }
251
251
  const busy = running();
252
252
  if (busy) {
@@ -256,11 +256,11 @@ function jsonApp(def) {
256
256
  if (next === null) {
257
257
  return {
258
258
  ok: false,
259
- message: `${def.file} is not plain JSON; remove "${SERVER_NAME}" there.`,
259
+ message: `${tilde(def.file)} isn’t plain JSON. Remove “${SERVER_NAME}” from it yourself.`,
260
260
  };
261
261
  }
262
262
  save(def.file, next);
263
- return { ok: true, message: `removed from ${def.file}` };
263
+ return { ok: true, message: `removed from ${tilde(def.file)}` };
264
264
  },
265
265
  };
266
266
  }
@@ -285,7 +285,7 @@ function claudeCode(paths) {
285
285
  async connect(server) {
286
286
  const claude = appCommand(paths, "claude");
287
287
  if (!claude) {
288
- return { ok: false, message: "the claude command was not found" };
288
+ return { ok: false, message: "The claude command wasn’t found." };
289
289
  }
290
290
  // Claude Code keeps rewriting its own file: its command changes it, not we.
291
291
  await run(claude, ["mcp", "remove", "--scope", "user", SERVER_NAME]).catch(() => undefined);
@@ -301,7 +301,7 @@ function claudeCode(paths) {
301
301
  server.command,
302
302
  ...server.args,
303
303
  ]);
304
- return { ok: true, message: "added with `claude mcp add --scope user`" };
304
+ return { ok: true, message: "added with claude mcp add --scope user" };
305
305
  }
306
306
  catch (error) {
307
307
  return { ok: false, message: error.message.split("\n")[0] };
@@ -310,10 +310,10 @@ function claudeCode(paths) {
310
310
  async disconnect() {
311
311
  const claude = appCommand(paths, "claude");
312
312
  if (!claude) {
313
- return { ok: true, message: "was not connected" };
313
+ return { ok: true, message: "wasn’t connected" };
314
314
  }
315
315
  await run(claude, ["mcp", "remove", "--scope", "user", SERVER_NAME]).catch(() => undefined);
316
- return { ok: true, message: "removed with `claude mcp remove`" };
316
+ return { ok: true, message: "removed with claude mcp remove" };
317
317
  },
318
318
  };
319
319
  }
@@ -329,15 +329,15 @@ function codex() {
329
329
  connected: async () => tomlHasServer(read(file) ?? ""),
330
330
  async connect(server) {
331
331
  save(file, withTomlServer(read(file), server));
332
- return { ok: true, message: `added to ${file}` };
332
+ return { ok: true, message: `added to ${tilde(file)}` };
333
333
  },
334
334
  async disconnect() {
335
335
  const text = read(file);
336
336
  if (!text || !tomlHasServer(text)) {
337
- return { ok: true, message: "was not connected" };
337
+ return { ok: true, message: "wasn’t connected" };
338
338
  }
339
339
  save(file, withoutTomlServer(text));
340
- return { ok: true, message: `removed from ${file}` };
340
+ return { ok: true, message: `removed from ${tilde(file)}` };
341
341
  },
342
342
  };
343
343
  }
@@ -355,15 +355,15 @@ function goose() {
355
355
  connected: async () => yamlHasExtension(read(file) ?? ""),
356
356
  async connect(server) {
357
357
  save(file, withYamlExtension(read(file), server));
358
- return { ok: true, message: `added to ${file}` };
358
+ return { ok: true, message: `added to ${tilde(file)}` };
359
359
  },
360
360
  async disconnect() {
361
361
  const text = read(file);
362
362
  if (!text || !yamlHasExtension(text)) {
363
- return { ok: true, message: "was not connected" };
363
+ return { ok: true, message: "wasn’t connected" };
364
364
  }
365
365
  save(file, withoutYamlExtension(text));
366
- return { ok: true, message: `removed from ${file}` };
366
+ return { ok: true, message: `removed from ${tilde(file)}` };
367
367
  },
368
368
  };
369
369
  }
@@ -397,7 +397,7 @@ function openClaw(paths) {
397
397
  "--args",
398
398
  ...server.args,
399
399
  ]);
400
- return { ok: true, message: "added with `openclaw mcp set`" };
400
+ return { ok: true, message: "added with openclaw mcp set" };
401
401
  }
402
402
  catch (error) {
403
403
  return { ok: false, message: error.message.split("\n")[0] };
@@ -409,7 +409,7 @@ function openClaw(paths) {
409
409
  return json.disconnect();
410
410
  }
411
411
  await run(openclaw, ["mcp", "unset", SERVER_NAME]).catch(() => undefined);
412
- return { ok: true, message: "removed with `openclaw mcp unset`" };
412
+ return { ok: true, message: "removed with openclaw mcp unset" };
413
413
  },
414
414
  };
415
415
  }
@@ -492,18 +492,18 @@ export async function appStates(paths) {
492
492
  export async function connectCommand(paths, ids, remove) {
493
493
  const unknown = ids.filter((id) => !apps(paths).some((app) => app.id === id));
494
494
  if (unknown.length) {
495
- console.error(`Unknown app ${unknown.join(", ")}. One of: ${apps(paths)
495
+ console.error(`Unknown app ${unknown.map((id) => `“${id}”`).join(", ")}. Choose from: ${apps(paths)
496
496
  .map((app) => app.id)
497
- .join(", ")}`);
497
+ .join(", ")}.`);
498
498
  return 2;
499
499
  }
500
500
  const results = await connectApps(paths, ids, remove);
501
501
  if (!results.length) {
502
- console.log("No AI app found on this machine. Name one: connect cursor");
502
+ console.log(`No AI app found on this computer. Name one: ${cmd("connect cursor")}`);
503
503
  return 0;
504
504
  }
505
505
  for (const { app, outcome } of results) {
506
- console.log(`${outcome.ok ? ok : bad} ${app.name.padEnd(22)} ${dim(tilde(outcome.message))}`);
506
+ console.log(`${outcome.ok ? ok : bad} ${app.name.padEnd(22)} ${dim(outcome.message)}`);
507
507
  if (!outcome.ok && outcome.snippet) {
508
508
  console.log(outcome.snippet);
509
509
  }
@@ -511,7 +511,7 @@ export async function connectCommand(paths, ids, remove) {
511
511
  if (!remove && results.some((r) => r.outcome.ok)) {
512
512
  console.log("");
513
513
  console.log(dim("Restart the app (or reload its MCP servers) to see the wizards in it."));
514
- console.log(dim("Claude Desktop, Cursor, VS Code and Goose show a run as a widget; the others as text."));
514
+ console.log(dim("Claude Desktop, Cursor, VS Code and Goose show a run as a widget, the others as text."));
515
515
  }
516
516
  return results.every((r) => r.outcome.ok) ? 0 : 1;
517
517
  }
@@ -3,7 +3,7 @@ import { declaredFeatures, featuresFile, fileFeatures, writeFeature } from "../f
3
3
  import { discoverPlugins } from "../plugins/discovery.js";
4
4
  import { pluginDirs } from "../plugins/loader.js";
5
5
  import { proModulesDir } from "../plugins/pro-state.js";
6
- import { command } from "./home.js";
6
+ import { cmd, tilde } from "./ui.js";
7
7
  /**
8
8
  * `wizards features` — the feature switches of this install, from its `.features` file. Not in
9
9
  * the help: for whoever works on a part before it is on for everyone.
@@ -31,20 +31,20 @@ export function featuresCommand(args) {
31
31
  const kind = d ? d.kind : "?";
32
32
  console.log(`${all[n] ? "on " : "off"} ${n.padEnd(28)} ${kind.padEnd(8)} ${source.padEnd(9)} ${d?.label ?? ""}`);
33
33
  }
34
- console.log(`\nfile: ${file}\nAn account's or a team's switches come on top of these while the runtime runs.`);
34
+ console.log(`\nFile: ${tilde(file)}\nWhile engenty wizards runs, account and team switches apply on top of these.`);
35
35
  return 0;
36
36
  }
37
37
  if (!FEATURE_NAME.test(name)) {
38
- console.error(`"${name}" is no switch name (teamPlans, contacts.leadScoring).`);
38
+ console.error(`“${name}” isn’t a valid switch name (e.g. teamPlans, contacts.leadScoring).`);
39
39
  return 2;
40
40
  }
41
41
  if (value === "on" || value === "off" || value === "default") {
42
42
  writeFeature(name, value === "default" ? null : value === "on", file);
43
43
  console.log(value === "default"
44
- ? `${name} follows the default again (${file}). A running runtime reads it at once.`
45
- : `${name} is ${value} on this machine (${file}). A running runtime reads it at once.`);
44
+ ? `${name} follows the default again (${tilde(file)}). engenty wizards picks this up straight away.`
45
+ : `${name} is ${value} on this computer (${tilde(file)}). engenty wizards picks this up straight away.`);
46
46
  return 0;
47
47
  }
48
- console.error(`${command()} features ${name} on|off|default`);
48
+ console.error(`Usage: ${cmd(`features ${name} on|off|default`)}`);
49
49
  return 2;
50
50
  }
@@ -11,45 +11,57 @@ import { logsCommand, reportCommand } from "./report.js";
11
11
  import { setup } from "./setup.js";
12
12
  import { open, start, studio } from "./start.js";
13
13
  import { autostart, restart, status, stop } from "./status.js";
14
+ import { cmd, cyan, tilde } from "./ui.js";
14
15
  import { update } from "./update.js";
15
16
  import { validateCommand } from "./validate.js";
17
+ /** One line of the help: the name padded, then coloured, so the columns stay straight. */
18
+ const line = (name, what) => ` ${cyan(name.padEnd(command().length + 12))}${what}`;
19
+ const more = (what) => ` ${" ".repeat(command().length + 12)}${what}`;
16
20
  const HELP = `engenty wizards ${packageVersion()}
21
+ engenty wizards runs the studio and your wizards on this computer. Below, “it” means engenty wizards.
17
22
 
18
- ${command()} what runs, and a menu: start, stop, update …; the first time, the setup runs first
19
- (needs a terminal)
20
- ${command()} start start it and open the studio; it runs in this terminal until Ctrl-C
21
- ${command()} studio open the studio; when it does not run, it starts as a service first
22
- ${command()} setup guided setup: your engenty account, an AI client to think with, ffmpeg,
23
- start at login
24
- ${command()} account your engenty account: status, sign-in, sign-out
25
- ${command()} open let this browser into the running studio (--print: only show the link)
26
- ${command()} status what is installed and what runs
27
- ${command()} doctor status, and what is wrong
28
- ${command()} logs the last lines it printed (\`logs 500\` for more)
29
- ${command()} report send status, model settings and the last log lines to your account, for support
30
- ${command()} stop stop it; the data is kept
31
- ${command()} restart stop it and start it again (after an update: the new version)
32
- ${command()} update the newest version
33
- ${command()} autostart start it at login: on, off (macOS: a LaunchAgent; Linux: a systemd service)
34
- ${command()} connect your wizards in your AI apps: every app found, or name them —
35
- ${apps(layout())
23
+ Usage
24
+ ${command()} [command] [options]
25
+
26
+ Commands
27
+ ${line(command(), "show the status and a menu (start, stop, update…); runs the setup the first time")}
28
+ ${more("(needs a terminal)")}
29
+ ${line(`${command()} start`, "start it in this terminal and open the studio (Ctrl-C stops it)")}
30
+ ${line(`${command()} studio`, "open the studio, starting it in the background if needed")}
31
+ ${line(`${command()} setup`, "set up your engenty account, an AI client, ffmpeg and start at login")}
32
+ ${line(`${command()} account`, "show your engenty account; add sign-in or sign-out to change it")}
33
+ ${line(`${command()} open`, "sign this browser in to the running studio (--print: only print the link)")}
34
+ ${line(`${command()} status`, "show what’s installed and running")}
35
+ ${line(`${command()} doctor`, "show the status and check for problems")}
36
+ ${line(`${command()} logs`, `print the last log lines (${command()} logs 500 for more)`)}
37
+ ${line(`${command()} report`, "send the status, model settings and last log lines to your account for support")}
38
+ ${line(`${command()} stop`, "stop it (your data is kept)")}
39
+ ${line(`${command()} restart`, "restart it, e.g. to switch to a newly installed version")}
40
+ ${line(`${command()} update`, "install the latest version")}
41
+ ${line(`${command()} autostart`, "start it when you log in: add on or off")}
42
+ ${more("(macOS: a LaunchAgent; Linux: a systemd user service)")}
43
+ ${line(`${command()} connect`, "add your wizards to your AI apps: every app found, or the ones you name:")}
44
+ ${more(apps(layout())
36
45
  .map((app) => app.id)
37
- .join(", ")}
38
- ${command()} disconnect take them out again (same names)
39
- ${command()} mcp the MCP server over stdio, as the AI apps start it
40
- ${command()} validate check wizards without the studio: .wizard files, unpacked packages, wizard JSON
41
- (--json for the issues as JSON); a project's connectors and plugins are
42
- checked on import
46
+ .join(", "))}
47
+ ${line(`${command()} disconnect`, "remove your wizards from those apps (same names)")}
48
+ ${line(`${command()} mcp`, "run the MCP server over stdio (AI apps start this)")}
49
+ ${line(`${command()} validate`, "check wizards without the studio: .wizard files, unpacked packages, wizard JSON")}
50
+ ${more("--json prints the issues as JSON. A space’s connectors and plugins are checked on import.")}
43
51
 
44
- --yes, -y ask nothing and install nothing optional
45
- --local setup without an account: nothing leaves this computer
46
- --client <name> install this AI client without asking: ${CLIENTS.map((c) => c.id).join(", ")}
47
- --no-open start without opening the browser
48
- --service, -s start as a service, detached from this terminal; \`${command()} stop\` stops it
49
- --version, --help
52
+ Options
53
+ --yes, -y don’t ask; skip everything optional
54
+ --local set up without an account; nothing leaves this computer
55
+ --client <name> install this AI client without asking: ${CLIENTS.map((c) => c.id).join(", ")}
56
+ --no-open start without opening the browser
57
+ --background, -s start in the background, detached from this terminal (${command()} stop stops it)
58
+ --version, -v print the version
59
+ --help, -h print this help
50
60
 
51
- Everything lives in ~/.engenty/wizards (ENGENTY_HOME moves it); settings go into its .env.
61
+ Everything lives in ${tilde(layout().home)} (ENGENTY_HOME moves it). Settings go in its .env file.
52
62
  `;
63
+ /** After a mistake on the command line: one line, not the whole help. */
64
+ const SEE_HELP = `Run ${cmd("help")} to see every command.`;
53
65
  function parse(argv) {
54
66
  const args = {
55
67
  command: "",
@@ -75,7 +87,7 @@ function parse(argv) {
75
87
  else if (arg === "--no-open") {
76
88
  args.open = false;
77
89
  }
78
- else if (arg === "--service" || arg === "-s") {
90
+ else if (arg === "--background" || arg === "--service" || arg === "-s") {
79
91
  args.service = true;
80
92
  }
81
93
  else if (arg === "--print") {
@@ -87,7 +99,7 @@ function parse(argv) {
87
99
  else if (arg === "--client") {
88
100
  const id = argv[++i];
89
101
  if (!CLIENTS.some((client) => client.id === id)) {
90
- return `--client takes one of: ${CLIENTS.map((c) => c.id).join(", ")}`;
102
+ return `--client takes one of these: ${CLIENTS.map((c) => c.id).join(", ")}.`;
91
103
  }
92
104
  args.clients.push(id);
93
105
  }
@@ -110,7 +122,7 @@ function parse(argv) {
110
122
  args.paths.push(arg);
111
123
  }
112
124
  else if (args.command) {
113
- return `Unexpected argument ${arg}.`;
125
+ return `Unexpected argument “${arg}”.`;
114
126
  }
115
127
  else {
116
128
  args.command = arg;
@@ -121,7 +133,7 @@ function parse(argv) {
121
133
  async function main(argv) {
122
134
  const args = parse(argv);
123
135
  if (typeof args === "string") {
124
- console.error(`${args}\n\n${HELP}`);
136
+ console.error(`${args} ${SEE_HELP}`);
125
137
  return 2;
126
138
  }
127
139
  const paths = layout();
@@ -129,11 +141,11 @@ async function main(argv) {
129
141
  if (!args.command) {
130
142
  // Without a command: the menu, which needs a terminal. Starting is \`start\`.
131
143
  if (args.service || !args.open || args.print) {
132
- console.error(`Options to start it go with start: ${command()} start ${argv.join(" ")}`);
144
+ console.error(`Those options belong to start: ${cmd(`start ${argv.join(" ")}`)}`);
133
145
  return 2;
134
146
  }
135
147
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
136
- console.error(`The menu needs a terminal. To start it: ${command()} start\n\n${HELP}`);
148
+ console.error(`The menu needs a terminal. To start engenty wizards: ${cmd("start")}\n\n${HELP}`);
137
149
  return 2;
138
150
  }
139
151
  return menu(paths, setupOptions, HELP);
@@ -192,12 +204,12 @@ async function main(argv) {
192
204
  process.chdir(startedIn);
193
205
  return validateCommand(args.paths, args.json);
194
206
  default:
195
- console.error(`Unknown command ${args.command}.\n\n${HELP}`);
207
+ console.error(`Unknown command “${args.command}”. ${SEE_HELP}`);
196
208
  return 2;
197
209
  }
198
210
  }
199
211
  if (!nodeIsCurrent()) {
200
- console.error(`engenty wizards needs Node.js 24.11 or newer; this is ${process.version}.`);
212
+ console.error(`engenty wizards needs Node.js 24.11 or newer. This is ${process.version}.`);
201
213
  process.exit(1);
202
214
  }
203
215
  process.exitCode = await main(process.argv.slice(2));
@@ -3,6 +3,7 @@ import { join } from "node:path";
3
3
  import { createInterface } from "node:readline";
4
4
  import { LOCAL_CLIENT_HEADER, LOCAL_MCP_HEADER, mintLocalTicket } from "../auth/local-ticket.js";
5
5
  import { runningRuntime, settings, startInBackground } from "./start.js";
6
+ import { tilde } from "./ui.js";
6
7
  /** The JSON-RPC messages of a response: a JSON body (one or a batch) or a server-sent stream. */
7
8
  export function messagesOf(contentType, body) {
8
9
  if (!body.trim()) {
@@ -48,12 +49,12 @@ export async function mcp(paths) {
48
49
  if (found) {
49
50
  return found;
50
51
  }
51
- process.stderr.write("engenty wizards is not running; starting it …\n");
52
+ process.stderr.write("engenty wizards isn’t running. Starting it…\n");
52
53
  const started = await startInBackground(paths);
53
54
  if (started) {
54
55
  return started;
55
56
  }
56
- throw new Error(`engenty wizards did not start; see ${join(paths.logs, "runtime.log")}.`);
57
+ throw new Error(`engenty wizards didn’t start. Its log is in ${tilde(join(paths.logs, "runtime.log"))}.`);
57
58
  })();
58
59
  running.catch(() => {
59
60
  running = null;
@@ -64,7 +65,7 @@ export async function mcp(paths) {
64
65
  const at = await runtime();
65
66
  const secret = readSecret(paths, dataDir);
66
67
  if (!secret) {
67
- throw new Error("The data folder has no secret; start engenty wizards once.");
68
+ throw new Error("No secret in the data folder yet. Start engenty wizards once first.");
68
69
  }
69
70
  const response = await fetch(`http://127.0.0.1:${at.port}/api/mcp`, {
70
71
  method: "POST",
@@ -79,7 +80,7 @@ export async function mcp(paths) {
79
80
  });
80
81
  const body = await response.text();
81
82
  if (!response.ok && response.status !== 202) {
82
- throw new Error(`engenty wizards answered ${response.status}: ${body.slice(0, 300)}`);
83
+ throw new Error(`engenty wizards responded with ${response.status}: ${body.slice(0, 300)}`);
83
84
  }
84
85
  return messagesOf(response.headers.get("content-type") ?? "", body);
85
86
  };
@@ -7,7 +7,7 @@ import { command, packageVersion, readInstallNote } from "./home.js";
7
7
  import { setup } from "./setup.js";
8
8
  import { open, runningRuntime, settings, start } from "./start.js";
9
9
  import { restart, status, stop } from "./status.js";
10
- import { badge, cyan, dim, no, ok } from "./ui.js";
10
+ import { badge, busy, cmd, cyan, dim, no, ok } from "./ui.js";
11
11
  import { update } from "./update.js";
12
12
  async function look(paths) {
13
13
  const { dataDir } = settings(paths);
@@ -26,7 +26,7 @@ function report({ running, atLogin, release, account }) {
26
26
  : `${no} not running`,
27
27
  ];
28
28
  if (!("unavailable" in atLogin)) {
29
- lines.push(atLogin.on ? `${ok} starts at login` : `${no} does not start at login`);
29
+ lines.push(atLogin.on ? `${ok} starts at login` : `${no} doesn’t start at login`);
30
30
  }
31
31
  lines.push(account.kind === "linked"
32
32
  ? `${ok} signed in as ${describeAccount(account)}`
@@ -41,38 +41,38 @@ function choices({ running, atLogin, release, account }) {
41
41
  ? [
42
42
  { value: "open", label: "Open the studio", hint: "in your browser" },
43
43
  { value: "restart", label: "Restart" },
44
- { value: "stop", label: "Stop", hint: "the data is kept" },
44
+ { value: "stop", label: "Stop", hint: "your data is kept" },
45
45
  ]
46
46
  : [
47
- { value: "start", label: "Start as service", hint: "in the background, opens the studio" },
48
- { value: "foreground", label: "Start here", hint: "in this terminal; Ctrl-C stops it" },
47
+ { value: "start", label: "Start in the background", hint: "and open the studio" },
48
+ { value: "foreground", label: "Start here", hint: "in this terminal (Ctrl-C stops it)" },
49
49
  ];
50
50
  options.push({
51
51
  value: "update",
52
52
  label: "Update",
53
- hint: release?.newer
54
- ? `${release.latest} is out`
55
- : release?.latest
56
- ? "you have the newest"
57
- : undefined,
53
+ hint: release?.newer ? `${release.latest} is out` : release?.latest ? "up to date" : undefined,
58
54
  });
59
55
  if (!("unavailable" in atLogin)) {
60
56
  options.push({
61
57
  value: "service",
62
- label: "Service settings",
63
- hint: atLogin.on ? "starts at login" : "does not start at login",
58
+ label: "Start at login",
59
+ hint: atLogin.on ? "on" : "off",
64
60
  });
65
61
  }
66
- options.push({ value: "connect", label: "Connect AI apps", hint: "your wizards in Claude, Cursor, Codex …" }, {
62
+ options.push({
63
+ value: "connect",
64
+ label: "Connect AI apps",
65
+ hint: "use your wizards in Claude, Cursor, Codex…",
66
+ }, {
67
67
  value: "account",
68
68
  label: "Account",
69
69
  hint: account.kind === "linked"
70
70
  ? `signed in as ${describeAccount(account)}`
71
71
  : describeAccount(account),
72
- }, { value: "status", label: "Status", hint: "what is installed, and what is wrong" }, { value: "setup", label: "Setup", hint: "account, AI client, ffmpeg, start at login" }, { value: "help", label: "Help", hint: "every command" }, { value: "quit", label: "Quit", hint: running ? "it keeps running" : undefined });
72
+ }, { value: "status", label: "Status", hint: "what’s installed, and any problems" }, { value: "setup", label: "Setup", hint: "account, AI client, ffmpeg, start at login" }, { value: "help", label: "Help", hint: "every command" }, { value: "quit", label: "Quit", hint: running ? "engenty wizards keeps running" : undefined });
73
73
  return options;
74
74
  }
75
- /** How the service runs: whether it starts at login. Esc goes back to the menu. */
75
+ /** Whether it starts at login. Esc goes back to the menu. */
76
76
  async function serviceSettings(paths, atLogin) {
77
77
  if ("unavailable" in atLogin) {
78
78
  return;
@@ -81,7 +81,7 @@ async function serviceSettings(paths, atLogin) {
81
81
  message: "Start at login?",
82
82
  initialValue: atLogin.on,
83
83
  options: [
84
- { value: true, label: "Yes", hint: "runs in the background from login on" },
84
+ { value: true, label: "Yes", hint: "runs in the background whenever you log in" },
85
85
  { value: false, label: "No", hint: "only when you start it" },
86
86
  ],
87
87
  });
@@ -92,20 +92,20 @@ async function serviceSettings(paths, atLogin) {
92
92
  await setAutostart(paths, on);
93
93
  }
94
94
  catch (error) {
95
- p.log.warn(`Starting at login could not be switched: ${error.message}`);
95
+ p.log.warn(`Couldn’t change start at login: ${error.message}`);
96
96
  }
97
97
  }
98
98
  /** The commands used most, for when the menu ends. */
99
99
  function commands() {
100
100
  const name = command();
101
101
  return [
102
- [`${name} start`, "start it and open the studio"],
103
- [`${name} studio`, "open the studio; starts it when it does not run"],
104
- [`${name} stop`, "stop it; the data is kept"],
105
- [`${name} status`, "what is installed and what runs"],
106
- [`${name} update`, "the newest version"],
107
- [`${name} --version`, "the version you have"],
108
- [`${name} help`, "every command"],
102
+ [`${name} start`, "start engenty wizards and open the studio"],
103
+ [`${name} studio`, "open the studio, starting engenty wizards if needed"],
104
+ [`${name} stop`, "stop engenty wizards (your data is kept)"],
105
+ [`${name} status`, "show what’s installed and running"],
106
+ [`${name} update`, "install the latest version"],
107
+ [`${name} --version`, "print the version you have"],
108
+ [`${name} help`, "list every command"],
109
109
  ]
110
110
  .map(([line, what]) => `${cyan(line.padEnd(name.length + 12))}${dim(what)}`)
111
111
  .join("\n");
@@ -123,13 +123,15 @@ export async function menu(paths, setupOptions, help) {
123
123
  }
124
124
  p.intro(`${badge("engenty wizards")} ${dim(packageVersion())}`);
125
125
  for (;;) {
126
- const spinner = p.spinner();
127
- spinner.start("Looking at what runs");
126
+ const spinner = busy("Checking status…");
128
127
  const state = await look(paths);
129
128
  spinner.stop(report(state));
130
- const action = await p.select({ message: "What now?", options: choices(state) });
129
+ const action = await p.select({
130
+ message: "What would you like to do?",
131
+ options: choices(state),
132
+ });
131
133
  if (p.isCancel(action) || action === "quit") {
132
- p.outro(dim(`${command()} opens this again; ${command()} help lists every command.`));
134
+ p.outro(dim(`${cmd()} opens this menu again. ${cmd("help")} lists every command.`));
133
135
  return 0;
134
136
  }
135
137
  switch (action) {
@@ -137,7 +139,7 @@ export async function menu(paths, setupOptions, help) {
137
139
  await start(paths, { open: true, background: true });
138
140
  break;
139
141
  case "foreground":
140
- p.outro("Starting engenty wizards here");
142
+ p.outro("Starting engenty wizards here…");
141
143
  return start(paths, { open: true });
142
144
  case "open":
143
145
  await open(paths, false);
@@ -155,7 +157,7 @@ export async function menu(paths, setupOptions, help) {
155
157
  }
156
158
  // This menu is still the old version, so it ends here.
157
159
  p.note(commands(), "Updated");
158
- p.outro(dim(`${command()} opens this menu again, in the new version.`));
160
+ p.outro(dim(`${cmd()} opens this menu again, in the new version.`));
159
161
  return 0;
160
162
  }
161
163
  case "service":
@@ -1,9 +1,8 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { LOCAL_MCP_HEADER, mintLocalTicket } from "../auth/local-ticket.js";
4
- import { command } from "./home.js";
5
4
  import { runningRuntime, settings } from "./start.js";
6
- import { bad, dim, ok } from "./ui.js";
5
+ import { bad, cmd, dim, ok, tilde } from "./ui.js";
7
6
  /**
8
7
  * `wizards logs` shows the last lines the running runtime printed; `wizards report` sends its
9
8
  * status, model settings and those lines to the linked account, where an operator reads them
@@ -26,11 +25,11 @@ async function ask(paths, method, path) {
26
25
  const { dataDir } = settings(paths);
27
26
  const running = await runningRuntime(dataDir);
28
27
  if (!running) {
29
- return `engenty wizards is not running. Start it with \`${command()} start\` first.`;
28
+ return `engenty wizards isn’t running. To start it: ${cmd("start")}`;
30
29
  }
31
30
  const secret = readSecret(paths, dataDir);
32
31
  if (!secret) {
33
- return "The data folder's secret could not be read.";
32
+ return `Couldn’t read the data folder’s secret (${tilde(join(dataDir, "secret"))}).`;
34
33
  }
35
34
  const res = await fetch(`http://127.0.0.1:${running.port}/api/local/debug${path}`, {
36
35
  method,
@@ -57,25 +56,24 @@ export async function logsCommand(paths, value) {
57
56
  console.log(`${dim(at)} ${level} ${line.text}`);
58
57
  }
59
58
  if (!lines.length) {
60
- console.log(dim("Nothing printed yet."));
59
+ console.log(dim("No log lines yet."));
61
60
  }
62
61
  return 0;
63
62
  }
64
63
  export async function reportCommand(paths) {
65
- console.log(dim("Sending status, model settings (no keys) and the last log lines …"));
66
64
  const answer = await ask(paths, "POST", "/report");
67
65
  if (typeof answer === "string") {
68
66
  console.error(answer);
69
67
  return 1;
70
68
  }
71
69
  if (answer.status === 409) {
72
- console.error(`${bad} This install is linked to no account. Link one in the studio under Settings → Account, then send again.`);
70
+ console.error(`${bad} This computer isn’t connected to an account. Sign in under Settings → Account in the studio, or with ${cmd("account sign-in")}. Then try again.`);
73
71
  return 1;
74
72
  }
75
73
  if (answer.status !== 200) {
76
- console.error(`${bad} ${answer.body.error ?? `The runtime answered ${answer.status}.`}`);
74
+ console.error(`${bad} ${answer.body.error ?? `engenty wizards responded with an error (${answer.status}).`}`);
77
75
  return 1;
78
76
  }
79
- console.log(`${ok} Report ${answer.body.id} is at ${answer.body.to}. Name it when you ask for help.`);
77
+ console.log(`${ok} Sent report ${answer.body.id} to ${answer.body.to}. Quote this ID when you ask for help.`);
80
78
  return 0;
81
79
  }