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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tabminal",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "A modern, persistent web terminal with multi-tab support and real-time system monitoring.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
@@ -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
- initFilePath = path.join(os.tmpdir(), `tabminal-init-${id}.bashrc`);
77
- const bashScript = `
78
- export PATH="${shellToolsPath}:$PATH"
79
- [ -f ~/.bashrc ] && source ~/.bashrc
80
- export PATH="${shellToolsPath}:$PATH"
81
-
82
- _tabminal_bash_preexec() {
83
- # Prevent capturing any of our own internal or setup commands.
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(shell, args, ptyOptions);
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
  });
@@ -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);