@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.
- package/README.md +129 -0
- package/dist/index.js +221 -0
- package/package.json +20 -15
- package/templates/counter/README.md +88 -0
- package/templates/counter/package.json +18 -0
- package/templates/counter/src/App.ts +65 -0
- package/templates/counter/src/components/Counter.ts +78 -0
- package/templates/counter/src/components/CounterPanel.ts +74 -0
- package/templates/counter/src/components/Header.ts +55 -0
- package/templates/counter/src/components/HistoryPanel.ts +96 -0
- package/templates/counter/src/components/KeyBindings.ts +60 -0
- package/templates/counter/src/components/StatsPanel.ts +101 -0
- package/templates/counter/src/main.ts +87 -0
- package/templates/counter/src/state/counters.ts +121 -0
- package/templates/counter/tsconfig.json +16 -0
- package/templates/dashboard/README.md +95 -0
- package/templates/dashboard/package.json +18 -0
- package/templates/dashboard/src/App.ts +72 -0
- package/templates/dashboard/src/components/Footer.ts +102 -0
- package/templates/dashboard/src/components/Header.ts +108 -0
- package/templates/dashboard/src/components/LogsPanel.ts +98 -0
- package/templates/dashboard/src/components/MetricsPanel.ts +145 -0
- package/templates/dashboard/src/components/Sidebar.ts +162 -0
- package/templates/dashboard/src/components/TrafficPanel.ts +129 -0
- package/templates/dashboard/src/main.ts +66 -0
- package/templates/dashboard/src/state/logs.ts +42 -0
- package/templates/dashboard/src/state/metrics.ts +129 -0
- package/templates/dashboard/src/state/theme.ts +20 -0
- package/templates/dashboard/tsconfig.json +16 -0
- package/templates/minimal/README.md +98 -0
- package/templates/minimal/package.json +18 -0
- package/templates/minimal/src/App.ts +108 -0
- package/templates/minimal/src/components/Header.ts +52 -0
- package/templates/minimal/src/main.ts +24 -0
- package/templates/minimal/tsconfig.json +16 -0
- package/src/commands/create.ts +0 -282
- package/src/index.ts +0 -75
- package/src/utils/colors.ts +0 -132
- 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
|
+
}
|
package/src/commands/create.ts
DELETED
|
@@ -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()
|
package/src/utils/colors.ts
DELETED
|
@@ -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
|
-
}
|