@silvery/examples 0.4.2
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/bin/cli.ts +286 -0
- package/examples/apps/aichat/components.tsx +469 -0
- package/examples/apps/aichat/index.tsx +207 -0
- package/examples/apps/aichat/script.ts +460 -0
- package/examples/apps/aichat/state.ts +326 -0
- package/examples/apps/aichat/types.ts +19 -0
- package/examples/apps/app-todo.tsx +201 -0
- package/examples/apps/async-data.tsx +208 -0
- package/examples/apps/cli-wizard.tsx +332 -0
- package/examples/apps/clipboard.tsx +183 -0
- package/examples/apps/components.tsx +463 -0
- package/examples/apps/data-explorer.tsx +490 -0
- package/examples/apps/dev-tools.tsx +379 -0
- package/examples/apps/explorer.tsx +731 -0
- package/examples/apps/gallery.tsx +653 -0
- package/examples/apps/inline-bench.tsx +136 -0
- package/examples/apps/kanban.tsx +267 -0
- package/examples/apps/layout-ref.tsx +185 -0
- package/examples/apps/outline.tsx +171 -0
- package/examples/apps/panes/index.tsx +205 -0
- package/examples/apps/paste-demo.tsx +198 -0
- package/examples/apps/scroll.tsx +77 -0
- package/examples/apps/search-filter.tsx +240 -0
- package/examples/apps/task-list.tsx +271 -0
- package/examples/apps/terminal.tsx +800 -0
- package/examples/apps/textarea.tsx +103 -0
- package/examples/apps/theme.tsx +515 -0
- package/examples/apps/transform.tsx +242 -0
- package/examples/apps/virtual-10k.tsx +405 -0
- package/examples/components/counter.tsx +45 -0
- package/examples/components/hello.tsx +34 -0
- package/examples/components/progress-bar.tsx +48 -0
- package/examples/components/select-list.tsx +50 -0
- package/examples/components/spinner.tsx +40 -0
- package/examples/components/text-input.tsx +57 -0
- package/examples/components/virtual-list.tsx +52 -0
- package/package.json +27 -0
package/bin/cli.ts
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* silvery CLI
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* bunx silvery — show help
|
|
7
|
+
* bunx silvery <name> — run an example by name (fuzzy match)
|
|
8
|
+
* bunx silvery examples — list all available examples
|
|
9
|
+
* bunx silvery doctor — check terminal capabilities
|
|
10
|
+
* bunx silvery --help — show usage help
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// ANSI helpers (no deps — must work before anything is imported)
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
const RESET = "\x1b[0m"
|
|
18
|
+
const BOLD = "\x1b[1m"
|
|
19
|
+
const DIM = "\x1b[2m"
|
|
20
|
+
const RED = "\x1b[31m"
|
|
21
|
+
const GREEN = "\x1b[32m"
|
|
22
|
+
const YELLOW = "\x1b[33m"
|
|
23
|
+
const BLUE = "\x1b[34m"
|
|
24
|
+
const MAGENTA = "\x1b[35m"
|
|
25
|
+
const CYAN = "\x1b[36m"
|
|
26
|
+
const WHITE = "\x1b[37m"
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Types
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
interface Example {
|
|
33
|
+
name: string
|
|
34
|
+
file: string
|
|
35
|
+
description: string
|
|
36
|
+
category: string
|
|
37
|
+
features?: string[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// Auto-Discovery
|
|
42
|
+
// =============================================================================
|
|
43
|
+
|
|
44
|
+
const CATEGORY_DIRS = ["components", "apps", "layout", "runtime", "inline", "kitty"] as const
|
|
45
|
+
|
|
46
|
+
const CATEGORY_DISPLAY: Record<string, string> = {
|
|
47
|
+
kitty: "Kitty Protocol",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const CATEGORY_ORDER: Record<string, number> = {
|
|
51
|
+
Components: 0,
|
|
52
|
+
Apps: 1,
|
|
53
|
+
Layout: 2,
|
|
54
|
+
Runtime: 3,
|
|
55
|
+
Inline: 4,
|
|
56
|
+
"Kitty Protocol": 5,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const CATEGORY_COLOR: Record<string, string> = {
|
|
60
|
+
Components: GREEN,
|
|
61
|
+
Apps: CYAN,
|
|
62
|
+
Layout: MAGENTA,
|
|
63
|
+
Runtime: BLUE,
|
|
64
|
+
Inline: YELLOW,
|
|
65
|
+
"Kitty Protocol": BLUE,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function discoverExamples(): Promise<Example[]> {
|
|
69
|
+
const { resolve } = await import("node:path")
|
|
70
|
+
const examplesDir = resolve(new URL(".", import.meta.url).pathname, "../examples")
|
|
71
|
+
const results: Example[] = []
|
|
72
|
+
|
|
73
|
+
for (const dir of CATEGORY_DIRS) {
|
|
74
|
+
const category = CATEGORY_DISPLAY[dir] ?? dir.charAt(0).toUpperCase() + dir.slice(1)
|
|
75
|
+
const glob = new Bun.Glob("*.tsx")
|
|
76
|
+
const dirPath = resolve(examplesDir, dir)
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
for (const file of glob.scanSync({ cwd: dirPath })) {
|
|
80
|
+
if (file.startsWith("_")) continue
|
|
81
|
+
|
|
82
|
+
// Try to get meta from export, fall back to filename
|
|
83
|
+
const name = file.replace(/\.tsx$/, "").replace(/-/g, " ")
|
|
84
|
+
results.push({
|
|
85
|
+
name,
|
|
86
|
+
description: "",
|
|
87
|
+
file: resolve(dirPath, file),
|
|
88
|
+
category,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
// Directory doesn't exist — skip
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Also scan aichat subdirectory
|
|
97
|
+
const aichatDir = resolve(examplesDir, "apps/aichat")
|
|
98
|
+
try {
|
|
99
|
+
const indexFile = resolve(aichatDir, "index.tsx")
|
|
100
|
+
const { stat } = await import("node:fs/promises")
|
|
101
|
+
await stat(indexFile)
|
|
102
|
+
results.push({
|
|
103
|
+
name: "aichat",
|
|
104
|
+
description: "AI Coding Agent demo",
|
|
105
|
+
file: indexFile,
|
|
106
|
+
category: "Apps",
|
|
107
|
+
})
|
|
108
|
+
} catch {
|
|
109
|
+
// No aichat
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
results.sort((a, b) => {
|
|
113
|
+
const catDiff = (CATEGORY_ORDER[a.category] ?? 99) - (CATEGORY_ORDER[b.category] ?? 99)
|
|
114
|
+
if (catDiff !== 0) return catDiff
|
|
115
|
+
return a.name.localeCompare(b.name)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
return results
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// =============================================================================
|
|
122
|
+
// Formatting
|
|
123
|
+
// =============================================================================
|
|
124
|
+
|
|
125
|
+
function printHelp(): void {
|
|
126
|
+
console.log(`
|
|
127
|
+
${BOLD}${YELLOW}@silvery/examples${RESET} — Try silvery without installing
|
|
128
|
+
|
|
129
|
+
${BOLD}Usage:${RESET}
|
|
130
|
+
bunx @silvery/examples ${DIM}<name>${RESET} Run an example by name (fuzzy match)
|
|
131
|
+
bunx @silvery/examples List all available examples
|
|
132
|
+
bunx @silvery/examples --help Show this help
|
|
133
|
+
|
|
134
|
+
${BOLD}Quick start:${RESET}
|
|
135
|
+
bunx @silvery/examples counter Simple counter (Hello World)
|
|
136
|
+
bunx @silvery/examples dashboard Responsive layout demo
|
|
137
|
+
bunx @silvery/examples kanban Kanban board with keyboard nav
|
|
138
|
+
bunx @silvery/examples textarea Rich text editor
|
|
139
|
+
|
|
140
|
+
${DIM}Documentation: https://silvery.dev${RESET}
|
|
141
|
+
`)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function printExampleList(examples: Example[]): void {
|
|
145
|
+
console.log(`\n${BOLD}${YELLOW} silvery${RESET}${DIM} examples${RESET}\n`)
|
|
146
|
+
|
|
147
|
+
let currentCategory = ""
|
|
148
|
+
|
|
149
|
+
for (const ex of examples) {
|
|
150
|
+
if (ex.category !== currentCategory) {
|
|
151
|
+
currentCategory = ex.category
|
|
152
|
+
const color = CATEGORY_COLOR[currentCategory] ?? WHITE
|
|
153
|
+
console.log(` ${color}${BOLD}${currentCategory}${RESET}`)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const nameStr = `${BOLD}${WHITE}${ex.name}${RESET}`
|
|
157
|
+
const descStr = ex.description ? `${DIM}${ex.description}${RESET}` : ""
|
|
158
|
+
console.log(` ${nameStr} ${descStr}`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`\n ${DIM}Run: bunx @silvery/examples <name>${RESET}\n`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function findExample(examples: Example[], query: string): Example | undefined {
|
|
165
|
+
const q = query.toLowerCase().replace(/-/g, " ")
|
|
166
|
+
|
|
167
|
+
const exact = examples.find((ex) => ex.name.toLowerCase() === q)
|
|
168
|
+
if (exact) return exact
|
|
169
|
+
|
|
170
|
+
const prefix = examples.find((ex) => ex.name.toLowerCase().startsWith(q))
|
|
171
|
+
if (prefix) return prefix
|
|
172
|
+
|
|
173
|
+
const substring = examples.find((ex) => ex.name.toLowerCase().includes(q))
|
|
174
|
+
if (substring) return substring
|
|
175
|
+
|
|
176
|
+
return undefined
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function printNoMatch(query: string, examples: Example[]): void {
|
|
180
|
+
console.error(`\n${RED}${BOLD}Error:${RESET} No example matching "${query}"\n`)
|
|
181
|
+
console.error(`${DIM}Available examples:${RESET}`)
|
|
182
|
+
|
|
183
|
+
for (const ex of examples) {
|
|
184
|
+
console.error(` ${WHITE}${ex.name}${RESET}`)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.error(`\n${DIM}Run ${BOLD}bunx @silvery/examples${RESET}${DIM} for full list.${RESET}\n`)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// =============================================================================
|
|
191
|
+
// Subcommands
|
|
192
|
+
// =============================================================================
|
|
193
|
+
|
|
194
|
+
async function exampleCommand(args: string[]): Promise<void> {
|
|
195
|
+
const examples = await discoverExamples()
|
|
196
|
+
|
|
197
|
+
if (args.length === 0 || args[0] === "--list" || args[0] === "-l") {
|
|
198
|
+
printExampleList(examples)
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const query = args.filter((a) => !a.startsWith("--")).join(" ")
|
|
203
|
+
if (!query) {
|
|
204
|
+
printExampleList(examples)
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const match = findExample(examples, query)
|
|
209
|
+
if (!match) {
|
|
210
|
+
printNoMatch(query, examples)
|
|
211
|
+
process.exit(1)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log(`${DIM}Running ${BOLD}${match.name}${RESET}${DIM}...${RESET}\n`)
|
|
215
|
+
|
|
216
|
+
const proc = Bun.spawn(["bun", "run", match.file], {
|
|
217
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
218
|
+
})
|
|
219
|
+
const exitCode = await proc.exited
|
|
220
|
+
process.exit(exitCode)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function doctorCommand(): Promise<void> {
|
|
224
|
+
// Try to resolve termtest from @silvery/ag-term (installed as dependency of silvery)
|
|
225
|
+
const { resolve } = await import("node:path")
|
|
226
|
+
|
|
227
|
+
// In monorepo: ../../ag-term/src/termtest.ts
|
|
228
|
+
// In npm install: node_modules/@silvery/ag-term/src/termtest.ts
|
|
229
|
+
const candidates = [
|
|
230
|
+
resolve(new URL(".", import.meta.url).pathname, "../../ag-term/src/termtest.ts"),
|
|
231
|
+
resolve(new URL(".", import.meta.url).pathname, "../node_modules/@silvery/ag-term/src/termtest.ts"),
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
for (const termtestPath of candidates) {
|
|
235
|
+
try {
|
|
236
|
+
const { stat } = await import("node:fs/promises")
|
|
237
|
+
await stat(termtestPath)
|
|
238
|
+
const proc = Bun.spawn(["bun", "run", termtestPath], {
|
|
239
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
240
|
+
})
|
|
241
|
+
const exitCode = await proc.exited
|
|
242
|
+
process.exit(exitCode)
|
|
243
|
+
} catch {
|
|
244
|
+
continue
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.error(`${RED}Error:${RESET} Could not find terminal diagnostics.`)
|
|
249
|
+
console.error(`${DIM}Make sure silvery is installed: bun add silvery${RESET}`)
|
|
250
|
+
process.exit(1)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// =============================================================================
|
|
254
|
+
// Main
|
|
255
|
+
// =============================================================================
|
|
256
|
+
|
|
257
|
+
async function main(): Promise<void> {
|
|
258
|
+
const args = process.argv.slice(2)
|
|
259
|
+
|
|
260
|
+
// Top-level flags
|
|
261
|
+
if (args.includes("--help") || args.includes("-h") || args.length === 0) {
|
|
262
|
+
printHelp()
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
267
|
+
try {
|
|
268
|
+
const { resolve } = await import("node:path")
|
|
269
|
+
const pkgPath = resolve(new URL(".", import.meta.url).pathname, "../package.json")
|
|
270
|
+
const pkg = await Bun.file(pkgPath).json()
|
|
271
|
+
console.log(`@silvery/examples ${pkg.version}`)
|
|
272
|
+
} catch {
|
|
273
|
+
console.log("@silvery/examples (version unknown)")
|
|
274
|
+
}
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// "bunx @silvery/examples counter" → run counter example directly
|
|
279
|
+
// "bunx @silvery/examples" → list (handled above by args.length === 0)
|
|
280
|
+
await exampleCommand(args)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
main().catch((err) => {
|
|
284
|
+
console.error(err)
|
|
285
|
+
process.exit(1)
|
|
286
|
+
})
|