@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
@@ -8,13 +8,13 @@ import {
8
8
  ReloadOutlined
9
9
  } from '@ant-design/icons'
10
10
  import {
11
- notification,
12
11
  Spin,
13
12
  Button,
14
- Dropdown,
15
- message,
16
- Modal
13
+ Dropdown
17
14
  } from 'antd'
15
+ import { notification } from '../common/notification'
16
+ import message from '../common/message'
17
+ import Modal from '../common/modal'
18
18
  import classnames from 'classnames'
19
19
  import './terminal.styl'
20
20
  import {
@@ -24,7 +24,6 @@ import {
24
24
  isWin,
25
25
  transferTypeMap,
26
26
  rendererTypes,
27
- cwdId,
28
27
  isMac,
29
28
  zmodemTransferPackSize
30
29
  } from '../../common/constants.js'
@@ -51,6 +50,10 @@ import { getFilePath, isUnsafeFilename } from '../../common/file-drop-utils.js'
51
50
  import { CommandTrackerAddon } from './command-tracker-addon.js'
52
51
  import AIIcon from '../icons/ai-icon.jsx'
53
52
  import { formatBytes } from '../../common/byte-format.js'
53
+ import {
54
+ getShellIntegrationCommand,
55
+ detectShellType
56
+ } from './shell.js'
54
57
  import * as fs from './fs.js'
55
58
  import iconsMap from '../sys-menu/icons-map.jsx'
56
59
  import { refs, refsStatic } from '../common/ref.js'
@@ -59,19 +62,6 @@ import SearchResultBar from './terminal-search-bar'
59
62
 
60
63
  const e = window.translate
61
64
 
62
- const PS1_SETUP_CMD = `\recho $0|grep csh >/dev/null && set prompt_bak="$prompt" && set prompt="$prompt${cwdId}%/${cwdId}"\r
63
- echo $0|grep zsh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'${cwdId}%d${cwdId}'\r
64
- echo $0|grep ash >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
65
- echo $0|grep ksh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
66
- echo $0|grep '^sh' >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
67
- clear\r`
68
-
69
- const PS1_RESTORE_CMD = `\recho $0|grep csh >/dev/null && set prompt="$prompt_bak"\r
70
- echo $0|grep zsh >/dev/null && PS1="$PS1_bak"\r
71
- echo $0|grep ash >/dev/null && PS1="$PS1_bak"\r
72
- echo $0|grep ksh >/dev/null && PS1="$PS1_bak"\r
73
- echo $0|grep '^sh' >/dev/null && PS1="$PS1_bak"\r
74
- clear\r`
75
65
  class Term extends Component {
76
66
  constructor (props) {
77
67
  super(props)
@@ -88,6 +78,7 @@ class Term extends Component {
88
78
  }
89
79
  this.id = `term-${this.props.tab.id}`
90
80
  refs.add(this.id, this)
81
+ this.currentInput = ''
91
82
  }
92
83
 
93
84
  domRef = createRef()
@@ -140,29 +131,6 @@ class Term extends Component {
140
131
  background: 'rgba(0,0,0,0)'
141
132
  }
142
133
  }
143
-
144
- const sftpPathFollowSshChanged = !isEqual(
145
- this.props.sftpPathFollowSsh,
146
- prevProps.sftpPathFollowSsh
147
- )
148
-
149
- if (sftpPathFollowSshChanged) {
150
- if (this.props.sftpPathFollowSsh) {
151
- if (this.attachAddon && this.term) {
152
- this.attachAddon._sendData(PS1_SETUP_CMD)
153
- this.term.cwdId = cwdId
154
- } else {
155
- console.warn('Term or attachAddon not ready for PS1_SETUP_CMD in componentDidUpdate')
156
- }
157
- } else {
158
- if (this.attachAddon) {
159
- this.attachAddon._sendData(PS1_RESTORE_CMD)
160
- }
161
- if (this.term) {
162
- delete this.term.cwdId
163
- }
164
- }
165
- }
166
134
  }
167
135
 
168
136
  componentWillUnmount () {
@@ -321,8 +289,7 @@ class Term extends Component {
321
289
  ),
322
290
  okText: e('ok'),
323
291
  cancelText: e('cancel'),
324
- onOk: () => this.onPaste(true),
325
- onCancel: Modal.destroyAll
292
+ onOk: () => this.onPaste(true)
326
293
  })
327
294
  }
328
295
 
@@ -382,7 +349,7 @@ class Term extends Component {
382
349
  this.attachAddon._sendData(`"${filePath}" `)
383
350
  return
384
351
  } catch (e) {
385
- log.error('Failed to parse fromFile data:', e)
352
+ console.error('Failed to parse fromFile data:', e)
386
353
  }
387
354
  }
388
355
 
@@ -441,12 +408,15 @@ class Term extends Component {
441
408
  }
442
409
 
443
410
  onzmodemRetract = () => {
444
- log.debug('zmodemRetract')
411
+ console.debug('zmodemRetract')
445
412
  }
446
413
 
447
414
  writeBanner = (type) => {
448
- this.term.write('\r\nRecommmend use trzsz instead: https://github.com/trzsz/trzsz\r\n')
449
- this.term.write(`\x1b[32mZMODEM::${type}::START\x1b[0m\r\n\r\n`)
415
+ const border = '='.repeat(50)
416
+ this.term.write(`\r\n${border}\r\n`)
417
+ this.term.write('\x1b[33m\x1b[1mRecommend use trzsz instead: https://github.com/trzsz/trzsz\x1b[0m\r\n')
418
+ this.term.write(`${border}\r\n\r\n`)
419
+ this.term.write(`\x1b[32m\x1b[1mZMODEM::${type}::START\x1b[0m\r\n`)
450
420
  }
451
421
 
452
422
  onReceiveZmodemSession = async () => {
@@ -885,20 +855,16 @@ class Term extends Component {
885
855
  }
886
856
 
887
857
  getCwd = () => {
888
- if (
889
- this.props.sftpPathFollowSsh &&
890
- this.term &&
891
- this.term.buffer.active.type !== 'alternate' && !this.term.cwdId
892
- ) {
893
- // This block should ideally not be hit for initial setup if runInitScript works.
894
- // It acts as a fallback.
895
- this.term.cwdId = cwdId
896
- if (this.attachAddon) {
897
- this.attachAddon._sendData(PS1_SETUP_CMD)
898
- } else {
899
- console.warn('attachAddon not ready for PS1_SETUP_CMD in getCwd fallback')
858
+ // Use shell integration CWD if available
859
+ if (this.cmdAddon && this.cmdAddon.hasShellIntegration()) {
860
+ const cwd = this.cmdAddon.getCwd()
861
+ if (cwd) {
862
+ this.setCwd(cwd)
863
+ return cwd
900
864
  }
901
865
  }
866
+ // Fallback: no longer needed with shell integration
867
+ return ''
902
868
  }
903
869
 
904
870
  setCwd = (cwd) => {
@@ -942,35 +908,66 @@ class Term extends Component {
942
908
  ?.closeSuggestions()
943
909
  }
944
910
 
945
- onData = (d) => {
946
- if (this.cmdAddon) {
947
- this.cmdAddon.handleData(d)
911
+ openSuggestions = (cursorPos, data) => {
912
+ refsStatic
913
+ .get('terminal-suggestions')
914
+ ?.openSuggestions(cursorPos, data)
915
+ }
916
+
917
+ getCurrentInput = () => {
918
+ return this.currentInput
919
+ }
920
+
921
+ setCurrentInput = (value) => {
922
+ this.currentInput = value
923
+ }
924
+
925
+ updateCurrentInput = (d) => {
926
+ // Handle backspace (both \x7f and \b)
927
+ if (d === '\x7f' || d === '\b') {
928
+ this.currentInput = this.currentInput.slice(0, -1)
929
+ return
930
+ }
931
+ // Handle Ctrl+U (clear line)
932
+ if (d === '\x15') {
933
+ this.currentInput = ''
934
+ return
948
935
  }
949
- const data = this.getCmd().trim()
950
- if (!d.includes('\r')) {
951
- delete this.userTypeExit
936
+ // Handle Ctrl+W (delete word)
937
+ if (d === '\x17') {
938
+ this.currentInput = this.currentInput.replace(/\S*\s*$/, '')
939
+ return
940
+ }
941
+ // Handle Ctrl+C (cancel)
942
+ if (d === '\x03') {
943
+ this.currentInput = ''
944
+ return
945
+ }
946
+ // Handle Enter
947
+ if (d === '\r' || d === '\n') {
948
+ this.currentInput = ''
949
+ return
950
+ }
951
+ // Handle Escape and other control characters
952
+ if (d.charCodeAt(0) < 32 && d !== '\t') {
953
+ return
954
+ }
955
+ // Handle arrow keys and other escape sequences
956
+ if (d.startsWith('\x1b')) {
957
+ return
958
+ }
959
+ // Regular character input - append to buffer
960
+ this.currentInput += d
961
+ }
962
+
963
+ onData = (d) => {
964
+ this.updateCurrentInput(d)
965
+ const data = this.getCurrentInput()
966
+ if (this.props.config.showCmdSuggestions && data) {
952
967
  const cursorPos = this.getCursorPosition()
953
- if (this.props.config.showCmdSuggestions && data.length > 1) {
954
- refsStatic
955
- .get('terminal-suggestions')
956
- ?.openSuggestions(cursorPos, data)
957
- }
968
+ this.openSuggestions(cursorPos, data)
958
969
  } else {
959
970
  this.closeSuggestions()
960
- if (this.term.buffer.active.type !== 'alternate') {
961
- this.timers.getCwd = setTimeout(this.getCwd, 200)
962
- }
963
- const exitCmds = [
964
- 'exit',
965
- 'logout'
966
- ]
967
- window.store.addCmdHistory(data)
968
- if (exitCmds.includes(data)) {
969
- this.userTypeExit = true
970
- this.timers.userTypeExit = setTimeout(() => {
971
- delete this.userTypeExit
972
- }, 2000)
973
- }
974
971
  }
975
972
  }
976
973
 
@@ -981,8 +978,8 @@ class Term extends Component {
981
978
  try {
982
979
  term.loadAddon(new WebglAddon())
983
980
  } catch (e) {
984
- log.error('render with webgl failed, fallback to canvas')
985
- log.error(e)
981
+ console.error('render with webgl failed, fallback to canvas')
982
+ console.error(e)
986
983
  term.loadAddon(new CanvasAddon())
987
984
  }
988
985
  }
@@ -1021,6 +1018,16 @@ class Term extends Component {
1021
1018
  // term.on('keydown', this.handleEvent)
1022
1019
  this.fitAddon = new FitAddon()
1023
1020
  this.cmdAddon = new CommandTrackerAddon()
1021
+ // Register callback for shell integration command tracking
1022
+ this.cmdAddon.onCommandExecuted((cmd) => {
1023
+ if (cmd && cmd.trim()) {
1024
+ window.store.addCmdHistory(cmd.trim())
1025
+ }
1026
+ })
1027
+ // Register callback for shell integration CWD tracking
1028
+ this.cmdAddon.onCwdChanged((cwd) => {
1029
+ this.setCwd(cwd)
1030
+ })
1024
1031
  this.searchAddon = new SearchAddon()
1025
1032
  const ligtureAddon = new LigaturesAddon()
1026
1033
  this.searchAddon.onDidChangeResults(this.onSearchResultsChange)
@@ -1035,7 +1042,6 @@ class Term extends Component {
1035
1042
  term.onData(this.onData)
1036
1043
  this.term = term
1037
1044
  term.onSelectionChange(this.onSelectionChange)
1038
- term.attachCustomKeyEventHandler(this.handleKeyboardEvent.bind(this))
1039
1045
  await this.remoteInit(term)
1040
1046
  }
1041
1047
 
@@ -1060,29 +1066,85 @@ class Term extends Component {
1060
1066
  startDirectory,
1061
1067
  runScripts
1062
1068
  } = this.props.tab
1069
+
1070
+ const scripts = runScripts ? [...runScripts] : []
1063
1071
  const startFolder = startDirectory || window.initFolder
1064
1072
  if (startFolder) {
1065
- if (this.attachAddon) {
1066
- const cmd = `cd "${startFolder}"\r`
1067
- this.attachAddon._sendData(cmd)
1068
- } else {
1069
- console.warn('attachAddon not ready for cd command in runInitScript')
1070
- }
1073
+ scripts.unshift({ script: `cd "${startFolder}"`, delay: 0 })
1071
1074
  }
1075
+ this.pendingRunScripts = scripts
1072
1076
 
1073
- if (this.props.sftpPathFollowSsh) {
1074
- if (this.term && this.attachAddon) {
1075
- this.attachAddon._sendData(PS1_SETUP_CMD)
1076
- this.term.cwdId = cwdId
1077
- } else {
1078
- console.warn('Term or attachAddon not ready for PS1_SETUP_CMD in runInitScript')
1079
- }
1077
+ // Inject shell integration from client-side (works for both local and remote)
1078
+ // Skip on Windows as shell integration is not supported there
1079
+ if (this.canInjectShellIntegration()) {
1080
+ this.injectShellIntegration()
1081
+ } else {
1082
+ // No shell integration, run scripts immediately
1083
+ this.startDelayedScripts()
1080
1084
  }
1085
+ }
1086
+
1087
+ canInjectShellIntegration = () => {
1088
+ return this.isSsh() || this.isLocal()
1089
+ }
1090
+
1091
+ isSsh = () => {
1092
+ const { host, type } = this.props.tab
1093
+ return host && (type === 'ssh' || type === undefined)
1094
+ }
1081
1095
 
1096
+ isLocal = () => {
1097
+ const { host, type } = this.props.tab
1098
+ return !host && (type === 'local' || type === undefined)
1099
+ }
1100
+
1101
+ /**
1102
+ * Start running delayed scripts
1103
+ * Called after shell integration injection completes (or immediately if disabled)
1104
+ */
1105
+ startDelayedScripts = () => {
1106
+ const runScripts = this.pendingRunScripts
1082
1107
  if (runScripts && runScripts.length) {
1083
1108
  this.delayedScripts = deepCopy(runScripts)
1084
1109
  this.timers.timerDelay = setTimeout(this.runDelayedScripts, this.delayedScripts[0].delay || 0)
1085
1110
  }
1111
+ this.pendingRunScripts = null
1112
+ }
1113
+
1114
+ /**
1115
+ * Inject shell integration commands from client-side
1116
+ * This replaces the server-side source xxx.xxx approach
1117
+ * Uses output suppression to hide the injection command
1118
+ */
1119
+ injectShellIntegration = () => {
1120
+ // Detect shell type from login script or local shell config
1121
+ let shellType = 'bash'
1122
+ if (this.isLocal()) {
1123
+ const { config } = this.props
1124
+ const localShell = isMac ? config.execMac : config.execLinux
1125
+ shellType = detectShellType(localShell)
1126
+ }
1127
+
1128
+ const isRemote = this.isSsh()
1129
+
1130
+ // Remote sessions might need longer timeout for shell integration detection
1131
+ const integrationCmd = getShellIntegrationCommand(shellType)
1132
+
1133
+ if (integrationCmd && this.attachAddon) {
1134
+ // Wait for initial data (prompt/banner) to arrive before injecting
1135
+ this.attachAddon.onInitialData(() => {
1136
+ if (this.attachAddon) {
1137
+ // Start suppressing output before sending the integration command
1138
+ // This hides the command and its output until OSC 633 is detected
1139
+ const suppressionTimeout = isRemote ? 5000 : 3000
1140
+ // Pass callback to run delayed scripts after suppression ends
1141
+ this.attachAddon.startOutputSuppression(suppressionTimeout, () => {
1142
+ this.startDelayedScripts()
1143
+ })
1144
+ this.attachAddon._sendData(integrationCmd)
1145
+ }
1146
+ })
1147
+ }
1086
1148
  }
1087
1149
 
1088
1150
  runDelayedScripts = () => {
@@ -1292,13 +1354,13 @@ class Term extends Component {
1292
1354
  try {
1293
1355
  this.fitAddon.fit()
1294
1356
  } catch (e) {
1295
- log.info('resize failed')
1357
+ console.info('resize failed')
1296
1358
  }
1297
1359
  }
1298
1360
  }, 200)
1299
1361
 
1300
1362
  onerrorSocket = err => {
1301
- log.error('onerrorSocket', err)
1363
+ console.error('onerrorSocket', err)
1302
1364
  }
1303
1365
 
1304
1366
  oncloseSocket = () => {
@@ -1320,7 +1382,7 @@ class Term extends Component {
1320
1382
  }
1321
1383
  this.socketCloseWarning = notification.warning({
1322
1384
  key,
1323
- title: e('socketCloseTip'),
1385
+ message: e('socketCloseTip'),
1324
1386
  duration: 30,
1325
1387
  description: (
1326
1388
  <div className='pd2y'>
@@ -1,5 +1,6 @@
1
1
  import { useRef, useState } from 'react'
2
- import { Button, Input, message, Upload, Form, Space } from 'antd'
2
+ import { Button, Input, Upload, Form, Space } from 'antd'
3
+ import message from '../common/message'
3
4
  import {
4
5
  convertTheme,
5
6
  convertThemeToText,
@@ -8,7 +8,7 @@ import {
8
8
  ImportOutlined,
9
9
  EditOutlined
10
10
  } from '@ant-design/icons'
11
- import { Upload, Button } from 'antd'
11
+ import { Upload, Button, Space } from 'antd'
12
12
 
13
13
  const e = window.translate
14
14
 
@@ -32,16 +32,20 @@ export default class BookmarkTransport extends PureComponent {
32
32
  )
33
33
  }
34
34
 
35
- render () {
36
- return [
37
- this.renderEdit(),
35
+ renderExport () {
36
+ return (
38
37
  <Button
39
38
  icon={<ExportOutlined />}
40
39
  onClick={this.handleDownload}
41
40
  title={e('export')}
42
41
  className='download-bookmark-icon'
43
42
  key='export'
44
- />,
43
+ />
44
+ )
45
+ }
46
+
47
+ renderImport () {
48
+ return (
45
49
  <Upload
46
50
  beforeUpload={this.beforeUpload}
47
51
  fileList={[]}
@@ -53,6 +57,24 @@ export default class BookmarkTransport extends PureComponent {
53
57
  title={e('importFromFile')}
54
58
  />
55
59
  </Upload>
60
+ )
61
+ }
62
+
63
+ render () {
64
+ const edit = this.renderEdit()
65
+ console.log('edit', edit)
66
+ if (edit === null) {
67
+ return (
68
+ <Space.Compact>
69
+ {this.renderExport()}
70
+ {this.renderImport()}
71
+ </Space.Compact>
72
+ )
73
+ }
74
+ return [
75
+ edit,
76
+ this.renderExport(),
77
+ this.renderImport()
56
78
  ]
57
79
  }
58
80
  }
@@ -9,9 +9,9 @@ import {
9
9
  } from '../../common/constants'
10
10
  import {
11
11
  Spin,
12
- message,
13
12
  Tag
14
13
  } from 'antd'
14
+ import message from '../common/message'
15
15
  import Modal from '../common/modal'
16
16
  import * as ls from '../../common/safe-local-storage'
17
17
  import { copy } from '../../common/clipboard'
@@ -1,4 +1,4 @@
1
- import { notification } from 'antd'
1
+ import { notification } from '../common/notification'
2
2
  import { CopyOutlined } from '@ant-design/icons'
3
3
  import { copy } from '../../common/clipboard'
4
4
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import handleError from '../common/error-handler'
6
- import { Modal } from 'antd'
6
+ import Modal from '../components/common/modal'
7
7
  import { debounce, some, get, pickBy } from 'lodash-es'
8
8
  import {
9
9
  modals,
@@ -117,7 +117,7 @@ export default Store => {
117
117
  setTimeout(queue, 3000)
118
118
  setTimeout(run, 4000)
119
119
  } catch (e) {
120
- log.error(e)
120
+ console.error(e)
121
121
  }
122
122
  }
123
123
 
@@ -341,6 +341,9 @@ export default Store => {
341
341
  }
342
342
 
343
343
  Store.prototype.addCmdHistory = action(function (cmd) {
344
+ if (!cmd || !cmd.trim()) {
345
+ return
346
+ }
344
347
  const { terminalCommandHistory } = window.store
345
348
  terminalCommandHistory.add(cmd)
346
349
  if (terminalCommandHistory.size > 100) {
@@ -2,7 +2,7 @@
2
2
  * db upgrade
3
3
  */
4
4
 
5
- import { Modal } from 'antd'
5
+ import Modal from '../components/common/modal'
6
6
  import delay from '../common/wait'
7
7
 
8
8
  export default (Store) => {
@@ -9,6 +9,7 @@ import {
9
9
  infoTabs,
10
10
  openedSidebarKey,
11
11
  sidebarPinnedKey,
12
+ pinnedQuickCommandBarKey,
12
13
  sftpDefaultSortSettingKey,
13
14
  batchInputLsKey,
14
15
  expandedKeysLsKey,
@@ -150,7 +151,7 @@ export default () => {
150
151
  quickCommands: [],
151
152
  quickCommandId: '',
152
153
  openQuickCommandBar: false,
153
- pinnedQuickCommandBar: false,
154
+ pinnedQuickCommandBar: ls.getItem(pinnedQuickCommandBarKey) === 'y',
154
155
  qmSortByFrequency: ls.getItem(qmSortByFrequencyKey) === 'yes',
155
156
 
156
157
  // sidebar
@@ -35,7 +35,7 @@ function getHost (argv, opts) {
35
35
  }
36
36
 
37
37
  export async function addTabFromCommandLine (store, opts) {
38
- log.debug('command line params', opts)
38
+ console.debug('command line params', opts)
39
39
  if (!opts) {
40
40
  return false
41
41
  }
@@ -94,7 +94,7 @@ export async function addTabFromCommandLine (store, opts) {
94
94
  if (options.privateKeyPath) {
95
95
  conf.privateKey = await fs.readFile(options.privateKeyPath)
96
96
  }
97
- log.debug('command line opts', conf)
97
+ console.debug('command line opts', conf)
98
98
  if (
99
99
  (conf.username && conf.host) ||
100
100
  conf.fromCmdLine
@@ -5,6 +5,8 @@
5
5
 
6
6
  import uid from '../common/uid'
7
7
  import { settingMap } from '../common/constants'
8
+ import { refs } from '../components/common/ref'
9
+ import deepCopy from 'json-deep-copy'
8
10
 
9
11
  export default Store => {
10
12
  // Initialize MCP handler - called when MCP widget is started
@@ -142,27 +144,8 @@ export default Store => {
142
144
 
143
145
  // ==================== Bookmark APIs ====================
144
146
 
145
- Store.prototype.mcpListBookmarks = function (args = {}) {
146
- const { store } = window
147
- let bookmarks = store.bookmarks
148
-
149
- if (args.groupId) {
150
- const group = store.bookmarkGroups.find(g => g.id === args.groupId)
151
- if (group && group.bookmarkIds) {
152
- const idSet = new Set(group.bookmarkIds)
153
- bookmarks = bookmarks.filter(b => idSet.has(b.id))
154
- }
155
- }
156
-
157
- return bookmarks.map(b => ({
158
- id: b.id,
159
- title: b.title,
160
- host: b.host,
161
- port: b.port,
162
- username: b.username,
163
- type: b.type || 'ssh',
164
- color: b.color
165
- }))
147
+ Store.prototype.mcpListBookmarks = function () {
148
+ return deepCopy(window.store.bookmarks)
166
149
  }
167
150
 
168
151
  Store.prototype.mcpGetBookmark = function (args) {
@@ -171,9 +154,7 @@ export default Store => {
171
154
  if (!bookmark) {
172
155
  throw new Error(`Bookmark not found: ${args.id}`)
173
156
  }
174
- // Return bookmark without sensitive data
175
- const { password, passphrase, privateKey, ...safeBookmark } = bookmark
176
- return safeBookmark
157
+ return deepCopy(bookmark)
177
158
  }
178
159
 
179
160
  Store.prototype.mcpAddBookmark = async function (args) {
@@ -218,16 +199,11 @@ export default Store => {
218
199
 
219
200
  Store.prototype.mcpDeleteBookmark = function (args) {
220
201
  const { store } = window
221
- const bookmark = store.bookmarks.find(b => b.id === args.id)
222
- if (!bookmark) {
223
- throw new Error(`Bookmark not found: ${args.id}`)
224
- }
225
-
226
202
  store.delItem({ id: args.id }, settingMap.bookmarks)
227
203
 
228
204
  return {
229
205
  success: true,
230
- message: `Bookmark "${bookmark.title}" deleted`
206
+ message: `Bookmark "${args.id}" deleted`
231
207
  }
232
208
  }
233
209
 
@@ -249,14 +225,7 @@ export default Store => {
249
225
  // ==================== Bookmark Group APIs ====================
250
226
 
251
227
  Store.prototype.mcpListBookmarkGroups = function () {
252
- const { store } = window
253
- return store.bookmarkGroups.map(g => ({
254
- id: g.id,
255
- title: g.title,
256
- level: g.level,
257
- bookmarkCount: (g.bookmarkIds || []).length,
258
- subgroupCount: (g.bookmarkGroupIds || []).length
259
- }))
228
+ return deepCopy(window.store.bookmarkGroups)
260
229
  }
261
230
 
262
231
  Store.prototype.mcpAddBookmarkGroup = async function (args) {
@@ -281,15 +250,7 @@ export default Store => {
281
250
  // ==================== Quick Command APIs ====================
282
251
 
283
252
  Store.prototype.mcpListQuickCommands = function () {
284
- const { store } = window
285
- return store.quickCommands.map(q => ({
286
- id: q.id,
287
- name: q.name,
288
- command: q.command,
289
- commands: q.commands,
290
- inputOnly: q.inputOnly,
291
- labels: q.labels
292
- }))
253
+ return deepCopy(window.store.quickCommands)
293
254
  }
294
255
 
295
256
  Store.prototype.mcpAddQuickCommand = function (args) {
@@ -297,7 +258,7 @@ export default Store => {
297
258
  const qm = {
298
259
  id: uid(),
299
260
  name: args.name,
300
- command: args.command,
261
+ commands: args.commands,
301
262
  inputOnly: args.inputOnly || false,
302
263
  labels: args.labels || []
303
264
  }
@@ -475,7 +436,6 @@ export default Store => {
475
436
 
476
437
  Store.prototype.mcpGetTerminalSelection = function (args) {
477
438
  const { store } = window
478
- const { refs } = require('../components/common/ref')
479
439
  const tabId = args.tabId || store.activeTabId
480
440
 
481
441
  if (!tabId) {
@@ -497,7 +457,6 @@ export default Store => {
497
457
 
498
458
  Store.prototype.mcpGetTerminalOutput = function (args) {
499
459
  const { store } = window
500
- const { refs } = require('../components/common/ref')
501
460
  const tabId = args.tabId || store.activeTabId
502
461
  const lineCount = args.lines || 50
503
462