@lunora/cli 1.0.0-alpha.8 → 1.0.0-alpha.80

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/LICENSE.md +6 -0
  2. package/__assets__/package-og.svg +1 -1
  3. package/dist/bin.mjs +1 -1
  4. package/dist/index.d.mts +290 -117
  5. package/dist/index.d.ts +290 -117
  6. package/dist/index.mjs +8 -8
  7. package/dist/packem_chunks/handler.mjs +87 -6
  8. package/dist/packem_chunks/handler10.mjs +8 -14
  9. package/dist/packem_chunks/handler11.mjs +19 -189
  10. package/dist/packem_chunks/handler12.mjs +176 -115
  11. package/dist/packem_chunks/handler13.mjs +118 -52
  12. package/dist/packem_chunks/handler14.mjs +50 -43
  13. package/dist/packem_chunks/handler15.mjs +48 -67
  14. package/dist/packem_chunks/handler16.mjs +85 -37
  15. package/dist/packem_chunks/handler17.mjs +39 -100
  16. package/dist/packem_chunks/handler18.mjs +87 -152
  17. package/dist/packem_chunks/handler19.mjs +148 -67
  18. package/dist/packem_chunks/handler2.mjs +6 -4
  19. package/dist/packem_chunks/handler20.mjs +77 -75
  20. package/dist/packem_chunks/handler21.mjs +71 -288
  21. package/dist/packem_chunks/handler3.mjs +6 -3
  22. package/dist/packem_chunks/handler4.mjs +2 -2
  23. package/dist/packem_chunks/handler5.mjs +7 -4
  24. package/dist/packem_chunks/handler6.mjs +10 -7
  25. package/dist/packem_chunks/handler7.mjs +2 -2
  26. package/dist/packem_chunks/handler8.mjs +1 -1
  27. package/dist/packem_chunks/handler9.mjs +315 -12
  28. package/dist/packem_chunks/planDevCommand.mjs +758 -81
  29. package/dist/packem_chunks/runCodegenCommand.mjs +5 -4
  30. package/dist/packem_chunks/runDeployCommand.mjs +126 -20
  31. package/dist/packem_chunks/runInitCommand.mjs +1661 -179
  32. package/dist/packem_chunks/runMigrateGenerateCommand.mjs +5 -4
  33. package/dist/packem_chunks/runResetCommand.mjs +2 -2
  34. package/dist/packem_chunks/runRpcCommand.mjs +3 -2
  35. package/dist/packem_shared/{COMMANDS-Bn8luojF.mjs → COMMANDS-DW8lQmuW.mjs} +85 -23
  36. package/dist/packem_shared/{DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs → DEFAULT_IMPORT_BATCH_SIZE-CRQmmBZM.mjs} +3 -2
  37. package/dist/packem_shared/{api-spec-CtA6ilu4.mjs → api-spec-Bx0iKbxA.mjs} +3 -1
  38. package/dist/packem_shared/{buildRegistryIndex-BcYe607_.mjs → buildRegistryIndex-BS5ig822.mjs} +1 -1
  39. package/dist/packem_shared/codegen-error-DJG-ghs_.mjs +31 -0
  40. package/dist/packem_shared/{command-BDXcJCCJ.mjs → command-lYnl4QyF.mjs} +6 -1
  41. package/dist/packem_shared/{commands-DqsEzojt.mjs → commands-ClEvcz3V.mjs} +224 -18
  42. package/dist/packem_shared/{createLogger-CHPNjFw2.mjs → createLogger-CIWSHrTL.mjs} +40 -8
  43. package/dist/packem_shared/{createRecordingSpawner-DxI3mebw.mjs → createRecordingSpawner-Cw5Iu73G.mjs} +12 -2
  44. package/dist/packem_shared/{detect-package-manager-DYp7n3mJ.mjs → detect-package-manager-DvEthdCw.mjs} +26 -30
  45. package/dist/packem_shared/{insertSchemaExtension-BuzF6-t2.mjs → insertSchemaExtension-DAqbfr9Z.mjs} +15 -10
  46. package/dist/packem_shared/{open-url-Dfq6fAyT.mjs → open-url-4PBLY9X0.mjs} +3 -2
  47. package/dist/packem_shared/{output-format-7gyGR3h8.mjs → output-format-B4642rjE.mjs} +1 -1
  48. package/dist/packem_shared/{parseManifest--vZf2FY1.mjs → parseManifest-Dbp-Q2q3.mjs} +36 -9
  49. package/dist/packem_shared/prompt-cancelled-APzX1Im-.mjs +9 -0
  50. package/dist/packem_shared/runAddCommand-DGjFXGqa.mjs +4 -0
  51. package/dist/packem_shared/storage-DEXpJkXh.mjs +84 -0
  52. package/dist/packem_shared/tui-prompts-BjEN8XgP.mjs +658 -0
  53. package/dist/packem_shared/wrangler-secrets-Dq_Fkbm-.mjs +49 -0
  54. package/package.json +16 -13
  55. package/skills/lunora-quickstart/SKILL.md +25 -5
  56. package/skills/lunora-setup-storage/SKILL.md +7 -3
  57. package/dist/packem_shared/features-ocSSpZtS.mjs +0 -24
  58. package/dist/packem_shared/runAddCommand-G544_v6e.mjs +0 -4
  59. package/dist/packem_shared/tui-prompts-XHFxlOg5.mjs +0 -269
@@ -1,14 +1,15 @@
1
1
  import { existsSync, mkdirSync, writeFileSync, mkdtempSync, rmSync, readFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { discoverSchema, discoverMigrations } from '@lunora/codegen';
4
+ import { LunoraError } from '@lunora/errors';
4
5
  import { join } from '@visulima/path';
5
6
  import { Project } from 'ts-morph';
6
7
  import { r as resolveAdminBaseUrl } from '../packem_shared/admin-url-4UzT-CI4.mjs';
7
- import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
8
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
8
9
  import { diffSnapshots, renderMigrationFile } from '../packem_shared/diffSnapshots-BeDvvNiF.mjs';
9
10
  import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
10
11
  import schemaIrToSnapshot from '../packem_shared/schemaIrToSnapshot-DdsljJT-.mjs';
11
- import { runExportCommand, runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs';
12
+ import { runExportCommand, runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-CRQmmBZM.mjs';
12
13
 
13
14
  const SNAPSHOT_FILENAME = ".snapshot.json";
14
15
  const NON_ALPHANUMERIC = /[^\da-z]+/gu;
@@ -41,12 +42,12 @@ const loadSnapshot = (path) => {
41
42
  const raw = readFileSync(path, "utf8");
42
43
  const parsed = JSON.parse(raw);
43
44
  if (parsed.version !== 1) {
44
- throw new Error(`unsupported snapshot version: ${parsed.version}`);
45
+ throw new LunoraError("INTERNAL", `unsupported snapshot version: ${parsed.version}`);
45
46
  }
46
47
  return parsed;
47
48
  } catch (error) {
48
49
  const message = error instanceof Error ? error.message : String(error);
49
- throw new Error(`failed to read ${path}: ${message}`, { cause: error });
50
+ throw new LunoraError("INTERNAL", `failed to read ${path}: ${message}`, { cause: error });
50
51
  }
51
52
  };
52
53
  const runMigrateGenerateCommand = (options) => {
@@ -1,7 +1,7 @@
1
1
  import { existsSync, rmSync } from 'node:fs';
2
2
  import { join } from '@visulima/path';
3
- import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
4
- import { a as tuiConfirm } from '../packem_shared/tui-prompts-XHFxlOg5.mjs';
3
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
4
+ import { b as tuiConfirm } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
5
5
 
6
6
  const runResetCommand = async (options) => {
7
7
  const cwd = options.cwd ?? process.cwd();
@@ -1,4 +1,5 @@
1
- import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
1
+ import { LunoraError } from '@lunora/errors';
2
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
2
3
  import { r as resolveWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
3
4
 
4
5
  const parseArgsJson = (raw) => {
@@ -9,7 +10,7 @@ const parseArgsJson = (raw) => {
9
10
  return JSON.parse(raw);
10
11
  } catch (error) {
11
12
  const message = error instanceof Error ? error.message : String(error);
12
- throw new Error(`failed to parse --args as JSON: ${message}`, { cause: error });
13
+ throw new LunoraError("INTERNAL", `failed to parse --args as JSON: ${message}`, { cause: error });
13
14
  }
14
15
  };
15
16
  const TRAILING_SLASH = /\/$/u;
@@ -1,20 +1,27 @@
1
1
  import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { resolveHint, isLunoraError, flattenHint, findSolutionByMessage } from '@lunora/errors';
2
3
  import { createCerebro } from '@visulima/cerebro';
3
4
  import completionCommand from '@visulima/cerebro/command/completion';
4
5
  import versionCommand from '@visulima/cerebro/command/version';
5
- import { A as API_SPEC_HELP } from './api-spec-CtA6ilu4.mjs';
6
- import { createLogger } from './createLogger-CHPNjFw2.mjs';
6
+ import { A as API_SPEC_HELP } from './api-spec-Bx0iKbxA.mjs';
7
+ import { createLogger } from './createLogger-CIWSHrTL.mjs';
8
+ import { VisulimaError, renderError } from '@visulima/error';
7
9
  import { tmpdir } from 'node:os';
8
10
  import { join } from 'node:path';
9
11
 
10
12
  const addCommand = {
11
- argument: { description: "Feature or registry item: auth | email | storage | crons | presence | ratelimit | backup | …", name: "feature", type: String },
12
- description: "Add a feature or registry item (auth, email, storage, crons, …) to the current Lunora project",
13
+ argument: {
14
+ description: "Feature or registry item: ai | auth | email | storage | crons | presence | queue | workflow | flags | backup | …",
15
+ name: "feature",
16
+ type: String
17
+ },
18
+ description: "Add a feature or registry item (ai, auth, email, storage, crons, …) to the current Lunora project",
13
19
  examples: [
14
20
  ["lunora add auth", "Add authentication (asks which provider)"],
15
21
  ["lunora add auth --provider clerk", "Add Clerk auth without prompting"],
16
22
  ["lunora add email", "Add transactional email (Cloudflare Email Workers + dev mail catcher)"],
17
- ["lunora add storage", "Add the R2 storage registry item"],
23
+ ["lunora add storage", "Add the R2 storage registry item (asks for the bucket name)"],
24
+ ["lunora add storage --bucket my-app-uploads", "Add storage with a bucket name, no prompt"],
18
25
  ["lunora add crons", "Add the scheduled-jobs registry item"],
19
26
  ["lunora add storage --ref alpha", "Add an item from the alpha branch's registry"]
20
27
  ],
@@ -25,7 +32,10 @@ const addCommand = {
25
32
  name: "add",
26
33
  options: [
27
34
  { description: "auth: provider to use without prompting (auth | clerk | auth0)", name: "provider", type: String },
28
- { description: "Skip the provider prompt and use the default (email & password)", name: "yes", type: Boolean },
35
+ { description: "auth: D1 database name to use without prompting (lowercase alphanumeric + hyphens)", name: "db", type: String },
36
+ { description: "storage: R2 bucket name to use without prompting (lowercase alphanumeric + hyphens)", name: "bucket", type: String },
37
+ { description: "email: verified destination address to use without prompting", name: "mail-to", type: String },
38
+ { description: "Skip prompts (auth provider, DB name, bucket name, mail destination) and use the defaults", name: "yes", type: Boolean },
29
39
  { description: "Local registry root (offline; expects <name>/ subdirs)", name: "from", type: String },
30
40
  { description: "Override the remote registry source base (e.g. gh:owner/repo/registry)", name: "source", type: String },
31
41
  {
@@ -33,7 +43,8 @@ const addCommand = {
33
43
  name: "ref",
34
44
  type: String
35
45
  },
36
- { description: "Permit --source values outside gh:/github:/https://", name: "allow-unsafe-source", type: Boolean }
46
+ { description: "Permit --source values outside gh:/github:/https://", name: "allow-unsafe-source", type: Boolean },
47
+ { description: "Output format: pretty (default) or json", name: "format", type: String }
37
48
  ]
38
49
  };
39
50
 
@@ -208,9 +219,19 @@ const deploymentsCommand = {
208
219
  };
209
220
 
210
221
  const devCommand = {
222
+ argument: {
223
+ description: "Optional subcommand: stop (shut the running dev server down) | status (report it) | logs (print its captured output)",
224
+ name: "args",
225
+ type: String
226
+ },
211
227
  description: "Run the dev stack: wrangler worker + studio + codegen watch",
212
228
  examples: [
213
229
  ["lunora dev", "Run the worker + studio + codegen watch"],
230
+ ["lunora dev --background", "Run detached: blocks until ready, prints URL + PID, then returns"],
231
+ ["lunora dev stop", "Stop the background/tracked dev server (idempotent)"],
232
+ ["lunora dev status", "Report the running dev server (URL, PID, uptime)"],
233
+ ["lunora dev logs", "Print the captured dev-server log (background runs)"],
234
+ ["lunora dev --json", "Machine-readable JSON log lines (also LUNORA_LOG_JSON=1)"],
214
235
  ["lunora dev --no-studio", "Skip the embedded studio server"],
215
236
  ["lunora dev --worker-port 8080", "Use a custom wrangler dev port"],
216
237
  ["lunora dev --remote", "Proxy D1/KV/R2 to the deployed worker (also LUNORA_REMOTE=1)"]
@@ -220,10 +241,19 @@ const devCommand = {
220
241
  return { default: m.execute };
221
242
  }),
222
243
  name: "dev",
244
+ // KEEP IN SYNC with `daemonArguments` in `./lifecycle.ts`: a new flag that
245
+ // must reach a `--background` daemon has to be forwarded there explicitly.
223
246
  options: [
224
247
  { description: `Which API spec(s) codegen emits: ${API_SPEC_HELP} (default openapi)`, name: "api-spec", type: String },
225
248
  { description: "Studio server port (default 6173)", name: "port", type: Number },
226
249
  { description: "wrangler dev port (default 8787)", name: "worker-port", type: Number },
250
+ {
251
+ description: "Run the dev server as a managed background process (auto-enabled when an AI agent is detected; LUNORA_AGENT_MODE=0 disables)",
252
+ name: "background",
253
+ type: Boolean
254
+ },
255
+ { description: "Emit machine-readable JSON log lines (also LUNORA_LOG_JSON=1; auto-enabled for AI agents)", name: "json", type: Boolean },
256
+ { description: "How many trailing lines `lunora dev logs` prints (default 100, 0 = all)", name: "lines", type: Number },
227
257
  { description: "Don't start the embedded studio server", name: "no-studio", type: Boolean },
228
258
  { description: "Don't watch + regenerate codegen", name: "no-codegen", type: Boolean },
229
259
  { description: "Proxy D1/KV/R2 bindings to the deployed worker (or set LUNORA_REMOTE=1)", name: "remote", type: Boolean }
@@ -256,21 +286,24 @@ const doctorCommand = {
256
286
  };
257
287
 
258
288
  const envCommand = {
259
- argument: { description: "list | get <KEY> | set <KEY> <VALUE> | unset <KEY> | push | diff | doctor", name: "subcommand", type: String },
260
- description: "Manage .dev.vars and sync secrets via wrangler (list | get | set | unset | push | diff | doctor)",
289
+ argument: { description: "list | get <KEY> | set <KEY> <VALUE> | unset <KEY> | generate [KEY] | push | diff | doctor", name: "subcommand", type: String },
290
+ description: "Manage .dev.vars and sync secrets via wrangler (list | get | set | unset | generate | push | diff | doctor)",
261
291
  examples: [
262
292
  ["lunora env list", "List .dev.vars keys"],
263
293
  ["lunora env set API_KEY secret", "Set a local variable"],
294
+ ["lunora env generate", "Generate strong values for the project's secrets (print KEY=value)"],
295
+ ["lunora env generate AUTH_SECRET --set", "Generate one secret and write it to .dev.vars"],
264
296
  ["lunora env push --yes", "Upload secrets to Cloudflare"],
265
297
  ["lunora env diff", "Compare local .dev.vars keys against Cloudflare"]
266
298
  ],
267
299
  group: "Data",
268
- loader: () => import('../packem_chunks/handler21.mjs').then((m) => {
300
+ loader: () => import('../packem_chunks/handler9.mjs').then((m) => {
269
301
  return { default: m.execute };
270
302
  }),
271
303
  name: "env",
272
304
  options: [
273
305
  { description: "Target production for `push` (passes --env production to wrangler)", name: "prod", type: Boolean },
306
+ { description: "For `generate` — write the generated secrets into .dev.vars instead of printing them", name: "set", type: Boolean },
274
307
  {
275
308
  description: "Push secrets to a temporary-account deployment when unauthenticated (wrangler secret put --temporary). Errors if you're already authenticated.",
276
309
  name: "temporary",
@@ -288,7 +321,7 @@ const exportCommand = {
288
321
  ["lunora export --tables messages,users", "Export only specific tables"]
289
322
  ],
290
323
  group: "Data",
291
- loader: () => import('../packem_chunks/handler9.mjs').then((m) => {
324
+ loader: () => import('../packem_chunks/handler10.mjs').then((m) => {
292
325
  return { default: m.execute };
293
326
  }),
294
327
  name: "export",
@@ -310,7 +343,7 @@ const importCommand = {
310
343
  description: "Bulk-insert rows from an NDJSON file via the worker's admin endpoint",
311
344
  examples: [["lunora import backup.ndjson", "Bulk-insert rows from an NDJSON file"]],
312
345
  group: "Data",
313
- loader: () => import('../packem_chunks/handler10.mjs').then((m) => {
346
+ loader: () => import('../packem_chunks/handler11.mjs').then((m) => {
314
347
  return { default: m.execute };
315
348
  }),
316
349
  name: "import",
@@ -334,7 +367,7 @@ const infoCommand = {
334
367
  ["lunora info --json", "Emit a JSON snapshot"]
335
368
  ],
336
369
  group: "Project",
337
- loader: () => import('../packem_chunks/handler11.mjs').then((m) => {
370
+ loader: () => import('../packem_chunks/handler12.mjs').then((m) => {
338
371
  return { default: m.execute };
339
372
  }),
340
373
  name: "info",
@@ -415,6 +448,16 @@ const initCommand = {
415
448
  description: "Also scaffold a CI deploy pipeline: github (.github/workflows/deploy.yml) or gitlab (.gitlab-ci.yml)",
416
449
  name: "ci",
417
450
  type: String
451
+ },
452
+ {
453
+ description: "Add features non-interactively after scaffolding (comma-separated): ai | auth | backup | browser | cloudflare-access | crons | email | flags | hyperdrive | payment | presence | queue | storage | workflow",
454
+ name: "add",
455
+ type: String
456
+ },
457
+ {
458
+ description: "Walk through every step (prompts + output) without writing files, installing, or running git",
459
+ name: "dry-run",
460
+ type: Boolean
418
461
  }
419
462
  ]
420
463
  };
@@ -428,7 +471,7 @@ const insightsCommand = {
428
471
  ["lunora insights --prod --url https://app.example.com --token $LUNORA_ADMIN_TOKEN", "Report against production"]
429
472
  ],
430
473
  group: "Develop",
431
- loader: () => import('../packem_chunks/handler12.mjs').then((m) => {
474
+ loader: () => import('../packem_chunks/handler13.mjs').then((m) => {
432
475
  return { default: m.execute };
433
476
  }),
434
477
  name: "insights",
@@ -450,7 +493,7 @@ const linkCommand = {
450
493
  ["lunora link --remove", "Remove the link"]
451
494
  ],
452
495
  group: "Deploy",
453
- loader: () => import('../packem_chunks/handler13.mjs').then((m) => {
496
+ loader: () => import('../packem_chunks/handler14.mjs').then((m) => {
454
497
  return { default: m.execute };
455
498
  }),
456
499
  name: "link",
@@ -466,7 +509,7 @@ const logsCommand = {
466
509
  argument: { description: "Worker name (defaults to the name in wrangler config)", name: "worker", type: String },
467
510
  description: "Stream live logs from a deployed Lunora Worker",
468
511
  group: "Deploy",
469
- loader: () => import('../packem_chunks/handler14.mjs').then((m) => {
512
+ loader: () => import('../packem_chunks/handler15.mjs').then((m) => {
470
513
  return { default: m.execute };
471
514
  }),
472
515
  name: "logs",
@@ -526,7 +569,7 @@ const prepareCommand = {
526
569
  description: "Run codegen + binding reconcile + wrangler validation (no Vite) — for CI",
527
570
  examples: [["lunora prepare", "Codegen + binding reconcile + validate (CI, before deploy)"]],
528
571
  group: "Deploy",
529
- loader: () => import('../packem_chunks/handler15.mjs').then((m) => {
572
+ loader: () => import('../packem_chunks/handler16.mjs').then((m) => {
530
573
  return { default: m.execute };
531
574
  }),
532
575
  name: "prepare",
@@ -550,7 +593,7 @@ const registryCommand = {
550
593
  ["lunora registry build --check", "Verify the committed catalog is current"]
551
594
  ],
552
595
  group: "Project",
553
- loader: () => import('../packem_chunks/handler16.mjs').then((m) => {
596
+ loader: () => import('../packem_chunks/handler17.mjs').then((m) => {
554
597
  return { default: m.execute };
555
598
  }),
556
599
  name: "registry",
@@ -600,7 +643,7 @@ const rulesCommand = {
600
643
  ["lunora rules check --strict", "Exit non-zero when rules are missing (CI gate)"]
601
644
  ],
602
645
  group: "Project",
603
- loader: () => import('../packem_chunks/handler17.mjs').then((m) => {
646
+ loader: () => import('../packem_chunks/handler18.mjs').then((m) => {
604
647
  return { default: m.execute };
605
648
  }),
606
649
  name: "rules",
@@ -638,7 +681,7 @@ const seedCommand = {
638
681
  ["lunora seed --seed 7 --dry-run", "Print the NDJSON for seed 7 without inserting"]
639
682
  ],
640
683
  group: "Data",
641
- loader: () => import('../packem_chunks/handler18.mjs').then((m) => {
684
+ loader: () => import('../packem_chunks/handler19.mjs').then((m) => {
642
685
  return { default: m.execute };
643
686
  }),
644
687
  name: "seed",
@@ -667,7 +710,7 @@ const verifyCommand = {
667
710
  ["lunora verify --no-typecheck", "Skip the TypeScript type-check"]
668
711
  ],
669
712
  group: "Deploy",
670
- loader: () => import('../packem_chunks/handler19.mjs').then((m) => {
713
+ loader: () => import('../packem_chunks/handler20.mjs').then((m) => {
671
714
  return { default: m.execute };
672
715
  }),
673
716
  name: "verify",
@@ -686,13 +729,28 @@ const viewCommand = {
686
729
  ["lunora view --remote", "Open the deployed studio"]
687
730
  ],
688
731
  group: "Project",
689
- loader: () => import('../packem_chunks/handler20.mjs').then((m) => {
732
+ loader: () => import('../packem_chunks/handler21.mjs').then((m) => {
690
733
  return { default: m.execute };
691
734
  }),
692
735
  name: "view",
693
736
  options: [{ description: "Open the deployed worker URL instead of localhost", name: "remote", type: Boolean }]
694
737
  };
695
738
 
739
+ const NO_STACK = { filterStacktrace: () => false, hideErrorCodeView: true };
740
+ const renderLunoraError = (error, options = {}) => {
741
+ const message = error instanceof Error ? error.message : String(error);
742
+ const hint = resolveHint(isLunoraError(error) ? { code: error.code, hint: error.hint, message } : message);
743
+ const rendered = new VisulimaError({
744
+ // `renderError` iterates `hint` as lines; the shared flattener returns one
745
+ // string, so split it back to lines for the terminal renderer.
746
+ hint: hint === void 0 ? void 0 : flattenHint(hint).split("\n"),
747
+ message: options.reason === void 0 ? message : `${options.reason}: ${message}`,
748
+ name: error instanceof Error && error.name.length > 0 ? error.name : "Error"
749
+ });
750
+ rendered.stack = "";
751
+ return renderError(rendered, NO_STACK);
752
+ };
753
+
696
754
  const editDistance = (a, b) => {
697
755
  const distances = Array.from({ length: b.length + 1 }, (_, index) => index);
698
756
  for (let row = 1; row <= a.length; row += 1) {
@@ -907,7 +965,11 @@ const reportRunError = (error) => {
907
965
  const message = error instanceof Error ? error.message : String(error);
908
966
  const unknown = UNKNOWN_COMMAND.exec(message);
909
967
  if (!unknown?.groups) {
910
- logger.error(message);
968
+ if (isLunoraError(error) || findSolutionByMessage(message) !== void 0) {
969
+ logger.error(renderLunoraError(error));
970
+ } else {
971
+ logger.error(message);
972
+ }
911
973
  return;
912
974
  }
913
975
  const name = unknown.groups.name ?? "";
@@ -1,5 +1,6 @@
1
1
  import { createWriteStream, createReadStream } from 'node:fs';
2
2
  import { unlink, stat } from 'node:fs/promises';
3
+ import { LunoraError } from '@lunora/errors';
3
4
  import { r as resolveAdminBaseUrl } from './admin-url-4UzT-CI4.mjs';
4
5
 
5
6
  const EXPORT_ENDPOINT_PATH = "/_lunora/admin/export";
@@ -183,7 +184,7 @@ const runImportCommand = async (options) => {
183
184
  });
184
185
  if (!response.ok) {
185
186
  const text = await response.text().catch(() => "<no body>");
186
- throw new Error(`import batch failed (HTTP ${String(response.status)}): ${text}`);
187
+ throw new LunoraError("INTERNAL", `import batch failed (HTTP ${String(response.status)}): ${text}`);
187
188
  }
188
189
  const json = await response.json();
189
190
  if (json.inserted) {
@@ -213,7 +214,7 @@ const runImportCommand = async (options) => {
213
214
  parsedDocument = JSON.parse(trimmed);
214
215
  } catch (error) {
215
216
  const message = error instanceof Error ? error.message : String(error);
216
- throw new Error(`invalid JSON on line ${String(lineNumber)}: ${message}`, { cause: error });
217
+ throw new LunoraError("INTERNAL", `invalid JSON on line ${String(lineNumber)}: ${message}`, { cause: error });
217
218
  }
218
219
  batch.push(JSON.stringify({ doc: parsedDocument, table: options.table }));
219
220
  };
@@ -1,3 +1,5 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
1
3
  const API_SPEC_VALUES = ["both", "none", "openapi", "openrpc"];
2
4
  const API_SPEC_HELP = API_SPEC_VALUES.join(" | ");
3
5
  const parseApiSpec = (value) => {
@@ -7,7 +9,7 @@ const parseApiSpec = (value) => {
7
9
  if (API_SPEC_VALUES.includes(value)) {
8
10
  return value;
9
11
  }
10
- throw new Error(`invalid --api-spec "${value}" — expected one of: ${API_SPEC_HELP}`);
12
+ throw new LunoraError("INTERNAL", `invalid --api-spec "${value}" — expected one of: ${API_SPEC_HELP}`);
11
13
  };
12
14
 
13
15
  export { API_SPEC_HELP as A, parseApiSpec as p };
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
2
  import { join } from '@visulima/path';
3
- import parseManifest from './parseManifest--vZf2FY1.mjs';
3
+ import parseManifest from './parseManifest-Dbp-Q2q3.mjs';
4
4
 
5
5
  const CONTROL_CHARS = /[\u0000-\u001F\u007F-\u009F]/gu;
6
6
  const stripControlChars = (value) => value === void 0 ? void 0 : value.replaceAll(CONTROL_CHARS, "");
@@ -0,0 +1,31 @@
1
+ import { findLunoraSolution } from '@lunora/codegen';
2
+ import { VisulimaError, renderError } from '@visulima/error';
3
+
4
+ const flattenSolutionBody = (solution) => solution.body.split("\n").filter((line) => !line.startsWith("```")).join("\n").replaceAll(/\*\*(.+?)\*\*/gu, "$1").replaceAll(/`([^`]+)`/gu, "$1");
5
+ const NO_STACK = { filterStacktrace: () => false, hideErrorCodeView: true };
6
+ const renderCodegenFailure = (error, reason) => {
7
+ const message = error instanceof Error ? error.message : String(error);
8
+ const solution = findLunoraSolution(message);
9
+ const rendered = new VisulimaError({
10
+ hint: solution ? [solution.header, "", flattenSolutionBody(solution)] : void 0,
11
+ message: reason === void 0 ? `codegen failed: ${message}` : `codegen failed (${reason}): ${message}`,
12
+ name: "CodegenError"
13
+ });
14
+ rendered.stack = "";
15
+ return renderError(rendered, NO_STACK);
16
+ };
17
+ const renderCodegenHint = (message) => {
18
+ const solution = findLunoraSolution(message);
19
+ if (solution === void 0) {
20
+ return void 0;
21
+ }
22
+ const rendered = new VisulimaError({
23
+ hint: [flattenSolutionBody(solution)],
24
+ message: solution.header,
25
+ name: "Hint"
26
+ });
27
+ rendered.stack = "";
28
+ return renderError(rendered, NO_STACK);
29
+ };
30
+
31
+ export { renderCodegenHint as a, renderCodegenFailure as r };
@@ -1,4 +1,5 @@
1
- import { createLogger } from './createLogger-CHPNjFw2.mjs';
1
+ import { createLogger } from './createLogger-CIWSHrTL.mjs';
2
+ import { P as PromptCancelledError, a as PROMPT_CANCEL_EXIT_CODE } from './prompt-cancelled-APzX1Im-.mjs';
2
3
 
3
4
  const defineHandler = (body) => async (toolbox) => {
4
5
  const logger = createLogger();
@@ -6,6 +7,10 @@ const defineHandler = (body) => async (toolbox) => {
6
7
  const { code } = await body({ argument: toolbox.argument, cwd: toolbox.process.cwd, logger, options: toolbox.options });
7
8
  toolbox.process.exit(code);
8
9
  } catch (error) {
10
+ if (error instanceof PromptCancelledError) {
11
+ toolbox.process.exit(PROMPT_CANCEL_EXIT_CODE);
12
+ return;
13
+ }
9
14
  logger.error(error instanceof Error ? error.message : String(error));
10
15
  toolbox.process.exit(1);
11
16
  }