@silvery/examples 0.5.2 → 0.5.4

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/{examples/apps → apps}/aichat/index.tsx +4 -3
  3. package/{examples/apps → apps}/async-data.tsx +4 -4
  4. package/apps/components.tsx +658 -0
  5. package/{examples/apps → apps}/data-explorer.tsx +8 -8
  6. package/{examples/apps → apps}/dev-tools.tsx +35 -19
  7. package/{examples/apps → apps}/inline-bench.tsx +3 -1
  8. package/{examples/apps → apps}/kanban.tsx +20 -22
  9. package/{examples/apps → apps}/layout-ref.tsx +6 -6
  10. package/{examples/apps → apps}/panes/index.tsx +1 -1
  11. package/{examples/apps → apps}/paste-demo.tsx +2 -2
  12. package/{examples/apps → apps}/scroll.tsx +2 -2
  13. package/{examples/apps → apps}/search-filter.tsx +1 -1
  14. package/apps/selection.tsx +342 -0
  15. package/apps/spatial-focus-demo.tsx +368 -0
  16. package/{examples/apps → apps}/task-list.tsx +1 -1
  17. package/apps/terminal-caps-demo.tsx +334 -0
  18. package/apps/text-selection-demo.tsx +189 -0
  19. package/apps/textarea.tsx +155 -0
  20. package/{examples/apps → apps}/theme.tsx +1 -1
  21. package/apps/vterm-demo/index.tsx +216 -0
  22. package/dist/cli.d.mts +1 -0
  23. package/dist/cli.mjs +190 -0
  24. package/dist/cli.mjs.map +1 -0
  25. package/layout/dashboard.tsx +953 -0
  26. package/layout/live-resize.tsx +282 -0
  27. package/layout/overflow.tsx +51 -0
  28. package/layout/text-layout.tsx +283 -0
  29. package/package.json +27 -11
  30. package/bin/cli.ts +0 -294
  31. package/examples/apps/components.tsx +0 -463
  32. package/examples/apps/textarea.tsx +0 -91
  33. /package/{examples/_banner.tsx → _banner.tsx} +0 -0
  34. /package/{examples/apps → apps}/aichat/components.tsx +0 -0
  35. /package/{examples/apps → apps}/aichat/script.ts +0 -0
  36. /package/{examples/apps → apps}/aichat/state.ts +0 -0
  37. /package/{examples/apps → apps}/aichat/types.ts +0 -0
  38. /package/{examples/apps → apps}/app-todo.tsx +0 -0
  39. /package/{examples/apps → apps}/cli-wizard.tsx +0 -0
  40. /package/{examples/apps → apps}/clipboard.tsx +0 -0
  41. /package/{examples/apps → apps}/explorer.tsx +0 -0
  42. /package/{examples/apps → apps}/gallery.tsx +0 -0
  43. /package/{examples/apps → apps}/outline.tsx +0 -0
  44. /package/{examples/apps → apps}/terminal.tsx +0 -0
  45. /package/{examples/apps → apps}/transform.tsx +0 -0
  46. /package/{examples/apps → apps}/virtual-10k.tsx +0 -0
  47. /package/{examples/components → components}/counter.tsx +0 -0
  48. /package/{examples/components → components}/hello.tsx +0 -0
  49. /package/{examples/components → components}/progress-bar.tsx +0 -0
  50. /package/{examples/components → components}/select-list.tsx +0 -0
  51. /package/{examples/components → components}/spinner.tsx +0 -0
  52. /package/{examples/components → components}/text-input.tsx +0 -0
  53. /package/{examples/components → components}/virtual-list.tsx +0 -0
@@ -0,0 +1,216 @@
1
+ /**
2
+ * VTerm Demo -- same ChatApp in inline, fullscreen, and panes modes.
3
+ *
4
+ * Run: bun examples/apps/vterm-demo/index.tsx --mode=inline|fullscreen|panes [--fast]
5
+ */
6
+
7
+ import React, { useState, useEffect } from "react"
8
+ import { Box, Text, ListView, useWindowSize } from "silvery"
9
+ import { SearchProvider, SearchBar } from "@silvery/ag-react"
10
+ import { run, useInput, type Key } from "silvery/runtime"
11
+ import type { ExampleMeta } from "../../_banner.js"
12
+ import { SCRIPT } from "../aichat/script.js"
13
+ import type { ScriptEntry } from "../aichat/types.js"
14
+ import type { Exchange } from "../aichat/types.js"
15
+
16
+ export const meta: ExampleMeta = {
17
+ name: "VTerm Demo",
18
+ description: "Same chat app in inline, fullscreen, and panes modes",
19
+ demo: true,
20
+ features: ["ListView", "SearchProvider", "inline", "fullscreen", "panes"],
21
+ }
22
+
23
+ interface ListItemMeta {
24
+ isCursor: boolean
25
+ }
26
+
27
+ // ============================================================================
28
+ // Auto-advancing scripted content
29
+ // ============================================================================
30
+
31
+ function useAutoContent(script: ScriptEntry[], fast: boolean): Exchange[] {
32
+ const [exchanges, setExchanges] = useState<Exchange[]>([])
33
+ const [idx, setIdx] = useState(0)
34
+
35
+ useEffect(() => {
36
+ if (idx >= script.length) return
37
+ const delay = fast ? 120 : 600 + Math.random() * 1000
38
+ const timer = setTimeout(() => {
39
+ const entry = script[idx]!
40
+ setExchanges((prev) => [...prev, { ...entry, id: idx }])
41
+ setIdx((i) => i + 1)
42
+ }, delay)
43
+ return () => clearTimeout(timer)
44
+ }, [idx, script, fast])
45
+
46
+ return exchanges
47
+ }
48
+
49
+ // ============================================================================
50
+ // ChatApp — reusable across all modes
51
+ // ============================================================================
52
+
53
+ function ChatApp({
54
+ height,
55
+ active = true,
56
+ surfaceId,
57
+ fast,
58
+ }: {
59
+ height: number
60
+ active?: boolean
61
+ surfaceId?: string
62
+ fast: boolean
63
+ }) {
64
+ const exchanges = useAutoContent(SCRIPT, fast)
65
+
66
+ if (exchanges.length === 0) {
67
+ return (
68
+ <Box paddingX={1}>
69
+ <Text color="$muted">Waiting...</Text>
70
+ </Box>
71
+ )
72
+ }
73
+
74
+ return (
75
+ <ListView
76
+ items={exchanges}
77
+ height={height}
78
+ getKey={(ex: Exchange) => ex.id}
79
+ scrollTo={exchanges.length - 1}
80
+ active={active}
81
+ surfaceId={surfaceId}
82
+ cache={{
83
+ mode: "virtual",
84
+ isCacheable: (_ex: Exchange, i: number) => i < exchanges.length - 1,
85
+ }}
86
+ search={{ getText: (ex: Exchange) => ex.content }}
87
+ renderItem={(ex: Exchange, _i: number, meta: ListItemMeta) => (
88
+ <Box paddingX={1}>
89
+ <Text>
90
+ {meta.isCursor ? ">" : " "}{" "}
91
+ <Text color={ex.role === "user" ? "$primary" : ex.role === "agent" ? "$success" : "$warning"} bold>
92
+ {ex.role}
93
+ </Text>
94
+ : {ex.content.slice(0, 70)}
95
+ </Text>
96
+ </Box>
97
+ )}
98
+ />
99
+ )
100
+ }
101
+
102
+ // ============================================================================
103
+ // Status bar
104
+ // ============================================================================
105
+
106
+ function StatusBar({ mode, count }: { mode: string; count: number }) {
107
+ return (
108
+ <Box paddingX={1}>
109
+ <Text color="$muted">
110
+ [{mode}] {count} exchanges | Ctrl+F search | q quit
111
+ </Text>
112
+ </Box>
113
+ )
114
+ }
115
+
116
+ // ============================================================================
117
+ // Fullscreen / Inline layout
118
+ // ============================================================================
119
+
120
+ function SingleApp({ mode, fast }: { mode: string; fast: boolean }) {
121
+ const exchanges = useAutoContent(SCRIPT, fast)
122
+ const { rows } = useWindowSize()
123
+
124
+ useInput((_input: string, key: Key) => {
125
+ if (key.escape || _input === "q") return "exit"
126
+ })
127
+
128
+ const listHeight = Math.max(5, rows - 2)
129
+
130
+ return (
131
+ <Box flexDirection="column" height={rows}>
132
+ <ChatApp height={listHeight} fast={fast} />
133
+ <SearchBar />
134
+ <StatusBar mode={mode} count={exchanges.length} />
135
+ </Box>
136
+ )
137
+ }
138
+
139
+ // ============================================================================
140
+ // Panes layout
141
+ // ============================================================================
142
+
143
+ function PanesApp({ fast }: { fast: boolean }) {
144
+ const [focus, setFocus] = useState<"left" | "right">("left")
145
+ const exchanges = useAutoContent(SCRIPT, fast)
146
+ const { rows } = useWindowSize()
147
+
148
+ useInput((_input: string, key: Key) => {
149
+ if (key.escape || _input === "q") return "exit"
150
+ if (key.tab) setFocus((f) => (f === "left" ? "right" : "left"))
151
+ })
152
+
153
+ const listHeight = Math.max(5, rows - 4)
154
+
155
+ return (
156
+ <Box flexDirection="column" height={rows}>
157
+ <Box flexDirection="row" flexGrow={1}>
158
+ <Box
159
+ width="50%"
160
+ flexDirection="column"
161
+ borderStyle="single"
162
+ borderColor={focus === "left" ? "$primary" : "$border"}
163
+ overflow="hidden"
164
+ >
165
+ <Box paddingX={1}>
166
+ <Text color={focus === "left" ? "$primary" : "$muted"} bold={focus === "left"}>
167
+ Pane A
168
+ </Text>
169
+ </Box>
170
+ <ChatApp height={listHeight} active={focus === "left"} surfaceId="left" fast={fast} />
171
+ </Box>
172
+ <Box
173
+ width="50%"
174
+ flexDirection="column"
175
+ borderStyle="single"
176
+ borderColor={focus === "right" ? "$primary" : "$border"}
177
+ overflow="hidden"
178
+ >
179
+ <Box paddingX={1}>
180
+ <Text color={focus === "right" ? "$primary" : "$muted"} bold={focus === "right"}>
181
+ Pane B
182
+ </Text>
183
+ </Box>
184
+ <ChatApp height={listHeight} active={focus === "right"} surfaceId="right" fast={fast} />
185
+ </Box>
186
+ </Box>
187
+ <SearchBar />
188
+ <StatusBar mode="panes" count={exchanges.length} />
189
+ </Box>
190
+ )
191
+ }
192
+
193
+ // ============================================================================
194
+ // Entry
195
+ // ============================================================================
196
+
197
+ export async function main() {
198
+ const args = process.argv.slice(2)
199
+ const fast = args.includes("--fast")
200
+ const modeArg = args.find((a) => a.startsWith("--mode="))?.split("=")[1] ?? "fullscreen"
201
+ const mode = modeArg as "inline" | "fullscreen" | "panes"
202
+
203
+ const runtimeMode = mode === "panes" ? "fullscreen" : mode
204
+ const app = mode === "panes" ? <PanesApp fast={fast} /> : <SingleApp mode={mode} fast={fast} />
205
+
206
+ using handle = await run(<SearchProvider>{app}</SearchProvider>, {
207
+ mode: runtimeMode,
208
+ kitty: false,
209
+ textSizing: false,
210
+ })
211
+ await handle.waitUntilExit()
212
+ }
213
+
214
+ if (import.meta.main) {
215
+ main().catch(console.error)
216
+ }
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+ //#region bin/cli.ts
3
+ /**
4
+ * silvery CLI
5
+ *
6
+ * Usage:
7
+ * bunx silvery — show help
8
+ * bunx silvery <name> — run an example by name (fuzzy match)
9
+ * bunx silvery examples — list all available examples
10
+ * bunx silvery doctor — check terminal capabilities
11
+ * bunx silvery --help — show usage help
12
+ */
13
+ const RESET = "\x1B[0m";
14
+ const BOLD = "\x1B[1m";
15
+ const DIM = "\x1B[2m";
16
+ const RED = "\x1B[31m";
17
+ const GREEN = "\x1B[32m";
18
+ const YELLOW = "\x1B[33m";
19
+ const BLUE = "\x1B[34m";
20
+ const MAGENTA = "\x1B[35m";
21
+ const CYAN = "\x1B[36m";
22
+ const WHITE = "\x1B[37m";
23
+ const CATEGORY_DIRS = [
24
+ "components",
25
+ "apps",
26
+ "layout",
27
+ "runtime",
28
+ "inline",
29
+ "kitty"
30
+ ];
31
+ const CATEGORY_DISPLAY = { kitty: "Kitty Protocol" };
32
+ const CATEGORY_ORDER = {
33
+ Components: 0,
34
+ Apps: 1,
35
+ Layout: 2,
36
+ Runtime: 3,
37
+ Inline: 4,
38
+ "Kitty Protocol": 5
39
+ };
40
+ const CATEGORY_COLOR = {
41
+ Components: GREEN,
42
+ Apps: CYAN,
43
+ Layout: MAGENTA,
44
+ Runtime: BLUE,
45
+ Inline: YELLOW,
46
+ "Kitty Protocol": BLUE
47
+ };
48
+ async function discoverExamples() {
49
+ const { resolve, dirname } = await import("node:path");
50
+ const { fileURLToPath } = await import("node:url");
51
+ const { readdirSync } = await import("node:fs");
52
+ const examplesDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
53
+ const results = [];
54
+ for (const dir of CATEGORY_DIRS) {
55
+ const category = CATEGORY_DISPLAY[dir] ?? dir.charAt(0).toUpperCase() + dir.slice(1);
56
+ const dirPath = resolve(examplesDir, dir);
57
+ try {
58
+ const files = readdirSync(dirPath).filter((f) => f.endsWith(".tsx") && !f.startsWith("_"));
59
+ for (const file of files) {
60
+ const name = file.replace(/\.tsx$/, "").replace(/-/g, " ");
61
+ results.push({
62
+ name,
63
+ description: "",
64
+ file: resolve(dirPath, file),
65
+ category
66
+ });
67
+ }
68
+ } catch {}
69
+ }
70
+ const aichatDir = resolve(examplesDir, "apps/aichat");
71
+ try {
72
+ const indexFile = resolve(aichatDir, "index.tsx");
73
+ const { stat } = await import("node:fs/promises");
74
+ await stat(indexFile);
75
+ results.push({
76
+ name: "aichat",
77
+ description: "AI Coding Agent demo",
78
+ file: indexFile,
79
+ category: "Apps"
80
+ });
81
+ } catch {}
82
+ results.sort((a, b) => {
83
+ const catDiff = (CATEGORY_ORDER[a.category] ?? 99) - (CATEGORY_ORDER[b.category] ?? 99);
84
+ if (catDiff !== 0) return catDiff;
85
+ return a.name.localeCompare(b.name);
86
+ });
87
+ return results;
88
+ }
89
+ function printHelp() {
90
+ console.log(`
91
+ ${BOLD}${YELLOW}@silvery/examples${RESET} — Try silvery without installing
92
+
93
+ ${BOLD}Usage:${RESET}
94
+ bunx @silvery/examples ${DIM}<name>${RESET} Run an example by name (fuzzy match)
95
+ bunx @silvery/examples List all available examples
96
+ bunx @silvery/examples --help Show this help
97
+
98
+ ${BOLD}Quick start:${RESET}
99
+ bunx @silvery/examples counter Simple counter (Hello World)
100
+ bunx @silvery/examples dashboard Responsive layout demo
101
+ bunx @silvery/examples kanban Kanban board with keyboard nav
102
+ bunx @silvery/examples textarea Rich text editor
103
+
104
+ ${DIM}Documentation: https://silvery.dev${RESET}
105
+ `);
106
+ }
107
+ function printExampleList(examples) {
108
+ console.log(`\n${BOLD}${YELLOW} silvery${RESET}${DIM} examples${RESET}\n`);
109
+ let currentCategory = "";
110
+ for (const ex of examples) {
111
+ if (ex.category !== currentCategory) {
112
+ currentCategory = ex.category;
113
+ const color = CATEGORY_COLOR[currentCategory] ?? WHITE;
114
+ console.log(` ${color}${BOLD}${currentCategory}${RESET}`);
115
+ }
116
+ const nameStr = `${BOLD}${WHITE}${ex.name}${RESET}`;
117
+ const descStr = ex.description ? `${DIM}${ex.description}${RESET}` : "";
118
+ console.log(` ${nameStr} ${descStr}`);
119
+ }
120
+ console.log(`\n ${DIM}Run: bunx @silvery/examples <name>${RESET}\n`);
121
+ }
122
+ function findExample(examples, query) {
123
+ const q = query.toLowerCase().replace(/-/g, " ");
124
+ const exact = examples.find((ex) => ex.name.toLowerCase() === q);
125
+ if (exact) return exact;
126
+ const prefix = examples.find((ex) => ex.name.toLowerCase().startsWith(q));
127
+ if (prefix) return prefix;
128
+ const substring = examples.find((ex) => ex.name.toLowerCase().includes(q));
129
+ if (substring) return substring;
130
+ }
131
+ function printNoMatch(query, examples) {
132
+ console.error(`\n${RED}${BOLD}Error:${RESET} No example matching "${query}"\n`);
133
+ console.error(`${DIM}Available examples:${RESET}`);
134
+ for (const ex of examples) console.error(` ${WHITE}${ex.name}${RESET}`);
135
+ console.error(`\n${DIM}Run ${BOLD}bunx @silvery/examples${RESET}${DIM} for full list.${RESET}\n`);
136
+ }
137
+ async function exampleCommand(args) {
138
+ const examples = await discoverExamples();
139
+ if (args.length === 0 || args[0] === "--list" || args[0] === "-l") {
140
+ printExampleList(examples);
141
+ return;
142
+ }
143
+ const query = args.filter((a) => !a.startsWith("--")).join(" ");
144
+ if (!query) {
145
+ printExampleList(examples);
146
+ return;
147
+ }
148
+ const match = findExample(examples, query);
149
+ if (!match) {
150
+ printNoMatch(query, examples);
151
+ process.exit(1);
152
+ }
153
+ console.log(`${DIM}Running ${BOLD}${match.name}${RESET}${DIM}...${RESET}\n`);
154
+ const { spawn } = await import("node:child_process");
155
+ const runtime = typeof globalThis.Bun !== "undefined" ? "bun" : "node";
156
+ spawn(runtime, runtime === "bun" ? ["run", match.file] : [match.file], { stdio: "inherit" }).on("exit", (code) => process.exit(code ?? 1));
157
+ }
158
+ async function main() {
159
+ const args = process.argv.slice(2);
160
+ if (args.includes("--help") || args.includes("-h")) {
161
+ printHelp();
162
+ return;
163
+ }
164
+ if (args.length === 0) {
165
+ printExampleList(await discoverExamples());
166
+ return;
167
+ }
168
+ if (args.includes("--version") || args.includes("-v")) {
169
+ try {
170
+ const { resolve, dirname } = await import("node:path");
171
+ const { fileURLToPath } = await import("node:url");
172
+ const { readFileSync } = await import("node:fs");
173
+ const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), "../package.json");
174
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
175
+ console.log(`@silvery/examples ${pkg.version}`);
176
+ } catch {
177
+ console.log("@silvery/examples (version unknown)");
178
+ }
179
+ return;
180
+ }
181
+ await exampleCommand(args);
182
+ }
183
+ main().catch((err) => {
184
+ console.error(err);
185
+ process.exit(1);
186
+ });
187
+ //#endregion
188
+ export {};
189
+
190
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../bin/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * silvery CLI\n *\n * Usage:\n * bunx silvery — show help\n * bunx silvery <name> — run an example by name (fuzzy match)\n * bunx silvery examples — list all available examples\n * bunx silvery doctor — check terminal capabilities\n * bunx silvery --help — show usage help\n */\n\n// =============================================================================\n// ANSI helpers (no deps — must work before anything is imported)\n// =============================================================================\n\nconst RESET = \"\\x1b[0m\"\nconst BOLD = \"\\x1b[1m\"\nconst DIM = \"\\x1b[2m\"\nconst RED = \"\\x1b[31m\"\nconst GREEN = \"\\x1b[32m\"\nconst YELLOW = \"\\x1b[33m\"\nconst BLUE = \"\\x1b[34m\"\nconst MAGENTA = \"\\x1b[35m\"\nconst CYAN = \"\\x1b[36m\"\nconst WHITE = \"\\x1b[37m\"\n\n// =============================================================================\n// Types\n// =============================================================================\n\ninterface Example {\n name: string\n file: string\n description: string\n category: string\n features?: string[]\n}\n\n// =============================================================================\n// Auto-Discovery\n// =============================================================================\n\nconst CATEGORY_DIRS = [\"components\", \"apps\", \"layout\", \"runtime\", \"inline\", \"kitty\"] as const\n\nconst CATEGORY_DISPLAY: Record<string, string> = {\n kitty: \"Kitty Protocol\",\n}\n\nconst CATEGORY_ORDER: Record<string, number> = {\n Components: 0,\n Apps: 1,\n Layout: 2,\n Runtime: 3,\n Inline: 4,\n \"Kitty Protocol\": 5,\n}\n\nconst CATEGORY_COLOR: Record<string, string> = {\n Components: GREEN,\n Apps: CYAN,\n Layout: MAGENTA,\n Runtime: BLUE,\n Inline: YELLOW,\n \"Kitty Protocol\": BLUE,\n}\n\nasync function discoverExamples(): Promise<Example[]> {\n const { resolve, dirname } = await import(\"node:path\")\n const { fileURLToPath } = await import(\"node:url\")\n const { readdirSync } = await import(\"node:fs\")\n const __dirname = dirname(fileURLToPath(import.meta.url))\n const examplesDir = resolve(__dirname, \"..\")\n const results: Example[] = []\n\n for (const dir of CATEGORY_DIRS) {\n const category = CATEGORY_DISPLAY[dir] ?? dir.charAt(0).toUpperCase() + dir.slice(1)\n const dirPath = resolve(examplesDir, dir)\n\n try {\n const files = readdirSync(dirPath).filter((f: string) => f.endsWith(\".tsx\") && !f.startsWith(\"_\"))\n for (const file of files) {\n const name = file.replace(/\\.tsx$/, \"\").replace(/-/g, \" \")\n results.push({\n name,\n description: \"\",\n file: resolve(dirPath, file),\n category,\n })\n }\n } catch {\n // Directory doesn't exist — skip\n }\n }\n\n // Also scan aichat subdirectory\n const aichatDir = resolve(examplesDir, \"apps/aichat\")\n try {\n const indexFile = resolve(aichatDir, \"index.tsx\")\n const { stat } = await import(\"node:fs/promises\")\n await stat(indexFile)\n results.push({\n name: \"aichat\",\n description: \"AI Coding Agent demo\",\n file: indexFile,\n category: \"Apps\",\n })\n } catch {\n // No aichat\n }\n\n results.sort((a, b) => {\n const catDiff = (CATEGORY_ORDER[a.category] ?? 99) - (CATEGORY_ORDER[b.category] ?? 99)\n if (catDiff !== 0) return catDiff\n return a.name.localeCompare(b.name)\n })\n\n return results\n}\n\n// =============================================================================\n// Formatting\n// =============================================================================\n\nfunction printHelp(): void {\n console.log(`\n${BOLD}${YELLOW}@silvery/examples${RESET} — Try silvery without installing\n\n${BOLD}Usage:${RESET}\n bunx @silvery/examples ${DIM}<name>${RESET} Run an example by name (fuzzy match)\n bunx @silvery/examples List all available examples\n bunx @silvery/examples --help Show this help\n\n${BOLD}Quick start:${RESET}\n bunx @silvery/examples counter Simple counter (Hello World)\n bunx @silvery/examples dashboard Responsive layout demo\n bunx @silvery/examples kanban Kanban board with keyboard nav\n bunx @silvery/examples textarea Rich text editor\n\n${DIM}Documentation: https://silvery.dev${RESET}\n`)\n}\n\nfunction printExampleList(examples: Example[]): void {\n console.log(`\\n${BOLD}${YELLOW} silvery${RESET}${DIM} examples${RESET}\\n`)\n\n let currentCategory = \"\"\n\n for (const ex of examples) {\n if (ex.category !== currentCategory) {\n currentCategory = ex.category\n const color = CATEGORY_COLOR[currentCategory] ?? WHITE\n console.log(` ${color}${BOLD}${currentCategory}${RESET}`)\n }\n\n const nameStr = `${BOLD}${WHITE}${ex.name}${RESET}`\n const descStr = ex.description ? `${DIM}${ex.description}${RESET}` : \"\"\n console.log(` ${nameStr} ${descStr}`)\n }\n\n console.log(`\\n ${DIM}Run: bunx @silvery/examples <name>${RESET}\\n`)\n}\n\nfunction findExample(examples: Example[], query: string): Example | undefined {\n const q = query.toLowerCase().replace(/-/g, \" \")\n\n const exact = examples.find((ex) => ex.name.toLowerCase() === q)\n if (exact) return exact\n\n const prefix = examples.find((ex) => ex.name.toLowerCase().startsWith(q))\n if (prefix) return prefix\n\n const substring = examples.find((ex) => ex.name.toLowerCase().includes(q))\n if (substring) return substring\n\n return undefined\n}\n\nfunction printNoMatch(query: string, examples: Example[]): void {\n console.error(`\\n${RED}${BOLD}Error:${RESET} No example matching \"${query}\"\\n`)\n console.error(`${DIM}Available examples:${RESET}`)\n\n for (const ex of examples) {\n console.error(` ${WHITE}${ex.name}${RESET}`)\n }\n\n console.error(`\\n${DIM}Run ${BOLD}bunx @silvery/examples${RESET}${DIM} for full list.${RESET}\\n`)\n}\n\n// =============================================================================\n// Subcommands\n// =============================================================================\n\nasync function exampleCommand(args: string[]): Promise<void> {\n const examples = await discoverExamples()\n\n if (args.length === 0 || args[0] === \"--list\" || args[0] === \"-l\") {\n printExampleList(examples)\n return\n }\n\n const query = args.filter((a) => !a.startsWith(\"--\")).join(\" \")\n if (!query) {\n printExampleList(examples)\n return\n }\n\n const match = findExample(examples, query)\n if (!match) {\n printNoMatch(query, examples)\n process.exit(1)\n }\n\n console.log(`${DIM}Running ${BOLD}${match.name}${RESET}${DIM}...${RESET}\\n`)\n\n const { spawn } = await import(\"node:child_process\")\n const runtime = typeof globalThis.Bun !== \"undefined\" ? \"bun\" : \"node\"\n const runArgs = runtime === \"bun\" ? [\"run\", match.file] : [match.file]\n const proc = spawn(runtime, runArgs, { stdio: \"inherit\" })\n proc.on(\"exit\", (code) => process.exit(code ?? 1))\n}\n\nasync function doctorCommand(): Promise<void> {\n const { resolve, dirname } = await import(\"node:path\")\n const { fileURLToPath } = await import(\"node:url\")\n const __dirname = dirname(fileURLToPath(import.meta.url))\n\n const candidates = [\n resolve(__dirname, \"../../ag-term/src/termtest.ts\"),\n resolve(__dirname, \"../node_modules/@silvery/ag-term/src/termtest.ts\"),\n ]\n\n for (const termtestPath of candidates) {\n try {\n const { stat } = await import(\"node:fs/promises\")\n await stat(termtestPath)\n const { spawn } = await import(\"node:child_process\")\n const runtime = typeof globalThis.Bun !== \"undefined\" ? \"bun\" : \"node\"\n const runArgs = runtime === \"bun\" ? [\"run\", termtestPath] : [termtestPath]\n const proc = spawn(runtime, runArgs, { stdio: \"inherit\" })\n proc.on(\"exit\", (code) => process.exit(code ?? 1))\n return\n } catch {\n continue\n }\n }\n\n console.error(`${RED}Error:${RESET} Could not find terminal diagnostics.`)\n console.error(`${DIM}Make sure silvery is installed: npm install silvery${RESET}`)\n process.exit(1)\n}\n\n// =============================================================================\n// Main\n// =============================================================================\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2)\n\n // Top-level flags\n if (args.includes(\"--help\") || args.includes(\"-h\")) {\n printHelp()\n return\n }\n\n // No args → list examples\n if (args.length === 0) {\n const examples = await discoverExamples()\n printExampleList(examples)\n return\n }\n\n if (args.includes(\"--version\") || args.includes(\"-v\")) {\n try {\n const { resolve, dirname } = await import(\"node:path\")\n const { fileURLToPath } = await import(\"node:url\")\n const { readFileSync } = await import(\"node:fs\")\n const __dirname = dirname(fileURLToPath(import.meta.url))\n const pkgPath = resolve(__dirname, \"../package.json\")\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { version?: string }\n console.log(`@silvery/examples ${pkg.version}`)\n } catch {\n console.log(\"@silvery/examples (version unknown)\")\n }\n return\n }\n\n // \"bunx @silvery/examples counter\" → run counter example directly\n // \"bunx @silvery/examples\" → list (handled above by args.length === 0)\n await exampleCommand(args)\n}\n\nmain().catch((err) => {\n console.error(err)\n process.exit(1)\n})\n"],"mappings":"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"}