@pablozaiden/terminatui 0.3.0-beta-1 → 0.4.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 (142) hide show
  1. package/package.json +10 -3
  2. package/src/__tests__/adapterNoSharedUi.test.ts +34 -0
  3. package/src/__tests__/configOnChange.test.ts +63 -0
  4. package/src/__tests__/schemaToFields.test.ts +0 -4
  5. package/src/__tests__/tuiRootNoCoupling.test.ts +25 -0
  6. package/src/builtins/version.ts +1 -1
  7. package/src/index.ts +22 -0
  8. package/src/tui/TuiApplication.tsx +0 -4
  9. package/src/tui/TuiRoot.tsx +58 -102
  10. package/src/tui/actions.ts +4 -0
  11. package/src/tui/adapters/ink/InkRenderer.tsx +191 -41
  12. package/src/tui/adapters/ink/SemanticInkRenderer.tsx +210 -0
  13. package/src/tui/adapters/ink/components/Button.tsx +10 -2
  14. package/src/tui/adapters/ink/components/Overlay.tsx +8 -2
  15. package/src/tui/adapters/ink/components/Panel.tsx +26 -5
  16. package/src/tui/adapters/ink/components/ScrollView.tsx +44 -3
  17. package/src/tui/adapters/ink/components/Spinner.tsx +8 -2
  18. package/src/tui/adapters/ink/keyboard.ts +0 -3
  19. package/src/tui/adapters/ink/ui/CommandSelector.tsx +56 -0
  20. package/src/tui/adapters/ink/ui/ConfigForm.tsx +77 -0
  21. package/src/tui/adapters/ink/ui/Header.tsx +25 -0
  22. package/src/tui/adapters/ink/ui/JsonHighlight.tsx +21 -0
  23. package/src/tui/adapters/ink/ui/ResultsPanel.tsx +57 -0
  24. package/src/tui/adapters/opentui/OpenTuiRenderer.tsx +190 -39
  25. package/src/tui/adapters/opentui/SemanticOpenTuiRenderer.tsx +192 -0
  26. package/src/tui/adapters/opentui/components/Label.tsx +2 -2
  27. package/src/tui/adapters/opentui/components/Overlay.tsx +12 -3
  28. package/src/tui/adapters/opentui/components/Panel.tsx +11 -1
  29. package/src/tui/adapters/opentui/components/ScrollView.tsx +1 -8
  30. package/src/tui/adapters/opentui/components/Spinner.tsx +1 -1
  31. package/src/tui/adapters/opentui/keyboard.ts +0 -3
  32. package/src/tui/adapters/opentui/ui/CommandSelector.tsx +55 -0
  33. package/src/tui/adapters/opentui/ui/ConfigForm.tsx +74 -0
  34. package/src/tui/adapters/opentui/ui/Header.tsx +24 -0
  35. package/src/tui/adapters/opentui/ui/JsonHighlight.tsx +20 -0
  36. package/src/tui/adapters/opentui/ui/LogsPanel.tsx +44 -0
  37. package/src/tui/adapters/opentui/ui/ResultsPanel.tsx +62 -0
  38. package/src/tui/adapters/shared/TerminalClipboard.ts +65 -0
  39. package/src/tui/adapters/{opentui/hooks → shared}/useSpinner.ts +5 -1
  40. package/src/tui/adapters/types.ts +25 -45
  41. package/src/tui/components/JsonHighlight.tsx +41 -111
  42. package/src/tui/context/ActionContext.tsx +51 -0
  43. package/src/tui/context/ExecutorContext.tsx +7 -1
  44. package/src/tui/context/NavigationContext.tsx +20 -4
  45. package/src/tui/controllers/CommandBrowserController.tsx +100 -0
  46. package/src/tui/controllers/ConfigController.tsx +183 -0
  47. package/src/tui/controllers/EditorController.tsx +169 -0
  48. package/src/tui/controllers/LogsController.tsx +48 -0
  49. package/src/tui/controllers/OutcomeController.tsx +110 -0
  50. package/src/tui/driver/TuiDriver.tsx +148 -0
  51. package/src/tui/driver/context/TuiDriverContext.tsx +44 -0
  52. package/src/tui/driver/types.ts +72 -0
  53. package/src/tui/semantic/AppShell.tsx +30 -0
  54. package/src/tui/semantic/CommandBrowserScreen.tsx +16 -0
  55. package/src/tui/semantic/ConfigScreen.tsx +23 -0
  56. package/src/tui/semantic/EditorScreen.tsx +20 -0
  57. package/src/tui/semantic/LogsScreen.tsx +9 -0
  58. package/src/tui/semantic/RunningScreen.tsx +17 -0
  59. package/src/tui/semantic/layoutTypes.ts +72 -0
  60. package/src/tui/semantic/render.tsx +44 -0
  61. package/src/tui/semantic/types.ts +31 -98
  62. package/src/tui/utils/jsonTokenizer.ts +98 -0
  63. package/src/tui/utils/schemaToFields.ts +1 -25
  64. package/.devcontainer/devcontainer.json +0 -19
  65. package/.devcontainer/install-prerequisites.sh +0 -49
  66. package/.github/workflows/copilot-setup-steps.yml +0 -32
  67. package/.github/workflows/pull-request.yml +0 -27
  68. package/.github/workflows/release-npm-package.yml +0 -81
  69. package/AGENTS.md +0 -43
  70. package/CLAUDE.md +0 -1
  71. package/bun.lock +0 -321
  72. package/examples/tui-app/commands/config/app/get.ts +0 -62
  73. package/examples/tui-app/commands/config/app/index.ts +0 -23
  74. package/examples/tui-app/commands/config/app/set.ts +0 -96
  75. package/examples/tui-app/commands/config/index.ts +0 -28
  76. package/examples/tui-app/commands/config/user/get.ts +0 -61
  77. package/examples/tui-app/commands/config/user/index.ts +0 -23
  78. package/examples/tui-app/commands/config/user/set.ts +0 -57
  79. package/examples/tui-app/commands/greet.ts +0 -78
  80. package/examples/tui-app/commands/math.ts +0 -111
  81. package/examples/tui-app/commands/status.ts +0 -86
  82. package/examples/tui-app/index.ts +0 -38
  83. package/guides/01-hello-world.md +0 -101
  84. package/guides/02-adding-options.md +0 -103
  85. package/guides/03-multiple-commands.md +0 -161
  86. package/guides/04-subcommands.md +0 -206
  87. package/guides/05-interactive-tui.md +0 -209
  88. package/guides/06-config-validation.md +0 -256
  89. package/guides/07-async-cancellation.md +0 -334
  90. package/guides/08-complete-application.md +0 -507
  91. package/guides/README.md +0 -78
  92. package/src/tui/adapters/ink/components/Code.tsx +0 -6
  93. package/src/tui/adapters/ink/components/Container.tsx +0 -5
  94. package/src/tui/adapters/ink/components/Spacer.tsx +0 -15
  95. package/src/tui/adapters/ink/components/Value.tsx +0 -7
  96. package/src/tui/adapters/opentui/components/Code.tsx +0 -12
  97. package/src/tui/adapters/opentui/components/Container.tsx +0 -56
  98. package/src/tui/adapters/opentui/components/Spacer.tsx +0 -5
  99. package/src/tui/adapters/opentui/components/Value.tsx +0 -13
  100. package/src/tui/components/ActionButton.tsx +0 -0
  101. package/src/tui/components/CommandSelector.tsx +0 -119
  102. package/src/tui/components/ConfigForm.tsx +0 -174
  103. package/src/tui/components/FieldRow.tsx +0 -0
  104. package/src/tui/components/Header.tsx +0 -32
  105. package/src/tui/components/ModalBase.tsx +0 -38
  106. package/src/tui/components/ResultsPanel.tsx +0 -84
  107. package/src/tui/components/StatusBar.tsx +0 -44
  108. package/src/tui/components/logColors.ts +0 -12
  109. package/src/tui/components/types.ts +0 -30
  110. package/src/tui/context/ClipboardContext.tsx +0 -87
  111. package/src/tui/context/KeyboardContext.tsx +0 -132
  112. package/src/tui/hooks/useActiveKeyHandler.ts +0 -75
  113. package/src/tui/hooks/useClipboard.ts +0 -81
  114. package/src/tui/hooks/useClipboardProvider.ts +0 -42
  115. package/src/tui/hooks/useGlobalKeyHandler.ts +0 -54
  116. package/src/tui/modals/CliModal.tsx +0 -82
  117. package/src/tui/modals/EditorModal.tsx +0 -207
  118. package/src/tui/modals/LogsModal.tsx +0 -98
  119. package/src/tui/registry.ts +0 -102
  120. package/src/tui/screens/CommandSelectScreen.tsx +0 -162
  121. package/src/tui/screens/ConfigScreen.tsx +0 -160
  122. package/src/tui/screens/ErrorScreen.tsx +0 -58
  123. package/src/tui/screens/ResultsScreen.tsx +0 -60
  124. package/src/tui/screens/RunningScreen.tsx +0 -72
  125. package/src/tui/screens/ScreenBase.ts +0 -6
  126. package/src/tui/semantic/Button.tsx +0 -7
  127. package/src/tui/semantic/Code.tsx +0 -7
  128. package/src/tui/semantic/CodeHighlight.tsx +0 -7
  129. package/src/tui/semantic/Container.tsx +0 -7
  130. package/src/tui/semantic/Field.tsx +0 -7
  131. package/src/tui/semantic/Label.tsx +0 -7
  132. package/src/tui/semantic/MenuButton.tsx +0 -7
  133. package/src/tui/semantic/MenuItem.tsx +0 -7
  134. package/src/tui/semantic/Overlay.tsx +0 -7
  135. package/src/tui/semantic/Panel.tsx +0 -7
  136. package/src/tui/semantic/ScrollView.tsx +0 -9
  137. package/src/tui/semantic/Select.tsx +0 -7
  138. package/src/tui/semantic/Spacer.tsx +0 -7
  139. package/src/tui/semantic/Spinner.tsx +0 -7
  140. package/src/tui/semantic/TextInput.tsx +0 -7
  141. package/src/tui/semantic/Value.tsx +0 -7
  142. package/tsconfig.json +0 -25
@@ -1,507 +0,0 @@
1
- # Guide 8: Building a Complete Application (Complex)
2
-
3
- Bring together everything to build a production-ready CLI application with multiple commands, services, logging, and a polished TUI.
4
-
5
- ## What You'll Build
6
-
7
- A task management CLI with:
8
- - Multiple commands: list, add, complete, stats
9
- - Shared services (database, notifications)
10
- - Logging with configurable verbosity
11
- - Full TUI support with custom result rendering
12
- - Config validation and type safety
13
-
14
- ```bash
15
- tasks add "Write documentation" --priority high
16
- tasks list --filter pending
17
- tasks complete abc123
18
- tasks stats
19
- tasks --tui # Interactive mode
20
- ```
21
-
22
- ## Project Structure
23
-
24
- ```
25
- src/
26
- ├── index.ts # Application entry
27
- ├── services/
28
- │ ├── database.ts # Task storage
29
- │ └── notifications.ts # Task notifications
30
- ├── commands/
31
- │ ├── add.ts
32
- │ ├── list.ts
33
- │ ├── complete.ts
34
- │ └── stats.ts
35
- └── types.ts # Shared types
36
- ```
37
-
38
- ## Step 1: Define Shared Types
39
-
40
- Create `src/types.ts`:
41
-
42
- ```typescript
43
- export interface Task {
44
- id: string;
45
- title: string;
46
- priority: "low" | "medium" | "high";
47
- status: "pending" | "completed";
48
- createdAt: Date;
49
- completedAt?: Date;
50
- }
51
-
52
- export interface TaskStats {
53
- total: number;
54
- pending: number;
55
- completed: number;
56
- byPriority: Record<string, number>;
57
- }
58
- ```
59
-
60
- ## Step 2: Create Services
61
-
62
- Create `src/services/database.ts`:
63
-
64
- ```typescript
65
- import type { Task, TaskStats } from "../types";
66
-
67
- // In-memory database (replace with real DB in production)
68
- const tasks: Map<string, Task> = new Map();
69
-
70
- export class Database {
71
- async addTask(title: string, priority: Task["priority"]): Promise<Task> {
72
- const id = Math.random().toString(36).substring(7);
73
- const task: Task = {
74
- id,
75
- title,
76
- priority,
77
- status: "pending",
78
- createdAt: new Date(),
79
- };
80
- tasks.set(id, task);
81
- return task;
82
- }
83
-
84
- async getTask(id: string): Promise<Task | undefined> {
85
- return tasks.get(id);
86
- }
87
-
88
- async listTasks(filter?: "pending" | "completed" | "all"): Promise<Task[]> {
89
- const all = Array.from(tasks.values());
90
- if (!filter || filter === "all") return all;
91
- return all.filter((t) => t.status === filter);
92
- }
93
-
94
- async completeTask(id: string): Promise<Task | undefined> {
95
- const task = tasks.get(id);
96
- if (task) {
97
- task.status = "completed";
98
- task.completedAt = new Date();
99
- tasks.set(id, task);
100
- }
101
- return task;
102
- }
103
-
104
- async getStats(): Promise<TaskStats> {
105
- const all = Array.from(tasks.values());
106
- return {
107
- total: all.length,
108
- pending: all.filter((t) => t.status === "pending").length,
109
- completed: all.filter((t) => t.status === "completed").length,
110
- byPriority: {
111
- high: all.filter((t) => t.priority === "high").length,
112
- medium: all.filter((t) => t.priority === "medium").length,
113
- low: all.filter((t) => t.priority === "low").length,
114
- },
115
- };
116
- }
117
- }
118
- ```
119
-
120
- Create `src/services/notifications.ts`:
121
-
122
- ```typescript
123
- import type { Task } from "../types";
124
- import type { Logger } from "@pablozaiden/terminatui";
125
-
126
- export class NotificationService {
127
- constructor(private logger: Logger) {}
128
-
129
- taskAdded(task: Task): void {
130
- this.logger.info(`📝 Task added: ${task.title} [${task.priority}]`);
131
- if (task.priority === "high") {
132
- this.logger.warn(`⚠️ High priority task created!`);
133
- }
134
- }
135
-
136
- taskCompleted(task: Task): void {
137
- this.logger.info(`✅ Task completed: ${task.title}`);
138
- const duration = task.completedAt!.getTime() - task.createdAt.getTime();
139
- const hours = Math.floor(duration / 3600000);
140
- if (hours > 24) {
141
- this.logger.debug(`Task took ${hours} hours to complete`);
142
- }
143
- }
144
- }
145
- ```
146
-
147
- ## Step 3: Create Commands
148
-
149
- Create `src/commands/add.ts`:
150
-
151
- ```typescript
152
- import {
153
- Command,
154
- ConfigValidationError,
155
- type OptionSchema,
156
- type OptionValues,
157
- type CommandResult,
158
- } from "@pablozaiden/terminatui";
159
- import { Database } from "../services/database";
160
- import { NotificationService } from "../services/notifications";
161
- import type { Task } from "../types";
162
-
163
- const options = {
164
- title: {
165
- type: "string",
166
- description: "Task title",
167
- required: true,
168
- label: "Task Title",
169
- order: 1,
170
- },
171
- priority: {
172
- type: "string",
173
- description: "Task priority",
174
- default: "medium",
175
- enum: ["low", "medium", "high"],
176
- label: "Priority",
177
- order: 2,
178
- },
179
- } satisfies OptionSchema;
180
-
181
- interface AddConfig {
182
- title: string;
183
- priority: Task["priority"];
184
- }
185
-
186
- export class AddCommand extends Command<typeof options, AddConfig> {
187
- readonly name = "add";
188
- readonly description = "Add a new task";
189
- readonly options = options;
190
- readonly displayName = "Add Task";
191
- readonly actionLabel = "Create Task";
192
-
193
- private db = new Database();
194
-
195
- override buildConfig(opts: OptionValues<typeof options>): AddConfig {
196
- const title = (opts["title"] as string)?.trim();
197
- if (!title) {
198
- throw new ConfigValidationError("Task title cannot be empty", "title");
199
- }
200
- if (title.length > 200) {
201
- throw new ConfigValidationError(
202
- "Task title must be 200 characters or less",
203
- "title"
204
- );
205
- }
206
-
207
- const priority = opts["priority"] as Task["priority"] ?? "medium";
208
-
209
- return { title, priority };
210
- }
211
-
212
- async execute(config: AddConfig): Promise<CommandResult> {
213
- const task = await this.db.addTask(config.title, config.priority);
214
-
215
- // (Example) optional: log to console or your own logger
216
- console.log(`Created task: ${task.title}`);
217
-
218
- return {
219
- success: true,
220
- data: task,
221
- message: `Created task: ${task.title} (${task.id})`,
222
- };
223
- }
224
- }
225
- ```
226
-
227
- Create `src/commands/list.ts`:
228
-
229
- ```typescript
230
- import {
231
- Command,
232
- type OptionSchema,
233
- type OptionValues,
234
- type CommandResult,
235
- } from "@pablozaiden/terminatui";
236
- import { Database } from "../services/database";
237
- import type { Task } from "../types";
238
-
239
- const options = {
240
- filter: {
241
- type: "string",
242
- description: "Filter by status",
243
- default: "all",
244
- enum: ["all", "pending", "completed"],
245
- label: "Status Filter",
246
- },
247
- priority: {
248
- type: "string",
249
- description: "Filter by priority",
250
- enum: ["low", "medium", "high"],
251
- label: "Priority Filter",
252
- },
253
- } satisfies OptionSchema;
254
-
255
- interface ListConfig {
256
- filter: "all" | "pending" | "completed";
257
- priority?: Task["priority"];
258
- }
259
-
260
- export class ListCommand extends Command<typeof options, ListConfig> {
261
- readonly name = "list";
262
- readonly description = "List tasks";
263
- readonly options = options;
264
- readonly displayName = "List Tasks";
265
- readonly actionLabel = "Show Tasks";
266
-
267
- // Execute immediately when selected in TUI
268
- readonly immediateExecution = true;
269
-
270
- private db = new Database();
271
-
272
- override buildConfig(opts: OptionValues<typeof options>): ListConfig {
273
- return {
274
- filter: (opts["filter"] as ListConfig["filter"]) ?? "all",
275
- priority: opts["priority"] as Task["priority"] | undefined,
276
- };
277
- }
278
-
279
- async execute(config: ListConfig): Promise<CommandResult> {
280
- let tasks = await this.db.listTasks(config.filter);
281
-
282
- if (config.priority) {
283
- tasks = tasks.filter((t) => t.priority === config.priority);
284
- }
285
-
286
- return {
287
- success: true,
288
- data: tasks,
289
- message: `Found ${tasks.length} tasks`,
290
- };
291
- }
292
- }
293
- ```
294
-
295
- Create `src/commands/complete.ts`:
296
-
297
- ```typescript
298
- import {
299
- Command,
300
- ConfigValidationError,
301
- type OptionSchema,
302
- type OptionValues,
303
- type CommandResult,
304
- } from "@pablozaiden/terminatui";
305
- import { Database } from "../services/database";
306
- import { NotificationService } from "../services/notifications";
307
- import type { Task } from "../types";
308
-
309
- const options = {
310
- id: {
311
- type: "string",
312
- description: "Task ID to complete",
313
- required: true,
314
- label: "Task ID",
315
- },
316
- } satisfies OptionSchema;
317
-
318
- interface CompleteConfig {
319
- id: string;
320
- }
321
-
322
- export class CompleteCommand extends Command<typeof options, CompleteConfig> {
323
- readonly name = "complete";
324
- readonly description = "Mark a task as complete";
325
- readonly options = options;
326
- readonly displayName = "Complete Task";
327
- readonly actionLabel = "Mark Complete";
328
-
329
- private db = new Database();
330
-
331
- override buildConfig(opts: OptionValues<typeof options>): CompleteConfig {
332
- const id = opts["id"] as string;
333
- if (!id?.trim()) {
334
- throw new ConfigValidationError("Task ID is required", "id");
335
- }
336
- return { id: id.trim() };
337
- }
338
-
339
- async execute(config: CompleteConfig): Promise<CommandResult> {
340
- const task = await this.db.completeTask(config.id);
341
-
342
- if (!task) {
343
- return {
344
- success: false,
345
- message: `Task not found: ${config.id}`,
346
- };
347
- }
348
-
349
- console.log(`Completed task: ${task.title}`);
350
-
351
- return {
352
- success: true,
353
- data: task,
354
- message: `Completed: ${task.title}`,
355
- };
356
- }
357
- }
358
- ```
359
-
360
- Create `src/commands/stats.ts`:
361
-
362
- ```typescript
363
- import {
364
- Command,
365
- type OptionSchema,
366
- type CommandResult,
367
- } from "@pablozaiden/terminatui";
368
- import { Database } from "../services/database";
369
- import type { TaskStats } from "../types";
370
-
371
- const options = {} satisfies OptionSchema;
372
-
373
- export class StatsCommand extends Command<typeof options> {
374
- readonly name = "stats";
375
- readonly description = "Show task statistics";
376
- readonly options = options;
377
- readonly displayName = "Statistics";
378
- readonly actionLabel = "Show Stats";
379
- readonly immediateExecution = true;
380
-
381
- private db = new Database();
382
-
383
- async execute(): Promise<CommandResult> {
384
- const stats = await this.db.getStats();
385
-
386
- return {
387
- success: true,
388
- data: stats,
389
- message: `Total: ${stats.total}, Pending: ${stats.pending}, Completed: ${stats.completed}`,
390
- };
391
- }
392
- }
393
- ```
394
-
395
- ## Step 4: Create the Application
396
-
397
- Create `src/index.ts`:
398
-
399
- ```typescript
400
- import { TuiApplication } from "@pablozaiden/terminatui";
401
- import { AddCommand } from "./commands/add";
402
- import { ListCommand } from "./commands/list";
403
- import { CompleteCommand } from "./commands/complete";
404
- import { StatsCommand } from "./commands/stats";
405
-
406
- class TasksCLI extends TuiApplication {
407
- constructor() {
408
- super({
409
- name: "tasks",
410
- version: "1.0.0",
411
- description: "A simple task management CLI",
412
- commands: [
413
- new AddCommand(),
414
- new ListCommand(),
415
- new CompleteCommand(),
416
- new StatsCommand(),
417
- ],
418
- });
419
- }
420
-
421
- // Optional: Override onBeforeRun for setup
422
- protected override async onBeforeRun(): Promise<void> {
423
- this.logger.debug("Tasks CLI starting...");
424
- // Initialize database connections, load config, etc.
425
- }
426
-
427
- // Optional: Override onAfterRun for cleanup
428
- protected override async onAfterRun(): Promise<void> {
429
- this.logger.debug("Tasks CLI shutting down...");
430
- // Close database connections, save state, etc.
431
- }
432
- }
433
-
434
- await new TasksCLI().run();
435
- ```
436
-
437
- ## Step 5: Configure Package
438
-
439
- Update `package.json`:
440
-
441
- ```json
442
- {
443
- "name": "tasks-cli",
444
- "version": "1.0.0",
445
- "type": "module",
446
- "bin": {
447
- "tasks": "./src/index.ts"
448
- },
449
- "scripts": {
450
- "start": "bun src/index.ts",
451
- "tui": "bun src/index.ts --tui",
452
- "build": "bun build src/index.ts --outdir dist --target bun"
453
- },
454
- "dependencies": {
455
- "@pablozaiden/terminatui": "^1.0.0"
456
- }
457
- }
458
- ```
459
-
460
- ## Step 6: Run the Application
461
-
462
- ```bash
463
- # CLI Mode
464
- bun start add "Write documentation" --priority high
465
- bun start list --filter pending
466
- bun start complete abc123
467
- bun start stats
468
-
469
- # TUI Mode
470
- bun tui
471
-
472
- # With verbose logging
473
- bun start --verbose add "Debug task" --priority low
474
- ```
475
-
476
- ## TUI Feature Summary
477
-
478
- | Feature | Implementation |
479
- |---------|----------------|
480
- | Command Groups | `group = "Tasks"` or `"Analytics"` |
481
- | Command Order | `order = 1, 2, 3` |
482
- | Display Names | `displayName = "Add Task"` |
483
- | Action Labels | `actionLabel = "Create Task"` |
484
- | Immediate Execution | `immediateExecution = true` |
485
- | Clipboard Support | `getClipboardContent()` |
486
-
487
- ## What You Learned
488
-
489
- - **Project Structure**: Organize code into commands, services, types
490
- - **Shared Services**: Database and notification services
491
- - **Command Groups**: Organize commands in TUI sidebar
492
- - **Full TUI Integration**: Clipboard, immediate execution
493
- - **Lifecycle Hooks**: `onBeforeRun` and `onAfterRun`
494
- - **Production Patterns**: Error handling, validation, logging
495
-
496
- ## Complete Series Summary
497
-
498
- 1. **Hello World** - Basic command structure
499
- 2. **Adding Options** - Option types and defaults
500
- 3. **Multiple Commands** - Command organization
501
- 4. **Subcommands** - Nested command hierarchies
502
- 5. **Interactive TUI** - TuiApplication basics
503
- 6. **Config Validation** - buildConfig and validation
504
- 7. **Async Cancellation** - Cancellable operations
505
- 8. **Complete Application** - Full production app
506
-
507
- You now have all the tools to build powerful CLI applications with terminatui! 🎉
package/guides/README.md DELETED
@@ -1,78 +0,0 @@
1
- # Terminatui Framework Guides
2
-
3
- Step-by-step tutorials for building CLI applications with the terminatui framework.
4
-
5
- ## Guide Overview
6
-
7
- | # | Guide | Level | Topics |
8
- |---|-------|-------|--------|
9
- | 1 | [Hello World](01-hello-world.md) | Super Simple | Basic Command, Application |
10
- | 2 | [Adding Options](02-adding-options.md) | Super Simple | Option types, defaults, aliases |
11
- | 3 | [Multiple Commands](03-multiple-commands.md) | Basic | Multiple commands, project structure |
12
- | 4 | [Subcommands](04-subcommands.md) | Basic | Nested subcommands, hierarchies |
13
- | 5 | [Interactive TUI](05-interactive-tui.md) | Normal | TuiApplication, metadata, keyboard |
14
- | 6 | [Config Validation](06-config-validation.md) | Normal | buildConfig, ConfigValidationError |
15
- | 7 | [Async Cancellation](07-async-cancellation.md) | Complex | AbortSignal, cancellation, cleanup |
16
- | 8 | [Complete Application](08-complete-application.md) | Complex | Full app, services, best practices |
17
-
18
- ## Learning Path
19
-
20
- ### Beginners
21
- Start with guides 1-2 to understand the basics of commands and options.
22
-
23
- ### Intermediate
24
- Continue with guides 3-4 to learn about organizing larger applications.
25
-
26
- ### Advanced
27
- Work through guides 5-8 to master TUI features, validation, and production patterns.
28
-
29
- ## Quick Start
30
-
31
- ```bash
32
- # Create a new project
33
- mkdir my-cli && cd my-cli
34
- bun init -y
35
- bun add @pablozaiden/terminatui
36
-
37
- # Create your first command
38
- cat > src/index.ts << 'EOF'
39
- import { Command, Application, type OptionSchema } from "@pablozaiden/terminatui";
40
-
41
- const options = {
42
- name: { type: "string", description: "Your name" },
43
- } satisfies OptionSchema;
44
-
45
- class HelloCommand extends Command<typeof options> {
46
- readonly name = "hello";
47
- readonly description = "Say hello";
48
- readonly options = options;
49
-
50
- async execute(config: Record<string, unknown>) {
51
- const name = config["name"] ?? "World";
52
- console.log(`Hello, ${name}!`);
53
- return { success: true };
54
- }
55
- }
56
-
57
- class MyCLI extends Application {
58
- constructor() {
59
- super({ name: "my-cli", version: "1.0.0", commands: [new HelloCommand()] });
60
- }
61
- }
62
-
63
- // Recommended: let Terminatui read `Bun.argv.slice(2)`
64
- await new MyCLI().run();
65
-
66
- // For tests or programmatic invocation:
67
- // await new MyCLI().runFromArgs(["hello", "--name", "Developer"]);
68
- EOF
69
-
70
- # Run it
71
- bun src/index.ts hello --name "Developer"
72
- ```
73
-
74
- ## Prerequisites
75
-
76
- - [Bun](https://bun.sh)
77
- - Basic TypeScript knowledge
78
- - Terminal/command-line familiarity
@@ -1,6 +0,0 @@
1
- import { Text } from "ink";
2
- import type { CodeProps } from "../../../semantic/types.ts";
3
-
4
- export function Code({ children }: CodeProps) {
5
- return <Text color="gray">{children}</Text>;
6
- }
@@ -1,5 +0,0 @@
1
- import type { ContainerProps } from "../../../semantic/types.ts";
2
-
3
- export function Container({ children }: ContainerProps) {
4
- return <>{children}</>;
5
- }
@@ -1,15 +0,0 @@
1
- import { Text } from "ink";
2
- import type { SpacerProps } from "../../../semantic/types.ts";
3
-
4
- export function Spacer({ size, axis }: SpacerProps) {
5
- if (axis === "horizontal") {
6
- return <Text>{" ".repeat(size)}</Text>;
7
- }
8
- return (
9
- <>
10
- {Array.from({ length: size }).map((_, idx) => (
11
- <Text key={idx} />
12
- ))}
13
- </>
14
- );
15
- }
@@ -1,7 +0,0 @@
1
- import { Text } from "ink";
2
- import type { ValueProps } from "../../../semantic/types.ts";
3
- import { toPlainText } from "../utils.ts";
4
-
5
- export function Value({ children }: ValueProps) {
6
- return <Text color="magenta">{toPlainText(children)}</Text>;
7
- }
@@ -1,12 +0,0 @@
1
- import type { CodeProps } from "../../../semantic/types.ts";
2
- import { SemanticColors } from "../../../theme.ts";
3
-
4
- export function Code({ color = "code", children }: CodeProps) {
5
- const fg = SemanticColors[color] ?? SemanticColors.code;
6
-
7
- return (
8
- <text fg={fg}>
9
- {children}
10
- </text>
11
- );
12
- }
@@ -1,56 +0,0 @@
1
- import type { ReactNode } from "react";
2
- import type { ContainerProps, Spacing } from "../../../semantic/types.ts";
3
-
4
- function normalizePadding(padding: number | Spacing | undefined):
5
- | { padding?: number; paddingTop?: number; paddingRight?: number; paddingBottom?: number; paddingLeft?: number }
6
- | undefined {
7
- if (padding === undefined) {
8
- return undefined;
9
- }
10
-
11
- if (typeof padding === "number") {
12
- return { padding };
13
- }
14
-
15
- return {
16
- paddingTop: padding.top ?? 0,
17
- paddingRight: padding.right ?? 0,
18
- paddingBottom: padding.bottom ?? 0,
19
- paddingLeft: padding.left ?? 0,
20
- };
21
- }
22
-
23
- export function Container({
24
- children,
25
- flex,
26
- width,
27
- height,
28
- flexDirection,
29
- alignItems,
30
- justifyContent,
31
- gap,
32
- padding,
33
- noShrink,
34
- }: ContainerProps & { children?: ReactNode }) {
35
- const resolvedPadding = normalizePadding(padding);
36
-
37
- return (
38
- <box
39
- flexGrow={flex}
40
- flexShrink={noShrink ? 0 : flex === undefined ? undefined : 1}
41
- width={width as any}
42
- height={height as any}
43
- flexDirection={flexDirection as any}
44
- alignItems={alignItems as any}
45
- justifyContent={justifyContent as any}
46
- gap={gap}
47
- padding={resolvedPadding?.padding}
48
- paddingTop={resolvedPadding?.paddingTop}
49
- paddingRight={resolvedPadding?.paddingRight}
50
- paddingBottom={resolvedPadding?.paddingBottom}
51
- paddingLeft={resolvedPadding?.paddingLeft}
52
- >
53
- {children}
54
- </box>
55
- );
56
- }
@@ -1,5 +0,0 @@
1
- import type { SpacerProps } from "../../../semantic/types.ts";
2
-
3
- export function Spacer({ size, axis = "vertical" }: SpacerProps) {
4
- return axis === "horizontal" ? <box width={size} flexShrink={0} /> : <box height={size} flexShrink={0} />;
5
- }