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
package/README.md CHANGED
@@ -2,181 +2,77 @@
2
2
 
3
3
  **Polished Terminal UIs in React.**
4
4
 
5
- Responsive layouts, scrollable containers, 100x+ faster incremental updates, and full support for modern terminal capabilities. 30+ components from TextInput to VirtualList. Pure TypeScript, no WASM.
5
+ Ink-compatible React renderer for terminals same `Box`, `Text`, `useInput` API you know. Plus everything you wish Ink had.
6
6
 
7
- ```
8
- npm install silvery react
9
- ```
7
+ > **Note:** Under active development. APIs may change. Feedback welcome.
10
8
 
11
- > **Status:** Alpha — under active development. APIs may change. Early adopters and feedback welcome.
9
+ ```console
10
+ $ npm install silvery react
11
+ ```
12
12
 
13
13
  ```tsx
14
14
  import { useState } from "react"
15
- import { render, Box, Text, useInput, useContentRect, createTerm } from "silvery"
15
+ import { render, Box, Text, useInput } from "silvery"
16
16
 
17
- function App() {
18
- const { width } = useContentRect()
17
+ function Counter() {
19
18
  const [count, setCount] = useState(0)
20
-
21
19
  useInput((input) => {
22
20
  if (input === "j") setCount((c) => c + 1)
23
- if (input === "k") setCount((c) => c - 1)
24
- if (input === "q") return "exit"
25
21
  })
26
-
27
22
  return (
28
- <Box flexDirection="column" padding={1}>
29
- <Text bold>Counter ({width} cols wide)</Text>
23
+ <Box borderStyle="round" padding={1}>
30
24
  <Text>Count: {count}</Text>
31
- <Text dim>j/k = change, q = quit</Text>
32
25
  </Box>
33
26
  )
34
27
  }
35
28
 
36
- using term = createTerm()
37
- await render(<App />, term).run()
38
- ```
39
-
40
- ## Renderer
41
-
42
- ### Responsive layout
43
-
44
- `useContentRect()` returns actual dimensions synchronously -- no post-layout effect, no `{width: 0, height: 0}` on first render. Components adapt to their available space immediately.
45
-
46
- ```tsx
47
- function Responsive() {
48
- const { width } = useContentRect()
49
- return width > 80 ? <FullDashboard /> : <CompactView />
50
- }
51
- ```
52
-
53
- ### Scrollable containers
54
-
55
- `overflow="scroll"` with `scrollTo` -- the framework handles measurement, clipping, and scroll position. No manual virtualization needed.
56
-
57
- ```tsx
58
- <Box height={20} overflow="scroll" scrollTo={selectedIndex}>
59
- {items.map((item) => (
60
- <Card key={item.id} item={item} />
61
- ))}
62
- </Box>
63
- ```
64
-
65
- ### Per-node dirty tracking
66
-
67
- Seven independent dirty flags per node. When a user presses a key, only the affected nodes re-render -- bypassing React reconciliation entirely for unchanged subtrees. Typical interactive updates complete in ~170 microseconds for 1000 nodes, compared to full-tree re-renders.
68
-
69
- ### Multi-target rendering
70
-
71
- Terminal today, Canvas 2D and DOM experimental. Same React components, different rendering backends.
72
-
73
- ## Framework Layers (Optional)
74
-
75
- ### Input layer stack
76
-
77
- DOM-style event bubbling with modal isolation. Opening a dialog automatically captures input -- no manual guard checks in every handler.
78
-
79
- ```tsx
80
- <InputLayerProvider>
81
- <Board />
82
- {isOpen && <Dialog />} {/* Dialog captures input; Board doesn't see it */}
83
- </InputLayerProvider>
29
+ await render(<Counter />).run()
84
30
  ```
85
31
 
86
- ### Spatial focus navigation
32
+ ### Familiar
87
33
 
88
- Tree-based focus with scopes, arrow-key directional movement, click-to-focus, and `useFocusWithin`. Go beyond tab-order.
34
+ - **React 18 + 19** hooks, refs, effects, suspense all works
35
+ - **Flexbox layout** — `Box` with `flexDirection`, `padding`, `gap`, `flexGrow`, just like Ink
36
+ - **Ink/Chalk compatible** — same component model, `@silvery/ink` compatibility layer for migration
89
37
 
90
- ### Command and keybinding system
38
+ ### Better
91
39
 
92
- Named commands with IDs, help text, configurable keybindings, and runtime introspection. Build discoverable, AI-automatable interfaces.
93
-
94
- ```tsx
95
- const MyComponent = withCommands(BaseComponent, () => [
96
- { id: "save", label: "Save", keys: ["ctrl+s"], action: () => save() },
97
- { id: "quit", label: "Quit", keys: ["q", "ctrl+c"], action: () => exit() },
98
- ])
99
- ```
100
-
101
- ### Mouse support
102
-
103
- SGR mouse protocol with DOM-style event props -- `onClick`, `onMouseDown`, `onWheel`, hit testing, drag support.
104
-
105
- ### Multi-line text editing
106
-
107
- Built-in `TextArea` with word wrap, scrolling, cursor movement, selection, and undo/redo via `EditContext`.
108
-
109
- ### 30+ built-in components
110
-
111
- TextArea, TextInput, VirtualList, SelectList, Table, CommandPalette, ModalDialog, Tabs, TreeView, SplitView, Toast, Image, and more -- all with built-in scrolling, focus, and input handling.
112
-
113
- ### Theme system
114
-
115
- `@silvery/theme` with 38 built-in palettes and semantic color tokens (`$primary`, `$error`, `$border`, etc.) that adapt automatically.
116
-
117
- ### TEA state machines
118
-
119
- Optional [Elm Architecture](https://guide.elm-lang.org/architecture/) alongside React hooks. Pure `(action, state) -> [state, effects]` functions for testable, replayable, undoable UI logic.
40
+ - **Smaller install** ~177 KB gzipped all included (Ink 6 pulls 16MB into node_modules)
41
+ - **Pure TypeScript, zero native deps** — no WASM, no build steps — works on Alpine, CI, Docker, everywhere
42
+ - **Incremental rendering** — per-node dirty tracking, [~100x faster interactive updates](tests/perf/render.bench.ts)
43
+ - **Responsive layout** `useContentRect()` returns actual dimensions synchronously during render
44
+ - **Dynamic scrollback** renders (and re-renders!) into the terminal's scroll history, not just alternate screen
45
+ - **Scrollable containers** `overflow="scroll"` with automatic measurement and clipping
46
+ - **Theme system** — 38 palettes, semantic design/color tokens (`$primary`, `$error`), auto-detects terminal colors
47
+ - **30+ components** — TextInput, TextArea, SelectList, VirtualList, Table, Tabs, CommandPalette, ModalDialog, Toast, and more
48
+ - **Focus system** — scoped focus, arrow-key directional nav, click-to-focus
49
+ - **Extremely composable** — use as just a renderer (`render`), add a runtime (`run`), or build full apps (`createApp`). Mix with any React state library (useState, Zustand, Jotai, Redux). Swap terminal backends (real TTY, headless, xterm.js emulator) for testing. Embed silvery components in existing CLIs. Use the layout engine standalone. Render to terminal, or (experimental) Canvas, or DOM
50
+ - **Most complete terminal protocol support** — 100+ escape sequences, all auto-negotiated: 12 OSC (hyperlinks, clipboard, palette, text sizing, semantic prompts, notifications), 35+ CSI (cursor, mouse modes, paste, focus, sync output, device queries), 50+ SGR (6 underline styles, underline colors, truecolor, 256-color), full Kitty keyboard (5 flags), full SGR mouse (any-event, drag, wheel)
120
51
 
121
52
  ## Packages
122
53
 
123
- | Package | Description |
124
- | ------------------------------------ | ----------------------------------------- |
125
- | [`silvery`](packages/) | Umbrella -- re-exports `@silvery/react` |
126
- | [`@silvery/react`](packages/react) | React reconciler, hooks, renderer |
127
- | [`@silvery/term`](packages/term) | Terminal rendering pipeline, ANSI styling |
128
- | [`@silvery/ui`](packages/ui) | Component library (30+ components) |
129
- | [`@silvery/theme`](packages/theme) | Theming with 38 palettes |
130
- | [`@silvery/tea`](packages/tea) | TEA state machine store |
131
- | [`@silvery/compat`](packages/compat) | Ink/Chalk compatibility layers |
132
- | [`@silvery/test`](packages/test) | Testing utilities and locators |
133
-
134
- ## Compatibility
135
-
136
- `silvery/ink` and `silvery/chalk` provide compatibility layers for existing React terminal apps. The core API (`Box`, `Text`, `useInput`, `render`) is intentionally familiar -- most existing code works with minimal changes. See the [migration guide](docs/guide/migration.md) for details.
137
-
138
- ## When to Use Silvery
139
-
140
- Silvery is designed for **complex interactive TUIs** — dashboards, editors, kanban boards, chat interfaces. If you need scrollable containers, mouse support, spatial focus, or components that adapt to their size, Silvery provides these out of the box.
141
-
142
- For simple one-shot CLI prompts or spinners, mature alternatives with larger plugin ecosystems may be a better fit today.
54
+ | Package | Description |
55
+ | --------------- | ------------------------------------------------------------------------------------------ |
56
+ | `silvery` | Components, hooks, renderer — the one package you need |
57
+ | `@silvery/test` | Testing utilities and locators |
58
+ | `@silvery/ink` | Ink compatibility layer |
59
+ | `@silvery/tea` | Optional [TEA](https://guide.elm-lang.org/architecture/) state management for complex apps |
143
60
 
144
61
  ## Ecosystem
145
62
 
146
- | Project | What |
147
- | ------------------------------------------ | -------------------------------------------------------------- |
148
- | [Termless](https://termless.dev) | Headless terminal testing -- like Playwright for terminal apps |
149
- | [Flexily](https://beorn.github.io/flexily) | Pure JS flexbox layout engine (Yoga-compatible, zero WASM) |
150
- | [Loggily](https://beorn.github.io/loggily) | Debug + structured logging + tracing |
151
-
152
- See the [roadmap](https://silvery.dev/roadmap) for what's next.
153
-
154
- ## Performance
63
+ | Project | What |
64
+ | ------------------------------------------ | ------------------------------------------------------------- |
65
+ | [Termless](https://termless.dev) | Headless terminal testing like Playwright for terminal apps |
66
+ | [Flexily](https://beorn.github.io/flexily) | Pure JS flexbox layout engine (Yoga-compatible, zero WASM) |
67
+ | [Loggily](https://beorn.github.io/loggily) | Debug + structured logging + tracing |
155
68
 
156
- _Apple M1 Max, Bun 1.3.9. Reproduce: `bun run bench:compare`_
69
+ ## Coming
157
70
 
158
- | Scenario | Silvery | Ink 5 |
159
- | --------------------------------------- | ------- | ------- |
160
- | Cold render (1 component) | 165 us | 271 us |
161
- | Cold render (1000 components) | 463 ms | 541 ms |
162
- | Typical interactive update (1000 nodes) | 169 us | 20.7 ms |
163
- | Layout (50-node kanban) | 57 us | 88 us |
71
+ - **Renderers** Canvas 2D, Web DOM (experimental today, production later)
72
+ - **Frameworks** Svelte, Solid.js, Vue adapters
73
+ - **@silvery/tea** Structured state management with commands, keybindings, effects-as-data
164
74
 
165
- **Why the difference?** Interactive updates (cursor move, scroll, toggle) typically change one or two nodes. Silvery's per-node dirty tracking updates only those nodes — 169 us for a 1000-node tree. Traditional full-tree renderers re-render the entire React tree and run complete layout on every state change — 20.7 ms. For the updates that dominate interactive use, Silvery is ~100x faster.
166
-
167
- Full re-renders where the entire tree changes are comparable or faster in full-tree renderers (simpler string concatenation vs Silvery's 5-phase pipeline). That trade-off is inherent to supporting responsive layout, and full re-renders are rare in interactive apps.
168
-
169
- ## Documentation
170
-
171
- Full docs at [silvery.dev](https://silvery.dev) -- getting started guide, API reference, component catalog, and migration guide.
172
-
173
- ## Development
174
-
175
- ```bash
176
- bun install
177
- bun test
178
- bun run lint
179
- ```
75
+ **Runtimes:** Bun >= 1.0 and Node.js >= 18. CLI (`silvery` command) requires Bun.
180
76
 
181
77
  ## License
182
78
 
package/dist/chalk.js ADDED
@@ -0,0 +1,3 @@
1
+ import{Chalk}from"chalk";var CI_ENVS=["CI","GITHUB_ACTIONS","GITLAB_CI","JENKINS_URL","BUILDKITE","CIRCLECI","TRAVIS"];function detectColor(stdout){if(process.env.NO_COLOR!==void 0)return null;let forceColor=process.env.FORCE_COLOR;if(forceColor!==void 0){if(forceColor==="0"||forceColor==="false")return null;if(forceColor==="1")return"basic";if(forceColor==="2")return"256";if(forceColor==="3")return"truecolor";return"basic"}if(!stdout.isTTY)return null;if(process.env.TERM==="dumb")return null;let colorTerm=process.env.COLORTERM;if(colorTerm==="truecolor"||colorTerm==="24bit")return"truecolor";let term=process.env.TERM??"";if(term.includes("truecolor")||term.includes("24bit")||term.includes("xterm-ghostty")||term.includes("xterm-kitty")||term.includes("wezterm"))return"truecolor";if(term.includes("256color")||term.includes("256"))return"256";let termProgram=process.env.TERM_PROGRAM;if(termProgram==="iTerm.app"||termProgram==="Apple_Terminal")return termProgram==="iTerm.app"?"truecolor":"256";if(termProgram==="Ghostty"||termProgram==="WezTerm")return"truecolor";if(process.env.KITTY_WINDOW_ID)return"truecolor";if(term.includes("xterm")||term.includes("color")||term.includes("ansi"))return"basic";if(CI_ENVS.some((env)=>process.env[env]!==void 0))return"basic";if(process.env.WT_SESSION)return"truecolor";return"basic"}function toChalkLevel(cl){if(cl===null)return 0;if(cl==="basic")return 1;if(cl==="256")return 2;return 3}function fromChalkLevel(level){if(level===0)return null;if(level===1)return"basic";if(level===2)return"256";return"truecolor"}var detectedLevel=toChalkLevel(typeof process<"u"&&process.stdout?detectColor(process.stdout):null),chalk=new Chalk({level:detectedLevel}),chalk_default=chalk;var supportsColor=detectedLevel===0?!1:{level:detectedLevel},supportsColorStderr=(()=>{if(process?.stderr)return!1;let level=toChalkLevel(detectColor(process.stderr));return level===0?!1:{level}})(),modifierNames=["reset","bold","dim","italic","underline","overline","inverse","hidden","strikethrough","visible"],foregroundColorNames=["black","red","green","yellow","blue","magenta","cyan","white","gray","grey","blackBright","redBright","greenBright","yellowBright","blueBright","magentaBright","cyanBright","whiteBright"],backgroundColorNames=["bgBlack","bgRed","bgGreen","bgYellow","bgBlue","bgMagenta","bgCyan","bgWhite","bgGray","bgGrey","bgBlackBright","bgRedBright","bgGreenBright","bgYellowBright","bgBlueBright","bgMagentaBright","bgCyanBright","bgWhiteBright"],colorNames=[...foregroundColorNames,...backgroundColorNames];export{toChalkLevel,supportsColorStderr,supportsColor,modifierNames,fromChalkLevel,foregroundColorNames,detectColor,chalk_default as default,colorNames,backgroundColorNames,Chalk};
2
+
3
+ //# debugId=123D36099BFF5DC664756E2164756E21
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../packages/ink/src/chalk.ts", "../packages/ag-term/src/ansi/detection.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * silvery/chalk — Drop-in chalk replacement.\n *\n * ```ts\n * // Before:\n * import chalk from 'chalk'\n *\n * // After:\n * import chalk from 'silvery/chalk'\n * ```\n *\n * The default export is a chainable styling function identical to chalk's API.\n * Under the hood it uses @silvery/ansi's Term (which itself wraps chalk).\n *\n * For silvery-native features (detection, hyperlinks, extended underlines),\n * use `@silvery/ansi` directly.\n *\n * @packageDocumentation\n */\n\nimport { Chalk, type ChalkInstance } from \"chalk\"\nimport { detectColor } from \"@silvery/ag-term/ansi/detection\"\nimport type { ColorLevel } from \"@silvery/ag-term/ansi/types\"\n\n// =============================================================================\n// Color level conversion\n// =============================================================================\n\ntype ChalkLevel = 0 | 1 | 2 | 3\n\nfunction toChalkLevel(cl: ColorLevel | null): ChalkLevel {\n if (cl === null) return 0\n if (cl === \"basic\") return 1\n if (cl === \"256\") return 2\n return 3 // truecolor\n}\n\nfunction fromChalkLevel(level: ChalkLevel): ColorLevel | null {\n if (level === 0) return null\n if (level === 1) return \"basic\"\n if (level === 2) return \"256\"\n return \"truecolor\"\n}\n\n// =============================================================================\n// Default chalk instance (auto-detected)\n// =============================================================================\n\nconst detectedLevel = toChalkLevel(\n // eslint-disable-next-line @typescript-eslint/prefer-optional-chain -- typeof guard prevents ReferenceError in environments without process global\n typeof process !== \"undefined\" && process.stdout ? detectColor(process.stdout) : null,\n)\n\n/**\n * Default chalk instance — drop-in replacement for `import chalk from 'chalk'`.\n *\n * Supports the full chainable API: `chalk.bold.red('error')`, `chalk.hex('#ff0')('hi')`, etc.\n */\nconst chalk = new Chalk({ level: detectedLevel })\nexport default chalk\n\n// =============================================================================\n// Named exports (chalk 5.x compatibility)\n// =============================================================================\n\nexport { Chalk, type ChalkInstance }\n\n/**\n * Color support detection for stdout.\n * Returns false if no color, or an object with the chalk level.\n */\nexport const supportsColor: false | { level: ChalkLevel } = detectedLevel === 0 ? false : { level: detectedLevel }\n\n/**\n * Color support detection for stderr.\n */\nexport const supportsColorStderr: false | { level: ChalkLevel } = (() => {\n if (process?.stderr) return false\n const level = toChalkLevel(detectColor(process.stderr))\n return level === 0 ? false : { level }\n})()\n\n// =============================================================================\n// Chalk name lists (for programmatic access)\n// =============================================================================\n\nexport const modifierNames = [\n \"reset\",\n \"bold\",\n \"dim\",\n \"italic\",\n \"underline\",\n \"overline\",\n \"inverse\",\n \"hidden\",\n \"strikethrough\",\n \"visible\",\n] as const\n\nexport const foregroundColorNames = [\n \"black\",\n \"red\",\n \"green\",\n \"yellow\",\n \"blue\",\n \"magenta\",\n \"cyan\",\n \"white\",\n \"gray\",\n \"grey\",\n \"blackBright\",\n \"redBright\",\n \"greenBright\",\n \"yellowBright\",\n \"blueBright\",\n \"magentaBright\",\n \"cyanBright\",\n \"whiteBright\",\n] as const\n\nexport const backgroundColorNames = [\n \"bgBlack\",\n \"bgRed\",\n \"bgGreen\",\n \"bgYellow\",\n \"bgBlue\",\n \"bgMagenta\",\n \"bgCyan\",\n \"bgWhite\",\n \"bgGray\",\n \"bgGrey\",\n \"bgBlackBright\",\n \"bgRedBright\",\n \"bgGreenBright\",\n \"bgYellowBright\",\n \"bgBlueBright\",\n \"bgMagentaBright\",\n \"bgCyanBright\",\n \"bgWhiteBright\",\n] as const\n\nexport const colorNames = [...foregroundColorNames, ...backgroundColorNames] as const\n\n// Re-export detection utilities that chalk users often need\nexport { detectColor, toChalkLevel, fromChalkLevel }\nexport type { ColorLevel, ChalkLevel }\n",
6
+ "/**\n * Terminal capability detection.\n *\n * Detects:\n * - Cursor control (can reposition cursor)\n * - Input capability (can read raw keystrokes)\n * - Color level (basic, 256, truecolor)\n * - Unicode support (can render unicode symbols)\n * - Extended underline support (curly, dotted, etc)\n * - Terminal capabilities profile (TerminalCaps)\n */\n\nimport { spawnSync } from \"child_process\"\nimport type { ColorLevel } from \"./types\"\n\n// =============================================================================\n// Cursor Detection\n// =============================================================================\n\n/**\n * Detect if terminal supports cursor control (repositioning).\n * Returns false for dumb terminals and piped output.\n */\nexport function detectCursor(stdout: NodeJS.WriteStream): boolean {\n // Not a TTY - no cursor control\n if (!stdout.isTTY) return false\n\n // Dumb terminal - no cursor control\n if (process.env.TERM === \"dumb\") return false\n\n return true\n}\n\n// =============================================================================\n// Input Detection\n// =============================================================================\n\n/**\n * Detect if terminal can read raw keystrokes.\n * Requires stdin to be a TTY with raw mode support.\n */\nexport function detectInput(stdin: NodeJS.ReadStream): boolean {\n // Not a TTY - no raw input\n if (!stdin.isTTY) return false\n\n // Check if setRawMode is available\n return typeof stdin.setRawMode === \"function\"\n}\n\n// =============================================================================\n// Color Detection\n// =============================================================================\n\n/**\n * Known CI environments that may not support colors well.\n */\nconst CI_ENVS = [\"CI\", \"GITHUB_ACTIONS\", \"GITLAB_CI\", \"JENKINS_URL\", \"BUILDKITE\", \"CIRCLECI\", \"TRAVIS\"]\n\n/**\n * Detect color level supported by terminal.\n * Returns null if no color support.\n *\n * Checks (in order):\n * 1. NO_COLOR env var - forces no color\n * 2. FORCE_COLOR env var - forces color level\n * 3. COLORTERM=truecolor - truecolor support\n * 4. TERM patterns - detect from terminal type\n * 5. CI detection - basic colors in CI\n */\nexport function detectColor(stdout: NodeJS.WriteStream): ColorLevel | null {\n // NO_COLOR takes precedence (see https://no-color.org/)\n if (process.env.NO_COLOR !== undefined) {\n return null\n }\n\n // FORCE_COLOR overrides detection\n const forceColor = process.env.FORCE_COLOR\n if (forceColor !== undefined) {\n if (forceColor === \"0\" || forceColor === \"false\") return null\n if (forceColor === \"1\") return \"basic\"\n if (forceColor === \"2\") return \"256\"\n if (forceColor === \"3\") return \"truecolor\"\n // Any other truthy value defaults to basic\n return \"basic\"\n }\n\n // Non-TTY without FORCE_COLOR - no colors\n if (!stdout.isTTY) {\n return null\n }\n\n // Dumb terminal\n if (process.env.TERM === \"dumb\") {\n return null\n }\n\n // COLORTERM=truecolor indicates 24-bit support\n const colorTerm = process.env.COLORTERM\n if (colorTerm === \"truecolor\" || colorTerm === \"24bit\") {\n return \"truecolor\"\n }\n\n // Check TERM for color hints\n const term = process.env.TERM ?? \"\"\n\n // Known truecolor terminals\n if (\n term.includes(\"truecolor\") ||\n term.includes(\"24bit\") ||\n term.includes(\"xterm-ghostty\") ||\n term.includes(\"xterm-kitty\") ||\n term.includes(\"wezterm\")\n ) {\n return \"truecolor\"\n }\n\n // 256-color terminals\n if (term.includes(\"256color\") || term.includes(\"256\")) {\n return \"256\"\n }\n\n // Modern macOS terminals typically support truecolor\n const termProgram = process.env.TERM_PROGRAM\n if (termProgram === \"iTerm.app\" || termProgram === \"Apple_Terminal\") {\n return termProgram === \"iTerm.app\" ? \"truecolor\" : \"256\"\n }\n\n // Ghostty, WezTerm, Kitty via TERM_PROGRAM\n if (termProgram === \"Ghostty\" || termProgram === \"WezTerm\") {\n return \"truecolor\"\n }\n\n // Kitty via env var\n if (process.env.KITTY_WINDOW_ID) {\n return \"truecolor\"\n }\n\n // xterm-color variants get basic colors\n if (term.includes(\"xterm\") || term.includes(\"color\") || term.includes(\"ansi\")) {\n return \"basic\"\n }\n\n // CI environments usually support basic colors\n if (CI_ENVS.some((env) => process.env[env] !== undefined)) {\n return \"basic\"\n }\n\n // Windows Terminal (modern)\n if (process.env.WT_SESSION) {\n return \"truecolor\"\n }\n\n // Default: basic colors if TTY\n return \"basic\"\n}\n\n// =============================================================================\n// Unicode Detection\n// =============================================================================\n\n/**\n * Detect if terminal can render unicode symbols.\n * Based on TERM, locale, and known terminal apps.\n */\nexport function detectUnicode(): boolean {\n // CI environments - often UTF-8 capable but be conservative\n if (process.env.CI) {\n // GitHub Actions is UTF-8\n if (process.env.GITHUB_ACTIONS) return true\n // Other CI - check LANG\n }\n\n // Check locale for UTF-8\n const lang = process.env.LANG ?? process.env.LC_ALL ?? process.env.LC_CTYPE ?? \"\"\n if (lang.toLowerCase().includes(\"utf-8\") || lang.toLowerCase().includes(\"utf8\")) {\n return true\n }\n\n // Windows Terminal\n if (process.env.WT_SESSION) {\n return true\n }\n\n // Modern terminal programs\n const termProgram = process.env.TERM_PROGRAM ?? \"\"\n if ([\"iTerm.app\", \"Ghostty\", \"WezTerm\", \"Apple_Terminal\"].includes(termProgram)) {\n return true\n }\n\n // Kitty\n if (process.env.KITTY_WINDOW_ID) {\n return true\n }\n\n // Check TERM for modern terminals\n const term = process.env.TERM ?? \"\"\n if (term.includes(\"xterm\") || term.includes(\"rxvt\") || term.includes(\"screen\") || term.includes(\"tmux\")) {\n return true\n }\n\n // Default: assume no unicode for safety\n return false\n}\n\n// =============================================================================\n// Extended Underline Detection\n// =============================================================================\n\n/**\n * Known terminals with extended underline support.\n */\nconst EXTENDED_UNDERLINE_TERMS = [\"xterm-ghostty\", \"xterm-kitty\", \"wezterm\", \"xterm-256color\"]\n\n/**\n * Known terminal programs with extended underline support.\n */\nconst EXTENDED_UNDERLINE_PROGRAMS = [\"Ghostty\", \"iTerm.app\", \"WezTerm\"]\n\n/**\n * Detect if terminal supports extended underline styles.\n * (curly, dotted, dashed, double)\n *\n * Extended underlines use SGR 4:x (style) and SGR 58;2;r;g;b (color).\n * These are NOT supported by Terminal.app, which misinterprets them\n * as background colors causing visual artifacts.\n */\nexport function detectExtendedUnderline(): boolean {\n const term = process.env.TERM ?? \"\"\n const termProgram = process.env.TERM_PROGRAM ?? \"\"\n\n // Apple Terminal doesn't support extended underlines - check FIRST\n // because it often sets TERM=xterm-256color which would otherwise match\n if (termProgram === \"Apple_Terminal\") {\n return false\n }\n\n // Check TERM variable for known modern terminals\n if (EXTENDED_UNDERLINE_TERMS.some((t) => term.includes(t))) {\n return true\n }\n\n // Check TERM_PROGRAM for known terminal applications\n if (EXTENDED_UNDERLINE_PROGRAMS.some((p) => termProgram.includes(p))) {\n return true\n }\n\n // Kitty sets KITTY_WINDOW_ID\n if (process.env.KITTY_WINDOW_ID) {\n return true\n }\n\n // Default to false for unknown terminals\n return false\n}\n\n// =============================================================================\n// Terminal Capabilities Profile\n// =============================================================================\n\nexport interface TerminalCaps {\n /** Terminal program name (from TERM_PROGRAM) */\n program: string\n /** TERM value */\n term: string\n /** Color support level */\n colorLevel: \"none\" | \"basic\" | \"256\" | \"truecolor\"\n /** Kitty keyboard protocol supported */\n kittyKeyboard: boolean\n /** Kitty graphics protocol (inline images) */\n kittyGraphics: boolean\n /** Sixel graphics supported */\n sixel: boolean\n /** OSC 52 clipboard */\n osc52: boolean\n /** OSC 8 hyperlinks */\n hyperlinks: boolean\n /** OSC 9/99 notifications */\n notifications: boolean\n /** Bracketed paste mode */\n bracketedPaste: boolean\n /** SGR mouse tracking */\n mouse: boolean\n /** Synchronized output (DEC 2026) */\n syncOutput: boolean\n /** Unicode/emoji support */\n unicode: boolean\n /** SGR 4:x underline style subparameters (curly, dotted, dashed) */\n underlineStyles: boolean\n /** SGR 58 underline color */\n underlineColor: boolean\n /** Text-presentation emoji (⚠, ☑, ⭐) rendered as 2-wide.\n * Modern terminals (Ghostty, iTerm, Kitty) render these at emoji width (2 cells).\n * Terminal.app renders them at text width (1 cell). */\n textEmojiWide: boolean\n /** OSC 66 text sizing protocol likely supported (Kitty 0.40+, Ghostty) */\n textSizingSupported: boolean\n /** Heuristic: likely dark background (for theme selection) */\n darkBackground: boolean\n /** Heuristic: likely has Nerd Font installed (for icon selection) */\n nerdfont: boolean\n}\n\n/**\n * Default capabilities (assumes modern terminal with full support).\n */\nexport function defaultCaps(): TerminalCaps {\n return {\n program: \"\",\n term: \"\",\n colorLevel: \"truecolor\",\n kittyKeyboard: false,\n kittyGraphics: false,\n sixel: false,\n osc52: false,\n hyperlinks: false,\n notifications: false,\n bracketedPaste: true,\n mouse: true,\n syncOutput: false,\n unicode: true,\n underlineStyles: true,\n underlineColor: true,\n textEmojiWide: true,\n textSizingSupported: false,\n darkBackground: true,\n nerdfont: false,\n }\n}\n\n/**\n * Cached result of macOS dark mode detection.\n * Computed lazily on first access to avoid spawnSync at module load time.\n */\nlet cachedMacOSDarkMode: boolean | undefined\n\n/**\n * Check if macOS is in dark mode by reading the system appearance preference.\n * Uses `defaults read -g AppleInterfaceStyle` — returns \"Dark\" when dark mode\n * is active, exits non-zero when light mode. ~2ms via spawnSync.\n *\n * Result is cached after first call to avoid repeated process spawns.\n */\nfunction detectMacOSDarkMode(): boolean {\n if (cachedMacOSDarkMode !== undefined) return cachedMacOSDarkMode\n\n try {\n const result = spawnSync(\"defaults\", [\"read\", \"-g\", \"AppleInterfaceStyle\"], {\n encoding: \"utf-8\",\n timeout: 500,\n })\n cachedMacOSDarkMode = result.stdout?.trim() === \"Dark\"\n } catch {\n cachedMacOSDarkMode = false\n }\n\n return cachedMacOSDarkMode\n}\n\n/** Detect terminal capabilities from environment variables.\n * Synchronous. Minimal I/O: may run `defaults` on macOS for Apple_Terminal.\n */\nexport function detectTerminalCaps(): TerminalCaps {\n const program = process.env.TERM_PROGRAM ?? \"\"\n const term = process.env.TERM ?? \"\"\n const colorTerm = process.env.COLORTERM ?? \"\"\n const noColor = process.env.NO_COLOR !== undefined\n\n const isAppleTerminal = program === \"Apple_Terminal\"\n\n let colorLevel: TerminalCaps[\"colorLevel\"] = \"none\"\n if (!noColor) {\n if (isAppleTerminal) {\n colorLevel = \"256\"\n } else if (colorTerm === \"truecolor\" || colorTerm === \"24bit\") {\n colorLevel = \"truecolor\"\n } else if (term.includes(\"256color\")) {\n colorLevel = \"256\"\n } else if (process.stdout?.isTTY) {\n colorLevel = \"basic\"\n }\n }\n\n const isKitty = term === \"xterm-kitty\"\n const isITerm = program === \"iTerm.app\"\n const isGhostty = program === \"ghostty\"\n const isWezTerm = program === \"WezTerm\"\n const isAlacritty = program === \"Alacritty\"\n const isFoot = term === \"foot\" || term === \"foot-extra\"\n const isModern = isKitty || isITerm || isGhostty || isWezTerm || isFoot\n\n // Kitty v0.40+ supports OSC 66 text sizing\n let isKittyWithTextSizing = false\n if (isKitty) {\n const version = process.env.TERM_PROGRAM_VERSION ?? \"\"\n const parts = version.split(\".\")\n const major = Number(parts[0]) || 0\n const minor = Number(parts[1]) || 0\n isKittyWithTextSizing = major > 0 || (major === 0 && minor >= 40)\n }\n\n let darkBackground = !isAppleTerminal\n const colorFgBg = process.env.COLORFGBG\n if (colorFgBg) {\n const parts = colorFgBg.split(\";\")\n const bg = parseInt(parts[parts.length - 1] ?? \"\", 10)\n if (!isNaN(bg)) {\n darkBackground = bg < 7\n }\n } else if (isAppleTerminal) {\n darkBackground = detectMacOSDarkMode()\n }\n\n let nerdfont = isModern || isAlacritty\n const nfEnv = process.env.NERDFONT\n if (nfEnv === \"0\" || nfEnv === \"false\") nerdfont = false\n else if (nfEnv === \"1\" || nfEnv === \"true\") nerdfont = true\n\n const underlineExtensions = isModern || isAlacritty\n\n return {\n program,\n term,\n colorLevel,\n kittyKeyboard: isKitty || isGhostty || isWezTerm || isFoot,\n kittyGraphics: isKitty || isGhostty,\n sixel: isFoot || isWezTerm,\n osc52: isModern || isAlacritty,\n hyperlinks: isModern || isAlacritty,\n notifications: isITerm || isKitty,\n bracketedPaste: true,\n mouse: true,\n syncOutput: isModern || isAlacritty,\n unicode: true,\n underlineStyles: underlineExtensions,\n underlineColor: underlineExtensions,\n textEmojiWide: !isAppleTerminal,\n textSizingSupported: isKittyWithTextSizing, // Ghostty parses OSC 66 but doesn't render it (v1.3.0)\n darkBackground,\n nerdfont,\n }\n}\n"
7
+ ],
8
+ "mappings": "AAoBA,yBCoCA,IAAM,QAAU,CAAC,KAAM,iBAAkB,YAAa,cAAe,YAAa,WAAY,QAAQ,EAa/F,SAAS,WAAW,CAAC,OAA+C,CAEzE,GAAI,QAAQ,IAAI,WAAa,OAC3B,OAAO,KAIT,IAAM,WAAa,QAAQ,IAAI,YAC/B,GAAI,aAAe,OAAW,CAC5B,GAAI,aAAe,KAAO,aAAe,QAAS,OAAO,KACzD,GAAI,aAAe,IAAK,MAAO,QAC/B,GAAI,aAAe,IAAK,MAAO,MAC/B,GAAI,aAAe,IAAK,MAAO,YAE/B,MAAO,QAIT,GAAI,CAAC,OAAO,MACV,OAAO,KAIT,GAAI,QAAQ,IAAI,OAAS,OACvB,OAAO,KAIT,IAAM,UAAY,QAAQ,IAAI,UAC9B,GAAI,YAAc,aAAe,YAAc,QAC7C,MAAO,YAIT,IAAM,KAAO,QAAQ,IAAI,MAAQ,GAGjC,GACE,KAAK,SAAS,WAAW,GACzB,KAAK,SAAS,OAAO,GACrB,KAAK,SAAS,eAAe,GAC7B,KAAK,SAAS,aAAa,GAC3B,KAAK,SAAS,SAAS,EAEvB,MAAO,YAIT,GAAI,KAAK,SAAS,UAAU,GAAK,KAAK,SAAS,KAAK,EAClD,MAAO,MAIT,IAAM,YAAc,QAAQ,IAAI,aAChC,GAAI,cAAgB,aAAe,cAAgB,iBACjD,OAAO,cAAgB,YAAc,YAAc,MAIrD,GAAI,cAAgB,WAAa,cAAgB,UAC/C,MAAO,YAIT,GAAI,QAAQ,IAAI,gBACd,MAAO,YAIT,GAAI,KAAK,SAAS,OAAO,GAAK,KAAK,SAAS,OAAO,GAAK,KAAK,SAAS,MAAM,EAC1E,MAAO,QAIT,GAAI,QAAQ,KAAK,CAAC,MAAQ,QAAQ,IAAI,OAAS,MAAS,EACtD,MAAO,QAIT,GAAI,QAAQ,IAAI,WACd,MAAO,YAIT,MAAO,QD3HT,SAAS,YAAY,CAAC,GAAmC,CACvD,GAAI,KAAO,KAAM,MAAO,GACxB,GAAI,KAAO,QAAS,MAAO,GAC3B,GAAI,KAAO,MAAO,MAAO,GACzB,MAAO,GAGT,SAAS,cAAc,CAAC,MAAsC,CAC5D,GAAI,QAAU,EAAG,OAAO,KACxB,GAAI,QAAU,EAAG,MAAO,QACxB,GAAI,QAAU,EAAG,MAAO,MACxB,MAAO,YAOT,IAAM,cAAgB,aAEpB,OAAO,QAAY,KAAe,QAAQ,OAAS,YAAY,QAAQ,MAAM,EAAI,IACnF,EAOM,MAAQ,IAAI,MAAM,CAAE,MAAO,aAAc,CAAC,EACjC,oBAYR,IAAM,cAA+C,gBAAkB,EAAI,GAAQ,CAAE,MAAO,aAAc,EAKpG,qBAAsD,IAAM,CACvE,GAAI,SAAS,OAAQ,MAAO,GAC5B,IAAM,MAAQ,aAAa,YAAY,QAAQ,MAAM,CAAC,EACtD,OAAO,QAAU,EAAI,GAAQ,CAAE,KAAM,IACpC,EAMU,cAAgB,CAC3B,QACA,OACA,MACA,SACA,YACA,WACA,UACA,SACA,gBACA,SACF,EAEa,qBAAuB,CAClC,QACA,MACA,QACA,SACA,OACA,UACA,OACA,QACA,OACA,OACA,cACA,YACA,cACA,eACA,aACA,gBACA,aACA,aACF,EAEa,qBAAuB,CAClC,UACA,QACA,UACA,WACA,SACA,YACA,SACA,UACA,SACA,SACA,gBACA,cACA,gBACA,iBACA,eACA,kBACA,eACA,eACF,EAEa,WAAa,CAAC,GAAG,qBAAsB,GAAG,oBAAoB",
9
+ "debugId": "123D36099BFF5DC664756E2164756E21",
10
+ "names": []
11
+ }