@rlabs-inc/create-tui 0.1.5 → 0.2.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 (39) hide show
  1. package/README.md +129 -0
  2. package/dist/index.js +221 -0
  3. package/package.json +20 -15
  4. package/templates/counter/README.md +88 -0
  5. package/templates/counter/package.json +18 -0
  6. package/templates/counter/src/App.ts +65 -0
  7. package/templates/counter/src/components/Counter.ts +78 -0
  8. package/templates/counter/src/components/CounterPanel.ts +74 -0
  9. package/templates/counter/src/components/Header.ts +55 -0
  10. package/templates/counter/src/components/HistoryPanel.ts +96 -0
  11. package/templates/counter/src/components/KeyBindings.ts +60 -0
  12. package/templates/counter/src/components/StatsPanel.ts +101 -0
  13. package/templates/counter/src/main.ts +87 -0
  14. package/templates/counter/src/state/counters.ts +121 -0
  15. package/templates/counter/tsconfig.json +16 -0
  16. package/templates/dashboard/README.md +95 -0
  17. package/templates/dashboard/package.json +18 -0
  18. package/templates/dashboard/src/App.ts +72 -0
  19. package/templates/dashboard/src/components/Footer.ts +102 -0
  20. package/templates/dashboard/src/components/Header.ts +108 -0
  21. package/templates/dashboard/src/components/LogsPanel.ts +98 -0
  22. package/templates/dashboard/src/components/MetricsPanel.ts +145 -0
  23. package/templates/dashboard/src/components/Sidebar.ts +162 -0
  24. package/templates/dashboard/src/components/TrafficPanel.ts +129 -0
  25. package/templates/dashboard/src/main.ts +66 -0
  26. package/templates/dashboard/src/state/logs.ts +42 -0
  27. package/templates/dashboard/src/state/metrics.ts +129 -0
  28. package/templates/dashboard/src/state/theme.ts +20 -0
  29. package/templates/dashboard/tsconfig.json +16 -0
  30. package/templates/minimal/README.md +98 -0
  31. package/templates/minimal/package.json +18 -0
  32. package/templates/minimal/src/App.ts +108 -0
  33. package/templates/minimal/src/components/Header.ts +52 -0
  34. package/templates/minimal/src/main.ts +24 -0
  35. package/templates/minimal/tsconfig.json +16 -0
  36. package/src/commands/create.ts +0 -282
  37. package/src/index.ts +0 -75
  38. package/src/utils/colors.ts +0 -132
  39. package/src/utils/prompts.ts +0 -273
@@ -0,0 +1,24 @@
1
+ /**
2
+ * {{PROJECT_NAME}} - TUI Application
3
+ *
4
+ * Entry point for your TUI application.
5
+ * Run with: bun run dev
6
+ */
7
+
8
+ import { mount, keyboard } from '@rlabs-inc/tui'
9
+ import { App } from './App'
10
+
11
+ async function main() {
12
+ // Mount the application in fullscreen mode
13
+ const cleanup = await mount(
14
+ () => App(),
15
+ { mode: 'fullscreen', mouse: true }
16
+ )
17
+
18
+ // Global keyboard shortcuts
19
+ keyboard.onKey(['q', 'Q', 'Escape'], () => {
20
+ cleanup()
21
+ })
22
+ }
23
+
24
+ main().catch(console.error)
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "declaration": true,
10
+ "outDir": "dist",
11
+ "rootDir": "src",
12
+ "types": ["bun-types"]
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }
@@ -1,282 +0,0 @@
1
- /**
2
- * Create command - scaffold a new TUI Framework project
3
- */
4
-
5
- import { mkdir, exists } from 'fs/promises'
6
- import { join, resolve, basename } from 'path'
7
- import { $ } from 'bun'
8
- import { c, symbols, box } from '../utils/colors'
9
- import { text, confirm, spinner } from '../utils/prompts'
10
-
11
- interface CreateOptions {
12
- projectName?: string
13
- template?: string
14
- }
15
-
16
- export async function create(options: CreateOptions) {
17
- console.log()
18
- console.log(box([
19
- `${c.brightCyan(symbols.sparkle)} ${c.bold('Create TUI Project')} ${c.brightCyan(symbols.sparkle)}`,
20
- '',
21
- c.dim('Scaffold a new TUI Framework application'),
22
- ], { padding: 1 }))
23
- console.log()
24
-
25
- // Get project name
26
- let projectName = options.projectName
27
-
28
- if (!projectName) {
29
- projectName = await text({
30
- message: 'Project name',
31
- placeholder: 'my-tui-app',
32
- defaultValue: 'my-tui-app',
33
- validate: (value) => {
34
- if (!value) return 'Project name is required'
35
- if (!/^[a-z0-9-_]+$/i.test(value)) {
36
- return 'Project name can only contain letters, numbers, dashes, and underscores'
37
- }
38
- return true
39
- }
40
- })
41
- }
42
-
43
- const projectPath = resolve(process.cwd(), projectName)
44
- const displayName = basename(projectPath)
45
-
46
- // Check if directory exists
47
- if (await exists(projectPath)) {
48
- const overwrite = await confirm({
49
- message: `Directory ${c.bold(displayName)} already exists. Overwrite?`,
50
- defaultValue: false
51
- })
52
-
53
- if (!overwrite) {
54
- console.log(`${c.muted(symbols.cross)} Cancelled`)
55
- return
56
- }
57
- }
58
-
59
- console.log()
60
-
61
- // Create project
62
- const spin = spinner('Creating project structure...')
63
-
64
- try {
65
- // Create directories
66
- await mkdir(join(projectPath, 'src', 'components'), { recursive: true })
67
-
68
- // Write files using Bun.write (faster than fs.writeFile)
69
- await Promise.all([
70
- Bun.write(join(projectPath, 'package.json'), PACKAGE_JSON(displayName)),
71
- Bun.write(join(projectPath, 'bunfig.toml'), BUNFIG_TOML),
72
- Bun.write(join(projectPath, 'tsconfig.json'), TSCONFIG_JSON),
73
- Bun.write(join(projectPath, '.gitignore'), GITIGNORE),
74
- Bun.write(join(projectPath, 'README.md'), README(displayName)),
75
- Bun.write(join(projectPath, 'src', 'main.ts'), MAIN_TS),
76
- Bun.write(join(projectPath, 'src', 'App.tui'), APP_TUI),
77
- Bun.write(join(projectPath, 'src', 'components', 'Counter.tui'), COUNTER_TUI),
78
- ])
79
-
80
- spin.stop('Project structure created')
81
-
82
- // Install dependencies using Bun shell
83
- const installSpin = spinner('Installing dependencies...')
84
-
85
- try {
86
- await $`cd ${projectPath} && bun install`.quiet()
87
- installSpin.stop('Dependencies installed')
88
- } catch {
89
- installSpin.stop()
90
- console.log(`${c.warning(symbols.bullet)} Run ${c.info('bun install')} manually to install dependencies`)
91
- }
92
-
93
- // Success!
94
- console.log()
95
- console.log(box([
96
- `${c.success(symbols.check)} ${c.bold('Project created!')}`,
97
- '',
98
- `${c.dim('Next steps:')}`,
99
- '',
100
- ` ${c.muted('$')} ${c.info(`cd ${displayName}`)}`,
101
- ` ${c.muted('$')} ${c.info('bun run dev')}`,
102
- '',
103
- c.dim('Happy hacking!'),
104
- ], { title: ` ${symbols.star} Success `, padding: 1 }))
105
- console.log()
106
-
107
- } catch (error) {
108
- spin.stop()
109
- throw error
110
- }
111
- }
112
-
113
- // Template files
114
-
115
- const PACKAGE_JSON = (name: string) => `{
116
- "name": "${name}",
117
- "version": "0.1.0",
118
- "type": "module",
119
- "scripts": {
120
- "dev": "bun run src/main.ts",
121
- "build": "bun build src/main.ts --outfile dist/main.js --target bun"
122
- },
123
- "dependencies": {
124
- "@rlabs-inc/tui": "latest",
125
- "@rlabs-inc/signals": "latest"
126
- },
127
- "devDependencies": {
128
- "@rlabs-inc/tui-compiler": "latest",
129
- "@types/bun": "latest",
130
- "typescript": "^5.0.0"
131
- }
132
- }
133
- `
134
-
135
- const BUNFIG_TOML = `# TUI Framework configuration
136
- # The compiler plugin enables .tui file imports
137
-
138
- preload = ["@rlabs-inc/tui-compiler/register"]
139
- `
140
-
141
- const TSCONFIG_JSON = `{
142
- "compilerOptions": {
143
- "target": "ESNext",
144
- "module": "ESNext",
145
- "moduleResolution": "bundler",
146
- "strict": true,
147
- "esModuleInterop": true,
148
- "skipLibCheck": true,
149
- "forceConsistentCasingInFileNames": true,
150
- "resolveJsonModule": true,
151
- "declaration": true,
152
- "declarationMap": true,
153
- "noEmit": true,
154
- "types": ["bun-types"]
155
- },
156
- "include": ["src/**/*"],
157
- "exclude": ["node_modules", "dist"]
158
- }
159
- `
160
-
161
- const GITIGNORE = `# Dependencies
162
- node_modules/
163
-
164
- # Build output
165
- dist/
166
-
167
- # Environment
168
- .env
169
- .env.local
170
-
171
- # IDE
172
- .vscode/
173
- .idea/
174
- *.swp
175
- *.swo
176
-
177
- # OS
178
- .DS_Store
179
- Thumbs.db
180
-
181
- # Debug
182
- *.log
183
- `
184
-
185
- const README = (name: string) => `# ${name}
186
-
187
- A TUI Framework application.
188
-
189
- ## Getting Started
190
-
191
- \`\`\`bash
192
- # Run in development
193
- bun run dev
194
-
195
- # Build for production
196
- bun run build
197
- \`\`\`
198
-
199
- ## Project Structure
200
-
201
- \`\`\`
202
- ${name}/
203
- ├── src/
204
- │ ├── main.ts # Entry point
205
- │ ├── App.tui # Root component
206
- │ └── components/ # Reusable components
207
- │ └── Counter.tui
208
- ├── bunfig.toml # Bun configuration (enables .tui compiler)
209
- ├── package.json
210
- └── tsconfig.json
211
- \`\`\`
212
-
213
- ## Learn More
214
-
215
- - [TUI Framework Documentation](https://github.com/rlabs-inc/tui)
216
- - [Signals Library](https://github.com/rlabs-inc/signals)
217
- `
218
-
219
- const MAIN_TS = `/**
220
- * TUI Application Entry Point
221
- */
222
-
223
- import '@rlabs-inc/tui-compiler/register'
224
- import { mount, keyboard } from '@rlabs-inc/tui'
225
- import App from './App.tui'
226
-
227
- async function main() {
228
- // Mount the application
229
- const cleanup = await mount(() => {
230
- App()
231
- })
232
-
233
- // Handle exit (q or Ctrl+C)
234
- keyboard.on((event) => {
235
- if (event.key === 'q' || (event.modifiers.ctrl && event.key === 'c')) {
236
- cleanup().then(() => process.exit(0))
237
- }
238
- })
239
- }
240
-
241
- main().catch(console.error)
242
- `
243
-
244
- const APP_TUI = `<script lang="ts">
245
- import Counter from './components/Counter.tui'
246
- </script>
247
-
248
- <box
249
- width="100%"
250
- height="100%"
251
- flexDirection="column"
252
- justifyContent="center"
253
- alignItems="center"
254
- gap={2}
255
- >
256
- <text variant="accent">Welcome to TUI Framework</text>
257
- <Counter />
258
- <text variant="muted">Press Q to quit</text>
259
- </box>
260
- `
261
-
262
- const COUNTER_TUI = `<script lang="ts">
263
- const count = signal(0)
264
-
265
- keyboard.onKey(['+', '=', 'ArrowUp'], () => count.value++)
266
- keyboard.onKey(['-', '_', 'ArrowDown'], () => count.value--)
267
- keyboard.onKey('r', () => count.value = 0)
268
- </script>
269
-
270
- <box
271
- border={1}
272
- borderColor={t.primary}
273
- padding={1}
274
- flexDirection="column"
275
- alignItems="center"
276
- gap={1}
277
- >
278
- <text fg={t.accent}>Counter</text>
279
- <text fg={t.textBright}>{count}</text>
280
- <text fg={t.textMuted}>[+/-] change [r] reset</text>
281
- </box>
282
- `
package/src/index.ts DELETED
@@ -1,75 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * create-tui - Create TUI Framework applications
4
- *
5
- * Usage:
6
- * bun create tui my-app
7
- * bunx create-tui my-app
8
- * npx create-tui my-app
9
- */
10
-
11
- import { parseArgs } from 'util'
12
- import { c, symbols } from './utils/colors'
13
- import { create } from './commands/create'
14
-
15
- const VERSION = '0.1.4'
16
-
17
- const HELP = `
18
- ${c.bold('@rlabs-inc/create-tui')} ${c.muted(`v${VERSION}`)}
19
-
20
- ${c.dim('Create TUI Framework applications')}
21
- ${c.dim('The Terminal UI Framework for TypeScript/Bun')}
22
-
23
- ${c.bold('Usage:')}
24
- ${c.info('bunx @rlabs-inc/create-tui')} ${c.muted('<project-name>')}
25
- ${c.info('npx @rlabs-inc/create-tui')} ${c.muted('<project-name>')}
26
-
27
- ${c.bold('Options:')}
28
- ${c.muted('-h, --help')} Show this help message
29
- ${c.muted('-v, --version')} Show version
30
-
31
- ${c.bold('Examples:')}
32
- ${c.dim('$')} ${c.info('bunx @rlabs-inc/create-tui')} my-app
33
- ${c.dim('$')} ${c.info('bunx @rlabs-inc/create-tui')} dashboard
34
-
35
- ${c.muted('Documentation: https://github.com/rlabs-inc/tui')}
36
- `
37
-
38
- async function main() {
39
- try {
40
- const { values, positionals } = parseArgs({
41
- args: Bun.argv.slice(2),
42
- options: {
43
- help: { type: 'boolean', short: 'h' },
44
- version: { type: 'boolean', short: 'v' },
45
- template: { type: 'string', short: 't' },
46
- },
47
- allowPositionals: true,
48
- strict: false,
49
- })
50
-
51
- // Help flag
52
- if (values.help) {
53
- console.log(HELP)
54
- return
55
- }
56
-
57
- // Version flag
58
- if (values.version) {
59
- console.log(`${c.bold('@rlabs-inc/create-tui')} ${c.muted(`v${VERSION}`)}`)
60
- return
61
- }
62
-
63
- // Get project name from positional args
64
- const projectName = positionals[0]
65
-
66
- // Run create command (will prompt for name if not provided)
67
- await create({ projectName, template: values.template as string | undefined })
68
-
69
- } catch (error: any) {
70
- console.error(`${c.error(symbols.cross)} ${error.message}`)
71
- process.exit(1)
72
- }
73
- }
74
-
75
- main()
@@ -1,132 +0,0 @@
1
- /**
2
- * Terminal colors using Bun's built-in color API
3
- *
4
- * - Automatically respects NO_COLOR environment variable
5
- * - Automatically detects terminal color support (16, 256, or 16m colors)
6
- * - Uses semantic colors that work with any terminal theme
7
- */
8
-
9
- const RESET = '\x1b[0m'
10
-
11
- // Helper to wrap text with ANSI color
12
- function colorize(text: string, color: string): string {
13
- const ansi = Bun.color(color, 'ansi')
14
- if (!ansi) return text
15
- return `${ansi}${text}${RESET}`
16
- }
17
-
18
- // Style helpers
19
- function bold(text: string): string {
20
- return `\x1b[1m${text}${RESET}`
21
- }
22
-
23
- function dim(text: string): string {
24
- return `\x1b[2m${text}${RESET}`
25
- }
26
-
27
- function italic(text: string): string {
28
- return `\x1b[3m${text}${RESET}`
29
- }
30
-
31
- function underline(text: string): string {
32
- return `\x1b[4m${text}${RESET}`
33
- }
34
-
35
- // Semantic color helpers that work with any terminal theme
36
- export const c = {
37
- // Emphasis
38
- bold,
39
- dim,
40
- italic,
41
- underline,
42
-
43
- // Semantic colors using Bun.color for automatic terminal detection
44
- success: (s: string) => colorize(s, '#22c55e'), // Green
45
- error: (s: string) => colorize(s, '#ef4444'), // Red
46
- warning: (s: string) => colorize(s, '#eab308'), // Yellow
47
- info: (s: string) => colorize(s, '#06b6d4'), // Cyan
48
- accent: (s: string) => colorize(s, '#a855f7'), // Purple
49
-
50
- // Muted text (gray)
51
- muted: (s: string) => colorize(s, '#6b7280'),
52
-
53
- // Bright variants
54
- brightGreen: (s: string) => colorize(s, '#4ade80'),
55
- brightCyan: (s: string) => colorize(s, '#22d3ee'),
56
- brightYellow: (s: string) => colorize(s, '#facc15'),
57
- brightMagenta: (s: string) => colorize(s, '#c084fc'),
58
-
59
- // Custom color
60
- color: (s: string, color: string) => colorize(s, color),
61
- }
62
-
63
- // Symbols that work in most terminals
64
- export const symbols = {
65
- check: '\u2714', // ✔
66
- cross: '\u2718', // ✘
67
- arrow: '\u276F', // ❯
68
- bullet: '\u25CF', // ●
69
- line: '\u2500', // ─
70
- corner: '\u2514', // └
71
- tee: '\u251C', // ├
72
- vertical: '\u2502', // │
73
- star: '\u2605', // ★
74
- sparkle: '\u2728', // ✨
75
- }
76
-
77
- // Strip ANSI codes for length calculation
78
- function stripAnsi(s: string): string {
79
- return s.replace(/\x1b\[[0-9;]*m/g, '')
80
- }
81
-
82
- // Box drawing for beautiful output
83
- export function box(content: string[], options: { title?: string; padding?: number } = {}): string {
84
- const padding = options.padding ?? 1
85
- const maxWidth = Math.max(...content.map(l => stripAnsi(l).length), stripAnsi(options.title ?? '').length)
86
- const width = maxWidth + padding * 2
87
-
88
- const horizontal = symbols.line.repeat(width + 2)
89
- const empty = `${symbols.vertical}${' '.repeat(width + 2)}${symbols.vertical}`
90
- const pad = ' '.repeat(padding)
91
-
92
- const lines: string[] = []
93
-
94
- // Top border with optional title
95
- if (options.title) {
96
- const titleText = options.title
97
- const strippedTitle = stripAnsi(titleText)
98
- const leftPad = Math.floor((width + 2 - strippedTitle.length) / 2)
99
- const rightPad = width + 2 - strippedTitle.length - leftPad
100
- lines.push(`\u256D${symbols.line.repeat(leftPad)}${titleText}${symbols.line.repeat(rightPad)}\u256E`)
101
- } else {
102
- lines.push(`\u256D${horizontal}\u256E`)
103
- }
104
-
105
- // Top padding
106
- for (let i = 0; i < padding; i++) lines.push(empty)
107
-
108
- // Content
109
- for (const line of content) {
110
- const stripped = stripAnsi(line)
111
- const rightPad = width - stripped.length - padding
112
- lines.push(`${symbols.vertical}${pad}${line}${' '.repeat(Math.max(0, rightPad) + padding)}${symbols.vertical}`)
113
- }
114
-
115
- // Bottom padding
116
- for (let i = 0; i < padding; i++) lines.push(empty)
117
-
118
- // Bottom border
119
- lines.push(`\u2570${horizontal}\u256F`)
120
-
121
- return lines.join('\n')
122
- }
123
-
124
- // Spinner frames
125
- export const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
126
-
127
- // Progress bar
128
- export function progressBar(percent: number, width = 20): string {
129
- const filled = Math.round(width * percent)
130
- const empty = width - filled
131
- return `${c.success('\u2588'.repeat(filled))}${c.muted('\u2591'.repeat(empty))}`
132
- }