@pablozaiden/terminatui 0.3.0-beta-1 → 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/package.json +10 -3
- package/src/__tests__/configOnChange.test.ts +63 -0
- package/src/builtins/version.ts +1 -1
- package/src/index.ts +22 -0
- package/src/tui/adapters/ink/InkRenderer.tsx +4 -0
- package/src/tui/adapters/opentui/OpenTuiRenderer.tsx +4 -0
- package/src/tui/adapters/types.ts +1 -0
- package/src/tui/screens/ConfigScreen.tsx +6 -1
- package/src/tui/screens/ResultsScreen.tsx +9 -1
- package/src/tui/screens/RunningScreen.tsx +1 -1
- 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 -43
- package/CLAUDE.md +0 -1
- package/bun.lock +0 -321
- package/examples/tui-app/commands/config/app/get.ts +0 -62
- package/examples/tui-app/commands/config/app/index.ts +0 -23
- package/examples/tui-app/commands/config/app/set.ts +0 -96
- package/examples/tui-app/commands/config/index.ts +0 -28
- package/examples/tui-app/commands/config/user/get.ts +0 -61
- package/examples/tui-app/commands/config/user/index.ts +0 -23
- package/examples/tui-app/commands/config/user/set.ts +0 -57
- package/examples/tui-app/commands/greet.ts +0 -78
- package/examples/tui-app/commands/math.ts +0 -111
- package/examples/tui-app/commands/status.ts +0 -86
- package/examples/tui-app/index.ts +0 -38
- package/guides/01-hello-world.md +0 -101
- package/guides/02-adding-options.md +0 -103
- package/guides/03-multiple-commands.md +0 -161
- package/guides/04-subcommands.md +0 -206
- package/guides/05-interactive-tui.md +0 -209
- package/guides/06-config-validation.md +0 -256
- package/guides/07-async-cancellation.md +0 -334
- package/guides/08-complete-application.md +0 -507
- package/guides/README.md +0 -78
- package/tsconfig.json +0 -25
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { Command, type CommandResult } from "../../../../../src/core/command.ts";
|
|
2
|
-
import { UserGetCommand } from "./get.ts";
|
|
3
|
-
import { UserSetCommand } from "./set.ts";
|
|
4
|
-
|
|
5
|
-
export class UserConfigCommand extends Command {
|
|
6
|
-
readonly name = "user";
|
|
7
|
-
override displayName = "User Settings";
|
|
8
|
-
readonly description = "Manage user configuration";
|
|
9
|
-
readonly options = {};
|
|
10
|
-
|
|
11
|
-
override readonly subCommands = [
|
|
12
|
-
new UserGetCommand(),
|
|
13
|
-
new UserSetCommand(),
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
override execute(): CommandResult {
|
|
17
|
-
console.log("Use 'config user <command>' for user configuration.");
|
|
18
|
-
console.log("Available: get, set");
|
|
19
|
-
return { success: true };
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export { UserGetCommand, UserSetCommand };
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { Command, type CommandResult } from "../../../../../src/core/command";
|
|
2
|
-
import { AppContext } from "../../../../../src/core/context";
|
|
3
|
-
import type { OptionSchema, OptionValues } from "../../../../../src/types/command";
|
|
4
|
-
|
|
5
|
-
const options = {
|
|
6
|
-
key: {
|
|
7
|
-
type: "string",
|
|
8
|
-
description: "Configuration key to set",
|
|
9
|
-
required: true,
|
|
10
|
-
label: "Key",
|
|
11
|
-
order: 1,
|
|
12
|
-
group: "Required",
|
|
13
|
-
placeholder: "e.g., name, email, theme",
|
|
14
|
-
},
|
|
15
|
-
value: {
|
|
16
|
-
type: "string",
|
|
17
|
-
description: "Value to set",
|
|
18
|
-
required: true,
|
|
19
|
-
label: "Value",
|
|
20
|
-
order: 2,
|
|
21
|
-
group: "Required",
|
|
22
|
-
placeholder: "Enter value...",
|
|
23
|
-
},
|
|
24
|
-
} as const satisfies OptionSchema;
|
|
25
|
-
|
|
26
|
-
export class UserSetCommand extends Command<typeof options> {
|
|
27
|
-
readonly name = "set";
|
|
28
|
-
override displayName = "Set User Config";
|
|
29
|
-
readonly description = "Set a user configuration value";
|
|
30
|
-
readonly options = options;
|
|
31
|
-
|
|
32
|
-
override readonly actionLabel = "Set Value";
|
|
33
|
-
|
|
34
|
-
override readonly examples = [
|
|
35
|
-
{ command: "config user set --key name --value 'Jane Doe'", description: "Set user name" },
|
|
36
|
-
{ command: "config user set --key theme --value light", description: "Set theme" },
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
override async execute(opts: OptionValues<typeof options>): Promise<CommandResult> {
|
|
40
|
-
AppContext.current.logger.info(`Setting user.${opts.key} = "${opts.value}"`);
|
|
41
|
-
|
|
42
|
-
// Simulate setting the value
|
|
43
|
-
await new Promise(resolve => setTimeout(resolve, 300));
|
|
44
|
-
|
|
45
|
-
AppContext.current.logger.info(`Successfully updated user configuration`);
|
|
46
|
-
return {
|
|
47
|
-
success: true,
|
|
48
|
-
data: { key: opts.key, value: opts.value },
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
override renderResult(result: CommandResult): string {
|
|
53
|
-
if (!result.success) return result.message || "Error";
|
|
54
|
-
const data = result.data as { key: string; value: string };
|
|
55
|
-
return `✓ Set user.${data.key} = "${data.value}"`;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import { Command, type CommandResult } from "../../../src/core/command";
|
|
3
|
-
import { AppContext } from "../../../src/core/context";
|
|
4
|
-
import type { OptionSchema, OptionValues } from "../../../src/types/command";
|
|
5
|
-
import { JsonHighlight } from "../../../src/tui/components/JsonHighlight.tsx";
|
|
6
|
-
|
|
7
|
-
const greetOptions = {
|
|
8
|
-
name: {
|
|
9
|
-
type: "string",
|
|
10
|
-
description: "Name to greet",
|
|
11
|
-
required: true,
|
|
12
|
-
label: "Name",
|
|
13
|
-
order: 1,
|
|
14
|
-
group: "Required",
|
|
15
|
-
placeholder: "Enter name...",
|
|
16
|
-
},
|
|
17
|
-
loud: {
|
|
18
|
-
type: "boolean",
|
|
19
|
-
description: "Use uppercase",
|
|
20
|
-
alias: "l",
|
|
21
|
-
default: true,
|
|
22
|
-
label: "Loud Mode",
|
|
23
|
-
order: 2,
|
|
24
|
-
group: "Options",
|
|
25
|
-
},
|
|
26
|
-
times: {
|
|
27
|
-
type: "number",
|
|
28
|
-
description: "Number of times to greet",
|
|
29
|
-
default: 1,
|
|
30
|
-
label: "Repeat Count",
|
|
31
|
-
order: 3,
|
|
32
|
-
group: "Options",
|
|
33
|
-
},
|
|
34
|
-
} as const satisfies OptionSchema;
|
|
35
|
-
|
|
36
|
-
export class GreetCommand extends Command<typeof greetOptions> {
|
|
37
|
-
readonly name = "greet";
|
|
38
|
-
override displayName = "Greet";
|
|
39
|
-
readonly description = "Greet someone with a friendly message";
|
|
40
|
-
readonly options = greetOptions;
|
|
41
|
-
|
|
42
|
-
override readonly actionLabel = "Say Hello";
|
|
43
|
-
|
|
44
|
-
override readonly examples = [
|
|
45
|
-
{ command: "greet --name World", description: "Simple greeting" },
|
|
46
|
-
{ command: "greet --name World --loud --times 3", description: "Loud greeting 3 times" },
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
override async execute(opts: OptionValues<typeof greetOptions>): Promise<CommandResult> {
|
|
50
|
-
const greeting = this.createGreeting(opts);
|
|
51
|
-
AppContext.current.logger.trace(greeting);
|
|
52
|
-
return {
|
|
53
|
-
success: true,
|
|
54
|
-
data: { greeting, timestamp: new Date().toISOString(), meta: { loud: opts.loud, times: opts.times } },
|
|
55
|
-
message: greeting,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
override renderResult(result: CommandResult): ReactNode {
|
|
60
|
-
return JsonHighlight({ value: result.data });
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
override getClipboardContent(result: CommandResult): string | undefined {
|
|
64
|
-
const data = result.data as { greeting?: string } | undefined;
|
|
65
|
-
return data?.greeting;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private createGreeting(opts: OptionValues<typeof greetOptions>): string {
|
|
69
|
-
const name = opts.name as string;
|
|
70
|
-
const loud = opts.loud as boolean;
|
|
71
|
-
const times = (opts.times as number) || 1;
|
|
72
|
-
|
|
73
|
-
let message = `Hello, ${name}!`;
|
|
74
|
-
if (loud) message = message.toUpperCase();
|
|
75
|
-
|
|
76
|
-
return Array(times).fill(message).join("\n");
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { Command, type CommandResult } from "../../../src/core/command";
|
|
2
|
-
import { AppContext } from "../../../src/core/context";
|
|
3
|
-
import type { OptionSchema, OptionValues } from "../../../src/types/command";
|
|
4
|
-
|
|
5
|
-
const mathOptions = {
|
|
6
|
-
operation: {
|
|
7
|
-
type: "string",
|
|
8
|
-
description: "Math operation to perform",
|
|
9
|
-
required: true,
|
|
10
|
-
enum: ["add", "subtract", "multiply", "divide"] as const,
|
|
11
|
-
label: "Operation",
|
|
12
|
-
order: 1,
|
|
13
|
-
group: "Required",
|
|
14
|
-
},
|
|
15
|
-
a: {
|
|
16
|
-
type: "number",
|
|
17
|
-
description: "First number",
|
|
18
|
-
required: true,
|
|
19
|
-
label: "First Number",
|
|
20
|
-
order: 2,
|
|
21
|
-
group: "Required",
|
|
22
|
-
},
|
|
23
|
-
b: {
|
|
24
|
-
type: "number",
|
|
25
|
-
description: "Second number",
|
|
26
|
-
required: true,
|
|
27
|
-
label: "Second Number",
|
|
28
|
-
order: 3,
|
|
29
|
-
group: "Required",
|
|
30
|
-
},
|
|
31
|
-
showSteps: {
|
|
32
|
-
type: "boolean",
|
|
33
|
-
description: "Show calculation steps",
|
|
34
|
-
default: false,
|
|
35
|
-
label: "Show Steps",
|
|
36
|
-
order: 4,
|
|
37
|
-
group: "Options",
|
|
38
|
-
},
|
|
39
|
-
} as const satisfies OptionSchema;
|
|
40
|
-
|
|
41
|
-
export class MathCommand extends Command<typeof mathOptions> {
|
|
42
|
-
readonly name = "math";
|
|
43
|
-
override displayName = "Math Operations";
|
|
44
|
-
readonly description = "Perform basic math operations";
|
|
45
|
-
readonly options = mathOptions;
|
|
46
|
-
|
|
47
|
-
override readonly actionLabel = "Calculate";
|
|
48
|
-
|
|
49
|
-
override async execute(opts: OptionValues<typeof mathOptions>): Promise<CommandResult> {
|
|
50
|
-
const result = this.calculate(opts);
|
|
51
|
-
if (!result.success) {
|
|
52
|
-
AppContext.current.logger.error(result.message || "Calculation failed");
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
override renderResult(result: CommandResult): string {
|
|
58
|
-
if (!result.success) return result.message || "Error";
|
|
59
|
-
const data = result.data as { expression: string; result: number; steps?: string[] };
|
|
60
|
-
let output = `${data.expression} = ${data.result}`;
|
|
61
|
-
if (data.steps) {
|
|
62
|
-
output += "\n\nSteps:\n" + data.steps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
|
|
63
|
-
}
|
|
64
|
-
return output;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
private calculate(opts: OptionValues<typeof mathOptions>): CommandResult {
|
|
68
|
-
const op = opts.operation as string;
|
|
69
|
-
const a = opts.a as number;
|
|
70
|
-
const b = opts.b as number;
|
|
71
|
-
const showSteps = opts.showSteps as boolean;
|
|
72
|
-
|
|
73
|
-
let result: number;
|
|
74
|
-
let expression: string;
|
|
75
|
-
const steps: string[] = [];
|
|
76
|
-
|
|
77
|
-
switch (op) {
|
|
78
|
-
case "add":
|
|
79
|
-
result = a + b;
|
|
80
|
-
expression = `${a} + ${b}`;
|
|
81
|
-
if (showSteps) steps.push(`Adding ${a} and ${b}`, `Result: ${result}`);
|
|
82
|
-
break;
|
|
83
|
-
case "subtract":
|
|
84
|
-
result = a - b;
|
|
85
|
-
expression = `${a} - ${b}`;
|
|
86
|
-
if (showSteps) steps.push(`Subtracting ${b} from ${a}`, `Result: ${result}`);
|
|
87
|
-
break;
|
|
88
|
-
case "multiply":
|
|
89
|
-
result = a * b;
|
|
90
|
-
expression = `${a} × ${b}`;
|
|
91
|
-
if (showSteps) steps.push(`Multiplying ${a} by ${b}`, `Result: ${result}`);
|
|
92
|
-
break;
|
|
93
|
-
case "divide":
|
|
94
|
-
if (b === 0) {
|
|
95
|
-
return { success: false, message: "Cannot divide by zero" };
|
|
96
|
-
}
|
|
97
|
-
result = a / b;
|
|
98
|
-
expression = `${a} ÷ ${b}`;
|
|
99
|
-
if (showSteps) steps.push(`Dividing ${a} by ${b}`, `Result: ${result}`);
|
|
100
|
-
break;
|
|
101
|
-
default:
|
|
102
|
-
return { success: false, message: `Unknown operation: ${op}` };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
success: true,
|
|
107
|
-
data: { expression, result, steps: showSteps ? steps : undefined },
|
|
108
|
-
message: `${expression} = ${result}`,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { Command, type CommandExecutionContext, type CommandResult } from "../../../src/core/command";
|
|
2
|
-
import { AppContext } from "../../../src/core/context";
|
|
3
|
-
import type { OptionSchema, OptionValues } from "../../../src/types/command";
|
|
4
|
-
|
|
5
|
-
const statusOptions = {
|
|
6
|
-
detailed: {
|
|
7
|
-
type: "boolean",
|
|
8
|
-
description: "Show detailed status",
|
|
9
|
-
default: false,
|
|
10
|
-
label: "Detailed",
|
|
11
|
-
alias: "d",
|
|
12
|
-
order: 1,
|
|
13
|
-
},
|
|
14
|
-
} as const satisfies OptionSchema;
|
|
15
|
-
|
|
16
|
-
export class StatusCommand extends Command<typeof statusOptions> {
|
|
17
|
-
readonly name = "status";
|
|
18
|
-
override displayName = "Status";
|
|
19
|
-
readonly description = "Show application status";
|
|
20
|
-
readonly options = statusOptions;
|
|
21
|
-
|
|
22
|
-
override readonly actionLabel = "Check Status";
|
|
23
|
-
override readonly immediateExecution = true; // No required fields
|
|
24
|
-
|
|
25
|
-
override async execute(opts: OptionValues<typeof statusOptions>, execCtx : CommandExecutionContext): Promise<CommandResult> {
|
|
26
|
-
const result = await this.getStatus(opts, execCtx);
|
|
27
|
-
AppContext.current.logger.info(result.message || "Status check complete");
|
|
28
|
-
return result;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
override renderResult(result: CommandResult): string {
|
|
32
|
-
if (!result.success) return "❌ Status check failed";
|
|
33
|
-
const data = result.data as { uptime: string; memory: string; platform: string; version: string };
|
|
34
|
-
return [
|
|
35
|
-
"✓ Application Status",
|
|
36
|
-
"",
|
|
37
|
-
` Uptime: ${data.uptime}`,
|
|
38
|
-
` Memory: ${data.memory}`,
|
|
39
|
-
` Platform: ${data.platform}`,
|
|
40
|
-
` Bun: ${data.version}`,
|
|
41
|
-
].join("\n");
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
private async getStatus(opts: OptionValues<typeof statusOptions>, execCtx: CommandExecutionContext): Promise<CommandResult> {
|
|
45
|
-
const detailed = opts.detailed as boolean;
|
|
46
|
-
|
|
47
|
-
// Simulate some async work
|
|
48
|
-
await new Promise(resolve => {
|
|
49
|
-
let count = 0;
|
|
50
|
-
let interval = setInterval(() => {
|
|
51
|
-
count++;
|
|
52
|
-
AppContext.current.logger.info(`Applying configuration... (${count}/5)`);
|
|
53
|
-
if (count >= 5 || execCtx.signal.aborted) {
|
|
54
|
-
if (count < 5) {
|
|
55
|
-
AppContext.current.logger.warn("Status check aborted");
|
|
56
|
-
}
|
|
57
|
-
clearInterval(interval);
|
|
58
|
-
resolve(undefined);
|
|
59
|
-
}
|
|
60
|
-
}, 1000);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const memMB = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
64
|
-
const uptimeSec = Math.round(process.uptime());
|
|
65
|
-
|
|
66
|
-
const data: Record<string, unknown> = {
|
|
67
|
-
uptime: `${uptimeSec} seconds`,
|
|
68
|
-
memory: `${memMB} MB`,
|
|
69
|
-
platform: process.platform,
|
|
70
|
-
version: Bun.version,
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
if (detailed) {
|
|
74
|
-
Object.assign(data, {
|
|
75
|
-
cwd: process.cwd(),
|
|
76
|
-
pid: process.pid,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
success: true,
|
|
82
|
-
data,
|
|
83
|
-
message: "All systems operational",
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
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/tui/TuiApplication.tsx";
|
|
16
|
-
import { ConfigCommand } from "./commands/config/index.ts";
|
|
17
|
-
import { GreetCommand } from "./commands/greet.ts";
|
|
18
|
-
import { MathCommand } from "./commands/math.ts";
|
|
19
|
-
import { StatusCommand } from "./commands/status.ts";
|
|
20
|
-
|
|
21
|
-
class ExampleApp extends TuiApplication {
|
|
22
|
-
constructor() {
|
|
23
|
-
super({
|
|
24
|
-
name: "example",
|
|
25
|
-
version: "1.0.0",
|
|
26
|
-
commands: [
|
|
27
|
-
new GreetCommand(),
|
|
28
|
-
new MathCommand(),
|
|
29
|
-
new StatusCommand(),
|
|
30
|
-
new ConfigCommand(),
|
|
31
|
-
],
|
|
32
|
-
enableTui: true,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Run the app
|
|
38
|
-
await new ExampleApp().run();
|
package/guides/01-hello-world.md
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
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 {
|
|
32
|
-
Command,
|
|
33
|
-
type OptionSchema,
|
|
34
|
-
type CommandResult,
|
|
35
|
-
type CommandExecutionContext,
|
|
36
|
-
} from "@pablozaiden/terminatui";
|
|
37
|
-
|
|
38
|
-
const options = {
|
|
39
|
-
name: {
|
|
40
|
-
type: "string",
|
|
41
|
-
description: "Name to greet",
|
|
42
|
-
required: true,
|
|
43
|
-
},
|
|
44
|
-
} satisfies OptionSchema;
|
|
45
|
-
|
|
46
|
-
export class GreetCommand extends Command<typeof options> {
|
|
47
|
-
readonly name = "greet";
|
|
48
|
-
readonly description = "Greet someone";
|
|
49
|
-
readonly options = options;
|
|
50
|
-
|
|
51
|
-
execute(config: { name: string }, _execCtx: CommandExecutionContext): CommandResult {
|
|
52
|
-
console.log(`Hello, ${config.name}!`);
|
|
53
|
-
return { success: true };
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Step 3: Create the App
|
|
59
|
-
|
|
60
|
-
Create `src/index.ts`:
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
import { Application } from "@pablozaiden/terminatui";
|
|
64
|
-
import { GreetCommand } from "./greet";
|
|
65
|
-
|
|
66
|
-
class MyApp extends Application {
|
|
67
|
-
constructor() {
|
|
68
|
-
super({
|
|
69
|
-
name: "myapp",
|
|
70
|
-
version: "1.0.0",
|
|
71
|
-
commands: [new GreetCommand()],
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
await new MyApp().run();
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Step 4: Run It
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
bun src/index.ts greet --name Alice
|
|
83
|
-
# Output: Hello, Alice!
|
|
84
|
-
|
|
85
|
-
bun src/index.ts help
|
|
86
|
-
# Shows available commands
|
|
87
|
-
|
|
88
|
-
bun src/index.ts greet help
|
|
89
|
-
# Shows greet options
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## What You Learned
|
|
93
|
-
|
|
94
|
-
- Commands extend the `Command` class
|
|
95
|
-
- Options are defined with `OptionSchema`
|
|
96
|
-
- The `execute()` method handles the logic
|
|
97
|
-
- `Application` ties everything together
|
|
98
|
-
|
|
99
|
-
## Next Steps
|
|
100
|
-
|
|
101
|
-
→ [Guide 2: Adding Options](02-adding-options.md)
|
|
@@ -1,103 +0,0 @@
|
|
|
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 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(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)
|