@k1e1n04/mav 0.1.18 → 0.1.19
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/package.json +2 -5
- package/dist/src/ui/app.d.ts +4 -2
- package/dist/src/ui/app.js +32 -46
- package/dist/src/ui/app.js.map +1 -1
- package/dist/src/ui/detail.d.ts +5 -4
- package/dist/src/ui/detail.js +16 -18
- package/dist/src/ui/detail.js.map +1 -1
- package/dist/src/ui/overview.d.ts +21 -8
- package/dist/src/ui/overview.js +269 -233
- package/dist/src/ui/overview.js.map +1 -1
- package/dist/src/ui/terminal.d.ts +28 -0
- package/dist/src/ui/terminal.js +74 -0
- package/dist/src/ui/terminal.js.map +1 -0
- package/package.json +2 -5
- package/src/ui/app.ts +36 -46
- package/src/ui/detail.ts +19 -21
- package/src/ui/overview.ts +291 -232
- package/src/ui/terminal.ts +104 -0
- package/src/types/neo-blessed.d.ts +0 -5
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface KeyInfo {
|
|
2
|
+
name?: string;
|
|
3
|
+
ctrl?: boolean;
|
|
4
|
+
meta?: boolean;
|
|
5
|
+
shift?: boolean;
|
|
6
|
+
sequence?: string;
|
|
7
|
+
}
|
|
8
|
+
type KeypressHandler = (str: string, key: KeyInfo) => void;
|
|
9
|
+
type DataHandler = (chunk: string | Buffer) => void;
|
|
10
|
+
type ResizeHandler = () => void;
|
|
11
|
+
export declare class TerminalUI {
|
|
12
|
+
readonly input: NodeJS.ReadStream;
|
|
13
|
+
readonly output: NodeJS.WriteStream;
|
|
14
|
+
private resizeEmitter;
|
|
15
|
+
constructor(input?: NodeJS.ReadStream, output?: NodeJS.WriteStream);
|
|
16
|
+
get cols(): number;
|
|
17
|
+
get rows(): number;
|
|
18
|
+
onKeypress(handler: KeypressHandler): () => void;
|
|
19
|
+
onData(handler: DataHandler): () => void;
|
|
20
|
+
onResize(handler: ResizeHandler): () => void;
|
|
21
|
+
write(data: string): void;
|
|
22
|
+
clearScreen(): void;
|
|
23
|
+
render(content: string): void;
|
|
24
|
+
enterAlternateScreen(): void;
|
|
25
|
+
exitAlternateScreen(): void;
|
|
26
|
+
destroy(): void;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { emitKeypressEvents } from 'node:readline';
|
|
3
|
+
export class TerminalUI {
|
|
4
|
+
input;
|
|
5
|
+
output;
|
|
6
|
+
resizeEmitter = new EventEmitter();
|
|
7
|
+
constructor(input = process.stdin, output = process.stdout) {
|
|
8
|
+
this.input = input;
|
|
9
|
+
this.output = output;
|
|
10
|
+
emitKeypressEvents(this.input);
|
|
11
|
+
if (this.input.isTTY && typeof this.input.setRawMode === 'function') {
|
|
12
|
+
this.input.setRawMode(true);
|
|
13
|
+
}
|
|
14
|
+
this.input.resume();
|
|
15
|
+
if ('on' in this.output) {
|
|
16
|
+
this.output.on('resize', () => {
|
|
17
|
+
this.resizeEmitter.emit('resize');
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
this.write('\x1b]0;mav\x07');
|
|
21
|
+
}
|
|
22
|
+
get cols() {
|
|
23
|
+
return ('columns' in this.output && typeof this.output.columns === 'number')
|
|
24
|
+
? this.output.columns
|
|
25
|
+
: 80;
|
|
26
|
+
}
|
|
27
|
+
get rows() {
|
|
28
|
+
return ('rows' in this.output && typeof this.output.rows === 'number')
|
|
29
|
+
? this.output.rows
|
|
30
|
+
: 24;
|
|
31
|
+
}
|
|
32
|
+
onKeypress(handler) {
|
|
33
|
+
const wrapped = (str, key) => handler(str, key);
|
|
34
|
+
this.input.on('keypress', wrapped);
|
|
35
|
+
return () => {
|
|
36
|
+
this.input.off('keypress', wrapped);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
onData(handler) {
|
|
40
|
+
this.input.on('data', handler);
|
|
41
|
+
return () => {
|
|
42
|
+
this.input.off('data', handler);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
onResize(handler) {
|
|
46
|
+
this.resizeEmitter.on('resize', handler);
|
|
47
|
+
return () => {
|
|
48
|
+
this.resizeEmitter.off('resize', handler);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
write(data) {
|
|
52
|
+
this.output.write(data);
|
|
53
|
+
}
|
|
54
|
+
clearScreen() {
|
|
55
|
+
this.write('\x1b[H\x1b[2J');
|
|
56
|
+
}
|
|
57
|
+
render(content) {
|
|
58
|
+
this.clearScreen();
|
|
59
|
+
this.write(content);
|
|
60
|
+
}
|
|
61
|
+
enterAlternateScreen() {
|
|
62
|
+
this.write('\x1b[?1049h');
|
|
63
|
+
}
|
|
64
|
+
exitAlternateScreen() {
|
|
65
|
+
this.write('\x1b[?1049l');
|
|
66
|
+
}
|
|
67
|
+
destroy() {
|
|
68
|
+
this.exitAlternateScreen();
|
|
69
|
+
if (this.input.isTTY && typeof this.input.setRawMode === 'function') {
|
|
70
|
+
this.input.setRawMode(false);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=terminal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../../src/ui/terminal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAclD,MAAM,OAAO,UAAU;IACZ,KAAK,CAAmB;IACxB,MAAM,CAAoB;IAC3B,aAAa,GAAG,IAAI,YAAY,EAAE,CAAA;IAE1C,YACE,QAA2B,OAAO,CAAC,KAAK,EACxC,SAA6B,OAAO,CAAC,MAAM;QAE3C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QAEpB,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC9B,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;YACpE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QAC7B,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAA;QAEnB,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;gBAC5B,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACnC,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAA;IAC9B,CAAC;IAED,IAAI,IAAI;QACN,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC;YAC1E,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;YACrB,CAAC,CAAC,EAAE,CAAA;IACR,CAAC;IAED,IAAI,IAAI;QACN,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC;YACpE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI;YAClB,CAAC,CAAC,EAAE,CAAA;IACR,CAAC;IAED,UAAU,CAAC,OAAwB;QACjC,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,GAAY,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;QAChE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;QAClC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;QACrC,CAAC,CAAA;IACH,CAAC;IAED,MAAM,CAAC,OAAoB;QACzB,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAC9B,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACjC,CAAC,CAAA;IACH,CAAC;IAED,QAAQ,CAAC,OAAsB;QAC7B,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QACxC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAC3C,CAAC,CAAA;IACH,CAAC;IAED,KAAK,CAAC,IAAY;QAChB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IAED,WAAW;QACT,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;IAC7B,CAAC;IAED,MAAM,CAAC,OAAe;QACpB,IAAI,CAAC,WAAW,EAAE,CAAA;QAClB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACrB,CAAC;IAED,oBAAoB;QAClB,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;IAC3B,CAAC;IAED,mBAAmB;QACjB,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;IAC3B,CAAC;IAED,OAAO;QACL,IAAI,CAAC,mBAAmB,EAAE,CAAA;QAC1B,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;YACpE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;QAC9B,CAAC;IACH,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k1e1n04/mav",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "Multi-agent view — manage multiple AI CLI sessions in one terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,9 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"commander": "13.1.0",
|
|
37
37
|
"js-yaml": "4.1.0",
|
|
38
|
-
"
|
|
39
|
-
"node-pty": "1.0.0",
|
|
40
|
-
"term.js": "^0.0.7"
|
|
38
|
+
"node-pty": "1.0.0"
|
|
41
39
|
},
|
|
42
40
|
"pnpm": {
|
|
43
41
|
"onlyBuiltDependencies": [
|
|
@@ -46,7 +44,6 @@
|
|
|
46
44
|
]
|
|
47
45
|
},
|
|
48
46
|
"devDependencies": {
|
|
49
|
-
"@types/blessed": "^0.1.27",
|
|
50
47
|
"@types/js-yaml": "4.0.9",
|
|
51
48
|
"@types/node": "22.15.21",
|
|
52
49
|
"tsx": "4.19.3",
|
package/src/ui/app.ts
CHANGED
|
@@ -1,95 +1,87 @@
|
|
|
1
|
-
import blessed from 'neo-blessed'
|
|
2
|
-
import type { Widgets } from 'neo-blessed'
|
|
3
1
|
import type { AgentSession } from '../agent.js'
|
|
4
2
|
import type { SessionManager } from '../session-manager.js'
|
|
5
3
|
import { saveState } from '../state.js'
|
|
6
|
-
import { OverviewUI } from './overview.js'
|
|
7
4
|
import { DetailUI } from './detail.js'
|
|
5
|
+
import { OverviewUI } from './overview.js'
|
|
6
|
+
import type { KeyInfo } from './terminal.js'
|
|
7
|
+
import { TerminalUI } from './terminal.js'
|
|
8
8
|
|
|
9
9
|
type Mode = 'overview' | 'detail'
|
|
10
10
|
|
|
11
11
|
export class App {
|
|
12
|
-
private screen: Widgets.Screen
|
|
13
12
|
private manager: SessionManager
|
|
14
13
|
private statePath: string
|
|
14
|
+
private terminal: TerminalUI
|
|
15
15
|
private overviewUI: OverviewUI
|
|
16
16
|
private detailUI: DetailUI
|
|
17
17
|
private mode: Mode = 'overview'
|
|
18
18
|
|
|
19
|
-
constructor(manager: SessionManager, statePath: string) {
|
|
19
|
+
constructor(manager: SessionManager, statePath: string, terminal = new TerminalUI()) {
|
|
20
20
|
this.manager = manager
|
|
21
21
|
this.statePath = statePath
|
|
22
|
+
this.terminal = terminal
|
|
22
23
|
|
|
23
|
-
this.
|
|
24
|
-
smartCSR: true,
|
|
25
|
-
title: 'mav',
|
|
26
|
-
fullUnicode: true,
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
this.overviewUI = new OverviewUI(this.screen, manager, (session) => {
|
|
24
|
+
this.overviewUI = new OverviewUI(terminal, manager, (session) => {
|
|
30
25
|
if (session) {
|
|
31
26
|
this.switchToDetail(session)
|
|
32
27
|
}
|
|
33
28
|
})
|
|
34
|
-
this.detailUI = new DetailUI(
|
|
29
|
+
this.detailUI = new DetailUI(terminal, () => {
|
|
35
30
|
if (this.mode === 'detail') {
|
|
36
31
|
this.switchToOverview()
|
|
37
32
|
}
|
|
38
33
|
})
|
|
39
34
|
|
|
40
|
-
const proto = Object.getPrototypeOf(this.screen) as { render?: () => void }
|
|
41
|
-
const baseRender = proto.render ?? this.screen.render
|
|
42
|
-
const protoRender = baseRender.bind(this.screen)
|
|
43
|
-
;(this.screen as unknown as { render: () => void }).render = () => {
|
|
44
|
-
if (this.mode === 'detail') return
|
|
45
|
-
protoRender()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
35
|
this.bindGlobalKeys()
|
|
49
36
|
|
|
50
|
-
this.
|
|
51
|
-
const cols = this.
|
|
52
|
-
const rows = this.
|
|
37
|
+
this.terminal.onResize(() => {
|
|
38
|
+
const cols = this.terminal.cols
|
|
39
|
+
const rows = this.terminal.rows
|
|
53
40
|
if (this.mode === 'detail') {
|
|
54
41
|
this.detailUI.resize(cols, rows)
|
|
55
42
|
return
|
|
56
43
|
}
|
|
57
44
|
|
|
58
45
|
this.overviewUI.resizeSelectedSession()
|
|
59
|
-
this.screen.render()
|
|
60
46
|
})
|
|
61
47
|
}
|
|
62
48
|
|
|
63
49
|
private bindGlobalKeys(): void {
|
|
64
|
-
this.
|
|
65
|
-
if (this.mode === 'detail') return
|
|
66
|
-
this.shutdown()
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
this.screen.key('C-c', () => {
|
|
50
|
+
this.terminal.onKeypress((str, key) => {
|
|
70
51
|
if (this.mode === 'overview') {
|
|
71
|
-
this.
|
|
52
|
+
this.handleOverviewKeypress(str, key)
|
|
72
53
|
}
|
|
73
54
|
})
|
|
55
|
+
}
|
|
74
56
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
57
|
+
private handleOverviewKeypress(str: string, key: KeyInfo): void {
|
|
58
|
+
if (key.ctrl && key.name === 'c') {
|
|
59
|
+
this.shutdown()
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (str === 'q') {
|
|
64
|
+
this.shutdown()
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!this.overviewUI.isPromptOpen() && (key.name === 'right' || key.name === 'enter' || key.name === 'return')) {
|
|
78
69
|
const session = this.manager.selectedSession
|
|
79
70
|
if (!session) return
|
|
80
|
-
this.switchToDetail()
|
|
81
|
-
|
|
71
|
+
this.switchToDetail(session)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.overviewUI.handleKeypress(str, key)
|
|
82
76
|
}
|
|
83
77
|
|
|
84
78
|
private switchToDetail(session: AgentSession | null = this.manager.selectedSession): void {
|
|
85
79
|
if (!session) return
|
|
86
80
|
this.mode = 'detail'
|
|
87
81
|
this.overviewUI.hide()
|
|
88
|
-
this.
|
|
89
|
-
this.screen.program.disableMouse()
|
|
90
|
-
this.screen.realloc()
|
|
82
|
+
this.terminal.exitAlternateScreen()
|
|
91
83
|
this.detailUI.attach(session)
|
|
92
|
-
this.detailUI.resize(this.
|
|
84
|
+
this.detailUI.resize(this.terminal.cols, this.terminal.rows)
|
|
93
85
|
this.detailUI.show()
|
|
94
86
|
}
|
|
95
87
|
|
|
@@ -97,9 +89,7 @@ export class App {
|
|
|
97
89
|
this.mode = 'overview'
|
|
98
90
|
this.detailUI.detach()
|
|
99
91
|
this.detailUI.hide()
|
|
100
|
-
this.
|
|
101
|
-
this.screen.program.enableMouse()
|
|
102
|
-
this.screen.realloc()
|
|
92
|
+
this.terminal.enterAlternateScreen()
|
|
103
93
|
this.overviewUI.show()
|
|
104
94
|
}
|
|
105
95
|
|
|
@@ -110,13 +100,13 @@ export class App {
|
|
|
110
100
|
// 終了シーケンスは継続する
|
|
111
101
|
}
|
|
112
102
|
this.manager.killAll()
|
|
113
|
-
this.
|
|
103
|
+
this.terminal.destroy()
|
|
114
104
|
process.exit(0)
|
|
115
105
|
}
|
|
116
106
|
|
|
117
107
|
start(): void {
|
|
108
|
+
this.terminal.enterAlternateScreen()
|
|
118
109
|
this.overviewUI.show()
|
|
119
110
|
this.overviewUI.resizeSelectedSession()
|
|
120
|
-
this.screen.render()
|
|
121
111
|
}
|
|
122
112
|
}
|
package/src/ui/detail.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { appendFileSync } from 'node:fs'
|
|
2
|
-
import type { Widgets } from 'neo-blessed'
|
|
3
2
|
import type { AgentSession } from '../agent.js'
|
|
3
|
+
import type { TerminalUI } from './terminal.js'
|
|
4
4
|
|
|
5
5
|
export class DetailUI {
|
|
6
6
|
private static readonly KEYBOARD_FLAGS_RESPONSE_PATTERN = /\x1b\[\?(\d+)u/g
|
|
7
7
|
private static readonly PENDING_INPUT_FLUSH_DELAY_MS = 25
|
|
8
|
+
private static readonly EXIT_HINT =
|
|
9
|
+
'\x1b[7m mav detail \x1b[27m Press Ctrl+] to return to Overview\r\n\r\n'
|
|
8
10
|
|
|
9
11
|
private static readonly EXIT_SEQUENCES = [
|
|
10
12
|
'\x1d',
|
|
@@ -16,24 +18,24 @@ export class DetailUI {
|
|
|
16
18
|
/^\x1b\[99;5u$/,
|
|
17
19
|
] as const
|
|
18
20
|
|
|
19
|
-
private
|
|
21
|
+
private terminal: TerminalUI
|
|
20
22
|
private onExitDetail: () => void
|
|
21
23
|
private currentSession: AgentSession | null = null
|
|
22
24
|
private dataListener: ((data: string) => void) | null = null
|
|
23
|
-
private
|
|
25
|
+
private rawInputDisposer: (() => void) | null = null
|
|
24
26
|
private pendingInput = ''
|
|
25
27
|
private pendingInputTimer: ReturnType<typeof setTimeout> | null = null
|
|
26
28
|
private keyboardEnhancementFlags: string | null = null
|
|
27
29
|
|
|
28
|
-
constructor(
|
|
29
|
-
this.
|
|
30
|
+
constructor(terminal: TerminalUI, onExitDetail: () => void) {
|
|
31
|
+
this.terminal = terminal
|
|
30
32
|
this.onExitDetail = onExitDetail
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
private isExitShortcut(input: string): boolean {
|
|
34
36
|
return (
|
|
35
37
|
DetailUI.EXIT_SEQUENCES.includes(
|
|
36
|
-
|
|
38
|
+
input as (typeof DetailUI.EXIT_SEQUENCES)[number]
|
|
37
39
|
) ||
|
|
38
40
|
DetailUI.EXIT_SEQUENCE_PATTERNS.some((pattern) => pattern.test(input))
|
|
39
41
|
)
|
|
@@ -84,25 +86,24 @@ export class DetailUI {
|
|
|
84
86
|
this.currentSession = session
|
|
85
87
|
this.pendingInput = ''
|
|
86
88
|
|
|
87
|
-
const { input, output } = this.screen.program
|
|
88
|
-
|
|
89
89
|
const safeLog = session.logBuffer
|
|
90
90
|
.join('')
|
|
91
91
|
.replace(/\x1b\[\?104[79][hl]|\x1b\[\?47[hl]/g, '')
|
|
92
92
|
.replace(/\x1b\[(?:>?\d*c|\?u|>q|\?\d+\$p)/g, '')
|
|
93
93
|
if (this.keyboardEnhancementFlags) {
|
|
94
|
-
|
|
94
|
+
this.terminal.write(`\x1b[=${this.keyboardEnhancementFlags}u`)
|
|
95
95
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
this.terminal.write('\x1b[?1l')
|
|
97
|
+
this.terminal.clearScreen()
|
|
98
|
+
this.terminal.write(DetailUI.EXIT_HINT)
|
|
99
|
+
this.terminal.write(safeLog)
|
|
99
100
|
|
|
100
101
|
this.dataListener = (data: string) => {
|
|
101
|
-
|
|
102
|
+
this.terminal.write(data)
|
|
102
103
|
}
|
|
103
104
|
session.on('data', this.dataListener)
|
|
104
105
|
|
|
105
|
-
this.
|
|
106
|
+
this.rawInputDisposer = this.terminal.onData((chunk: string | Buffer) => {
|
|
106
107
|
const str = Buffer.isBuffer(chunk)
|
|
107
108
|
? chunk.toString('utf8')
|
|
108
109
|
: typeof chunk === 'string'
|
|
@@ -134,8 +135,7 @@ export class DetailUI {
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
this.flushPendingInput()
|
|
137
|
-
}
|
|
138
|
-
input.on('data', this.rawInputListener)
|
|
138
|
+
})
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
detach(): void {
|
|
@@ -143,12 +143,10 @@ export class DetailUI {
|
|
|
143
143
|
this.currentSession.off('data', this.dataListener)
|
|
144
144
|
this.dataListener = null
|
|
145
145
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this.rawInputListener = null
|
|
149
|
-
}
|
|
146
|
+
this.rawInputDisposer?.()
|
|
147
|
+
this.rawInputDisposer = null
|
|
150
148
|
if (this.keyboardEnhancementFlags) {
|
|
151
|
-
this.
|
|
149
|
+
this.terminal.write('\x1b[=0u')
|
|
152
150
|
}
|
|
153
151
|
this.clearPendingInputTimer()
|
|
154
152
|
this.pendingInput = ''
|