ccmanager 0.1.0 → 0.1.2

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.
@@ -0,0 +1,178 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { SessionManager } from './sessionManager.js';
3
+ import { spawn } from 'node-pty';
4
+ // Create mock pty process
5
+ const createMockPtyProcess = () => {
6
+ const handlers = {
7
+ data: [],
8
+ exit: [],
9
+ };
10
+ return {
11
+ write: vi.fn(),
12
+ resize: vi.fn(),
13
+ onData: vi.fn((handler) => {
14
+ handlers.data.push(handler);
15
+ }),
16
+ onExit: vi.fn((handler) => {
17
+ handlers.exit.push(handler);
18
+ }),
19
+ kill: vi.fn(),
20
+ _emit: (event, ...args) => {
21
+ if (event === 'data' && handlers.data.length > 0) {
22
+ handlers.data.forEach(h => h(args[0]));
23
+ }
24
+ else if (event === 'exit' && handlers.exit.length > 0) {
25
+ handlers.exit.forEach(h => h(args[0]));
26
+ }
27
+ },
28
+ };
29
+ };
30
+ // Mock node-pty
31
+ vi.mock('node-pty', () => ({
32
+ spawn: vi.fn(),
33
+ }));
34
+ // Mock @xterm/headless
35
+ vi.mock('@xterm/headless', () => ({
36
+ default: {
37
+ Terminal: vi.fn().mockImplementation(() => ({
38
+ buffer: {
39
+ active: {
40
+ length: 10,
41
+ cursorY: 0,
42
+ cursorX: 0,
43
+ getLine: vi.fn(),
44
+ },
45
+ },
46
+ write: vi.fn(),
47
+ resize: vi.fn(),
48
+ clear: vi.fn(),
49
+ onData: vi.fn(),
50
+ })),
51
+ },
52
+ }));
53
+ describe('SessionManager - Partial TUI Update Integration', () => {
54
+ let sessionManager;
55
+ const mockWorktreePath = '/test/worktree';
56
+ beforeEach(() => {
57
+ sessionManager = new SessionManager();
58
+ vi.clearAllMocks();
59
+ });
60
+ it('should not accumulate duplicate content in output history', () => {
61
+ // Create a mock PTY process
62
+ const mockProcess = createMockPtyProcess();
63
+ vi.mocked(spawn).mockReturnValue(mockProcess);
64
+ // Create a session
65
+ sessionManager.createSession(mockWorktreePath);
66
+ const session = sessionManager.sessions.get(mockWorktreePath);
67
+ expect(session).toBeDefined();
68
+ // Simulate multiple partial updates from Claude Code
69
+ const updates = [
70
+ '+ Exploring... (10s ・ 53 tokens ・ esc to interrupt)\r',
71
+ '\x1B[1A\x1B[K+ Exploring... (10s ・ 57 tokens ・ esc to interrupt)\r',
72
+ '\x1B[1A\x1B[K+ Exploring... (10s ・ 76 tokens ・ esc to interrupt)\r',
73
+ '\x1B[1A\x1B[K+ Exploring... (10s ・ 89 tokens ・ esc to interrupt)\r',
74
+ '\x1B[1A\x1B[K+ Exploring... (10s ・ 102 tokens ・ esc to interrupt)\r',
75
+ ];
76
+ // Process each update
77
+ updates.forEach(update => {
78
+ // Simulate PTY data event
79
+ mockProcess._emit('data', update);
80
+ });
81
+ // Check that the virtual terminal received all updates
82
+ expect(session.terminal.write).toHaveBeenCalledTimes(5);
83
+ // The outputHistory should be empty since we removed that functionality
84
+ expect(session.outputHistory).toEqual([]);
85
+ });
86
+ it('should use virtual terminal buffer for session restoration', () => {
87
+ // Create a mock PTY process
88
+ const mockProcess = createMockPtyProcess();
89
+ vi.mocked(spawn).mockReturnValue(mockProcess);
90
+ sessionManager.createSession(mockWorktreePath);
91
+ const session = sessionManager.sessions.get(mockWorktreePath);
92
+ // Mock the terminal buffer to contain the final state
93
+ // Type cast is acceptable for test mocks
94
+ const mockTerminal = session.terminal;
95
+ mockTerminal.buffer.active.getLine = vi.fn((index) => {
96
+ const lines = [
97
+ 'Welcome to Claude Code',
98
+ '+ Exploring... (10s ・ 218 tokens ・ esc to interrupt)',
99
+ '',
100
+ 'Task completed successfully',
101
+ '> ',
102
+ ];
103
+ if (index < lines.length) {
104
+ return {
105
+ translateToString: () => lines[index],
106
+ };
107
+ }
108
+ return null;
109
+ });
110
+ mockTerminal.buffer.active.length = 5;
111
+ mockTerminal.buffer.active.cursorY = 4;
112
+ mockTerminal.buffer.active.cursorX = 2;
113
+ // Emit restore event
114
+ sessionManager.emit('sessionRestore', session);
115
+ // The terminal buffer should be used for restoration, not output history
116
+ // This prevents duplicate content issues
117
+ expect(session.outputHistory).toEqual([]);
118
+ });
119
+ it('should handle ANSI escape sequences correctly in virtual terminal', () => {
120
+ // Create a mock PTY process
121
+ const mockProcess = createMockPtyProcess();
122
+ vi.mocked(spawn).mockReturnValue(mockProcess);
123
+ sessionManager.createSession(mockWorktreePath);
124
+ const session = sessionManager.sessions.get(mockWorktreePath);
125
+ // Simulate data with ANSI escape sequences
126
+ const dataWithEscapes = [
127
+ 'Line 1\n',
128
+ 'Line 2\n',
129
+ '\x1B[1A\x1B[KReplaced Line 2\n', // Move up one line, clear line, write new text
130
+ '\x1B[2J\x1B[H', // Clear screen and move to home
131
+ 'Fresh start\n',
132
+ ];
133
+ dataWithEscapes.forEach(data => {
134
+ mockProcess._emit('data', data);
135
+ });
136
+ // Virtual terminal should handle all the escape sequences
137
+ expect(session.terminal.write).toHaveBeenCalledTimes(5);
138
+ // No raw output should be stored
139
+ expect(session.outputHistory).toEqual([]);
140
+ });
141
+ it('should emit sessionData events for active sessions only', () => {
142
+ // Create a mock PTY process
143
+ const mockProcess = createMockPtyProcess();
144
+ vi.mocked(spawn).mockReturnValue(mockProcess);
145
+ const dataHandler = vi.fn();
146
+ sessionManager.on('sessionData', dataHandler);
147
+ sessionManager.createSession(mockWorktreePath);
148
+ const session = sessionManager.sessions.get(mockWorktreePath);
149
+ // Session is not active by default
150
+ mockProcess._emit('data', 'Test data 1');
151
+ // Should not emit data when inactive
152
+ expect(dataHandler).not.toHaveBeenCalled();
153
+ // Activate session
154
+ sessionManager.setSessionActive(mockWorktreePath, true);
155
+ // Now data should be emitted
156
+ mockProcess._emit('data', 'Test data 2');
157
+ expect(dataHandler).toHaveBeenCalledWith(session, 'Test data 2');
158
+ });
159
+ it('should restore session without replaying output history', () => {
160
+ // Create a mock PTY process
161
+ const mockProcess = createMockPtyProcess();
162
+ vi.mocked(spawn).mockReturnValue(mockProcess);
163
+ const restoreHandler = vi.fn();
164
+ sessionManager.on('sessionRestore', restoreHandler);
165
+ sessionManager.createSession(mockWorktreePath);
166
+ const session = sessionManager.sessions.get(mockWorktreePath);
167
+ // Add some data to the session
168
+ mockProcess._emit('data', 'Old output that should not be replayed\n');
169
+ mockProcess._emit('data', 'More old output\n');
170
+ // Deactivate then reactivate session
171
+ sessionManager.setSessionActive(mockWorktreePath, false);
172
+ sessionManager.setSessionActive(mockWorktreePath, true);
173
+ // Should emit restore event
174
+ expect(restoreHandler).toHaveBeenCalledWith(session);
175
+ // But should not have any output history to replay
176
+ expect(session.outputHistory).toEqual([]);
177
+ });
178
+ });
@@ -1,6 +1,9 @@
1
1
  import { spawn } from 'node-pty';
2
2
  import { EventEmitter } from 'events';
3
3
  import pkg from '@xterm/headless';
4
+ import { exec } from 'child_process';
5
+ import { configurationManager } from './configurationManager.js';
6
+ import { WorktreeService } from './worktreeService.js';
4
7
  const { Terminal } = pkg;
5
8
  export class SessionManager extends EventEmitter {
6
9
  stripAnsi(str) {
@@ -101,7 +104,7 @@ export class SessionManager extends EventEmitter {
101
104
  process: ptyProcess,
102
105
  state: 'busy', // Session starts as busy when created
103
106
  output: [],
104
- outputHistory: [],
107
+ outputHistory: [], // Kept for backward compatibility but no longer used
105
108
  lastActivity: new Date(),
106
109
  isActive: false,
107
110
  terminal,
@@ -115,20 +118,10 @@ export class SessionManager extends EventEmitter {
115
118
  setupBackgroundHandler(session) {
116
119
  // This handler always runs for all data
117
120
  session.process.onData((data) => {
118
- // Write data to virtual terminal
121
+ // Write data to virtual terminal - this maintains the proper rendered state
119
122
  session.terminal.write(data);
120
- // Store in output history as Buffer
121
- const buffer = Buffer.from(data, 'utf8');
122
- session.outputHistory.push(buffer);
123
- // Limit memory usage - keep max 10MB of output history
124
- const MAX_HISTORY_SIZE = 10 * 1024 * 1024; // 10MB
125
- let totalSize = session.outputHistory.reduce((sum, buf) => sum + buf.length, 0);
126
- while (totalSize > MAX_HISTORY_SIZE && session.outputHistory.length > 0) {
127
- const removed = session.outputHistory.shift();
128
- if (removed) {
129
- totalSize -= removed.length;
130
- }
131
- }
123
+ // We no longer need to maintain outputHistory since we use the virtual terminal buffer
124
+ // This prevents duplicate content issues and reduces memory usage
132
125
  session.lastActivity = new Date();
133
126
  // Only emit data events when session is active
134
127
  if (session.isActive) {
@@ -141,6 +134,7 @@ export class SessionManager extends EventEmitter {
141
134
  const newState = this.detectTerminalState(session.terminal);
142
135
  if (newState !== oldState) {
143
136
  session.state = newState;
137
+ this.executeStatusHook(oldState, newState, session);
144
138
  this.emit('sessionStateChanged', session);
145
139
  }
146
140
  }, 100); // Check every 100ms
@@ -163,8 +157,9 @@ export class SessionManager extends EventEmitter {
163
157
  const session = this.sessions.get(worktreePath);
164
158
  if (session) {
165
159
  session.isActive = active;
166
- // If becoming active, emit a restore event with the output history
167
- if (active && session.outputHistory.length > 0) {
160
+ // If becoming active, emit a restore event
161
+ // The Session component will use the virtual terminal buffer instead of outputHistory
162
+ if (active) {
168
163
  this.emit('sessionRestore', session);
169
164
  }
170
165
  }
@@ -196,6 +191,36 @@ export class SessionManager extends EventEmitter {
196
191
  getAllSessions() {
197
192
  return Array.from(this.sessions.values());
198
193
  }
194
+ executeStatusHook(oldState, newState, session) {
195
+ const statusHooks = configurationManager.getStatusHooks();
196
+ const hook = statusHooks[newState];
197
+ if (hook && hook.enabled && hook.command) {
198
+ // Get branch information
199
+ const worktreeService = new WorktreeService();
200
+ const worktrees = worktreeService.getWorktrees();
201
+ const worktree = worktrees.find(wt => wt.path === session.worktreePath);
202
+ const branch = worktree?.branch || 'unknown';
203
+ // Execute the hook command in the session's worktree directory
204
+ exec(hook.command, {
205
+ cwd: session.worktreePath,
206
+ env: {
207
+ ...process.env,
208
+ CCMANAGER_OLD_STATE: oldState,
209
+ CCMANAGER_NEW_STATE: newState,
210
+ CCMANAGER_WORKTREE: session.worktreePath,
211
+ CCMANAGER_WORKTREE_BRANCH: branch,
212
+ CCMANAGER_SESSION_ID: session.id,
213
+ },
214
+ }, (error, _stdout, stderr) => {
215
+ if (error) {
216
+ console.error(`Failed to execute ${newState} hook: ${error.message}`);
217
+ }
218
+ if (stderr) {
219
+ console.error(`Hook stderr: ${stderr}`);
220
+ }
221
+ });
222
+ }
223
+ }
199
224
  destroy() {
200
225
  // Clean up all sessions
201
226
  for (const worktreePath of this.sessions.keys()) {
@@ -1,11 +1,8 @@
1
1
  import { ShortcutKey, ShortcutConfig } from '../types/index.js';
2
2
  import { Key } from 'ink';
3
3
  export declare class ShortcutManager {
4
- private shortcuts;
5
- private configPath;
6
4
  private reservedKeys;
7
5
  constructor();
8
- private loadShortcuts;
9
6
  private validateShortcut;
10
7
  private isReservedKey;
11
8
  saveShortcuts(shortcuts: ShortcutConfig): boolean;
@@ -1,21 +1,6 @@
1
- import { DEFAULT_SHORTCUTS, } from '../types/index.js';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import * as os from 'os';
1
+ import { configurationManager } from './configurationManager.js';
5
2
  export class ShortcutManager {
6
3
  constructor() {
7
- Object.defineProperty(this, "shortcuts", {
8
- enumerable: true,
9
- configurable: true,
10
- writable: true,
11
- value: void 0
12
- });
13
- Object.defineProperty(this, "configPath", {
14
- enumerable: true,
15
- configurable: true,
16
- writable: true,
17
- value: void 0
18
- });
19
4
  Object.defineProperty(this, "reservedKeys", {
20
5
  enumerable: true,
21
6
  configurable: true,
@@ -27,31 +12,6 @@ export class ShortcutManager {
27
12
  { ctrl: true, key: '[' },
28
13
  ]
29
14
  });
30
- // Use platform-specific config directory
31
- const configDir = process.platform === 'win32'
32
- ? path.join(process.env['APPDATA'] || os.homedir(), 'ccmanager')
33
- : path.join(os.homedir(), '.config', 'ccmanager');
34
- this.configPath = path.join(configDir, 'shortcuts.json');
35
- this.shortcuts = this.loadShortcuts();
36
- }
37
- loadShortcuts() {
38
- try {
39
- if (fs.existsSync(this.configPath)) {
40
- const data = fs.readFileSync(this.configPath, 'utf8');
41
- const loaded = JSON.parse(data);
42
- // Validate loaded shortcuts
43
- const validated = {
44
- returnToMenu: this.validateShortcut(loaded.returnToMenu) ||
45
- DEFAULT_SHORTCUTS.returnToMenu,
46
- cancel: this.validateShortcut(loaded.cancel) || DEFAULT_SHORTCUTS.cancel,
47
- };
48
- return validated;
49
- }
50
- }
51
- catch (error) {
52
- console.error('Failed to load shortcuts:', error);
53
- }
54
- return { ...DEFAULT_SHORTCUTS };
55
15
  }
56
16
  validateShortcut(shortcut) {
57
17
  if (!shortcut || typeof shortcut !== 'object') {
@@ -88,30 +48,21 @@ export class ShortcutManager {
88
48
  }
89
49
  saveShortcuts(shortcuts) {
90
50
  // Validate all shortcuts
51
+ const currentShortcuts = configurationManager.getShortcuts();
91
52
  const validated = {
92
53
  returnToMenu: this.validateShortcut(shortcuts.returnToMenu) ||
93
- this.shortcuts.returnToMenu,
94
- cancel: this.validateShortcut(shortcuts.cancel) || this.shortcuts.cancel,
54
+ currentShortcuts.returnToMenu,
55
+ cancel: this.validateShortcut(shortcuts.cancel) || currentShortcuts.cancel,
95
56
  };
96
- try {
97
- const dir = path.dirname(this.configPath);
98
- if (!fs.existsSync(dir)) {
99
- fs.mkdirSync(dir, { recursive: true });
100
- }
101
- fs.writeFileSync(this.configPath, JSON.stringify(validated, null, 2));
102
- this.shortcuts = validated;
103
- return true;
104
- }
105
- catch (error) {
106
- console.error('Failed to save shortcuts:', error);
107
- return false;
108
- }
57
+ configurationManager.setShortcuts(validated);
58
+ return true;
109
59
  }
110
60
  getShortcuts() {
111
- return { ...this.shortcuts };
61
+ return configurationManager.getShortcuts();
112
62
  }
113
63
  matchesShortcut(shortcutName, input, key) {
114
- const shortcut = this.shortcuts[shortcutName];
64
+ const shortcuts = configurationManager.getShortcuts();
65
+ const shortcut = shortcuts[shortcutName];
115
66
  if (!shortcut)
116
67
  return false;
117
68
  // Handle escape key specially
@@ -129,7 +80,8 @@ export class ShortcutManager {
129
80
  return input.toLowerCase() === shortcut.key.toLowerCase();
130
81
  }
131
82
  getShortcutDisplay(shortcutName) {
132
- const shortcut = this.shortcuts[shortcutName];
83
+ const shortcuts = configurationManager.getShortcuts();
84
+ const shortcut = shortcuts[shortcutName];
133
85
  if (!shortcut)
134
86
  return '';
135
87
  const parts = [];
@@ -32,7 +32,12 @@ export class WorktreeService {
32
32
  };
33
33
  }
34
34
  else if (line.startsWith('branch ')) {
35
- currentWorktree.branch = line.substring(7);
35
+ let branch = line.substring(7);
36
+ // Remove refs/heads/ prefix if present
37
+ if (branch.startsWith('refs/heads/')) {
38
+ branch = branch.substring(11);
39
+ }
40
+ currentWorktree.branch = branch;
36
41
  }
37
42
  else if (line === 'bare') {
38
43
  currentWorktree.isMainWorktree = true;
@@ -1,4 +1,6 @@
1
1
  import { IPty } from 'node-pty';
2
+ import type pkg from '@xterm/headless';
3
+ export type Terminal = InstanceType<typeof pkg.Terminal>;
2
4
  export type SessionState = 'idle' | 'busy' | 'waiting_input';
3
5
  export interface Worktree {
4
6
  path: string;
@@ -15,7 +17,7 @@ export interface Session {
15
17
  outputHistory: Buffer[];
16
18
  lastActivity: Date;
17
19
  isActive: boolean;
18
- terminal: any;
20
+ terminal: Terminal;
19
21
  stateCheckInterval?: NodeJS.Timeout;
20
22
  }
21
23
  export interface SessionManager {
@@ -36,3 +38,16 @@ export interface ShortcutConfig {
36
38
  cancel: ShortcutKey;
37
39
  }
38
40
  export declare const DEFAULT_SHORTCUTS: ShortcutConfig;
41
+ export interface StatusHook {
42
+ command: string;
43
+ enabled: boolean;
44
+ }
45
+ export interface StatusHookConfig {
46
+ idle?: StatusHook;
47
+ busy?: StatusHook;
48
+ waiting_input?: StatusHook;
49
+ }
50
+ export interface ConfigurationData {
51
+ shortcuts?: ShortcutConfig;
52
+ statusHooks?: StatusHookConfig;
53
+ }
@@ -0,0 +1,119 @@
1
+ import type { Terminal } from '../types/index.js';
2
+ /**
3
+ * TerminalSerializer: Converts terminal screen content to text while preserving colors and formatting.
4
+ *
5
+ * Imagine taking a screenshot of your terminal, but instead of an image, you get text that
6
+ * can recreate the exact same appearance with all colors and styles when displayed again.
7
+ */
8
+ export declare class TerminalSerializer {
9
+ /**
10
+ * ESC: The "magic" prefix that tells the terminal "the next characters are instructions, not text"
11
+ * '\x1b[' is like saying "Hey terminal, listen up for special commands!"
12
+ */
13
+ private static readonly ESC;
14
+ /**
15
+ * RESET: The command that tells terminal "go back to normal text" (no colors, no bold, etc.)
16
+ * Like clicking "clear formatting" in a word processor
17
+ */
18
+ private static readonly RESET;
19
+ /**
20
+ * Determines which color system is being used for a given color value.
21
+ *
22
+ * Terminal supports different color systems:
23
+ * - Mode 0 (DEFAULT): Basic terminal colors (like white text on black background)
24
+ * - Mode 1 (P16): 16 basic colors (8 normal + 8 bright versions)
25
+ * - Mode 2 (P256): 256 colors (used for more variety)
26
+ * - Mode 3 (RGB): True color with Red, Green, Blue values (16.7 million colors)
27
+ *
28
+ * @param colorValue - The packed color information from the terminal
29
+ * @returns 0, 1, 2, or 3 indicating which color system to use
30
+ */
31
+ private static getColorMode;
32
+ /**
33
+ * Extracts the actual color value from the packed color information.
34
+ *
35
+ * The extraction method depends on the color mode:
36
+ * - For RGB mode: Extracts all three color components (R, G, B)
37
+ * - For palette modes: Extracts just the color index number
38
+ *
39
+ * Think of it like unpacking a suitcase - different items are packed differently
40
+ *
41
+ * @param colorValue - The packed color information from the terminal
42
+ * @returns The actual color value (either RGB values or palette index)
43
+ */
44
+ private static extractColor;
45
+ /**
46
+ * Converts a color value into the special text codes that terminals understand.
47
+ *
48
+ * Terminals use "ANSI escape sequences" - special character combinations that control
49
+ * how text appears. It's like HTML tags, but for terminals.
50
+ *
51
+ * Examples of what this function produces:
52
+ * - Red text: "\x1b[31m" (tells terminal "make the following text red")
53
+ * - Blue background: "\x1b[44m" (tells terminal "make the background blue")
54
+ * - RGB color: "\x1b[38;2;255;128;0m" (orange text using RGB values)
55
+ *
56
+ * @param colorValue - The color to convert (packed number with mode and color data)
57
+ * @param isBackground - true for background color, false for text color
58
+ * @returns ANSI escape sequence string that terminals can interpret
59
+ */
60
+ private static colorToAnsi;
61
+ /**
62
+ * Converts a single line of terminal content into text with color/style codes.
63
+ *
64
+ * This function processes each character in a line and:
65
+ * 1. Extracts the character itself
66
+ * 2. Checks its color (text and background)
67
+ * 3. Checks its style (bold, italic, underline, etc.)
68
+ * 4. Adds the necessary codes to recreate that appearance
69
+ *
70
+ * It's like going through a line character by character and noting:
71
+ * "This letter is red, this one is bold, this one has blue background..."
72
+ *
73
+ * @param line - One line from the terminal screen
74
+ * @param cols - Number of columns (width) of the terminal
75
+ * @param trimRight - Whether to remove trailing spaces (like right-trim in text editors)
76
+ * @returns The line as text with embedded color/style codes
77
+ */
78
+ private static serializeLine;
79
+ /**
80
+ * Converts the entire terminal screen (or part of it) into text with colors preserved.
81
+ *
82
+ * This is the main function that processes multiple lines of terminal content.
83
+ * It's like taking a "text screenshot" of your terminal that can be replayed later
84
+ * with all the colors and formatting intact.
85
+ *
86
+ * @param terminal - The terminal object containing the screen buffer
87
+ * @param options - Configuration options:
88
+ * - startLine: First line to include (default: 0, the top)
89
+ * - endLine: Last line to include (default: bottom of screen)
90
+ * - trimRight: Remove trailing spaces from each line (default: true)
91
+ * - includeEmptyLines: Keep blank lines in output (default: true)
92
+ * @returns Multi-line string with embedded ANSI codes for colors/styles
93
+ */
94
+ static serialize(terminal: Terminal, options?: {
95
+ startLine?: number;
96
+ endLine?: number;
97
+ trimRight?: boolean;
98
+ includeEmptyLines?: boolean;
99
+ }): string;
100
+ /**
101
+ * Convenience function to get just the last few lines from the terminal.
102
+ *
103
+ * Useful when you only need recent output, like:
104
+ * - Getting the last error message
105
+ * - Showing recent command output
106
+ * - Displaying the current prompt
107
+ *
108
+ * Example: getLastLines(terminal, 10) gets the last 10 lines
109
+ *
110
+ * @param terminal - The terminal object containing the screen buffer
111
+ * @param lineCount - How many lines from the bottom to include
112
+ * @param options - Same options as serialize() for controlling output format
113
+ * @returns The requested lines as text with color/style codes
114
+ */
115
+ static getLastLines(terminal: Terminal, lineCount: number, options?: {
116
+ trimRight?: boolean;
117
+ includeEmptyLines?: boolean;
118
+ }): string;
119
+ }