@lunora/cli 1.0.0-alpha.4 → 1.0.0-alpha.5

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.
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from './packem_shared/COMMANDS-CHw4zOZ9.mjs';
2
+ import { runCli } from './packem_shared/COMMANDS-DXaq12xm.mjs';
3
3
 
4
4
  try {
5
5
  const code = await runCli();
package/dist/index.d.mts CHANGED
@@ -532,7 +532,7 @@ interface OfferDeps {
532
532
  * maps to the `mail` item. Picked items are applied in selection order.
533
533
  * Non-interactive: prints how to add them later and changes nothing.
534
534
  */
535
- type Template = "astro" | "next" | "nuxt" | "standalone" | "sveltekit" | "tanstack-start-react" | "tanstack-start-solid" | "vite";
535
+ type Template = "astro" | "next" | "nuxt" | "standalone" | "sveltekit" | "tanstack-start-react" | "tanstack-start-solid" | "vite-react";
536
536
  interface InitCommandOptions {
537
537
  /**
538
538
  * When true, accept `--source` values that don't start with `gh:` /
@@ -605,7 +605,7 @@ interface InitCommandResult {
605
605
  * scaffold's exit code.
606
606
  */
607
607
  declare const runInitCommand: (options: InitCommandOptions) => Promise<InitCommandResult>;
608
- /** Narrow a raw `--template` value to a known {@link Template} (defaults to vite). */
608
+ /** Narrow a raw `--template` value to a known {@link Template} (defaults to vite-react). */
609
609
  interface MigrateGenerateCommandOptions {
610
610
  cwd?: string;
611
611
  logger: Logger;
package/dist/index.d.ts CHANGED
@@ -532,7 +532,7 @@ interface OfferDeps {
532
532
  * maps to the `mail` item. Picked items are applied in selection order.
533
533
  * Non-interactive: prints how to add them later and changes nothing.
534
534
  */
535
- type Template = "astro" | "next" | "nuxt" | "standalone" | "sveltekit" | "tanstack-start-react" | "tanstack-start-solid" | "vite";
535
+ type Template = "astro" | "next" | "nuxt" | "standalone" | "sveltekit" | "tanstack-start-react" | "tanstack-start-solid" | "vite-react";
536
536
  interface InitCommandOptions {
537
537
  /**
538
538
  * When true, accept `--source` values that don't start with `gh:` /
@@ -605,7 +605,7 @@ interface InitCommandResult {
605
605
  * scaffold's exit code.
606
606
  */
607
607
  declare const runInitCommand: (options: InitCommandOptions) => Promise<InitCommandResult>;
608
- /** Narrow a raw `--template` value to a known {@link Template} (defaults to vite). */
608
+ /** Narrow a raw `--template` value to a known {@link Template} (defaults to vite-react). */
609
609
  interface MigrateGenerateCommandOptions {
610
610
  cwd?: string;
611
611
  logger: Logger;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { COMMANDS, VERSION, runCli } from './packem_shared/COMMANDS-CHw4zOZ9.mjs';
1
+ export { COMMANDS, VERSION, runCli } from './packem_shared/COMMANDS-DXaq12xm.mjs';
2
2
  export { runCodegenCommand } from './packem_chunks/runCodegenCommand.mjs';
3
3
  export { DEFAULT_IMPORT_BATCH_SIZE, runExportCommand, runImportCommand } from './packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs';
4
4
  export { runDeployCommand } from './packem_chunks/runDeployCommand.mjs';
@@ -16,4 +16,4 @@ export { createRecordingSpawner, defaultSpawner } from './packem_shared/defaultS
16
16
  export { default as parseManifest } from './packem_shared/parseManifest--vZf2FY1.mjs';
17
17
  export { REQUIRED_COMPATIBILITY_DATE, REQUIRED_FLAG, validateWranglerProject as validateWrangler, validateWranglerConfig } from '@lunora/config';
18
18
  export { buildRegistryIndex } from './packem_shared/buildRegistryIndex-BcYe607_.mjs';
19
- export { r as runAddCommand, a as runBuildIndexCommand, b as runRegistryViewCommand } from './packem_shared/commands-SUPdjsu5.mjs';
19
+ export { r as runAddCommand, a as runBuildIndexCommand, b as runRegistryViewCommand } from './packem_shared/commands-B9nASOYd.mjs';
@@ -1,9 +1,10 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { findWranglerFile, promptSelect } from '@lunora/config';
2
+ import { findWranglerFile } from '@lunora/config';
3
3
  import { join } from '@visulima/path';
4
4
  import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
5
+ import { t as tuiSelect } from '../packem_shared/tui-prompts-Bm15GPJA.mjs';
5
6
  import { n as normalizeFeature, E as EMAIL_ITEM, D as DEFAULT_AUTH_ITEM, p as promptAuthProvider, A as AUTH_PROVIDER_OPTIONS } from '../packem_shared/features-ocSSpZtS.mjs';
6
- import { r as runAddCommand } from '../packem_shared/commands-SUPdjsu5.mjs';
7
+ import { r as runAddCommand } from '../packem_shared/commands-B9nASOYd.mjs';
7
8
 
8
9
  const providerToItem = (provider) => {
9
10
  const value = provider.trim().toLowerCase();
@@ -24,7 +25,7 @@ const resolveAuthItem = async (options) => {
24
25
  if (options.yes === true) {
25
26
  return DEFAULT_AUTH_ITEM;
26
27
  }
27
- const select = options.promptSelect ?? ((message, choices, settings) => promptSelect(message, choices, settings));
28
+ const select = options.promptSelect ?? ((message, choices, settings) => tuiSelect(message, choices, settings));
28
29
  return promptAuthProvider(select);
29
30
  };
30
31
  const resolveFeatureItems = async (feature, options) => {
@@ -1,5 +1,5 @@
1
1
  import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
2
- import { r as runAddCommand, b as runRegistryViewCommand, a as runBuildIndexCommand } from '../packem_shared/commands-SUPdjsu5.mjs';
2
+ import { r as runAddCommand, b as runRegistryViewCommand, a as runBuildIndexCommand } from '../packem_shared/commands-B9nASOYd.mjs';
3
3
 
4
4
  const execute = defineHandler(({ argument, cwd, logger, options }) => {
5
5
  const subcommand = argument[0];
@@ -2,12 +2,12 @@ import { existsSync } from 'node:fs';
2
2
  import { mkdtemp, writeFile, rm } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { discoverSchema, schemaFromIr } from '@lunora/codegen';
5
- import { promptYesNo } from '@lunora/config';
6
5
  import { seedPlan } from '@lunora/seed';
7
6
  import { join } from '@visulima/path';
8
7
  import { Project } from 'ts-morph';
9
8
  import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
10
9
  import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
10
+ import { a as tuiConfirm } from '../packem_shared/tui-prompts-Bm15GPJA.mjs';
11
11
  import { runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs';
12
12
  import { runResetCommand } from './runResetCommand.mjs';
13
13
 
@@ -88,10 +88,8 @@ const confirmRemoteSeedTarget = async (options, generated) => {
88
88
  options.logger.error("seed: refusing to insert into a non-local target without confirmation — re-run with --yes");
89
89
  return seedFailure(1);
90
90
  }
91
- const confirmer = options.confirm ?? promptYesNo;
92
- const confirmed = await confirmer(
93
- `This will insert ${String(generated)} generated row(s) into ${options.url ?? "the production worker"}. Continue? [y/N] `
94
- );
91
+ const confirmer = options.confirm ?? tuiConfirm;
92
+ const confirmed = await confirmer(`This will insert ${String(generated)} generated row(s) into ${options.url ?? "the production worker"}. Continue?`);
95
93
  if (!confirmed) {
96
94
  options.logger.info("seed: aborted");
97
95
  return seedFailure(1);
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { detectAgentRules, inferLunoraBindings, packageNamesFromBindings, ensureDevVarsExample, ensureDevVariables, createConfirm, isInteractive, DEV_VARS_FILE, DEV_VARS_EXAMPLE_FILE, claimAgentRulesHint, AGENT_RULES_HINT, materializeRemoteWranglerConfig, formatLunoraEvent, resolveRemoteEnabled, readProjectRemotePreference } from '@lunora/config';
2
+ import { detectAgentRules, inferLunoraBindings, packageNamesFromBindings, ensureDevVarsExample, ensureDevVariables, isInteractive, DEV_VARS_FILE, DEV_VARS_EXAMPLE_FILE, claimAgentRulesHint, AGENT_RULES_HINT, materializeRemoteWranglerConfig, formatLunoraEvent, resolveRemoteEnabled, readProjectRemotePreference } from '@lunora/config';
3
3
  import { p as parseApiSpec } from '../packem_shared/api-spec-CtA6ilu4.mjs';
4
4
  import { existsSync, watch, readFileSync } from 'node:fs';
5
5
  import { join } from 'node:path';
@@ -9,6 +9,7 @@ import { dirname, join as join$1 } from '@visulima/path';
9
9
  import { createServer, request } from 'node:http';
10
10
  import { connect } from 'node:net';
11
11
  import { loadStudioAssets, studioAssetsStamp, renderStudioHtml, resolveAdminToken, SCHEMA_EDIT_ENDPOINT, POLICY_SCAFFOLD_ENDPOINT, SEED_ENDPOINT, serveJsonHandler, handleSchemaEditRequest, handlePolicyScaffoldRequest, handleSeedRequest } from '@lunora/config/studio-host';
12
+ import { c as createTuiConfirm } from '../packem_shared/tui-prompts-Bm15GPJA.mjs';
12
13
 
13
14
  const DEFAULT_DEBOUNCE_MS = 100;
14
15
  const PATH_SEGMENT_SEPARATOR = /[/\\]/u;
@@ -441,7 +442,7 @@ const offerDevVariablesScaffold = async (options, cwd) => {
441
442
  } catch {
442
443
  }
443
444
  const result = await (options.ensureEnv ?? ensureDevVariables)({
444
- confirm: createConfirm(),
445
+ confirm: createTuiConfirm(),
445
446
  cwd,
446
447
  info: (message) => {
447
448
  options.logger.info(message);
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, writeFileSync, readdirSync, mkdtempSync, rmSync, readFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
- import { detectFramework as detectFramework$1, isInteractive, promptSelect, promptMultiSelect } from '@lunora/config';
3
+ import { detectFramework as detectFramework$1, isInteractive } from '@lunora/config';
4
4
  import { walkSync } from '@visulima/fs';
5
5
  import { resolve, join as join$1, relative, dirname as dirname$1, basename } from '@visulima/path';
6
6
  import { downloadTemplate } from 'giget';
@@ -9,7 +9,8 @@ import { join, dirname } from 'node:path';
9
9
  import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
10
10
  import MagicString from 'magic-string';
11
11
  import { Project, SyntaxKind } from 'ts-morph';
12
- import { c as resolveSourceRef, d as resolveDistTag, r as runAddCommand } from '../packem_shared/commands-SUPdjsu5.mjs';
12
+ import { c as resolveSourceRef, d as resolveDistTag, r as runAddCommand } from '../packem_shared/commands-B9nASOYd.mjs';
13
+ import { b as tuiIntro, d as tuiOutro, t as tuiSelect, e as tuiMultiSelect, w as withTuiSpinner } from '../packem_shared/tui-prompts-Bm15GPJA.mjs';
13
14
  import { p as promptAuthProvider, E as EMAIL_ITEM } from '../packem_shared/features-ocSSpZtS.mjs';
14
15
 
15
16
  const GITHUB_CONTENT = `name: Deploy
@@ -570,31 +571,48 @@ const offerIsInteractive = (options) => options.yes !== true && (options.prompt
570
571
  const maybeOfferExtras = async (options, projectDirectory) => {
571
572
  const interactive = offerIsInteractive(options);
572
573
  const apply = async (names) => {
573
- const result = await runAddCommand({
574
- allowUnsafeSource: options.allowUnsafeSource,
575
- cwd: projectDirectory,
576
- from: options.registryFrom,
577
- logger: options.logger,
578
- names: [...names],
579
- ref: options.ref,
580
- source: options.registrySource,
581
- yes: true
582
- });
574
+ const applyLogger = isInteractive() ? {
575
+ error: (message) => {
576
+ options.logger.error(message);
577
+ },
578
+ info: () => {
579
+ },
580
+ success: () => {
581
+ },
582
+ warn: (message) => {
583
+ options.logger.warn(message);
584
+ }
585
+ } : options.logger;
586
+ const result = await withTuiSpinner(
587
+ `adding ${names.join(", ")}…`,
588
+ () => runAddCommand({
589
+ allowUnsafeSource: options.allowUnsafeSource,
590
+ cwd: projectDirectory,
591
+ from: options.registryFrom,
592
+ logger: applyLogger,
593
+ names: [...names],
594
+ ref: options.ref,
595
+ source: options.registrySource,
596
+ yes: true
597
+ })
598
+ );
583
599
  return result.code === 0;
584
600
  };
601
+ await tuiIntro("let's finish setting up your app");
585
602
  await offerRegistryExtras({
586
603
  apply,
587
604
  interactive,
588
605
  logger: options.logger,
589
- multiSelect: options.prompt?.multiSelect ?? ((message, choices, settings) => promptMultiSelect(message, choices, settings)),
590
- select: options.prompt?.select ?? ((message, choices, settings) => promptSelect(message, choices, settings))
606
+ multiSelect: options.prompt?.multiSelect ?? ((message, choices, settings) => tuiMultiSelect(message, choices, settings)),
607
+ select: options.prompt?.select ?? ((message, choices, settings) => tuiSelect(message, choices, settings))
591
608
  });
609
+ await tuiOutro("you're all set — run `pnpm dev` to start.");
592
610
  };
593
611
  const scaffoldNewProject = async (options, cwd) => {
594
612
  const name = options.name ?? "lunora-app";
595
- const templateType = options.templateType ?? "vite";
613
+ const templateType = options.templateType ?? "vite-react";
596
614
  if (templateType === "next") {
597
- options.logger.warn('template "next" is not yet available — re-run with `-t vite` or `-t standalone`.');
615
+ options.logger.warn('template "next" is not yet available — re-run with `-t vite-react` or `-t standalone`.');
598
616
  return { code: 1, files: [], target: "" };
599
617
  }
600
618
  if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
@@ -631,7 +649,7 @@ const runInitCommand = async (options) => {
631
649
  }
632
650
  return result;
633
651
  };
634
- const isTemplate = (value) => value === "astro" || value === "next" || value === "nuxt" || value === "standalone" || value === "sveltekit" || value === "tanstack-start-react" || value === "tanstack-start-solid" || value === "vite";
652
+ const isTemplate = (value) => value === "astro" || value === "next" || value === "nuxt" || value === "standalone" || value === "sveltekit" || value === "tanstack-start-react" || value === "tanstack-start-solid" || value === "vite-react";
635
653
  const resolveCiProvider = (raw, logger) => {
636
654
  if (raw === void 0) {
637
655
  return void 0;
@@ -643,8 +661,8 @@ const resolveCiProvider = (raw, logger) => {
643
661
  return void 0;
644
662
  };
645
663
  const execute = defineHandler(({ argument, cwd, logger, options }) => {
646
- const templateRaw = options.template ?? "vite";
647
- const template = isTemplate(templateRaw) ? templateRaw : "vite";
664
+ const templateRaw = options.template ?? "vite-react";
665
+ const template = isTemplate(templateRaw) ? templateRaw : "vite-react";
648
666
  return runInitCommand({
649
667
  allowUnsafeSource: options.allowUnsafeSource === true,
650
668
  cwd,
@@ -1,7 +1,7 @@
1
1
  import { existsSync, rmSync } from 'node:fs';
2
- import { promptYesNo } from '@lunora/config';
3
2
  import { join } from '@visulima/path';
4
3
  import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
4
+ import { a as tuiConfirm } from '../packem_shared/tui-prompts-Bm15GPJA.mjs';
5
5
 
6
6
  const runResetCommand = async (options) => {
7
7
  const cwd = options.cwd ?? process.cwd();
@@ -15,8 +15,8 @@ const runResetCommand = async (options) => {
15
15
  options.logger.error("reset: stdin is not a TTY — re-run with --yes to confirm deleting .wrangler/state");
16
16
  return { code: 1, removed: [] };
17
17
  }
18
- const confirmer = options.confirm ?? promptYesNo;
19
- const confirmed = await confirmer("This will delete .wrangler/state. Continue? [y/N] ");
18
+ const confirmer = options.confirm ?? tuiConfirm;
19
+ const confirmed = await confirmer("This will delete .wrangler/state. Continue?");
20
20
  if (!confirmed) {
21
21
  options.logger.info("reset: aborted");
22
22
  return { code: 1, removed: [] };
@@ -361,8 +361,8 @@ const initCommand = {
361
361
  options: [
362
362
  {
363
363
  alias: "t",
364
- defaultValue: "vite",
365
- description: "Template to scaffold (vite | standalone | astro | nuxt | sveltekit | tanstack-start-react | tanstack-start-solid)",
364
+ defaultValue: "vite-react",
365
+ description: "Template to scaffold (vite-react | standalone | astro | nuxt | sveltekit | tanstack-start-react | tanstack-start-solid)",
366
366
  name: "template",
367
367
  type: String
368
368
  },
@@ -1,8 +1,9 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
2
2
  import { dirname, join } from '@visulima/path';
3
- import { DEV_VARS_FILE, parseDevVariableEntries, promptYesNo } from '@lunora/config';
3
+ import { DEV_VARS_FILE, parseDevVariableEntries } from '@lunora/config';
4
4
  import { modify, applyEdits, parse } from 'jsonc-parser';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { a as tuiConfirm } from './tui-prompts-Bm15GPJA.mjs';
6
7
  import { collectCatalog, buildRegistryIndex } from './buildRegistryIndex-BcYe607_.mjs';
7
8
  import { insertSchemaExtension } from './insertSchemaExtension-BuzF6-t2.mjs';
8
9
  import { createHash } from 'node:crypto';
@@ -272,8 +273,8 @@ const confirmDepMutation = async (items, options) => {
272
273
  options.logger.error(`add: stdin is not a TTY and the requested items ${reasonText} — re-run with --yes to confirm`);
273
274
  return false;
274
275
  }
275
- const confirmer = options.confirm ?? promptYesNo;
276
- const confirmed = await confirmer(`The requested items ${reasonText}. Continue? [y/N] `);
276
+ const confirmer = options.confirm ?? tuiConfirm;
277
+ const confirmed = await confirmer(`The requested items ${reasonText}. Continue?`);
277
278
  if (!confirmed) {
278
279
  options.logger.info("add: aborted");
279
280
  }
@@ -1,4 +1,4 @@
1
1
  import 'node:fs';
2
2
  import '@visulima/path';
3
- export { r as runAddCommand, a as runBuildIndexCommand, e as runListCommand, b as runRegistryViewCommand } from './commands-SUPdjsu5.mjs';
3
+ export { r as runAddCommand, a as runBuildIndexCommand, e as runListCommand, b as runRegistryViewCommand } from './commands-B9nASOYd.mjs';
4
4
  import './buildRegistryIndex-BcYe607_.mjs';
@@ -0,0 +1,229 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { isInteractive } from '@lunora/config';
3
+ import { render } from '@visulima/tui';
4
+ import { Box } from '@visulima/tui/components/box';
5
+ import { CommandPalette } from '@visulima/tui/components/command-palette';
6
+ import { ConfirmInput } from '@visulima/tui/components/confirm-input';
7
+ import { MultiSelect } from '@visulima/tui/components/multi-select';
8
+ import { SelectInput } from '@visulima/tui/components/select-input';
9
+ import { Spinner } from '@visulima/tui/components/spinner';
10
+ import { Text } from '@visulima/tui/components/text';
11
+ import { useApp } from '@visulima/tui/hooks/use-app';
12
+ import { useInput } from '@visulima/tui/hooks/use-input';
13
+ import { useEffect } from 'react';
14
+
15
+ const ACCENT = "#a855f7";
16
+ const SCROLL_LIMIT = 10;
17
+ const FILTER_THRESHOLD = 8;
18
+ const PromptFrame = ({ children }) => jsx(Box, { borderColor: ACCENT, borderStyle: "round", flexDirection: "column", paddingX: 1, children });
19
+ const runInkPrompt = async (build, fallback) => {
20
+ let result = fallback;
21
+ const instance = render(
22
+ build((value) => {
23
+ result = value;
24
+ }),
25
+ { exitOnCtrlC: true }
26
+ );
27
+ try {
28
+ await instance.waitUntilExit();
29
+ } catch {
30
+ } finally {
31
+ instance.unmount();
32
+ }
33
+ return result;
34
+ };
35
+ const itemLabel = (option) => option.description === void 0 ? option.label : `${option.label} — ${option.description}`;
36
+ const useCommit = (finish) => {
37
+ const { exit } = useApp();
38
+ return (result) => {
39
+ finish(result);
40
+ exit();
41
+ };
42
+ };
43
+ const useEscapeToExit = () => {
44
+ const { exit } = useApp();
45
+ useInput((_input, key) => {
46
+ if (key.escape) {
47
+ exit();
48
+ }
49
+ });
50
+ };
51
+ const SelectView = ({ finish, initialIndex, message, options }) => {
52
+ const commit = useCommit(finish);
53
+ useEscapeToExit();
54
+ return jsxs(PromptFrame, { children: [
55
+ jsx(Text, { bold: true, children: message }),
56
+ jsx(
57
+ SelectInput,
58
+ {
59
+ accentColor: ACCENT,
60
+ initialIndex,
61
+ items: options.map((option) => {
62
+ return { key: option.value, label: itemLabel(option), value: option.value };
63
+ }),
64
+ limit: SCROLL_LIMIT,
65
+ onSelect: (item) => {
66
+ commit(item.value);
67
+ }
68
+ }
69
+ )
70
+ ] });
71
+ };
72
+ const PaletteView = ({ finish, message, options }) => {
73
+ const { exit } = useApp();
74
+ const commit = useCommit(finish);
75
+ return jsxs(Box, { flexDirection: "column", children: [
76
+ jsx(Text, { bold: true, children: message }),
77
+ jsx(
78
+ CommandPalette,
79
+ {
80
+ accentColor: ACCENT,
81
+ autoFocus: true,
82
+ commands: options.map((option) => {
83
+ return { description: option.description, id: option.value, label: option.label };
84
+ }),
85
+ limit: SCROLL_LIMIT,
86
+ onCancel: () => {
87
+ exit();
88
+ },
89
+ onSelect: (id) => {
90
+ commit(id);
91
+ },
92
+ placeholder: "Type to filter…"
93
+ }
94
+ )
95
+ ] });
96
+ };
97
+ const tuiSelect = async (message, options, settings) => {
98
+ if (!isInteractive() || options.length === 0) {
99
+ return settings?.default;
100
+ }
101
+ if (options.length > FILTER_THRESHOLD) {
102
+ return runInkPrompt((finish) => jsx(PaletteView, { finish, message, options }), settings?.default);
103
+ }
104
+ const defaultIndex = settings?.default === void 0 ? -1 : options.findIndex((option) => option.value === settings.default);
105
+ return runInkPrompt(
106
+ (finish) => jsx(SelectView, { finish, initialIndex: defaultIndex >= 0 ? defaultIndex : void 0, message, options }),
107
+ settings?.default
108
+ );
109
+ };
110
+ const MultiSelectView = ({ defaults, finish, message, options }) => {
111
+ const commit = useCommit(finish);
112
+ useEscapeToExit();
113
+ return jsxs(PromptFrame, { children: [
114
+ jsx(Text, { bold: true, children: message }),
115
+ jsx(Text, { dimColor: true, children: "space toggles · enter confirms · esc cancels" }),
116
+ jsx(
117
+ MultiSelect,
118
+ {
119
+ accentColor: ACCENT,
120
+ defaultValue: [...defaults],
121
+ limit: SCROLL_LIMIT,
122
+ onSubmit: (values) => {
123
+ const chosen = new Set(values);
124
+ commit(options.filter((option) => chosen.has(option.value)).map((option) => option.value));
125
+ },
126
+ options: options.map((option) => {
127
+ return { key: option.value, label: itemLabel(option), value: option.value };
128
+ })
129
+ }
130
+ )
131
+ ] });
132
+ };
133
+ const tuiMultiSelect = async (message, options, settings) => {
134
+ const defaults = settings?.defaults ?? [];
135
+ if (!isInteractive() || options.length === 0) {
136
+ return [...defaults];
137
+ }
138
+ return runInkPrompt((finish) => jsx(MultiSelectView, { defaults, finish, message, options }), [...defaults]);
139
+ };
140
+ const ConfirmView = ({ defaultYes, finish, message }) => {
141
+ const commit = useCommit(finish);
142
+ return jsx(PromptFrame, { children: jsxs(Box, { children: [
143
+ jsxs(Text, { bold: true, children: [
144
+ message,
145
+ " "
146
+ ] }),
147
+ jsx(
148
+ ConfirmInput,
149
+ {
150
+ defaultChoice: defaultYes ? "confirm" : "cancel",
151
+ onCancel: () => {
152
+ commit(false);
153
+ },
154
+ onConfirm: () => {
155
+ commit(true);
156
+ }
157
+ }
158
+ )
159
+ ] }) });
160
+ };
161
+ const tuiConfirm = async (message, options) => {
162
+ const defaultYes = options?.defaultYes === true;
163
+ if (!isInteractive()) {
164
+ return defaultYes;
165
+ }
166
+ return runInkPrompt((finish) => jsx(ConfirmView, { defaultYes, finish, message }), false);
167
+ };
168
+ const createTuiConfirm = () => isInteractive() ? (message) => tuiConfirm(message, { defaultYes: true }) : () => Promise.resolve(false);
169
+ const SelfExit = ({ children }) => {
170
+ const { exit } = useApp();
171
+ useEffect(() => {
172
+ exit();
173
+ }, [exit]);
174
+ return children;
175
+ };
176
+ const printFrame = async (element) => {
177
+ const instance = render(jsx(SelfExit, { children: element }));
178
+ try {
179
+ await instance.waitUntilExit();
180
+ } catch {
181
+ } finally {
182
+ instance.unmount();
183
+ }
184
+ };
185
+ const tuiIntro = async (message) => {
186
+ if (!isInteractive()) {
187
+ return;
188
+ }
189
+ await printFrame(
190
+ jsxs(Box, { borderColor: ACCENT, borderStyle: "round", paddingX: 1, children: [
191
+ jsxs(Text, { bold: true, color: ACCENT, children: [
192
+ "◆ Lunora",
193
+ " "
194
+ ] }),
195
+ jsx(Text, { children: message })
196
+ ] })
197
+ );
198
+ };
199
+ const tuiOutro = async (message) => {
200
+ if (!isInteractive()) {
201
+ return;
202
+ }
203
+ await printFrame(
204
+ jsxs(Box, { paddingX: 1, children: [
205
+ jsx(Text, { color: "green", children: "✔ " }),
206
+ jsx(Text, { children: message })
207
+ ] })
208
+ );
209
+ };
210
+ const SpinnerView = ({ label }) => jsxs(Box, { children: [
211
+ jsx(Text, { color: ACCENT, children: jsx(Spinner, { type: "dots" }) }),
212
+ jsxs(Text, { children: [
213
+ " ",
214
+ label
215
+ ] })
216
+ ] });
217
+ const withTuiSpinner = async (label, task) => {
218
+ if (!isInteractive()) {
219
+ return task();
220
+ }
221
+ const instance = render(jsx(SpinnerView, { label }));
222
+ try {
223
+ return await task();
224
+ } finally {
225
+ instance.unmount();
226
+ }
227
+ };
228
+
229
+ export { tuiConfirm as a, tuiIntro as b, createTuiConfirm as c, tuiOutro as d, tuiMultiSelect as e, tuiSelect as t, withTuiSpinner as w };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/cli",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-alpha.5",
4
4
  "description": "The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -65,9 +65,12 @@
65
65
  "@visulima/pail": "4.0.0-alpha.22",
66
66
  "@visulima/path": "3.0.0-alpha.13",
67
67
  "@visulima/spinner": "1.0.0-alpha.4",
68
+ "@visulima/tui": "1.0.0-alpha.26",
68
69
  "giget": "3.3.0",
69
70
  "jsonc-parser": "^3.3.1",
70
71
  "magic-string": "^0.30.21",
72
+ "react": "^19.2.7",
73
+ "react-reconciler": "^0.33.0",
71
74
  "ts-morph": "^28.0.0"
72
75
  },
73
76
  "engines": {