@pablozaiden/terminatui 0.1.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 (95) hide show
  1. package/.devcontainer/devcontainer.json +19 -0
  2. package/.devcontainer/install-prerequisites.sh +49 -0
  3. package/.github/workflows/copilot-setup-steps.yml +32 -0
  4. package/.github/workflows/pull-request.yml +27 -0
  5. package/.github/workflows/release-npm-package.yml +78 -0
  6. package/LICENSE +21 -0
  7. package/README.md +524 -0
  8. package/examples/tui-app/commands/greet.ts +75 -0
  9. package/examples/tui-app/commands/index.ts +3 -0
  10. package/examples/tui-app/commands/math.ts +114 -0
  11. package/examples/tui-app/commands/status.ts +75 -0
  12. package/examples/tui-app/index.ts +34 -0
  13. package/guides/01-hello-world.md +96 -0
  14. package/guides/02-adding-options.md +103 -0
  15. package/guides/03-multiple-commands.md +163 -0
  16. package/guides/04-subcommands.md +206 -0
  17. package/guides/05-interactive-tui.md +194 -0
  18. package/guides/06-config-validation.md +264 -0
  19. package/guides/07-async-cancellation.md +388 -0
  20. package/guides/08-complete-application.md +673 -0
  21. package/guides/README.md +74 -0
  22. package/package.json +32 -0
  23. package/src/__tests__/application.test.ts +425 -0
  24. package/src/__tests__/buildCliCommand.test.ts +125 -0
  25. package/src/__tests__/builtins.test.ts +133 -0
  26. package/src/__tests__/colors.test.ts +127 -0
  27. package/src/__tests__/command.test.ts +157 -0
  28. package/src/__tests__/commandClass.test.ts +130 -0
  29. package/src/__tests__/context.test.ts +97 -0
  30. package/src/__tests__/help.test.ts +412 -0
  31. package/src/__tests__/parser.test.ts +268 -0
  32. package/src/__tests__/registry.test.ts +195 -0
  33. package/src/__tests__/registryNew.test.ts +160 -0
  34. package/src/__tests__/schemaToFields.test.ts +176 -0
  35. package/src/__tests__/table.test.ts +146 -0
  36. package/src/__tests__/tui.test.ts +26 -0
  37. package/src/builtins/help.ts +85 -0
  38. package/src/builtins/index.ts +4 -0
  39. package/src/builtins/settings.ts +106 -0
  40. package/src/builtins/version.ts +72 -0
  41. package/src/cli/help.ts +174 -0
  42. package/src/cli/index.ts +3 -0
  43. package/src/cli/output/colors.ts +74 -0
  44. package/src/cli/output/index.ts +2 -0
  45. package/src/cli/output/table.ts +141 -0
  46. package/src/cli/parser.ts +241 -0
  47. package/src/commands/help.ts +50 -0
  48. package/src/commands/index.ts +1 -0
  49. package/src/components/index.ts +147 -0
  50. package/src/core/application.ts +461 -0
  51. package/src/core/command.ts +269 -0
  52. package/src/core/context.ts +112 -0
  53. package/src/core/help.ts +214 -0
  54. package/src/core/index.ts +15 -0
  55. package/src/core/logger.ts +164 -0
  56. package/src/core/registry.ts +140 -0
  57. package/src/hooks/index.ts +131 -0
  58. package/src/index.ts +137 -0
  59. package/src/registry/commandRegistry.ts +77 -0
  60. package/src/registry/index.ts +1 -0
  61. package/src/tui/TuiApp.tsx +582 -0
  62. package/src/tui/TuiApplication.tsx +230 -0
  63. package/src/tui/app.ts +29 -0
  64. package/src/tui/components/ActionButton.tsx +36 -0
  65. package/src/tui/components/CliModal.tsx +81 -0
  66. package/src/tui/components/CommandSelector.tsx +159 -0
  67. package/src/tui/components/ConfigForm.tsx +148 -0
  68. package/src/tui/components/EditorModal.tsx +177 -0
  69. package/src/tui/components/FieldRow.tsx +30 -0
  70. package/src/tui/components/Header.tsx +31 -0
  71. package/src/tui/components/JsonHighlight.tsx +128 -0
  72. package/src/tui/components/LogsPanel.tsx +86 -0
  73. package/src/tui/components/ResultsPanel.tsx +93 -0
  74. package/src/tui/components/StatusBar.tsx +59 -0
  75. package/src/tui/components/index.ts +13 -0
  76. package/src/tui/components/types.ts +30 -0
  77. package/src/tui/context/KeyboardContext.tsx +118 -0
  78. package/src/tui/context/index.ts +7 -0
  79. package/src/tui/hooks/index.ts +35 -0
  80. package/src/tui/hooks/useClipboard.ts +66 -0
  81. package/src/tui/hooks/useCommandExecutor.ts +131 -0
  82. package/src/tui/hooks/useConfigState.ts +171 -0
  83. package/src/tui/hooks/useKeyboardHandler.ts +91 -0
  84. package/src/tui/hooks/useLogStream.ts +96 -0
  85. package/src/tui/hooks/useSpinner.ts +46 -0
  86. package/src/tui/index.ts +65 -0
  87. package/src/tui/theme.ts +21 -0
  88. package/src/tui/utils/buildCliCommand.ts +90 -0
  89. package/src/tui/utils/index.ts +13 -0
  90. package/src/tui/utils/parameterPersistence.ts +96 -0
  91. package/src/tui/utils/schemaToFields.ts +144 -0
  92. package/src/types/command.ts +103 -0
  93. package/src/types/execution.ts +11 -0
  94. package/src/types/index.ts +1 -0
  95. package/tsconfig.json +25 -0
@@ -0,0 +1,75 @@
1
+ import {
2
+ Command,
3
+ type AppContext,
4
+ type OptionSchema,
5
+ type OptionValues,
6
+ type CommandResult
7
+ } from "../../../src/index.ts";
8
+
9
+ const statusOptions = {
10
+ detailed: {
11
+ type: "boolean",
12
+ description: "Show detailed status",
13
+ default: false,
14
+ label: "Detailed",
15
+ order: 1,
16
+ },
17
+ } as const satisfies OptionSchema;
18
+
19
+ export class StatusCommand extends Command<typeof statusOptions> {
20
+ readonly name = "status";
21
+ readonly description = "Show application status";
22
+ readonly options = statusOptions;
23
+
24
+ override readonly actionLabel = "Check Status";
25
+ override readonly immediateExecution = true; // No required fields
26
+
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");
30
+ return result;
31
+ }
32
+
33
+ override renderResult(result: CommandResult): string {
34
+ if (!result.success) return "❌ Status check failed";
35
+ const data = result.data as { uptime: string; memory: string; platform: string; version: string };
36
+ return [
37
+ "✓ Application Status",
38
+ "",
39
+ ` Uptime: ${data.uptime}`,
40
+ ` Memory: ${data.memory}`,
41
+ ` Platform: ${data.platform}`,
42
+ ` Node: ${data.version}`,
43
+ ].join("\n");
44
+ }
45
+
46
+ private async getStatus(opts: OptionValues<typeof statusOptions>): Promise<CommandResult> {
47
+ const detailed = opts.detailed as boolean;
48
+
49
+ // Simulate some async work
50
+ await new Promise((resolve) => setTimeout(resolve, 500));
51
+
52
+ const memMB = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
53
+ const uptimeSec = Math.round(process.uptime());
54
+
55
+ const data: Record<string, unknown> = {
56
+ uptime: `${uptimeSec} seconds`,
57
+ memory: `${memMB} MB`,
58
+ platform: process.platform,
59
+ version: Bun.version,
60
+ };
61
+
62
+ if (detailed) {
63
+ Object.assign(data, {
64
+ cwd: process.cwd(),
65
+ pid: process.pid,
66
+ });
67
+ }
68
+
69
+ return {
70
+ success: true,
71
+ data,
72
+ message: "All systems operational",
73
+ };
74
+ }
75
+ }
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Example TUI Application
4
+ *
5
+ * Demonstrates how to use TuiApplication to build a CLI/TUI app
6
+ * with minimal effort. Just run:
7
+ *
8
+ * bun examples/tui-app/index.ts
9
+ *
10
+ * Or in CLI mode:
11
+ *
12
+ * bun examples/tui-app/index.ts greet --name "World" --loud
13
+ */
14
+
15
+ import { TuiApplication } from "../../src/index.ts";
16
+ import { GreetCommand, MathCommand, StatusCommand } from "./commands/index.ts";
17
+
18
+ class ExampleApp extends TuiApplication {
19
+ constructor() {
20
+ super({
21
+ name: "example",
22
+ version: "1.0.0",
23
+ commands: [
24
+ new GreetCommand(),
25
+ new MathCommand(),
26
+ new StatusCommand(),
27
+ ],
28
+ enableTui: true,
29
+ });
30
+ }
31
+ }
32
+
33
+ // Run the app
34
+ await new ExampleApp().run(Bun.argv.slice(2));
@@ -0,0 +1,96 @@
1
+ # Guide 1: Hello World CLI (Super Simple)
2
+
3
+ Create your first CLI app with Terminatui in under 5 minutes.
4
+
5
+ ## What You'll Build
6
+
7
+ A simple CLI that greets the user by name.
8
+
9
+ ```bash
10
+ myapp greet --name Alice
11
+ # Output: Hello, Alice!
12
+ ```
13
+
14
+ ## Prerequisites
15
+
16
+ - Bun installed (`curl -fsSL https://bun.sh/install | bash`)
17
+
18
+ ## Step 1: Create Project
19
+
20
+ ```bash
21
+ mkdir hello-cli && cd hello-cli
22
+ bun init -y
23
+ bun add @pablozaiden/terminatui
24
+ ```
25
+
26
+ ## Step 2: Create the Command
27
+
28
+ Create `src/greet.ts`:
29
+
30
+ ```typescript
31
+ import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
32
+
33
+ const options = {
34
+ name: {
35
+ type: "string",
36
+ description: "Name to greet",
37
+ required: true,
38
+ },
39
+ } satisfies OptionSchema;
40
+
41
+ export class GreetCommand extends Command<typeof options> {
42
+ readonly name = "greet";
43
+ readonly description = "Greet someone";
44
+ readonly options = options;
45
+
46
+ execute(_ctx: AppContext, config: { name: string }): CommandResult {
47
+ console.log(`Hello, ${config.name}!`);
48
+ return { success: true };
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## Step 3: Create the App
54
+
55
+ Create `src/index.ts`:
56
+
57
+ ```typescript
58
+ import { Application } from "@pablozaiden/terminatui";
59
+ import { GreetCommand } from "./greet";
60
+
61
+ class MyApp extends Application {
62
+ constructor() {
63
+ super({
64
+ name: "myapp",
65
+ version: "1.0.0",
66
+ commands: [new GreetCommand()],
67
+ });
68
+ }
69
+ }
70
+
71
+ await new MyApp().run();
72
+ ```
73
+
74
+ ## Step 4: Run It
75
+
76
+ ```bash
77
+ bun src/index.ts greet --name Alice
78
+ # Output: Hello, Alice!
79
+
80
+ bun src/index.ts help
81
+ # Shows available commands
82
+
83
+ bun src/index.ts greet help
84
+ # Shows greet options
85
+ ```
86
+
87
+ ## What You Learned
88
+
89
+ - Commands extend the `Command` class
90
+ - Options are defined with `OptionSchema`
91
+ - The `execute()` method handles the logic
92
+ - `Application` ties everything together
93
+
94
+ ## Next Steps
95
+
96
+ → [Guide 2: Adding Options](02-adding-options.md)
@@ -0,0 +1,103 @@
1
+ # Guide 2: Adding Options (Super Simple)
2
+
3
+ Add boolean and string options to make your CLI more flexible.
4
+
5
+ ## What You'll Build
6
+
7
+ Extend the greeting command with a `--loud` flag:
8
+
9
+ ```bash
10
+ myapp greet --name Alice
11
+ # Output: Hello, Alice!
12
+
13
+ myapp greet --name Alice --loud
14
+ # Output: HELLO, ALICE!
15
+ ```
16
+
17
+ ## Step 1: Add the Option
18
+
19
+ Update `src/greet.ts`:
20
+
21
+ ```typescript
22
+ import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
23
+
24
+ const options = {
25
+ name: {
26
+ type: "string",
27
+ description: "Name to greet",
28
+ required: true,
29
+ },
30
+ loud: {
31
+ type: "boolean",
32
+ description: "Shout the greeting",
33
+ alias: "l", // Short flag: -l
34
+ default: false,
35
+ },
36
+ } satisfies OptionSchema;
37
+
38
+ export class GreetCommand extends Command<typeof options> {
39
+ readonly name = "greet";
40
+ readonly description = "Greet someone";
41
+ readonly options = options;
42
+
43
+ execute(_ctx: AppContext, config: { name: string; loud: boolean }): CommandResult {
44
+ const message = `Hello, ${config.name}!`;
45
+ console.log(config.loud ? message.toUpperCase() : message);
46
+ return { success: true };
47
+ }
48
+ }
49
+ ```
50
+
51
+ ## Step 2: Test It
52
+
53
+ ```bash
54
+ # Normal greeting
55
+ bun src/index.ts greet --name Alice
56
+ # Hello, Alice!
57
+
58
+ # Loud greeting with long flag
59
+ bun src/index.ts greet --name Bob --loud
60
+ # HELLO, BOB!
61
+
62
+ # Loud greeting with short flag
63
+ bun src/index.ts greet --name Charlie -l
64
+ # HELLO, CHARLIE!
65
+
66
+ # View help to see options
67
+ bun src/index.ts greet help
68
+ ```
69
+
70
+ ## Key Concepts
71
+
72
+ ### Option Types
73
+
74
+ | Type | Description | Example |
75
+ |------|-------------|---------|
76
+ | `string` | Text value | `--name Alice` |
77
+ | `boolean` | Flag (true/false) | `--loud` or `--no-loud` |
78
+ | `number` | Numeric value | `--count 5` |
79
+ | `array` | Multiple values | `--tags a --tags b` |
80
+
81
+ ### Option Properties
82
+
83
+ ```typescript
84
+ {
85
+ type: "string",
86
+ description: "Help text", // Required
87
+ required: true, // Must be provided
88
+ default: "value", // Default if not provided
89
+ alias: "n", // Short flag (-n)
90
+ enum: ["a", "b", "c"], // Restrict to values
91
+ }
92
+ ```
93
+
94
+ ## What You Learned
95
+
96
+ - Add options with different types
97
+ - Use `alias` for short flags (-l instead of --loud)
98
+ - Use `default` for optional values
99
+ - Boolean flags can be negated with `--no-` prefix
100
+
101
+ ## Next Steps
102
+
103
+ → [Guide 3: Multiple Commands](03-multiple-commands.md)
@@ -0,0 +1,163 @@
1
+ # Guide 3: Multiple Commands (Basic)
2
+
3
+ Build a CLI with multiple commands that share common functionality.
4
+
5
+ ## What You'll Build
6
+
7
+ A file utility CLI with `list` and `count` commands:
8
+
9
+ ```bash
10
+ fileutil list --dir ./src
11
+ # Lists files in directory
12
+
13
+ fileutil count --dir ./src --ext .ts
14
+ # Counts files with extension
15
+ ```
16
+
17
+ ## Step 1: Create the List Command
18
+
19
+ Create `src/commands/list.ts`:
20
+
21
+ ```typescript
22
+ import { readdirSync } from "fs";
23
+ import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
24
+
25
+ const options = {
26
+ dir: {
27
+ type: "string",
28
+ description: "Directory to list",
29
+ required: true,
30
+ alias: "d",
31
+ },
32
+ } satisfies OptionSchema;
33
+
34
+ export class ListCommand extends Command<typeof options> {
35
+ readonly name = "list";
36
+ readonly description = "List files in a directory";
37
+ readonly options = options;
38
+
39
+ execute(_ctx: AppContext, config: { dir: string }): CommandResult {
40
+ try {
41
+ const files = readdirSync(config.dir);
42
+ console.log(`Files in ${config.dir}:`);
43
+ files.forEach((file) => console.log(` ${file}`));
44
+ return { success: true, message: `Found ${files.length} files` };
45
+ } catch (error) {
46
+ return { success: false, error: String(error) };
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ ## Step 2: Create the Count Command
53
+
54
+ Create `src/commands/count.ts`:
55
+
56
+ ```typescript
57
+ import { readdirSync } from "fs";
58
+ import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
59
+
60
+ const options = {
61
+ dir: {
62
+ type: "string",
63
+ description: "Directory to search",
64
+ required: true,
65
+ alias: "d",
66
+ },
67
+ ext: {
68
+ type: "string",
69
+ description: "File extension to count (e.g., .ts)",
70
+ alias: "e",
71
+ },
72
+ } satisfies OptionSchema;
73
+
74
+ export class CountCommand extends Command<typeof options> {
75
+ readonly name = "count";
76
+ readonly description = "Count files in a directory";
77
+ readonly options = options;
78
+
79
+ execute(_ctx: AppContext, config: { dir: string; ext?: string }): CommandResult {
80
+ try {
81
+ let files = readdirSync(config.dir);
82
+
83
+ if (config.ext) {
84
+ files = files.filter((f) => f.endsWith(config.ext!));
85
+ }
86
+
87
+ console.log(`Found ${files.length} files${config.ext ? ` with ${config.ext}` : ""}`);
88
+ return { success: true, data: { count: files.length } };
89
+ } catch (error) {
90
+ return { success: false, error: String(error) };
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ ## Step 3: Create the Application
97
+
98
+ Create `src/index.ts`:
99
+
100
+ ```typescript
101
+ import { Application } from "@pablozaiden/terminatui";
102
+ import { ListCommand } from "./commands/list";
103
+ import { CountCommand } from "./commands/count";
104
+
105
+ class FileUtilApp extends Application {
106
+ constructor() {
107
+ super({
108
+ name: "fileutil",
109
+ version: "1.0.0",
110
+ description: "File utility commands",
111
+ commands: [
112
+ new ListCommand(),
113
+ new CountCommand(),
114
+ ],
115
+ });
116
+ }
117
+ }
118
+
119
+ await new FileUtilApp().run();
120
+ ```
121
+
122
+ ## Step 4: Test It
123
+
124
+ ```bash
125
+ # List files
126
+ bun src/index.ts list --dir ./src
127
+ # Files in ./src:
128
+ # commands
129
+ # index.ts
130
+
131
+ # Count all files
132
+ bun src/index.ts count -d ./src
133
+ # Found 2 files
134
+
135
+ # Count only .ts files
136
+ bun src/index.ts count -d ./src -e .ts
137
+ # Found 1 files with .ts
138
+
139
+ # Show help
140
+ bun src/index.ts help
141
+ # Lists: list, count, version, help
142
+ ```
143
+
144
+ ## Project Structure
145
+
146
+ ```
147
+ src/
148
+ ├── index.ts # App entry point
149
+ └── commands/
150
+ ├── list.ts # List command
151
+ └── count.ts # Count command
152
+ ```
153
+
154
+ ## What You Learned
155
+
156
+ - Create multiple commands in separate files
157
+ - Return structured `CommandResult` with data
158
+ - Handle errors gracefully
159
+ - Use consistent option patterns across commands
160
+
161
+ ## Next Steps
162
+
163
+ → [Guide 4: Subcommands](04-subcommands.md)
@@ -0,0 +1,206 @@
1
+ # Guide 4: Subcommands (Basic)
2
+
3
+ Organize related commands under a parent command for cleaner CLI structure.
4
+
5
+ ## What You'll Build
6
+
7
+ A database CLI with nested subcommands:
8
+
9
+ ```bash
10
+ dbctl db migrate --target latest
11
+ dbctl db seed --file data.json
12
+ dbctl db status
13
+ ```
14
+
15
+ ## Step 1: Create the Subcommands
16
+
17
+ Create `src/commands/db/migrate.ts`:
18
+
19
+ ```typescript
20
+ import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
21
+
22
+ const options = {
23
+ target: {
24
+ type: "string",
25
+ description: "Migration target version",
26
+ default: "latest",
27
+ },
28
+ dry: {
29
+ type: "boolean",
30
+ description: "Dry run without applying",
31
+ default: false,
32
+ },
33
+ } satisfies OptionSchema;
34
+
35
+ export class MigrateCommand extends Command<typeof options> {
36
+ readonly name = "migrate";
37
+ readonly description = "Run database migrations";
38
+ readonly options = options;
39
+
40
+ execute(ctx: AppContext, config: { target: string; dry: boolean }): CommandResult {
41
+ ctx.logger.info(`Migrating to: ${config.target}`);
42
+
43
+ if (config.dry) {
44
+ console.log("DRY RUN: Would migrate to", config.target);
45
+ } else {
46
+ console.log("Migrating to", config.target, "...");
47
+ console.log("Migration complete!");
48
+ }
49
+
50
+ return { success: true };
51
+ }
52
+ }
53
+ ```
54
+
55
+ Create `src/commands/db/seed.ts`:
56
+
57
+ ```typescript
58
+ import { Command, type AppContext, type OptionSchema, type CommandResult } from "@pablozaiden/terminatui";
59
+
60
+ const options = {
61
+ file: {
62
+ type: "string",
63
+ description: "Seed data file",
64
+ required: true,
65
+ alias: "f",
66
+ },
67
+ } satisfies OptionSchema;
68
+
69
+ export class SeedCommand extends Command<typeof options> {
70
+ readonly name = "seed";
71
+ readonly description = "Seed the database with data";
72
+ readonly options = options;
73
+
74
+ execute(ctx: AppContext, config: { file: string }): CommandResult {
75
+ ctx.logger.info(`Seeding from: ${config.file}`);
76
+ console.log(`Loading seed data from ${config.file}...`);
77
+ console.log("Database seeded successfully!");
78
+ return { success: true };
79
+ }
80
+ }
81
+ ```
82
+
83
+ Create `src/commands/db/status.ts`:
84
+
85
+ ```typescript
86
+ import { Command, type AppContext, type CommandResult } from "@pablozaiden/terminatui";
87
+
88
+ export class StatusCommand extends Command {
89
+ readonly name = "status";
90
+ readonly description = "Show database status";
91
+ readonly options = {};
92
+
93
+ execute(_ctx: AppContext): CommandResult {
94
+ console.log("Database Status:");
95
+ console.log(" Connected: Yes");
96
+ console.log(" Version: 1.2.3");
97
+ console.log(" Migrations: 5 applied");
98
+ return { success: true };
99
+ }
100
+ }
101
+ ```
102
+
103
+ ## Step 2: Create the Parent Command
104
+
105
+ Create `src/commands/db/index.ts`:
106
+
107
+ ```typescript
108
+ import { Command, type AppContext, type CommandResult } from "@pablozaiden/terminatui";
109
+ import { MigrateCommand } from "./migrate";
110
+ import { SeedCommand } from "./seed";
111
+ import { StatusCommand } from "./status";
112
+
113
+ export class DbCommand extends Command {
114
+ readonly name = "db";
115
+ readonly description = "Database operations";
116
+ readonly options = {};
117
+
118
+ // Subcommands are nested here
119
+ override readonly subCommands = [
120
+ new MigrateCommand(),
121
+ new SeedCommand(),
122
+ new StatusCommand(),
123
+ ];
124
+
125
+ // Parent command can have its own execute (optional)
126
+ execute(_ctx: AppContext): CommandResult {
127
+ console.log("Use 'dbctl db <command>' for database operations.");
128
+ console.log("Available: migrate, seed, status");
129
+ return { success: true };
130
+ }
131
+ }
132
+ ```
133
+
134
+ ## Step 3: Create the Application
135
+
136
+ Create `src/index.ts`:
137
+
138
+ ```typescript
139
+ import { Application } from "@pablozaiden/terminatui";
140
+ import { DbCommand } from "./commands/db";
141
+
142
+ class DbCtlApp extends Application {
143
+ constructor() {
144
+ super({
145
+ name: "dbctl",
146
+ version: "1.0.0",
147
+ description: "Database control CLI",
148
+ commands: [new DbCommand()],
149
+ });
150
+ }
151
+ }
152
+
153
+ await new DbCtlApp().run();
154
+ ```
155
+
156
+ ## Step 4: Test It
157
+
158
+ ```bash
159
+ # Show db command help
160
+ bun src/index.ts db help
161
+ # Shows: migrate, seed, status
162
+
163
+ # Run migration
164
+ bun src/index.ts db migrate
165
+ # Migrating to latest...
166
+
167
+ # Dry run migration
168
+ bun src/index.ts db migrate --target v2 --dry
169
+ # DRY RUN: Would migrate to v2
170
+
171
+ # Seed database
172
+ bun src/index.ts db seed -f data.json
173
+ # Loading seed data from data.json...
174
+
175
+ # Check status
176
+ bun src/index.ts db status
177
+ # Database Status: ...
178
+
179
+ # Get help for subcommand
180
+ bun src/index.ts db migrate help
181
+ # Shows migrate options
182
+ ```
183
+
184
+ ## Project Structure
185
+
186
+ ```
187
+ src/
188
+ ├── index.ts
189
+ └── commands/
190
+ └── db/
191
+ ├── index.ts # Parent command
192
+ ├── migrate.ts # Subcommand
193
+ ├── seed.ts # Subcommand
194
+ └── status.ts # Subcommand
195
+ ```
196
+
197
+ ## What You Learned
198
+
199
+ - Group related commands under a parent
200
+ - Define subcommands with `subCommands` property
201
+ - Each subcommand has its own options
202
+ - Help is automatically nested (`db help`, `db migrate help`)
203
+
204
+ ## Next Steps
205
+
206
+ → [Guide 5: Interactive TUI](05-interactive-tui.md)