@pablozaiden/terminatui 0.2.0 → 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.
- package/AGENTS.md +14 -2
- package/CLAUDE.md +1 -0
- package/README.md +64 -43
- package/bun.lock +85 -0
- package/examples/tui-app/commands/config/app/get.ts +6 -10
- package/examples/tui-app/commands/config/app/index.ts +2 -6
- package/examples/tui-app/commands/config/app/set.ts +23 -13
- package/examples/tui-app/commands/config/index.ts +2 -6
- package/examples/tui-app/commands/config/user/get.ts +6 -10
- package/examples/tui-app/commands/config/user/index.ts +2 -6
- package/examples/tui-app/commands/config/user/set.ts +6 -10
- package/examples/tui-app/commands/greet.ts +13 -11
- package/examples/tui-app/commands/math.ts +5 -9
- package/examples/tui-app/commands/status.ts +21 -12
- package/examples/tui-app/index.ts +6 -3
- package/guides/01-hello-world.md +7 -2
- package/guides/02-adding-options.md +2 -2
- package/guides/03-multiple-commands.md +6 -8
- package/guides/04-subcommands.md +8 -8
- package/guides/05-interactive-tui.md +45 -30
- package/guides/06-config-validation.md +4 -12
- package/guides/07-async-cancellation.md +14 -16
- package/guides/08-complete-application.md +12 -42
- package/guides/README.md +7 -3
- package/package.json +4 -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__/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 +4 -4
- 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/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 +135 -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 +115 -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 +70 -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 +160 -0
- package/src/tui/screens/ErrorScreen.tsx +58 -0
- package/src/tui/screens/ResultsScreen.tsx +60 -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/examples/tui-app/commands/index.ts +0 -4
- 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/index.ts +0 -137
- 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
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
KeyboardPriority,
|
|
4
|
-
} from "../tui/index.ts";
|
|
5
|
-
|
|
6
|
-
describe("TUI", () => {
|
|
7
|
-
describe("KeyboardPriority", () => {
|
|
8
|
-
test("Modal has highest priority", () => {
|
|
9
|
-
expect(KeyboardPriority.Modal).toBe(100);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test("Focused has medium priority", () => {
|
|
13
|
-
expect(KeyboardPriority.Focused).toBe(50);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test("Global has lowest priority", () => {
|
|
17
|
-
expect(KeyboardPriority.Global).toBe(0);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("priorities are correctly ordered", () => {
|
|
21
|
-
expect(KeyboardPriority.Modal).toBeGreaterThan(KeyboardPriority.Focused);
|
|
22
|
-
expect(KeyboardPriority.Focused).toBeGreaterThan(KeyboardPriority.Global);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
package/src/builtins/index.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { HelpCommand, createHelpCommandForParent, createRootHelpCommand } from "./help.ts";
|
|
2
|
-
export { VersionCommand, createVersionCommand, formatVersion } from "./version.ts";
|
|
3
|
-
export { SettingsCommand, createSettingsCommand } from "./settings.ts";
|
|
4
|
-
export type { VersionConfig } from "./version.ts";
|
package/src/cli/help.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import type { Command, OptionDef } from "../types/command.ts";
|
|
2
|
-
import { colors } from "./output/colors.ts";
|
|
3
|
-
|
|
4
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
-
type AnyCommand = Command<any, any>;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Format usage line for a command
|
|
9
|
-
*/
|
|
10
|
-
export function formatUsage(
|
|
11
|
-
command: AnyCommand,
|
|
12
|
-
appName = "cli"
|
|
13
|
-
): string {
|
|
14
|
-
const parts = [appName, command.name];
|
|
15
|
-
|
|
16
|
-
if (command.subcommands && Object.keys(command.subcommands).length > 0) {
|
|
17
|
-
parts.push("[command]");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (command.options && Object.keys(command.options).length > 0) {
|
|
21
|
-
parts.push("[options]");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return parts.join(" ");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Format subcommands list
|
|
29
|
-
*/
|
|
30
|
-
export function formatCommands(command: AnyCommand): string {
|
|
31
|
-
if (!command.subcommands) return "";
|
|
32
|
-
|
|
33
|
-
const entries = Object.entries(command.subcommands)
|
|
34
|
-
.filter(([, cmd]) => !cmd.hidden)
|
|
35
|
-
.map(([name, cmd]) => {
|
|
36
|
-
const aliases = cmd.aliases?.length ? ` (${cmd.aliases.join(", ")})` : "";
|
|
37
|
-
return ` ${colors.cyan(name)}${aliases} ${cmd.description}`;
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
if (entries.length === 0) return "";
|
|
41
|
-
|
|
42
|
-
return ["Commands:", ...entries].join("\n");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Format options list
|
|
47
|
-
*/
|
|
48
|
-
export function formatOptions(command: AnyCommand): string {
|
|
49
|
-
if (!command.options) return "";
|
|
50
|
-
|
|
51
|
-
const entries = Object.entries(command.options).map(([name, defUntyped]) => {
|
|
52
|
-
const def = defUntyped as OptionDef;
|
|
53
|
-
const alias = def.alias ? `-${def.alias}, ` : " ";
|
|
54
|
-
const flag = `${alias}--${name}`;
|
|
55
|
-
const required = def.required ? colors.red("*") : "";
|
|
56
|
-
const defaultVal = def.default !== undefined ? ` (default: ${def.default})` : "";
|
|
57
|
-
const enumVals = def.enum ? ` [${def.enum.join("|")}]` : "";
|
|
58
|
-
|
|
59
|
-
return ` ${colors.yellow(flag)}${required} ${def.description}${enumVals}${defaultVal}`;
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
if (entries.length === 0) return "";
|
|
63
|
-
|
|
64
|
-
return ["Options:", ...entries].join("\n");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Format examples list
|
|
69
|
-
*/
|
|
70
|
-
export function formatExamples(command: AnyCommand): string {
|
|
71
|
-
if (!command.examples?.length) return "";
|
|
72
|
-
|
|
73
|
-
const entries = command.examples.map(
|
|
74
|
-
(ex) => ` ${colors.dim("$")} ${ex.command}\n ${colors.dim(ex.description)}`
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
return ["Examples:", ...entries].join("\n");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Format global options (log-level, detailed-logs)
|
|
82
|
-
*/
|
|
83
|
-
export function formatGlobalOptions(): string {
|
|
84
|
-
const entries = [
|
|
85
|
-
` ${colors.yellow("--log-level")} <level> Set log level [silly|trace|debug|info|warn|error|fatal]`,
|
|
86
|
-
` ${colors.yellow("--detailed-logs")} Enable detailed log output`,
|
|
87
|
-
` ${colors.yellow("--no-detailed-logs")} Disable detailed log output`,
|
|
88
|
-
];
|
|
89
|
-
|
|
90
|
-
return ["Global Options:", ...entries].join("\n");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Get command summary line
|
|
95
|
-
*/
|
|
96
|
-
export function getCommandSummary(command: AnyCommand): string {
|
|
97
|
-
const aliases = command.aliases?.length ? ` (${command.aliases.join(", ")})` : "";
|
|
98
|
-
return `${command.name}${aliases}: ${command.description}`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Generate full help text for a command
|
|
103
|
-
*/
|
|
104
|
-
export function generateHelp(
|
|
105
|
-
command: AnyCommand,
|
|
106
|
-
options: { appName?: string; version?: string } = {}
|
|
107
|
-
): string {
|
|
108
|
-
const { appName = "cli", version } = options;
|
|
109
|
-
const sections: string[] = [];
|
|
110
|
-
|
|
111
|
-
// Header
|
|
112
|
-
if (version) {
|
|
113
|
-
sections.push(`${colors.bold(appName)} ${colors.dim(`v${version}`)}`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Description
|
|
117
|
-
sections.push(command.description);
|
|
118
|
-
|
|
119
|
-
// Usage
|
|
120
|
-
sections.push(`\n${colors.bold("Usage:")}\n ${formatUsage(command, appName)}`);
|
|
121
|
-
|
|
122
|
-
// Commands
|
|
123
|
-
const commandsSection = formatCommands(command);
|
|
124
|
-
if (commandsSection) {
|
|
125
|
-
sections.push(`\n${commandsSection}`);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Options
|
|
129
|
-
const optionsSection = formatOptions(command);
|
|
130
|
-
if (optionsSection) {
|
|
131
|
-
sections.push(`\n${optionsSection}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Examples
|
|
135
|
-
const examplesSection = formatExamples(command);
|
|
136
|
-
if (examplesSection) {
|
|
137
|
-
sections.push(`\n${examplesSection}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return sections.join("\n");
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Generate help text for a specific command (includes global options)
|
|
145
|
-
*/
|
|
146
|
-
export function generateCommandHelp(
|
|
147
|
-
command: AnyCommand,
|
|
148
|
-
appName = "cli"
|
|
149
|
-
): string {
|
|
150
|
-
const sections: string[] = [];
|
|
151
|
-
|
|
152
|
-
// Description
|
|
153
|
-
sections.push(command.description);
|
|
154
|
-
|
|
155
|
-
// Usage
|
|
156
|
-
sections.push(`\n${colors.bold("Usage:")}\n ${formatUsage(command, appName)}`);
|
|
157
|
-
|
|
158
|
-
// Options
|
|
159
|
-
const optionsSection = formatOptions(command);
|
|
160
|
-
if (optionsSection) {
|
|
161
|
-
sections.push(`\n${optionsSection}`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Global Options
|
|
165
|
-
sections.push(`\n${formatGlobalOptions()}`);
|
|
166
|
-
|
|
167
|
-
// Examples
|
|
168
|
-
const examplesSection = formatExamples(command);
|
|
169
|
-
if (examplesSection) {
|
|
170
|
-
sections.push(`\n${examplesSection}`);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return sections.join("\n");
|
|
174
|
-
}
|
package/src/cli/index.ts
DELETED
package/src/cli/output/index.ts
DELETED
package/src/cli/output/table.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Column configuration for tables
|
|
3
|
-
*/
|
|
4
|
-
export interface ColumnConfig {
|
|
5
|
-
key: string;
|
|
6
|
-
header?: string;
|
|
7
|
-
width?: number;
|
|
8
|
-
align?: "left" | "right" | "center";
|
|
9
|
-
formatter?: (value: unknown) => string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface TableOptions {
|
|
13
|
-
columns?: (string | ColumnConfig)[];
|
|
14
|
-
showHeaders?: boolean;
|
|
15
|
-
border?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Create a formatted table string
|
|
20
|
-
*/
|
|
21
|
-
export function table<T extends Record<string, unknown>>(
|
|
22
|
-
data: T[],
|
|
23
|
-
options: TableOptions = {}
|
|
24
|
-
): string {
|
|
25
|
-
if (data.length === 0) return "";
|
|
26
|
-
|
|
27
|
-
const { showHeaders = true } = options;
|
|
28
|
-
|
|
29
|
-
// Determine columns
|
|
30
|
-
const columns: ColumnConfig[] = options.columns
|
|
31
|
-
? options.columns.map((col) =>
|
|
32
|
-
typeof col === "string" ? { key: col, header: col } : col
|
|
33
|
-
)
|
|
34
|
-
: Object.keys(data[0] ?? {}).map((key) => ({ key, header: key }));
|
|
35
|
-
|
|
36
|
-
// Calculate column widths
|
|
37
|
-
const widths = columns.map((col) => {
|
|
38
|
-
const headerWidth = (col.header ?? col.key).length;
|
|
39
|
-
const maxDataWidth = Math.max(
|
|
40
|
-
...data.map((row) => {
|
|
41
|
-
const value = row[col.key];
|
|
42
|
-
const formatted = col.formatter ? col.formatter(value) : String(value ?? "");
|
|
43
|
-
return formatted.length;
|
|
44
|
-
})
|
|
45
|
-
);
|
|
46
|
-
return col.width ?? Math.max(headerWidth, maxDataWidth);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Format a row
|
|
50
|
-
const formatRow = (values: string[]): string => {
|
|
51
|
-
return values
|
|
52
|
-
.map((val, i) => {
|
|
53
|
-
const width = widths[i] ?? 10;
|
|
54
|
-
const col = columns[i];
|
|
55
|
-
const align = col?.align ?? "left";
|
|
56
|
-
|
|
57
|
-
if (align === "right") {
|
|
58
|
-
return val.padStart(width);
|
|
59
|
-
} else if (align === "center") {
|
|
60
|
-
const leftPad = Math.floor((width - val.length) / 2);
|
|
61
|
-
return val.padStart(leftPad + val.length).padEnd(width);
|
|
62
|
-
}
|
|
63
|
-
return val.padEnd(width);
|
|
64
|
-
})
|
|
65
|
-
.join(" ");
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const rows: string[] = [];
|
|
69
|
-
|
|
70
|
-
// Add header
|
|
71
|
-
if (showHeaders) {
|
|
72
|
-
const headers = columns.map((col) => col.header ?? col.key);
|
|
73
|
-
rows.push(formatRow(headers));
|
|
74
|
-
rows.push(widths.map((w) => "-".repeat(w)).join(" "));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Add data rows
|
|
78
|
-
for (const row of data) {
|
|
79
|
-
const values = columns.map((col) => {
|
|
80
|
-
const value = row[col.key];
|
|
81
|
-
return col.formatter ? col.formatter(value) : String(value ?? "");
|
|
82
|
-
});
|
|
83
|
-
rows.push(formatRow(values));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return rows.join("\n");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Create a key-value list
|
|
91
|
-
*/
|
|
92
|
-
export function keyValueList(
|
|
93
|
-
data: Record<string, unknown>,
|
|
94
|
-
options: { separator?: string } = {}
|
|
95
|
-
): string {
|
|
96
|
-
const { separator = ":" } = options;
|
|
97
|
-
|
|
98
|
-
const entries = Object.entries(data);
|
|
99
|
-
if (entries.length === 0) return "";
|
|
100
|
-
|
|
101
|
-
const maxKeyLength = Math.max(...entries.map(([key]) => key.length));
|
|
102
|
-
|
|
103
|
-
return entries
|
|
104
|
-
.map(([key, value]) => `${key.padEnd(maxKeyLength)}${separator} ${value}`)
|
|
105
|
-
.join("\n");
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Create a bullet list
|
|
110
|
-
*/
|
|
111
|
-
export function bulletList(
|
|
112
|
-
items: string[],
|
|
113
|
-
options: { bullet?: string; indent?: number } = {}
|
|
114
|
-
): string {
|
|
115
|
-
const { bullet = "•", indent = 0 } = options;
|
|
116
|
-
|
|
117
|
-
if (items.length === 0) return "";
|
|
118
|
-
|
|
119
|
-
const prefix = " ".repeat(indent);
|
|
120
|
-
return items.map((item) => `${prefix}${bullet} ${item}`).join("\n");
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Create a numbered list
|
|
125
|
-
*/
|
|
126
|
-
export function numberedList(
|
|
127
|
-
items: string[],
|
|
128
|
-
options: { start?: number; indent?: number } = {}
|
|
129
|
-
): string {
|
|
130
|
-
const { start = 1, indent = 0 } = options;
|
|
131
|
-
|
|
132
|
-
if (items.length === 0) return "";
|
|
133
|
-
|
|
134
|
-
const prefix = " ".repeat(indent);
|
|
135
|
-
const maxNum = start + items.length - 1;
|
|
136
|
-
const numWidth = String(maxNum).length;
|
|
137
|
-
|
|
138
|
-
return items
|
|
139
|
-
.map((item, i) => `${prefix}${String(start + i).padStart(numWidth)}. ${item}`)
|
|
140
|
-
.join("\n");
|
|
141
|
-
}
|
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
|
-
}
|