@pablozaiden/terminatui 0.2.0 → 0.3.0

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 (181) hide show
  1. package/README.md +64 -43
  2. package/package.json +11 -8
  3. package/src/__tests__/application.test.ts +87 -68
  4. package/src/__tests__/buildCliCommand.test.ts +99 -119
  5. package/src/__tests__/builtins.test.ts +27 -75
  6. package/src/__tests__/command.test.ts +100 -131
  7. package/src/__tests__/configOnChange.test.ts +63 -0
  8. package/src/__tests__/context.test.ts +1 -26
  9. package/src/__tests__/helpCore.test.ts +227 -0
  10. package/src/__tests__/parser.test.ts +98 -244
  11. package/src/__tests__/registry.test.ts +33 -160
  12. package/src/__tests__/schemaToFields.test.ts +75 -158
  13. package/src/builtins/help.ts +12 -4
  14. package/src/builtins/settings.ts +18 -32
  15. package/src/builtins/version.ts +3 -3
  16. package/src/cli/output/colors.ts +1 -1
  17. package/src/cli/parser.ts +26 -95
  18. package/src/core/application.ts +192 -110
  19. package/src/core/command.ts +26 -9
  20. package/src/core/context.ts +31 -20
  21. package/src/core/help.ts +24 -18
  22. package/src/core/knownCommands.ts +13 -0
  23. package/src/core/logger.ts +39 -42
  24. package/src/core/registry.ts +5 -12
  25. package/src/index.ts +22 -137
  26. package/src/tui/TuiApplication.tsx +63 -120
  27. package/src/tui/TuiRoot.tsx +135 -0
  28. package/src/tui/adapters/factory.ts +19 -0
  29. package/src/tui/adapters/ink/InkRenderer.tsx +139 -0
  30. package/src/tui/adapters/ink/components/Button.tsx +12 -0
  31. package/src/tui/adapters/ink/components/Code.tsx +6 -0
  32. package/src/tui/adapters/ink/components/CodeHighlight.tsx +6 -0
  33. package/src/tui/adapters/ink/components/Container.tsx +5 -0
  34. package/src/tui/adapters/ink/components/Field.tsx +12 -0
  35. package/src/tui/adapters/ink/components/Label.tsx +24 -0
  36. package/src/tui/adapters/ink/components/MenuButton.tsx +12 -0
  37. package/src/tui/adapters/ink/components/MenuItem.tsx +17 -0
  38. package/src/tui/adapters/ink/components/Overlay.tsx +5 -0
  39. package/src/tui/adapters/ink/components/Panel.tsx +15 -0
  40. package/src/tui/adapters/ink/components/ScrollView.tsx +5 -0
  41. package/src/tui/adapters/ink/components/Select.tsx +44 -0
  42. package/src/tui/adapters/ink/components/Spacer.tsx +15 -0
  43. package/src/tui/adapters/ink/components/Spinner.tsx +5 -0
  44. package/src/tui/adapters/ink/components/TextInput.tsx +22 -0
  45. package/src/tui/adapters/ink/components/Value.tsx +7 -0
  46. package/src/tui/adapters/ink/keyboard.ts +97 -0
  47. package/src/tui/adapters/ink/utils.ts +16 -0
  48. package/src/tui/adapters/opentui/OpenTuiRenderer.tsx +119 -0
  49. package/src/tui/adapters/opentui/components/Button.tsx +13 -0
  50. package/src/tui/adapters/opentui/components/Code.tsx +12 -0
  51. package/src/tui/adapters/opentui/components/CodeHighlight.tsx +24 -0
  52. package/src/tui/adapters/opentui/components/Container.tsx +56 -0
  53. package/src/tui/adapters/opentui/components/Field.tsx +18 -0
  54. package/src/tui/adapters/opentui/components/Label.tsx +15 -0
  55. package/src/tui/adapters/opentui/components/MenuButton.tsx +14 -0
  56. package/src/tui/adapters/opentui/components/MenuItem.tsx +29 -0
  57. package/src/tui/adapters/opentui/components/Overlay.tsx +21 -0
  58. package/src/tui/adapters/opentui/components/Panel.tsx +78 -0
  59. package/src/tui/adapters/opentui/components/ScrollView.tsx +85 -0
  60. package/src/tui/adapters/opentui/components/Select.tsx +59 -0
  61. package/src/tui/adapters/opentui/components/Spacer.tsx +5 -0
  62. package/src/tui/adapters/opentui/components/Spinner.tsx +12 -0
  63. package/src/tui/adapters/opentui/components/TextInput.tsx +13 -0
  64. package/src/tui/adapters/opentui/components/Value.tsx +13 -0
  65. package/src/tui/{hooks → adapters/opentui/hooks}/useSpinner.ts +2 -11
  66. package/src/tui/adapters/opentui/keyboard.ts +61 -0
  67. package/src/tui/adapters/types.ts +71 -0
  68. package/src/tui/components/ActionButton.tsx +0 -36
  69. package/src/tui/components/CommandSelector.tsx +45 -92
  70. package/src/tui/components/ConfigForm.tsx +68 -42
  71. package/src/tui/components/FieldRow.tsx +0 -30
  72. package/src/tui/components/Header.tsx +14 -13
  73. package/src/tui/components/JsonHighlight.tsx +10 -17
  74. package/src/tui/components/ModalBase.tsx +38 -0
  75. package/src/tui/components/ResultsPanel.tsx +27 -36
  76. package/src/tui/components/StatusBar.tsx +24 -39
  77. package/src/tui/components/logColors.ts +12 -0
  78. package/src/tui/context/ClipboardContext.tsx +87 -0
  79. package/src/tui/context/ExecutorContext.tsx +139 -0
  80. package/src/tui/context/KeyboardContext.tsx +85 -71
  81. package/src/tui/context/LogsContext.tsx +35 -0
  82. package/src/tui/context/NavigationContext.tsx +194 -0
  83. package/src/tui/context/RendererContext.tsx +20 -0
  84. package/src/tui/context/TuiAppContext.tsx +58 -0
  85. package/src/tui/hooks/useActiveKeyHandler.ts +75 -0
  86. package/src/tui/hooks/useBackHandler.ts +34 -0
  87. package/src/tui/hooks/useClipboard.ts +40 -25
  88. package/src/tui/hooks/useClipboardProvider.ts +42 -0
  89. package/src/tui/hooks/useGlobalKeyHandler.ts +54 -0
  90. package/src/tui/modals/CliModal.tsx +82 -0
  91. package/src/tui/modals/EditorModal.tsx +207 -0
  92. package/src/tui/modals/LogsModal.tsx +98 -0
  93. package/src/tui/registry.ts +102 -0
  94. package/src/tui/screens/CommandSelectScreen.tsx +162 -0
  95. package/src/tui/screens/ConfigScreen.tsx +165 -0
  96. package/src/tui/screens/ErrorScreen.tsx +58 -0
  97. package/src/tui/screens/ResultsScreen.tsx +68 -0
  98. package/src/tui/screens/RunningScreen.tsx +72 -0
  99. package/src/tui/screens/ScreenBase.ts +6 -0
  100. package/src/tui/semantic/Button.tsx +7 -0
  101. package/src/tui/semantic/Code.tsx +7 -0
  102. package/src/tui/semantic/CodeHighlight.tsx +7 -0
  103. package/src/tui/semantic/Container.tsx +7 -0
  104. package/src/tui/semantic/Field.tsx +7 -0
  105. package/src/tui/semantic/Label.tsx +7 -0
  106. package/src/tui/semantic/MenuButton.tsx +7 -0
  107. package/src/tui/semantic/MenuItem.tsx +7 -0
  108. package/src/tui/semantic/Overlay.tsx +7 -0
  109. package/src/tui/semantic/Panel.tsx +7 -0
  110. package/src/tui/semantic/ScrollView.tsx +9 -0
  111. package/src/tui/semantic/Select.tsx +7 -0
  112. package/src/tui/semantic/Spacer.tsx +7 -0
  113. package/src/tui/semantic/Spinner.tsx +7 -0
  114. package/src/tui/semantic/TextInput.tsx +7 -0
  115. package/src/tui/semantic/Value.tsx +7 -0
  116. package/src/tui/semantic/types.ts +195 -0
  117. package/src/tui/theme.ts +25 -14
  118. package/src/tui/utils/buildCliCommand.ts +1 -0
  119. package/src/tui/utils/getEnumKeys.ts +3 -0
  120. package/src/tui/utils/parameterPersistence.ts +1 -0
  121. package/src/types/command.ts +0 -60
  122. package/.devcontainer/devcontainer.json +0 -19
  123. package/.devcontainer/install-prerequisites.sh +0 -49
  124. package/.github/workflows/copilot-setup-steps.yml +0 -32
  125. package/.github/workflows/pull-request.yml +0 -27
  126. package/.github/workflows/release-npm-package.yml +0 -81
  127. package/AGENTS.md +0 -31
  128. package/bun.lock +0 -236
  129. package/examples/tui-app/commands/config/app/get.ts +0 -66
  130. package/examples/tui-app/commands/config/app/index.ts +0 -27
  131. package/examples/tui-app/commands/config/app/set.ts +0 -86
  132. package/examples/tui-app/commands/config/index.ts +0 -32
  133. package/examples/tui-app/commands/config/user/get.ts +0 -65
  134. package/examples/tui-app/commands/config/user/index.ts +0 -27
  135. package/examples/tui-app/commands/config/user/set.ts +0 -61
  136. package/examples/tui-app/commands/greet.ts +0 -76
  137. package/examples/tui-app/commands/index.ts +0 -4
  138. package/examples/tui-app/commands/math.ts +0 -115
  139. package/examples/tui-app/commands/status.ts +0 -77
  140. package/examples/tui-app/index.ts +0 -35
  141. package/guides/01-hello-world.md +0 -96
  142. package/guides/02-adding-options.md +0 -103
  143. package/guides/03-multiple-commands.md +0 -163
  144. package/guides/04-subcommands.md +0 -206
  145. package/guides/05-interactive-tui.md +0 -194
  146. package/guides/06-config-validation.md +0 -264
  147. package/guides/07-async-cancellation.md +0 -336
  148. package/guides/08-complete-application.md +0 -537
  149. package/guides/README.md +0 -74
  150. package/src/__tests__/colors.test.ts +0 -127
  151. package/src/__tests__/commandClass.test.ts +0 -130
  152. package/src/__tests__/help.test.ts +0 -412
  153. package/src/__tests__/registryNew.test.ts +0 -160
  154. package/src/__tests__/table.test.ts +0 -146
  155. package/src/__tests__/tui.test.ts +0 -26
  156. package/src/builtins/index.ts +0 -4
  157. package/src/cli/help.ts +0 -174
  158. package/src/cli/index.ts +0 -3
  159. package/src/cli/output/index.ts +0 -2
  160. package/src/cli/output/table.ts +0 -141
  161. package/src/commands/help.ts +0 -50
  162. package/src/commands/index.ts +0 -1
  163. package/src/components/index.ts +0 -147
  164. package/src/core/index.ts +0 -15
  165. package/src/hooks/index.ts +0 -131
  166. package/src/registry/commandRegistry.ts +0 -77
  167. package/src/registry/index.ts +0 -1
  168. package/src/tui/TuiApp.tsx +0 -619
  169. package/src/tui/app.ts +0 -29
  170. package/src/tui/components/CliModal.tsx +0 -81
  171. package/src/tui/components/EditorModal.tsx +0 -177
  172. package/src/tui/components/LogsPanel.tsx +0 -86
  173. package/src/tui/components/index.ts +0 -13
  174. package/src/tui/context/index.ts +0 -7
  175. package/src/tui/hooks/index.ts +0 -35
  176. package/src/tui/hooks/useKeyboardHandler.ts +0 -91
  177. package/src/tui/hooks/useLogStream.ts +0 -96
  178. package/src/tui/index.ts +0 -65
  179. package/src/tui/utils/index.ts +0 -13
  180. package/src/types/index.ts +0 -1
  181. package/tsconfig.json +0 -25
@@ -1,50 +0,0 @@
1
- import { defineCommand, type Command } from "../types/command.ts";
2
- import { generateHelp } from "../cli/help.ts";
3
-
4
- interface HelpCommandOptions {
5
- getCommands: () => Command[];
6
- appName?: string;
7
- version?: string;
8
- }
9
-
10
- /**
11
- * Create a help command
12
- */
13
- export function createHelpCommand(options: HelpCommandOptions) {
14
- const { getCommands, appName = "cli", version } = options;
15
-
16
- return defineCommand({
17
- name: "help",
18
- description: "Show help information",
19
- aliases: ["--help", "-h"],
20
- hidden: true,
21
- options: {
22
- command: {
23
- type: "string" as const,
24
- description: "Command to show help for",
25
- },
26
- },
27
- execute: (ctx) => {
28
- const commands = getCommands();
29
- const commandName = ctx.options["command"];
30
-
31
- if (commandName && typeof commandName === "string") {
32
- const cmd = commands.find((c) => c.name === commandName);
33
- if (cmd) {
34
- console.log(generateHelp(cmd, { appName, version }));
35
- return;
36
- }
37
- }
38
-
39
- // Show root help
40
- const rootCommand = defineCommand({
41
- name: appName,
42
- description: `${appName} CLI`,
43
- subcommands: Object.fromEntries(commands.map((c) => [c.name, c])),
44
- execute: () => {},
45
- });
46
-
47
- console.log(generateHelp(rootCommand, { appName, version }));
48
- },
49
- });
50
- }
@@ -1 +0,0 @@
1
- export * from "./help.ts";
@@ -1,147 +0,0 @@
1
- import React from "react";
2
-
3
- /**
4
- * Box component props
5
- */
6
- export interface BoxProps {
7
- children?: React.ReactNode;
8
- flexDirection?: "row" | "column";
9
- padding?: number;
10
- margin?: number;
11
- borderStyle?: "single" | "double" | "round" | "none";
12
- }
13
-
14
- /**
15
- * Box component for layout
16
- */
17
- export function Box({ children }: BoxProps) {
18
- return React.createElement("div", null, children);
19
- }
20
-
21
- /**
22
- * Text component props
23
- */
24
- export interface TextProps {
25
- children?: React.ReactNode;
26
- color?: string;
27
- bold?: boolean;
28
- dim?: boolean;
29
- }
30
-
31
- /**
32
- * Text component for styled text
33
- */
34
- export function Text({ children }: TextProps) {
35
- return React.createElement("span", null, children);
36
- }
37
-
38
- /**
39
- * Input component props
40
- */
41
- export interface InputProps {
42
- value: string;
43
- onChange: (value: string) => void;
44
- placeholder?: string;
45
- disabled?: boolean;
46
- }
47
-
48
- /**
49
- * Input component
50
- */
51
- export function Input({ value, onChange, placeholder }: InputProps) {
52
- return React.createElement("input", {
53
- value,
54
- onChange: (e: { target: { value: string } }) => onChange(e.target.value),
55
- placeholder,
56
- });
57
- }
58
-
59
- /**
60
- * Select option
61
- */
62
- export interface SelectOption {
63
- label: string;
64
- value: string;
65
- }
66
-
67
- /**
68
- * Select component props
69
- */
70
- export interface SelectProps {
71
- options: SelectOption[];
72
- value?: string;
73
- onChange: (value: string) => void;
74
- }
75
-
76
- /**
77
- * Select component
78
- */
79
- export function Select({ options, value, onChange }: SelectProps) {
80
- return React.createElement(
81
- "select",
82
- { value, onChange: (e: { target: { value: string } }) => onChange(e.target.value) },
83
- options.map((opt) =>
84
- React.createElement("option", { key: opt.value, value: opt.value }, opt.label)
85
- )
86
- );
87
- }
88
-
89
- /**
90
- * Button component props
91
- */
92
- export interface ButtonProps {
93
- label: string;
94
- onPress: () => void;
95
- disabled?: boolean;
96
- variant?: "primary" | "secondary" | "danger";
97
- }
98
-
99
- /**
100
- * Button component
101
- */
102
- export function Button({ label, onPress, disabled }: ButtonProps) {
103
- return React.createElement("button", { onClick: onPress, disabled }, label);
104
- }
105
-
106
- /**
107
- * Modal component props
108
- */
109
- export interface ModalProps {
110
- isOpen: boolean;
111
- onClose: () => void;
112
- title?: string;
113
- children: React.ReactNode;
114
- }
115
-
116
- /**
117
- * Modal component
118
- */
119
- export function Modal({ isOpen, onClose, title, children }: ModalProps) {
120
- if (!isOpen) return null;
121
-
122
- return React.createElement(
123
- "div",
124
- { className: "modal" },
125
- React.createElement(
126
- "div",
127
- { className: "modal-content" },
128
- title && React.createElement("h2", null, title),
129
- children,
130
- React.createElement("button", { onClick: onClose }, "Close")
131
- )
132
- );
133
- }
134
-
135
- /**
136
- * Spinner component props
137
- */
138
- export interface SpinnerProps {
139
- label?: string;
140
- }
141
-
142
- /**
143
- * Spinner component
144
- */
145
- export function Spinner({ label }: SpinnerProps) {
146
- return React.createElement("span", null, label ?? "Loading...");
147
- }
package/src/core/index.ts DELETED
@@ -1,15 +0,0 @@
1
- // Core exports
2
- export { Application, type ApplicationConfig, type ApplicationHooks, type GlobalOptions } from "./application.ts";
3
- export { AppContext, type AppConfig } from "./context.ts";
4
- export { Command, ConfigValidationError, AbortError, type AnyCommand, type CommandExample, type CommandResult, type CommandExecutionContext } from "./command.ts";
5
- export { CommandRegistry, type ResolveResult } from "./registry.ts";
6
- export { Logger, createLogger, LogLevel, type LoggerConfig, type LogEvent } from "./logger.ts";
7
- export {
8
- generateCommandHelp,
9
- generateAppHelp,
10
- formatUsage,
11
- formatSubCommands,
12
- formatOptions,
13
- formatExamples,
14
- type HelpOptions,
15
- } from "./help.ts";
@@ -1,131 +0,0 @@
1
- import { useState, useCallback } from "react";
2
- import type { Command, OptionSchema, OptionValues } from "../types/command.ts";
3
-
4
- /**
5
- * Hook for command execution
6
- */
7
- export function useCommand<T extends OptionSchema>(command: Command<T>) {
8
- const [isExecuting, setIsExecuting] = useState(false);
9
- const [error, setError] = useState<Error | null>(null);
10
-
11
- const execute = useCallback(
12
- async (options: OptionValues<T>) => {
13
- setIsExecuting(true);
14
- setError(null);
15
-
16
- try {
17
- await command.execute({
18
- options,
19
- args: [],
20
- commandPath: [command.name],
21
- });
22
- } catch (err) {
23
- setError(err instanceof Error ? err : new Error(String(err)));
24
- } finally {
25
- setIsExecuting(false);
26
- }
27
- },
28
- [command]
29
- );
30
-
31
- return { execute, isExecuting, error };
32
- }
33
-
34
- /**
35
- * Hook for managing options state
36
- */
37
- export function useOptions<T extends OptionSchema>(
38
- schema: T,
39
- initialValues?: Partial<OptionValues<T>>
40
- ) {
41
- const [values, setValues] = useState<OptionValues<T>>(() => {
42
- const defaults: Record<string, unknown> = {};
43
- for (const [key, def] of Object.entries(schema)) {
44
- defaults[key] = initialValues?.[key as keyof T] ?? def.default;
45
- }
46
- return defaults as OptionValues<T>;
47
- });
48
-
49
- const setValue = useCallback(
50
- <K extends keyof T>(key: K, value: OptionValues<T>[K]) => {
51
- setValues((prev) => ({ ...prev, [key]: value }));
52
- },
53
- []
54
- );
55
-
56
- const reset = useCallback(() => {
57
- const defaults: Record<string, unknown> = {};
58
- for (const [key, def] of Object.entries(schema)) {
59
- defaults[key] = def.default;
60
- }
61
- setValues(defaults as OptionValues<T>);
62
- }, [schema]);
63
-
64
- return { values, setValue, setValues, reset };
65
- }
66
-
67
- /**
68
- * Hook for navigation between views
69
- */
70
- export function useNavigation(views: string[], initialView?: string) {
71
- const [currentView, setCurrentView] = useState(initialView ?? views[0] ?? "");
72
- const [history, setHistory] = useState<string[]>([]);
73
-
74
- const navigate = useCallback((view: string) => {
75
- setHistory((prev) => [...prev, currentView]);
76
- setCurrentView(view);
77
- }, [currentView]);
78
-
79
- const goBack = useCallback(() => {
80
- const prev = history[history.length - 1];
81
- if (prev) {
82
- setHistory((h) => h.slice(0, -1));
83
- setCurrentView(prev);
84
- }
85
- }, [history]);
86
-
87
- const canGoBack = history.length > 0;
88
-
89
- return { currentView, navigate, goBack, canGoBack };
90
- }
91
-
92
- /**
93
- * Hook for modal state
94
- */
95
- export function useModal(initialOpen = false) {
96
- const [isOpen, setIsOpen] = useState(initialOpen);
97
-
98
- const open = useCallback(() => setIsOpen(true), []);
99
- const close = useCallback(() => setIsOpen(false), []);
100
- const toggle = useCallback(() => setIsOpen((prev) => !prev), []);
101
-
102
- return { isOpen, open, close, toggle };
103
- }
104
-
105
- /**
106
- * Hook for async operations
107
- */
108
- export function useAsync<T>(asyncFn: () => Promise<T>) {
109
- const [data, setData] = useState<T | null>(null);
110
- const [isLoading, setIsLoading] = useState(false);
111
- const [error, setError] = useState<Error | null>(null);
112
-
113
- const execute = useCallback(async () => {
114
- setIsLoading(true);
115
- setError(null);
116
-
117
- try {
118
- const result = await asyncFn();
119
- setData(result);
120
- return result;
121
- } catch (err) {
122
- const error = err instanceof Error ? err : new Error(String(err));
123
- setError(error);
124
- throw error;
125
- } finally {
126
- setIsLoading(false);
127
- }
128
- }, [asyncFn]);
129
-
130
- return { data, isLoading, error, execute };
131
- }
@@ -1,77 +0,0 @@
1
- import type { Command, OptionSchema } from "../types/command.ts";
2
-
3
- /**
4
- * Command registry for managing commands
5
- */
6
- export interface CommandRegistry<T extends OptionSchema = OptionSchema> {
7
- register(command: Command<T>): void;
8
- get(name: string): Command<T> | undefined;
9
- resolve(nameOrAlias: string): Command<T> | undefined;
10
- has(nameOrAlias: string): boolean;
11
- list(): Command<T>[];
12
- getNames(): string[];
13
- getCommandMap(): Record<string, Command<T>>;
14
- }
15
-
16
- /**
17
- * Create a command registry
18
- */
19
- export function createCommandRegistry<
20
- T extends OptionSchema = OptionSchema,
21
- >(): CommandRegistry<T> {
22
- const commands = new Map<string, Command<T>>();
23
- const aliases = new Map<string, string>();
24
-
25
- return {
26
- register(command: Command<T>): void {
27
- if (commands.has(command.name)) {
28
- throw new Error(`Command "${command.name}" is already registered`);
29
- }
30
-
31
- commands.set(command.name, command);
32
-
33
- if (command.aliases) {
34
- for (const alias of command.aliases) {
35
- if (aliases.has(alias) || commands.has(alias)) {
36
- throw new Error(`Alias "${alias}" conflicts with existing command or alias`);
37
- }
38
- aliases.set(alias, command.name);
39
- }
40
- }
41
- },
42
-
43
- get(name: string): Command<T> | undefined {
44
- return commands.get(name);
45
- },
46
-
47
- resolve(nameOrAlias: string): Command<T> | undefined {
48
- // Try direct name first
49
- const cmd = commands.get(nameOrAlias);
50
- if (cmd) return cmd;
51
-
52
- // Try alias
53
- const resolvedName = aliases.get(nameOrAlias);
54
- if (resolvedName) {
55
- return commands.get(resolvedName);
56
- }
57
-
58
- return undefined;
59
- },
60
-
61
- has(nameOrAlias: string): boolean {
62
- return commands.has(nameOrAlias) || aliases.has(nameOrAlias);
63
- },
64
-
65
- list(): Command<T>[] {
66
- return Array.from(commands.values());
67
- },
68
-
69
- getNames(): string[] {
70
- return Array.from(commands.keys());
71
- },
72
-
73
- getCommandMap(): Record<string, Command<T>> {
74
- return Object.fromEntries(commands);
75
- },
76
- };
77
- }
@@ -1 +0,0 @@
1
- export * from "./commandRegistry.ts";