@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.
- package/README.md +64 -43
- package/package.json +11 -8
- package/src/__tests__/application.test.ts +87 -68
- package/src/__tests__/buildCliCommand.test.ts +99 -119
- package/src/__tests__/builtins.test.ts +27 -75
- package/src/__tests__/command.test.ts +100 -131
- package/src/__tests__/configOnChange.test.ts +63 -0
- package/src/__tests__/context.test.ts +1 -26
- package/src/__tests__/helpCore.test.ts +227 -0
- package/src/__tests__/parser.test.ts +98 -244
- package/src/__tests__/registry.test.ts +33 -160
- package/src/__tests__/schemaToFields.test.ts +75 -158
- package/src/builtins/help.ts +12 -4
- package/src/builtins/settings.ts +18 -32
- package/src/builtins/version.ts +3 -3
- package/src/cli/output/colors.ts +1 -1
- package/src/cli/parser.ts +26 -95
- package/src/core/application.ts +192 -110
- package/src/core/command.ts +26 -9
- package/src/core/context.ts +31 -20
- package/src/core/help.ts +24 -18
- package/src/core/knownCommands.ts +13 -0
- package/src/core/logger.ts +39 -42
- package/src/core/registry.ts +5 -12
- package/src/index.ts +22 -137
- package/src/tui/TuiApplication.tsx +63 -120
- package/src/tui/TuiRoot.tsx +135 -0
- package/src/tui/adapters/factory.ts +19 -0
- package/src/tui/adapters/ink/InkRenderer.tsx +139 -0
- package/src/tui/adapters/ink/components/Button.tsx +12 -0
- package/src/tui/adapters/ink/components/Code.tsx +6 -0
- package/src/tui/adapters/ink/components/CodeHighlight.tsx +6 -0
- package/src/tui/adapters/ink/components/Container.tsx +5 -0
- package/src/tui/adapters/ink/components/Field.tsx +12 -0
- package/src/tui/adapters/ink/components/Label.tsx +24 -0
- package/src/tui/adapters/ink/components/MenuButton.tsx +12 -0
- package/src/tui/adapters/ink/components/MenuItem.tsx +17 -0
- package/src/tui/adapters/ink/components/Overlay.tsx +5 -0
- package/src/tui/adapters/ink/components/Panel.tsx +15 -0
- package/src/tui/adapters/ink/components/ScrollView.tsx +5 -0
- package/src/tui/adapters/ink/components/Select.tsx +44 -0
- package/src/tui/adapters/ink/components/Spacer.tsx +15 -0
- package/src/tui/adapters/ink/components/Spinner.tsx +5 -0
- package/src/tui/adapters/ink/components/TextInput.tsx +22 -0
- package/src/tui/adapters/ink/components/Value.tsx +7 -0
- package/src/tui/adapters/ink/keyboard.ts +97 -0
- package/src/tui/adapters/ink/utils.ts +16 -0
- package/src/tui/adapters/opentui/OpenTuiRenderer.tsx +119 -0
- package/src/tui/adapters/opentui/components/Button.tsx +13 -0
- package/src/tui/adapters/opentui/components/Code.tsx +12 -0
- package/src/tui/adapters/opentui/components/CodeHighlight.tsx +24 -0
- package/src/tui/adapters/opentui/components/Container.tsx +56 -0
- package/src/tui/adapters/opentui/components/Field.tsx +18 -0
- package/src/tui/adapters/opentui/components/Label.tsx +15 -0
- package/src/tui/adapters/opentui/components/MenuButton.tsx +14 -0
- package/src/tui/adapters/opentui/components/MenuItem.tsx +29 -0
- package/src/tui/adapters/opentui/components/Overlay.tsx +21 -0
- package/src/tui/adapters/opentui/components/Panel.tsx +78 -0
- package/src/tui/adapters/opentui/components/ScrollView.tsx +85 -0
- package/src/tui/adapters/opentui/components/Select.tsx +59 -0
- package/src/tui/adapters/opentui/components/Spacer.tsx +5 -0
- package/src/tui/adapters/opentui/components/Spinner.tsx +12 -0
- package/src/tui/adapters/opentui/components/TextInput.tsx +13 -0
- package/src/tui/adapters/opentui/components/Value.tsx +13 -0
- package/src/tui/{hooks → adapters/opentui/hooks}/useSpinner.ts +2 -11
- package/src/tui/adapters/opentui/keyboard.ts +61 -0
- package/src/tui/adapters/types.ts +71 -0
- package/src/tui/components/ActionButton.tsx +0 -36
- package/src/tui/components/CommandSelector.tsx +45 -92
- package/src/tui/components/ConfigForm.tsx +68 -42
- package/src/tui/components/FieldRow.tsx +0 -30
- package/src/tui/components/Header.tsx +14 -13
- package/src/tui/components/JsonHighlight.tsx +10 -17
- package/src/tui/components/ModalBase.tsx +38 -0
- package/src/tui/components/ResultsPanel.tsx +27 -36
- package/src/tui/components/StatusBar.tsx +24 -39
- package/src/tui/components/logColors.ts +12 -0
- package/src/tui/context/ClipboardContext.tsx +87 -0
- package/src/tui/context/ExecutorContext.tsx +139 -0
- package/src/tui/context/KeyboardContext.tsx +85 -71
- package/src/tui/context/LogsContext.tsx +35 -0
- package/src/tui/context/NavigationContext.tsx +194 -0
- package/src/tui/context/RendererContext.tsx +20 -0
- package/src/tui/context/TuiAppContext.tsx +58 -0
- package/src/tui/hooks/useActiveKeyHandler.ts +75 -0
- package/src/tui/hooks/useBackHandler.ts +34 -0
- package/src/tui/hooks/useClipboard.ts +40 -25
- package/src/tui/hooks/useClipboardProvider.ts +42 -0
- package/src/tui/hooks/useGlobalKeyHandler.ts +54 -0
- package/src/tui/modals/CliModal.tsx +82 -0
- package/src/tui/modals/EditorModal.tsx +207 -0
- package/src/tui/modals/LogsModal.tsx +98 -0
- package/src/tui/registry.ts +102 -0
- package/src/tui/screens/CommandSelectScreen.tsx +162 -0
- package/src/tui/screens/ConfigScreen.tsx +165 -0
- package/src/tui/screens/ErrorScreen.tsx +58 -0
- package/src/tui/screens/ResultsScreen.tsx +68 -0
- package/src/tui/screens/RunningScreen.tsx +72 -0
- package/src/tui/screens/ScreenBase.ts +6 -0
- package/src/tui/semantic/Button.tsx +7 -0
- package/src/tui/semantic/Code.tsx +7 -0
- package/src/tui/semantic/CodeHighlight.tsx +7 -0
- package/src/tui/semantic/Container.tsx +7 -0
- package/src/tui/semantic/Field.tsx +7 -0
- package/src/tui/semantic/Label.tsx +7 -0
- package/src/tui/semantic/MenuButton.tsx +7 -0
- package/src/tui/semantic/MenuItem.tsx +7 -0
- package/src/tui/semantic/Overlay.tsx +7 -0
- package/src/tui/semantic/Panel.tsx +7 -0
- package/src/tui/semantic/ScrollView.tsx +9 -0
- package/src/tui/semantic/Select.tsx +7 -0
- package/src/tui/semantic/Spacer.tsx +7 -0
- package/src/tui/semantic/Spinner.tsx +7 -0
- package/src/tui/semantic/TextInput.tsx +7 -0
- package/src/tui/semantic/Value.tsx +7 -0
- package/src/tui/semantic/types.ts +195 -0
- package/src/tui/theme.ts +25 -14
- package/src/tui/utils/buildCliCommand.ts +1 -0
- package/src/tui/utils/getEnumKeys.ts +3 -0
- package/src/tui/utils/parameterPersistence.ts +1 -0
- package/src/types/command.ts +0 -60
- package/.devcontainer/devcontainer.json +0 -19
- package/.devcontainer/install-prerequisites.sh +0 -49
- package/.github/workflows/copilot-setup-steps.yml +0 -32
- package/.github/workflows/pull-request.yml +0 -27
- package/.github/workflows/release-npm-package.yml +0 -81
- package/AGENTS.md +0 -31
- package/bun.lock +0 -236
- package/examples/tui-app/commands/config/app/get.ts +0 -66
- package/examples/tui-app/commands/config/app/index.ts +0 -27
- package/examples/tui-app/commands/config/app/set.ts +0 -86
- package/examples/tui-app/commands/config/index.ts +0 -32
- package/examples/tui-app/commands/config/user/get.ts +0 -65
- package/examples/tui-app/commands/config/user/index.ts +0 -27
- package/examples/tui-app/commands/config/user/set.ts +0 -61
- package/examples/tui-app/commands/greet.ts +0 -76
- package/examples/tui-app/commands/index.ts +0 -4
- package/examples/tui-app/commands/math.ts +0 -115
- package/examples/tui-app/commands/status.ts +0 -77
- package/examples/tui-app/index.ts +0 -35
- package/guides/01-hello-world.md +0 -96
- package/guides/02-adding-options.md +0 -103
- package/guides/03-multiple-commands.md +0 -163
- package/guides/04-subcommands.md +0 -206
- package/guides/05-interactive-tui.md +0 -194
- package/guides/06-config-validation.md +0 -264
- package/guides/07-async-cancellation.md +0 -336
- package/guides/08-complete-application.md +0 -537
- package/guides/README.md +0 -74
- package/src/__tests__/colors.test.ts +0 -127
- package/src/__tests__/commandClass.test.ts +0 -130
- package/src/__tests__/help.test.ts +0 -412
- package/src/__tests__/registryNew.test.ts +0 -160
- package/src/__tests__/table.test.ts +0 -146
- package/src/__tests__/tui.test.ts +0 -26
- package/src/builtins/index.ts +0 -4
- package/src/cli/help.ts +0 -174
- package/src/cli/index.ts +0 -3
- package/src/cli/output/index.ts +0 -2
- package/src/cli/output/table.ts +0 -141
- package/src/commands/help.ts +0 -50
- package/src/commands/index.ts +0 -1
- package/src/components/index.ts +0 -147
- package/src/core/index.ts +0 -15
- package/src/hooks/index.ts +0 -131
- package/src/registry/commandRegistry.ts +0 -77
- package/src/registry/index.ts +0 -1
- package/src/tui/TuiApp.tsx +0 -619
- package/src/tui/app.ts +0 -29
- package/src/tui/components/CliModal.tsx +0 -81
- package/src/tui/components/EditorModal.tsx +0 -177
- package/src/tui/components/LogsPanel.tsx +0 -86
- package/src/tui/components/index.ts +0 -13
- package/src/tui/context/index.ts +0 -7
- package/src/tui/hooks/index.ts +0 -35
- package/src/tui/hooks/useKeyboardHandler.ts +0 -91
- package/src/tui/hooks/useLogStream.ts +0 -96
- package/src/tui/index.ts +0 -65
- package/src/tui/utils/index.ts +0 -13
- package/src/types/index.ts +0 -1
- package/tsconfig.json +0 -25
package/src/commands/help.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commands/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./help.ts";
|
package/src/components/index.ts
DELETED
|
@@ -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";
|
package/src/hooks/index.ts
DELETED
|
@@ -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
|
-
}
|
package/src/registry/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./commandRegistry.ts";
|