@electerm/electerm-react 2.3.198 → 2.4.16

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 (72) hide show
  1. package/client/common/clipboard.js +1 -1
  2. package/client/common/constants.js +2 -2
  3. package/client/common/download.jsx +3 -2
  4. package/client/common/error-handler.jsx +5 -9
  5. package/client/common/fetch-from-server.js +1 -1
  6. package/client/common/fetch.jsx +5 -5
  7. package/client/common/icon-helpers.jsx +16 -0
  8. package/client/common/parse-json-safe.js +1 -1
  9. package/client/common/pre.js +0 -7
  10. package/client/common/sftp.js +1 -1
  11. package/client/common/terminal-theme.js +1 -1
  12. package/client/common/transfer.js +2 -2
  13. package/client/common/upgrade.js +2 -2
  14. package/client/components/ai/ai-chat.jsx +10 -1
  15. package/client/components/auth/login.jsx +1 -1
  16. package/client/components/bg/css-overwrite.jsx +1 -1
  17. package/client/components/bookmark-form/form-renderer.jsx +3 -2
  18. package/client/components/common/input-auto-focus.jsx +1 -1
  19. package/client/components/common/message.jsx +131 -0
  20. package/client/components/common/message.styl +58 -0
  21. package/client/components/common/modal.jsx +176 -0
  22. package/client/components/common/modal.styl +22 -0
  23. package/client/components/common/notification-with-details.jsx +1 -1
  24. package/client/components/common/notification.jsx +94 -0
  25. package/client/components/common/notification.styl +51 -0
  26. package/client/components/main/connection-hopping-warnning.jsx +1 -3
  27. package/client/components/main/error-wrapper.jsx +3 -2
  28. package/client/components/main/main.jsx +4 -11
  29. package/client/components/main/upgrade.jsx +6 -4
  30. package/client/components/profile/profile-form-elem.jsx +1 -1
  31. package/client/components/quick-commands/quick-commands-box.jsx +5 -2
  32. package/client/components/quick-commands/quick-commands-form-elem.jsx +1 -1
  33. package/client/components/rdp/rdp-session.jsx +2 -2
  34. package/client/components/session/session.jsx +4 -9
  35. package/client/components/setting-panel/deep-link-control.jsx +2 -1
  36. package/client/components/setting-panel/keyword-input.jsx +60 -0
  37. package/client/components/setting-panel/keywords-form.jsx +2 -7
  38. package/client/components/setting-panel/setting-common.jsx +1 -1
  39. package/client/components/setting-panel/setting-terminal.jsx +1 -1
  40. package/client/components/setting-panel/tab-settings.jsx +1 -1
  41. package/client/components/setting-sync/setting-sync-form.jsx +53 -3
  42. package/client/components/setting-sync/setting-sync.jsx +2 -1
  43. package/client/components/sftp/owner-list.js +6 -6
  44. package/client/components/sftp/sftp-entry.jsx +6 -4
  45. package/client/components/shortcuts/shortcut-editor.jsx +2 -2
  46. package/client/components/ssh-config/ssh-config-load-notify.jsx +3 -2
  47. package/client/components/tabs/tab.jsx +1 -1
  48. package/client/components/tabs/workspace-save-modal.jsx +2 -1
  49. package/client/components/terminal/attach-addon-custom.js +142 -26
  50. package/client/components/terminal/command-tracker-addon.js +164 -53
  51. package/client/components/terminal/highlight-addon.js +84 -43
  52. package/client/components/terminal/shell.js +138 -0
  53. package/client/components/terminal/terminal-command-dropdown.jsx +3 -0
  54. package/client/components/terminal/terminal.jsx +165 -103
  55. package/client/components/theme/theme-form.jsx +2 -1
  56. package/client/components/tree-list/bookmark-transport.jsx +27 -5
  57. package/client/components/vnc/vnc-session.jsx +1 -1
  58. package/client/components/widgets/widget-notification-with-details.jsx +1 -1
  59. package/client/store/common.js +5 -2
  60. package/client/store/db-upgrade.js +1 -1
  61. package/client/store/init-state.js +2 -1
  62. package/client/store/load-data.js +2 -2
  63. package/client/store/mcp-handler.js +9 -50
  64. package/client/store/setting.js +1 -3
  65. package/client/store/store.js +2 -1
  66. package/client/store/sync.js +14 -8
  67. package/client/store/system-menu.js +2 -1
  68. package/client/store/tab.js +1 -1
  69. package/client/store/widgets.js +1 -3
  70. package/package.json +1 -1
  71. package/client/common/track.js +0 -7
  72. package/client/components/batch-op/batch-op-entry.jsx +0 -13
@@ -29,7 +29,7 @@ const linuxListGroup = 'cat /etc/group'
29
29
 
30
30
  export async function remoteListUsers (pid) {
31
31
  const users = await runCmd(pid, linuxListUser)
32
- .catch(log.error)
32
+ .catch(console.error)
33
33
  if (users) {
34
34
  return parseNames(users)
35
35
  }
@@ -38,7 +38,7 @@ export async function remoteListUsers (pid) {
38
38
 
39
39
  export async function remoteListGroups (pid) {
40
40
  const groups = await runCmd(pid, linuxListGroup)
41
- .catch(log.error)
41
+ .catch(console.error)
42
42
  if (groups) {
43
43
  return parseNames(groups)
44
44
  }
@@ -50,7 +50,7 @@ export async function localListUsers () {
50
50
  return {}
51
51
  } else if (isMac) {
52
52
  const g = await fs.run('dscl . -list /Users UniqueID')
53
- .catch(log.error)
53
+ .catch(console.error)
54
54
  return g
55
55
  ? g.split('\n')
56
56
  .reduce((p, s) => {
@@ -65,7 +65,7 @@ export async function localListUsers () {
65
65
  }, {})
66
66
  : {}
67
67
  } else {
68
- const g = await fs.run(linuxListUser).catch(log.error)
68
+ const g = await fs.run(linuxListUser).catch(console.error)
69
69
  return g
70
70
  ? parseNames(g)
71
71
  : {}
@@ -77,7 +77,7 @@ export async function localListGroups () {
77
77
  return {}
78
78
  } else if (isMac) {
79
79
  const g = await fs.run('dscl . list /Groups PrimaryGroupID')
80
- .catch(log.error)
80
+ .catch(console.error)
81
81
  return g
82
82
  ? g.split('\n')
83
83
  .reduce((p, s) => {
@@ -89,7 +89,7 @@ export async function localListGroups () {
89
89
  }, {})
90
90
  : {}
91
91
  } else {
92
- const g = await fs.run(linuxListGroup).catch(log.error)
92
+ const g = await fs.run(linuxListGroup).catch(console.error)
93
93
  return g
94
94
  ? parseNames(g)
95
95
  : {}
@@ -2,7 +2,9 @@ import { Component } from 'react'
2
2
  import { refs } from '../common/ref'
3
3
  import generate from '../../common/uid'
4
4
  import runIdle from '../../common/run-idle'
5
- import { Spin, Modal, notification } from 'antd'
5
+ import { Spin } from 'antd'
6
+ import { notification } from '../common/notification'
7
+ import Modal from '../common/modal'
6
8
  import clone from '../../common/to-simple-obj'
7
9
  import { isEqual, last, isNumber, some, isArray, pick, uniq, debounce } from 'lodash-es'
8
10
  import FileSection from './file-item'
@@ -846,8 +848,8 @@ export default class Sftp extends Component {
846
848
  sftp,
847
849
  realpath
848
850
  ).catch(e => {
849
- log.debug('seems a bad symbolic link')
850
- log.debug(e)
851
+ console.debug('seems a bad symbolic link')
852
+ console.debug(e)
851
853
  return null
852
854
  })
853
855
  if (!realFileInfo) {
@@ -981,7 +983,7 @@ export default class Sftp extends Component {
981
983
  const np = this.parsePath(type, this.state[nt])
982
984
  if (!isValidPath(np)) {
983
985
  return notification.warning({
984
- title: 'path not valid'
986
+ message: 'path not valid'
985
987
  })
986
988
  }
987
989
  this.setState({
@@ -1,9 +1,9 @@
1
1
  import { PureComponent } from 'react'
2
2
  import {
3
3
  Button,
4
- Input,
5
- message
4
+ Input
6
5
  } from 'antd'
6
+ import message from '../common/message'
7
7
  import {
8
8
  EditFilled,
9
9
  CheckOutlined,
@@ -1,5 +1,6 @@
1
1
  import React, { useEffect } from 'react'
2
- import { notification, Button } from 'antd'
2
+ import { Button } from 'antd'
3
+ import { notification } from '../common/notification'
3
4
  import * as ls from '../../common/safe-local-storage'
4
5
  import {
5
6
  sshConfigKey,
@@ -20,7 +21,7 @@ function handleIgnore () {
20
21
 
21
22
  function showNotification () {
22
23
  notification.info({
23
- title: e('loadSshConfigs'),
24
+ message: e('loadSshConfigs'),
24
25
  duration: 0,
25
26
  placement: 'bottom',
26
27
  key: 'sshConfigNotify',
@@ -12,9 +12,9 @@ import {
12
12
  } from '@ant-design/icons'
13
13
  import {
14
14
  Tooltip,
15
- message,
16
15
  Dropdown
17
16
  } from 'antd'
17
+ import message from '../common/message'
18
18
  import classnames from 'classnames'
19
19
  import { pick } from 'lodash-es'
20
20
  import Input from '../common/input-auto-focus'
@@ -5,7 +5,8 @@
5
5
  import React, { useState } from 'react'
6
6
  import { auto } from 'manate/react'
7
7
  import Modal from '../common/modal'
8
- import { Input, Select, Button, Space, message, Radio } from 'antd'
8
+ import { Input, Select, Button, Space, Radio } from 'antd'
9
+ import message from '../common/message'
9
10
  import { SaveOutlined, EditOutlined } from '@ant-design/icons'
10
11
 
11
12
  const e = window.translate
@@ -2,7 +2,6 @@
2
2
  * customize AttachAddon
3
3
  */
4
4
  import { AttachAddon } from '@xterm/addon-attach'
5
- import regEscape from 'escape-string-regexp'
6
5
 
7
6
  export default class AttachAddonCustom extends AttachAddon {
8
7
  constructor (term, socket, isWindowsShell) {
@@ -10,6 +9,80 @@ export default class AttachAddonCustom extends AttachAddon {
10
9
  this.term = term
11
10
  this.socket = socket
12
11
  this.isWindowsShell = isWindowsShell
12
+ // Output suppression state for shell integration injection
13
+ this.outputSuppressed = false
14
+ this.suppressedData = []
15
+ this.suppressTimeout = null
16
+ this.onSuppressionEndCallback = null
17
+ // Track if we've received initial data from the terminal
18
+ this.hasReceivedInitialData = false
19
+ this.onInitialDataCallback = null
20
+ }
21
+
22
+ /**
23
+ * Set callback for when initial data is received
24
+ * @param {Function} callback - Called when first data arrives
25
+ */
26
+ onInitialData = (callback) => {
27
+ if (this.hasReceivedInitialData) {
28
+ // Already received, call immediately
29
+ callback()
30
+ } else {
31
+ this.onInitialDataCallback = callback
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Start suppressing output - used during shell integration injection
37
+ * @param {number} timeout - Max time to suppress in ms (safety fallback)
38
+ * @param {Function} onEnd - Callback when suppression ends
39
+ */
40
+ startOutputSuppression = (timeout = 3000, onEnd = null) => {
41
+ this.outputSuppressed = true
42
+ this.suppressedData = []
43
+ this.onSuppressionEndCallback = onEnd
44
+ // Safety timeout to ensure we always resume
45
+ this.suppressTimeout = setTimeout(() => {
46
+ console.warn('[AttachAddon] Output suppression timeout reached, resuming')
47
+ this.stopOutputSuppression(false)
48
+ }, timeout)
49
+ }
50
+
51
+ /**
52
+ * Stop suppressing output and optionally discard buffered data
53
+ * @param {boolean} discard - If true, discard buffered data; if false, write it to terminal
54
+ */
55
+ stopOutputSuppression = (discard = true) => {
56
+ if (this.suppressTimeout) {
57
+ clearTimeout(this.suppressTimeout)
58
+ this.suppressTimeout = null
59
+ }
60
+ this.outputSuppressed = false
61
+
62
+ if (!discard && this.suppressedData.length > 0) {
63
+ // Write buffered data to terminal
64
+ for (const data of this.suppressedData) {
65
+ this.writeToTerminalDirect(data)
66
+ }
67
+ }
68
+ this.suppressedData = []
69
+
70
+ // Call the end callback if set
71
+ if (this.onSuppressionEndCallback) {
72
+ const callback = this.onSuppressionEndCallback
73
+ this.onSuppressionEndCallback = null
74
+ callback()
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Check if we should resume output based on OSC 633 detection
80
+ * Called when shell integration is detected
81
+ */
82
+ onShellIntegrationDetected = () => {
83
+ if (this.outputSuppressed) {
84
+ this.stopOutputSuppression(true) // Discard the integration command output
85
+ }
13
86
  }
14
87
 
15
88
  activate (terminal = this.term) {
@@ -43,11 +116,76 @@ export default class AttachAddonCustom extends AttachAddon {
43
116
  }
44
117
  }
45
118
 
119
+ /**
120
+ * Check if data contains OSC 633 shell integration sequences
121
+ * @param {string} str - Data string to check
122
+ * @returns {boolean} True if OSC 633 sequence detected
123
+ */
124
+ checkForShellIntegration = (str) => {
125
+ // OSC 633 sequences: ESC]633;X where X is A, B, C, D, E, or P
126
+ // ESC is character code 27 (0x1b)
127
+ // Use includes with the actual characters to avoid lint warning
128
+ const ESC = String.fromCharCode(27)
129
+ return str.includes(ESC + ']633;')
130
+ }
131
+
132
+ /**
133
+ * Write directly to terminal, bypassing suppression check
134
+ * Used for flushing buffered data
135
+ */
136
+ writeToTerminalDirect = (data) => {
137
+ const { term } = this
138
+ if (term.parent?.onZmodem) {
139
+ return
140
+ }
141
+ if (typeof data === 'string') {
142
+ return term.write(data)
143
+ }
144
+ term?.write(data)
145
+ }
146
+
46
147
  writeToTerminal = (data) => {
47
148
  const { term } = this
48
149
  if (term.parent?.onZmodem) {
49
150
  return
50
151
  }
152
+
153
+ // Track initial data arrival
154
+ if (!this.hasReceivedInitialData) {
155
+ this.hasReceivedInitialData = true
156
+ if (this.onInitialDataCallback) {
157
+ const callback = this.onInitialDataCallback
158
+ this.onInitialDataCallback = null
159
+ // Call after a micro-delay to ensure this data is written first
160
+ setTimeout(callback, 0)
161
+ }
162
+ }
163
+
164
+ // Check for shell integration in the data (only when suppressing)
165
+ if (this.outputSuppressed) {
166
+ let str = data
167
+ if (typeof data !== 'string') {
168
+ // Convert to string to check for OSC 633
169
+ const decoder = this.decoder || new TextDecoder('utf-8')
170
+ try {
171
+ str = decoder.decode(data instanceof ArrayBuffer ? data : new Uint8Array(data))
172
+ } catch (e) {
173
+ str = ''
174
+ }
175
+ }
176
+
177
+ // If we detect OSC 633, shell integration is working
178
+ if (this.checkForShellIntegration(str)) {
179
+ this.onShellIntegrationDetected()
180
+ // Don't buffer this - just discard the integration output
181
+ return
182
+ }
183
+
184
+ // Buffer the data while suppressed
185
+ this.suppressedData.push(data)
186
+ return
187
+ }
188
+
51
189
  if (typeof data === 'string') {
52
190
  return term.write(data)
53
191
  }
@@ -62,31 +200,9 @@ export default class AttachAddonCustom extends AttachAddon {
62
200
  const { term } = this
63
201
  term?.parent?.notifyOnData()
64
202
  const str = this.decoder.decode(data)
65
- if (term?.parent?.props.sftpPathFollowSsh && term?.buffer.active.type !== 'alternate') {
66
- const {
67
- cwdId
68
- } = term
69
- const nss = str.split('\r')
70
- const nnss = []
71
- for (const str1 of nss) {
72
- const ns = str1.trim()
73
- if (cwdId) {
74
- const cwdIdEscaped = regEscape(cwdId)
75
- const dirRegex = new RegExp(`${cwdIdEscaped}([^\\n]+?)${cwdIdEscaped}`, 'g')
76
- if (ns.match(dirRegex)) {
77
- const cwd = dirRegex.exec(ns)[1].trim()
78
- if (cwd === '~' || cwd === '%d' || cwd === '%/' || cwd === '$PWD') term.parent.setCwd('')
79
- else term.parent.setCwd(cwd)
80
- nnss.push(ns.replaceAll(dirRegex, ''))
81
- } else nnss.push(str1)
82
- } else {
83
- nnss.push(str1)
84
- }
85
- }
86
- term.write(nnss.join('\r'))
87
- } else {
88
- term?.write(str)
89
- }
203
+ // CWD tracking is now handled by shell integration automatically
204
+ // No need to parse PS1 markers
205
+ term?.write(str)
90
206
  }
91
207
 
92
208
  sendToServer = (data) => {
@@ -1,83 +1,194 @@
1
+ /**
2
+ * CommandTrackerAddon - Tracks the current command in the terminal
3
+ *
4
+ * This addon uses Shell Integration via OSC 633 escape sequences for reliable
5
+ * command tracking. The shell emits special sequences that tell us:
6
+ * - OSC 633 ; A - Prompt started
7
+ * - OSC 633 ; B - Command input started (ready for typing)
8
+ * - OSC 633 ; C - Command execution started (output begins)
9
+ * - OSC 633 ; D ; <exitCode> - Command finished
10
+ * - OSC 633 ; E ; <command> - The command line being executed
11
+ * - OSC 633 ; P ; Cwd=<path> - Current working directory
12
+ *
13
+ * This properly handles:
14
+ * - Command history (arrow up/down)
15
+ * - Tab completion
16
+ * - Paste operations
17
+ * - Shell-side editing (readline, vi-mode)
18
+ * - Multi-line commands
19
+ * - Any custom prompt
20
+ */
21
+
1
22
  export class CommandTrackerAddon {
2
23
  constructor () {
3
24
  this.terminal = undefined
4
- this.activeCommand = ''
5
- this.currentCommand = ''
6
- this.cursorPosition = 0
7
- this.timeout = null
8
- this.handleKey = this.debounce(this._handleKey, 200) // 10ms debounce
25
+ this._disposables = []
26
+
27
+ // Shell integration state
28
+ this.currentCommand = '' // Command being typed
29
+ this.executedCommand = '' // Last executed command
30
+ this.lastExitCode = null
31
+ this.cwd = ''
32
+ this.shellIntegrationActive = false
33
+
34
+ // Event callbacks for shell integration events
35
+ this._onCommandExecuted = null // Called when OSC 633;E is received
36
+ this._onCwdChanged = null // Called when OSC 633;P;Cwd= is received
9
37
  }
10
38
 
11
- debounce = (func, wait) => {
12
- return (...args) => {
13
- const later = () => {
14
- clearTimeout(this.timeout)
15
- func.apply(this, args)
16
- }
39
+ /**
40
+ * Register callback for when a command is executed (received via OSC 633;E)
41
+ * @param {function} callback - Called with (command: string)
42
+ */
43
+ onCommandExecuted (callback) {
44
+ this._onCommandExecuted = callback
45
+ }
17
46
 
18
- clearTimeout(this.timeout)
19
- this.timeout = setTimeout(later, wait)
20
- }
47
+ /**
48
+ * Register callback for when CWD changes (received via OSC 633;P;Cwd=)
49
+ * @param {function} callback - Called with (cwd: string)
50
+ */
51
+ onCwdChanged (callback) {
52
+ this._onCwdChanged = callback
21
53
  }
22
54
 
23
55
  activate (terminal) {
24
56
  this.terminal = terminal
57
+
58
+ // Register OSC 633 handler for shell integration
59
+ // OSC 633 is the VS Code / modern terminal shell integration protocol
60
+ if (terminal.parser && terminal.parser.registerOscHandler) {
61
+ const oscHandler = terminal.parser.registerOscHandler(633, (data) => {
62
+ return this._handleOsc633(data)
63
+ })
64
+ this._disposables.push(oscHandler)
65
+ }
25
66
  }
26
67
 
27
68
  dispose () {
28
- this.term = null
69
+ this.terminal = null
29
70
  if (this._disposables) {
30
71
  this._disposables.forEach(d => d.dispose())
31
72
  this._disposables.length = 0
32
73
  }
33
- if (this.timeout) {
34
- clearTimeout(this.timeout)
35
- this.timeout = null
36
- }
37
74
  }
38
75
 
39
- handleData = (data) => {
40
- // Handle regular input
41
- this.activeCommand = this.activeCommand.slice(0, this.cursorPosition) + data + this.activeCommand.slice(this.cursorPosition)
42
- this.cursorPosition += data.length
76
+ /**
77
+ * Handle OSC 633 shell integration sequences
78
+ * @param {string} data - The OSC data after "633;"
79
+ * @returns {boolean} Whether the sequence was handled
80
+ */
81
+ _handleOsc633 (data) {
82
+ if (!data) return false
83
+
84
+ // Parse the sequence: first char is the command type
85
+ const command = data.charAt(0)
86
+ const args = data.length > 1 ? data.substring(2) : '' // Skip "X;" part
87
+
88
+ switch (command) {
89
+ case 'A': // Prompt started
90
+ this.shellIntegrationActive = true
91
+ // Reset current command when new prompt appears
92
+ this.currentCommand = ''
93
+ return true
94
+
95
+ case 'B': // Command input started (after prompt)
96
+ return true
97
+
98
+ case 'C': // Command execution started
99
+ return true
100
+
101
+ case 'D': // Command finished
102
+ // Parse exit code if provided
103
+ if (args) {
104
+ this.lastExitCode = parseInt(args, 10)
105
+ } else {
106
+ this.lastExitCode = null
107
+ }
108
+ return true
109
+
110
+ case 'E': // Command line
111
+ // The actual command being executed
112
+ this.executedCommand = this._deserializeOscValue(args)
113
+ this.currentCommand = this.executedCommand
114
+ // Call the callback if registered
115
+ if (this._onCommandExecuted && this.executedCommand) {
116
+ this._onCommandExecuted(this.executedCommand)
117
+ }
118
+ return true
119
+
120
+ case 'P': // Property (e.g., Cwd=<path>)
121
+ this._handleProperty(args)
122
+ return true
123
+
124
+ default:
125
+ return false
126
+ }
43
127
  }
44
128
 
45
- // This is now our internal handler
46
- _handleKey = (e) => {
47
- const { key } = e
48
- if (e.ctrlKey && key.toLowerCase() === 'c') {
49
- this.clearCommand()
50
- } else if (key === 'Enter') {
51
- // Command executed, reset
52
- this.currentCommand = this.activeCommand
53
- this.activeCommand = ''
54
- this.cursorPosition = 0
55
- } else if (key === 'Backspace') {
56
- // Handle backspace
57
- if (this.cursorPosition > 0) {
58
- this.activeCommand = this.activeCommand.slice(0, this.cursorPosition - 1) + this.activeCommand.slice(this.cursorPosition)
59
- this.cursorPosition--
60
- }
61
- } else if (key === 'ArrowLeft') {
62
- // Move cursor left
63
- if (this.cursorPosition > 0) {
64
- this.cursorPosition--
65
- }
66
- } else if (key === 'ArrowRight') {
67
- // Move cursor right
68
- if (this.cursorPosition < this.activeCommand.length) {
69
- this.cursorPosition++
129
+ /**
130
+ * Handle OSC 633 ; P property sequences
131
+ * @param {string} data - Property data like "Cwd=/path/to/dir"
132
+ */
133
+ _handleProperty (data) {
134
+ const eqIndex = data.indexOf('=')
135
+ if (eqIndex === -1) return
136
+
137
+ const key = data.substring(0, eqIndex)
138
+ const value = this._deserializeOscValue(data.substring(eqIndex + 1))
139
+
140
+ switch (key) {
141
+ case 'Cwd': {
142
+ const oldCwd = this.cwd
143
+ this.cwd = value
144
+ // Call the callback if registered and CWD actually changed
145
+ if (this._onCwdChanged && oldCwd !== value) {
146
+ this._onCwdChanged(value)
147
+ }
148
+ break
70
149
  }
150
+ // Add more properties as needed
71
151
  }
72
152
  }
73
153
 
74
- clearCommand () {
75
- this.activeCommand = ''
76
- this.currentCommand = ''
77
- this.cursorPosition = 0
154
+ /**
155
+ * Deserialize OSC 633 escaped values
156
+ * Handles: \\ -> \, \x3b -> ;
157
+ * @param {string} value - Escaped value
158
+ * @returns {string} Unescaped value
159
+ */
160
+ _deserializeOscValue (value) {
161
+ if (!value) return ''
162
+ return value
163
+ .replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
164
+ .replace(/\\\\/g, '\\')
78
165
  }
79
166
 
167
+ /**
168
+ * Get the current command (from shell integration)
169
+ */
80
170
  getCurrentCommand () {
81
- return this.activeCommand || this.currentCommand
171
+ return this.executedCommand || this.currentCommand || ''
172
+ }
173
+
174
+ /**
175
+ * Get the last exit code (if available via shell integration)
176
+ */
177
+ getLastExitCode () {
178
+ return this.lastExitCode
179
+ }
180
+
181
+ /**
182
+ * Get current working directory (if available via shell integration)
183
+ */
184
+ getCwd () {
185
+ return this.cwd
186
+ }
187
+
188
+ /**
189
+ * Check if shell integration is active
190
+ */
191
+ hasShellIntegration () {
192
+ return this.shellIntegrationActive
82
193
  }
83
194
  }