@pablozaiden/terminatui 0.1.2 → 0.3.0-beta-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 (175) hide show
  1. package/AGENTS.md +43 -0
  2. package/CLAUDE.md +1 -0
  3. package/README.md +64 -43
  4. package/bun.lock +85 -0
  5. package/examples/tui-app/commands/config/app/get.ts +62 -0
  6. package/examples/tui-app/commands/config/app/index.ts +23 -0
  7. package/examples/tui-app/commands/config/app/set.ts +96 -0
  8. package/examples/tui-app/commands/config/index.ts +28 -0
  9. package/examples/tui-app/commands/config/user/get.ts +61 -0
  10. package/examples/tui-app/commands/config/user/index.ts +23 -0
  11. package/examples/tui-app/commands/config/user/set.ts +57 -0
  12. package/examples/tui-app/commands/greet.ts +14 -11
  13. package/examples/tui-app/commands/math.ts +6 -9
  14. package/examples/tui-app/commands/status.ts +24 -13
  15. package/examples/tui-app/index.ts +7 -3
  16. package/guides/01-hello-world.md +7 -2
  17. package/guides/02-adding-options.md +2 -2
  18. package/guides/03-multiple-commands.md +6 -8
  19. package/guides/04-subcommands.md +8 -8
  20. package/guides/05-interactive-tui.md +45 -30
  21. package/guides/06-config-validation.md +4 -12
  22. package/guides/07-async-cancellation.md +15 -69
  23. package/guides/08-complete-application.md +13 -179
  24. package/guides/README.md +7 -3
  25. package/package.json +4 -8
  26. package/src/__tests__/application.test.ts +87 -68
  27. package/src/__tests__/buildCliCommand.test.ts +99 -119
  28. package/src/__tests__/builtins.test.ts +27 -75
  29. package/src/__tests__/command.test.ts +100 -131
  30. package/src/__tests__/context.test.ts +1 -26
  31. package/src/__tests__/helpCore.test.ts +227 -0
  32. package/src/__tests__/parser.test.ts +98 -244
  33. package/src/__tests__/registry.test.ts +33 -160
  34. package/src/__tests__/schemaToFields.test.ts +75 -158
  35. package/src/builtins/help.ts +19 -4
  36. package/src/builtins/settings.ts +18 -32
  37. package/src/builtins/version.ts +4 -4
  38. package/src/cli/output/colors.ts +1 -1
  39. package/src/cli/parser.ts +26 -95
  40. package/src/core/application.ts +192 -110
  41. package/src/core/command.ts +26 -9
  42. package/src/core/context.ts +31 -20
  43. package/src/core/help.ts +24 -18
  44. package/src/core/knownCommands.ts +13 -0
  45. package/src/core/logger.ts +39 -42
  46. package/src/core/registry.ts +5 -12
  47. package/src/tui/TuiApplication.tsx +63 -120
  48. package/src/tui/TuiRoot.tsx +135 -0
  49. package/src/tui/adapters/factory.ts +19 -0
  50. package/src/tui/adapters/ink/InkRenderer.tsx +135 -0
  51. package/src/tui/adapters/ink/components/Button.tsx +12 -0
  52. package/src/tui/adapters/ink/components/Code.tsx +6 -0
  53. package/src/tui/adapters/ink/components/CodeHighlight.tsx +6 -0
  54. package/src/tui/adapters/ink/components/Container.tsx +5 -0
  55. package/src/tui/adapters/ink/components/Field.tsx +12 -0
  56. package/src/tui/adapters/ink/components/Label.tsx +24 -0
  57. package/src/tui/adapters/ink/components/MenuButton.tsx +12 -0
  58. package/src/tui/adapters/ink/components/MenuItem.tsx +17 -0
  59. package/src/tui/adapters/ink/components/Overlay.tsx +5 -0
  60. package/src/tui/adapters/ink/components/Panel.tsx +15 -0
  61. package/src/tui/adapters/ink/components/ScrollView.tsx +5 -0
  62. package/src/tui/adapters/ink/components/Select.tsx +44 -0
  63. package/src/tui/adapters/ink/components/Spacer.tsx +15 -0
  64. package/src/tui/adapters/ink/components/Spinner.tsx +5 -0
  65. package/src/tui/adapters/ink/components/TextInput.tsx +22 -0
  66. package/src/tui/adapters/ink/components/Value.tsx +7 -0
  67. package/src/tui/adapters/ink/keyboard.ts +97 -0
  68. package/src/tui/adapters/ink/utils.ts +16 -0
  69. package/src/tui/adapters/opentui/OpenTuiRenderer.tsx +115 -0
  70. package/src/tui/adapters/opentui/components/Button.tsx +13 -0
  71. package/src/tui/adapters/opentui/components/Code.tsx +12 -0
  72. package/src/tui/adapters/opentui/components/CodeHighlight.tsx +24 -0
  73. package/src/tui/adapters/opentui/components/Container.tsx +56 -0
  74. package/src/tui/adapters/opentui/components/Field.tsx +18 -0
  75. package/src/tui/adapters/opentui/components/Label.tsx +15 -0
  76. package/src/tui/adapters/opentui/components/MenuButton.tsx +14 -0
  77. package/src/tui/adapters/opentui/components/MenuItem.tsx +29 -0
  78. package/src/tui/adapters/opentui/components/Overlay.tsx +21 -0
  79. package/src/tui/adapters/opentui/components/Panel.tsx +78 -0
  80. package/src/tui/adapters/opentui/components/ScrollView.tsx +85 -0
  81. package/src/tui/adapters/opentui/components/Select.tsx +59 -0
  82. package/src/tui/adapters/opentui/components/Spacer.tsx +5 -0
  83. package/src/tui/adapters/opentui/components/Spinner.tsx +12 -0
  84. package/src/tui/adapters/opentui/components/TextInput.tsx +13 -0
  85. package/src/tui/adapters/opentui/components/Value.tsx +13 -0
  86. package/src/tui/{hooks → adapters/opentui/hooks}/useSpinner.ts +2 -11
  87. package/src/tui/adapters/opentui/keyboard.ts +61 -0
  88. package/src/tui/adapters/types.ts +70 -0
  89. package/src/tui/components/ActionButton.tsx +0 -36
  90. package/src/tui/components/CommandSelector.tsx +52 -92
  91. package/src/tui/components/ConfigForm.tsx +68 -42
  92. package/src/tui/components/FieldRow.tsx +0 -30
  93. package/src/tui/components/Header.tsx +14 -13
  94. package/src/tui/components/JsonHighlight.tsx +10 -17
  95. package/src/tui/components/ModalBase.tsx +38 -0
  96. package/src/tui/components/ResultsPanel.tsx +27 -36
  97. package/src/tui/components/StatusBar.tsx +24 -39
  98. package/src/tui/components/logColors.ts +12 -0
  99. package/src/tui/context/ClipboardContext.tsx +87 -0
  100. package/src/tui/context/ExecutorContext.tsx +139 -0
  101. package/src/tui/context/KeyboardContext.tsx +85 -71
  102. package/src/tui/context/LogsContext.tsx +35 -0
  103. package/src/tui/context/NavigationContext.tsx +194 -0
  104. package/src/tui/context/RendererContext.tsx +20 -0
  105. package/src/tui/context/TuiAppContext.tsx +58 -0
  106. package/src/tui/hooks/useActiveKeyHandler.ts +75 -0
  107. package/src/tui/hooks/useBackHandler.ts +34 -0
  108. package/src/tui/hooks/useClipboard.ts +40 -25
  109. package/src/tui/hooks/useClipboardProvider.ts +42 -0
  110. package/src/tui/hooks/useGlobalKeyHandler.ts +54 -0
  111. package/src/tui/modals/CliModal.tsx +82 -0
  112. package/src/tui/modals/EditorModal.tsx +207 -0
  113. package/src/tui/modals/LogsModal.tsx +98 -0
  114. package/src/tui/registry.ts +102 -0
  115. package/src/tui/screens/CommandSelectScreen.tsx +162 -0
  116. package/src/tui/screens/ConfigScreen.tsx +160 -0
  117. package/src/tui/screens/ErrorScreen.tsx +58 -0
  118. package/src/tui/screens/ResultsScreen.tsx +60 -0
  119. package/src/tui/screens/RunningScreen.tsx +72 -0
  120. package/src/tui/screens/ScreenBase.ts +6 -0
  121. package/src/tui/semantic/Button.tsx +7 -0
  122. package/src/tui/semantic/Code.tsx +7 -0
  123. package/src/tui/semantic/CodeHighlight.tsx +7 -0
  124. package/src/tui/semantic/Container.tsx +7 -0
  125. package/src/tui/semantic/Field.tsx +7 -0
  126. package/src/tui/semantic/Label.tsx +7 -0
  127. package/src/tui/semantic/MenuButton.tsx +7 -0
  128. package/src/tui/semantic/MenuItem.tsx +7 -0
  129. package/src/tui/semantic/Overlay.tsx +7 -0
  130. package/src/tui/semantic/Panel.tsx +7 -0
  131. package/src/tui/semantic/ScrollView.tsx +9 -0
  132. package/src/tui/semantic/Select.tsx +7 -0
  133. package/src/tui/semantic/Spacer.tsx +7 -0
  134. package/src/tui/semantic/Spinner.tsx +7 -0
  135. package/src/tui/semantic/TextInput.tsx +7 -0
  136. package/src/tui/semantic/Value.tsx +7 -0
  137. package/src/tui/semantic/types.ts +195 -0
  138. package/src/tui/theme.ts +25 -14
  139. package/src/tui/utils/buildCliCommand.ts +1 -0
  140. package/src/tui/utils/getEnumKeys.ts +3 -0
  141. package/src/tui/utils/parameterPersistence.ts +1 -0
  142. package/src/types/command.ts +0 -60
  143. package/examples/tui-app/commands/index.ts +0 -3
  144. package/src/__tests__/colors.test.ts +0 -127
  145. package/src/__tests__/commandClass.test.ts +0 -130
  146. package/src/__tests__/help.test.ts +0 -412
  147. package/src/__tests__/registryNew.test.ts +0 -160
  148. package/src/__tests__/table.test.ts +0 -146
  149. package/src/__tests__/tui.test.ts +0 -26
  150. package/src/builtins/index.ts +0 -4
  151. package/src/cli/help.ts +0 -174
  152. package/src/cli/index.ts +0 -3
  153. package/src/cli/output/index.ts +0 -2
  154. package/src/cli/output/table.ts +0 -141
  155. package/src/commands/help.ts +0 -50
  156. package/src/commands/index.ts +0 -1
  157. package/src/components/index.ts +0 -147
  158. package/src/core/index.ts +0 -15
  159. package/src/hooks/index.ts +0 -131
  160. package/src/index.ts +0 -137
  161. package/src/registry/commandRegistry.ts +0 -77
  162. package/src/registry/index.ts +0 -1
  163. package/src/tui/TuiApp.tsx +0 -582
  164. package/src/tui/app.ts +0 -29
  165. package/src/tui/components/CliModal.tsx +0 -81
  166. package/src/tui/components/EditorModal.tsx +0 -177
  167. package/src/tui/components/LogsPanel.tsx +0 -86
  168. package/src/tui/components/index.ts +0 -13
  169. package/src/tui/context/index.ts +0 -7
  170. package/src/tui/hooks/index.ts +0 -35
  171. package/src/tui/hooks/useKeyboardHandler.ts +0 -91
  172. package/src/tui/hooks/useLogStream.ts +0 -96
  173. package/src/tui/index.ts +0 -65
  174. package/src/tui/utils/index.ts +0 -13
  175. package/src/types/index.ts +0 -1
@@ -0,0 +1,96 @@
1
+ import { Command, type CommandExecutionContext, type CommandResult } from "../../../../../src/core/command";
2
+ import { AppContext } from "../../../../../src/core/context";
3
+ import type { OptionSchema, OptionValues } from "../../../../../src/types/command";
4
+
5
+ const options = {
6
+ key: {
7
+ type: "string",
8
+ description: "Application configuration key to set",
9
+ required: true,
10
+ label: "Key",
11
+ order: 1,
12
+ group: "Required",
13
+ placeholder: "e.g., port, debug, logLevel",
14
+ },
15
+ value: {
16
+ type: "string",
17
+ description: "Value to set",
18
+ required: true,
19
+ label: "Value",
20
+ order: 2,
21
+ group: "Required",
22
+ placeholder: "Enter value...",
23
+ },
24
+ type: {
25
+ type: "string",
26
+ description: "Value type for parsing",
27
+ enum: ["string", "number", "boolean"] as const,
28
+ default: "string",
29
+ label: "Value Type",
30
+ order: 3,
31
+ group: "Options",
32
+ },
33
+ } as const satisfies OptionSchema;
34
+
35
+ export class AppSetCommand extends Command<typeof options> {
36
+ readonly name = "set";
37
+ override displayName = "Set App Config";
38
+ readonly description = "Set an application configuration value";
39
+ readonly options = options;
40
+
41
+ override readonly actionLabel = "Set Value";
42
+
43
+ override readonly examples = [
44
+ { command: "config app set --key port --value 8080 --type number", description: "Set app port" },
45
+ { command: "config app set --key debug --value false --type boolean", description: "Disable debug" },
46
+ ];
47
+
48
+ override async execute(opts: OptionValues<typeof options>, execCtx: CommandExecutionContext): Promise<CommandResult> {
49
+ let parsedValue: string | number | boolean = opts.value;
50
+
51
+ // Parse value based on type
52
+ if (opts.type === "number") {
53
+ parsedValue = Number(opts.value);
54
+ if (isNaN(parsedValue)) {
55
+ AppContext.current.logger.error(`Invalid number value: "${opts.value}"`);
56
+ return {
57
+ success: false,
58
+ message: `Invalid number: "${opts.value}"`,
59
+ };
60
+ }
61
+ } else if (opts.type === "boolean") {
62
+ parsedValue = opts.value.toLowerCase() === "true";
63
+ }
64
+
65
+ AppContext.current.logger.info(`Setting app.${opts.key} = ${JSON.stringify(parsedValue)}`);
66
+
67
+
68
+ // Simulate setting the value on the 5th iteration
69
+ await new Promise(resolve => {
70
+ let count = 0;
71
+ let interval = setInterval(() => {
72
+ count++;
73
+ AppContext.current.logger.info(`Applying configuration... (${count}/5)`);
74
+ if (count >= 5 || execCtx.signal.aborted) {
75
+ if (count < 5) {
76
+ AppContext.current.logger.warn("Configuration update aborted");
77
+ }
78
+ clearInterval(interval);
79
+ resolve(undefined);
80
+ }
81
+ }, 1000);
82
+ });
83
+
84
+ AppContext.current.logger.info(`Successfully updated application configuration`);
85
+ return {
86
+ success: true,
87
+ data: { key: opts.key, value: parsedValue, type: opts.type },
88
+ };
89
+ }
90
+
91
+ override renderResult(result: CommandResult): string {
92
+ if (!result.success) return result.message || "Error";
93
+ const data = result.data as { key: string; value: string | number | boolean; type: string };
94
+ return `✓ Set app.${data.key} = ${JSON.stringify(data.value)} (${data.type})`;
95
+ }
96
+ }
@@ -0,0 +1,28 @@
1
+ import { UserConfigCommand } from "./user/index.ts";
2
+ import { AppConfigCommand } from "./app/index.ts";
3
+ import { Command, type CommandResult } from "../../../../src/core/command.ts";
4
+
5
+ export class ConfigCommand extends Command {
6
+ readonly name = "config";
7
+ override displayName = "Config";
8
+ readonly description = "Manage configuration (user and app settings)";
9
+ readonly options = {};
10
+
11
+ override readonly subCommands = [
12
+ new UserConfigCommand(),
13
+ new AppConfigCommand(),
14
+ ];
15
+
16
+ override readonly examples = [
17
+ { command: "config user get --key name", description: "Get user name" },
18
+ { command: "config user set --key theme --value dark", description: "Set user theme" },
19
+ { command: "config app get --key port", description: "Get app port" },
20
+ { command: "config app set --key debug --value true --type boolean", description: "Enable debug mode" },
21
+ ];
22
+
23
+ override execute(): CommandResult {
24
+ console.log("Use 'config <category> <command>' to manage settings.");
25
+ console.log("Categories: user, app");
26
+ return { success: true };
27
+ }
28
+ }
@@ -0,0 +1,61 @@
1
+ import { Command, type CommandResult } from "../../../../../src/core/command";
2
+ import { AppContext } from "../../../../../src/core/context";
3
+ import type { OptionSchema, OptionValues } from "../../../../../src/types/command";
4
+
5
+ const options = {
6
+ key: {
7
+ type: "string",
8
+ description: "Configuration key to get",
9
+ required: true,
10
+ label: "Key",
11
+ order: 1,
12
+ group: "Required",
13
+ placeholder: "e.g., name, email, theme",
14
+ },
15
+ } as const satisfies OptionSchema;
16
+
17
+ export class UserGetCommand extends Command<typeof options> {
18
+ readonly name = "get";
19
+ override displayName = "Get User Config";
20
+ readonly description = "Get a user configuration value";
21
+ readonly options = options;
22
+
23
+ override readonly actionLabel = "Get Value";
24
+
25
+ override readonly examples = [
26
+ { command: "config user get --key name", description: "Get user name" },
27
+ { command: "config user get --key email", description: "Get user email" },
28
+ ];
29
+
30
+ override async execute(opts: OptionValues<typeof options>): Promise<CommandResult> {
31
+ // Simulated user config store
32
+ const userConfig: Record<string, string> = {
33
+ name: "John Doe",
34
+ email: "john@example.com",
35
+ theme: "dark",
36
+ language: "en",
37
+ };
38
+
39
+ const value = userConfig[opts.key];
40
+
41
+ if (value === undefined) {
42
+ AppContext.current.logger.warn(`Key "${opts.key}" not found in user configuration`);
43
+ return {
44
+ success: false,
45
+ message: `Key "${opts.key}" not found`,
46
+ };
47
+ }
48
+
49
+ AppContext.current.logger.info(`Retrieved user.${opts.key} = ${value}`);
50
+ return {
51
+ success: true,
52
+ data: { key: opts.key, value },
53
+ };
54
+ }
55
+
56
+ override renderResult(result: CommandResult): string {
57
+ if (!result.success) return result.message || "Error";
58
+ const data = result.data as { key: string; value: string };
59
+ return `user.${data.key} = "${data.value}"`;
60
+ }
61
+ }
@@ -0,0 +1,23 @@
1
+ import { Command, type CommandResult } from "../../../../../src/core/command.ts";
2
+ import { UserGetCommand } from "./get.ts";
3
+ import { UserSetCommand } from "./set.ts";
4
+
5
+ export class UserConfigCommand extends Command {
6
+ readonly name = "user";
7
+ override displayName = "User Settings";
8
+ readonly description = "Manage user configuration";
9
+ readonly options = {};
10
+
11
+ override readonly subCommands = [
12
+ new UserGetCommand(),
13
+ new UserSetCommand(),
14
+ ];
15
+
16
+ override execute(): CommandResult {
17
+ console.log("Use 'config user <command>' for user configuration.");
18
+ console.log("Available: get, set");
19
+ return { success: true };
20
+ }
21
+ }
22
+
23
+ export { UserGetCommand, UserSetCommand };
@@ -0,0 +1,57 @@
1
+ import { Command, type CommandResult } from "../../../../../src/core/command";
2
+ import { AppContext } from "../../../../../src/core/context";
3
+ import type { OptionSchema, OptionValues } from "../../../../../src/types/command";
4
+
5
+ const options = {
6
+ key: {
7
+ type: "string",
8
+ description: "Configuration key to set",
9
+ required: true,
10
+ label: "Key",
11
+ order: 1,
12
+ group: "Required",
13
+ placeholder: "e.g., name, email, theme",
14
+ },
15
+ value: {
16
+ type: "string",
17
+ description: "Value to set",
18
+ required: true,
19
+ label: "Value",
20
+ order: 2,
21
+ group: "Required",
22
+ placeholder: "Enter value...",
23
+ },
24
+ } as const satisfies OptionSchema;
25
+
26
+ export class UserSetCommand extends Command<typeof options> {
27
+ readonly name = "set";
28
+ override displayName = "Set User Config";
29
+ readonly description = "Set a user configuration value";
30
+ readonly options = options;
31
+
32
+ override readonly actionLabel = "Set Value";
33
+
34
+ override readonly examples = [
35
+ { command: "config user set --key name --value 'Jane Doe'", description: "Set user name" },
36
+ { command: "config user set --key theme --value light", description: "Set theme" },
37
+ ];
38
+
39
+ override async execute(opts: OptionValues<typeof options>): Promise<CommandResult> {
40
+ AppContext.current.logger.info(`Setting user.${opts.key} = "${opts.value}"`);
41
+
42
+ // Simulate setting the value
43
+ await new Promise(resolve => setTimeout(resolve, 300));
44
+
45
+ AppContext.current.logger.info(`Successfully updated user configuration`);
46
+ return {
47
+ success: true,
48
+ data: { key: opts.key, value: opts.value },
49
+ };
50
+ }
51
+
52
+ override renderResult(result: CommandResult): string {
53
+ if (!result.success) return result.message || "Error";
54
+ const data = result.data as { key: string; value: string };
55
+ return `✓ Set user.${data.key} = "${data.value}"`;
56
+ }
57
+ }
@@ -1,10 +1,8 @@
1
- import {
2
- Command,
3
- type AppContext,
4
- type OptionSchema,
5
- type OptionValues,
6
- type CommandResult
7
- } from "../../../src/index.ts";
1
+ import type { ReactNode } from "react";
2
+ import { Command, type CommandResult } from "../../../src/core/command";
3
+ import { AppContext } from "../../../src/core/context";
4
+ import type { OptionSchema, OptionValues } from "../../../src/types/command";
5
+ import { JsonHighlight } from "../../../src/tui/components/JsonHighlight.tsx";
8
6
 
9
7
  const greetOptions = {
10
8
  name: {
@@ -20,7 +18,7 @@ const greetOptions = {
20
18
  type: "boolean",
21
19
  description: "Use uppercase",
22
20
  alias: "l",
23
- default: false,
21
+ default: true,
24
22
  label: "Loud Mode",
25
23
  order: 2,
26
24
  group: "Options",
@@ -37,6 +35,7 @@ const greetOptions = {
37
35
 
38
36
  export class GreetCommand extends Command<typeof greetOptions> {
39
37
  readonly name = "greet";
38
+ override displayName = "Greet";
40
39
  readonly description = "Greet someone with a friendly message";
41
40
  readonly options = greetOptions;
42
41
 
@@ -47,16 +46,20 @@ export class GreetCommand extends Command<typeof greetOptions> {
47
46
  { command: "greet --name World --loud --times 3", description: "Loud greeting 3 times" },
48
47
  ];
49
48
 
50
- override async execute(ctx: AppContext, opts: OptionValues<typeof greetOptions>): Promise<CommandResult> {
49
+ override async execute(opts: OptionValues<typeof greetOptions>): Promise<CommandResult> {
51
50
  const greeting = this.createGreeting(opts);
52
- ctx.logger.info(greeting);
51
+ AppContext.current.logger.trace(greeting);
53
52
  return {
54
53
  success: true,
55
- data: { greeting, timestamp: new Date().toISOString() },
54
+ data: { greeting, timestamp: new Date().toISOString(), meta: { loud: opts.loud, times: opts.times } },
56
55
  message: greeting,
57
56
  };
58
57
  }
59
58
 
59
+ override renderResult(result: CommandResult): ReactNode {
60
+ return JsonHighlight({ value: result.data });
61
+ }
62
+
60
63
  override getClipboardContent(result: CommandResult): string | undefined {
61
64
  const data = result.data as { greeting?: string } | undefined;
62
65
  return data?.greeting;
@@ -1,10 +1,6 @@
1
- import {
2
- Command,
3
- type AppContext,
4
- type OptionSchema,
5
- type OptionValues,
6
- type CommandResult
7
- } from "../../../src/index.ts";
1
+ import { Command, type CommandResult } from "../../../src/core/command";
2
+ import { AppContext } from "../../../src/core/context";
3
+ import type { OptionSchema, OptionValues } from "../../../src/types/command";
8
4
 
9
5
  const mathOptions = {
10
6
  operation: {
@@ -44,15 +40,16 @@ const mathOptions = {
44
40
 
45
41
  export class MathCommand extends Command<typeof mathOptions> {
46
42
  readonly name = "math";
43
+ override displayName = "Math Operations";
47
44
  readonly description = "Perform basic math operations";
48
45
  readonly options = mathOptions;
49
46
 
50
47
  override readonly actionLabel = "Calculate";
51
48
 
52
- override async execute(ctx: AppContext, opts: OptionValues<typeof mathOptions>): Promise<CommandResult> {
49
+ override async execute(opts: OptionValues<typeof mathOptions>): Promise<CommandResult> {
53
50
  const result = this.calculate(opts);
54
51
  if (!result.success) {
55
- ctx.logger.error(result.message || "Calculation failed");
52
+ AppContext.current.logger.error(result.message || "Calculation failed");
56
53
  }
57
54
  return result;
58
55
  }
@@ -1,10 +1,6 @@
1
- import {
2
- Command,
3
- type AppContext,
4
- type OptionSchema,
5
- type OptionValues,
6
- type CommandResult
7
- } from "../../../src/index.ts";
1
+ import { Command, type CommandExecutionContext, type CommandResult } from "../../../src/core/command";
2
+ import { AppContext } from "../../../src/core/context";
3
+ import type { OptionSchema, OptionValues } from "../../../src/types/command";
8
4
 
9
5
  const statusOptions = {
10
6
  detailed: {
@@ -12,21 +8,23 @@ const statusOptions = {
12
8
  description: "Show detailed status",
13
9
  default: false,
14
10
  label: "Detailed",
11
+ alias: "d",
15
12
  order: 1,
16
13
  },
17
14
  } as const satisfies OptionSchema;
18
15
 
19
16
  export class StatusCommand extends Command<typeof statusOptions> {
20
17
  readonly name = "status";
18
+ override displayName = "Status";
21
19
  readonly description = "Show application status";
22
20
  readonly options = statusOptions;
23
21
 
24
22
  override readonly actionLabel = "Check Status";
25
23
  override readonly immediateExecution = true; // No required fields
26
24
 
27
- override async execute(ctx: AppContext, opts: OptionValues<typeof statusOptions>): Promise<CommandResult> {
28
- const result = await this.getStatus(opts);
29
- ctx.logger.info(result.message || "Status check complete");
25
+ override async execute(opts: OptionValues<typeof statusOptions>, execCtx : CommandExecutionContext): Promise<CommandResult> {
26
+ const result = await this.getStatus(opts, execCtx);
27
+ AppContext.current.logger.info(result.message || "Status check complete");
30
28
  return result;
31
29
  }
32
30
 
@@ -39,15 +37,28 @@ export class StatusCommand extends Command<typeof statusOptions> {
39
37
  ` Uptime: ${data.uptime}`,
40
38
  ` Memory: ${data.memory}`,
41
39
  ` Platform: ${data.platform}`,
42
- ` Node: ${data.version}`,
40
+ ` Bun: ${data.version}`,
43
41
  ].join("\n");
44
42
  }
45
43
 
46
- private async getStatus(opts: OptionValues<typeof statusOptions>): Promise<CommandResult> {
44
+ private async getStatus(opts: OptionValues<typeof statusOptions>, execCtx: CommandExecutionContext): Promise<CommandResult> {
47
45
  const detailed = opts.detailed as boolean;
48
46
 
49
47
  // Simulate some async work
50
- await new Promise((resolve) => setTimeout(resolve, 500));
48
+ await new Promise(resolve => {
49
+ let count = 0;
50
+ let interval = setInterval(() => {
51
+ count++;
52
+ AppContext.current.logger.info(`Applying configuration... (${count}/5)`);
53
+ if (count >= 5 || execCtx.signal.aborted) {
54
+ if (count < 5) {
55
+ AppContext.current.logger.warn("Status check aborted");
56
+ }
57
+ clearInterval(interval);
58
+ resolve(undefined);
59
+ }
60
+ }, 1000);
61
+ });
51
62
 
52
63
  const memMB = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
53
64
  const uptimeSec = Math.round(process.uptime());
@@ -12,8 +12,11 @@
12
12
  * bun examples/tui-app/index.ts greet --name "World" --loud
13
13
  */
14
14
 
15
- import { TuiApplication } from "../../src/index.ts";
16
- import { GreetCommand, MathCommand, StatusCommand } from "./commands/index.ts";
15
+ import { TuiApplication } from "../../src/tui/TuiApplication.tsx";
16
+ import { ConfigCommand } from "./commands/config/index.ts";
17
+ import { GreetCommand } from "./commands/greet.ts";
18
+ import { MathCommand } from "./commands/math.ts";
19
+ import { StatusCommand } from "./commands/status.ts";
17
20
 
18
21
  class ExampleApp extends TuiApplication {
19
22
  constructor() {
@@ -24,6 +27,7 @@ class ExampleApp extends TuiApplication {
24
27
  new GreetCommand(),
25
28
  new MathCommand(),
26
29
  new StatusCommand(),
30
+ new ConfigCommand(),
27
31
  ],
28
32
  enableTui: true,
29
33
  });
@@ -31,4 +35,4 @@ class ExampleApp extends TuiApplication {
31
35
  }
32
36
 
33
37
  // Run the app
34
- await new ExampleApp().run(Bun.argv.slice(2));
38
+ await new ExampleApp().run();
@@ -28,7 +28,12 @@ bun add @pablozaiden/terminatui
28
28
  Create `src/greet.ts`:
29
29
 
30
30
  ```typescript
31
- import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
31
+ import {
32
+ Command,
33
+ type OptionSchema,
34
+ type CommandResult,
35
+ type CommandExecutionContext,
36
+ } from "@pablozaiden/terminatui";
32
37
 
33
38
  const options = {
34
39
  name: {
@@ -43,7 +48,7 @@ export class GreetCommand extends Command<typeof options> {
43
48
  readonly description = "Greet someone";
44
49
  readonly options = options;
45
50
 
46
- execute(_ctx: AppContext, config: { name: string }): CommandResult {
51
+ execute(config: { name: string }, _execCtx: CommandExecutionContext): CommandResult {
47
52
  console.log(`Hello, ${config.name}!`);
48
53
  return { success: true };
49
54
  }
@@ -19,7 +19,7 @@ myapp greet --name Alice --loud
19
19
  Update `src/greet.ts`:
20
20
 
21
21
  ```typescript
22
- import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
22
+ import { Command, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
23
23
 
24
24
  const options = {
25
25
  name: {
@@ -40,7 +40,7 @@ export class GreetCommand extends Command<typeof options> {
40
40
  readonly description = "Greet someone";
41
41
  readonly options = options;
42
42
 
43
- execute(_ctx: AppContext, config: { name: string; loud: boolean }): CommandResult {
43
+ execute(config: { name: string; loud: boolean }): CommandResult {
44
44
  const message = `Hello, ${config.name}!`;
45
45
  console.log(config.loud ? message.toUpperCase() : message);
46
46
  return { success: true };
@@ -19,8 +19,7 @@ fileutil count --dir ./src --ext .ts
19
19
  Create `src/commands/list.ts`:
20
20
 
21
21
  ```typescript
22
- import { readdirSync } from "fs";
23
- import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
22
+ import { Command, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
24
23
 
25
24
  const options = {
26
25
  dir: {
@@ -36,9 +35,9 @@ export class ListCommand extends Command<typeof options> {
36
35
  readonly description = "List files in a directory";
37
36
  readonly options = options;
38
37
 
39
- execute(_ctx: AppContext, config: { dir: string }): CommandResult {
38
+ async execute(config: { dir: string }): Promise<CommandResult> {
40
39
  try {
41
- const files = readdirSync(config.dir);
40
+ const files = await Array.fromAsync(new Bun.Glob("*").scan({ cwd: config.dir, onlyFiles: false }));
42
41
  console.log(`Files in ${config.dir}:`);
43
42
  files.forEach((file) => console.log(` ${file}`));
44
43
  return { success: true, message: `Found ${files.length} files` };
@@ -54,8 +53,7 @@ export class ListCommand extends Command<typeof options> {
54
53
  Create `src/commands/count.ts`:
55
54
 
56
55
  ```typescript
57
- import { readdirSync } from "fs";
58
- import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
56
+ import { Command, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
59
57
 
60
58
  const options = {
61
59
  dir: {
@@ -76,9 +74,9 @@ export class CountCommand extends Command<typeof options> {
76
74
  readonly description = "Count files in a directory";
77
75
  readonly options = options;
78
76
 
79
- execute(_ctx: AppContext, config: { dir: string; ext?: string }): CommandResult {
77
+ async execute(config: { dir: string; ext?: string }): Promise<CommandResult> {
80
78
  try {
81
- let files = readdirSync(config.dir);
79
+ let files = await Array.fromAsync(new Bun.Glob("*").scan({ cwd: config.dir, onlyFiles: false }));
82
80
 
83
81
  if (config.ext) {
84
82
  files = files.filter((f) => f.endsWith(config.ext!));
@@ -17,7 +17,7 @@ dbctl db status
17
17
  Create `src/commands/db/migrate.ts`:
18
18
 
19
19
  ```typescript
20
- import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
20
+ import { Command, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
21
21
 
22
22
  const options = {
23
23
  target: {
@@ -37,7 +37,7 @@ export class MigrateCommand extends Command<typeof options> {
37
37
  readonly description = "Run database migrations";
38
38
  readonly options = options;
39
39
 
40
- execute(ctx: AppContext, config: { target: string; dry: boolean }): CommandResult {
40
+ execute(config: { target: string; dry: boolean }): CommandResult {
41
41
  ctx.logger.info(`Migrating to: ${config.target}`);
42
42
 
43
43
  if (config.dry) {
@@ -55,7 +55,7 @@ export class MigrateCommand extends Command<typeof options> {
55
55
  Create `src/commands/db/seed.ts`:
56
56
 
57
57
  ```typescript
58
- import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
58
+ import { Command, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
59
59
 
60
60
  const options = {
61
61
  file: {
@@ -71,7 +71,7 @@ export class SeedCommand extends Command<typeof options> {
71
71
  readonly description = "Seed the database with data";
72
72
  readonly options = options;
73
73
 
74
- execute(ctx: AppContext, config: { file: string }): CommandResult {
74
+ execute(config: { file: string }): CommandResult {
75
75
  ctx.logger.info(`Seeding from: ${config.file}`);
76
76
  console.log(`Loading seed data from ${config.file}...`);
77
77
  console.log("Database seeded successfully!");
@@ -83,14 +83,14 @@ export class SeedCommand extends Command<typeof options> {
83
83
  Create `src/commands/db/status.ts`:
84
84
 
85
85
  ```typescript
86
- import { Command, type AppContext, type CommandResult } from "@pablozaiden/terminatui";
86
+ import { Command, type CommandResult } from "@pablozaiden/terminatui";
87
87
 
88
88
  export class StatusCommand extends Command {
89
89
  readonly name = "status";
90
90
  readonly description = "Show database status";
91
91
  readonly options = {};
92
92
 
93
- execute(_ctx: AppContext): CommandResult {
93
+ execute(): CommandResult {
94
94
  console.log("Database Status:");
95
95
  console.log(" Connected: Yes");
96
96
  console.log(" Version: 1.2.3");
@@ -105,7 +105,7 @@ export class StatusCommand extends Command {
105
105
  Create `src/commands/db/index.ts`:
106
106
 
107
107
  ```typescript
108
- import { Command, type AppContext, type CommandResult } from "@pablozaiden/terminatui";
108
+ import { Command, type CommandResult } from "@pablozaiden/terminatui";
109
109
  import { MigrateCommand } from "./migrate";
110
110
  import { SeedCommand } from "./seed";
111
111
  import { StatusCommand } from "./status";
@@ -123,7 +123,7 @@ export class DbCommand extends Command {
123
123
  ];
124
124
 
125
125
  // Parent command can have its own execute (optional)
126
- execute(_ctx: AppContext): CommandResult {
126
+ execute(): CommandResult {
127
127
  console.log("Use 'dbctl db <command>' for database operations.");
128
128
  console.log("Available: migrate, seed, status");
129
129
  return { success: true };