@particle-academy/fancy-term 0.1.0 → 0.2.0

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/theme.ts","../src/hooks/use-terminal-fit.ts","../src/hooks/use-terminal.ts","../src/output-diff.ts","../src/components/Terminal.tsx","../src/hooks/use-terminal-session.ts"],"names":["useRef","useEffect","XTerm","FitAddon","forwardRef","Terminal","useImperativeHandle","jsx","useState","useCallback"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { TerminalTheme } from \"./types\";\n\n/**\n * The default \"sexy\" Fancy dark terminal theme — a zinc surface, violet cursor,\n * and a balanced ANSI palette drawn from the react-fancy Tailwind v4 tokens.\n * Pass a `theme` prop / option to override.\n */\nexport const fancyDarkTheme: TerminalTheme = {\n background: \"#09090b\", // zinc-950\n foreground: \"#e4e4e7\", // zinc-200\n cursor: \"#8b5cf6\", // violet-500\n cursorAccent: \"#09090b\",\n selectionBackground: \"#3f3f46\", // zinc-700\n black: \"#18181b\",\n red: \"#f43f5e\",\n green: \"#10b981\",\n yellow: \"#f59e0b\",\n blue: \"#3b82f6\",\n magenta: \"#8b5cf6\",\n cyan: \"#22d3ee\",\n white: \"#e4e4e7\",\n brightBlack: \"#52525b\",\n brightRed: \"#fb7185\",\n brightGreen: \"#34d399\",\n brightYellow: \"#fbbf24\",\n brightBlue: \"#60a5fa\",\n brightMagenta: \"#a78bfa\",\n brightCyan: \"#67e8f9\",\n brightWhite: \"#fafafa\",\n};\n","import { useEffect, useRef, type RefObject } from \"react\";\n\n/**\n * Run `fit` whenever the container resizes — and once it first has a real,\n * non-zero box. Guards the hidden-tab / late-mount 0×0 case: fitting a collapsed\n * container yields garbage rows/cols (or throws), so we simply skip until there\n * is layout to measure.\n */\nexport function useTerminalFit(\n ref: RefObject<HTMLElement | null>,\n fit: () => void,\n enabled = true,\n): void {\n const fitRef = useRef(fit);\n fitRef.current = fit;\n\n useEffect(() => {\n if (!enabled) return;\n const el = ref.current;\n if (!el) return;\n\n const run = () => {\n if (el.clientWidth > 0 && el.clientHeight > 0) fitRef.current();\n };\n\n const observer = new ResizeObserver(run);\n observer.observe(el);\n run(); // initial — fires once the box has layout\n\n return () => observer.disconnect();\n }, [ref, enabled]);\n}\n","import { useEffect, useRef, type RefObject } from \"react\";\nimport { Terminal as XTerm } from \"@xterm/xterm\";\nimport { FitAddon } from \"@xterm/addon-fit\";\nimport { fancyDarkTheme } from \"../theme\";\nimport { useTerminalFit } from \"./use-terminal-fit\";\nimport type { TerminalHandle, TerminalOptions } from \"../types\";\n\nconst DEFAULT_FONT =\n 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace';\n\n/** Read the whole xterm buffer as plain text — what an agent \"sees\". */\nfunction readBuffer(term: XTerm | null): string {\n if (!term) return \"\";\n const buf = term.buffer.active;\n const lines: string[] = [];\n for (let i = 0; i < buf.length; i++) {\n const line = buf.getLine(i);\n if (line) lines.push(line.translateToString(true));\n }\n return lines.join(\"\\n\").replace(/\\n+$/, \"\");\n}\n\n/**\n * Create + own an xterm.js instance for a container, returning a stable\n * {@link TerminalHandle}. The headless engine layer behind `<Terminal>` — use it\n * directly when you want the terminal without the component shell (custom chrome,\n * an agent bridge host, tests). Options are read live, so changing them re-applies\n * without tearing down the terminal.\n */\nexport function useTerminal(\n containerRef: RefObject<HTMLElement | null>,\n options: TerminalOptions = {},\n): TerminalHandle {\n const xtermRef = useRef<XTerm | null>(null);\n const fitRef = useRef<FitAddon | null>(null);\n const optsRef = useRef(options);\n optsRef.current = options;\n\n // Stable handle — its methods always operate on the live xterm via the ref.\n const handleRef = useRef<TerminalHandle | null>(null);\n if (handleRef.current === null) {\n handleRef.current = {\n get xterm() {\n return xtermRef.current;\n },\n write: (d) => xtermRef.current?.write(d),\n writeln: (d) => xtermRef.current?.writeln(d),\n clear: () => xtermRef.current?.clear(),\n reset: () => xtermRef.current?.reset(),\n fit: () => {\n try {\n fitRef.current?.fit();\n } catch {\n /* container not measurable yet — ignore */\n }\n },\n focus: () => xtermRef.current?.focus(),\n getBuffer: () => readBuffer(xtermRef.current),\n getSelection: () => xtermRef.current?.getSelection() ?? \"\",\n };\n }\n const handle = handleRef.current;\n\n // Create the engine once for the container.\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const o = optsRef.current;\n\n const term = new XTerm({\n theme: o.theme ?? fancyDarkTheme,\n rows: o.rows,\n cols: o.cols,\n disableStdin: o.readOnly ?? false,\n cursorBlink: o.cursorBlink ?? true,\n cursorStyle: o.cursorStyle ?? \"block\",\n fontFamily: o.fontFamily ?? DEFAULT_FONT,\n fontSize: o.fontSize ?? 13,\n scrollback: o.scrollback ?? 1000,\n allowProposedApi: true,\n });\n const fitAddon = new FitAddon();\n term.loadAddon(fitAddon);\n term.open(el);\n xtermRef.current = term;\n fitRef.current = fitAddon;\n\n const dataSub = term.onData((d) => optsRef.current.onData?.(d));\n const resizeSub = term.onResize(({ cols, rows }) =>\n optsRef.current.onResize?.({ cols, rows }),\n );\n\n if (o.initialOutput) term.write(o.initialOutput);\n if ((o.fit ?? true) && el.clientWidth > 0 && el.clientHeight > 0) {\n try {\n fitAddon.fit();\n } catch {\n /* ignore */\n }\n }\n\n return () => {\n dataSub.dispose();\n resizeSub.dispose();\n term.dispose();\n xtermRef.current = null;\n fitRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [containerRef]);\n\n // Re-apply reactive options without tearing the terminal down.\n useEffect(() => {\n const t = xtermRef.current;\n if (!t) return;\n if (options.theme) t.options.theme = options.theme;\n if (options.readOnly !== undefined) t.options.disableStdin = options.readOnly;\n if (options.cursorBlink !== undefined) t.options.cursorBlink = options.cursorBlink;\n if (options.cursorStyle) t.options.cursorStyle = options.cursorStyle;\n if (options.fontSize) t.options.fontSize = options.fontSize;\n if (options.fontFamily) t.options.fontFamily = options.fontFamily;\n }, [options.theme, options.readOnly, options.cursorBlink, options.cursorStyle, options.fontSize, options.fontFamily]);\n\n // Auto-fit on container resize (0×0-guarded).\n useTerminalFit(containerRef, () => handle.fit(), options.fit ?? true);\n\n return handle;\n}\n","/**\n * Decide what to push to the terminal when the controlled `output` changes.\n * Terminals are append-only streams, so the common case (output grew) writes\n * only the delta; a value that no longer extends what we've written means a\n * wholesale replace, so reset + rewrite. Returns null when nothing changed.\n */\nexport function diffOutput(\n written: string,\n next: string,\n): { reset: boolean; write: string } | null {\n if (next === written) return null;\n if (next.startsWith(written)) return { reset: false, write: next.slice(written.length) };\n return { reset: true, write: next };\n}\n","import { forwardRef, useEffect, useImperativeHandle, useRef } from \"react\";\nimport { useTerminal } from \"../hooks/use-terminal\";\nimport { diffOutput } from \"../output-diff\";\nimport type { TerminalHandle, TerminalProps } from \"../types\";\n\n/**\n * Human+ `<Terminal>` — a controlled, themeable xterm.js terminal.\n *\n * - **Authoring surface:** terse, controlled (`output` + `onData`), JSON-friendly\n * props (rows/cols, theme tokens, initial buffer), a stable `data-fancy-terminal`\n * handle, and a ref exposing {@link TerminalHandle} (write/clear/fit/getBuffer/…).\n * - **Inhabited surface:** the same handle is what an MCP bridge drives, so an\n * embedded agent reads the buffer + writes input without DOM-scraping.\n *\n * The parent must have a height — the terminal fits its container (like any xterm\n * surface); a 0-height parent collapses it.\n *\n * ```tsx\n * const [out, setOut] = useState(\"\");\n * <div style={{ height: 360 }}>\n * <Terminal output={out} onData={(d) => backend.send(d)} />\n * </div>\n * ```\n */\nexport const Terminal = forwardRef<TerminalHandle, TerminalProps>(function Terminal(\n {\n output,\n theme,\n rows,\n cols,\n fit = true,\n readOnly,\n cursorBlink,\n cursorStyle,\n fontFamily,\n fontSize,\n scrollback,\n initialOutput,\n onData,\n onResize,\n className,\n style,\n ...rest\n },\n ref,\n) {\n const containerRef = useRef<HTMLDivElement>(null);\n\n const handle = useTerminal(containerRef, {\n theme,\n rows,\n cols,\n fit,\n readOnly,\n cursorBlink,\n cursorStyle,\n fontFamily,\n fontSize,\n scrollback,\n // `initialOutput` is for uncontrolled use; with a controlled `output` the\n // diffing effect below owns the buffer, so don't double-write.\n initialOutput: output === undefined ? initialOutput : undefined,\n onData,\n onResize,\n });\n\n useImperativeHandle(ref, () => handle, [handle]);\n\n // Controlled output: write only the appended delta; reset + rewrite if the\n // value diverges from what we've already written (a wholesale replace).\n const written = useRef(\"\");\n useEffect(() => {\n if (output === undefined) return;\n const change = diffOutput(written.current, output);\n if (!change) return;\n if (change.reset) handle.reset();\n handle.write(change.write);\n written.current = output;\n }, [output, handle]);\n\n return (\n <div\n ref={containerRef}\n data-fancy-terminal=\"\"\n data-readonly={readOnly ? \"\" : undefined}\n className={className}\n style={{ width: \"100%\", height: \"100%\", ...style }}\n {...rest}\n />\n );\n});\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\n/**\n * A streamed terminal backend — a PTY, an SSH session, or a command runner.\n * `subscribe` pushes output chunks (wire it to an Echo channel or a fancy-query\n * `useFancyStream` feed); `send` forwards the user's keystrokes upstream.\n */\nexport interface TerminalSessionTransport {\n /** Forward user input / keystrokes to the backend. */\n send: (data: string) => void | Promise<void>;\n /** Subscribe to backend output chunks; return an unsubscribe function. */\n subscribe: (onChunk: (chunk: string) => void) => (() => void) | void;\n}\n\nexport interface UseTerminalSessionOptions {\n transport: TerminalSessionTransport;\n /** Replayed history written before live output starts. */\n initial?: string;\n}\n\nexport interface TerminalSessionApi {\n /** Accumulated output — feed to `<Terminal output={output} />`. */\n output: string;\n /** Forward keystrokes upstream — wire to `<Terminal onData={sendData} />`. */\n sendData: (data: string) => void;\n /** Append a chunk locally (e.g. a client-side echo or banner). */\n append: (chunk: string) => void;\n /** Clear the local buffer. */\n clear: () => void;\n}\n\n/**\n * Bind a `<Terminal>` to a streamed backend: accumulates incoming output into a\n * controlled buffer and forwards keystrokes upstream — the glue between the\n * display surface and a PTY/command channel.\n *\n * ```tsx\n * const session = useTerminalSession({\n * transport: {\n * send: (d) => echo.private(`pty.${id}`).whisper(\"stdin\", { d }),\n * subscribe: (onChunk) => {\n * const ch = echo.private(`pty.${id}`).listen(\".stdout\", (e) => onChunk(e.chunk));\n * return () => echo.leave(`pty.${id}`);\n * },\n * },\n * });\n * <Terminal output={session.output} onData={session.sendData} />\n * ```\n */\nexport function useTerminalSession(options: UseTerminalSessionOptions): TerminalSessionApi {\n const [output, setOutput] = useState(options.initial ?? \"\");\n\n const transportRef = useRef(options.transport);\n transportRef.current = options.transport;\n\n useEffect(() => {\n const unsub = transportRef.current.subscribe((chunk) => setOutput((o) => o + chunk));\n return () => {\n if (typeof unsub === \"function\") unsub();\n };\n // subscribe once; the transport is read live via the ref.\n }, []);\n\n const sendData = useCallback((data: string) => {\n void transportRef.current.send(data);\n }, []);\n\n const append = useCallback((chunk: string) => setOutput((o) => o + chunk), []);\n const clear = useCallback(() => setOutput(\"\"), []);\n\n return { output, sendData, append, clear };\n}\n"]}
1
+ {"version":3,"sources":["../src/theme.ts","../src/hooks/use-terminal-fit.ts","../src/hooks/use-terminal.ts","../src/types.ts","../src/components/ShellSwitcher.tsx","../src/output-diff.ts","../src/shell-select.ts","../src/components/Terminal.tsx","../src/hooks/use-terminal-session.ts"],"names":["useRef","useEffect","XTerm","FitAddon","useState","useId","useCallback","jsxs","jsx","forwardRef","Terminal","useImperativeHandle"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { TerminalTheme } from \"./types\";\n\n/**\n * The default \"sexy\" Fancy dark terminal theme — a zinc surface, violet cursor,\n * and a balanced ANSI palette drawn from the react-fancy Tailwind v4 tokens.\n * Pass a `theme` prop / option to override.\n */\nexport const fancyDarkTheme: TerminalTheme = {\n background: \"#09090b\", // zinc-950\n foreground: \"#e4e4e7\", // zinc-200\n cursor: \"#8b5cf6\", // violet-500\n cursorAccent: \"#09090b\",\n selectionBackground: \"#3f3f46\", // zinc-700\n black: \"#18181b\",\n red: \"#f43f5e\",\n green: \"#10b981\",\n yellow: \"#f59e0b\",\n blue: \"#3b82f6\",\n magenta: \"#8b5cf6\",\n cyan: \"#22d3ee\",\n white: \"#e4e4e7\",\n brightBlack: \"#52525b\",\n brightRed: \"#fb7185\",\n brightGreen: \"#34d399\",\n brightYellow: \"#fbbf24\",\n brightBlue: \"#60a5fa\",\n brightMagenta: \"#a78bfa\",\n brightCyan: \"#67e8f9\",\n brightWhite: \"#fafafa\",\n};\n","import { useEffect, useRef, type RefObject } from \"react\";\n\n/**\n * Run `fit` whenever the container resizes — and once it first has a real,\n * non-zero box. Guards the hidden-tab / late-mount 0×0 case: fitting a collapsed\n * container yields garbage rows/cols (or throws), so we simply skip until there\n * is layout to measure.\n */\nexport function useTerminalFit(\n ref: RefObject<HTMLElement | null>,\n fit: () => void,\n enabled = true,\n): void {\n const fitRef = useRef(fit);\n fitRef.current = fit;\n\n useEffect(() => {\n if (!enabled) return;\n const el = ref.current;\n if (!el) return;\n\n const run = () => {\n if (el.clientWidth > 0 && el.clientHeight > 0) fitRef.current();\n };\n\n const observer = new ResizeObserver(run);\n observer.observe(el);\n run(); // initial — fires once the box has layout\n\n return () => observer.disconnect();\n }, [ref, enabled]);\n}\n","import { useEffect, useRef, type RefObject } from \"react\";\nimport { Terminal as XTerm } from \"@xterm/xterm\";\nimport { FitAddon } from \"@xterm/addon-fit\";\nimport { fancyDarkTheme } from \"../theme\";\nimport { useTerminalFit } from \"./use-terminal-fit\";\nimport type { TerminalHandle, TerminalOptions } from \"../types\";\n\nconst DEFAULT_FONT =\n 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace';\n\n/** Read the whole xterm buffer as plain text — what an agent \"sees\". */\nfunction readBuffer(term: XTerm | null): string {\n if (!term) return \"\";\n const buf = term.buffer.active;\n const lines: string[] = [];\n for (let i = 0; i < buf.length; i++) {\n const line = buf.getLine(i);\n if (line) lines.push(line.translateToString(true));\n }\n return lines.join(\"\\n\").replace(/\\n+$/, \"\");\n}\n\n/**\n * Create + own an xterm.js instance for a container, returning a stable\n * {@link TerminalHandle}. The headless engine layer behind `<Terminal>` — use it\n * directly when you want the terminal without the component shell (custom chrome,\n * an agent bridge host, tests). Options are read live, so changing them re-applies\n * without tearing down the terminal.\n */\nexport function useTerminal(\n containerRef: RefObject<HTMLElement | null>,\n options: TerminalOptions = {},\n): TerminalHandle {\n const xtermRef = useRef<XTerm | null>(null);\n const fitRef = useRef<FitAddon | null>(null);\n const optsRef = useRef(options);\n optsRef.current = options;\n\n // Stable handle — its methods always operate on the live xterm via the ref.\n const handleRef = useRef<TerminalHandle | null>(null);\n if (handleRef.current === null) {\n handleRef.current = {\n get xterm() {\n return xtermRef.current;\n },\n write: (d) => xtermRef.current?.write(d),\n writeln: (d) => xtermRef.current?.writeln(d),\n clear: () => xtermRef.current?.clear(),\n reset: () => xtermRef.current?.reset(),\n fit: () => {\n try {\n fitRef.current?.fit();\n } catch {\n /* container not measurable yet — ignore */\n }\n },\n focus: () => xtermRef.current?.focus(),\n getBuffer: () => readBuffer(xtermRef.current),\n getSelection: () => xtermRef.current?.getSelection() ?? \"\",\n // Shell selection is owned by the <Terminal> component layer (it needs the\n // `shells` list + onShellChange). The headless engine is shell-agnostic, so\n // these are no-ops here and get overridden when <Terminal> composes them.\n setShell: () => {},\n getShell: () => undefined,\n };\n }\n const handle = handleRef.current;\n\n // Create the engine once for the container.\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const o = optsRef.current;\n\n const term = new XTerm({\n theme: o.theme ?? fancyDarkTheme,\n rows: o.rows,\n cols: o.cols,\n disableStdin: o.readOnly ?? false,\n cursorBlink: o.cursorBlink ?? true,\n cursorStyle: o.cursorStyle ?? \"block\",\n fontFamily: o.fontFamily ?? DEFAULT_FONT,\n fontSize: o.fontSize ?? 13,\n scrollback: o.scrollback ?? 1000,\n allowProposedApi: true,\n });\n const fitAddon = new FitAddon();\n term.loadAddon(fitAddon);\n term.open(el);\n xtermRef.current = term;\n fitRef.current = fitAddon;\n\n const dataSub = term.onData((d) => optsRef.current.onData?.(d));\n const resizeSub = term.onResize(({ cols, rows }) =>\n optsRef.current.onResize?.({ cols, rows }),\n );\n\n if (o.initialOutput) term.write(o.initialOutput);\n if ((o.fit ?? true) && el.clientWidth > 0 && el.clientHeight > 0) {\n try {\n fitAddon.fit();\n } catch {\n /* ignore */\n }\n }\n\n return () => {\n dataSub.dispose();\n resizeSub.dispose();\n term.dispose();\n xtermRef.current = null;\n fitRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [containerRef]);\n\n // Re-apply reactive options without tearing the terminal down.\n useEffect(() => {\n const t = xtermRef.current;\n if (!t) return;\n if (options.theme) t.options.theme = options.theme;\n if (options.readOnly !== undefined) t.options.disableStdin = options.readOnly;\n if (options.cursorBlink !== undefined) t.options.cursorBlink = options.cursorBlink;\n if (options.cursorStyle) t.options.cursorStyle = options.cursorStyle;\n if (options.fontSize) t.options.fontSize = options.fontSize;\n if (options.fontFamily) t.options.fontFamily = options.fontFamily;\n }, [options.theme, options.readOnly, options.cursorBlink, options.cursorStyle, options.fontSize, options.fontFamily]);\n\n // Auto-fit on container resize (0×0-guarded).\n useTerminalFit(containerRef, () => handle.fit(), options.fit ?? true);\n\n return handle;\n}\n","import type { HTMLAttributes } from \"react\";\nimport type { ITheme, Terminal as XTerm } from \"@xterm/xterm\";\n\n/** xterm.js color theme (re-exported for ergonomic typing). */\nexport type TerminalTheme = ITheme;\n\nexport type CursorStyle = \"block\" | \"underline\" | \"bar\";\n\n/**\n * A selectable shell / profile. Fully JSON-friendly (primitives only) so an agent\n * can emit it. fancy-term owns the *selected-shell state + UI + events*; the\n * `command` / `args` / `cwd` fields are **host hints** — fancy-term never spawns a\n * shell, the host reconnects its PTY/command backend to the chosen profile.\n */\nexport interface ShellProfile {\n /** Stable key, e.g. `\"powershell\"`. */\n id: string;\n /** Display label, e.g. `\"PowerShell\"`. */\n label: string;\n /** Optional short glyph / emoji / single char (kept JSON-friendly — no ReactNode). */\n icon?: string;\n /** Host hint — the executable, e.g. `\"pwsh\"` / `\"cmd.exe\"`. */\n command?: string;\n /** Host hint — launch args. */\n args?: string[];\n /** Host hint — working directory. */\n cwd?: string;\n}\n\n/**\n * Imperative handle exposed via the `<Terminal>` ref and returned by\n * {@link useTerminal}. The same surface an MCP bridge drives so an embedded\n * agent reads the buffer + writes input without DOM-scraping.\n */\nexport interface TerminalHandle {\n /** The underlying xterm.js instance — escape hatch for addons / advanced use. Null before mount. */\n readonly xterm: XTerm | null;\n /** Write raw data (ANSI escape sequences honored) to the terminal. */\n write: (data: string) => void;\n /** Write data followed by CRLF. */\n writeln: (data: string) => void;\n /** Clear the viewport (keeps scrollback). */\n clear: () => void;\n /** Full reset — clears scrollback + terminal state. */\n reset: () => void;\n /** Re-fit to the container (no-op when the box is 0-size or fit is off). */\n fit: () => void;\n /** Focus the terminal input. */\n focus: () => void;\n /** The full buffer as plain text — what an agent \"sees\". */\n getBuffer: () => string;\n /** The current text selection, or \"\" when nothing is selected. */\n getSelection: () => string;\n /**\n * Switch the active shell by id. Resolves the matching {@link ShellProfile}\n * from `shells` and fires `onShellChange`; in uncontrolled mode it also updates\n * the internal selection. No-op when the id isn't in `shells`.\n */\n setShell: (id: string) => void;\n /** The active shell id, or `undefined` when no shell is selected. */\n getShell: () => string | undefined;\n}\n\n/** Engine options shared by `<Terminal>` and {@link useTerminal}. */\nexport interface TerminalOptions {\n /** xterm color theme. Omit for the built-in Fancy dark theme. */\n theme?: TerminalTheme;\n /** Fixed grid size. Omit + leave `fit` on to size from the container. */\n rows?: number;\n cols?: number;\n /** Auto-fit to the container via the fit addon + ResizeObserver. Default true. */\n fit?: boolean;\n /** Block stdin (display-only terminal). Default false. */\n readOnly?: boolean;\n /** Blink the cursor. Default true. */\n cursorBlink?: boolean;\n /** Cursor shape. Default \"block\". */\n cursorStyle?: CursorStyle;\n /** Monospace font stack. */\n fontFamily?: string;\n /** Font size in px. Default 13. */\n fontSize?: number;\n /** Scrollback lines. Default 1000. */\n scrollback?: number;\n /** Data written once on mount, before any controlled `output`. */\n initialOutput?: string;\n /** User keystrokes / paste data (xterm `onData`). Wire to your PTY/command backend. */\n onData?: (data: string) => void;\n /** Notified on terminal resize. */\n onResize?: (size: { cols: number; rows: number }) => void;\n}\n\nexport interface TerminalProps extends TerminalOptions, Omit<HTMLAttributes<HTMLDivElement>, \"onInput\" | \"onResize\" | \"children\"> {\n /**\n * The controlled output buffer. The component diffs against the previous value\n * and writes only the appended delta, so a host can drive the terminal from\n * React state — e.g. streaming command output via fancy-query's\n * `useFancyStream`. Replacing it with a non-extending string resets + rewrites.\n */\n output?: string;\n\n /**\n * The shells / profiles a host offers. fancy-term renders the selector and\n * tracks the choice; the host reacts (reconnects its PTY to the chosen shell).\n * Spread {@link BUILTIN_SHELLS} for sensible presets, but the host owns the list.\n */\n shells?: ShellProfile[];\n /** Controlled active-shell id. Omit for uncontrolled (internal) selection. */\n activeShell?: string;\n /** Fired when the user (or `setShell`) switches shells. */\n onShellChange?: (id: string, profile: ShellProfile) => void;\n /**\n * Render the built-in `<ShellSwitcher>` toolbar above the xterm surface when\n * `shells` is provided. Default `false` — opt-in so existing layout is unchanged.\n */\n showShellBar?: boolean;\n}\n\n/**\n * Resolve a shell id to its {@link ShellProfile} within a list. Pure helper —\n * the single place `setShell` / `<ShellSwitcher>` / the session hook agree on\n * what \"switch to id X\" means.\n */\nexport function resolveShell(\n shells: ShellProfile[] | undefined,\n id: string | undefined,\n): ShellProfile | undefined {\n if (!shells || id === undefined) return undefined;\n return shells.find((s) => s.id === id);\n}\n\n/**\n * A small set of sensible built-in shell presets a host can spread / filter.\n * `command` / `args` are host hints only — fancy-term never spawns them.\n */\nexport const BUILTIN_SHELLS: readonly ShellProfile[] = [\n { id: \"cmd\", label: \"Command Prompt\", icon: \">_\", command: \"cmd.exe\" },\n { id: \"powershell\", label: \"Windows PowerShell\", icon: \"PS\", command: \"powershell.exe\" },\n { id: \"pwsh\", label: \"PowerShell\", icon: \"PS\", command: \"pwsh\" },\n {\n id: \"git-bash\",\n label: \"Git Bash\",\n icon: \"\",\n command: \"C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe\",\n args: [\"--login\", \"-i\"],\n },\n { id: \"bash\", label: \"Bash\", icon: \"$\", command: \"bash\" },\n { id: \"zsh\", label: \"Zsh\", icon: \"%\", command: \"zsh\" },\n] as const;\n","import {\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n} from \"react\";\nimport { resolveShell, type ShellProfile } from \"../types\";\n\nexport interface ShellSwitcherProps {\n /** The available shells / profiles. */\n shells: ShellProfile[];\n /** Controlled selected shell id. */\n value?: string;\n /** Fired when a shell is picked. */\n onChange: (id: string, profile: ShellProfile) => void;\n className?: string;\n style?: CSSProperties;\n /** Disable the control. */\n disabled?: boolean;\n}\n\nconst PANEL_BG = \"#18181b\"; // zinc-900\nconst BORDER = \"#3f3f46\"; // zinc-700\nconst FG = \"#e4e4e7\"; // zinc-200\nconst MUTED = \"#a1a1aa\"; // zinc-400\nconst ACCENT = \"#8b5cf6\"; // violet-500\nconst HOVER_BG = \"#27272a\"; // zinc-800\n\n/**\n * Compact, accessible shell picker styled to the Fancy dark theme. Self-contained\n * (zero third-party deps) — a button that toggles a listbox of `shells`.\n *\n * Stable handles for agents: the root carries `data-fancy-shell-switcher` and each\n * option carries `data-shell-id`, so a bridge can pick a shell without DOM-guessing.\n *\n * Controlled: pass `value` + `onChange`. Keyboard accessible (Arrow / Enter / Esc).\n */\nexport function ShellSwitcher({\n shells,\n value,\n onChange,\n className,\n style,\n disabled,\n}: ShellSwitcherProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(0);\n const rootRef = useRef<HTMLDivElement>(null);\n const listId = useId();\n\n const selected = resolveShell(shells, value);\n const selectedIndex = selected ? shells.indexOf(selected) : -1;\n\n // Close on outside click.\n useEffect(() => {\n if (!open) return;\n const onDocClick = (e: MouseEvent) => {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n };\n document.addEventListener(\"mousedown\", onDocClick);\n return () => document.removeEventListener(\"mousedown\", onDocClick);\n }, [open]);\n\n const openMenu = useCallback(() => {\n if (disabled) return;\n setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);\n setOpen(true);\n }, [disabled, selectedIndex]);\n\n const pick = useCallback(\n (i: number) => {\n const profile = shells[i];\n if (!profile) return;\n onChange(profile.id, profile);\n setOpen(false);\n },\n [shells, onChange],\n );\n\n const onButtonKeyDown = (e: KeyboardEvent) => {\n if (disabled) return;\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\" || e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n openMenu();\n }\n };\n\n const onListKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n setOpen(false);\n return;\n }\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActiveIndex((i) => Math.min(shells.length - 1, i + 1));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActiveIndex((i) => Math.max(0, i - 1));\n } else if (e.key === \"Home\") {\n e.preventDefault();\n setActiveIndex(0);\n } else if (e.key === \"End\") {\n e.preventDefault();\n setActiveIndex(shells.length - 1);\n } else if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n pick(activeIndex);\n }\n };\n\n const buttonStyle: CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n height: 26,\n padding: \"0 8px\",\n fontSize: 12,\n lineHeight: 1,\n fontFamily: \"inherit\",\n color: FG,\n background: PANEL_BG,\n border: `1px solid ${BORDER}`,\n borderRadius: 6,\n cursor: disabled ? \"default\" : \"pointer\",\n opacity: disabled ? 0.6 : 1,\n userSelect: \"none\",\n };\n\n return (\n <div\n ref={rootRef}\n data-fancy-shell-switcher=\"\"\n data-active-shell={value ?? undefined}\n className={className}\n style={{ position: \"relative\", display: \"inline-block\", ...style }}\n >\n <button\n type=\"button\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-label=\"Select shell\"\n disabled={disabled}\n style={buttonStyle}\n onClick={() => (open ? setOpen(false) : openMenu())}\n onKeyDown={onButtonKeyDown}\n >\n {selected?.icon ? (\n <span aria-hidden=\"true\" style={{ color: ACCENT }}>\n {selected.icon}\n </span>\n ) : null}\n <span>{selected?.label ?? \"Select shell\"}</span>\n <span aria-hidden=\"true\" style={{ color: MUTED, fontSize: 9 }}>\n ▼\n </span>\n </button>\n\n {open ? (\n <ul\n role=\"listbox\"\n id={listId}\n aria-label=\"Shells\"\n tabIndex={-1}\n // eslint-disable-next-line jsx-a11y/no-autofocus\n autoFocus\n onKeyDown={onListKeyDown}\n ref={(el) => el?.focus()}\n style={{\n position: \"absolute\",\n zIndex: 50,\n top: \"calc(100% + 4px)\",\n left: 0,\n minWidth: \"100%\",\n margin: 0,\n padding: 4,\n listStyle: \"none\",\n background: PANEL_BG,\n border: `1px solid ${BORDER}`,\n borderRadius: 8,\n boxShadow: \"0 8px 24px rgba(0,0,0,0.45)\",\n outline: \"none\",\n }}\n >\n {shells.map((s, i) => {\n const isSelected = s.id === value;\n const isActive = i === activeIndex;\n return (\n <li\n key={s.id}\n role=\"option\"\n aria-selected={isSelected}\n data-shell-id={s.id}\n onMouseEnter={() => setActiveIndex(i)}\n onClick={() => pick(i)}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n padding: \"6px 8px\",\n fontSize: 12,\n borderRadius: 5,\n color: FG,\n cursor: \"pointer\",\n background: isActive ? HOVER_BG : \"transparent\",\n }}\n >\n <span\n aria-hidden=\"true\"\n style={{\n width: 18,\n textAlign: \"center\",\n color: isSelected ? ACCENT : MUTED,\n }}\n >\n {s.icon ?? \"\"}\n </span>\n <span style={{ flex: 1 }}>{s.label}</span>\n {isSelected ? (\n <span aria-hidden=\"true\" style={{ color: ACCENT, fontSize: 11 }}>\n ✓\n </span>\n ) : null}\n </li>\n );\n })}\n </ul>\n ) : null}\n </div>\n );\n}\n","/**\n * Decide what to push to the terminal when the controlled `output` changes.\n * Terminals are append-only streams, so the common case (output grew) writes\n * only the delta; a value that no longer extends what we've written means a\n * wholesale replace, so reset + rewrite. Returns null when nothing changed.\n */\nexport function diffOutput(\n written: string,\n next: string,\n): { reset: boolean; write: string } | null {\n if (next === written) return null;\n if (next.startsWith(written)) return { reset: false, write: next.slice(written.length) };\n return { reset: true, write: next };\n}\n","import { resolveShell, type ShellProfile } from \"./types\";\n\n/**\n * Pure decision for \"select shell `id`\": resolves the profile and reports whether\n * the selection actually changed. The single source of truth shared by\n * `<Terminal>`'s `setShell`, `<ShellSwitcher>`, and `useTerminalSession`, so the\n * interactive behavior is unit-testable without a DOM.\n *\n * Returns `null` when the id isn't in `shells` (a no-op — unknown shell).\n */\nexport function decideShellSelect(\n shells: ShellProfile[] | undefined,\n current: string | undefined,\n id: string,\n): { profile: ShellProfile; changed: boolean } | null {\n const profile = resolveShell(shells, id);\n if (!profile) return null;\n return { profile, changed: current !== id };\n}\n\n/**\n * Pure decision for a session shell switch. The session hook holds the buffer +\n * the transport, so it only needs to know: did the shell change? If so it must\n * reset the buffer (to `initial`) and reconnect the transport. Extracted so the\n * reset-and-reconnect contract is unit-testable without rendering the hook.\n */\nexport function decideShellSwitch(\n current: string | undefined,\n next: string | undefined,\n): { changed: boolean } {\n return { changed: current !== next };\n}\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\nimport { useTerminal } from \"../hooks/use-terminal\";\nimport { ShellSwitcher } from \"./ShellSwitcher\";\nimport { diffOutput } from \"../output-diff\";\nimport { decideShellSelect } from \"../shell-select\";\nimport type { TerminalHandle, TerminalProps } from \"../types\";\n\n/**\n * Human+ `<Terminal>` — a controlled, themeable xterm.js terminal.\n *\n * - **Authoring surface:** terse, controlled (`output` + `onData`), JSON-friendly\n * props (rows/cols, theme tokens, initial buffer), a stable `data-fancy-terminal`\n * handle, and a ref exposing {@link TerminalHandle} (write/clear/fit/getBuffer/…).\n * - **Inhabited surface:** the same handle is what an MCP bridge drives, so an\n * embedded agent reads the buffer + writes input without DOM-scraping.\n *\n * The parent must have a height — the terminal fits its container (like any xterm\n * surface); a 0-height parent collapses it.\n *\n * ```tsx\n * const [out, setOut] = useState(\"\");\n * <div style={{ height: 360 }}>\n * <Terminal output={out} onData={(d) => backend.send(d)} />\n * </div>\n * ```\n */\nexport const Terminal = forwardRef<TerminalHandle, TerminalProps>(function Terminal(\n {\n output,\n theme,\n rows,\n cols,\n fit = true,\n readOnly,\n cursorBlink,\n cursorStyle,\n fontFamily,\n fontSize,\n scrollback,\n initialOutput,\n onData,\n onResize,\n shells,\n activeShell,\n onShellChange,\n showShellBar = false,\n className,\n style,\n ...rest\n },\n ref,\n) {\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Selected shell: controlled by `activeShell` when provided, else internal.\n const isShellControlled = activeShell !== undefined;\n const [internalShell, setInternalShell] = useState<string | undefined>(undefined);\n const shellId = isShellControlled ? activeShell : internalShell;\n\n // Read shell state live so the stable handle's setShell/getShell never go stale.\n const shellStateRef = useRef({ shells, shellId, isShellControlled, onShellChange });\n shellStateRef.current = { shells, shellId, isShellControlled, onShellChange };\n\n const selectShell = useCallback((id: string) => {\n const s = shellStateRef.current;\n const decision = decideShellSelect(s.shells, s.shellId, id);\n if (!decision) return; // unknown id — no-op\n if (!s.isShellControlled) setInternalShell(id);\n s.onShellChange?.(id, decision.profile);\n }, []);\n\n const handle = useTerminal(containerRef, {\n theme,\n rows,\n cols,\n fit,\n readOnly,\n cursorBlink,\n cursorStyle,\n fontFamily,\n fontSize,\n scrollback,\n // `initialOutput` is for uncontrolled use; with a controlled `output` the\n // diffing effect below owns the buffer, so don't double-write.\n initialOutput: output === undefined ? initialOutput : undefined,\n onData,\n onResize,\n });\n\n useImperativeHandle(\n ref,\n (): TerminalHandle => ({\n ...handle,\n setShell: selectShell,\n getShell: () => shellStateRef.current.shellId,\n }),\n [handle, selectShell],\n );\n\n // Controlled output: write only the appended delta; reset + rewrite if the\n // value diverges from what we've already written (a wholesale replace).\n const written = useRef(\"\");\n useEffect(() => {\n if (output === undefined) return;\n const change = diffOutput(written.current, output);\n if (!change) return;\n if (change.reset) handle.reset();\n handle.write(change.write);\n written.current = output;\n }, [output, handle]);\n\n const surface = (\n <div\n ref={containerRef}\n data-fancy-terminal=\"\"\n data-readonly={readOnly ? \"\" : undefined}\n data-fancy-terminal-shell={shellId ?? undefined}\n // When the shell bar is shown the wrapper owns the box; the surface flexes.\n style={\n showShellBar && shells\n ? { width: \"100%\", flex: 1, minHeight: 0 }\n : { width: \"100%\", height: \"100%\", ...style }\n }\n {...(showShellBar && shells ? {} : rest)}\n />\n );\n\n if (!showShellBar || !shells) return surface;\n\n return (\n <div\n data-fancy-terminal-shell={shellId ?? undefined}\n className={className}\n style={{ display: \"flex\", flexDirection: \"column\", width: \"100%\", height: \"100%\", ...style }}\n {...rest}\n >\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n padding: \"4px 6px\",\n background: \"#09090b\",\n borderBottom: \"1px solid #27272a\",\n }}\n >\n <ShellSwitcher\n shells={shells}\n value={shellId}\n onChange={(id) => selectShell(id)}\n disabled={readOnly}\n />\n </div>\n {surface}\n </div>\n );\n});\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { decideShellSwitch } from \"../shell-select\";\n\n/**\n * A streamed terminal backend — a PTY, an SSH session, or a command runner.\n * `subscribe` pushes output chunks (wire it to an Echo channel or a fancy-query\n * `useFancyStream` feed); `send` forwards the user's keystrokes upstream.\n */\nexport interface TerminalSessionTransport {\n /** Forward user input / keystrokes to the backend. */\n send: (data: string) => void | Promise<void>;\n /** Subscribe to backend output chunks; return an unsubscribe function. */\n subscribe: (onChunk: (chunk: string) => void) => (() => void) | void;\n /**\n * Optional — called when the active shell changes (and once on mount if a\n * `shell` is provided). The host (re)connects its PTY/command backend to the\n * chosen shell here. Transports that don't care about shells can omit it; the\n * hook stays fully backward compatible.\n */\n connect?: (shell: string | undefined) => void | Promise<void>;\n}\n\nexport interface UseTerminalSessionOptions {\n transport: TerminalSessionTransport;\n /** Replayed history written before live output starts. */\n initial?: string;\n /** The active shell id. Changing it resets the buffer + calls `transport.connect`. */\n shell?: string;\n}\n\nexport interface TerminalSessionApi {\n /** Accumulated output — feed to `<Terminal output={output} />`. */\n output: string;\n /** Forward keystrokes upstream — wire to `<Terminal onData={sendData} />`. */\n sendData: (data: string) => void;\n /** Append a chunk locally (e.g. a client-side echo or banner). */\n append: (chunk: string) => void;\n /** Clear the local buffer. */\n clear: () => void;\n /** The active shell id (controlled by `options.shell` when provided). */\n shell: string | undefined;\n /**\n * Switch shells: resets the buffer (back to `initial`) and tells the transport\n * to reconnect via `transport.connect(id)`. In controlled mode (`options.shell`\n * set) this still fires `connect`; pass the new id down via `options.shell` to\n * reflect it. No-op when the id matches the current shell.\n */\n switchShell: (id: string | undefined) => void;\n}\n\n/**\n * Bind a `<Terminal>` to a streamed backend: accumulates incoming output into a\n * controlled buffer and forwards keystrokes upstream — the glue between the\n * display surface and a PTY/command channel.\n *\n * ```tsx\n * const session = useTerminalSession({\n * transport: {\n * send: (d) => echo.private(`pty.${id}`).whisper(\"stdin\", { d }),\n * subscribe: (onChunk) => {\n * const ch = echo.private(`pty.${id}`).listen(\".stdout\", (e) => onChunk(e.chunk));\n * return () => echo.leave(`pty.${id}`);\n * },\n * },\n * });\n * <Terminal output={session.output} onData={session.sendData} />\n * ```\n */\nexport function useTerminalSession(options: UseTerminalSessionOptions): TerminalSessionApi {\n const initial = options.initial ?? \"\";\n const [output, setOutput] = useState(initial);\n\n // In uncontrolled mode `switchShell` drives this; in controlled mode the\n // `options.shell` effect keeps it in sync.\n const [internalShell, setInternalShell] = useState<string | undefined>(options.shell);\n const shell = options.shell !== undefined ? options.shell : internalShell;\n\n const transportRef = useRef(options.transport);\n transportRef.current = options.transport;\n const initialRef = useRef(initial);\n initialRef.current = initial;\n\n useEffect(() => {\n const unsub = transportRef.current.subscribe((chunk) => setOutput((o) => o + chunk));\n return () => {\n if (typeof unsub === \"function\") unsub();\n };\n // subscribe once; the transport is read live via the ref.\n }, []);\n\n // Connect on mount + whenever the active shell changes. Resetting the buffer\n // is owned by switchShell (uncontrolled) / this effect (controlled).\n const connectedShell = useRef<string | undefined>(undefined);\n const mounted = useRef(false);\n useEffect(() => {\n // On mount: connect once (even if shell is undefined, for parity).\n if (!mounted.current) {\n mounted.current = true;\n connectedShell.current = shell;\n void transportRef.current.connect?.(shell);\n return;\n }\n if (!decideShellSwitch(connectedShell.current, shell).changed) return;\n connectedShell.current = shell;\n setOutput(initialRef.current); // reset the buffer for the new shell\n void transportRef.current.connect?.(shell);\n }, [shell]);\n\n const sendData = useCallback((data: string) => {\n void transportRef.current.send(data);\n }, []);\n\n const append = useCallback((chunk: string) => setOutput((o) => o + chunk), []);\n const clear = useCallback(() => setOutput(\"\"), []);\n\n const switchShell = useCallback((id: string | undefined) => {\n // Uncontrolled: flip internal state, the effect above does reset + connect.\n // Controlled (options.shell set): still drive a reset + connect now so the\n // host sees the switch immediately even before it echoes options.shell back.\n setInternalShell((cur) => (cur === id ? cur : id));\n if (!decideShellSwitch(connectedShell.current, id).changed) return;\n connectedShell.current = id;\n setOutput(initialRef.current);\n void transportRef.current.connect?.(id);\n }, []);\n\n return { output, sendData, append, clear, shell, switchShell };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,10 +1,30 @@
1
1
  import * as react from 'react';
2
- import { HTMLAttributes, RefObject } from 'react';
2
+ import { HTMLAttributes, CSSProperties, RefObject } from 'react';
3
3
  import { ITheme, Terminal as Terminal$1 } from '@xterm/xterm';
4
4
 
5
5
  /** xterm.js color theme (re-exported for ergonomic typing). */
6
6
  type TerminalTheme = ITheme;
7
7
  type CursorStyle = "block" | "underline" | "bar";
8
+ /**
9
+ * A selectable shell / profile. Fully JSON-friendly (primitives only) so an agent
10
+ * can emit it. fancy-term owns the *selected-shell state + UI + events*; the
11
+ * `command` / `args` / `cwd` fields are **host hints** — fancy-term never spawns a
12
+ * shell, the host reconnects its PTY/command backend to the chosen profile.
13
+ */
14
+ interface ShellProfile {
15
+ /** Stable key, e.g. `"powershell"`. */
16
+ id: string;
17
+ /** Display label, e.g. `"PowerShell"`. */
18
+ label: string;
19
+ /** Optional short glyph / emoji / single char (kept JSON-friendly — no ReactNode). */
20
+ icon?: string;
21
+ /** Host hint — the executable, e.g. `"pwsh"` / `"cmd.exe"`. */
22
+ command?: string;
23
+ /** Host hint — launch args. */
24
+ args?: string[];
25
+ /** Host hint — working directory. */
26
+ cwd?: string;
27
+ }
8
28
  /**
9
29
  * Imperative handle exposed via the `<Terminal>` ref and returned by
10
30
  * {@link useTerminal}. The same surface an MCP bridge drives so an embedded
@@ -29,6 +49,14 @@ interface TerminalHandle {
29
49
  getBuffer: () => string;
30
50
  /** The current text selection, or "" when nothing is selected. */
31
51
  getSelection: () => string;
52
+ /**
53
+ * Switch the active shell by id. Resolves the matching {@link ShellProfile}
54
+ * from `shells` and fires `onShellChange`; in uncontrolled mode it also updates
55
+ * the internal selection. No-op when the id isn't in `shells`.
56
+ */
57
+ setShell: (id: string) => void;
58
+ /** The active shell id, or `undefined` when no shell is selected. */
59
+ getShell: () => string | undefined;
32
60
  }
33
61
  /** Engine options shared by `<Terminal>` and {@link useTerminal}. */
34
62
  interface TerminalOptions {
@@ -69,7 +97,33 @@ interface TerminalProps extends TerminalOptions, Omit<HTMLAttributes<HTMLDivElem
69
97
  * `useFancyStream`. Replacing it with a non-extending string resets + rewrites.
70
98
  */
71
99
  output?: string;
100
+ /**
101
+ * The shells / profiles a host offers. fancy-term renders the selector and
102
+ * tracks the choice; the host reacts (reconnects its PTY to the chosen shell).
103
+ * Spread {@link BUILTIN_SHELLS} for sensible presets, but the host owns the list.
104
+ */
105
+ shells?: ShellProfile[];
106
+ /** Controlled active-shell id. Omit for uncontrolled (internal) selection. */
107
+ activeShell?: string;
108
+ /** Fired when the user (or `setShell`) switches shells. */
109
+ onShellChange?: (id: string, profile: ShellProfile) => void;
110
+ /**
111
+ * Render the built-in `<ShellSwitcher>` toolbar above the xterm surface when
112
+ * `shells` is provided. Default `false` — opt-in so existing layout is unchanged.
113
+ */
114
+ showShellBar?: boolean;
72
115
  }
116
+ /**
117
+ * Resolve a shell id to its {@link ShellProfile} within a list. Pure helper —
118
+ * the single place `setShell` / `<ShellSwitcher>` / the session hook agree on
119
+ * what "switch to id X" means.
120
+ */
121
+ declare function resolveShell(shells: ShellProfile[] | undefined, id: string | undefined): ShellProfile | undefined;
122
+ /**
123
+ * A small set of sensible built-in shell presets a host can spread / filter.
124
+ * `command` / `args` are host hints only — fancy-term never spawns them.
125
+ */
126
+ declare const BUILTIN_SHELLS: readonly ShellProfile[];
73
127
 
74
128
  /**
75
129
  * Human+ `<Terminal>` — a controlled, themeable xterm.js terminal.
@@ -92,6 +146,29 @@ interface TerminalProps extends TerminalOptions, Omit<HTMLAttributes<HTMLDivElem
92
146
  */
93
147
  declare const Terminal: react.ForwardRefExoticComponent<TerminalProps & react.RefAttributes<TerminalHandle>>;
94
148
 
149
+ interface ShellSwitcherProps {
150
+ /** The available shells / profiles. */
151
+ shells: ShellProfile[];
152
+ /** Controlled selected shell id. */
153
+ value?: string;
154
+ /** Fired when a shell is picked. */
155
+ onChange: (id: string, profile: ShellProfile) => void;
156
+ className?: string;
157
+ style?: CSSProperties;
158
+ /** Disable the control. */
159
+ disabled?: boolean;
160
+ }
161
+ /**
162
+ * Compact, accessible shell picker styled to the Fancy dark theme. Self-contained
163
+ * (zero third-party deps) — a button that toggles a listbox of `shells`.
164
+ *
165
+ * Stable handles for agents: the root carries `data-fancy-shell-switcher` and each
166
+ * option carries `data-shell-id`, so a bridge can pick a shell without DOM-guessing.
167
+ *
168
+ * Controlled: pass `value` + `onChange`. Keyboard accessible (Arrow / Enter / Esc).
169
+ */
170
+ declare function ShellSwitcher({ shells, value, onChange, className, style, disabled, }: ShellSwitcherProps): react.JSX.Element;
171
+
95
172
  /**
96
173
  * Create + own an xterm.js instance for a container, returning a stable
97
174
  * {@link TerminalHandle}. The headless engine layer behind `<Terminal>` — use it
@@ -119,11 +196,20 @@ interface TerminalSessionTransport {
119
196
  send: (data: string) => void | Promise<void>;
120
197
  /** Subscribe to backend output chunks; return an unsubscribe function. */
121
198
  subscribe: (onChunk: (chunk: string) => void) => (() => void) | void;
199
+ /**
200
+ * Optional — called when the active shell changes (and once on mount if a
201
+ * `shell` is provided). The host (re)connects its PTY/command backend to the
202
+ * chosen shell here. Transports that don't care about shells can omit it; the
203
+ * hook stays fully backward compatible.
204
+ */
205
+ connect?: (shell: string | undefined) => void | Promise<void>;
122
206
  }
123
207
  interface UseTerminalSessionOptions {
124
208
  transport: TerminalSessionTransport;
125
209
  /** Replayed history written before live output starts. */
126
210
  initial?: string;
211
+ /** The active shell id. Changing it resets the buffer + calls `transport.connect`. */
212
+ shell?: string;
127
213
  }
128
214
  interface TerminalSessionApi {
129
215
  /** Accumulated output — feed to `<Terminal output={output} />`. */
@@ -134,6 +220,15 @@ interface TerminalSessionApi {
134
220
  append: (chunk: string) => void;
135
221
  /** Clear the local buffer. */
136
222
  clear: () => void;
223
+ /** The active shell id (controlled by `options.shell` when provided). */
224
+ shell: string | undefined;
225
+ /**
226
+ * Switch shells: resets the buffer (back to `initial`) and tells the transport
227
+ * to reconnect via `transport.connect(id)`. In controlled mode (`options.shell`
228
+ * set) this still fires `connect`; pass the new id down via `options.shell` to
229
+ * reflect it. No-op when the id matches the current shell.
230
+ */
231
+ switchShell: (id: string | undefined) => void;
137
232
  }
138
233
  /**
139
234
  * Bind a `<Terminal>` to a streamed backend: accumulates incoming output into a
@@ -162,4 +257,4 @@ declare function useTerminalSession(options: UseTerminalSessionOptions): Termina
162
257
  */
163
258
  declare const fancyDarkTheme: TerminalTheme;
164
259
 
165
- export { type CursorStyle, Terminal, type TerminalHandle, type TerminalOptions, type TerminalProps, type TerminalSessionApi, type TerminalSessionTransport, type TerminalTheme, type UseTerminalSessionOptions, fancyDarkTheme, useTerminal, useTerminalFit, useTerminalSession };
260
+ export { BUILTIN_SHELLS, type CursorStyle, type ShellProfile, ShellSwitcher, type ShellSwitcherProps, Terminal, type TerminalHandle, type TerminalOptions, type TerminalProps, type TerminalSessionApi, type TerminalSessionTransport, type TerminalTheme, type UseTerminalSessionOptions, fancyDarkTheme, resolveShell, useTerminal, useTerminalFit, useTerminalSession };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,30 @@
1
1
  import * as react from 'react';
2
- import { HTMLAttributes, RefObject } from 'react';
2
+ import { HTMLAttributes, CSSProperties, RefObject } from 'react';
3
3
  import { ITheme, Terminal as Terminal$1 } from '@xterm/xterm';
4
4
 
5
5
  /** xterm.js color theme (re-exported for ergonomic typing). */
6
6
  type TerminalTheme = ITheme;
7
7
  type CursorStyle = "block" | "underline" | "bar";
8
+ /**
9
+ * A selectable shell / profile. Fully JSON-friendly (primitives only) so an agent
10
+ * can emit it. fancy-term owns the *selected-shell state + UI + events*; the
11
+ * `command` / `args` / `cwd` fields are **host hints** — fancy-term never spawns a
12
+ * shell, the host reconnects its PTY/command backend to the chosen profile.
13
+ */
14
+ interface ShellProfile {
15
+ /** Stable key, e.g. `"powershell"`. */
16
+ id: string;
17
+ /** Display label, e.g. `"PowerShell"`. */
18
+ label: string;
19
+ /** Optional short glyph / emoji / single char (kept JSON-friendly — no ReactNode). */
20
+ icon?: string;
21
+ /** Host hint — the executable, e.g. `"pwsh"` / `"cmd.exe"`. */
22
+ command?: string;
23
+ /** Host hint — launch args. */
24
+ args?: string[];
25
+ /** Host hint — working directory. */
26
+ cwd?: string;
27
+ }
8
28
  /**
9
29
  * Imperative handle exposed via the `<Terminal>` ref and returned by
10
30
  * {@link useTerminal}. The same surface an MCP bridge drives so an embedded
@@ -29,6 +49,14 @@ interface TerminalHandle {
29
49
  getBuffer: () => string;
30
50
  /** The current text selection, or "" when nothing is selected. */
31
51
  getSelection: () => string;
52
+ /**
53
+ * Switch the active shell by id. Resolves the matching {@link ShellProfile}
54
+ * from `shells` and fires `onShellChange`; in uncontrolled mode it also updates
55
+ * the internal selection. No-op when the id isn't in `shells`.
56
+ */
57
+ setShell: (id: string) => void;
58
+ /** The active shell id, or `undefined` when no shell is selected. */
59
+ getShell: () => string | undefined;
32
60
  }
33
61
  /** Engine options shared by `<Terminal>` and {@link useTerminal}. */
34
62
  interface TerminalOptions {
@@ -69,7 +97,33 @@ interface TerminalProps extends TerminalOptions, Omit<HTMLAttributes<HTMLDivElem
69
97
  * `useFancyStream`. Replacing it with a non-extending string resets + rewrites.
70
98
  */
71
99
  output?: string;
100
+ /**
101
+ * The shells / profiles a host offers. fancy-term renders the selector and
102
+ * tracks the choice; the host reacts (reconnects its PTY to the chosen shell).
103
+ * Spread {@link BUILTIN_SHELLS} for sensible presets, but the host owns the list.
104
+ */
105
+ shells?: ShellProfile[];
106
+ /** Controlled active-shell id. Omit for uncontrolled (internal) selection. */
107
+ activeShell?: string;
108
+ /** Fired when the user (or `setShell`) switches shells. */
109
+ onShellChange?: (id: string, profile: ShellProfile) => void;
110
+ /**
111
+ * Render the built-in `<ShellSwitcher>` toolbar above the xterm surface when
112
+ * `shells` is provided. Default `false` — opt-in so existing layout is unchanged.
113
+ */
114
+ showShellBar?: boolean;
72
115
  }
116
+ /**
117
+ * Resolve a shell id to its {@link ShellProfile} within a list. Pure helper —
118
+ * the single place `setShell` / `<ShellSwitcher>` / the session hook agree on
119
+ * what "switch to id X" means.
120
+ */
121
+ declare function resolveShell(shells: ShellProfile[] | undefined, id: string | undefined): ShellProfile | undefined;
122
+ /**
123
+ * A small set of sensible built-in shell presets a host can spread / filter.
124
+ * `command` / `args` are host hints only — fancy-term never spawns them.
125
+ */
126
+ declare const BUILTIN_SHELLS: readonly ShellProfile[];
73
127
 
74
128
  /**
75
129
  * Human+ `<Terminal>` — a controlled, themeable xterm.js terminal.
@@ -92,6 +146,29 @@ interface TerminalProps extends TerminalOptions, Omit<HTMLAttributes<HTMLDivElem
92
146
  */
93
147
  declare const Terminal: react.ForwardRefExoticComponent<TerminalProps & react.RefAttributes<TerminalHandle>>;
94
148
 
149
+ interface ShellSwitcherProps {
150
+ /** The available shells / profiles. */
151
+ shells: ShellProfile[];
152
+ /** Controlled selected shell id. */
153
+ value?: string;
154
+ /** Fired when a shell is picked. */
155
+ onChange: (id: string, profile: ShellProfile) => void;
156
+ className?: string;
157
+ style?: CSSProperties;
158
+ /** Disable the control. */
159
+ disabled?: boolean;
160
+ }
161
+ /**
162
+ * Compact, accessible shell picker styled to the Fancy dark theme. Self-contained
163
+ * (zero third-party deps) — a button that toggles a listbox of `shells`.
164
+ *
165
+ * Stable handles for agents: the root carries `data-fancy-shell-switcher` and each
166
+ * option carries `data-shell-id`, so a bridge can pick a shell without DOM-guessing.
167
+ *
168
+ * Controlled: pass `value` + `onChange`. Keyboard accessible (Arrow / Enter / Esc).
169
+ */
170
+ declare function ShellSwitcher({ shells, value, onChange, className, style, disabled, }: ShellSwitcherProps): react.JSX.Element;
171
+
95
172
  /**
96
173
  * Create + own an xterm.js instance for a container, returning a stable
97
174
  * {@link TerminalHandle}. The headless engine layer behind `<Terminal>` — use it
@@ -119,11 +196,20 @@ interface TerminalSessionTransport {
119
196
  send: (data: string) => void | Promise<void>;
120
197
  /** Subscribe to backend output chunks; return an unsubscribe function. */
121
198
  subscribe: (onChunk: (chunk: string) => void) => (() => void) | void;
199
+ /**
200
+ * Optional — called when the active shell changes (and once on mount if a
201
+ * `shell` is provided). The host (re)connects its PTY/command backend to the
202
+ * chosen shell here. Transports that don't care about shells can omit it; the
203
+ * hook stays fully backward compatible.
204
+ */
205
+ connect?: (shell: string | undefined) => void | Promise<void>;
122
206
  }
123
207
  interface UseTerminalSessionOptions {
124
208
  transport: TerminalSessionTransport;
125
209
  /** Replayed history written before live output starts. */
126
210
  initial?: string;
211
+ /** The active shell id. Changing it resets the buffer + calls `transport.connect`. */
212
+ shell?: string;
127
213
  }
128
214
  interface TerminalSessionApi {
129
215
  /** Accumulated output — feed to `<Terminal output={output} />`. */
@@ -134,6 +220,15 @@ interface TerminalSessionApi {
134
220
  append: (chunk: string) => void;
135
221
  /** Clear the local buffer. */
136
222
  clear: () => void;
223
+ /** The active shell id (controlled by `options.shell` when provided). */
224
+ shell: string | undefined;
225
+ /**
226
+ * Switch shells: resets the buffer (back to `initial`) and tells the transport
227
+ * to reconnect via `transport.connect(id)`. In controlled mode (`options.shell`
228
+ * set) this still fires `connect`; pass the new id down via `options.shell` to
229
+ * reflect it. No-op when the id matches the current shell.
230
+ */
231
+ switchShell: (id: string | undefined) => void;
137
232
  }
138
233
  /**
139
234
  * Bind a `<Terminal>` to a streamed backend: accumulates incoming output into a
@@ -162,4 +257,4 @@ declare function useTerminalSession(options: UseTerminalSessionOptions): Termina
162
257
  */
163
258
  declare const fancyDarkTheme: TerminalTheme;
164
259
 
165
- export { type CursorStyle, Terminal, type TerminalHandle, type TerminalOptions, type TerminalProps, type TerminalSessionApi, type TerminalSessionTransport, type TerminalTheme, type UseTerminalSessionOptions, fancyDarkTheme, useTerminal, useTerminalFit, useTerminalSession };
260
+ export { BUILTIN_SHELLS, type CursorStyle, type ShellProfile, ShellSwitcher, type ShellSwitcherProps, Terminal, type TerminalHandle, type TerminalOptions, type TerminalProps, type TerminalSessionApi, type TerminalSessionTransport, type TerminalTheme, type UseTerminalSessionOptions, fancyDarkTheme, resolveShell, useTerminal, useTerminalFit, useTerminalSession };