@electerm/electerm-react 3.1.6 → 3.1.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.
- package/client/common/constants.js +0 -2
- package/client/common/db.js +20 -10
- package/client/components/file-transfer/conflict-resolve.jsx +8 -0
- package/client/components/footer/cmd-history.jsx +2 -4
- package/client/components/quick-commands/qm.styl +8 -0
- package/client/components/quick-commands/quick-command-item.jsx +4 -0
- package/client/components/quick-commands/quick-commands-box.jsx +10 -0
- package/client/components/quick-commands/quick-commands-form-elem.jsx +1 -1
- package/client/components/quick-commands/quick-commands-list-form.jsx +59 -4
- package/client/components/quick-commands/quick-commands-list.jsx +33 -3
- package/client/components/setting-panel/list.styl +5 -1
- package/client/components/terminal/terminal-command-dropdown.jsx +1 -1
- package/client/components/terminal/terminal.jsx +14 -1
- package/client/components/tree-list/tree-list-item.jsx +5 -0
- package/client/components/tree-list/tree-list.jsx +17 -4
- package/client/components/tree-list/tree-list.styl +1 -1
- package/client/store/common.js +15 -15
- package/client/store/init-state.js +6 -31
- package/client/store/mcp-handler.js +315 -129
- package/client/store/store.js +1 -1
- package/client/store/transfer-list.js +1 -0
- package/client/store/watch.js +1 -25
- package/package.json +1 -1
|
@@ -352,6 +352,4 @@ export const terminalTypes = [
|
|
|
352
352
|
export const sshConfigLoadKey = 'ssh-config-loaded'
|
|
353
353
|
export const sshConfigKey = 'ignore-ssh-config'
|
|
354
354
|
export const connectionHoppingWarnKey = 'connectionHoppingWarnned'
|
|
355
|
-
export const aiChatHistoryKey = 'ai-chat-history'
|
|
356
355
|
export const syncServerDataKey = 'sync-server-data'
|
|
357
|
-
export const cmdHistoryKey = 'cmd-history'
|
package/client/common/db.js
CHANGED
|
@@ -23,17 +23,27 @@ const dbAction = (...args) => {
|
|
|
23
23
|
/**
|
|
24
24
|
* standalone db names
|
|
25
25
|
*/
|
|
26
|
-
export const dbNames =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
export const dbNames = [
|
|
27
|
+
...without(
|
|
28
|
+
Object.keys(settingMap),
|
|
29
|
+
settingMap.setting,
|
|
30
|
+
settingMap.widgets
|
|
31
|
+
),
|
|
32
|
+
'history',
|
|
33
|
+
'terminalCommandHistory',
|
|
34
|
+
'aiChatHistory'
|
|
35
|
+
]
|
|
31
36
|
|
|
32
|
-
export const dbNamesForWatch =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
export const dbNamesForWatch = [
|
|
38
|
+
...without(
|
|
39
|
+
Object.keys(settingMap),
|
|
40
|
+
settingMap.setting,
|
|
41
|
+
settingMap.widgets
|
|
42
|
+
),
|
|
43
|
+
'history',
|
|
44
|
+
'terminalCommandHistory',
|
|
45
|
+
'aiChatHistory'
|
|
46
|
+
]
|
|
37
47
|
|
|
38
48
|
/**
|
|
39
49
|
* db insert
|
|
@@ -45,6 +45,14 @@ export default class ConfirmModalStore extends Component {
|
|
|
45
45
|
if (this.activeTransferId === transferId || this.queuedTransferIds.has(transferId)) {
|
|
46
46
|
return
|
|
47
47
|
}
|
|
48
|
+
const globalPolicy = window._transferConflictPolicy
|
|
49
|
+
if (globalPolicy && Object.values(fileActions).includes(globalPolicy)) {
|
|
50
|
+
const { id, transferBatch } = transfer
|
|
51
|
+
const trid = `tr-${transferBatch}-${id}`
|
|
52
|
+
const currentTransfer = refsTransfers.get(trid)
|
|
53
|
+
currentTransfer?.onDecision(globalPolicy)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
48
56
|
this.queue.push(transfer)
|
|
49
57
|
this.queuedTransferIds.add(transferId)
|
|
50
58
|
if (!this.activeTransferId) {
|
|
@@ -31,7 +31,7 @@ export default auto(function CmdHistory (props) {
|
|
|
31
31
|
|
|
32
32
|
function handleDeleteCommand (cmd, ev) {
|
|
33
33
|
ev.stopPropagation()
|
|
34
|
-
|
|
34
|
+
window.store.deleteCmdHistory(cmd)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function handleCopyCommand (cmd, ev) {
|
|
@@ -54,9 +54,7 @@ export default auto(function CmdHistory (props) {
|
|
|
54
54
|
setKeyword(e.target.value)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
const historyArray =
|
|
58
|
-
.map(([cmd, info]) => ({ cmd, ...info }))
|
|
59
|
-
.reverse()
|
|
57
|
+
const historyArray = (terminalCommandHistory || []).slice().reverse()
|
|
60
58
|
|
|
61
59
|
let filtered = filterArray(historyArray, keyword)
|
|
62
60
|
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
|
|
26
26
|
.item-list-unit.dragover
|
|
27
27
|
border: 1px dashed var(--primary)
|
|
28
|
+
.qm-item-dragover
|
|
29
|
+
border-left 2px solid var(--primary)
|
|
30
|
+
.qm-drag-handle
|
|
31
|
+
cursor grab
|
|
32
|
+
.qm-field-dragging
|
|
33
|
+
opacity 0.4
|
|
34
|
+
.qm-field-dragover
|
|
35
|
+
border-top 2px solid var(--primary)
|
|
28
36
|
.qm-label-select
|
|
29
37
|
min-width 120px
|
|
30
38
|
|
|
@@ -23,6 +23,8 @@ export default class QuickCommandsItem extends PureComponent {
|
|
|
23
23
|
draggable,
|
|
24
24
|
handleDragOver,
|
|
25
25
|
handleDragStart,
|
|
26
|
+
handleDragEnter,
|
|
27
|
+
handleDragLeave,
|
|
26
28
|
handleDrop
|
|
27
29
|
} = this.props
|
|
28
30
|
const cls = classNames('qm-item mg1r mg1b')
|
|
@@ -34,6 +36,8 @@ export default class QuickCommandsItem extends PureComponent {
|
|
|
34
36
|
draggable,
|
|
35
37
|
onDragOver: handleDragOver,
|
|
36
38
|
onDragStart: handleDragStart,
|
|
39
|
+
onDragEnter: handleDragEnter,
|
|
40
|
+
onDragLeave: handleDragLeave,
|
|
37
41
|
onDrop: handleDrop
|
|
38
42
|
}
|
|
39
43
|
return (
|
|
@@ -87,6 +87,14 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
87
87
|
e.dataTransfer.setData('idDragged', e.target.getAttribute('data-id'))
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
function onDragEnter (e) {
|
|
91
|
+
e.target.closest('.qm-item')?.classList.add('qm-item-dragover')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function onDragLeave (e) {
|
|
95
|
+
e.target.closest('.qm-item')?.classList.remove('qm-item-dragover')
|
|
96
|
+
}
|
|
97
|
+
|
|
90
98
|
function onDrop (e) {
|
|
91
99
|
onDropFunc(e, '.qm-item')
|
|
92
100
|
}
|
|
@@ -116,6 +124,8 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
116
124
|
draggable={!qmSortByFrequency}
|
|
117
125
|
handleDragOver={onDragOver}
|
|
118
126
|
handleDragStart={onDragStart}
|
|
127
|
+
handleDragEnter={onDragEnter}
|
|
128
|
+
handleDragLeave={onDragLeave}
|
|
119
129
|
handleDrop={onDrop}
|
|
120
130
|
/>
|
|
121
131
|
)
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
Button,
|
|
6
6
|
Input
|
|
7
7
|
} from 'antd'
|
|
8
|
-
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'
|
|
8
|
+
import { MinusCircleOutlined, PlusOutlined, HolderOutlined } from '@ant-design/icons'
|
|
9
9
|
import HelpIcon from '../common/help-icon'
|
|
10
10
|
import { copy } from '../../common/clipboard'
|
|
11
11
|
import { useRef } from 'react'
|
|
@@ -14,15 +14,70 @@ const FormItem = Form.Item
|
|
|
14
14
|
const FormList = Form.List
|
|
15
15
|
const e = window.translate
|
|
16
16
|
|
|
17
|
-
export default function renderQm () {
|
|
17
|
+
export default function renderQm (form) {
|
|
18
18
|
const focused = useRef(0)
|
|
19
|
-
|
|
19
|
+
const dragIndexRef = useRef(null)
|
|
20
|
+
|
|
21
|
+
function handleDragStart (e, index) {
|
|
22
|
+
dragIndexRef.current = index
|
|
23
|
+
e.target.closest('.ant-space-compact')?.classList.add('qm-field-dragging')
|
|
24
|
+
e.dataTransfer.effectAllowed = 'move'
|
|
25
|
+
e.dataTransfer.setData('text/plain', String(index))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function handleDragOver (e, index) {
|
|
29
|
+
e.preventDefault()
|
|
30
|
+
e.dataTransfer.dropEffect = 'move'
|
|
31
|
+
const el = e.target.closest('.ant-space-compact')
|
|
32
|
+
if (dragIndexRef.current !== index && el) {
|
|
33
|
+
el.classList.add('qm-field-dragover')
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function handleDragLeave (e) {
|
|
38
|
+
e.target.closest('.ant-space-compact')?.classList.remove('qm-field-dragover')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function handleDrop (e, index, form) {
|
|
42
|
+
e.preventDefault()
|
|
43
|
+
const el = e.target.closest('.ant-space-compact')
|
|
44
|
+
el?.classList.remove('qm-field-dragover')
|
|
45
|
+
const dragIndex = dragIndexRef.current
|
|
46
|
+
if (dragIndex === null || dragIndex === index) {
|
|
47
|
+
dragIndexRef.current = null
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
const commands = form.getFieldValue('commands') || []
|
|
51
|
+
const item = commands[dragIndex]
|
|
52
|
+
const newCommands = [...commands]
|
|
53
|
+
newCommands.splice(dragIndex, 1)
|
|
54
|
+
newCommands.splice(index, 0, item)
|
|
55
|
+
form.setFieldValue('commands', newCommands)
|
|
56
|
+
dragIndexRef.current = null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function handleDragEnd (e) {
|
|
60
|
+
const el = e.target.closest('.ant-space-compact')
|
|
61
|
+
el?.classList.remove('qm-field-dragging')
|
|
62
|
+
el?.classList.remove('qm-field-dragover')
|
|
63
|
+
dragIndexRef.current = null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function renderItem (field, i, add, remove, form) {
|
|
20
67
|
return (
|
|
21
68
|
<Space.Compact
|
|
22
69
|
align='center'
|
|
23
70
|
className='width-100 mg2b'
|
|
24
71
|
key={field.key}
|
|
72
|
+
draggable
|
|
73
|
+
onDragStart={(e) => handleDragStart(e, i)}
|
|
74
|
+
onDragOver={(e) => handleDragOver(e, i)}
|
|
75
|
+
onDragLeave={handleDragLeave}
|
|
76
|
+
onDrop={(e) => handleDrop(e, i, form)}
|
|
77
|
+
onDragEnd={handleDragEnd}
|
|
25
78
|
>
|
|
79
|
+
<HolderOutlined className='mg1r qm-drag-handle' />
|
|
80
|
+
|
|
26
81
|
<Space.Addon>{e('delay')}</Space.Addon>
|
|
27
82
|
<FormItem
|
|
28
83
|
label=''
|
|
@@ -119,7 +174,7 @@ export default function renderQm () {
|
|
|
119
174
|
<>
|
|
120
175
|
{
|
|
121
176
|
fields.map((field, i) => {
|
|
122
|
-
return renderItem(field, i, add, remove)
|
|
177
|
+
return renderItem(field, i, add, remove, form)
|
|
123
178
|
})
|
|
124
179
|
}
|
|
125
180
|
<FormItem>
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import List from '../setting-panel/list'
|
|
6
|
-
import { PlusOutlined } from '@ant-design/icons'
|
|
6
|
+
import { PlusOutlined, CopyOutlined } from '@ant-design/icons'
|
|
7
7
|
import { Select } from 'antd'
|
|
8
8
|
import classnames from 'classnames'
|
|
9
9
|
import highlight from '../common/highlight'
|
|
10
10
|
import QmTransport from './quick-command-transport'
|
|
11
11
|
import onDrop from './on-drop'
|
|
12
|
+
import copy from 'json-deep-copy'
|
|
13
|
+
import uid from '../../common/uid'
|
|
12
14
|
|
|
13
15
|
const { Option } = Select
|
|
14
16
|
const e = window.translate
|
|
@@ -45,17 +47,44 @@ export default class QuickCommandsList extends List {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
handleDragEnter = e => {
|
|
48
|
-
e.target.closest('.item-list-unit').classList.add('dragover')
|
|
50
|
+
e.target.closest('.item-list-unit').classList.add('qm-field-dragover')
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
handleDragLeave = e => {
|
|
52
|
-
e.target.closest('.item-list-unit').classList.remove('dragover')
|
|
54
|
+
e.target.closest('.item-list-unit').classList.remove('qm-field-dragover')
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
handleDrop = e => {
|
|
56
58
|
onDrop(e, '.item-list-unit')
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
duplicateItem = (e, item) => {
|
|
62
|
+
e.stopPropagation()
|
|
63
|
+
const { store } = window
|
|
64
|
+
const newCommand = copy(item)
|
|
65
|
+
newCommand.id = uid()
|
|
66
|
+
const baseName = item.name.replace(/\(\d+\)$/, '')
|
|
67
|
+
const sameNameCount = store.currentQuickCommands.filter(
|
|
68
|
+
cmd => cmd.name && cmd.name.replace(/\(\d+\)$/, '').includes(baseName)
|
|
69
|
+
).length
|
|
70
|
+
const duplicateIndex = sameNameCount > 0 ? sameNameCount : 1
|
|
71
|
+
newCommand.name = baseName + '(' + duplicateIndex + ')'
|
|
72
|
+
store.addQuickCommand(newCommand)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
renderDuplicateBtn = (item) => {
|
|
76
|
+
if (!item.id) {
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
return (
|
|
80
|
+
<CopyOutlined
|
|
81
|
+
title={e('duplicate')}
|
|
82
|
+
className='pointer list-item-duplicate'
|
|
83
|
+
onClick={(e) => this.duplicateItem(e, item)}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
59
88
|
renderItem = (item, i) => {
|
|
60
89
|
if (!item) {
|
|
61
90
|
return null
|
|
@@ -94,6 +123,7 @@ export default class QuickCommandsList extends List {
|
|
|
94
123
|
}
|
|
95
124
|
{title}
|
|
96
125
|
</div>
|
|
126
|
+
{this.renderDuplicateBtn(item)}
|
|
97
127
|
{this.renderDelBtn(item)}
|
|
98
128
|
</div>
|
|
99
129
|
)
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
.list-item-apply
|
|
8
8
|
.list-item-remove
|
|
9
9
|
.list-item-bookmark
|
|
10
|
+
.list-item-duplicate
|
|
10
11
|
display none
|
|
11
12
|
width 24px
|
|
12
13
|
line-height 35px
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
.list-item-edit
|
|
36
37
|
.list-item-remove
|
|
37
38
|
.list-item-bookmark
|
|
39
|
+
.list-item-duplicate
|
|
38
40
|
display block
|
|
39
41
|
.theme-item:hover
|
|
40
42
|
.list-item-remove
|
|
@@ -49,4 +51,6 @@
|
|
|
49
51
|
// right 20px
|
|
50
52
|
.item-list-unit
|
|
51
53
|
.list-item-bookmark
|
|
52
|
-
right 18px
|
|
54
|
+
right 18px
|
|
55
|
+
.list-item-duplicate
|
|
56
|
+
right 24px
|
|
@@ -164,7 +164,7 @@ export default class TerminalCmdSuggestions extends Component {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
handleDelete = (item) => {
|
|
167
|
-
window.store.
|
|
167
|
+
window.store.deleteCmdHistory(item.command)
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
handleSelect = (item) => {
|
|
@@ -484,9 +484,22 @@ class Term extends Component {
|
|
|
484
484
|
}
|
|
485
485
|
|
|
486
486
|
onClear = () => {
|
|
487
|
+
const shouldClear = this.searchAddon &&
|
|
488
|
+
window.store.termSearchOpen &&
|
|
489
|
+
window.store.termSearch
|
|
490
|
+
if (
|
|
491
|
+
shouldClear
|
|
492
|
+
) {
|
|
493
|
+
this.searchAddon.clearDecorations()
|
|
494
|
+
}
|
|
487
495
|
this.term.clear()
|
|
488
496
|
this.term.focus()
|
|
489
|
-
|
|
497
|
+
if (shouldClear) {
|
|
498
|
+
this.searchAddon._linesCache = undefined
|
|
499
|
+
this.timers.clearSearchTimer = setTimeout(() => {
|
|
500
|
+
refsStatic.get('term-search')?.next()
|
|
501
|
+
}, 100)
|
|
502
|
+
}
|
|
490
503
|
}
|
|
491
504
|
|
|
492
505
|
isRemote = () => {
|
|
@@ -171,6 +171,10 @@ export default function TreeListItem (props) {
|
|
|
171
171
|
props.onDragStart(e)
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
const onDragEnter = e => {
|
|
175
|
+
props.onDragEnter(e)
|
|
176
|
+
}
|
|
177
|
+
|
|
174
178
|
const onDragLeave = e => {
|
|
175
179
|
props.onDragLeave(e)
|
|
176
180
|
}
|
|
@@ -224,6 +228,7 @@ export default function TreeListItem (props) {
|
|
|
224
228
|
'data-is-group': isGroup ? 'true' : 'false',
|
|
225
229
|
onDragOver,
|
|
226
230
|
onDragStart,
|
|
231
|
+
onDragEnter,
|
|
227
232
|
onDragLeave,
|
|
228
233
|
onDrop
|
|
229
234
|
}
|
|
@@ -381,6 +381,18 @@ export default class ItemListTree extends Component {
|
|
|
381
381
|
)
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
+
onDragEnter = e => {
|
|
385
|
+
e.preventDefault()
|
|
386
|
+
let {
|
|
387
|
+
target
|
|
388
|
+
} = e
|
|
389
|
+
const tar = findParentBySel(target, '.tree-item')
|
|
390
|
+
if (tar) {
|
|
391
|
+
target = tar
|
|
392
|
+
}
|
|
393
|
+
target.classList.add('item-dragover-top')
|
|
394
|
+
}
|
|
395
|
+
|
|
384
396
|
onDragLeave = e => {
|
|
385
397
|
e.preventDefault()
|
|
386
398
|
let {
|
|
@@ -390,7 +402,7 @@ export default class ItemListTree extends Component {
|
|
|
390
402
|
if (tar) {
|
|
391
403
|
target = tar
|
|
392
404
|
}
|
|
393
|
-
target.classList.remove('item-dragover')
|
|
405
|
+
target.classList.remove('item-dragover-top')
|
|
394
406
|
}
|
|
395
407
|
|
|
396
408
|
onDragOver = e => {
|
|
@@ -401,14 +413,14 @@ export default class ItemListTree extends Component {
|
|
|
401
413
|
if (tar) {
|
|
402
414
|
target = tar
|
|
403
415
|
}
|
|
404
|
-
target.classList.add('item-dragover')
|
|
416
|
+
target.classList.add('item-dragover-top')
|
|
405
417
|
}
|
|
406
418
|
|
|
407
419
|
onDrop = action(e => {
|
|
408
420
|
e.preventDefault()
|
|
409
|
-
const elems = document.querySelectorAll('.tree-item.item-dragover')
|
|
421
|
+
const elems = document.querySelectorAll('.tree-item.item-dragover-top')
|
|
410
422
|
elems.forEach(elem => {
|
|
411
|
-
elem.classList.remove('item-dragover')
|
|
423
|
+
elem.classList.remove('item-dragover-top')
|
|
412
424
|
})
|
|
413
425
|
let {
|
|
414
426
|
target
|
|
@@ -662,6 +674,7 @@ export default class ItemListTree extends Component {
|
|
|
662
674
|
'duplicateItem',
|
|
663
675
|
'onDragStart',
|
|
664
676
|
'onDrop',
|
|
677
|
+
'onDragEnter',
|
|
665
678
|
'onDragLeave',
|
|
666
679
|
'onDragOver'
|
|
667
680
|
]
|
package/client/store/common.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import * as ls from '../common/safe-local-storage'
|
|
19
19
|
import { refs, refsStatic } from '../components/common/ref'
|
|
20
20
|
import { action } from 'manate'
|
|
21
|
+
import uid from '../common/uid'
|
|
21
22
|
import deepCopy from 'json-deep-copy'
|
|
22
23
|
import { aiConfigsArr } from '../components/ai/ai-config-props'
|
|
23
24
|
import settingList from '../common/setting-list'
|
|
@@ -345,36 +346,35 @@ export default Store => {
|
|
|
345
346
|
return
|
|
346
347
|
}
|
|
347
348
|
const { terminalCommandHistory } = window.store
|
|
348
|
-
const existing = terminalCommandHistory.
|
|
349
|
+
const existing = terminalCommandHistory.find(item => item.cmd === cmd)
|
|
349
350
|
if (existing) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
count: existing.count + 1,
|
|
353
|
-
lastUseTime: new Date().toISOString()
|
|
354
|
-
})
|
|
351
|
+
existing.count = existing.count + 1
|
|
352
|
+
existing.lastUseTime = new Date().toISOString()
|
|
355
353
|
} else {
|
|
356
|
-
terminalCommandHistory.
|
|
354
|
+
terminalCommandHistory.push({
|
|
355
|
+
id: uid(),
|
|
356
|
+
cmd,
|
|
357
357
|
count: 1,
|
|
358
358
|
lastUseTime: new Date().toISOString()
|
|
359
359
|
})
|
|
360
360
|
}
|
|
361
|
-
if (terminalCommandHistory.
|
|
361
|
+
if (terminalCommandHistory.length > 200) {
|
|
362
362
|
// Delete oldest 20 items when history exceeds 100
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
for (let i = 0; i < 20 && i < entries.length; i++) {
|
|
366
|
-
terminalCommandHistory.delete(entries[i][0])
|
|
367
|
-
}
|
|
363
|
+
terminalCommandHistory.sort((a, b) => new Date(a.lastUseTime).getTime() - new Date(b.lastUseTime).getTime())
|
|
364
|
+
terminalCommandHistory.splice(0, 20)
|
|
368
365
|
}
|
|
369
366
|
})
|
|
370
367
|
|
|
371
368
|
Store.prototype.deleteCmdHistory = function (cmd) {
|
|
372
369
|
const { terminalCommandHistory } = window.store
|
|
373
|
-
terminalCommandHistory.
|
|
370
|
+
const idx = terminalCommandHistory.findIndex(item => item.cmd === cmd)
|
|
371
|
+
if (idx !== -1) {
|
|
372
|
+
terminalCommandHistory.splice(idx, 1)
|
|
373
|
+
}
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
Store.prototype.clearAllCmdHistory = function () {
|
|
377
|
-
window.store.terminalCommandHistory =
|
|
377
|
+
window.store.terminalCommandHistory = []
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
Store.prototype.runCmdFromHistory = function (cmd) {
|
|
@@ -21,10 +21,8 @@ import {
|
|
|
21
21
|
dismissDelKeyTipLsKey,
|
|
22
22
|
qmSortByFrequencyKey,
|
|
23
23
|
resolutionsLsKey,
|
|
24
|
-
aiChatHistoryKey,
|
|
25
24
|
syncServerDataKey,
|
|
26
|
-
splitMap
|
|
27
|
-
cmdHistoryKey
|
|
25
|
+
splitMap
|
|
28
26
|
} from '../common/constants'
|
|
29
27
|
import * as ls from '../common/safe-local-storage'
|
|
30
28
|
import { exclude } from 'manate'
|
|
@@ -54,7 +52,7 @@ export default () => {
|
|
|
54
52
|
lastDataUpdateTime: 0,
|
|
55
53
|
tabs: [],
|
|
56
54
|
activeTabId: '',
|
|
57
|
-
history:
|
|
55
|
+
history: [],
|
|
58
56
|
sshConfigs: [],
|
|
59
57
|
bookmarks: [],
|
|
60
58
|
bookmarksMap: new Map(),
|
|
@@ -74,32 +72,9 @@ export default () => {
|
|
|
74
72
|
addressBookmarksLocal: ls.getItemJSON(localAddrBookmarkLsKey, []),
|
|
75
73
|
openResolutionEdit: false,
|
|
76
74
|
resolutions: ls.getItemJSON(resolutionsLsKey, []),
|
|
77
|
-
// terminalCommandHistory:
|
|
78
|
-
//
|
|
79
|
-
terminalCommandHistory:
|
|
80
|
-
const savedData = ls.safeGetItemJSON(cmdHistoryKey, [])
|
|
81
|
-
const map = new Map()
|
|
82
|
-
if (Array.isArray(savedData)) {
|
|
83
|
-
// Check if old format (array of strings) or new format (array of objects)
|
|
84
|
-
if (savedData.length > 0 && typeof savedData[0] === 'string') {
|
|
85
|
-
// Old format: migrate to new format
|
|
86
|
-
savedData.forEach(cmd => {
|
|
87
|
-
map.set(cmd, { count: 1, lastUseTime: new Date().toISOString() })
|
|
88
|
-
})
|
|
89
|
-
} else {
|
|
90
|
-
// New format: array of {cmd, count, lastUseTime}
|
|
91
|
-
savedData.forEach(item => {
|
|
92
|
-
if (item.cmd) {
|
|
93
|
-
map.set(item.cmd, {
|
|
94
|
-
count: item.count || 1,
|
|
95
|
-
lastUseTime: item.lastUseTime || new Date().toISOString()
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return map
|
|
102
|
-
})(),
|
|
75
|
+
// terminalCommandHistory: [{ id, cmd, count, lastUseTime }]
|
|
76
|
+
// Loaded from DB in initData
|
|
77
|
+
terminalCommandHistory: [],
|
|
103
78
|
|
|
104
79
|
// workspaces
|
|
105
80
|
workspaces: [],
|
|
@@ -111,7 +86,7 @@ export default () => {
|
|
|
111
86
|
|
|
112
87
|
// batch input selected tab ids
|
|
113
88
|
_batchInputSelectedTabIds: new Set(),
|
|
114
|
-
aiChatHistory:
|
|
89
|
+
aiChatHistory: [],
|
|
115
90
|
|
|
116
91
|
// sftp
|
|
117
92
|
fileOperation: fileOperationsMap.cp, // cp or mv
|
|
@@ -7,6 +7,10 @@ import uid from '../common/uid'
|
|
|
7
7
|
import { settingMap } from '../common/constants'
|
|
8
8
|
import { refs } from '../components/common/ref'
|
|
9
9
|
import deepCopy from 'json-deep-copy'
|
|
10
|
+
import {
|
|
11
|
+
getLocalFileInfo,
|
|
12
|
+
getRemoteFileInfo
|
|
13
|
+
} from '../components/sftp/file-read'
|
|
10
14
|
|
|
11
15
|
export default Store => {
|
|
12
16
|
// Initialize MCP handler - called when MCP widget is started
|
|
@@ -56,8 +60,7 @@ export default Store => {
|
|
|
56
60
|
case 'add_bookmark_group':
|
|
57
61
|
result = await store.mcpAddBookmarkGroup(args)
|
|
58
62
|
break
|
|
59
|
-
|
|
60
|
-
// Quick command operations
|
|
63
|
+
/*
|
|
61
64
|
case 'list_quick_commands':
|
|
62
65
|
result = store.mcpListQuickCommands()
|
|
63
66
|
break
|
|
@@ -70,7 +73,7 @@ export default Store => {
|
|
|
70
73
|
case 'delete_quick_command':
|
|
71
74
|
result = store.mcpDeleteQuickCommand(args)
|
|
72
75
|
break
|
|
73
|
-
|
|
76
|
+
*/
|
|
74
77
|
// Tab operations
|
|
75
78
|
case 'list_tabs':
|
|
76
79
|
result = store.mcpListTabs()
|
|
@@ -105,20 +108,34 @@ export default Store => {
|
|
|
105
108
|
result = store.mcpGetTerminalOutput(args)
|
|
106
109
|
break
|
|
107
110
|
|
|
108
|
-
//
|
|
109
|
-
case '
|
|
110
|
-
result = store.
|
|
111
|
+
// SFTP operations
|
|
112
|
+
case 'sftp_list':
|
|
113
|
+
result = await store.mcpSftpList(args)
|
|
114
|
+
break
|
|
115
|
+
case 'sftp_del':
|
|
116
|
+
result = await store.mcpSftpDel(args)
|
|
111
117
|
break
|
|
112
|
-
case '
|
|
113
|
-
result = store.
|
|
118
|
+
case 'sftp_stat':
|
|
119
|
+
result = await store.mcpSftpStat(args)
|
|
120
|
+
break
|
|
121
|
+
case 'sftp_read_file':
|
|
122
|
+
result = await store.mcpSftpReadFile(args)
|
|
114
123
|
break
|
|
115
124
|
|
|
116
|
-
//
|
|
117
|
-
case '
|
|
118
|
-
result = store.
|
|
125
|
+
// File transfer operations
|
|
126
|
+
case 'sftp_upload':
|
|
127
|
+
result = await store.mcpSftpUpload(args)
|
|
119
128
|
break
|
|
120
|
-
case '
|
|
121
|
-
result = store.
|
|
129
|
+
case 'sftp_download':
|
|
130
|
+
result = await store.mcpSftpDownload(args)
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
// Zmodem (trzsz/rzsz) operations
|
|
134
|
+
case 'zmodem_upload':
|
|
135
|
+
result = store.mcpZmodemUpload(args)
|
|
136
|
+
break
|
|
137
|
+
case 'zmodem_download':
|
|
138
|
+
result = store.mcpZmodemDownload(args)
|
|
122
139
|
break
|
|
123
140
|
|
|
124
141
|
// Settings operations
|
|
@@ -249,58 +266,58 @@ export default Store => {
|
|
|
249
266
|
|
|
250
267
|
// ==================== Quick Command APIs ====================
|
|
251
268
|
|
|
252
|
-
Store.prototype.mcpListQuickCommands = function () {
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
Store.prototype.mcpAddQuickCommand = function (args) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
Store.prototype.mcpRunQuickCommand = function (args) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
Store.prototype.mcpDeleteQuickCommand = function (args) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
269
|
+
// Store.prototype.mcpListQuickCommands = function () {
|
|
270
|
+
// return deepCopy(window.store.quickCommands)
|
|
271
|
+
// }
|
|
272
|
+
|
|
273
|
+
// Store.prototype.mcpAddQuickCommand = function (args) {
|
|
274
|
+
// const { store } = window
|
|
275
|
+
// const qm = {
|
|
276
|
+
// id: uid(),
|
|
277
|
+
// name: args.name,
|
|
278
|
+
// commands: args.commands,
|
|
279
|
+
// inputOnly: args.inputOnly || false,
|
|
280
|
+
// labels: args.labels || []
|
|
281
|
+
// }
|
|
282
|
+
|
|
283
|
+
// store.addQuickCommand(qm)
|
|
284
|
+
|
|
285
|
+
// return {
|
|
286
|
+
// success: true,
|
|
287
|
+
// id: qm.id,
|
|
288
|
+
// message: `Quick command "${qm.name}" created`
|
|
289
|
+
// }
|
|
290
|
+
// }
|
|
291
|
+
|
|
292
|
+
// Store.prototype.mcpRunQuickCommand = function (args) {
|
|
293
|
+
// const { store } = window
|
|
294
|
+
// const qm = store.quickCommands.find(q => q.id === args.id)
|
|
295
|
+
// if (!qm) {
|
|
296
|
+
// throw new Error(`Quick command not found: ${args.id}`)
|
|
297
|
+
// }
|
|
298
|
+
|
|
299
|
+
// store.runQuickCommandItem(args.id)
|
|
300
|
+
|
|
301
|
+
// return {
|
|
302
|
+
// success: true,
|
|
303
|
+
// message: `Executed quick command "${qm.name}"`
|
|
304
|
+
// }
|
|
305
|
+
// }
|
|
306
|
+
|
|
307
|
+
// Store.prototype.mcpDeleteQuickCommand = function (args) {
|
|
308
|
+
// const { store } = window
|
|
309
|
+
// const qm = store.quickCommands.find(q => q.id === args.id)
|
|
310
|
+
// if (!qm) {
|
|
311
|
+
// throw new Error(`Quick command not found: ${args.id}`)
|
|
312
|
+
// }
|
|
313
|
+
|
|
314
|
+
// store.delQuickCommand({ id: args.id })
|
|
315
|
+
|
|
316
|
+
// return {
|
|
317
|
+
// success: true,
|
|
318
|
+
// message: `Deleted quick command "${qm.name}"`
|
|
319
|
+
// }
|
|
320
|
+
// }
|
|
304
321
|
|
|
305
322
|
// ==================== Tab APIs ====================
|
|
306
323
|
|
|
@@ -502,92 +519,261 @@ export default Store => {
|
|
|
502
519
|
}
|
|
503
520
|
}
|
|
504
521
|
|
|
505
|
-
// ====================
|
|
522
|
+
// ==================== Settings APIs ====================
|
|
506
523
|
|
|
507
|
-
Store.prototype.
|
|
524
|
+
Store.prototype.mcpGetSettings = function () {
|
|
508
525
|
const { store } = window
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
type: h.type,
|
|
517
|
-
time: h.time
|
|
518
|
-
}))
|
|
526
|
+
// Return safe settings (no sensitive data)
|
|
527
|
+
const config = store.config
|
|
528
|
+
const excludeKeys = ['apiKeyAI', 'syncSetting']
|
|
529
|
+
const safeConfig = Object.fromEntries(
|
|
530
|
+
Object.entries(config).filter(([key]) => !excludeKeys.includes(key))
|
|
531
|
+
)
|
|
532
|
+
return safeConfig
|
|
519
533
|
}
|
|
520
534
|
|
|
521
|
-
|
|
535
|
+
// ==================== SFTP APIs ====================
|
|
536
|
+
|
|
537
|
+
Store.prototype.mcpGetSshSftpRef = function (tabId) {
|
|
522
538
|
const { store } = window
|
|
523
|
-
|
|
539
|
+
const resolvedTabId = tabId || store.activeTabId
|
|
540
|
+
if (!resolvedTabId) {
|
|
541
|
+
throw new Error('No active tab')
|
|
542
|
+
}
|
|
543
|
+
const tab = store.tabs.find(t => t.id === resolvedTabId)
|
|
544
|
+
if (!tab) {
|
|
545
|
+
throw new Error(`Tab not found: ${resolvedTabId}`)
|
|
546
|
+
}
|
|
547
|
+
if (tab.type !== 'ssh' && tab.type !== 'ftp') {
|
|
548
|
+
throw new Error(`Tab "${resolvedTabId}" is not an SSH/SFTP tab (type: ${tab.type || 'local'})`)
|
|
549
|
+
}
|
|
550
|
+
const sftpEntry = refs.get('sftp-' + resolvedTabId)
|
|
551
|
+
if (!sftpEntry || !sftpEntry.sftp) {
|
|
552
|
+
throw new Error(`SFTP not initialized for tab "${resolvedTabId}". Open the SFTP panel first.`)
|
|
553
|
+
}
|
|
554
|
+
return { sftp: sftpEntry.sftp, tab, tabId: resolvedTabId }
|
|
555
|
+
}
|
|
524
556
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
557
|
+
Store.prototype.mcpSftpList = async function (args) {
|
|
558
|
+
const { sftp, tab, tabId } = window.store.mcpGetSshSftpRef(args.tabId)
|
|
559
|
+
const remotePath = args.remotePath
|
|
560
|
+
if (!remotePath) {
|
|
561
|
+
throw new Error('remotePath is required')
|
|
528
562
|
}
|
|
563
|
+
const list = await sftp.list(remotePath)
|
|
564
|
+
return { tabId, host: tab.host, path: remotePath, list }
|
|
529
565
|
}
|
|
530
566
|
|
|
531
|
-
|
|
567
|
+
Store.prototype.mcpSftpStat = async function (args) {
|
|
568
|
+
const { sftp, tab, tabId } = window.store.mcpGetSshSftpRef(args.tabId)
|
|
569
|
+
const remotePath = args.remotePath
|
|
570
|
+
if (!remotePath) {
|
|
571
|
+
throw new Error('remotePath is required')
|
|
572
|
+
}
|
|
573
|
+
const stat = await sftp.stat(remotePath)
|
|
574
|
+
return { tabId, host: tab.host, path: remotePath, stat }
|
|
575
|
+
}
|
|
532
576
|
|
|
533
|
-
Store.prototype.
|
|
534
|
-
const {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
status: t.status
|
|
542
|
-
}))
|
|
577
|
+
Store.prototype.mcpSftpReadFile = async function (args) {
|
|
578
|
+
const { sftp, tab, tabId } = window.store.mcpGetSshSftpRef(args.tabId)
|
|
579
|
+
const remotePath = args.remotePath
|
|
580
|
+
if (!remotePath) {
|
|
581
|
+
throw new Error('remotePath is required')
|
|
582
|
+
}
|
|
583
|
+
const content = await sftp.readFile(remotePath)
|
|
584
|
+
return { tabId, host: tab.host, path: remotePath, content }
|
|
543
585
|
}
|
|
544
586
|
|
|
545
|
-
Store.prototype.
|
|
546
|
-
const {
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
587
|
+
Store.prototype.mcpSftpDel = async function (args) {
|
|
588
|
+
const { sftp, tab, tabId } = window.store.mcpGetSshSftpRef(args.tabId)
|
|
589
|
+
const remotePath = args.remotePath
|
|
590
|
+
if (!remotePath) {
|
|
591
|
+
throw new Error('remotePath is required')
|
|
592
|
+
}
|
|
593
|
+
// Use stat to determine if it's a file or directory
|
|
594
|
+
const stat = await sftp.stat(remotePath)
|
|
595
|
+
const isDirectory = typeof stat.isDirectory === 'function'
|
|
596
|
+
? stat.isDirectory()
|
|
597
|
+
: !!stat.isDirectory
|
|
598
|
+
if (isDirectory) {
|
|
599
|
+
await sftp.rmdir(remotePath)
|
|
600
|
+
} else {
|
|
601
|
+
await sftp.rm(remotePath)
|
|
602
|
+
}
|
|
603
|
+
return { success: true, tabId, host: tab.host, path: remotePath, type: isDirectory ? 'directory' : 'file' }
|
|
556
604
|
}
|
|
557
605
|
|
|
558
|
-
// ====================
|
|
606
|
+
// ==================== File Transfer APIs ====================
|
|
559
607
|
|
|
560
|
-
Store.prototype.
|
|
608
|
+
Store.prototype.mcpSftpUpload = async function (args) {
|
|
561
609
|
const { store } = window
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
610
|
+
const { tab, tabId } = store.mcpGetSshSftpRef(args.tabId)
|
|
611
|
+
const localPath = args.localPath
|
|
612
|
+
const remotePath = args.remotePath
|
|
613
|
+
if (!localPath) {
|
|
614
|
+
throw new Error('localPath is required')
|
|
615
|
+
}
|
|
616
|
+
if (!remotePath) {
|
|
617
|
+
throw new Error('remotePath is required')
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
window._transferConflictPolicy = args.conflictPolicy || 'overwrite'
|
|
621
|
+
|
|
622
|
+
const fromFile = await getLocalFileInfo(localPath)
|
|
623
|
+
const transferItem = {
|
|
624
|
+
host: tab.host,
|
|
625
|
+
tabType: tab.type || 'ssh',
|
|
626
|
+
typeFrom: 'local',
|
|
627
|
+
typeTo: 'remote',
|
|
628
|
+
fromPath: localPath,
|
|
629
|
+
toPath: remotePath,
|
|
630
|
+
fromFile: {
|
|
631
|
+
...fromFile,
|
|
632
|
+
host: tab.host,
|
|
633
|
+
tabType: tab.type || 'ssh',
|
|
634
|
+
tabId,
|
|
635
|
+
title: tab.title
|
|
636
|
+
},
|
|
637
|
+
id: uid(),
|
|
638
|
+
title: tab.title,
|
|
639
|
+
tabId,
|
|
640
|
+
operation: ''
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
store.addTransferList([transferItem])
|
|
644
|
+
|
|
645
|
+
return {
|
|
646
|
+
success: true,
|
|
647
|
+
message: `Upload started: ${localPath} → ${tab.host}:${remotePath}`,
|
|
648
|
+
transferId: transferItem.id,
|
|
649
|
+
tabId
|
|
573
650
|
}
|
|
574
|
-
return safeConfig
|
|
575
651
|
}
|
|
576
652
|
|
|
577
|
-
Store.prototype.
|
|
653
|
+
Store.prototype.mcpSftpDownload = async function (args) {
|
|
578
654
|
const { store } = window
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
655
|
+
const { sftp, tab, tabId } = store.mcpGetSshSftpRef(args.tabId) // sftp used for getRemoteFileInfo
|
|
656
|
+
const remotePath = args.remotePath
|
|
657
|
+
const localPath = args.localPath
|
|
658
|
+
if (!remotePath) {
|
|
659
|
+
throw new Error('remotePath is required')
|
|
660
|
+
}
|
|
661
|
+
if (!localPath) {
|
|
662
|
+
throw new Error('localPath is required')
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
window._transferConflictPolicy = args.conflictPolicy || 'overwrite'
|
|
666
|
+
|
|
667
|
+
const fromFile = await getRemoteFileInfo(sftp, remotePath)
|
|
668
|
+
const transferItem = {
|
|
669
|
+
host: tab.host,
|
|
670
|
+
tabType: tab.type || 'ssh',
|
|
671
|
+
typeFrom: 'remote',
|
|
672
|
+
typeTo: 'local',
|
|
673
|
+
fromPath: remotePath,
|
|
674
|
+
toPath: localPath,
|
|
675
|
+
fromFile: {
|
|
676
|
+
...fromFile,
|
|
677
|
+
id: uid(),
|
|
678
|
+
isSymbolicLink: false
|
|
679
|
+
},
|
|
680
|
+
id: uid(),
|
|
681
|
+
title: tab.title,
|
|
682
|
+
tabId
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
store.addTransferList([transferItem])
|
|
686
|
+
|
|
687
|
+
return {
|
|
688
|
+
success: true,
|
|
689
|
+
message: `Download started: ${tab.host}:${remotePath} → ${localPath}`,
|
|
690
|
+
transferId: transferItem.id,
|
|
691
|
+
tabId
|
|
692
|
+
}
|
|
584
693
|
}
|
|
585
694
|
|
|
586
|
-
|
|
695
|
+
// ==================== Zmodem (trzsz/rzsz) APIs ====================
|
|
696
|
+
|
|
697
|
+
Store.prototype.mcpZmodemUpload = function (args) {
|
|
587
698
|
const { store } = window
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
699
|
+
const tabId = args.tabId || store.activeTabId
|
|
700
|
+
if (!tabId) {
|
|
701
|
+
throw new Error('No active tab')
|
|
702
|
+
}
|
|
703
|
+
const tab = store.tabs.find(t => t.id === tabId)
|
|
704
|
+
if (!tab) {
|
|
705
|
+
throw new Error(`Tab not found: ${tabId}`)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const files = args.files
|
|
709
|
+
if (!files || !Array.isArray(files) || files.length === 0) {
|
|
710
|
+
throw new Error('files array is required (list of local file paths to upload)')
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const protocol = args.protocol || 'rzsz'
|
|
714
|
+
const uploadCmd = protocol === 'trzsz' ? 'trz' : 'rz'
|
|
715
|
+
|
|
716
|
+
// Set the control variable to bypass native file dialog
|
|
717
|
+
window._apiControlSelectFile = files
|
|
718
|
+
|
|
719
|
+
const term = refs.get('term-' + tabId)
|
|
720
|
+
if (!term) {
|
|
721
|
+
throw new Error(`Terminal not found for tab: ${tabId}`)
|
|
722
|
+
}
|
|
723
|
+
term.runQuickCommand(uploadCmd)
|
|
724
|
+
|
|
725
|
+
return {
|
|
726
|
+
success: true,
|
|
727
|
+
protocol,
|
|
728
|
+
command: uploadCmd,
|
|
729
|
+
message: `${uploadCmd} upload initiated for ${files.length} file(s)`,
|
|
730
|
+
files,
|
|
731
|
+
tabId
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
Store.prototype.mcpZmodemDownload = function (args) {
|
|
736
|
+
const { store } = window
|
|
737
|
+
const tabId = args.tabId || store.activeTabId
|
|
738
|
+
if (!tabId) {
|
|
739
|
+
throw new Error('No active tab')
|
|
740
|
+
}
|
|
741
|
+
const tab = store.tabs.find(t => t.id === tabId)
|
|
742
|
+
if (!tab) {
|
|
743
|
+
throw new Error(`Tab not found: ${tabId}`)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const saveFolder = args.saveFolder
|
|
747
|
+
if (!saveFolder) {
|
|
748
|
+
throw new Error('saveFolder is required (local folder to save downloaded files)')
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const remoteFiles = args.remoteFiles
|
|
752
|
+
if (!remoteFiles || !Array.isArray(remoteFiles) || remoteFiles.length === 0) {
|
|
753
|
+
throw new Error('remoteFiles array is required (list of remote file paths to download)')
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const protocol = args.protocol || 'rzsz'
|
|
757
|
+
const downloadCmd = protocol === 'trzsz' ? 'tsz' : 'sz'
|
|
758
|
+
|
|
759
|
+
// Set the control variable to bypass native folder dialog
|
|
760
|
+
window._apiControlSelectFolder = saveFolder
|
|
761
|
+
|
|
762
|
+
const term = refs.get('term-' + tabId)
|
|
763
|
+
if (!term) {
|
|
764
|
+
throw new Error(`Terminal not found for tab: ${tabId}`)
|
|
765
|
+
}
|
|
766
|
+
const quotedFiles = remoteFiles.map(f => `"${f}"`).join(' ')
|
|
767
|
+
term.runQuickCommand(`${downloadCmd} ${quotedFiles}`)
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
success: true,
|
|
771
|
+
protocol,
|
|
772
|
+
command: downloadCmd,
|
|
773
|
+
message: `${downloadCmd} download initiated for ${remoteFiles.length} file(s) to ${saveFolder}`,
|
|
774
|
+
remoteFiles,
|
|
775
|
+
saveFolder,
|
|
776
|
+
tabId
|
|
777
|
+
}
|
|
592
778
|
}
|
|
593
779
|
}
|
package/client/store/store.js
CHANGED
|
@@ -176,7 +176,7 @@ class Store {
|
|
|
176
176
|
|
|
177
177
|
get terminalCommandSuggestions () {
|
|
178
178
|
const { store } = window
|
|
179
|
-
const historyCommands =
|
|
179
|
+
const historyCommands = store.terminalCommandHistory.map(item => item.cmd)
|
|
180
180
|
const batchInputCommands = store.batchInputs || []
|
|
181
181
|
const quickCommands = (store.quickCommands || []).reduce(
|
|
182
182
|
(p, q) => {
|
|
@@ -21,6 +21,7 @@ export default Store => {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
Store.prototype.addTransferList = function (items) {
|
|
24
|
+
// console.log('addTransferList', JSON.stringify(items, null, 2))
|
|
24
25
|
const { fileTransfers } = window.store
|
|
25
26
|
const transferBatch = uid()
|
|
26
27
|
const nextItems = items.map(t => {
|
package/client/store/watch.js
CHANGED
|
@@ -11,9 +11,7 @@ import {
|
|
|
11
11
|
expandedKeysLsKey,
|
|
12
12
|
resolutionsLsKey,
|
|
13
13
|
localAddrBookmarkLsKey,
|
|
14
|
-
syncServerDataKey
|
|
15
|
-
aiChatHistoryKey,
|
|
16
|
-
cmdHistoryKey
|
|
14
|
+
syncServerDataKey
|
|
17
15
|
} from '../common/constants'
|
|
18
16
|
import * as ls from '../common/safe-local-storage'
|
|
19
17
|
import { debounce, isEmpty } from 'lodash-es'
|
|
@@ -134,28 +132,6 @@ export default store => {
|
|
|
134
132
|
return store.syncServerStatus
|
|
135
133
|
}).start()
|
|
136
134
|
|
|
137
|
-
autoRun(() => {
|
|
138
|
-
ls.safeSetItemJSON('history', store.history)
|
|
139
|
-
return store.history
|
|
140
|
-
}).start()
|
|
141
|
-
|
|
142
|
-
autoRun(() => {
|
|
143
|
-
ls.safeSetItemJSON(aiChatHistoryKey, store.aiChatHistory)
|
|
144
|
-
return store.aiChatHistory
|
|
145
|
-
}).start()
|
|
146
|
-
|
|
147
|
-
autoRun(() => {
|
|
148
|
-
const history = store.terminalCommandHistory
|
|
149
|
-
// Save in new format: array of {cmd, count, lastUseTime}
|
|
150
|
-
const data = Array.from(history.entries()).map(([cmd, info]) => ({
|
|
151
|
-
cmd,
|
|
152
|
-
count: info.count,
|
|
153
|
-
lastUseTime: info.lastUseTime
|
|
154
|
-
}))
|
|
155
|
-
ls.safeSetItemJSON(cmdHistoryKey, data)
|
|
156
|
-
return store.terminalCommandHistory
|
|
157
|
-
}).start()
|
|
158
|
-
|
|
159
135
|
autoRun(() => {
|
|
160
136
|
store.updateBatchInputSelectedTabIds()
|
|
161
137
|
const tabs = store.getTabs()
|