spawn-term 3.0.4 → 3.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/cjs/components/App.js +28 -56
  2. package/dist/cjs/components/App.js.map +1 -1
  3. package/dist/cjs/components/ErrorFooter.js +120 -0
  4. package/dist/cjs/components/ErrorFooter.js.map +1 -0
  5. package/dist/cjs/components/ExpandedOutput.js +0 -2
  6. package/dist/cjs/components/ExpandedOutput.js.map +1 -1
  7. package/dist/cjs/components/StatusBar.js +22 -33
  8. package/dist/cjs/components/StatusBar.js.map +1 -1
  9. package/dist/cjs/index-esm.js +4 -0
  10. package/dist/cjs/index-esm.js.map +1 -1
  11. package/dist/cjs/lib/TerminalBuffer.js +139 -0
  12. package/dist/cjs/lib/TerminalBuffer.js.map +1 -0
  13. package/dist/cjs/session.js +129 -56
  14. package/dist/cjs/session.js.map +1 -1
  15. package/dist/cjs/src/components/ErrorFooter.d.ts +11 -0
  16. package/dist/cjs/src/index-esm.d.ts +1 -0
  17. package/dist/cjs/src/lib/TerminalBuffer.d.ts +32 -0
  18. package/dist/cjs/src/state/processStore.d.ts +14 -7
  19. package/dist/cjs/src/types.d.ts +2 -0
  20. package/dist/cjs/state/processStore.js +79 -26
  21. package/dist/cjs/state/processStore.js.map +1 -1
  22. package/dist/cjs/types.js.map +1 -1
  23. package/dist/esm/components/App.js +28 -56
  24. package/dist/esm/components/App.js.map +1 -1
  25. package/dist/esm/components/ErrorFooter.js +95 -0
  26. package/dist/esm/components/ErrorFooter.js.map +1 -0
  27. package/dist/esm/components/ExpandedOutput.js +0 -2
  28. package/dist/esm/components/ExpandedOutput.js.map +1 -1
  29. package/dist/esm/components/StatusBar.js +22 -33
  30. package/dist/esm/components/StatusBar.js.map +1 -1
  31. package/dist/esm/index-esm.js +1 -0
  32. package/dist/esm/index-esm.js.map +1 -1
  33. package/dist/esm/lib/TerminalBuffer.js +62 -0
  34. package/dist/esm/lib/TerminalBuffer.js.map +1 -0
  35. package/dist/esm/session.js +112 -35
  36. package/dist/esm/session.js.map +1 -1
  37. package/dist/esm/src/components/ErrorFooter.d.ts +11 -0
  38. package/dist/esm/src/index-esm.d.ts +1 -0
  39. package/dist/esm/src/lib/TerminalBuffer.d.ts +32 -0
  40. package/dist/esm/src/state/processStore.d.ts +14 -7
  41. package/dist/esm/src/types.d.ts +2 -0
  42. package/dist/esm/state/processStore.js +53 -22
  43. package/dist/esm/state/processStore.js.map +1 -1
  44. package/dist/esm/types.js.map +1 -1
  45. package/package.json +2 -1
  46. package/dist/cjs/components/ErrorDetailModal.js +0 -115
  47. package/dist/cjs/components/ErrorDetailModal.js.map +0 -1
  48. package/dist/cjs/components/ErrorListModal.js +0 -135
  49. package/dist/cjs/components/ErrorListModal.js.map +0 -1
  50. package/dist/cjs/src/components/ErrorDetailModal.d.ts +0 -8
  51. package/dist/cjs/src/components/ErrorListModal.d.ts +0 -8
  52. package/dist/esm/components/ErrorDetailModal.js +0 -99
  53. package/dist/esm/components/ErrorDetailModal.js.map +0 -1
  54. package/dist/esm/components/ErrorListModal.js +0 -116
  55. package/dist/esm/components/ErrorListModal.js.map +0 -1
  56. package/dist/esm/src/components/ErrorDetailModal.d.ts +0 -8
  57. package/dist/esm/src/components/ErrorListModal.d.ts +0 -8
@@ -2,7 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import { memo } from 'react';
4
4
  import { EXPANDED_MAX_VISIBLE_LINES } from '../constants.js';
5
- import { LineType } from '../types.js';
6
5
  export default /*#__PURE__*/ memo(function ExpandedOutput({ lines, scrollOffset, maxVisible = EXPANDED_MAX_VISIBLE_LINES }) {
7
6
  const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);
8
7
  const hasMore = lines.length > scrollOffset + maxVisible;
@@ -22,7 +21,6 @@ export default /*#__PURE__*/ memo(function ExpandedOutput({ lines, scrollOffset,
22
21
  children: [
23
22
  visibleLines.map((line, i)=>// biome-ignore lint/suspicious/noArrayIndexKey: Lines have no unique ID, index is stable for this scrolling view
24
23
  /*#__PURE__*/ _jsxs(Text, {
25
- color: line.type === LineType.stderr ? 'red' : undefined,
26
24
  children: [
27
25
  "│ ",
28
26
  line.text
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/ExpandedOutput.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport { EXPANDED_MAX_VISIBLE_LINES } from '../constants.ts';\nimport type { Line } from '../types.ts';\nimport { LineType } from '../types.ts';\n\ntype Props = {\n lines: Line[];\n scrollOffset: number;\n maxVisible?: number;\n};\n\nexport default memo(function ExpandedOutput({ lines, scrollOffset, maxVisible = EXPANDED_MAX_VISIBLE_LINES }: Props) {\n const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);\n const hasMore = lines.length > scrollOffset + maxVisible;\n const remaining = lines.length - scrollOffset - maxVisible;\n\n if (lines.length === 0) {\n return (\n <Box paddingLeft={2}>\n <Text dimColor>│ (no output)</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n {visibleLines.map((line, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: Lines have no unique ID, index is stable for this scrolling view\n <Text key={scrollOffset + i} color={line.type === LineType.stderr ? 'red' : undefined}>\n │ {line.text}\n </Text>\n ))}\n {hasMore && <Text dimColor>│ [+{remaining} more, j/k to scroll]</Text>}\n </Box>\n );\n});\n"],"names":["Box","Text","memo","EXPANDED_MAX_VISIBLE_LINES","LineType","ExpandedOutput","lines","scrollOffset","maxVisible","visibleLines","slice","hasMore","length","remaining","paddingLeft","dimColor","flexDirection","map","line","i","color","type","stderr","undefined","text"],"mappings":";AAAA,SAASA,GAAG,EAAEC,IAAI,QAAQ,MAAM;AAChC,SAASC,IAAI,QAAQ,QAAQ;AAC7B,SAASC,0BAA0B,QAAQ,kBAAkB;AAE7D,SAASC,QAAQ,QAAQ,cAAc;AAQvC,6BAAeF,KAAK,SAASG,eAAe,EAAEC,KAAK,EAAEC,YAAY,EAAEC,aAAaL,0BAA0B,EAAS;IACjH,MAAMM,eAAeH,MAAMI,KAAK,CAACH,cAAcA,eAAeC;IAC9D,MAAMG,UAAUL,MAAMM,MAAM,GAAGL,eAAeC;IAC9C,MAAMK,YAAYP,MAAMM,MAAM,GAAGL,eAAeC;IAEhD,IAAIF,MAAMM,MAAM,KAAK,GAAG;QACtB,qBACE,KAACZ;YAAIc,aAAa;sBAChB,cAAA,KAACb;gBAAKc,QAAQ;0BAAC;;;IAGrB;IAEA,qBACE,MAACf;QAAIgB,eAAc;QAASF,aAAa;;YACtCL,aAAaQ,GAAG,CAAC,CAACC,MAAMC,IACvB,iHAAiH;8BACjH,MAAClB;oBAA4BmB,OAAOF,KAAKG,IAAI,KAAKjB,SAASkB,MAAM,GAAG,QAAQC;;wBAAW;wBAClFL,KAAKM,IAAI;;mBADHjB,eAAeY;YAI3BR,yBAAW,MAACV;gBAAKc,QAAQ;;oBAAC;oBAAKF;oBAAU;;;;;AAGhD,GAAG"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/ExpandedOutput.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport { EXPANDED_MAX_VISIBLE_LINES } from '../constants.ts';\nimport type { Line } from '../types.ts';\n\ntype Props = {\n lines: Line[];\n scrollOffset: number;\n maxVisible?: number;\n};\n\nexport default memo(function ExpandedOutput({ lines, scrollOffset, maxVisible = EXPANDED_MAX_VISIBLE_LINES }: Props) {\n const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);\n const hasMore = lines.length > scrollOffset + maxVisible;\n const remaining = lines.length - scrollOffset - maxVisible;\n\n if (lines.length === 0) {\n return (\n <Box paddingLeft={2}>\n <Text dimColor>│ (no output)</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n {visibleLines.map((line, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: Lines have no unique ID, index is stable for this scrolling view\n <Text key={scrollOffset + i}>│ {line.text}</Text>\n ))}\n {hasMore && <Text dimColor>│ [+{remaining} more, j/k to scroll]</Text>}\n </Box>\n );\n});\n"],"names":["Box","Text","memo","EXPANDED_MAX_VISIBLE_LINES","ExpandedOutput","lines","scrollOffset","maxVisible","visibleLines","slice","hasMore","length","remaining","paddingLeft","dimColor","flexDirection","map","line","i","text"],"mappings":";AAAA,SAASA,GAAG,EAAEC,IAAI,QAAQ,MAAM;AAChC,SAASC,IAAI,QAAQ,QAAQ;AAC7B,SAASC,0BAA0B,QAAQ,kBAAkB;AAS7D,6BAAeD,KAAK,SAASE,eAAe,EAAEC,KAAK,EAAEC,YAAY,EAAEC,aAAaJ,0BAA0B,EAAS;IACjH,MAAMK,eAAeH,MAAMI,KAAK,CAACH,cAAcA,eAAeC;IAC9D,MAAMG,UAAUL,MAAMM,MAAM,GAAGL,eAAeC;IAC9C,MAAMK,YAAYP,MAAMM,MAAM,GAAGL,eAAeC;IAEhD,IAAIF,MAAMM,MAAM,KAAK,GAAG;QACtB,qBACE,KAACX;YAAIa,aAAa;sBAChB,cAAA,KAACZ;gBAAKa,QAAQ;0BAAC;;;IAGrB;IAEA,qBACE,MAACd;QAAIe,eAAc;QAASF,aAAa;;YACtCL,aAAaQ,GAAG,CAAC,CAACC,MAAMC,IACvB,iHAAiH;8BACjH,MAACjB;;wBAA4B;wBAAGgB,KAAKE,IAAI;;mBAA9Bb,eAAeY;YAE3BR,yBAAW,MAACT;gBAAKa,QAAQ;;oBAAC;oBAAKF;oBAAU;;;;;AAGhD,GAAG"}
@@ -33,40 +33,29 @@ import { SPINNER } from '../constants.js';
33
33
  import figures from '../lib/figures.js';
34
34
  import Spinner from './Spinner.js';
35
35
  export default /*#__PURE__*/ memo(function StatusBar({ running, done, errors, errorLines }) {
36
- return /*#__PURE__*/ _jsxs(Box, {
37
- justifyContent: "space-between",
38
- children: [
39
- /*#__PURE__*/ _jsx(Box, {
40
- children: /*#__PURE__*/ _jsxs(Text, {
41
- children: [
42
- running > 0 ? /*#__PURE__*/ _jsx(Spinner, _object_spread({}, SPINNER)) : /*#__PURE__*/ _jsx(Text, {
43
- color: "green",
44
- children: figures.tick
45
- }),
46
- ` Running: ${running} | `,
47
- /*#__PURE__*/ _jsx(Text, {
48
- color: "green",
49
- children: figures.tick
50
- }),
51
- ` Done: ${done} | `,
52
- /*#__PURE__*/ _jsx(Text, {
53
- color: "red",
54
- children: figures.cross
55
- }),
56
- ` Errors: ${errors}`,
57
- errorLines > 0 && /*#__PURE__*/ _jsx(Text, {
58
- dimColor: true,
59
- children: ` (${errorLines} lines)`
60
- })
61
- ]
62
- })
63
- }),
64
- errors > 0 && /*#__PURE__*/ _jsx(Box, {
65
- children: /*#__PURE__*/ _jsx(Text, {
36
+ return /*#__PURE__*/ _jsx(Box, {
37
+ children: /*#__PURE__*/ _jsxs(Text, {
38
+ children: [
39
+ running > 0 ? /*#__PURE__*/ _jsx(Spinner, _object_spread({}, SPINNER)) : /*#__PURE__*/ _jsx(Text, {
40
+ color: "green",
41
+ children: figures.tick
42
+ }),
43
+ ` Running: ${running} | `,
44
+ /*#__PURE__*/ _jsx(Text, {
45
+ color: "green",
46
+ children: figures.tick
47
+ }),
48
+ ` Done: ${done} | `,
49
+ /*#__PURE__*/ _jsx(Text, {
50
+ color: "red",
51
+ children: figures.cross
52
+ }),
53
+ ` Errors: ${errors}`,
54
+ errorLines > 0 && /*#__PURE__*/ _jsx(Text, {
66
55
  dimColor: true,
67
- children: "[e]rrors"
56
+ children: ` (${errorLines} lines)`
68
57
  })
69
- })
70
- ]
58
+ ]
59
+ })
71
60
  });
72
61
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/StatusBar.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport { SPINNER } from '../constants.ts';\nimport figures from '../lib/figures.ts';\nimport Spinner from './Spinner.ts';\n\ntype Props = {\n running: number;\n done: number;\n errors: number;\n errorLines: number;\n};\n\nexport default memo(function StatusBar({ running, done, errors, errorLines }: Props) {\n return (\n <Box justifyContent=\"space-between\">\n <Box>\n <Text>\n {running > 0 ? <Spinner {...SPINNER} /> : <Text color=\"green\">{figures.tick}</Text>}\n {` Running: ${running} | `}\n <Text color=\"green\">{figures.tick}</Text>\n {` Done: ${done} | `}\n <Text color=\"red\">{figures.cross}</Text>\n {` Errors: ${errors}`}\n {errorLines > 0 && <Text dimColor>{` (${errorLines} lines)`}</Text>}\n </Text>\n </Box>\n {errors > 0 && (\n <Box>\n <Text dimColor>[e]rrors</Text>\n </Box>\n )}\n </Box>\n );\n});\n"],"names":["Box","Text","memo","SPINNER","figures","Spinner","StatusBar","running","done","errors","errorLines","justifyContent","color","tick","cross","dimColor"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAASA,GAAG,EAAEC,IAAI,QAAQ,MAAM;AAChC,SAASC,IAAI,QAAQ,QAAQ;AAC7B,SAASC,OAAO,QAAQ,kBAAkB;AAC1C,OAAOC,aAAa,oBAAoB;AACxC,OAAOC,aAAa,eAAe;AASnC,6BAAeH,KAAK,SAASI,UAAU,EAAEC,OAAO,EAAEC,IAAI,EAAEC,MAAM,EAAEC,UAAU,EAAS;IACjF,qBACE,MAACV;QAAIW,gBAAe;;0BAClB,KAACX;0BACC,cAAA,MAACC;;wBACEM,UAAU,kBAAI,KAACF,4BAAYF,0BAAc,KAACF;4BAAKW,OAAM;sCAASR,QAAQS,IAAI;;wBAC1E,CAAC,UAAU,EAAEN,QAAQ,IAAI,CAAC;sCAC3B,KAACN;4BAAKW,OAAM;sCAASR,QAAQS,IAAI;;wBAChC,CAAC,OAAO,EAAEL,KAAK,IAAI,CAAC;sCACrB,KAACP;4BAAKW,OAAM;sCAAOR,QAAQU,KAAK;;wBAC/B,CAAC,SAAS,EAAEL,QAAQ;wBACpBC,aAAa,mBAAK,KAACT;4BAAKc,QAAQ;sCAAE,CAAC,EAAE,EAAEL,WAAW,OAAO,CAAC;;;;;YAG9DD,SAAS,mBACR,KAACT;0BACC,cAAA,KAACC;oBAAKc,QAAQ;8BAAC;;;;;AAKzB,GAAG"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/StatusBar.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport { SPINNER } from '../constants.ts';\nimport figures from '../lib/figures.ts';\nimport Spinner from './Spinner.ts';\n\ntype Props = {\n running: number;\n done: number;\n errors: number;\n errorLines: number;\n};\n\nexport default memo(function StatusBar({ running, done, errors, errorLines }: Props) {\n return (\n <Box>\n <Text>\n {running > 0 ? <Spinner {...SPINNER} /> : <Text color=\"green\">{figures.tick}</Text>}\n {` Running: ${running} | `}\n <Text color=\"green\">{figures.tick}</Text>\n {` Done: ${done} | `}\n <Text color=\"red\">{figures.cross}</Text>\n {` Errors: ${errors}`}\n {errorLines > 0 && <Text dimColor>{` (${errorLines} lines)`}</Text>}\n </Text>\n </Box>\n );\n});\n"],"names":["Box","Text","memo","SPINNER","figures","Spinner","StatusBar","running","done","errors","errorLines","color","tick","cross","dimColor"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAASA,GAAG,EAAEC,IAAI,QAAQ,MAAM;AAChC,SAASC,IAAI,QAAQ,QAAQ;AAC7B,SAASC,OAAO,QAAQ,kBAAkB;AAC1C,OAAOC,aAAa,oBAAoB;AACxC,OAAOC,aAAa,eAAe;AASnC,6BAAeH,KAAK,SAASI,UAAU,EAAEC,OAAO,EAAEC,IAAI,EAAEC,MAAM,EAAEC,UAAU,EAAS;IACjF,qBACE,KAACV;kBACC,cAAA,MAACC;;gBACEM,UAAU,kBAAI,KAACF,4BAAYF,0BAAc,KAACF;oBAAKU,OAAM;8BAASP,QAAQQ,IAAI;;gBAC1E,CAAC,UAAU,EAAEL,QAAQ,IAAI,CAAC;8BAC3B,KAACN;oBAAKU,OAAM;8BAASP,QAAQQ,IAAI;;gBAChC,CAAC,OAAO,EAAEJ,KAAK,IAAI,CAAC;8BACrB,KAACP;oBAAKU,OAAM;8BAAOP,QAAQS,KAAK;;gBAC/B,CAAC,SAAS,EAAEJ,QAAQ;gBACpBC,aAAa,mBAAK,KAACT;oBAAKa,QAAQ;8BAAE,CAAC,EAAE,EAAEJ,WAAW,OAAO,CAAC;;;;;AAInE,GAAG"}
@@ -1,5 +1,6 @@
1
1
  export { default as figures } from './lib/figures.js';
2
2
  export { default as formatArguments } from './lib/formatArguments.js';
3
+ export { TerminalBuffer } from './lib/TerminalBuffer.js';
3
4
  export * from './types.js';
4
5
  const major = +process.versions.node.split('.')[0];
5
6
  import { createSession as createSessionImpl } from './createSessionWrapper.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/index-esm.ts"],"sourcesContent":["export { default as figures } from './lib/figures.ts';\nexport { default as formatArguments } from './lib/formatArguments.ts';\nexport * from './types.ts';\n\nimport type { createSession as createSessionType, Session } from './createSessionWrapper.ts';\nexport type { Session };\n\nconst major = +process.versions.node.split('.')[0];\n\nimport { createSession as createSessionImpl } from './createSessionWrapper.ts';\nexport const createSession = major > 18 ? createSessionImpl : (undefined as typeof createSessionType);\n"],"names":["default","figures","formatArguments","major","process","versions","node","split","createSession","createSessionImpl","undefined"],"mappings":"AAAA,SAASA,WAAWC,OAAO,QAAQ,mBAAmB;AACtD,SAASD,WAAWE,eAAe,QAAQ,2BAA2B;AACtE,cAAc,aAAa;AAK3B,MAAMC,QAAQ,CAACC,QAAQC,QAAQ,CAACC,IAAI,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE;AAElD,SAASC,iBAAiBC,iBAAiB,QAAQ,4BAA4B;AAC/E,OAAO,MAAMD,gBAAgBL,QAAQ,KAAKM,oBAAqBC,UAAuC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/index-esm.ts"],"sourcesContent":["export { default as figures } from './lib/figures.ts';\nexport { default as formatArguments } from './lib/formatArguments.ts';\nexport { TerminalBuffer } from './lib/TerminalBuffer.ts';\nexport * from './types.ts';\n\nimport type { createSession as createSessionType, Session } from './createSessionWrapper.ts';\nexport type { Session };\n\nconst major = +process.versions.node.split('.')[0];\n\nimport { createSession as createSessionImpl } from './createSessionWrapper.ts';\nexport const createSession = major > 18 ? createSessionImpl : (undefined as typeof createSessionType);\n"],"names":["default","figures","formatArguments","TerminalBuffer","major","process","versions","node","split","createSession","createSessionImpl","undefined"],"mappings":"AAAA,SAASA,WAAWC,OAAO,QAAQ,mBAAmB;AACtD,SAASD,WAAWE,eAAe,QAAQ,2BAA2B;AACtE,SAASC,cAAc,QAAQ,0BAA0B;AACzD,cAAc,aAAa;AAK3B,MAAMC,QAAQ,CAACC,QAAQC,QAAQ,CAACC,IAAI,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE;AAElD,SAASC,iBAAiBC,iBAAiB,QAAQ,4BAA4B;AAC/E,OAAO,MAAMD,gBAAgBL,QAAQ,KAAKM,oBAAqBC,UAAuC"}
@@ -0,0 +1,62 @@
1
+ var _xterm_default;
2
+ import * as xterm from '@xterm/headless';
3
+ // Handle both ESM and CJS module formats
4
+ const Terminal = xterm.Terminal || ((_xterm_default = xterm.default) === null || _xterm_default === void 0 ? void 0 : _xterm_default.Terminal);
5
+ /**
6
+ * Wrapper around @xterm/headless Terminal that provides a virtual terminal buffer.
7
+ * Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce
8
+ * the actual rendered output rather than raw intermediate states.
9
+ */ export class TerminalBuffer {
10
+ /**
11
+ * Write raw data to the terminal buffer.
12
+ * The terminal interprets all ANSI sequences automatically.
13
+ */ write(data) {
14
+ const str = typeof data === 'string' ? data : data.toString('utf8');
15
+ this.terminal.write(str);
16
+ }
17
+ /**
18
+ * Resize the terminal width.
19
+ */ resize(cols) {
20
+ this.terminal.resize(cols, this.terminal.rows);
21
+ }
22
+ /**
23
+ * Extract the rendered lines from the terminal buffer.
24
+ * This returns the actual visible content after all ANSI sequences
25
+ * have been processed.
26
+ */ getLines() {
27
+ const buffer = this.terminal.buffer.active;
28
+ const lines = [];
29
+ for(let i = 0; i < buffer.length; i++){
30
+ const line = buffer.getLine(i);
31
+ if (line) {
32
+ // translateToString(trimRight) - trim trailing whitespace
33
+ // Also trim leading whitespace - tools like ncu/npm use cursor positioning
34
+ // which creates lines with leading spaces when interpreted by xterm
35
+ lines.push(line.translateToString(true).trimStart());
36
+ }
37
+ }
38
+ // Trim trailing empty lines
39
+ while(lines.length > 0 && lines[lines.length - 1] === ''){
40
+ lines.pop();
41
+ }
42
+ return lines;
43
+ }
44
+ /**
45
+ * Get the number of rendered lines.
46
+ */ get lineCount() {
47
+ return this.getLines().length;
48
+ }
49
+ /**
50
+ * Clean up terminal resources.
51
+ */ dispose() {
52
+ this.terminal.dispose();
53
+ }
54
+ constructor(cols, scrollback = 10000){
55
+ this.terminal = new Terminal({
56
+ cols,
57
+ rows: 50,
58
+ scrollback,
59
+ allowProposedApi: true
60
+ });
61
+ }
62
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/TerminalBuffer.ts"],"sourcesContent":["import * as xterm from '@xterm/headless';\n\n// Handle both ESM and CJS module formats\nconst Terminal = (xterm as { Terminal: typeof xterm.Terminal; default?: { Terminal: typeof xterm.Terminal } }).Terminal || (xterm as { default?: { Terminal: typeof xterm.Terminal } }).default?.Terminal;\n\n/**\n * Wrapper around @xterm/headless Terminal that provides a virtual terminal buffer.\n * Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce\n * the actual rendered output rather than raw intermediate states.\n */\nexport class TerminalBuffer {\n private terminal: InstanceType<typeof Terminal>;\n\n constructor(cols: number, scrollback = 10000) {\n this.terminal = new Terminal({\n cols,\n rows: 50, // Visible rows (doesn't matter much for headless)\n scrollback,\n allowProposedApi: true,\n });\n }\n\n /**\n * Write raw data to the terminal buffer.\n * The terminal interprets all ANSI sequences automatically.\n */\n write(data: string | Buffer): void {\n const str = typeof data === 'string' ? data : data.toString('utf8');\n this.terminal.write(str);\n }\n\n /**\n * Resize the terminal width.\n */\n resize(cols: number): void {\n this.terminal.resize(cols, this.terminal.rows);\n }\n\n /**\n * Extract the rendered lines from the terminal buffer.\n * This returns the actual visible content after all ANSI sequences\n * have been processed.\n */\n getLines(): string[] {\n const buffer = this.terminal.buffer.active;\n const lines: string[] = [];\n\n for (let i = 0; i < buffer.length; i++) {\n const line = buffer.getLine(i);\n if (line) {\n // translateToString(trimRight) - trim trailing whitespace\n // Also trim leading whitespace - tools like ncu/npm use cursor positioning\n // which creates lines with leading spaces when interpreted by xterm\n lines.push(line.translateToString(true).trimStart());\n }\n }\n\n // Trim trailing empty lines\n while (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n return lines;\n }\n\n /**\n * Get the number of rendered lines.\n */\n get lineCount(): number {\n return this.getLines().length;\n }\n\n /**\n * Clean up terminal resources.\n */\n dispose(): void {\n this.terminal.dispose();\n }\n}\n"],"names":["xterm","Terminal","default","TerminalBuffer","write","data","str","toString","terminal","resize","cols","rows","getLines","buffer","active","lines","i","length","line","getLine","push","translateToString","trimStart","pop","lineCount","dispose","scrollback","allowProposedApi"],"mappings":"IAG2H;AAH3H,YAAYA,WAAW,kBAAkB;AAEzC,yCAAyC;AACzC,MAAMC,WAAW,AAACD,MAA6FC,QAAQ,MAAI,iBAAA,AAACD,MAA4DE,OAAO,cAApE,qCAAA,eAAsED,QAAQ;AAEzM;;;;CAIC,GACD,OAAO,MAAME;IAYX;;;GAGC,GACDC,MAAMC,IAAqB,EAAQ;QACjC,MAAMC,MAAM,OAAOD,SAAS,WAAWA,OAAOA,KAAKE,QAAQ,CAAC;QAC5D,IAAI,CAACC,QAAQ,CAACJ,KAAK,CAACE;IACtB;IAEA;;GAEC,GACDG,OAAOC,IAAY,EAAQ;QACzB,IAAI,CAACF,QAAQ,CAACC,MAAM,CAACC,MAAM,IAAI,CAACF,QAAQ,CAACG,IAAI;IAC/C;IAEA;;;;GAIC,GACDC,WAAqB;QACnB,MAAMC,SAAS,IAAI,CAACL,QAAQ,CAACK,MAAM,CAACC,MAAM;QAC1C,MAAMC,QAAkB,EAAE;QAE1B,IAAK,IAAIC,IAAI,GAAGA,IAAIH,OAAOI,MAAM,EAAED,IAAK;YACtC,MAAME,OAAOL,OAAOM,OAAO,CAACH;YAC5B,IAAIE,MAAM;gBACR,0DAA0D;gBAC1D,2EAA2E;gBAC3E,oEAAoE;gBACpEH,MAAMK,IAAI,CAACF,KAAKG,iBAAiB,CAAC,MAAMC,SAAS;YACnD;QACF;QAEA,4BAA4B;QAC5B,MAAOP,MAAME,MAAM,GAAG,KAAKF,KAAK,CAACA,MAAME,MAAM,GAAG,EAAE,KAAK,GAAI;YACzDF,MAAMQ,GAAG;QACX;QAEA,OAAOR;IACT;IAEA;;GAEC,GACD,IAAIS,YAAoB;QACtB,OAAO,IAAI,CAACZ,QAAQ,GAAGK,MAAM;IAC/B;IAEA;;GAEC,GACDQ,UAAgB;QACd,IAAI,CAACjB,QAAQ,CAACiB,OAAO;IACvB;IAhEA,YAAYf,IAAY,EAAEgB,aAAa,KAAK,CAAE;QAC5C,IAAI,CAAClB,QAAQ,GAAG,IAAIP,SAAS;YAC3BS;YACAC,MAAM;YACNe;YACAC,kBAAkB;QACpB;IACF;AA0DF"}
@@ -1,3 +1,55 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ function _object_spread(target) {
15
+ for(var i = 1; i < arguments.length; i++){
16
+ var source = arguments[i] != null ? arguments[i] : {};
17
+ var ownKeys = Object.keys(source);
18
+ if (typeof Object.getOwnPropertySymbols === "function") {
19
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
20
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
21
+ }));
22
+ }
23
+ ownKeys.forEach(function(key) {
24
+ _define_property(target, key, source[key]);
25
+ });
26
+ }
27
+ return target;
28
+ }
29
+ function ownKeys(object, enumerableOnly) {
30
+ var keys = Object.keys(object);
31
+ if (Object.getOwnPropertySymbols) {
32
+ var symbols = Object.getOwnPropertySymbols(object);
33
+ if (enumerableOnly) {
34
+ symbols = symbols.filter(function(sym) {
35
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
36
+ });
37
+ }
38
+ keys.push.apply(keys, symbols);
39
+ }
40
+ return keys;
41
+ }
42
+ function _object_spread_props(target, source) {
43
+ source = source != null ? source : {};
44
+ if (Object.getOwnPropertyDescriptors) {
45
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
46
+ } else {
47
+ ownKeys(Object(source)).forEach(function(key) {
48
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
49
+ });
50
+ }
51
+ return target;
52
+ }
1
53
  function _object_without_properties(source, excluded) {
2
54
  if (source == null) return {};
3
55
  var target = _object_without_properties_loose(source, excluded);
@@ -33,11 +85,10 @@ import oo from 'on-one';
33
85
  import Queue from 'queue-cb';
34
86
  import App from './components/App.js';
35
87
  import { DEFAULT_MAX_FPS } from './constants.js';
36
- import addLines from './lib/addLines.js';
37
88
  import concatWritable from './lib/concatWritable.js';
38
89
  import formatArguments from './lib/formatArguments.js';
90
+ import { TerminalBuffer } from './lib/TerminalBuffer.js';
39
91
  import { ProcessStore } from './state/processStore.js';
40
- import { LineType } from './types.js';
41
92
  class SessionImpl {
42
93
  spawn(command, args, spawnOptions, options, callback) {
43
94
  if (this.closed) {
@@ -48,8 +99,28 @@ class SessionImpl {
48
99
  "stdio"
49
100
  ]);
50
101
  if (stdio === 'inherit') {
102
+ // When Ink is not rendering (stdout not a TTY), pass output directly to stdout
103
+ if (!this.inkApp) {
104
+ const cp = crossSpawn(command, args, _object_spread_props(_object_spread({}, csOptions), {
105
+ stdio: 'inherit'
106
+ }));
107
+ spawn.worker(cp, csOptions, (err)=>{
108
+ const res = err ? err : {};
109
+ res.stdout = null;
110
+ res.stderr = null;
111
+ res.output = [
112
+ null,
113
+ null,
114
+ null
115
+ ];
116
+ err ? callback(err) : callback(null, res);
117
+ });
118
+ return;
119
+ }
51
120
  this.runningCount++;
52
121
  const id = crypto.randomUUID();
122
+ // Create terminal buffer for ANSI sequence interpretation
123
+ const terminalBuffer = new TerminalBuffer(this.terminalWidth);
53
124
  this.store.addProcess({
54
125
  id,
55
126
  title: [
@@ -57,51 +128,49 @@ class SessionImpl {
57
128
  ].concat(formatArguments(args)).join(' '),
58
129
  state: 'running',
59
130
  lines: [],
131
+ terminalBuffer,
60
132
  group: options.group,
61
133
  expanded: options.expanded
62
134
  });
63
135
  const cp = crossSpawn(command, args, csOptions);
64
- const outputs = {
65
- stdout: null,
66
- stderr: null
67
- };
68
- const queue = new Queue();
136
+ // Pipe stdout and stderr directly to terminal buffer
137
+ // Both streams go to the same buffer to maintain correct ordering
69
138
  if (cp.stdout) {
70
- outputs.stdout = addLines((lines)=>{
71
- this.store.appendLines(id, lines.map((text)=>({
72
- type: LineType.stdout,
73
- text
74
- })));
139
+ cp.stdout.on('data', (chunk)=>{
140
+ terminalBuffer.write(chunk);
141
+ this.store.notify();
75
142
  });
76
- queue.defer(oo.bind(null, cp.stdout.pipe(outputs.stdout), [
143
+ }
144
+ if (cp.stderr) {
145
+ cp.stderr.on('data', (chunk)=>{
146
+ terminalBuffer.write(chunk);
147
+ this.store.notify();
148
+ });
149
+ }
150
+ // Wait for process to complete
151
+ const queue = new Queue();
152
+ if (cp.stdout) {
153
+ queue.defer(oo.bind(null, cp.stdout, [
77
154
  'error',
78
155
  'end',
79
- 'close',
80
- 'finish'
156
+ 'close'
81
157
  ]));
82
158
  }
83
159
  if (cp.stderr) {
84
- outputs.stderr = addLines((lines)=>{
85
- this.store.appendLines(id, lines.map((text)=>({
86
- type: LineType.stderr,
87
- text
88
- })));
89
- });
90
- queue.defer(oo.bind(null, cp.stderr.pipe(outputs.stderr), [
160
+ queue.defer(oo.bind(null, cp.stderr, [
91
161
  'error',
92
162
  'end',
93
- 'close',
94
- 'finish'
163
+ 'close'
95
164
  ]));
96
165
  }
97
166
  queue.defer(spawn.worker.bind(null, cp, csOptions));
98
167
  queue.await((err)=>{
99
168
  const res = err ? err : {};
100
- res.stdout = outputs.stdout ? outputs.stdout.output : null;
101
- res.stderr = outputs.stderr ? outputs.stderr.output : null;
169
+ res.stdout = null; // Not collecting raw output in inherit mode
170
+ res.stderr = null;
102
171
  res.output = [
103
- res.stdout,
104
- res.stderr,
172
+ null,
173
+ null,
105
174
  null
106
175
  ];
107
176
  this.store.updateProcess(id, {
@@ -234,13 +303,21 @@ class SessionImpl {
234
303
  this.store = new ProcessStore(options);
235
304
  var _options_interactive;
236
305
  this.isInteractive = (_options_interactive = options.interactive) !== null && _options_interactive !== void 0 ? _options_interactive : false;
237
- // Render Ink app immediately
238
- this.inkApp = render(/*#__PURE__*/ _jsx(App, {
239
- store: this.store
240
- }), {
241
- incrementalRendering: true,
242
- maxFps: DEFAULT_MAX_FPS
243
- });
306
+ // Use a very wide buffer to prevent line wrapping in xterm
307
+ // Actual display truncation is handled by Ink components
308
+ this.terminalWidth = 10000;
309
+ // Only render Ink when stdout is a real terminal
310
+ // When piped (e.g., nested spawn-term), skip Ink to avoid cursor positioning artifacts
311
+ if (process.stdout.isTTY) {
312
+ // Note: incrementalRendering disabled to prevent corruption when content shifts vertically
313
+ // (e.g., error footer appearing, processes completing, scroll position changes)
314
+ this.inkApp = render(/*#__PURE__*/ _jsx(App, {
315
+ store: this.store
316
+ }), {
317
+ incrementalRendering: false,
318
+ maxFps: DEFAULT_MAX_FPS
319
+ });
320
+ }
244
321
  }
245
322
  }
246
323
  export function createSession(options = {}) {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/session.tsx"],"sourcesContent":["import spawn, { crossSpawn, type SpawnResult } from 'cross-spawn-cb';\nimport crypto from 'crypto';\nimport { render } from 'ink';\nimport oo from 'on-one';\nimport Queue from 'queue-cb';\n\nimport App from './components/App.ts';\nimport { DEFAULT_MAX_FPS } from './constants.ts';\nimport addLines from './lib/addLines.ts';\nimport concatWritable from './lib/concatWritable.ts';\nimport formatArguments from './lib/formatArguments.ts';\nimport { ProcessStore } from './state/processStore.ts';\nimport type { ProcessOptions, SessionOptions, SpawnError, SpawnOptions, TerminalCallback } from './types.ts';\nimport { LineType } from './types.ts';\n\nexport interface Session {\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void;\n close(): void;\n waitAndClose(callback?: () => void): void;\n}\n\nclass SessionImpl implements Session {\n private store: ProcessStore;\n private inkApp: ReturnType<typeof render> | null = null;\n private runningCount = 0;\n private closed = false;\n private waitCallbacks: (() => void)[] = [];\n private isInteractive: boolean;\n\n constructor(options: SessionOptions = {}) {\n this.store = new ProcessStore(options);\n this.isInteractive = options.interactive ?? false;\n\n // Render Ink app immediately\n this.inkApp = render(<App store={this.store} />, {\n incrementalRendering: true,\n maxFps: DEFAULT_MAX_FPS,\n });\n }\n\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void {\n if (this.closed) {\n throw new Error('Session is closed');\n }\n\n const { encoding, stdio, ...csOptions } = spawnOptions;\n\n if (stdio === 'inherit') {\n this.runningCount++;\n const id = crypto.randomUUID();\n this.store.addProcess({\n id,\n title: [command].concat(formatArguments(args)).join(' '),\n state: 'running',\n lines: [],\n group: options.group,\n expanded: options.expanded,\n });\n\n const cp = crossSpawn(command, args, csOptions);\n const outputs = { stdout: null as ReturnType<typeof addLines> | null, stderr: null as ReturnType<typeof addLines> | null };\n\n const queue = new Queue();\n if (cp.stdout) {\n outputs.stdout = addLines((lines) => {\n this.store.appendLines(\n id,\n lines.map((text) => ({ type: LineType.stdout, text }))\n );\n });\n queue.defer(oo.bind(null, cp.stdout.pipe(outputs.stdout), ['error', 'end', 'close', 'finish']));\n }\n if (cp.stderr) {\n outputs.stderr = addLines((lines) => {\n this.store.appendLines(\n id,\n lines.map((text) => ({ type: LineType.stderr, text }))\n );\n });\n queue.defer(oo.bind(null, cp.stderr.pipe(outputs.stderr), ['error', 'end', 'close', 'finish']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = outputs.stdout ? (outputs.stdout as unknown as { output: string }).output : null;\n res.stderr = outputs.stderr ? (outputs.stderr as unknown as { output: string }).output : null;\n res.output = [res.stdout, res.stderr, null];\n this.store.updateProcess(id, { state: err ? 'error' : 'success' });\n\n this.onProcessComplete();\n err ? callback(err) : callback(null, res);\n });\n } else {\n // Non-inherit mode: collect output but don't display in UI\n const cp = crossSpawn(command, args, csOptions);\n const outputs = { stdout: null as ReturnType<typeof concatWritable> | null, stderr: null as ReturnType<typeof concatWritable> | null };\n\n const queue = new Queue();\n if (cp.stdout) {\n outputs.stdout = concatWritable((output) => {\n (outputs.stdout as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stdout.pipe(outputs.stdout), ['error', 'end', 'close', 'finish']));\n }\n if (cp.stderr) {\n outputs.stderr = concatWritable((output) => {\n (outputs.stderr as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stderr.pipe(outputs.stderr), ['error', 'end', 'close', 'finish']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = outputs.stdout ? (outputs.stdout as unknown as { output: string }).output : null;\n res.stderr = outputs.stderr ? (outputs.stderr as unknown as { output: string }).output : null;\n res.output = [res.stdout, res.stderr, null];\n err ? callback(err) : callback(null, res);\n });\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup();\n }\n\n waitAndClose(callback?: () => void): void {\n if (this.closed) {\n callback?.();\n return;\n }\n\n if (callback) this.waitCallbacks.push(callback);\n\n if (this.runningCount === 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n // If runningCount > 0, will close when it hits 0 in onProcessComplete\n }\n\n private onProcessComplete(): void {\n this.runningCount--;\n if (this.runningCount === 0 && this.waitCallbacks.length > 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n }\n\n private closeAndCallWaitCallbacks(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup(() => {\n for (const cb of this.waitCallbacks) cb();\n this.waitCallbacks = [];\n });\n }\n\n private cleanup(onComplete?: () => void): void {\n // Signal exit to React component\n this.store.signalExit(() => {\n this.store.reset();\n process.stdout.write('\\x1b[?25h'); // show cursor\n });\n\n // Wait for Ink to finish\n if (this.inkApp) {\n this.inkApp\n .waitUntilExit()\n .then(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n })\n .catch(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n });\n this.inkApp = null;\n } else {\n onComplete?.();\n }\n }\n}\n\nexport function createSession(options: SessionOptions = {}): Session {\n return new SessionImpl(options);\n}\n"],"names":["spawn","crossSpawn","crypto","render","oo","Queue","App","DEFAULT_MAX_FPS","addLines","concatWritable","formatArguments","ProcessStore","LineType","SessionImpl","command","args","spawnOptions","options","callback","closed","Error","encoding","stdio","csOptions","runningCount","id","randomUUID","store","addProcess","title","concat","join","state","lines","group","expanded","cp","outputs","stdout","stderr","queue","appendLines","map","text","type","defer","bind","pipe","worker","await","err","res","output","updateProcess","onProcessComplete","toString","close","cleanup","waitAndClose","waitCallbacks","push","isInteractive","unsubscribe","subscribe","getShouldExit","closeAndCallWaitCallbacks","length","cb","onComplete","signalExit","reset","process","write","inkApp","waitUntilExit","then","getExitCallback","catch","interactive","incrementalRendering","maxFps","createSession"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/session.tsx"],"sourcesContent":["import spawn, { crossSpawn, type SpawnResult } from 'cross-spawn-cb';\nimport crypto from 'crypto';\nimport { render } from 'ink';\nimport oo from 'on-one';\nimport Queue from 'queue-cb';\n\nimport App from './components/App.ts';\nimport { DEFAULT_MAX_FPS } from './constants.ts';\nimport concatWritable from './lib/concatWritable.ts';\nimport formatArguments from './lib/formatArguments.ts';\nimport { TerminalBuffer } from './lib/TerminalBuffer.ts';\nimport { ProcessStore } from './state/processStore.ts';\nimport type { ProcessOptions, SessionOptions, SpawnError, SpawnOptions, TerminalCallback } from './types.ts';\n\nexport interface Session {\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void;\n close(): void;\n waitAndClose(callback?: () => void): void;\n}\n\nclass SessionImpl implements Session {\n private store: ProcessStore;\n private inkApp: ReturnType<typeof render> | null = null;\n private runningCount = 0;\n private closed = false;\n private waitCallbacks: (() => void)[] = [];\n private isInteractive: boolean;\n private terminalWidth: number;\n\n constructor(options: SessionOptions = {}) {\n this.store = new ProcessStore(options);\n this.isInteractive = options.interactive ?? false;\n // Use a very wide buffer to prevent line wrapping in xterm\n // Actual display truncation is handled by Ink components\n this.terminalWidth = 10000;\n\n // Only render Ink when stdout is a real terminal\n // When piped (e.g., nested spawn-term), skip Ink to avoid cursor positioning artifacts\n if (process.stdout.isTTY) {\n // Note: incrementalRendering disabled to prevent corruption when content shifts vertically\n // (e.g., error footer appearing, processes completing, scroll position changes)\n this.inkApp = render(<App store={this.store} />, {\n incrementalRendering: false,\n maxFps: DEFAULT_MAX_FPS,\n });\n }\n }\n\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void {\n if (this.closed) {\n throw new Error('Session is closed');\n }\n\n const { encoding, stdio, ...csOptions } = spawnOptions;\n\n if (stdio === 'inherit') {\n // When Ink is not rendering (stdout not a TTY), pass output directly to stdout\n if (!this.inkApp) {\n const cp = crossSpawn(command, args, { ...csOptions, stdio: 'inherit' });\n spawn.worker(cp, csOptions, (err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = null;\n res.stderr = null;\n res.output = [null, null, null];\n err ? callback(err) : callback(null, res);\n });\n return;\n }\n\n this.runningCount++;\n const id = crypto.randomUUID();\n\n // Create terminal buffer for ANSI sequence interpretation\n const terminalBuffer = new TerminalBuffer(this.terminalWidth);\n\n this.store.addProcess({\n id,\n title: [command].concat(formatArguments(args)).join(' '),\n state: 'running',\n lines: [],\n terminalBuffer,\n group: options.group,\n expanded: options.expanded,\n });\n\n const cp = crossSpawn(command, args, csOptions);\n\n // Pipe stdout and stderr directly to terminal buffer\n // Both streams go to the same buffer to maintain correct ordering\n if (cp.stdout) {\n cp.stdout.on('data', (chunk: Buffer) => {\n terminalBuffer.write(chunk);\n this.store.notify();\n });\n }\n if (cp.stderr) {\n cp.stderr.on('data', (chunk: Buffer) => {\n terminalBuffer.write(chunk);\n this.store.notify();\n });\n }\n\n // Wait for process to complete\n const queue = new Queue();\n if (cp.stdout) {\n queue.defer(oo.bind(null, cp.stdout, ['error', 'end', 'close']));\n }\n if (cp.stderr) {\n queue.defer(oo.bind(null, cp.stderr, ['error', 'end', 'close']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = null; // Not collecting raw output in inherit mode\n res.stderr = null;\n res.output = [null, null, null];\n this.store.updateProcess(id, { state: err ? 'error' : 'success' });\n\n this.onProcessComplete();\n err ? callback(err) : callback(null, res);\n });\n } else {\n // Non-inherit mode: collect output but don't display in UI\n const cp = crossSpawn(command, args, csOptions);\n const outputs = { stdout: null as ReturnType<typeof concatWritable> | null, stderr: null as ReturnType<typeof concatWritable> | null };\n\n const queue = new Queue();\n if (cp.stdout) {\n outputs.stdout = concatWritable((output) => {\n (outputs.stdout as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stdout.pipe(outputs.stdout), ['error', 'end', 'close', 'finish']));\n }\n if (cp.stderr) {\n outputs.stderr = concatWritable((output) => {\n (outputs.stderr as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stderr.pipe(outputs.stderr), ['error', 'end', 'close', 'finish']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = outputs.stdout ? (outputs.stdout as unknown as { output: string }).output : null;\n res.stderr = outputs.stderr ? (outputs.stderr as unknown as { output: string }).output : null;\n res.output = [res.stdout, res.stderr, null];\n err ? callback(err) : callback(null, res);\n });\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup();\n }\n\n waitAndClose(callback?: () => void): void {\n if (this.closed) {\n callback?.();\n return;\n }\n\n if (callback) this.waitCallbacks.push(callback);\n\n if (this.runningCount === 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n // If runningCount > 0, will close when it hits 0 in onProcessComplete\n }\n\n private onProcessComplete(): void {\n this.runningCount--;\n if (this.runningCount === 0 && this.waitCallbacks.length > 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n }\n\n private closeAndCallWaitCallbacks(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup(() => {\n for (const cb of this.waitCallbacks) cb();\n this.waitCallbacks = [];\n });\n }\n\n private cleanup(onComplete?: () => void): void {\n // Signal exit to React component\n this.store.signalExit(() => {\n this.store.reset();\n process.stdout.write('\\x1b[?25h'); // show cursor\n });\n\n // Wait for Ink to finish\n if (this.inkApp) {\n this.inkApp\n .waitUntilExit()\n .then(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n })\n .catch(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n });\n this.inkApp = null;\n } else {\n onComplete?.();\n }\n }\n}\n\nexport function createSession(options: SessionOptions = {}): Session {\n return new SessionImpl(options);\n}\n"],"names":["spawn","crossSpawn","crypto","render","oo","Queue","App","DEFAULT_MAX_FPS","concatWritable","formatArguments","TerminalBuffer","ProcessStore","SessionImpl","command","args","spawnOptions","options","callback","closed","Error","encoding","stdio","csOptions","inkApp","cp","worker","err","res","stdout","stderr","output","runningCount","id","randomUUID","terminalBuffer","terminalWidth","store","addProcess","title","concat","join","state","lines","group","expanded","on","chunk","write","notify","queue","defer","bind","await","updateProcess","onProcessComplete","outputs","toString","pipe","close","cleanup","waitAndClose","waitCallbacks","push","isInteractive","unsubscribe","subscribe","getShouldExit","closeAndCallWaitCallbacks","length","cb","onComplete","signalExit","reset","process","waitUntilExit","then","getExitCallback","catch","interactive","isTTY","incrementalRendering","maxFps","createSession"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Line } from '../types.js';
2
+ type ErrorGroup = {
3
+ processName: string;
4
+ lines: Line[];
5
+ };
6
+ type Props = {
7
+ errors: ErrorGroup[];
8
+ isExpanded: boolean;
9
+ };
10
+ declare const _default: import("react").NamedExoticComponent<Props>;
11
+ export default _default;
@@ -1,5 +1,6 @@
1
1
  export { default as figures } from './lib/figures.js';
2
2
  export { default as formatArguments } from './lib/formatArguments.js';
3
+ export { TerminalBuffer } from './lib/TerminalBuffer.js';
3
4
  export * from './types.js';
4
5
  import type { createSession as createSessionType, Session } from './createSessionWrapper.js';
5
6
  export type { Session };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Wrapper around @xterm/headless Terminal that provides a virtual terminal buffer.
3
+ * Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce
4
+ * the actual rendered output rather than raw intermediate states.
5
+ */
6
+ export declare class TerminalBuffer {
7
+ private terminal;
8
+ constructor(cols: number, scrollback?: number);
9
+ /**
10
+ * Write raw data to the terminal buffer.
11
+ * The terminal interprets all ANSI sequences automatically.
12
+ */
13
+ write(data: string | Buffer): void;
14
+ /**
15
+ * Resize the terminal width.
16
+ */
17
+ resize(cols: number): void;
18
+ /**
19
+ * Extract the rendered lines from the terminal buffer.
20
+ * This returns the actual visible content after all ANSI sequences
21
+ * have been processed.
22
+ */
23
+ getLines(): string[];
24
+ /**
25
+ * Get the number of rendered lines.
26
+ */
27
+ get lineCount(): number;
28
+ /**
29
+ * Clean up terminal resources.
30
+ */
31
+ dispose(): void;
32
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ChildProcess, Line, SessionOptions } from '../types.js';
2
2
  type Listener = () => void;
3
- type Mode = 'normal' | 'interactive' | 'errorList' | 'errorDetail';
3
+ type Mode = 'normal' | 'interactive';
4
4
  export declare class ProcessStore {
5
5
  private processes;
6
6
  private completedIds;
@@ -9,10 +9,11 @@ export declare class ProcessStore {
9
9
  private exitCallback;
10
10
  private mode;
11
11
  private selectedIndex;
12
- private selectedErrorIndex;
13
12
  private expandedId;
14
13
  private scrollOffset;
15
14
  private listScrollOffset;
15
+ private errorFooterExpanded;
16
+ private bufferVersion;
16
17
  private header;
17
18
  private showStatusBar;
18
19
  private isInteractive;
@@ -29,10 +30,11 @@ export declare class ProcessStore {
29
30
  getErrorLineCount: () => number;
30
31
  getMode: () => Mode;
31
32
  getSelectedIndex: () => number;
32
- getSelectedErrorIndex: () => number;
33
33
  getExpandedId: () => string | null;
34
34
  getScrollOffset: () => number;
35
35
  getListScrollOffset: () => number;
36
+ getErrorFooterExpanded: () => boolean;
37
+ getBufferVersion: () => number;
36
38
  getHeader: () => string | undefined;
37
39
  getShowStatusBar: () => boolean;
38
40
  getIsInteractive: () => boolean;
@@ -41,14 +43,19 @@ export declare class ProcessStore {
41
43
  updateProcess(id: string, update: Partial<ChildProcess>): void;
42
44
  appendLines(id: string, newLines: Line[]): void;
43
45
  getProcess(id: string): ChildProcess | undefined;
46
+ getProcessLines(id: string): Line[];
47
+ getProcessLineCount(id: string): number;
44
48
  setMode(mode: Mode): void;
45
49
  selectNext(visibleCount?: number): void;
46
50
  selectPrev(visibleCount?: number): void;
47
51
  private adjustListScroll;
48
52
  getSelectedProcess(): ChildProcess | undefined;
49
- selectNextError(): void;
50
- selectPrevError(): void;
51
- getSelectedError(): ChildProcess | undefined;
53
+ toggleErrorFooter(): void;
54
+ expandErrorFooter(): void;
55
+ getErrorLines(): Array<{
56
+ processName: string;
57
+ lines: Line[];
58
+ }>;
52
59
  toggleExpand(): void;
53
60
  collapse(): void;
54
61
  scrollDown(maxVisible: number): void;
@@ -57,6 +64,6 @@ export declare class ProcessStore {
57
64
  getShouldExit: () => boolean;
58
65
  getExitCallback: () => (() => void) | null;
59
66
  reset(): void;
60
- private notify;
67
+ notify(): void;
61
68
  }
62
69
  export {};
@@ -19,11 +19,13 @@ export type Line = {
19
19
  text: string;
20
20
  };
21
21
  export type State = 'running' | 'error' | 'success';
22
+ import type { TerminalBuffer } from './lib/TerminalBuffer.js';
22
23
  export type ChildProcess = {
23
24
  id: string;
24
25
  group?: string;
25
26
  title: string;
26
27
  state: State;
27
28
  lines: Line[];
29
+ terminalBuffer?: TerminalBuffer;
28
30
  expanded?: boolean;
29
31
  };