tabminal 2.0.11 → 2.0.12
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/package.json +1 -1
- package/shell/tabminal-bashrc +17 -0
- package/shell/tabminal-hooks.bash +78 -0
- package/src/terminal-manager.mjs +32 -43
- package/src/terminal-session.mjs +29 -1
package/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
if [[ -n "${TABMINAL_SHELL_TOOLS_PATH:-}" ]]; then
|
|
2
|
+
export PATH="${TABMINAL_SHELL_TOOLS_PATH}:$PATH"
|
|
3
|
+
fi
|
|
4
|
+
|
|
5
|
+
if [[ -f ~/.bashrc ]]; then
|
|
6
|
+
source ~/.bashrc
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
if [[ -n "${TABMINAL_SHELL_TOOLS_PATH:-}" ]]; then
|
|
10
|
+
export PATH="${TABMINAL_SHELL_TOOLS_PATH}:$PATH"
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
if [[ -n "${TABMINAL_HOOKS_PATH:-}" ]] && [[ -f "$TABMINAL_HOOKS_PATH" ]]; then
|
|
14
|
+
source "$TABMINAL_HOOKS_PATH"
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
TABMINAL_SHELL_READY=1
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
if [[ -z "${TABMINAL_SESSION_ID:-}" ]]; then
|
|
2
|
+
return 0
|
|
3
|
+
fi
|
|
4
|
+
|
|
5
|
+
if [[ -n "${TABMINAL_BASH_HOOKS_LOADED:-}" ]]; then
|
|
6
|
+
return 0
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
TABMINAL_BASH_HOOKS_LOADED=1
|
|
10
|
+
|
|
11
|
+
_tabminal_bash_preexec() {
|
|
12
|
+
if [[ "${BASH_COMMAND:-}" == *"_tabminal_"* ]]; then
|
|
13
|
+
return
|
|
14
|
+
fi
|
|
15
|
+
if [[ "${BASH_COMMAND:-}" == "${PROMPT_COMMAND:-}" ]]; then
|
|
16
|
+
return
|
|
17
|
+
fi
|
|
18
|
+
_tabminal_last_command="$BASH_COMMAND"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_tabminal_bash_postexec() {
|
|
22
|
+
local exit_code="$?"
|
|
23
|
+
if [[ -n "${_tabminal_last_command:-}" ]]; then
|
|
24
|
+
local command_b64
|
|
25
|
+
command_b64=$(
|
|
26
|
+
echo -n "$_tabminal_last_command" | base64 | tr -d '\n'
|
|
27
|
+
)
|
|
28
|
+
printf '\x1b]1337;ExitCode=%s;CommandB64=%s\x07' \
|
|
29
|
+
"$exit_code" "$command_b64"
|
|
30
|
+
_tabminal_last_command=''
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_tabminal_apply_prompt_marker() {
|
|
35
|
+
local marker=$'\[\e]1337;TabminalPrompt\a\]'
|
|
36
|
+
if [[ "${PS1:-}" != *'TabminalPrompt'* ]]; then
|
|
37
|
+
PS1="${PS1}${marker}"
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
_tabminal_prompt_contains() {
|
|
42
|
+
local needle="$1"
|
|
43
|
+
local current="${PROMPT_COMMAND:-}"
|
|
44
|
+
[[ "$current" == *"$needle"* ]]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_tabminal_install_prompt_command() {
|
|
48
|
+
if ! _tabminal_prompt_contains '_tabminal_bash_postexec'; then
|
|
49
|
+
if [[ -n "${PROMPT_COMMAND:-}" ]]; then
|
|
50
|
+
printf -v PROMPT_COMMAND '_tabminal_bash_postexec; %s' \
|
|
51
|
+
"$PROMPT_COMMAND"
|
|
52
|
+
else
|
|
53
|
+
PROMPT_COMMAND='_tabminal_bash_postexec'
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if ! _tabminal_prompt_contains '_tabminal_apply_prompt_marker'; then
|
|
58
|
+
if [[ -n "${PROMPT_COMMAND:-}" ]]; then
|
|
59
|
+
PROMPT_COMMAND="${PROMPT_COMMAND}; _tabminal_apply_prompt_marker"
|
|
60
|
+
else
|
|
61
|
+
PROMPT_COMMAND='_tabminal_apply_prompt_marker'
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_tabminal_install_tmux_wrapper() {
|
|
67
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
68
|
+
return 0
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
tmux() {
|
|
72
|
+
command tmux -u "$@"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
trap '_tabminal_bash_preexec' DEBUG
|
|
77
|
+
_tabminal_install_prompt_command
|
|
78
|
+
_tabminal_install_tmux_wrapper
|
package/src/terminal-manager.mjs
CHANGED
|
@@ -37,6 +37,25 @@ const initialRows = Number.parseInt(
|
|
|
37
37
|
10
|
|
38
38
|
) || 30;
|
|
39
39
|
|
|
40
|
+
function buildBashBootstrap({
|
|
41
|
+
env,
|
|
42
|
+
shell,
|
|
43
|
+
shellToolsPath,
|
|
44
|
+
sessionId
|
|
45
|
+
}) {
|
|
46
|
+
const hookPath = path.join(shellToolsPath, 'tabminal-hooks.bash');
|
|
47
|
+
const rcfilePath = path.join(shellToolsPath, 'tabminal-bashrc');
|
|
48
|
+
|
|
49
|
+
env.TABMINAL_SESSION_ID = sessionId;
|
|
50
|
+
env.TABMINAL_SHELL_TOOLS_PATH = shellToolsPath;
|
|
51
|
+
env.TABMINAL_HOOKS_PATH = hookPath;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
shell,
|
|
55
|
+
args: ['--rcfile', rcfilePath, '-i']
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
40
59
|
export class TerminalManager {
|
|
41
60
|
constructor() {
|
|
42
61
|
this.sessions = new Map();
|
|
@@ -66,55 +85,25 @@ export class TerminalManager {
|
|
|
66
85
|
? `${shellToolsPath}${pathDelimiter}${existingPath}`
|
|
67
86
|
: shellToolsPath;
|
|
68
87
|
|
|
88
|
+
let spawnShell = shell;
|
|
69
89
|
let args = [];
|
|
70
|
-
let initFilePath = null;
|
|
71
90
|
let initDirPath = null;
|
|
72
91
|
|
|
73
92
|
try {
|
|
74
93
|
const shellName = path.basename(shell);
|
|
75
94
|
if (shellName === 'bash') {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if [[ "$BASH_COMMAND" == *"_tabminal_"* || "$BASH_COMMAND" == "$PROMPT_COMMAND" ]]; then
|
|
85
|
-
return
|
|
86
|
-
fi
|
|
87
|
-
_tabminal_last_command="$BASH_COMMAND"
|
|
88
|
-
}
|
|
89
|
-
trap '_tabminal_bash_preexec' DEBUG
|
|
90
|
-
|
|
91
|
-
_tabminal_bash_postexec() {
|
|
92
|
-
local EC="$?"
|
|
93
|
-
if [[ -n "$_tabminal_last_command" ]]; then
|
|
94
|
-
local CMD=$(echo -n "$_tabminal_last_command" | base64 | tr -d '\\n')
|
|
95
|
-
printf "\\x1b]1337;ExitCode=%s;CommandB64=%s\\x07" "$EC" "$CMD"
|
|
96
|
-
_tabminal_last_command="" # Reset after use
|
|
97
|
-
fi
|
|
98
|
-
}
|
|
99
|
-
_tabminal_apply_prompt_marker() {
|
|
100
|
-
local marker=$'\\[\\e]1337;TabminalPrompt\\a\\]'
|
|
101
|
-
if [[ "$PS1" != *"TabminalPrompt"* ]]; then
|
|
102
|
-
PS1="$PS1$marker"
|
|
103
|
-
fi
|
|
104
|
-
}
|
|
105
|
-
if [[ -n "$PROMPT_COMMAND" ]]; then
|
|
106
|
-
printf -v PROMPT_COMMAND "_tabminal_bash_postexec; %s; _tabminal_apply_prompt_marker" "$PROMPT_COMMAND"
|
|
107
|
-
else
|
|
108
|
-
PROMPT_COMMAND="_tabminal_bash_postexec; _tabminal_apply_prompt_marker"
|
|
109
|
-
fi
|
|
110
|
-
export PROMPT_COMMAND
|
|
111
|
-
`;
|
|
112
|
-
fs.writeFileSync(initFilePath, bashScript);
|
|
113
|
-
args = ['--rcfile', initFilePath, '-i'];
|
|
95
|
+
const bootstrap = buildBashBootstrap({
|
|
96
|
+
env,
|
|
97
|
+
shell,
|
|
98
|
+
shellToolsPath,
|
|
99
|
+
sessionId: id
|
|
100
|
+
});
|
|
101
|
+
spawnShell = bootstrap.shell;
|
|
102
|
+
args = bootstrap.args;
|
|
114
103
|
} else if (shellName === 'zsh') {
|
|
115
104
|
initDirPath = path.join(os.tmpdir(), `tabminal-zsh-${id}`);
|
|
116
105
|
fs.mkdirSync(initDirPath, { recursive: true });
|
|
117
|
-
initFilePath = path.join(initDirPath, '.zshrc');
|
|
106
|
+
const initFilePath = path.join(initDirPath, '.zshrc');
|
|
118
107
|
|
|
119
108
|
const zshScript = `
|
|
120
109
|
unset ZDOTDIR
|
|
@@ -165,10 +154,11 @@ precmd_functions+=(_tabminal_zsh_apply_prompt_marker)
|
|
|
165
154
|
if (process.platform !== 'win32') {
|
|
166
155
|
ptyOptions.encoding = 'utf8';
|
|
167
156
|
}
|
|
168
|
-
ptyProcess = pty.spawn(
|
|
157
|
+
ptyProcess = pty.spawn(spawnShell, args, ptyOptions);
|
|
169
158
|
} catch (err) {
|
|
170
159
|
const spawnInfo = {
|
|
171
|
-
shell,
|
|
160
|
+
shell: spawnShell,
|
|
161
|
+
requestedShell: shell,
|
|
172
162
|
args,
|
|
173
163
|
cwd: initialCwd,
|
|
174
164
|
cols,
|
|
@@ -214,7 +204,6 @@ precmd_functions+=(_tabminal_zsh_apply_prompt_marker)
|
|
|
214
204
|
this.removeSession(id);
|
|
215
205
|
// Cleanup temp files
|
|
216
206
|
try {
|
|
217
|
-
if (initFilePath && fs.existsSync(initFilePath)) fs.unlinkSync(initFilePath);
|
|
218
207
|
if (initDirPath && fs.existsSync(initDirPath)) fs.rmSync(initDirPath, { recursive: true, force: true });
|
|
219
208
|
} catch { /* ignore cleanup errors */ }
|
|
220
209
|
});
|
package/src/terminal-session.mjs
CHANGED
|
@@ -20,11 +20,31 @@ const TITLE_POLL_INTERVAL_MS = 3000;
|
|
|
20
20
|
|
|
21
21
|
const IGNORED_COMMANDS = [
|
|
22
22
|
'export PROMPT_COMMAND',
|
|
23
|
-
'__bash_prompt'
|
|
23
|
+
'__bash_prompt',
|
|
24
|
+
'TABMINAL_SHELL_READY=1'
|
|
24
25
|
];
|
|
25
26
|
|
|
26
27
|
const PROMPT_PREFIX = "You are now operating as an AI terminal assistant. Your name is `Tabminal`. You will assist users in resolving terminal or coding issues and answering other inquiries. When troubleshooting terminal errors, you will be provided with the execution history to understand the context. However, please focus primarily on the most recent runtime errors and the user's latest questions. Keep your answers concise and accurate. Resolve the issue clearly and provide the reasoning while avoiding lengthy elaborations. Most user terminal variable keys are normal under typical circumstances and do not need to be treated as security risks.\n\n";
|
|
27
28
|
|
|
29
|
+
function splitTrailingPartialSequence(chunk) {
|
|
30
|
+
if (!chunk) {
|
|
31
|
+
return { complete: '', partial: '' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const oscStart = chunk.lastIndexOf('\u001b]1337;');
|
|
35
|
+
if (oscStart >= 0) {
|
|
36
|
+
const oscTail = chunk.slice(oscStart);
|
|
37
|
+
if (!oscTail.includes('\u0007')) {
|
|
38
|
+
return {
|
|
39
|
+
complete: chunk.slice(0, oscStart),
|
|
40
|
+
partial: oscTail
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { complete: chunk, partial: '' };
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
export class TerminalSession {
|
|
29
49
|
constructor(pty, options = {}) {
|
|
30
50
|
this.pty = pty;
|
|
@@ -55,6 +75,7 @@ export class TerminalSession {
|
|
|
55
75
|
this.captureStartedAt = null;
|
|
56
76
|
this.lastExecution = null;
|
|
57
77
|
this.skipNextShellLog = false;
|
|
78
|
+
this.partialSequenceBuffer = '';
|
|
58
79
|
|
|
59
80
|
this.ansiParser = new AnsiParser({
|
|
60
81
|
inst_o: (s) => {
|
|
@@ -83,6 +104,13 @@ export class TerminalSession {
|
|
|
83
104
|
if (this.suppressPtyOutput) return;
|
|
84
105
|
|
|
85
106
|
if (typeof chunk !== 'string') chunk = chunk.toString('utf8');
|
|
107
|
+
if (this.partialSequenceBuffer) {
|
|
108
|
+
chunk = this.partialSequenceBuffer + chunk;
|
|
109
|
+
this.partialSequenceBuffer = '';
|
|
110
|
+
}
|
|
111
|
+
const split = splitTrailingPartialSequence(chunk);
|
|
112
|
+
chunk = split.complete;
|
|
113
|
+
this.partialSequenceBuffer = split.partial;
|
|
86
114
|
|
|
87
115
|
if (this.manager) {
|
|
88
116
|
this.manager.appendLog(this.id, chunk);
|