Skip to content

Commit c363507

Browse files
committed
feat(cli): add coding-agent template and publish-hardening
- Add `create --template coding-agent` scaffolding a Claude-Code-style terminal agent UI powered by the OpenAI SDK - Add prepublishOnly + verify:dist script to catch workspace imports leaking into the published tarball - Bump root to 1.5.1 and tighten tsup config for publish - Misc polish across CLI commands, AI components, and docs
1 parent 115fc75 commit c363507

29 files changed

Lines changed: 1142 additions & 273 deletions

README.md

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -278,20 +278,20 @@ import {
278278
279279
TermUI ships a dedicated set of AI/LLM UI components for building chat interfaces and agent UIs:
280280
281-
| Component | Description |
282-
| --------------- | ---------------------------------------------------------------- |
283-
| `ChatMessage` | Renders a single chat turn with role, content, and style |
284-
| `ChatThread` | Scrollable thread of `ChatMessage` components |
285-
| `ToolCall` | Displays a tool/function call with name and arguments |
286-
| `ThinkingBlock` | Collapsible reasoning/thinking block for chain-of-thought output |
287-
| `TokenUsage` | Shows prompt/completion/total token counts with optional cost |
288-
| `ContextMeter` | Progress bar showing context window usage with warn/critical zones |
289-
| `ModelSelector` | Interactive picker for AI model selection, grouped by provider |
290-
| `FileChange` | Diff-style display of file modifications from an agent |
291-
| `ToolApproval` | Confirmation prompt before executing a tool call (risk-gated) |
292-
| `StreamOutput` | Standalone streaming display — animates text or AsyncIterable |
293-
| `ConversationHistory` | Scrollable wrapper for ChatMessage history with ↑↓ navigation |
294-
| `ErrorRetry` | Error state with keyboard retry/dismiss affordance |
281+
| Component | Description |
282+
| --------------------- | ------------------------------------------------------------------ |
283+
| `ChatMessage` | Renders a single chat turn with role, content, and style |
284+
| `ChatThread` | Scrollable thread of `ChatMessage` components |
285+
| `ToolCall` | Displays a tool/function call with name and arguments |
286+
| `ThinkingBlock` | Collapsible reasoning/thinking block for chain-of-thought output |
287+
| `TokenUsage` | Shows prompt/completion/total token counts with optional cost |
288+
| `ContextMeter` | Progress bar showing context window usage with warn/critical zones |
289+
| `ModelSelector` | Interactive picker for AI model selection, grouped by provider |
290+
| `FileChange` | Diff-style display of file modifications from an agent |
291+
| `ToolApproval` | Confirmation prompt before executing a tool call (risk-gated) |
292+
| `StreamOutput` | Standalone streaming display — animates text or AsyncIterable |
293+
| `ConversationHistory` | Scrollable wrapper for ChatMessage history with ↑↓ navigation |
294+
| `ErrorRetry` | Error state with keyboard retry/dismiss affordance |
295295
296296
The `termui/ai` adapter provides streaming React hooks:
297297
@@ -327,21 +327,21 @@ npx termui add mcp
327327
328328
`npx termui add mcp` prompts you to choose one of three installation targets:
329329
330-
| Scope | Config file written |
331-
| --- | --- |
332-
| **Local project** | `.mcp.json` (Claude Code project scope) |
333-
| **Global — Claude Code** | `~/.claude/settings.json` |
330+
| Scope | Config file written |
331+
| --------------------------- | ----------------------------------------------------------------- |
332+
| **Local project** | `.mcp.json` (Claude Code project scope) |
333+
| **Global — Claude Code** | `~/.claude/settings.json` |
334334
| **Global — Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` |
335335
336336
Once installed, your AI assistant gets five tools:
337337
338-
| Tool | Description |
339-
| --- | --- |
340-
| `list_components` | Browse all components grouped by category |
341-
| `add_component` | Install component(s) into the current project |
342-
| `get_component_docs` | Full props + usage for a specific component |
343-
| `search_components` | Keyword search over the registry |
344-
| `get_theme_tokens` | List available themes and their token structure |
338+
| Tool | Description |
339+
| -------------------- | ----------------------------------------------- |
340+
| `list_components` | Browse all components grouped by category |
341+
| `add_component` | Install component(s) into the current project |
342+
| `get_component_docs` | Full props + usage for a specific component |
343+
| `search_components` | Keyword search over the registry |
344+
| `get_theme_tokens` | List available themes and their token structure |
345345
346346
---
347347
@@ -400,13 +400,13 @@ const openai = new OpenAI();
400400
401401
In-repo docs live under [`docs/`](./docs). Start at **[docs/index.md](./docs/index.md)** for the overview and category guides (layout, typography, feedback, charts, and more). API-focused pages:
402402
403-
| Doc | Contents |
404-
| ---------------------------------------------------- | ------------------------------------- |
405-
| [docs/api/cli.md](./docs/api/cli.md) | CLI reference |
406-
| [docs/api/hooks.md](./docs/api/hooks.md) | Hooks API |
407-
| [docs/api/testing.md](./docs/api/testing.md) | Testing utilities |
408-
| [docs/adapters.md](./docs/adapters.md) | Adapters overview |
409-
| [docs/accessibility.md](./docs/accessibility.md) | Keyboard nav, focus traps, ARIA, env vars |
403+
| Doc | Contents |
404+
| ------------------------------------------------ | ----------------------------------------- |
405+
| [docs/api/cli.md](./docs/api/cli.md) | CLI reference |
406+
| [docs/api/hooks.md](./docs/api/hooks.md) | Hooks API |
407+
| [docs/api/testing.md](./docs/api/testing.md) | Testing utilities |
408+
| [docs/adapters.md](./docs/adapters.md) | Adapters overview |
409+
| [docs/accessibility.md](./docs/accessibility.md) | Keyboard nav, focus traps, ARIA, env vars |
410410
411411
---
412412
@@ -515,13 +515,13 @@ pnpm --filter @termui/cli dev
515515
516516
## Roadmap
517517
518-
| Phase | Status | Description |
519-
| ----------- | -------------- | ------------------------------------------------------------------------------------------------------------------------ |
520-
| **Phase 1** | ✅ **Done** | 19 components, CLI (init/add/list), 3 themes, 12 hooks |
521-
| **Phase 2** | ✅ **Done** | 75 components, 9 themes, adapters, diff/update/theme commands |
522-
| **Phase 3** | ✅ **Done** | 101 components, charts, dev tools, templates, testing package |
523-
| **Phase 4** | ✅ Complete | AI components + streaming hooks, chalk/ora/meow/commander/inquirer/Vue/Svelte adapters, voice dictation, Windows CI, NO_UNICODE compat, `termui/testing` subpath, MCP server, accessibility hooks (`useKeyboardNavigation`, `useFocusTrap`), `StreamOutput`, `ConversationHistory`, `ErrorRetry`, `Modal`/`Drawer` focus trapping, 1,300+ tests |
524-
| **Phase 5** | 🔜 Planned | Plugin system, community registry |
518+
| Phase | Status | Description |
519+
| ----------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
520+
| **Phase 1** | ✅ **Done** | 19 components, CLI (init/add/list), 3 themes, 12 hooks |
521+
| **Phase 2** | ✅ **Done** | 75 components, 9 themes, adapters, diff/update/theme commands |
522+
| **Phase 3** | ✅ **Done** | 101 components, charts, dev tools, templates, testing package |
523+
| **Phase 4** | ✅ Complete | AI components + streaming hooks, chalk/ora/meow/commander/inquirer/Vue/Svelte adapters, voice dictation, Windows CI, NO_UNICODE compat, `termui/testing` subpath, MCP server, accessibility hooks (`useKeyboardNavigation`, `useFocusTrap`), `StreamOutput`, `ConversationHistory`, `ErrorRetry`, `Modal`/`Drawer` focus trapping, 1,300+ tests |
524+
| **Phase 5** | 🔜 Planned | Plugin system, community registry |
525525
526526
---
527527

docs/accessibility.md

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ TermUI is built for keyboard-only environments. Every interactive component is n
1010

1111
## Overview
1212

13-
| Concern | Mechanism |
14-
|---------|-----------|
15-
| Focus management | Ink `useFocus` / `useFocusManager` + `useFocusTrap` |
16-
| List navigation | `useKeyboardNavigation` |
13+
| Concern | Mechanism |
14+
| ------------------- | ----------------------------------------------------------- |
15+
| Focus management | Ink `useFocus` / `useFocusManager` + `useFocusTrap` |
16+
| List navigation | `useKeyboardNavigation` |
1717
| Screen reader hints | `AriaProps` (`aria-label`, `aria-description`, `aria-live`) |
18-
| Reduced motion | `useMotion()` / `NO_MOTION=1` env var |
18+
| Reduced motion | `useMotion()` / `NO_MOTION=1` env var |
1919

2020
---
2121

@@ -36,21 +36,21 @@ focus('my-button');
3636

3737
**`useFocus` options:**
3838

39-
| Option | Type | Default | Description |
40-
|--------|------|---------|-------------|
41-
| `id` | `string` || Unique ID used to target this element programmatically |
42-
| `isActive` | `boolean` | `true` | Whether this element can receive focus |
43-
| `autoFocus` | `boolean` | `false` | Focus this element on mount |
39+
| Option | Type | Default | Description |
40+
| ----------- | --------- | ------- | ------------------------------------------------------ |
41+
| `id` | `string` | | Unique ID used to target this element programmatically |
42+
| `isActive` | `boolean` | `true` | Whether this element can receive focus |
43+
| `autoFocus` | `boolean` | `false` | Focus this element on mount |
4444

4545
**`useFocusManager` returns:**
4646

47-
| Method | Description |
48-
|--------|-------------|
49-
| `focus(id)` | Move focus to the element with the given ID |
50-
| `focusNext()` | Move focus to the next registered element |
47+
| Method | Description |
48+
| ----------------- | --------------------------------------------- |
49+
| `focus(id)` | Move focus to the element with the given ID |
50+
| `focusNext()` | Move focus to the next registered element |
5151
| `focusPrevious()` | Move focus to the previous registered element |
52-
| `enableFocus()` | Re-enable the focus system |
53-
| `disableFocus()` | Disable the focus system |
52+
| `enableFocus()` | Re-enable the focus system |
53+
| `disableFocus()` | Disable the focus system |
5454

5555
---
5656

@@ -67,10 +67,10 @@ useFocusTrap(options: {
6767

6868
**Options:**
6969

70-
| Option | Type | Default | Description |
71-
|--------|------|---------|-------------|
70+
| Option | Type | Default | Description |
71+
| -------------- | ---------- | -------- | --------------------------------------------- |
7272
| `focusableIds` | `string[]` | required | Ordered list of focusable IDs within the trap |
73-
| `isActive` | `boolean` | `true` | Whether the trap is currently active |
73+
| `isActive` | `boolean` | `true` | Whether the trap is currently active |
7474

7575
When `isActive` becomes `true`, focus jumps to `focusableIds[0]` automatically. Set `isActive: false` when the container unmounts or hides to release focus.
7676

@@ -89,9 +89,12 @@ function ConfirmModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => voi
8989
const { isFocused: confirmFocused } = useFocus({ id: 'modal-confirm' });
9090
const { isFocused: cancelFocused } = useFocus({ id: 'modal-cancel' });
9191

92-
useInput((_, key) => {
93-
if (key.escape) onClose();
94-
}, { isActive: isOpen });
92+
useInput(
93+
(_, key) => {
94+
if (key.escape) onClose();
95+
},
96+
{ isActive: isOpen }
97+
);
9598

9699
if (!isOpen) return null;
97100

@@ -125,21 +128,21 @@ useKeyboardNavigation(options: {
125128

126129
**Options:**
127130

128-
| Option | Type | Default | Description |
129-
|--------|------|---------|-------------|
130-
| `itemCount` | `number` | required | Total number of items in the list |
131-
| `defaultIndex` | `number` | `0` | Initial active index |
132-
| `loop` | `boolean` | `true` | Wrap around at the ends |
133-
| `pageSize` | `number` | `10` | Items to jump on Page Up / Page Down |
134-
| `onSelect` | `(index: number) => void` || Called on Enter or Space |
135-
| `onDismiss` | `() => void` || Called on Escape |
136-
| `isActive` | `boolean` | `true` | Whether keyboard handling is active |
131+
| Option | Type | Default | Description |
132+
| -------------- | ------------------------- | -------- | ------------------------------------ |
133+
| `itemCount` | `number` | required | Total number of items in the list |
134+
| `defaultIndex` | `number` | `0` | Initial active index |
135+
| `loop` | `boolean` | `true` | Wrap around at the ends |
136+
| `pageSize` | `number` | `10` | Items to jump on Page Up / Page Down |
137+
| `onSelect` | `(index: number) => void` | | Called on Enter or Space |
138+
| `onDismiss` | `() => void` | | Called on Escape |
139+
| `isActive` | `boolean` | `true` | Whether keyboard handling is active |
137140

138141
**Return value:**
139142

140-
| Field | Type | Description |
141-
|-------|------|-------------|
142-
| `activeIndex` | `number` | Currently highlighted index |
143+
| Field | Type | Description |
144+
| ---------------- | ------------------------- | ---------------------------------------------------------- |
145+
| `activeIndex` | `number` | Currently highlighted index |
143146
| `setActiveIndex` | `(index: number) => void` | Programmatically move to an index (clamped to valid range) |
144147

145148
**Example — custom list component:**
@@ -159,7 +162,8 @@ function FileList({ files }: { files: string[] }) {
159162
<Box flexDirection="column">
160163
{files.map((file, i) => (
161164
<Text key={file} color={i === activeIndex ? 'cyan' : undefined}>
162-
{i === activeIndex ? '> ' : ' '}{file}
165+
{i === activeIndex ? '> ' : ' '}
166+
{file}
163167
</Text>
164168
))}
165169
</Box>
@@ -174,11 +178,7 @@ function FileList({ files }: { files: string[] }) {
174178
`Modal` and `Drawer` accept a `focusableIds` prop that activates `useFocusTrap` automatically — no need to call the hook manually when using these components.
175179

176180
```tsx
177-
<Modal
178-
isOpen={open}
179-
onClose={() => setOpen(false)}
180-
focusableIds={['ok-btn', 'cancel-btn']}
181-
>
181+
<Modal isOpen={open} onClose={() => setOpen(false)} focusableIds={['ok-btn', 'cancel-btn']}>
182182
<Button id="ok-btn">OK</Button>
183183
<Button id="cancel-btn">Cancel</Button>
184184
</Modal>
@@ -233,11 +233,11 @@ const { reduced } = useMotion();
233233

234234
**Triggers for `reduced: true`:**
235235

236-
| Trigger | Description |
237-
|---------|-------------|
238-
| `NO_MOTION=1` | Environment variable |
239-
| `CI=true` | CI environment (detected automatically) |
240-
| `<ThemeProvider reducedMotion>` | Prop on the root provider |
236+
| Trigger | Description |
237+
| ------------------------------- | --------------------------------------- |
238+
| `NO_MOTION=1` | Environment variable |
239+
| `CI=true` | CI environment (detected automatically) |
240+
| `<ThemeProvider reducedMotion>` | Prop on the root provider |
241241

242242
**Example:**
243243

@@ -259,17 +259,17 @@ function LoadingIndicator() {
259259

260260
Standard key bindings used consistently across all TermUI components:
261261

262-
| Key | Action |
263-
|-----|--------|
264-
| `Tab` | Move focus to next focusable element |
265-
| `Shift+Tab` | Move focus to previous focusable element |
266-
| `` / `` | Navigate list items |
267-
| `Home` | Jump to first item |
268-
| `End` | Jump to last item |
269-
| `Page Up` | Jump up by `pageSize` items (default 10) |
262+
| Key | Action |
263+
| ----------- | ------------------------------------------ |
264+
| `Tab` | Move focus to next focusable element |
265+
| `Shift+Tab` | Move focus to previous focusable element |
266+
| `` / `` | Navigate list items |
267+
| `Home` | Jump to first item |
268+
| `End` | Jump to last item |
269+
| `Page Up` | Jump up by `pageSize` items (default 10) |
270270
| `Page Down` | Jump down by `pageSize` items (default 10) |
271-
| `Enter` | Select / confirm focused item |
272-
| `Space` | Toggle or select focused item |
273-
| `Esc` | Close overlay / dismiss / cancel |
271+
| `Enter` | Select / confirm focused item |
272+
| `Space` | Toggle or select focused item |
273+
| `Esc` | Close overlay / dismiss / cancel |
274274

275275
All interactive components in TermUI follow these conventions. When building custom components, use `useKeyboardNavigation` and `useFocusTrap` to stay consistent with this contract.

0 commit comments

Comments
 (0)