silvery 0.3.0 → 0.4.1

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 (120) hide show
  1. package/README.md +41 -145
  2. package/dist/chalk.js +3 -0
  3. package/dist/chalk.js.map +11 -0
  4. package/dist/index.js +340 -0
  5. package/dist/index.js.map +282 -0
  6. package/dist/ink.js +129 -0
  7. package/dist/ink.js.map +140 -0
  8. package/dist/runtime.js +394 -0
  9. package/dist/runtime.js.map +286 -0
  10. package/dist/theme.js +343 -0
  11. package/dist/theme.js.map +286 -0
  12. package/dist/ui/animation.js +3 -0
  13. package/dist/ui/animation.js.map +15 -0
  14. package/dist/ui/ansi.js +3 -0
  15. package/dist/ui/ansi.js.map +10 -0
  16. package/dist/ui/cli.js +8 -0
  17. package/dist/ui/cli.js.map +14 -0
  18. package/dist/ui/display.js +4 -0
  19. package/dist/ui/display.js.map +10 -0
  20. package/dist/ui/image.js +4 -0
  21. package/dist/ui/image.js.map +15 -0
  22. package/dist/ui/input.js +3 -0
  23. package/dist/ui/input.js.map +11 -0
  24. package/dist/ui/progress.js +8 -0
  25. package/dist/ui/progress.js.map +20 -0
  26. package/dist/ui/react.js +3 -0
  27. package/dist/ui/react.js.map +15 -0
  28. package/dist/ui/utils.js +3 -0
  29. package/dist/ui/utils.js.map +10 -0
  30. package/dist/ui/wrappers.js +14 -0
  31. package/dist/ui/wrappers.js.map +19 -0
  32. package/dist/ui.js +17 -0
  33. package/dist/ui.js.map +20 -0
  34. package/package.json +67 -15
  35. package/src/index.ts +67 -1
  36. package/src/runtime.ts +4 -0
  37. package/src/theme.ts +4 -0
  38. package/src/ui/animation.ts +2 -0
  39. package/src/ui/ansi.ts +2 -0
  40. package/src/ui/cli.ts +2 -0
  41. package/src/ui/display.ts +2 -0
  42. package/src/ui/image.ts +2 -0
  43. package/src/ui/input.ts +2 -0
  44. package/src/ui/progress.ts +2 -0
  45. package/src/ui/react.ts +2 -0
  46. package/src/ui/utils.ts +2 -0
  47. package/src/ui/wrappers.ts +2 -0
  48. package/src/ui.ts +4 -0
  49. package/examples/CLAUDE.md +0 -75
  50. package/examples/_banner.tsx +0 -60
  51. package/examples/cli.ts +0 -228
  52. package/examples/index.md +0 -101
  53. package/examples/inline/inline-nontty.tsx +0 -98
  54. package/examples/inline/inline-progress.tsx +0 -79
  55. package/examples/inline/inline-simple.tsx +0 -63
  56. package/examples/inline/scrollback.tsx +0 -185
  57. package/examples/interactive/_input-debug.tsx +0 -110
  58. package/examples/interactive/_stdin-test.ts +0 -71
  59. package/examples/interactive/_textarea-bare.tsx +0 -45
  60. package/examples/interactive/aichat/components.tsx +0 -468
  61. package/examples/interactive/aichat/index.tsx +0 -207
  62. package/examples/interactive/aichat/script.ts +0 -460
  63. package/examples/interactive/aichat/state.ts +0 -326
  64. package/examples/interactive/aichat/types.ts +0 -19
  65. package/examples/interactive/app-todo.tsx +0 -198
  66. package/examples/interactive/async-data.tsx +0 -208
  67. package/examples/interactive/cli-wizard.tsx +0 -332
  68. package/examples/interactive/clipboard.tsx +0 -183
  69. package/examples/interactive/components.tsx +0 -463
  70. package/examples/interactive/data-explorer.tsx +0 -506
  71. package/examples/interactive/dev-tools.tsx +0 -379
  72. package/examples/interactive/explorer.tsx +0 -747
  73. package/examples/interactive/gallery.tsx +0 -652
  74. package/examples/interactive/inline-bench.tsx +0 -136
  75. package/examples/interactive/kanban.tsx +0 -267
  76. package/examples/interactive/layout-ref.tsx +0 -185
  77. package/examples/interactive/outline.tsx +0 -171
  78. package/examples/interactive/paste-demo.tsx +0 -198
  79. package/examples/interactive/scroll.tsx +0 -77
  80. package/examples/interactive/search-filter.tsx +0 -240
  81. package/examples/interactive/task-list.tsx +0 -279
  82. package/examples/interactive/terminal.tsx +0 -798
  83. package/examples/interactive/textarea.tsx +0 -103
  84. package/examples/interactive/theme.tsx +0 -336
  85. package/examples/interactive/transform.tsx +0 -256
  86. package/examples/interactive/virtual-10k.tsx +0 -413
  87. package/examples/kitty/canvas.tsx +0 -519
  88. package/examples/kitty/generate-samples.ts +0 -236
  89. package/examples/kitty/image-component.tsx +0 -273
  90. package/examples/kitty/images.tsx +0 -604
  91. package/examples/kitty/input.tsx +0 -371
  92. package/examples/kitty/keys.tsx +0 -378
  93. package/examples/kitty/paint.tsx +0 -1017
  94. package/examples/layout/dashboard.tsx +0 -551
  95. package/examples/layout/live-resize.tsx +0 -290
  96. package/examples/layout/overflow.tsx +0 -51
  97. package/examples/playground/README.md +0 -69
  98. package/examples/playground/build.ts +0 -61
  99. package/examples/playground/index.html +0 -420
  100. package/examples/playground/playground-app.tsx +0 -416
  101. package/examples/runtime/elm-counter.tsx +0 -206
  102. package/examples/runtime/hello-runtime.tsx +0 -73
  103. package/examples/runtime/pipe-composition.tsx +0 -184
  104. package/examples/runtime/run-counter.tsx +0 -78
  105. package/examples/runtime/runtime-counter.tsx +0 -197
  106. package/examples/screenshots/generate.tsx +0 -563
  107. package/examples/scrollback-perf.tsx +0 -230
  108. package/examples/viewer.tsx +0 -654
  109. package/examples/web/build.ts +0 -365
  110. package/examples/web/canvas-app.tsx +0 -80
  111. package/examples/web/canvas.html +0 -89
  112. package/examples/web/dom-app.tsx +0 -81
  113. package/examples/web/dom.html +0 -113
  114. package/examples/web/showcase-app.tsx +0 -107
  115. package/examples/web/showcase.html +0 -34
  116. package/examples/web/showcases/index.tsx +0 -56
  117. package/examples/web/viewer-app.tsx +0 -555
  118. package/examples/web/viewer.html +0 -30
  119. package/examples/web/xterm-app.tsx +0 -105
  120. package/examples/web/xterm.html +0 -118
@@ -1,379 +0,0 @@
1
- /**
2
- * Dev Tools — Log Viewer Example
3
- *
4
- * A live log viewer demonstrating:
5
- * - VirtualList for efficient rendering of thousands of log entries
6
- * - Keyboard shortcuts to add log entries at different severity levels
7
- * - Color-coded severity levels (DEBUG, INFO, WARN, ERROR)
8
- * - j/k navigation through log history
9
- * - Auto-scroll to latest entry
10
- *
11
- * Usage: bun run examples/interactive/dev-tools.tsx
12
- *
13
- * Controls:
14
- * j/k or Up/Down - Navigate through log entries
15
- * g/G - Jump to first/last entry
16
- * d - Add DEBUG entry
17
- * i - Add INFO entry
18
- * w - Add WARN entry
19
- * e - Add ERROR entry
20
- * c - Clear all logs
21
- * q or Esc - Quit
22
- */
23
-
24
- import React, { useState, useCallback, useMemo } from "react"
25
- import {
26
- render,
27
- Box,
28
- Text,
29
- VirtualList,
30
- Divider,
31
- useContentRect,
32
- useInput,
33
- useApp,
34
- createTerm,
35
- H1,
36
- Strong,
37
- Kbd,
38
- Muted,
39
- type Key,
40
- } from "../../src/index.js"
41
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
42
-
43
- export const meta: ExampleMeta = {
44
- name: "Dev Tools",
45
- description: "Log viewer with severity levels, VirtualList, and keyboard-driven log injection",
46
- features: ["VirtualList", "useInput()", "useContentRect()", "keyboard navigation"],
47
- }
48
-
49
- // ============================================================================
50
- // Types
51
- // ============================================================================
52
-
53
- type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR"
54
-
55
- interface LogEntry {
56
- id: number
57
- timestamp: Date
58
- level: LogLevel
59
- source: string
60
- message: string
61
- }
62
-
63
- // ============================================================================
64
- // Data Generation
65
- // ============================================================================
66
-
67
- const SOURCES = ["http", "db", "auth", "cache", "worker", "api", "scheduler", "queue", "metrics", "ws"]
68
-
69
- const LOG_TEMPLATES: Record<LogLevel, string[]> = {
70
- DEBUG: [
71
- "Cache miss for key user:session:{{id}}",
72
- "Query plan: sequential scan on events ({{n}} rows)",
73
- "WebSocket frame received: {{n}} bytes",
74
- "GC pause: {{n}}ms (minor collection)",
75
- "Connection pool stats: {{n}} active, {{n}} idle",
76
- "Route matched: GET /api/v2/resources/{{id}}",
77
- ],
78
- INFO: [
79
- "Request completed: 200 OK ({{n}}ms)",
80
- "User {{id}} authenticated via OAuth",
81
- "Background job processed: email_dispatch #{{id}}",
82
- "Server listening on port {{n}}",
83
- "Database migration applied: v{{n}}",
84
- "Health check passed (latency: {{n}}ms)",
85
- ],
86
- WARN: [
87
- "Slow query detected: {{n}}ms (threshold: 200ms)",
88
- "Rate limit approaching: {{n}}/1000 requests",
89
- "Memory usage: {{n}}% of allocated heap",
90
- "Retry attempt {{n}}/3 for external API call",
91
- "Certificate expires in {{n}} days",
92
- "Connection pool near capacity: {{n}}/100",
93
- ],
94
- ERROR: [
95
- "Unhandled exception in request handler: TypeError",
96
- "Database connection refused: ECONNREFUSED",
97
- "Authentication failed for user {{id}}: invalid token",
98
- "Timeout after {{n}}ms waiting for upstream service",
99
- "Disk usage critical: {{n}}% on /var/data",
100
- "Failed to process message from queue: malformed payload",
101
- ],
102
- }
103
-
104
- let nextLogId = 1
105
-
106
- function seededRandom(seed: number): () => number {
107
- let s = seed
108
- return () => {
109
- s = (s * 1664525 + 1013904223) & 0x7fffffff
110
- return s / 0x7fffffff
111
- }
112
- }
113
-
114
- function generateMessage(level: LogLevel, rng: () => number): string {
115
- const templates = LOG_TEMPLATES[level]
116
- const template = templates[Math.floor(rng() * templates.length)]!
117
- return template
118
- .replace(/\{\{id\}\}/g, () => String(Math.floor(rng() * 99999)))
119
- .replace(/\{\{n\}\}/g, () => String(Math.floor(rng() * 999)))
120
- }
121
-
122
- function createLogEntry(level: LogLevel, rng: () => number): LogEntry {
123
- return {
124
- id: nextLogId++,
125
- timestamp: new Date(),
126
- level,
127
- source: SOURCES[Math.floor(rng() * SOURCES.length)]!,
128
- message: generateMessage(level, rng),
129
- }
130
- }
131
-
132
- function generateInitialLogs(count: number): LogEntry[] {
133
- const rng = seededRandom(42)
134
- const levels: LogLevel[] = ["DEBUG", "INFO", "INFO", "INFO", "WARN", "ERROR"]
135
- const entries: LogEntry[] = []
136
- const now = Date.now()
137
-
138
- for (let i = 0; i < count; i++) {
139
- const level = levels[Math.floor(rng() * levels.length)]!
140
- const entry = createLogEntry(level, rng)
141
- // Spread timestamps over the last hour
142
- entry.timestamp = new Date(now - (count - i) * 1200)
143
- entries.push(entry)
144
- }
145
- return entries
146
- }
147
-
148
- // ============================================================================
149
- // Constants
150
- // ============================================================================
151
-
152
- const LEVEL_COLORS: Record<LogLevel, string> = {
153
- DEBUG: "$muted",
154
- INFO: "$primary",
155
- WARN: "$warning",
156
- ERROR: "$error",
157
- }
158
-
159
- const LEVEL_BADGES: Record<LogLevel, string> = {
160
- DEBUG: "DBG",
161
- INFO: "INF",
162
- WARN: "WRN",
163
- ERROR: "ERR",
164
- }
165
-
166
- // ============================================================================
167
- // Components
168
- // ============================================================================
169
-
170
- function formatTime(date: Date): string {
171
- return date.toLocaleTimeString("en-US", {
172
- hour: "2-digit",
173
- minute: "2-digit",
174
- second: "2-digit",
175
- hour12: false,
176
- })
177
- }
178
-
179
- function LogRow({ entry, isSelected }: { entry: LogEntry; isSelected: boolean }): JSX.Element {
180
- const badge = LEVEL_BADGES[entry.level]
181
- const color = LEVEL_COLORS[entry.level]
182
-
183
- return (
184
- <Box paddingX={1} backgroundColor={isSelected ? "$primary" : undefined}>
185
- <Muted>{formatTime(entry.timestamp)} </Muted>
186
- <Strong color={color}>{badge}</Strong>
187
- <Muted> [{entry.source.padEnd(9)}] </Muted>
188
- <Text>{entry.message}</Text>
189
- </Box>
190
- )
191
- }
192
-
193
- function LevelCounts({ entries }: { entries: LogEntry[] }): JSX.Element {
194
- const counts = useMemo(() => {
195
- const c = { DEBUG: 0, INFO: 0, WARN: 0, ERROR: 0 }
196
- for (const e of entries) c[e.level]++
197
- return c
198
- }, [entries])
199
-
200
- return (
201
- <Box gap={2}>
202
- <Strong color="$muted">
203
- {LEVEL_BADGES.DEBUG}:{counts.DEBUG}
204
- </Strong>
205
- <Strong color="$primary">
206
- {LEVEL_BADGES.INFO}:{counts.INFO}
207
- </Strong>
208
- <Strong color="$warning">
209
- {LEVEL_BADGES.WARN}:{counts.WARN}
210
- </Strong>
211
- <Strong color="$error">
212
- {LEVEL_BADGES.ERROR}:{counts.ERROR}
213
- </Strong>
214
- </Box>
215
- )
216
- }
217
-
218
- /** Inner component that reads the flex container's height via useContentRect */
219
- function LogListArea({ entries, cursor }: { entries: LogEntry[]; cursor: number }): JSX.Element {
220
- const { height } = useContentRect()
221
-
222
- return (
223
- <VirtualList
224
- items={entries}
225
- height={height}
226
- itemHeight={1}
227
- scrollTo={cursor}
228
- overscan={5}
229
- renderItem={(entry, index) => <LogRow key={entry.id} entry={entry} isSelected={index === cursor} />}
230
- />
231
- )
232
- }
233
-
234
- // ============================================================================
235
- // Main App
236
- // ============================================================================
237
-
238
- const INITIAL_COUNT = 200
239
- const rng = seededRandom(12345)
240
-
241
- export function DevTools(): JSX.Element {
242
- const { exit } = useApp()
243
- const { width } = useContentRect()
244
- const [entries, setEntries] = useState<LogEntry[]>(() => generateInitialLogs(INITIAL_COUNT))
245
- const [cursor, setCursor] = useState(INITIAL_COUNT - 1)
246
- const [autoScroll, setAutoScroll] = useState(true)
247
-
248
- const addEntry = useCallback(
249
- (level: LogLevel) => {
250
- const entry = createLogEntry(level, rng)
251
- setEntries((prev) => [...prev, entry])
252
- if (autoScroll) {
253
- setCursor((prev) => prev + 1)
254
- }
255
- },
256
- [autoScroll],
257
- )
258
-
259
- useInput(
260
- useCallback(
261
- (input: string, key: Key) => {
262
- // Quit
263
- if (input === "q" || key.escape) {
264
- exit()
265
- return
266
- }
267
-
268
- // Navigation
269
- if (input === "j" || key.downArrow) {
270
- setCursor((c) => Math.min(entries.length - 1, c + 1))
271
- setAutoScroll(false)
272
- return
273
- }
274
- if (input === "k" || key.upArrow) {
275
- setCursor((c) => Math.max(0, c - 1))
276
- setAutoScroll(false)
277
- return
278
- }
279
-
280
- // Jump to start/end
281
- if (input === "g" || key.home) {
282
- setCursor(0)
283
- setAutoScroll(false)
284
- return
285
- }
286
- if (input === "G" || key.end) {
287
- setCursor(entries.length - 1)
288
- setAutoScroll(true)
289
- return
290
- }
291
-
292
- // Add log entries
293
- if (input === "d") {
294
- addEntry("DEBUG")
295
- return
296
- }
297
- if (input === "i") {
298
- addEntry("INFO")
299
- return
300
- }
301
- if (input === "w") {
302
- addEntry("WARN")
303
- return
304
- }
305
- if (input === "e") {
306
- addEntry("ERROR")
307
- return
308
- }
309
-
310
- // Clear
311
- if (input === "c") {
312
- setEntries([])
313
- setCursor(0)
314
- setAutoScroll(true)
315
- return
316
- }
317
- },
318
- [entries.length, exit, addEntry],
319
- ),
320
- )
321
-
322
- return (
323
- <Box flexDirection="column" flexGrow={1}>
324
- {/* Header */}
325
- <Box paddingX={1} justifyContent="space-between">
326
- <Box gap={2}>
327
- <H1>Log Viewer</H1>
328
- <LevelCounts entries={entries} />
329
- </Box>
330
- <Box gap={1}>
331
- <Strong color="$primary">{cursor + 1}</Strong>
332
- <Muted>/ {entries.length}</Muted>
333
- {autoScroll && (
334
- <Text color="$success" bold>
335
- {" "}
336
- LIVE
337
- </Text>
338
- )}
339
- </Box>
340
- </Box>
341
-
342
- <Box paddingX={1}>
343
- <Divider />
344
- </Box>
345
-
346
- {/* Log list in a flex-grow container */}
347
- <Box flexGrow={1} flexDirection="column">
348
- <LogListArea entries={entries} cursor={cursor} />
349
- </Box>
350
-
351
- {/* Help bar */}
352
- <Box paddingX={1} justifyContent="space-between">
353
- <Muted>
354
- <Kbd>j/k</Kbd> navigate <Kbd>g/G</Kbd> start/end <Kbd>d/i/w/e</Kbd> add log <Kbd>c</Kbd> clear{" "}
355
- <Kbd>Esc/q</Kbd> quit
356
- </Muted>
357
- </Box>
358
- </Box>
359
- )
360
- }
361
-
362
- // ============================================================================
363
- // Main
364
- // ============================================================================
365
-
366
- async function main() {
367
- using term = createTerm()
368
- const { waitUntilExit } = await render(
369
- <ExampleBanner meta={meta} controls="j/k navigate g/G start/end d/i/w/e add log c clear Esc/q quit">
370
- <DevTools />
371
- </ExampleBanner>,
372
- term,
373
- )
374
- await waitUntilExit()
375
- }
376
-
377
- if (import.meta.main) {
378
- main().catch(console.error)
379
- }