@particle-academy/fancy-term 0.1.0 → 0.2.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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { forwardRef, useRef, useImperativeHandle, useEffect, useState, useCallback } from 'react';
1
+ import { forwardRef, useRef, useState, useCallback, useImperativeHandle, useEffect, useId } from 'react';
2
2
  import { Terminal as Terminal$1 } from '@xterm/xterm';
3
3
  import { FitAddon } from '@xterm/addon-fit';
4
- import { jsx } from 'react/jsx-runtime';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
5
 
6
6
  // src/components/Terminal.tsx
7
7
 
@@ -78,14 +78,26 @@ function useTerminal(containerRef, options = {}) {
78
78
  clear: () => xtermRef.current?.clear(),
79
79
  reset: () => xtermRef.current?.reset(),
80
80
  fit: () => {
81
+ const addon = fitRef.current;
82
+ if (!addon) return;
81
83
  try {
82
- fitRef.current?.fit();
84
+ const dims = addon.proposeDimensions();
85
+ if (!dims || !Number.isFinite(dims.cols) || !Number.isFinite(dims.rows) || dims.cols < 1 || dims.rows < 1) {
86
+ return;
87
+ }
88
+ addon.fit();
83
89
  } catch {
84
90
  }
85
91
  },
86
92
  focus: () => xtermRef.current?.focus(),
87
93
  getBuffer: () => readBuffer(xtermRef.current),
88
- getSelection: () => xtermRef.current?.getSelection() ?? ""
94
+ getSelection: () => xtermRef.current?.getSelection() ?? "",
95
+ // Shell selection is owned by the <Terminal> component layer (it needs the
96
+ // `shells` list + onShellChange). The headless engine is shell-agnostic, so
97
+ // these are no-ops here and get overridden when <Terminal> composes them.
98
+ setShell: () => {
99
+ },
100
+ getShell: () => void 0
89
101
  };
90
102
  }
91
103
  const handle = handleRef.current;
@@ -115,12 +127,7 @@ function useTerminal(containerRef, options = {}) {
115
127
  ({ cols, rows }) => optsRef.current.onResize?.({ cols, rows })
116
128
  );
117
129
  if (o.initialOutput) term.write(o.initialOutput);
118
- if ((o.fit ?? true) && el.clientWidth > 0 && el.clientHeight > 0) {
119
- try {
120
- fitAddon.fit();
121
- } catch {
122
- }
123
- }
130
+ if (o.fit ?? true) handle.fit();
124
131
  return () => {
125
132
  dataSub.dispose();
126
133
  resizeSub.dispose();
@@ -143,12 +150,231 @@ function useTerminal(containerRef, options = {}) {
143
150
  return handle;
144
151
  }
145
152
 
153
+ // src/types.ts
154
+ function resolveShell(shells, id) {
155
+ if (!shells || id === void 0) return void 0;
156
+ return shells.find((s) => s.id === id);
157
+ }
158
+ var BUILTIN_SHELLS = [
159
+ { id: "cmd", label: "Command Prompt", icon: ">_", command: "cmd.exe" },
160
+ { id: "powershell", label: "Windows PowerShell", icon: "PS", command: "powershell.exe" },
161
+ { id: "pwsh", label: "PowerShell", icon: "PS", command: "pwsh" },
162
+ {
163
+ id: "git-bash",
164
+ label: "Git Bash",
165
+ icon: "",
166
+ command: "C:\\Program Files\\Git\\bin\\bash.exe",
167
+ args: ["--login", "-i"]
168
+ },
169
+ { id: "bash", label: "Bash", icon: "$", command: "bash" },
170
+ { id: "zsh", label: "Zsh", icon: "%", command: "zsh" }
171
+ ];
172
+ var PANEL_BG = "#18181b";
173
+ var BORDER = "#3f3f46";
174
+ var FG = "#e4e4e7";
175
+ var MUTED = "#a1a1aa";
176
+ var ACCENT = "#8b5cf6";
177
+ var HOVER_BG = "#27272a";
178
+ function ShellSwitcher({
179
+ shells,
180
+ value,
181
+ onChange,
182
+ className,
183
+ style,
184
+ disabled
185
+ }) {
186
+ const [open, setOpen] = useState(false);
187
+ const [activeIndex, setActiveIndex] = useState(0);
188
+ const rootRef = useRef(null);
189
+ const listId = useId();
190
+ const selected = resolveShell(shells, value);
191
+ const selectedIndex = selected ? shells.indexOf(selected) : -1;
192
+ useEffect(() => {
193
+ if (!open) return;
194
+ const onDocClick = (e) => {
195
+ if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
196
+ };
197
+ document.addEventListener("mousedown", onDocClick);
198
+ return () => document.removeEventListener("mousedown", onDocClick);
199
+ }, [open]);
200
+ const openMenu = useCallback(() => {
201
+ if (disabled) return;
202
+ setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);
203
+ setOpen(true);
204
+ }, [disabled, selectedIndex]);
205
+ const pick = useCallback(
206
+ (i) => {
207
+ const profile = shells[i];
208
+ if (!profile) return;
209
+ onChange(profile.id, profile);
210
+ setOpen(false);
211
+ },
212
+ [shells, onChange]
213
+ );
214
+ const onButtonKeyDown = (e) => {
215
+ if (disabled) return;
216
+ if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Enter" || e.key === " ") {
217
+ e.preventDefault();
218
+ openMenu();
219
+ }
220
+ };
221
+ const onListKeyDown = (e) => {
222
+ if (e.key === "Escape") {
223
+ e.preventDefault();
224
+ setOpen(false);
225
+ return;
226
+ }
227
+ if (e.key === "ArrowDown") {
228
+ e.preventDefault();
229
+ setActiveIndex((i) => Math.min(shells.length - 1, i + 1));
230
+ } else if (e.key === "ArrowUp") {
231
+ e.preventDefault();
232
+ setActiveIndex((i) => Math.max(0, i - 1));
233
+ } else if (e.key === "Home") {
234
+ e.preventDefault();
235
+ setActiveIndex(0);
236
+ } else if (e.key === "End") {
237
+ e.preventDefault();
238
+ setActiveIndex(shells.length - 1);
239
+ } else if (e.key === "Enter" || e.key === " ") {
240
+ e.preventDefault();
241
+ pick(activeIndex);
242
+ }
243
+ };
244
+ const buttonStyle = {
245
+ display: "inline-flex",
246
+ alignItems: "center",
247
+ gap: 6,
248
+ height: 26,
249
+ padding: "0 8px",
250
+ fontSize: 12,
251
+ lineHeight: 1,
252
+ fontFamily: "inherit",
253
+ color: FG,
254
+ background: PANEL_BG,
255
+ border: `1px solid ${BORDER}`,
256
+ borderRadius: 6,
257
+ cursor: disabled ? "default" : "pointer",
258
+ opacity: disabled ? 0.6 : 1,
259
+ userSelect: "none"
260
+ };
261
+ return /* @__PURE__ */ jsxs(
262
+ "div",
263
+ {
264
+ ref: rootRef,
265
+ "data-fancy-shell-switcher": "",
266
+ "data-active-shell": value ?? void 0,
267
+ className,
268
+ style: { position: "relative", display: "inline-block", ...style },
269
+ children: [
270
+ /* @__PURE__ */ jsxs(
271
+ "button",
272
+ {
273
+ type: "button",
274
+ "aria-haspopup": "listbox",
275
+ "aria-expanded": open,
276
+ "aria-label": "Select shell",
277
+ disabled,
278
+ style: buttonStyle,
279
+ onClick: () => open ? setOpen(false) : openMenu(),
280
+ onKeyDown: onButtonKeyDown,
281
+ children: [
282
+ selected?.icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: ACCENT }, children: selected.icon }) : null,
283
+ /* @__PURE__ */ jsx("span", { children: selected?.label ?? "Select shell" }),
284
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: MUTED, fontSize: 9 }, children: "\u25BC" })
285
+ ]
286
+ }
287
+ ),
288
+ open ? /* @__PURE__ */ jsx(
289
+ "ul",
290
+ {
291
+ role: "listbox",
292
+ id: listId,
293
+ "aria-label": "Shells",
294
+ tabIndex: -1,
295
+ autoFocus: true,
296
+ onKeyDown: onListKeyDown,
297
+ ref: (el) => el?.focus(),
298
+ style: {
299
+ position: "absolute",
300
+ zIndex: 50,
301
+ top: "calc(100% + 4px)",
302
+ left: 0,
303
+ minWidth: "100%",
304
+ margin: 0,
305
+ padding: 4,
306
+ listStyle: "none",
307
+ background: PANEL_BG,
308
+ border: `1px solid ${BORDER}`,
309
+ borderRadius: 8,
310
+ boxShadow: "0 8px 24px rgba(0,0,0,0.45)",
311
+ outline: "none"
312
+ },
313
+ children: shells.map((s, i) => {
314
+ const isSelected = s.id === value;
315
+ const isActive = i === activeIndex;
316
+ return /* @__PURE__ */ jsxs(
317
+ "li",
318
+ {
319
+ role: "option",
320
+ "aria-selected": isSelected,
321
+ "data-shell-id": s.id,
322
+ onMouseEnter: () => setActiveIndex(i),
323
+ onClick: () => pick(i),
324
+ style: {
325
+ display: "flex",
326
+ alignItems: "center",
327
+ gap: 8,
328
+ padding: "6px 8px",
329
+ fontSize: 12,
330
+ borderRadius: 5,
331
+ color: FG,
332
+ cursor: "pointer",
333
+ background: isActive ? HOVER_BG : "transparent"
334
+ },
335
+ children: [
336
+ /* @__PURE__ */ jsx(
337
+ "span",
338
+ {
339
+ "aria-hidden": "true",
340
+ style: {
341
+ width: 18,
342
+ textAlign: "center",
343
+ color: isSelected ? ACCENT : MUTED
344
+ },
345
+ children: s.icon ?? ""
346
+ }
347
+ ),
348
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: s.label }),
349
+ isSelected ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: ACCENT, fontSize: 11 }, children: "\u2713" }) : null
350
+ ]
351
+ },
352
+ s.id
353
+ );
354
+ })
355
+ }
356
+ ) : null
357
+ ]
358
+ }
359
+ );
360
+ }
361
+
146
362
  // src/output-diff.ts
147
363
  function diffOutput(written, next) {
148
364
  if (next === written) return null;
149
365
  if (next.startsWith(written)) return { reset: false, write: next.slice(written.length) };
150
366
  return { reset: true, write: next };
151
367
  }
368
+
369
+ // src/shell-select.ts
370
+ function decideShellSelect(shells, current, id) {
371
+ const profile = resolveShell(shells, id);
372
+ if (!profile) return null;
373
+ return { profile, changed: current !== id };
374
+ }
375
+ function decideShellSwitch(current, next) {
376
+ return { changed: current !== next };
377
+ }
152
378
  var Terminal = forwardRef(function Terminal2({
153
379
  output,
154
380
  theme,
@@ -164,11 +390,27 @@ var Terminal = forwardRef(function Terminal2({
164
390
  initialOutput,
165
391
  onData,
166
392
  onResize,
393
+ shells,
394
+ activeShell,
395
+ onShellChange,
396
+ showShellBar = false,
167
397
  className,
168
398
  style,
169
399
  ...rest
170
400
  }, ref) {
171
401
  const containerRef = useRef(null);
402
+ const isShellControlled = activeShell !== void 0;
403
+ const [internalShell, setInternalShell] = useState(void 0);
404
+ const shellId = isShellControlled ? activeShell : internalShell;
405
+ const shellStateRef = useRef({ shells, shellId, isShellControlled, onShellChange });
406
+ shellStateRef.current = { shells, shellId, isShellControlled, onShellChange };
407
+ const selectShell = useCallback((id) => {
408
+ const s = shellStateRef.current;
409
+ const decision = decideShellSelect(s.shells, s.shellId, id);
410
+ if (!decision) return;
411
+ if (!s.isShellControlled) setInternalShell(id);
412
+ s.onShellChange?.(id, decision.profile);
413
+ }, []);
172
414
  const handle = useTerminal(containerRef, {
173
415
  theme,
174
416
  rows,
@@ -186,7 +428,15 @@ var Terminal = forwardRef(function Terminal2({
186
428
  onData,
187
429
  onResize
188
430
  });
189
- useImperativeHandle(ref, () => handle, [handle]);
431
+ useImperativeHandle(
432
+ ref,
433
+ () => ({
434
+ ...handle,
435
+ setShell: selectShell,
436
+ getShell: () => shellStateRef.current.shellId
437
+ }),
438
+ [handle, selectShell]
439
+ );
190
440
  const written = useRef("");
191
441
  useEffect(() => {
192
442
  if (output === void 0) return;
@@ -196,36 +446,97 @@ var Terminal = forwardRef(function Terminal2({
196
446
  handle.write(change.write);
197
447
  written.current = output;
198
448
  }, [output, handle]);
199
- return /* @__PURE__ */ jsx(
449
+ const surface = /* @__PURE__ */ jsx(
200
450
  "div",
201
451
  {
202
452
  ref: containerRef,
203
453
  "data-fancy-terminal": "",
204
454
  "data-readonly": readOnly ? "" : void 0,
455
+ "data-fancy-terminal-shell": shellId ?? void 0,
456
+ style: showShellBar && shells ? { width: "100%", flex: 1, minHeight: 0 } : { width: "100%", height: "100%", ...style },
457
+ ...showShellBar && shells ? {} : rest
458
+ }
459
+ );
460
+ if (!showShellBar || !shells) return surface;
461
+ return /* @__PURE__ */ jsxs(
462
+ "div",
463
+ {
464
+ "data-fancy-terminal-shell": shellId ?? void 0,
205
465
  className,
206
- style: { width: "100%", height: "100%", ...style },
207
- ...rest
466
+ style: { display: "flex", flexDirection: "column", width: "100%", height: "100%", ...style },
467
+ ...rest,
468
+ children: [
469
+ /* @__PURE__ */ jsx(
470
+ "div",
471
+ {
472
+ style: {
473
+ display: "flex",
474
+ alignItems: "center",
475
+ gap: 8,
476
+ padding: "4px 6px",
477
+ background: "#09090b",
478
+ borderBottom: "1px solid #27272a"
479
+ },
480
+ children: /* @__PURE__ */ jsx(
481
+ ShellSwitcher,
482
+ {
483
+ shells,
484
+ value: shellId,
485
+ onChange: (id) => selectShell(id),
486
+ disabled: readOnly
487
+ }
488
+ )
489
+ }
490
+ ),
491
+ surface
492
+ ]
208
493
  }
209
494
  );
210
495
  });
211
496
  function useTerminalSession(options) {
212
- const [output, setOutput] = useState(options.initial ?? "");
497
+ const initial = options.initial ?? "";
498
+ const [output, setOutput] = useState(initial);
499
+ const [internalShell, setInternalShell] = useState(options.shell);
500
+ const shell = options.shell !== void 0 ? options.shell : internalShell;
213
501
  const transportRef = useRef(options.transport);
214
502
  transportRef.current = options.transport;
503
+ const initialRef = useRef(initial);
504
+ initialRef.current = initial;
215
505
  useEffect(() => {
216
506
  const unsub = transportRef.current.subscribe((chunk) => setOutput((o) => o + chunk));
217
507
  return () => {
218
508
  if (typeof unsub === "function") unsub();
219
509
  };
220
510
  }, []);
511
+ const connectedShell = useRef(void 0);
512
+ const mounted = useRef(false);
513
+ useEffect(() => {
514
+ if (!mounted.current) {
515
+ mounted.current = true;
516
+ connectedShell.current = shell;
517
+ void transportRef.current.connect?.(shell);
518
+ return;
519
+ }
520
+ if (!decideShellSwitch(connectedShell.current, shell).changed) return;
521
+ connectedShell.current = shell;
522
+ setOutput(initialRef.current);
523
+ void transportRef.current.connect?.(shell);
524
+ }, [shell]);
221
525
  const sendData = useCallback((data) => {
222
526
  void transportRef.current.send(data);
223
527
  }, []);
224
528
  const append = useCallback((chunk) => setOutput((o) => o + chunk), []);
225
529
  const clear = useCallback(() => setOutput(""), []);
226
- return { output, sendData, append, clear };
530
+ const switchShell = useCallback((id) => {
531
+ setInternalShell((cur) => cur === id ? cur : id);
532
+ if (!decideShellSwitch(connectedShell.current, id).changed) return;
533
+ connectedShell.current = id;
534
+ setOutput(initialRef.current);
535
+ void transportRef.current.connect?.(id);
536
+ }, []);
537
+ return { output, sendData, append, clear, shell, switchShell };
227
538
  }
228
539
 
229
- export { Terminal, fancyDarkTheme, useTerminal, useTerminalFit, useTerminalSession };
540
+ export { BUILTIN_SHELLS, ShellSwitcher, Terminal, fancyDarkTheme, resolveShell, useTerminal, useTerminalFit, useTerminalSession };
230
541
  //# sourceMappingURL=index.js.map
231
542
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -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","Terminal"],"mappings":"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","file":"index.js","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","Terminal","useState","useCallback","jsx","jsxs"],"mappings":";;;;;;;;AAOO,IAAM,cAAA,GAAgC;AAAA,EAC3C,UAAA,EAAY,SAAA;AAAA;AAAA,EACZ,UAAA,EAAY,SAAA;AAAA;AAAA,EACZ,MAAA,EAAQ,SAAA;AAAA;AAAA,EACR,YAAA,EAAc,SAAA;AAAA,EACd,mBAAA,EAAqB,SAAA;AAAA;AAAA,EACrB,KAAA,EAAO,SAAA;AAAA,EACP,GAAA,EAAK,SAAA;AAAA,EACL,KAAA,EAAO,SAAA;AAAA,EACP,MAAA,EAAQ,SAAA;AAAA,EACR,IAAA,EAAM,SAAA;AAAA,EACN,OAAA,EAAS,SAAA;AAAA,EACT,IAAA,EAAM,SAAA;AAAA,EACN,KAAA,EAAO,SAAA;AAAA,EACP,WAAA,EAAa,SAAA;AAAA,EACb,SAAA,EAAW,SAAA;AAAA,EACX,WAAA,EAAa,SAAA;AAAA,EACb,YAAA,EAAc,SAAA;AAAA,EACd,UAAA,EAAY,SAAA;AAAA,EACZ,aAAA,EAAe,SAAA;AAAA,EACf,UAAA,EAAY,SAAA;AAAA,EACZ,WAAA,EAAa;AACf;ACrBO,SAAS,cAAA,CACd,GAAA,EACA,GAAA,EACA,OAAA,GAAU,IAAA,EACJ;AACN,EAAA,MAAM,MAAA,GAAS,OAAO,GAAG,CAAA;AACzB,EAAA,MAAA,CAAO,OAAA,GAAU,GAAA;AAEjB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,IAAA,IAAI,CAAC,EAAA,EAAI;AAET,IAAA,MAAM,MAAM,MAAM;AAChB,MAAA,IAAI,GAAG,WAAA,GAAc,CAAA,IAAK,GAAG,YAAA,GAAe,CAAA,SAAU,OAAA,EAAQ;AAAA,IAChE,CAAA;AAEA,IAAA,MAAM,QAAA,GAAW,IAAI,cAAA,CAAe,GAAG,CAAA;AACvC,IAAA,QAAA,CAAS,QAAQ,EAAE,CAAA;AACnB,IAAA,GAAA,EAAI;AAEJ,IAAA,OAAO,MAAM,SAAS,UAAA,EAAW;AAAA,EACnC,CAAA,EAAG,CAAC,GAAA,EAAK,OAAO,CAAC,CAAA;AACnB;;;ACxBA,IAAM,YAAA,GACJ,wFAAA;AAGF,SAAS,WAAW,IAAA,EAA4B;AAC9C,EAAA,IAAI,CAAC,MAAM,OAAO,EAAA;AAClB,EAAA,MAAM,GAAA,GAAM,KAAK,MAAA,CAAO,MAAA;AACxB,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACnC,IAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAA;AAC1B,IAAA,IAAI,MAAM,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,iBAAA,CAAkB,IAAI,CAAC,CAAA;AAAA,EACnD;AACA,EAAA,OAAO,MAAM,IAAA,CAAK,IAAI,CAAA,CAAE,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAC5C;AASO,SAAS,WAAA,CACd,YAAA,EACA,OAAA,GAA2B,EAAC,EACZ;AAChB,EAAA,MAAM,QAAA,GAAWA,OAAqB,IAAI,CAAA;AAC1C,EAAA,MAAM,MAAA,GAASA,OAAwB,IAAI,CAAA;AAC3C,EAAA,MAAM,OAAA,GAAUA,OAAO,OAAO,CAAA;AAC9B,EAAA,OAAA,CAAQ,OAAA,GAAU,OAAA;AAGlB,EAAA,MAAM,SAAA,GAAYA,OAA8B,IAAI,CAAA;AACpD,EAAA,IAAI,SAAA,CAAU,YAAY,IAAA,EAAM;AAC9B,IAAA,SAAA,CAAU,OAAA,GAAU;AAAA,MAClB,IAAI,KAAA,GAAQ;AACV,QAAA,OAAO,QAAA,CAAS,OAAA;AAAA,MAClB,CAAA;AAAA,MACA,OAAO,CAAC,CAAA,KAAM,QAAA,CAAS,OAAA,EAAS,MAAM,CAAC,CAAA;AAAA,MACvC,SAAS,CAAC,CAAA,KAAM,QAAA,CAAS,OAAA,EAAS,QAAQ,CAAC,CAAA;AAAA,MAC3C,KAAA,EAAO,MAAM,QAAA,CAAS,OAAA,EAAS,KAAA,EAAM;AAAA,MACrC,KAAA,EAAO,MAAM,QAAA,CAAS,OAAA,EAAS,KAAA,EAAM;AAAA,MACrC,KAAK,MAAM;AACT,QAAA,MAAM,QAAQ,MAAA,CAAO,OAAA;AACrB,QAAA,IAAI,CAAC,KAAA,EAAO;AACZ,QAAA,IAAI;AAOF,UAAA,MAAM,IAAA,GAAO,MAAM,iBAAA,EAAkB;AACrC,UAAA,IACE,CAAC,IAAA,IACD,CAAC,OAAO,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA,IAC1B,CAAC,OAAO,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA,IAC1B,IAAA,CAAK,OAAO,CAAA,IACZ,IAAA,CAAK,OAAO,CAAA,EACZ;AACA,YAAA;AAAA,UACF;AACA,UAAA,KAAA,CAAM,GAAA,EAAI;AAAA,QACZ,CAAA,CAAA,MAAQ;AAAA,QAER;AAAA,MACF,CAAA;AAAA,MACA,KAAA,EAAO,MAAM,QAAA,CAAS,OAAA,EAAS,KAAA,EAAM;AAAA,MACrC,SAAA,EAAW,MAAM,UAAA,CAAW,QAAA,CAAS,OAAO,CAAA;AAAA,MAC5C,YAAA,EAAc,MAAM,QAAA,CAAS,OAAA,EAAS,cAAa,IAAK,EAAA;AAAA;AAAA;AAAA;AAAA,MAIxD,UAAU,MAAM;AAAA,MAAC,CAAA;AAAA,MACjB,UAAU,MAAM;AAAA,KAClB;AAAA,EACF;AACA,EAAA,MAAM,SAAS,SAAA,CAAU,OAAA;AAGzB,EAAAC,UAAU,MAAM;AACd,IAAA,MAAM,KAAK,YAAA,CAAa,OAAA;AACxB,IAAA,IAAI,CAAC,EAAA,EAAI;AACT,IAAA,MAAM,IAAI,OAAA,CAAQ,OAAA;AAElB,IAAA,MAAM,IAAA,GAAO,IAAIC,UAAA,CAAM;AAAA,MACrB,KAAA,EAAO,EAAE,KAAA,IAAS,cAAA;AAAA,MAClB,MAAM,CAAA,CAAE,IAAA;AAAA,MACR,MAAM,CAAA,CAAE,IAAA;AAAA,MACR,YAAA,EAAc,EAAE,QAAA,IAAY,KAAA;AAAA,MAC5B,WAAA,EAAa,EAAE,WAAA,IAAe,IAAA;AAAA,MAC9B,WAAA,EAAa,EAAE,WAAA,IAAe,OAAA;AAAA,MAC9B,UAAA,EAAY,EAAE,UAAA,IAAc,YAAA;AAAA,MAC5B,QAAA,EAAU,EAAE,QAAA,IAAY,EAAA;AAAA,MACxB,UAAA,EAAY,EAAE,UAAA,IAAc,GAAA;AAAA,MAC5B,gBAAA,EAAkB;AAAA,KACnB,CAAA;AACD,IAAA,MAAM,QAAA,GAAW,IAAI,QAAA,EAAS;AAC9B,IAAA,IAAA,CAAK,UAAU,QAAQ,CAAA;AACvB,IAAA,IAAA,CAAK,KAAK,EAAE,CAAA;AACZ,IAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,IAAA,MAAA,CAAO,OAAA,GAAU,QAAA;AAEjB,IAAA,MAAM,OAAA,GAAU,KAAK,MAAA,CAAO,CAAC,MAAM,OAAA,CAAQ,OAAA,CAAQ,MAAA,GAAS,CAAC,CAAC,CAAA;AAC9D,IAAA,MAAM,YAAY,IAAA,CAAK,QAAA;AAAA,MAAS,CAAC,EAAE,IAAA,EAAM,IAAA,EAAK,KAC5C,OAAA,CAAQ,OAAA,CAAQ,QAAA,GAAW,EAAE,IAAA,EAAM,IAAA,EAAM;AAAA,KAC3C;AAEA,IAAA,IAAI,CAAA,CAAE,aAAA,EAAe,IAAA,CAAK,KAAA,CAAM,EAAE,aAAa,CAAA;AAG/C,IAAA,IAAI,CAAA,CAAE,GAAA,IAAO,IAAA,EAAM,MAAA,CAAO,GAAA,EAAI;AAE9B,IAAA,OAAO,MAAM;AACX,MAAA,OAAA,CAAQ,OAAA,EAAQ;AAChB,MAAA,SAAA,CAAU,OAAA,EAAQ;AAClB,MAAA,IAAA,CAAK,OAAA,EAAQ;AACb,MAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,MAAA,MAAA,CAAO,OAAA,GAAU,IAAA;AAAA,IACnB,CAAA;AAAA,EAEF,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAGjB,EAAAD,UAAU,MAAM;AACd,IAAA,MAAM,IAAI,QAAA,CAAS,OAAA;AACnB,IAAA,IAAI,CAAC,CAAA,EAAG;AACR,IAAA,IAAI,OAAA,CAAQ,KAAA,EAAO,CAAA,CAAE,OAAA,CAAQ,QAAQ,OAAA,CAAQ,KAAA;AAC7C,IAAA,IAAI,QAAQ,QAAA,KAAa,MAAA,EAAW,CAAA,CAAE,OAAA,CAAQ,eAAe,OAAA,CAAQ,QAAA;AACrE,IAAA,IAAI,QAAQ,WAAA,KAAgB,MAAA,EAAW,CAAA,CAAE,OAAA,CAAQ,cAAc,OAAA,CAAQ,WAAA;AACvE,IAAA,IAAI,OAAA,CAAQ,WAAA,EAAa,CAAA,CAAE,OAAA,CAAQ,cAAc,OAAA,CAAQ,WAAA;AACzD,IAAA,IAAI,OAAA,CAAQ,QAAA,EAAU,CAAA,CAAE,OAAA,CAAQ,WAAW,OAAA,CAAQ,QAAA;AACnD,IAAA,IAAI,OAAA,CAAQ,UAAA,EAAY,CAAA,CAAE,OAAA,CAAQ,aAAa,OAAA,CAAQ,UAAA;AAAA,EACzD,CAAA,EAAG,CAAC,OAAA,CAAQ,KAAA,EAAO,QAAQ,QAAA,EAAU,OAAA,CAAQ,WAAA,EAAa,OAAA,CAAQ,WAAA,EAAa,OAAA,CAAQ,QAAA,EAAU,OAAA,CAAQ,UAAU,CAAC,CAAA;AAGpH,EAAA,cAAA,CAAe,cAAc,MAAM,MAAA,CAAO,KAAI,EAAG,OAAA,CAAQ,OAAO,IAAI,CAAA;AAEpE,EAAA,OAAO,MAAA;AACT;;;ACvBO,SAAS,YAAA,CACd,QACA,EAAA,EAC0B;AAC1B,EAAA,IAAI,CAAC,MAAA,IAAU,EAAA,KAAO,MAAA,EAAW,OAAO,MAAA;AACxC,EAAA,OAAO,OAAO,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AACvC;AAMO,IAAM,cAAA,GAA0C;AAAA,EACrD,EAAE,IAAI,KAAA,EAAO,KAAA,EAAO,kBAAkB,IAAA,EAAM,IAAA,EAAM,SAAS,SAAA,EAAU;AAAA,EACrE,EAAE,IAAI,YAAA,EAAc,KAAA,EAAO,sBAAsB,IAAA,EAAM,IAAA,EAAM,SAAS,gBAAA,EAAiB;AAAA,EACvF,EAAE,IAAI,MAAA,EAAQ,KAAA,EAAO,cAAc,IAAA,EAAM,IAAA,EAAM,SAAS,MAAA,EAAO;AAAA,EAC/D;AAAA,IACE,EAAA,EAAI,UAAA;AAAA,IACJ,KAAA,EAAO,UAAA;AAAA,IACP,IAAA,EAAM,EAAA;AAAA,IACN,OAAA,EAAS,uCAAA;AAAA,IACT,IAAA,EAAM,CAAC,SAAA,EAAW,IAAI;AAAA,GACxB;AAAA,EACA,EAAE,IAAI,MAAA,EAAQ,KAAA,EAAO,QAAQ,IAAA,EAAM,GAAA,EAAK,SAAS,MAAA,EAAO;AAAA,EACxD,EAAE,IAAI,KAAA,EAAO,KAAA,EAAO,OAAO,IAAA,EAAM,GAAA,EAAK,SAAS,KAAA;AACjD;AC5HA,IAAM,QAAA,GAAW,SAAA;AACjB,IAAM,MAAA,GAAS,SAAA;AACf,IAAM,EAAA,GAAK,SAAA;AACX,IAAM,KAAA,GAAQ,SAAA;AACd,IAAM,MAAA,GAAS,SAAA;AACf,IAAM,QAAA,GAAW,SAAA;AAWV,SAAS,aAAA,CAAc;AAAA,EAC5B,MAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,KAAK,CAAA;AACtC,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,CAAC,CAAA;AAChD,EAAA,MAAM,OAAA,GAAUD,OAAuB,IAAI,CAAA;AAC3C,EAAA,MAAM,SAAS,KAAA,EAAM;AAErB,EAAA,MAAM,QAAA,GAAW,YAAA,CAAa,MAAA,EAAQ,KAAK,CAAA;AAC3C,EAAA,MAAM,aAAA,GAAgB,QAAA,GAAW,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,GAAI,EAAA;AAG5D,EAAAC,UAAU,MAAM;AACd,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,MAAM,UAAA,GAAa,CAAC,CAAA,KAAkB;AACpC,MAAA,IAAI,OAAA,CAAQ,OAAA,IAAW,CAAC,OAAA,CAAQ,OAAA,CAAQ,SAAS,CAAA,CAAE,MAAc,CAAA,EAAG,OAAA,CAAQ,KAAK,CAAA;AAAA,IACnF,CAAA;AACA,IAAA,QAAA,CAAS,gBAAA,CAAiB,aAAa,UAAU,CAAA;AACjD,IAAA,OAAO,MAAM,QAAA,CAAS,mBAAA,CAAoB,WAAA,EAAa,UAAU,CAAA;AAAA,EACnE,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,MAAM,QAAA,GAAW,YAAY,MAAM;AACjC,IAAA,IAAI,QAAA,EAAU;AACd,IAAA,cAAA,CAAe,aAAA,IAAiB,CAAA,GAAI,aAAA,GAAgB,CAAC,CAAA;AACrD,IAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,EACd,CAAA,EAAG,CAAC,QAAA,EAAU,aAAa,CAAC,CAAA;AAE5B,EAAA,MAAM,IAAA,GAAO,WAAA;AAAA,IACX,CAAC,CAAA,KAAc;AACb,MAAA,MAAM,OAAA,GAAU,OAAO,CAAC,CAAA;AACxB,MAAA,IAAI,CAAC,OAAA,EAAS;AACd,MAAA,QAAA,CAAS,OAAA,CAAQ,IAAI,OAAO,CAAA;AAC5B,MAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,IACf,CAAA;AAAA,IACA,CAAC,QAAQ,QAAQ;AAAA,GACnB;AAEA,EAAA,MAAM,eAAA,GAAkB,CAAC,CAAA,KAAqB;AAC5C,IAAA,IAAI,QAAA,EAAU;AACd,IAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,WAAA,IAAe,CAAA,CAAE,GAAA,KAAQ,SAAA,IAAa,CAAA,CAAE,GAAA,KAAQ,OAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,GAAA,EAAK;AACtF,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,QAAA,EAAS;AAAA,IACX;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,aAAA,GAAgB,CAAC,CAAA,KAAqB;AAC1C,IAAA,IAAI,CAAA,CAAE,QAAQ,QAAA,EAAU;AACtB,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,OAAA,CAAQ,KAAK,CAAA;AACb,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAA,CAAE,QAAQ,WAAA,EAAa;AACzB,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,cAAA,CAAe,CAAC,MAAM,IAAA,CAAK,GAAA,CAAI,OAAO,MAAA,GAAS,CAAA,EAAG,CAAA,GAAI,CAAC,CAAC,CAAA;AAAA,IAC1D,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,SAAA,EAAW;AAC9B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,cAAA,CAAe,CAAC,CAAA,KAAM,IAAA,CAAK,IAAI,CAAA,EAAG,CAAA,GAAI,CAAC,CAAC,CAAA;AAAA,IAC1C,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,MAAA,EAAQ;AAC3B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,cAAA,CAAe,CAAC,CAAA;AAAA,IAClB,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,KAAA,EAAO;AAC1B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,cAAA,CAAe,MAAA,CAAO,SAAS,CAAC,CAAA;AAAA,IAClC,WAAW,CAAA,CAAE,GAAA,KAAQ,OAAA,IAAW,CAAA,CAAE,QAAQ,GAAA,EAAK;AAC7C,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,IAAA,CAAK,WAAW,CAAA;AAAA,IAClB;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,WAAA,GAA6B;AAAA,IACjC,OAAA,EAAS,aAAA;AAAA,IACT,UAAA,EAAY,QAAA;AAAA,IACZ,GAAA,EAAK,CAAA;AAAA,IACL,MAAA,EAAQ,EAAA;AAAA,IACR,OAAA,EAAS,OAAA;AAAA,IACT,QAAA,EAAU,EAAA;AAAA,IACV,UAAA,EAAY,CAAA;AAAA,IACZ,UAAA,EAAY,SAAA;AAAA,IACZ,KAAA,EAAO,EAAA;AAAA,IACP,UAAA,EAAY,QAAA;AAAA,IACZ,MAAA,EAAQ,aAAa,MAAM,CAAA,CAAA;AAAA,IAC3B,YAAA,EAAc,CAAA;AAAA,IACd,MAAA,EAAQ,WAAW,SAAA,GAAY,SAAA;AAAA,IAC/B,OAAA,EAAS,WAAW,GAAA,GAAM,CAAA;AAAA,IAC1B,UAAA,EAAY;AAAA,GACd;AAEA,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,OAAA;AAAA,MACL,2BAAA,EAA0B,EAAA;AAAA,MAC1B,qBAAmB,KAAA,IAAS,MAAA;AAAA,MAC5B,SAAA;AAAA,MACA,OAAO,EAAE,QAAA,EAAU,YAAY,OAAA,EAAS,cAAA,EAAgB,GAAG,KAAA,EAAM;AAAA,MAEjE,QAAA,EAAA;AAAA,wBAAA,IAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,eAAA,EAAc,SAAA;AAAA,YACd,eAAA,EAAe,IAAA;AAAA,YACf,YAAA,EAAW,cAAA;AAAA,YACX,QAAA;AAAA,YACA,KAAA,EAAO,WAAA;AAAA,YACP,SAAS,MAAO,IAAA,GAAO,OAAA,CAAQ,KAAK,IAAI,QAAA,EAAS;AAAA,YACjD,SAAA,EAAW,eAAA;AAAA,YAEV,QAAA,EAAA;AAAA,cAAA,QAAA,EAAU,IAAA,mBACT,GAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAY,MAAA,EAAO,KAAA,EAAO,EAAE,KAAA,EAAO,MAAA,EAAO,EAC7C,QAAA,EAAA,QAAA,CAAS,IAAA,EACZ,CAAA,GACE,IAAA;AAAA,8BACJ,GAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,QAAA,EAAU,KAAA,IAAS,cAAA,EAAe,CAAA;AAAA,8BACzC,GAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAY,MAAA,EAAO,KAAA,EAAO,EAAE,KAAA,EAAO,KAAA,EAAO,QAAA,EAAU,CAAA,EAAE,EAAG,QAAA,EAAA,QAAA,EAE/D;AAAA;AAAA;AAAA,SACF;AAAA,QAEC,IAAA,mBACC,GAAA;AAAA,UAAC,IAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,SAAA;AAAA,YACL,EAAA,EAAI,MAAA;AAAA,YACJ,YAAA,EAAW,QAAA;AAAA,YACX,QAAA,EAAU,EAAA;AAAA,YAEV,SAAA,EAAS,IAAA;AAAA,YACT,SAAA,EAAW,aAAA;AAAA,YACX,GAAA,EAAK,CAAC,EAAA,KAAO,EAAA,EAAI,KAAA,EAAM;AAAA,YACvB,KAAA,EAAO;AAAA,cACL,QAAA,EAAU,UAAA;AAAA,cACV,MAAA,EAAQ,EAAA;AAAA,cACR,GAAA,EAAK,kBAAA;AAAA,cACL,IAAA,EAAM,CAAA;AAAA,cACN,QAAA,EAAU,MAAA;AAAA,cACV,MAAA,EAAQ,CAAA;AAAA,cACR,OAAA,EAAS,CAAA;AAAA,cACT,SAAA,EAAW,MAAA;AAAA,cACX,UAAA,EAAY,QAAA;AAAA,cACZ,MAAA,EAAQ,aAAa,MAAM,CAAA,CAAA;AAAA,cAC3B,YAAA,EAAc,CAAA;AAAA,cACd,SAAA,EAAW,6BAAA;AAAA,cACX,OAAA,EAAS;AAAA,aACX;AAAA,YAEC,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,KAAM;AACpB,cAAA,MAAM,UAAA,GAAa,EAAE,EAAA,KAAO,KAAA;AAC5B,cAAA,MAAM,WAAW,CAAA,KAAM,WAAA;AACvB,cAAA,uBACE,IAAA;AAAA,gBAAC,IAAA;AAAA,gBAAA;AAAA,kBAEC,IAAA,EAAK,QAAA;AAAA,kBACL,eAAA,EAAe,UAAA;AAAA,kBACf,iBAAe,CAAA,CAAE,EAAA;AAAA,kBACjB,YAAA,EAAc,MAAM,cAAA,CAAe,CAAC,CAAA;AAAA,kBACpC,OAAA,EAAS,MAAM,IAAA,CAAK,CAAC,CAAA;AAAA,kBACrB,KAAA,EAAO;AAAA,oBACL,OAAA,EAAS,MAAA;AAAA,oBACT,UAAA,EAAY,QAAA;AAAA,oBACZ,GAAA,EAAK,CAAA;AAAA,oBACL,OAAA,EAAS,SAAA;AAAA,oBACT,QAAA,EAAU,EAAA;AAAA,oBACV,YAAA,EAAc,CAAA;AAAA,oBACd,KAAA,EAAO,EAAA;AAAA,oBACP,MAAA,EAAQ,SAAA;AAAA,oBACR,UAAA,EAAY,WAAW,QAAA,GAAW;AAAA,mBACpC;AAAA,kBAEA,QAAA,EAAA;AAAA,oCAAA,GAAA;AAAA,sBAAC,MAAA;AAAA,sBAAA;AAAA,wBACC,aAAA,EAAY,MAAA;AAAA,wBACZ,KAAA,EAAO;AAAA,0BACL,KAAA,EAAO,EAAA;AAAA,0BACP,SAAA,EAAW,QAAA;AAAA,0BACX,KAAA,EAAO,aAAa,MAAA,GAAS;AAAA,yBAC/B;AAAA,wBAEC,YAAE,IAAA,IAAQ;AAAA;AAAA,qBACb;AAAA,oCACA,GAAA,CAAC,UAAK,KAAA,EAAO,EAAE,MAAM,CAAA,EAAE,EAAI,YAAE,KAAA,EAAM,CAAA;AAAA,oBAClC,UAAA,mBACC,GAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAY,MAAA,EAAO,KAAA,EAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,QAAA,EAAU,EAAA,EAAG,EAAG,oBAEjE,CAAA,GACE;AAAA;AAAA,iBAAA;AAAA,gBAjCC,CAAA,CAAE;AAAA,eAkCT;AAAA,YAEJ,CAAC;AAAA;AAAA,SACH,GACE;AAAA;AAAA;AAAA,GACN;AAEJ;;;ACnOO,SAAS,UAAA,CACd,SACA,IAAA,EAC0C;AAC1C,EAAA,IAAI,IAAA,KAAS,SAAS,OAAO,IAAA;AAC7B,EAAA,IAAI,IAAA,CAAK,UAAA,CAAW,OAAO,CAAA,EAAG,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,EAAE;AACvF,EAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,KAAA,EAAO,IAAA,EAAK;AACpC;;;ACHO,SAAS,iBAAA,CACd,MAAA,EACA,OAAA,EACA,EAAA,EACoD;AACpD,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,MAAA,EAAQ,EAAE,CAAA;AACvC,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AACrB,EAAA,OAAO,EAAE,OAAA,EAAS,OAAA,EAAS,OAAA,KAAY,EAAA,EAAG;AAC5C;AAQO,SAAS,iBAAA,CACd,SACA,IAAA,EACsB;AACtB,EAAA,OAAO,EAAE,OAAA,EAAS,OAAA,KAAY,IAAA,EAAK;AACrC;ACEO,IAAM,QAAA,GAAW,UAAA,CAA0C,SAASE,SAAAA,CACzE;AAAA,EACE,MAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA,GAAM,IAAA;AAAA,EACN,QAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,aAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,aAAA;AAAA,EACA,YAAA,GAAe,KAAA;AAAA,EACf,SAAA;AAAA,EACA,KAAA;AAAA,EACA,GAAG;AACL,CAAA,EACA,GAAA,EACA;AACA,EAAA,MAAM,YAAA,GAAeH,OAAuB,IAAI,CAAA;AAGhD,EAAA,MAAM,oBAAoB,WAAA,KAAgB,MAAA;AAC1C,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAII,SAA6B,MAAS,CAAA;AAChF,EAAA,MAAM,OAAA,GAAU,oBAAoB,WAAA,GAAc,aAAA;AAGlD,EAAA,MAAM,gBAAgBJ,MAAAA,CAAO,EAAE,QAAQ,OAAA,EAAS,iBAAA,EAAmB,eAAe,CAAA;AAClF,EAAA,aAAA,CAAc,OAAA,GAAU,EAAE,MAAA,EAAQ,OAAA,EAAS,mBAAmB,aAAA,EAAc;AAE5E,EAAA,MAAM,WAAA,GAAcK,WAAAA,CAAY,CAAC,EAAA,KAAe;AAC9C,IAAA,MAAM,IAAI,aAAA,CAAc,OAAA;AACxB,IAAA,MAAM,WAAW,iBAAA,CAAkB,CAAA,CAAE,MAAA,EAAQ,CAAA,CAAE,SAAS,EAAE,CAAA;AAC1D,IAAA,IAAI,CAAC,QAAA,EAAU;AACf,IAAA,IAAI,CAAC,CAAA,CAAE,iBAAA,EAAmB,gBAAA,CAAiB,EAAE,CAAA;AAC7C,IAAA,CAAA,CAAE,aAAA,GAAgB,EAAA,EAAI,QAAA,CAAS,OAAO,CAAA;AAAA,EACxC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,MAAA,GAAS,YAAY,YAAA,EAAc;AAAA,IACvC,KAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA;AAAA,IACA,GAAA;AAAA,IACA,QAAA;AAAA,IACA,WAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA,UAAA;AAAA;AAAA;AAAA,IAGA,aAAA,EAAe,MAAA,KAAW,MAAA,GAAY,aAAA,GAAgB,MAAA;AAAA,IACtD,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,mBAAA;AAAA,IACE,GAAA;AAAA,IACA,OAAuB;AAAA,MACrB,GAAG,MAAA;AAAA,MACH,QAAA,EAAU,WAAA;AAAA,MACV,QAAA,EAAU,MAAM,aAAA,CAAc,OAAA,CAAQ;AAAA,KACxC,CAAA;AAAA,IACA,CAAC,QAAQ,WAAW;AAAA,GACtB;AAIA,EAAA,MAAM,OAAA,GAAUL,OAAO,EAAE,CAAA;AACzB,EAAAC,UAAU,MAAM;AACd,IAAA,IAAI,WAAW,MAAA,EAAW;AAC1B,IAAA,MAAM,MAAA,GAAS,UAAA,CAAW,OAAA,CAAQ,OAAA,EAAS,MAAM,CAAA;AACjD,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,IAAI,MAAA,CAAO,KAAA,EAAO,MAAA,CAAO,KAAA,EAAM;AAC/B,IAAA,MAAA,CAAO,KAAA,CAAM,OAAO,KAAK,CAAA;AACzB,IAAA,OAAA,CAAQ,OAAA,GAAU,MAAA;AAAA,EACpB,CAAA,EAAG,CAAC,MAAA,EAAQ,MAAM,CAAC,CAAA;AAEnB,EAAA,MAAM,0BACJK,GAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,YAAA;AAAA,MACL,qBAAA,EAAoB,EAAA;AAAA,MACpB,eAAA,EAAe,WAAW,EAAA,GAAK,MAAA;AAAA,MAC/B,6BAA2B,OAAA,IAAW,MAAA;AAAA,MAEtC,OACE,YAAA,IAAgB,MAAA,GACZ,EAAE,KAAA,EAAO,QAAQ,IAAA,EAAM,CAAA,EAAG,SAAA,EAAW,CAAA,KACrC,EAAE,KAAA,EAAO,QAAQ,MAAA,EAAQ,MAAA,EAAQ,GAAG,KAAA,EAAM;AAAA,MAE/C,GAAI,YAAA,IAAgB,MAAA,GAAS,EAAC,GAAI;AAAA;AAAA,GACrC;AAGF,EAAA,IAAI,CAAC,YAAA,IAAgB,CAAC,MAAA,EAAQ,OAAO,OAAA;AAErC,EAAA,uBACEC,IAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,6BAA2B,OAAA,IAAW,MAAA;AAAA,MACtC,SAAA;AAAA,MACA,KAAA,EAAO,EAAE,OAAA,EAAS,MAAA,EAAQ,aAAA,EAAe,QAAA,EAAU,KAAA,EAAO,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,GAAG,KAAA,EAAM;AAAA,MAC1F,GAAG,IAAA;AAAA,MAEJ,QAAA,EAAA;AAAA,wBAAAD,GAAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,KAAA,EAAO;AAAA,cACL,OAAA,EAAS,MAAA;AAAA,cACT,UAAA,EAAY,QAAA;AAAA,cACZ,GAAA,EAAK,CAAA;AAAA,cACL,OAAA,EAAS,SAAA;AAAA,cACT,UAAA,EAAY,SAAA;AAAA,cACZ,YAAA,EAAc;AAAA,aAChB;AAAA,YAEA,QAAA,kBAAAA,GAAAA;AAAA,cAAC,aAAA;AAAA,cAAA;AAAA,gBACC,MAAA;AAAA,gBACA,KAAA,EAAO,OAAA;AAAA,gBACP,QAAA,EAAU,CAAC,EAAA,KAAO,WAAA,CAAY,EAAE,CAAA;AAAA,gBAChC,QAAA,EAAU;AAAA;AAAA;AACZ;AAAA,SACF;AAAA,QACC;AAAA;AAAA;AAAA,GACH;AAEJ,CAAC;AC/FM,SAAS,mBAAmB,OAAA,EAAwD;AACzF,EAAA,MAAM,OAAA,GAAU,QAAQ,OAAA,IAAW,EAAA;AACnC,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIF,SAAS,OAAO,CAAA;AAI5C,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAIA,QAAAA,CAA6B,QAAQ,KAAK,CAAA;AACpF,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,KAAU,MAAA,GAAY,QAAQ,KAAA,GAAQ,aAAA;AAE5D,EAAA,MAAM,YAAA,GAAeJ,MAAAA,CAAO,OAAA,CAAQ,SAAS,CAAA;AAC7C,EAAA,YAAA,CAAa,UAAU,OAAA,CAAQ,SAAA;AAC/B,EAAA,MAAM,UAAA,GAAaA,OAAO,OAAO,CAAA;AACjC,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAErB,EAAAC,UAAU,MAAM;AACd,IAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,OAAA,CAAQ,SAAA,CAAU,CAAC,KAAA,KAAU,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,GAAI,KAAK,CAAC,CAAA;AACnF,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,OAAO,KAAA,KAAU,UAAA,EAAY,KAAA,EAAM;AAAA,IACzC,CAAA;AAAA,EAEF,CAAA,EAAG,EAAE,CAAA;AAIL,EAAA,MAAM,cAAA,GAAiBD,OAA2B,MAAS,CAAA;AAC3D,EAAA,MAAM,OAAA,GAAUA,OAAO,KAAK,CAAA;AAC5B,EAAAC,UAAU,MAAM;AAEd,IAAA,IAAI,CAAC,QAAQ,OAAA,EAAS;AACpB,MAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAClB,MAAA,cAAA,CAAe,OAAA,GAAU,KAAA;AACzB,MAAA,KAAK,YAAA,CAAa,OAAA,CAAQ,OAAA,GAAU,KAAK,CAAA;AACzC,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAC,iBAAA,CAAkB,cAAA,CAAe,OAAA,EAAS,KAAK,EAAE,OAAA,EAAS;AAC/D,IAAA,cAAA,CAAe,OAAA,GAAU,KAAA;AACzB,IAAA,SAAA,CAAU,WAAW,OAAO,CAAA;AAC5B,IAAA,KAAK,YAAA,CAAa,OAAA,CAAQ,OAAA,GAAU,KAAK,CAAA;AAAA,EAC3C,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,MAAM,QAAA,GAAWI,WAAAA,CAAY,CAAC,IAAA,KAAiB;AAC7C,IAAA,KAAK,YAAA,CAAa,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA;AAAA,EACrC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,MAAA,GAASA,WAAAA,CAAY,CAAC,KAAA,KAAkB,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,GAAI,KAAK,CAAA,EAAG,EAAE,CAAA;AAC7E,EAAA,MAAM,QAAQA,WAAAA,CAAY,MAAM,UAAU,EAAE,CAAA,EAAG,EAAE,CAAA;AAEjD,EAAA,MAAM,WAAA,GAAcA,WAAAA,CAAY,CAAC,EAAA,KAA2B;AAI1D,IAAA,gBAAA,CAAiB,CAAC,GAAA,KAAS,GAAA,KAAQ,EAAA,GAAK,MAAM,EAAG,CAAA;AACjD,IAAA,IAAI,CAAC,iBAAA,CAAkB,cAAA,CAAe,OAAA,EAAS,EAAE,EAAE,OAAA,EAAS;AAC5D,IAAA,cAAA,CAAe,OAAA,GAAU,EAAA;AACzB,IAAA,SAAA,CAAU,WAAW,OAAO,CAAA;AAC5B,IAAA,KAAK,YAAA,CAAa,OAAA,CAAQ,OAAA,GAAU,EAAE,CAAA;AAAA,EACxC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,EAAE,MAAA,EAAQ,QAAA,EAAU,MAAA,EAAQ,KAAA,EAAO,OAAO,WAAA,EAAY;AAC/D","file":"index.js","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 const addon = fitRef.current;\n if (!addon) return;\n try {\n // Guard against an unmeasurable container / not-yet-rendered viewport:\n // proposeDimensions() returns undefined (or NaN) then, and fit() would\n // call xterm's resize(undefined,…), which console-errors \"cols must be\n // numeric\". Skip — the ResizeObserver fires again once it's measurable.\n // This matters most under `showShellBar`, whose extra layout pass races\n // the first fit.\n const dims = addon.proposeDimensions();\n if (\n !dims ||\n !Number.isFinite(dims.cols) ||\n !Number.isFinite(dims.rows) ||\n dims.cols < 1 ||\n dims.rows < 1\n ) {\n return;\n }\n addon.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 // Route through the guarded handle.fit() (proposeDimensions check) so a\n // not-yet-laid-out container never triggers an xterm resize(undefined).\n if (o.fit ?? true) handle.fit();\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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/fancy-term",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Human+ Terminal for React — a controlled, themeable <Terminal> wrapping xterm.js, with hooks and an MCP-bridgeable surface so embedded agents read the buffer, write input, and run commands without DOM-scraping.",
5
5
  "repository": {
6
6
  "type": "git",