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.
- package/README.md +41 -145
- package/dist/chalk.js +3 -0
- package/dist/chalk.js.map +11 -0
- package/dist/index.js +340 -0
- package/dist/index.js.map +282 -0
- package/dist/ink.js +129 -0
- package/dist/ink.js.map +140 -0
- package/dist/runtime.js +394 -0
- package/dist/runtime.js.map +286 -0
- package/dist/theme.js +343 -0
- package/dist/theme.js.map +286 -0
- package/dist/ui/animation.js +3 -0
- package/dist/ui/animation.js.map +15 -0
- package/dist/ui/ansi.js +3 -0
- package/dist/ui/ansi.js.map +10 -0
- package/dist/ui/cli.js +8 -0
- package/dist/ui/cli.js.map +14 -0
- package/dist/ui/display.js +4 -0
- package/dist/ui/display.js.map +10 -0
- package/dist/ui/image.js +4 -0
- package/dist/ui/image.js.map +15 -0
- package/dist/ui/input.js +3 -0
- package/dist/ui/input.js.map +11 -0
- package/dist/ui/progress.js +8 -0
- package/dist/ui/progress.js.map +20 -0
- package/dist/ui/react.js +3 -0
- package/dist/ui/react.js.map +15 -0
- package/dist/ui/utils.js +3 -0
- package/dist/ui/utils.js.map +10 -0
- package/dist/ui/wrappers.js +14 -0
- package/dist/ui/wrappers.js.map +19 -0
- package/dist/ui.js +17 -0
- package/dist/ui.js.map +20 -0
- package/package.json +67 -15
- package/src/index.ts +67 -1
- package/src/runtime.ts +4 -0
- package/src/theme.ts +4 -0
- package/src/ui/animation.ts +2 -0
- package/src/ui/ansi.ts +2 -0
- package/src/ui/cli.ts +2 -0
- package/src/ui/display.ts +2 -0
- package/src/ui/image.ts +2 -0
- package/src/ui/input.ts +2 -0
- package/src/ui/progress.ts +2 -0
- package/src/ui/react.ts +2 -0
- package/src/ui/utils.ts +2 -0
- package/src/ui/wrappers.ts +2 -0
- package/src/ui.ts +4 -0
- package/examples/CLAUDE.md +0 -75
- package/examples/_banner.tsx +0 -60
- package/examples/cli.ts +0 -228
- package/examples/index.md +0 -101
- package/examples/inline/inline-nontty.tsx +0 -98
- package/examples/inline/inline-progress.tsx +0 -79
- package/examples/inline/inline-simple.tsx +0 -63
- package/examples/inline/scrollback.tsx +0 -185
- package/examples/interactive/_input-debug.tsx +0 -110
- package/examples/interactive/_stdin-test.ts +0 -71
- package/examples/interactive/_textarea-bare.tsx +0 -45
- package/examples/interactive/aichat/components.tsx +0 -468
- package/examples/interactive/aichat/index.tsx +0 -207
- package/examples/interactive/aichat/script.ts +0 -460
- package/examples/interactive/aichat/state.ts +0 -326
- package/examples/interactive/aichat/types.ts +0 -19
- package/examples/interactive/app-todo.tsx +0 -198
- package/examples/interactive/async-data.tsx +0 -208
- package/examples/interactive/cli-wizard.tsx +0 -332
- package/examples/interactive/clipboard.tsx +0 -183
- package/examples/interactive/components.tsx +0 -463
- package/examples/interactive/data-explorer.tsx +0 -506
- package/examples/interactive/dev-tools.tsx +0 -379
- package/examples/interactive/explorer.tsx +0 -747
- package/examples/interactive/gallery.tsx +0 -652
- package/examples/interactive/inline-bench.tsx +0 -136
- package/examples/interactive/kanban.tsx +0 -267
- package/examples/interactive/layout-ref.tsx +0 -185
- package/examples/interactive/outline.tsx +0 -171
- package/examples/interactive/paste-demo.tsx +0 -198
- package/examples/interactive/scroll.tsx +0 -77
- package/examples/interactive/search-filter.tsx +0 -240
- package/examples/interactive/task-list.tsx +0 -279
- package/examples/interactive/terminal.tsx +0 -798
- package/examples/interactive/textarea.tsx +0 -103
- package/examples/interactive/theme.tsx +0 -336
- package/examples/interactive/transform.tsx +0 -256
- package/examples/interactive/virtual-10k.tsx +0 -413
- package/examples/kitty/canvas.tsx +0 -519
- package/examples/kitty/generate-samples.ts +0 -236
- package/examples/kitty/image-component.tsx +0 -273
- package/examples/kitty/images.tsx +0 -604
- package/examples/kitty/input.tsx +0 -371
- package/examples/kitty/keys.tsx +0 -378
- package/examples/kitty/paint.tsx +0 -1017
- package/examples/layout/dashboard.tsx +0 -551
- package/examples/layout/live-resize.tsx +0 -290
- package/examples/layout/overflow.tsx +0 -51
- package/examples/playground/README.md +0 -69
- package/examples/playground/build.ts +0 -61
- package/examples/playground/index.html +0 -420
- package/examples/playground/playground-app.tsx +0 -416
- package/examples/runtime/elm-counter.tsx +0 -206
- package/examples/runtime/hello-runtime.tsx +0 -73
- package/examples/runtime/pipe-composition.tsx +0 -184
- package/examples/runtime/run-counter.tsx +0 -78
- package/examples/runtime/runtime-counter.tsx +0 -197
- package/examples/screenshots/generate.tsx +0 -563
- package/examples/scrollback-perf.tsx +0 -230
- package/examples/viewer.tsx +0 -654
- package/examples/web/build.ts +0 -365
- package/examples/web/canvas-app.tsx +0 -80
- package/examples/web/canvas.html +0 -89
- package/examples/web/dom-app.tsx +0 -81
- package/examples/web/dom.html +0 -113
- package/examples/web/showcase-app.tsx +0 -107
- package/examples/web/showcase.html +0 -34
- package/examples/web/showcases/index.tsx +0 -56
- package/examples/web/viewer-app.tsx +0 -555
- package/examples/web/viewer.html +0 -30
- package/examples/web/xterm-app.tsx +0 -105
- 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
|
-
|
|
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
|
-
|
|
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
|
|
15
|
+
import { render, Box, Text, useInput } from "silvery"
|
|
16
16
|
|
|
17
|
-
function
|
|
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
|
|
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
|
-
|
|
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
|
-
###
|
|
32
|
+
### Familiar
|
|
87
33
|
|
|
88
|
-
|
|
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
|
-
###
|
|
38
|
+
### Better
|
|
91
39
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
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
|
|
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
|
-
|
|
69
|
+
## Coming
|
|
157
70
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
**
|
|
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
|
+
}
|