@electerm/electerm-react 2.3.166 → 2.3.176
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/download.jsx +1 -1
- package/client/common/error-handler.jsx +1 -1
- package/client/common/fetch.jsx +1 -1
- package/client/components/main/connection-hopping-warnning.jsx +1 -1
- package/client/components/quick-commands/qm.styl +0 -10
- package/client/components/quick-commands/quick-command-item.jsx +2 -5
- package/client/components/quick-commands/quick-commands-box.jsx +12 -23
- package/client/components/setting-panel/setting-modal.jsx +2 -1
- package/client/components/setting-sync/setting-sync-form.jsx +1 -1
- package/client/components/sftp/sftp-entry.jsx +1 -1
- package/client/components/sftp/sftp.styl +1 -1
- package/client/components/ssh-config/ssh-config-load-notify.jsx +1 -1
- package/client/components/terminal/terminal.jsx +1 -1
- package/client/components/widgets/widget-control.jsx +8 -1
- package/client/components/widgets/widget-form.jsx +14 -11
- package/client/css/includes/box.styl +2 -2
- package/client/store/common.js +9 -5
- package/client/store/mcp-handler.js +640 -0
- package/client/store/store.js +2 -0
- package/client/store/widgets.js +4 -0
- package/package.json +1 -1
package/client/common/fetch.jsx
CHANGED
|
@@ -17,16 +17,6 @@
|
|
|
17
17
|
.qm-list-wrap
|
|
18
18
|
max-height 100px
|
|
19
19
|
|
|
20
|
-
.fil-keyword
|
|
21
|
-
.fil-label
|
|
22
|
-
.qm-item
|
|
23
|
-
color var(--text-disabled)
|
|
24
|
-
.qm-item
|
|
25
|
-
&.name-match
|
|
26
|
-
&.label-match
|
|
27
|
-
font-weight bold
|
|
28
|
-
color var(--text)
|
|
29
|
-
|
|
30
20
|
.qm-search-input
|
|
31
21
|
max-width 200px
|
|
32
22
|
@media (max-width: 500px)
|
|
@@ -18,17 +18,14 @@ export default class QuickCommandsItem extends PureComponent {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
render () {
|
|
21
|
-
const { name, id,
|
|
21
|
+
const { name, id, shortcut } = this.props.item
|
|
22
22
|
const {
|
|
23
23
|
draggable,
|
|
24
24
|
handleDragOver,
|
|
25
25
|
handleDragStart,
|
|
26
26
|
handleDrop
|
|
27
27
|
} = this.props
|
|
28
|
-
const cls = classNames('qm-item mg1r mg1b'
|
|
29
|
-
'name-match': nameMatch,
|
|
30
|
-
'label-match': labelMatch
|
|
31
|
-
})
|
|
28
|
+
const cls = classNames('qm-item mg1r mg1b')
|
|
32
29
|
const btnProps = {
|
|
33
30
|
className: cls,
|
|
34
31
|
onClick: this.handleSelect,
|
|
@@ -129,15 +129,12 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
129
129
|
)
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
function
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
sorters.push((obj) => -(obj.clickCount || 0))
|
|
139
|
-
}
|
|
140
|
-
return sortBy(array, sorters)
|
|
132
|
+
function filterArray (array, keyword, labels) {
|
|
133
|
+
return array.filter(obj => {
|
|
134
|
+
const nameMatches = !keyword || obj.name.toLowerCase().includes(keyword)
|
|
135
|
+
const labelMatches = !labels.length || labels.some((label) => (obj.labels || []).includes(label))
|
|
136
|
+
return nameMatches && labelMatches
|
|
137
|
+
})
|
|
141
138
|
}
|
|
142
139
|
|
|
143
140
|
const {
|
|
@@ -156,14 +153,10 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
156
153
|
return renderNoCmd()
|
|
157
154
|
}
|
|
158
155
|
const keyword0 = keyword.toLowerCase()
|
|
159
|
-
const filtered =
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
nameMatch: keyword && d.name.toLowerCase().includes(keyword),
|
|
164
|
-
labelMatch: labels.some((label) => (d.labels || []).includes(label))
|
|
165
|
-
}
|
|
166
|
-
})
|
|
156
|
+
const filtered = filterArray(all, keyword0, labels)
|
|
157
|
+
const sorted = qmSortByFrequency
|
|
158
|
+
? sortBy(filtered, (obj) => -(obj.clickCount || 0))
|
|
159
|
+
: filtered
|
|
167
160
|
const sprops = {
|
|
168
161
|
value: labels,
|
|
169
162
|
mode: 'multiple',
|
|
@@ -174,11 +167,7 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
174
167
|
const tp = pinnedQuickCommandBar
|
|
175
168
|
? 'primary'
|
|
176
169
|
: 'text'
|
|
177
|
-
const cls = classNames(
|
|
178
|
-
'qm-list-wrap',
|
|
179
|
-
{ 'fil-label': !!labels.length },
|
|
180
|
-
{ 'fil-keyword': !!keyword }
|
|
181
|
-
)
|
|
170
|
+
const cls = classNames('qm-list-wrap')
|
|
182
171
|
const type = qmSortByFrequency ? 'primary' : 'default'
|
|
183
172
|
const w = openedSideBar ? 43 + leftSidebarWidth : 43
|
|
184
173
|
const qmProps = {
|
|
@@ -233,7 +222,7 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
233
222
|
</Space.Compact>
|
|
234
223
|
</Flex>
|
|
235
224
|
<div className={cls}>
|
|
236
|
-
{
|
|
225
|
+
{sorted.map(renderItem)}
|
|
237
226
|
</div>
|
|
238
227
|
</div>
|
|
239
228
|
</div>
|
|
@@ -39,7 +39,7 @@ export default auto(function SettingModalWrap (props) {
|
|
|
39
39
|
shouldConfirmDel: tabsShouldConfirmDel.includes(settingTab),
|
|
40
40
|
list: settingSidebarList
|
|
41
41
|
}
|
|
42
|
-
const { bookmarks, bookmarkGroups } = store
|
|
42
|
+
const { bookmarks, bookmarkGroups, widgetInstances } = store
|
|
43
43
|
const formProps = {
|
|
44
44
|
store,
|
|
45
45
|
formData: settingItem,
|
|
@@ -51,6 +51,7 @@ export default auto(function SettingModalWrap (props) {
|
|
|
51
51
|
]),
|
|
52
52
|
bookmarkGroups,
|
|
53
53
|
bookmarks,
|
|
54
|
+
widgetInstancesLength: widgetInstances.length,
|
|
54
55
|
serials: store.serials,
|
|
55
56
|
loaddingSerials: store.loaddingSerials
|
|
56
57
|
}
|
|
@@ -62,7 +62,7 @@ export default function SyncForm (props) {
|
|
|
62
62
|
const test = await window.store.testSyncToken(syncType, res.gistId)
|
|
63
63
|
if (!test) {
|
|
64
64
|
return notification.error({
|
|
65
|
-
|
|
65
|
+
title: 'token invalid'
|
|
66
66
|
})
|
|
67
67
|
}
|
|
68
68
|
if (!res.gistId && syncType !== syncTypes.custom && syncType !== syncTypes.cloud) {
|
|
@@ -5,7 +5,7 @@ import React, { useState } from 'react'
|
|
|
5
5
|
import WidgetForm from './widget-form'
|
|
6
6
|
import { showMsg } from './widget-notification-with-details'
|
|
7
7
|
|
|
8
|
-
export default function WidgetControl ({ formData }) {
|
|
8
|
+
export default function WidgetControl ({ formData, widgetInstancesLength }) {
|
|
9
9
|
const [loading, setLoading] = useState(false)
|
|
10
10
|
const widget = formData
|
|
11
11
|
if (!widget.id) {
|
|
@@ -16,6 +16,12 @@ export default function WidgetControl ({ formData }) {
|
|
|
16
16
|
)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Check if this widget already has a running instance
|
|
20
|
+
// widgetInstancesLength is used to trigger re-render when instances change
|
|
21
|
+
const hasRunningInstance = widgetInstancesLength > 0 && window.store.widgetInstances.some(
|
|
22
|
+
instance => instance.widgetId === widget.id
|
|
23
|
+
)
|
|
24
|
+
|
|
19
25
|
const handleFormSubmit = async (config) => {
|
|
20
26
|
setLoading(true)
|
|
21
27
|
try {
|
|
@@ -57,6 +63,7 @@ export default function WidgetControl ({ formData }) {
|
|
|
57
63
|
widget={widget}
|
|
58
64
|
onSubmit={handleFormSubmit}
|
|
59
65
|
loading={loading}
|
|
66
|
+
hasRunningInstance={hasRunningInstance}
|
|
60
67
|
/>
|
|
61
68
|
</div>
|
|
62
69
|
)
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Widget form component
|
|
3
3
|
*/
|
|
4
4
|
import React from 'react'
|
|
5
|
-
import { Form, Input, InputNumber, Switch, Select, Button } from 'antd'
|
|
5
|
+
import { Form, Input, InputNumber, Switch, Select, Button, Tooltip } from 'antd'
|
|
6
6
|
import { formItemLayout, tailFormItemLayout } from '../../common/form-layout'
|
|
7
7
|
|
|
8
|
-
export default function WidgetForm ({ widget, onSubmit, loading }) {
|
|
8
|
+
export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInstance }) {
|
|
9
9
|
const [form] = Form.useForm()
|
|
10
10
|
|
|
11
11
|
if (!widget) {
|
|
@@ -13,9 +13,10 @@ export default function WidgetForm ({ widget, onSubmit, loading }) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const { info } = widget
|
|
16
|
-
const { configs, type } = info
|
|
16
|
+
const { configs, type, singleInstance } = info
|
|
17
17
|
const isInstanceWidget = type === 'instance'
|
|
18
18
|
const txt = isInstanceWidget ? 'Start widget' : 'Run widget'
|
|
19
|
+
const isDisabled = loading || (singleInstance && hasRunningInstance)
|
|
19
20
|
|
|
20
21
|
const handleSubmit = async (values) => {
|
|
21
22
|
onSubmit(values)
|
|
@@ -95,14 +96,16 @@ export default function WidgetForm ({ widget, onSubmit, loading }) {
|
|
|
95
96
|
<Form.Item
|
|
96
97
|
{...tailFormItemLayout}
|
|
97
98
|
>
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
<Tooltip title={isDisabled && singleInstance && hasRunningInstance ? 'Already running, only one instance allowed' : ''}>
|
|
100
|
+
<Button
|
|
101
|
+
type='primary'
|
|
102
|
+
htmlType='submit'
|
|
103
|
+
loading={loading}
|
|
104
|
+
disabled={isDisabled}
|
|
105
|
+
>
|
|
106
|
+
{txt}
|
|
107
|
+
</Button>
|
|
108
|
+
</Tooltip>
|
|
106
109
|
</Form.Item>
|
|
107
110
|
</Form>
|
|
108
111
|
</div>
|
package/client/store/common.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import handleError from '../common/error-handler'
|
|
6
6
|
import { Modal } from 'antd'
|
|
7
|
-
import { debounce, some, get } from 'lodash-es'
|
|
7
|
+
import { debounce, some, get, pickBy } from 'lodash-es'
|
|
8
8
|
import {
|
|
9
9
|
modals,
|
|
10
10
|
leftSidebarWidthKey,
|
|
@@ -219,27 +219,31 @@ export default Store => {
|
|
|
219
219
|
delete p.name
|
|
220
220
|
delete p.id
|
|
221
221
|
if (type === connectionMap.rdp) {
|
|
222
|
+
const filtered = pickBy(p.rdp, (value) => value !== undefined && value !== '')
|
|
222
223
|
return {
|
|
223
224
|
...tab,
|
|
224
|
-
...
|
|
225
|
+
...filtered
|
|
225
226
|
}
|
|
226
227
|
} else if (type === connectionMap.vnc) {
|
|
228
|
+
const filtered = pickBy(p.vnc, (value) => value !== undefined && value !== '')
|
|
227
229
|
return {
|
|
228
230
|
...tab,
|
|
229
|
-
...
|
|
231
|
+
...filtered
|
|
230
232
|
}
|
|
231
233
|
} else if (type === connectionMap.telnet) {
|
|
234
|
+
const filtered = pickBy(p.telnet, (value) => value !== undefined && value !== '')
|
|
232
235
|
return {
|
|
233
236
|
...tab,
|
|
234
|
-
...
|
|
237
|
+
...filtered
|
|
235
238
|
}
|
|
236
239
|
}
|
|
237
240
|
delete p.rdp
|
|
238
241
|
delete p.vnc
|
|
239
242
|
delete p.telnet
|
|
243
|
+
const filtered = pickBy(p, (value) => value !== undefined && value !== '')
|
|
240
244
|
return {
|
|
241
245
|
...tab,
|
|
242
|
-
...
|
|
246
|
+
...filtered
|
|
243
247
|
}
|
|
244
248
|
}
|
|
245
249
|
Store.prototype.applyProfileToTabs = function (tab) {
|
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP (Model Context Protocol) handler for store
|
|
3
|
+
* Handles IPC requests from the MCP server widget
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import uid from '../common/uid'
|
|
7
|
+
import { settingMap } from '../common/constants'
|
|
8
|
+
|
|
9
|
+
export default Store => {
|
|
10
|
+
// Initialize MCP handler - called when MCP widget is started
|
|
11
|
+
Store.prototype.initMcpHandler = function () {
|
|
12
|
+
const { ipcOnEvent } = window.pre
|
|
13
|
+
// Listen for MCP requests from main process
|
|
14
|
+
ipcOnEvent('mcp-request', (event, request) => {
|
|
15
|
+
const { requestId, action, data } = request
|
|
16
|
+
if (action === 'tool-call') {
|
|
17
|
+
window.store.handleMcpToolCall(requestId, data.toolName, data.args)
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Handle individual tool calls
|
|
23
|
+
Store.prototype.handleMcpToolCall = async function (requestId, toolName, args) {
|
|
24
|
+
const { store } = window
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
let result
|
|
28
|
+
|
|
29
|
+
switch (toolName) {
|
|
30
|
+
// Bookmark operations
|
|
31
|
+
case 'list_bookmarks':
|
|
32
|
+
result = store.mcpListBookmarks(args)
|
|
33
|
+
break
|
|
34
|
+
case 'get_bookmark':
|
|
35
|
+
result = store.mcpGetBookmark(args)
|
|
36
|
+
break
|
|
37
|
+
case 'add_bookmark':
|
|
38
|
+
result = await store.mcpAddBookmark(args)
|
|
39
|
+
break
|
|
40
|
+
case 'edit_bookmark':
|
|
41
|
+
result = store.mcpEditBookmark(args)
|
|
42
|
+
break
|
|
43
|
+
case 'delete_bookmark':
|
|
44
|
+
result = store.mcpDeleteBookmark(args)
|
|
45
|
+
break
|
|
46
|
+
case 'open_bookmark':
|
|
47
|
+
result = store.mcpOpenBookmark(args)
|
|
48
|
+
break
|
|
49
|
+
|
|
50
|
+
// Bookmark group operations
|
|
51
|
+
case 'list_bookmark_groups':
|
|
52
|
+
result = store.mcpListBookmarkGroups()
|
|
53
|
+
break
|
|
54
|
+
case 'add_bookmark_group':
|
|
55
|
+
result = await store.mcpAddBookmarkGroup(args)
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
// Quick command operations
|
|
59
|
+
case 'list_quick_commands':
|
|
60
|
+
result = store.mcpListQuickCommands()
|
|
61
|
+
break
|
|
62
|
+
case 'add_quick_command':
|
|
63
|
+
result = store.mcpAddQuickCommand(args)
|
|
64
|
+
break
|
|
65
|
+
case 'run_quick_command':
|
|
66
|
+
result = store.mcpRunQuickCommand(args)
|
|
67
|
+
break
|
|
68
|
+
case 'delete_quick_command':
|
|
69
|
+
result = store.mcpDeleteQuickCommand(args)
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
// Tab operations
|
|
73
|
+
case 'list_tabs':
|
|
74
|
+
result = store.mcpListTabs()
|
|
75
|
+
break
|
|
76
|
+
case 'get_active_tab':
|
|
77
|
+
result = store.mcpGetActiveTab()
|
|
78
|
+
break
|
|
79
|
+
case 'switch_tab':
|
|
80
|
+
result = store.mcpSwitchTab(args)
|
|
81
|
+
break
|
|
82
|
+
case 'close_tab':
|
|
83
|
+
result = store.mcpCloseTab(args)
|
|
84
|
+
break
|
|
85
|
+
case 'reload_tab':
|
|
86
|
+
result = store.mcpReloadTab(args)
|
|
87
|
+
break
|
|
88
|
+
case 'duplicate_tab':
|
|
89
|
+
result = store.mcpDuplicateTab(args)
|
|
90
|
+
break
|
|
91
|
+
case 'open_local_terminal':
|
|
92
|
+
result = store.mcpOpenLocalTerminal()
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
// Terminal operations
|
|
96
|
+
case 'send_terminal_command':
|
|
97
|
+
result = store.mcpSendTerminalCommand(args)
|
|
98
|
+
break
|
|
99
|
+
case 'get_terminal_selection':
|
|
100
|
+
result = store.mcpGetTerminalSelection(args)
|
|
101
|
+
break
|
|
102
|
+
case 'get_terminal_output':
|
|
103
|
+
result = store.mcpGetTerminalOutput(args)
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
// History operations
|
|
107
|
+
case 'list_history':
|
|
108
|
+
result = store.mcpListHistory(args)
|
|
109
|
+
break
|
|
110
|
+
case 'clear_history':
|
|
111
|
+
result = store.mcpClearHistory()
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
// Transfer operations
|
|
115
|
+
case 'list_transfers':
|
|
116
|
+
result = store.mcpListTransfers()
|
|
117
|
+
break
|
|
118
|
+
case 'list_transfer_history':
|
|
119
|
+
result = store.mcpListTransferHistory(args)
|
|
120
|
+
break
|
|
121
|
+
|
|
122
|
+
// Settings operations
|
|
123
|
+
case 'get_settings':
|
|
124
|
+
result = store.mcpGetSettings()
|
|
125
|
+
break
|
|
126
|
+
case 'list_terminal_themes':
|
|
127
|
+
result = store.mcpListTerminalThemes()
|
|
128
|
+
break
|
|
129
|
+
case 'list_ui_themes':
|
|
130
|
+
result = store.mcpListUiThemes()
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
default:
|
|
134
|
+
throw new Error(`Unknown tool: ${toolName}`)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
window.api.sendMcpResponse({
|
|
138
|
+
requestId,
|
|
139
|
+
result
|
|
140
|
+
})
|
|
141
|
+
} catch (error) {
|
|
142
|
+
window.api.sendMcpResponse({
|
|
143
|
+
requestId,
|
|
144
|
+
error: error.message
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ==================== Bookmark APIs ====================
|
|
150
|
+
|
|
151
|
+
Store.prototype.mcpListBookmarks = function (args = {}) {
|
|
152
|
+
const { store } = window
|
|
153
|
+
let bookmarks = store.bookmarks
|
|
154
|
+
|
|
155
|
+
if (args.groupId) {
|
|
156
|
+
const group = store.bookmarkGroups.find(g => g.id === args.groupId)
|
|
157
|
+
if (group && group.bookmarkIds) {
|
|
158
|
+
const idSet = new Set(group.bookmarkIds)
|
|
159
|
+
bookmarks = bookmarks.filter(b => idSet.has(b.id))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return bookmarks.map(b => ({
|
|
164
|
+
id: b.id,
|
|
165
|
+
title: b.title,
|
|
166
|
+
host: b.host,
|
|
167
|
+
port: b.port,
|
|
168
|
+
username: b.username,
|
|
169
|
+
type: b.type || 'ssh',
|
|
170
|
+
color: b.color
|
|
171
|
+
}))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
Store.prototype.mcpGetBookmark = function (args) {
|
|
175
|
+
const { store } = window
|
|
176
|
+
const bookmark = store.bookmarks.find(b => b.id === args.id)
|
|
177
|
+
if (!bookmark) {
|
|
178
|
+
throw new Error(`Bookmark not found: ${args.id}`)
|
|
179
|
+
}
|
|
180
|
+
// Return bookmark without sensitive data
|
|
181
|
+
const { password, passphrase, privateKey, ...safeBookmark } = bookmark
|
|
182
|
+
return safeBookmark
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
Store.prototype.mcpAddBookmark = async function (args) {
|
|
186
|
+
const { store } = window
|
|
187
|
+
const bookmark = {
|
|
188
|
+
id: uid(),
|
|
189
|
+
title: args.title,
|
|
190
|
+
host: args.host || '',
|
|
191
|
+
port: args.port || 22,
|
|
192
|
+
username: args.username || '',
|
|
193
|
+
password: args.password || '',
|
|
194
|
+
type: args.type || 'local',
|
|
195
|
+
term: 'xterm-256color',
|
|
196
|
+
...args
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
store.addItem(bookmark, settingMap.bookmarks)
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
success: true,
|
|
203
|
+
id: bookmark.id,
|
|
204
|
+
message: `Bookmark "${bookmark.title}" created`
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
Store.prototype.mcpEditBookmark = function (args) {
|
|
209
|
+
const { store } = window
|
|
210
|
+
const { id, updates } = args
|
|
211
|
+
|
|
212
|
+
const bookmark = store.bookmarks.find(b => b.id === id)
|
|
213
|
+
if (!bookmark) {
|
|
214
|
+
throw new Error(`Bookmark not found: ${id}`)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
store.editItem(id, updates, settingMap.bookmarks)
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
success: true,
|
|
221
|
+
message: `Bookmark "${bookmark.title}" updated`
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
Store.prototype.mcpDeleteBookmark = function (args) {
|
|
226
|
+
const { store } = window
|
|
227
|
+
const bookmark = store.bookmarks.find(b => b.id === args.id)
|
|
228
|
+
if (!bookmark) {
|
|
229
|
+
throw new Error(`Bookmark not found: ${args.id}`)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
store.delItem({ id: args.id }, settingMap.bookmarks)
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
success: true,
|
|
236
|
+
message: `Bookmark "${bookmark.title}" deleted`
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
Store.prototype.mcpOpenBookmark = function (args) {
|
|
241
|
+
const { store } = window
|
|
242
|
+
const bookmark = store.bookmarks.find(b => b.id === args.id)
|
|
243
|
+
if (!bookmark) {
|
|
244
|
+
throw new Error(`Bookmark not found: ${args.id}`)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
store.onSelectBookmark(args.id)
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
success: true,
|
|
251
|
+
message: `Opened bookmark "${bookmark.title}"`
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ==================== Bookmark Group APIs ====================
|
|
256
|
+
|
|
257
|
+
Store.prototype.mcpListBookmarkGroups = function () {
|
|
258
|
+
const { store } = window
|
|
259
|
+
return store.bookmarkGroups.map(g => ({
|
|
260
|
+
id: g.id,
|
|
261
|
+
title: g.title,
|
|
262
|
+
level: g.level,
|
|
263
|
+
bookmarkCount: (g.bookmarkIds || []).length,
|
|
264
|
+
subgroupCount: (g.bookmarkGroupIds || []).length
|
|
265
|
+
}))
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
Store.prototype.mcpAddBookmarkGroup = async function (args) {
|
|
269
|
+
const { store } = window
|
|
270
|
+
const group = {
|
|
271
|
+
id: uid(),
|
|
272
|
+
title: args.title,
|
|
273
|
+
bookmarkIds: [],
|
|
274
|
+
bookmarkGroupIds: [],
|
|
275
|
+
level: args.parentId ? 2 : 1
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await store.addBookmarkGroup(group)
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
success: true,
|
|
282
|
+
id: group.id,
|
|
283
|
+
message: `Bookmark group "${group.title}" created`
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ==================== Quick Command APIs ====================
|
|
288
|
+
|
|
289
|
+
Store.prototype.mcpListQuickCommands = function () {
|
|
290
|
+
const { store } = window
|
|
291
|
+
return store.quickCommands.map(q => ({
|
|
292
|
+
id: q.id,
|
|
293
|
+
name: q.name,
|
|
294
|
+
command: q.command,
|
|
295
|
+
commands: q.commands,
|
|
296
|
+
inputOnly: q.inputOnly,
|
|
297
|
+
labels: q.labels
|
|
298
|
+
}))
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
Store.prototype.mcpAddQuickCommand = function (args) {
|
|
302
|
+
const { store } = window
|
|
303
|
+
const qm = {
|
|
304
|
+
id: uid(),
|
|
305
|
+
name: args.name,
|
|
306
|
+
command: args.command,
|
|
307
|
+
inputOnly: args.inputOnly || false,
|
|
308
|
+
labels: args.labels || []
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
store.addQuickCommand(qm)
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
success: true,
|
|
315
|
+
id: qm.id,
|
|
316
|
+
message: `Quick command "${qm.name}" created`
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
Store.prototype.mcpRunQuickCommand = function (args) {
|
|
321
|
+
const { store } = window
|
|
322
|
+
const qm = store.quickCommands.find(q => q.id === args.id)
|
|
323
|
+
if (!qm) {
|
|
324
|
+
throw new Error(`Quick command not found: ${args.id}`)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
store.runQuickCommandItem(args.id)
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
success: true,
|
|
331
|
+
message: `Executed quick command "${qm.name}"`
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
Store.prototype.mcpDeleteQuickCommand = function (args) {
|
|
336
|
+
const { store } = window
|
|
337
|
+
const qm = store.quickCommands.find(q => q.id === args.id)
|
|
338
|
+
if (!qm) {
|
|
339
|
+
throw new Error(`Quick command not found: ${args.id}`)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
store.delQuickCommand({ id: args.id })
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
success: true,
|
|
346
|
+
message: `Deleted quick command "${qm.name}"`
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ==================== Tab APIs ====================
|
|
351
|
+
|
|
352
|
+
Store.prototype.mcpListTabs = function () {
|
|
353
|
+
const { store } = window
|
|
354
|
+
return store.tabs.map(t => ({
|
|
355
|
+
id: t.id,
|
|
356
|
+
title: t.title,
|
|
357
|
+
host: t.host,
|
|
358
|
+
type: t.type || 'local',
|
|
359
|
+
status: t.status,
|
|
360
|
+
isTransporting: t.isTransporting,
|
|
361
|
+
batch: t.batch
|
|
362
|
+
}))
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
Store.prototype.mcpGetActiveTab = function () {
|
|
366
|
+
const { store } = window
|
|
367
|
+
const tab = store.currentTab
|
|
368
|
+
if (!tab) {
|
|
369
|
+
return { activeTabId: null, tab: null }
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
activeTabId: store.activeTabId,
|
|
373
|
+
tab: {
|
|
374
|
+
id: tab.id,
|
|
375
|
+
title: tab.title,
|
|
376
|
+
host: tab.host,
|
|
377
|
+
type: tab.type || 'local',
|
|
378
|
+
status: tab.status
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
Store.prototype.mcpSwitchTab = function (args) {
|
|
384
|
+
const { store } = window
|
|
385
|
+
const tab = store.tabs.find(t => t.id === args.tabId)
|
|
386
|
+
if (!tab) {
|
|
387
|
+
throw new Error(`Tab not found: ${args.tabId}`)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
store.activeTabId = args.tabId
|
|
391
|
+
if (tab.batch !== undefined) {
|
|
392
|
+
store[`activeTabId${tab.batch}`] = args.tabId
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
success: true,
|
|
397
|
+
message: `Switched to tab "${tab.title}"`
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
Store.prototype.mcpCloseTab = function (args) {
|
|
402
|
+
const { store } = window
|
|
403
|
+
const tab = store.tabs.find(t => t.id === args.tabId)
|
|
404
|
+
if (!tab) {
|
|
405
|
+
throw new Error(`Tab not found: ${args.tabId}`)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
store.delTab(args.tabId)
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
success: true,
|
|
412
|
+
message: `Closed tab "${tab.title}"`
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
Store.prototype.mcpReloadTab = function (args) {
|
|
417
|
+
const { store } = window
|
|
418
|
+
const tabId = args.tabId || store.activeTabId
|
|
419
|
+
const tab = store.tabs.find(t => t.id === tabId)
|
|
420
|
+
if (!tab) {
|
|
421
|
+
throw new Error(`Tab not found: ${tabId}`)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
store.reloadTab(tabId)
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
success: true,
|
|
428
|
+
message: `Reloaded tab "${tab.title}"`
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
Store.prototype.mcpDuplicateTab = function (args) {
|
|
433
|
+
const { store } = window
|
|
434
|
+
const tab = store.tabs.find(t => t.id === args.tabId)
|
|
435
|
+
if (!tab) {
|
|
436
|
+
throw new Error(`Tab not found: ${args.tabId}`)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
store.duplicateTab(args.tabId)
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
success: true,
|
|
443
|
+
message: `Duplicated tab "${tab.title}"`
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
Store.prototype.mcpOpenLocalTerminal = function () {
|
|
448
|
+
const { store } = window
|
|
449
|
+
store.addTab()
|
|
450
|
+
const newTabId = store.activeTabId
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
success: true,
|
|
454
|
+
tabId: newTabId,
|
|
455
|
+
message: 'Opened new local terminal'
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ==================== Terminal APIs ====================
|
|
460
|
+
|
|
461
|
+
Store.prototype.mcpSendTerminalCommand = function (args) {
|
|
462
|
+
const { store } = window
|
|
463
|
+
const tabId = args.tabId || store.activeTabId
|
|
464
|
+
const command = args.command
|
|
465
|
+
|
|
466
|
+
if (!tabId) {
|
|
467
|
+
throw new Error('No active terminal')
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (command === undefined || command === null) {
|
|
471
|
+
throw new Error('No command provided')
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
store.runQuickCommand(command, args.inputOnly || false)
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
success: true,
|
|
478
|
+
message: 'Command sent to terminal'
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
Store.prototype.mcpGetTerminalSelection = function (args) {
|
|
483
|
+
const { store } = window
|
|
484
|
+
const { refs } = require('../components/common/ref')
|
|
485
|
+
const tabId = args.tabId || store.activeTabId
|
|
486
|
+
|
|
487
|
+
if (!tabId) {
|
|
488
|
+
throw new Error('No active terminal')
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const term = refs.get('term-' + tabId)
|
|
492
|
+
if (!term || !term.term) {
|
|
493
|
+
throw new Error('Terminal not found')
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const selection = term.term.getSelection()
|
|
497
|
+
|
|
498
|
+
return {
|
|
499
|
+
selection: selection || '',
|
|
500
|
+
tabId
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
Store.prototype.mcpGetTerminalOutput = function (args) {
|
|
505
|
+
const { store } = window
|
|
506
|
+
const { refs } = require('../components/common/ref')
|
|
507
|
+
const tabId = args.tabId || store.activeTabId
|
|
508
|
+
const lineCount = args.lines || 50
|
|
509
|
+
|
|
510
|
+
if (!tabId) {
|
|
511
|
+
throw new Error('No active terminal')
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const term = refs.get('term-' + tabId)
|
|
515
|
+
if (!term || !term.term) {
|
|
516
|
+
throw new Error('Terminal not found')
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const buffer = term.term.buffer.active
|
|
520
|
+
if (!buffer) {
|
|
521
|
+
throw new Error('Terminal buffer not available')
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const cursorY = buffer.cursorY || 0
|
|
525
|
+
const baseY = buffer.baseY || 0
|
|
526
|
+
const totalLines = buffer.length || 0
|
|
527
|
+
|
|
528
|
+
// Calculate the actual content range
|
|
529
|
+
// baseY is the scroll offset, cursorY is cursor position in viewport
|
|
530
|
+
const actualContentEnd = baseY + cursorY + 1
|
|
531
|
+
const startLine = Math.max(0, actualContentEnd - lineCount)
|
|
532
|
+
const endLine = Math.min(totalLines, actualContentEnd)
|
|
533
|
+
const lines = []
|
|
534
|
+
|
|
535
|
+
for (let i = startLine; i < endLine; i++) {
|
|
536
|
+
const line = buffer.getLine(i)
|
|
537
|
+
if (line) {
|
|
538
|
+
const text = line.translateToString(true)
|
|
539
|
+
lines.push(text)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return {
|
|
544
|
+
output: lines.join('\n'),
|
|
545
|
+
lineCount: lines.length,
|
|
546
|
+
cursorY,
|
|
547
|
+
baseY,
|
|
548
|
+
tabId
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ==================== History APIs ====================
|
|
553
|
+
|
|
554
|
+
Store.prototype.mcpListHistory = function (args = {}) {
|
|
555
|
+
const { store } = window
|
|
556
|
+
const limit = args.limit || 50
|
|
557
|
+
const history = store.history.slice(0, limit)
|
|
558
|
+
|
|
559
|
+
return history.map(h => ({
|
|
560
|
+
id: h.id,
|
|
561
|
+
title: h.title,
|
|
562
|
+
host: h.host,
|
|
563
|
+
type: h.type,
|
|
564
|
+
time: h.time
|
|
565
|
+
}))
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
Store.prototype.mcpClearHistory = function () {
|
|
569
|
+
const { store } = window
|
|
570
|
+
store.history = []
|
|
571
|
+
|
|
572
|
+
return {
|
|
573
|
+
success: true,
|
|
574
|
+
message: 'History cleared'
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ==================== Transfer APIs ====================
|
|
579
|
+
|
|
580
|
+
Store.prototype.mcpListTransfers = function () {
|
|
581
|
+
const { store } = window
|
|
582
|
+
return store.fileTransfers.map(t => ({
|
|
583
|
+
id: t.id,
|
|
584
|
+
localPath: t.localPath,
|
|
585
|
+
remotePath: t.remotePath,
|
|
586
|
+
type: t.type,
|
|
587
|
+
percent: t.percent,
|
|
588
|
+
status: t.status
|
|
589
|
+
}))
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
Store.prototype.mcpListTransferHistory = function (args = {}) {
|
|
593
|
+
const { store } = window
|
|
594
|
+
const limit = args.limit || 50
|
|
595
|
+
return store.transferHistory.slice(0, limit).map(t => ({
|
|
596
|
+
id: t.id,
|
|
597
|
+
localPath: t.localPath,
|
|
598
|
+
remotePath: t.remotePath,
|
|
599
|
+
type: t.type,
|
|
600
|
+
status: t.status,
|
|
601
|
+
time: t.time
|
|
602
|
+
}))
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// ==================== Settings APIs ====================
|
|
606
|
+
|
|
607
|
+
Store.prototype.mcpGetSettings = function () {
|
|
608
|
+
const { store } = window
|
|
609
|
+
// Return safe settings (no sensitive data)
|
|
610
|
+
const config = store.config
|
|
611
|
+
const safeConfig = {
|
|
612
|
+
theme: config.theme,
|
|
613
|
+
language: config.language,
|
|
614
|
+
fontSize: config.fontSize,
|
|
615
|
+
fontFamily: config.fontFamily,
|
|
616
|
+
terminalType: config.terminalType,
|
|
617
|
+
cursorStyle: config.cursorStyle,
|
|
618
|
+
cursorBlink: config.cursorBlink,
|
|
619
|
+
scrollback: config.scrollback
|
|
620
|
+
}
|
|
621
|
+
return safeConfig
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
Store.prototype.mcpListTerminalThemes = function () {
|
|
625
|
+
const { store } = window
|
|
626
|
+
return store.terminalThemes.map(t => ({
|
|
627
|
+
id: t.id,
|
|
628
|
+
name: t.name,
|
|
629
|
+
themeLight: t.themeLight
|
|
630
|
+
}))
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
Store.prototype.mcpListUiThemes = function () {
|
|
634
|
+
const { store } = window
|
|
635
|
+
return (store.uiThemes || []).map(t => ({
|
|
636
|
+
id: t.id,
|
|
637
|
+
name: t.name
|
|
638
|
+
}))
|
|
639
|
+
}
|
|
640
|
+
}
|
package/client/store/store.js
CHANGED
|
@@ -25,6 +25,7 @@ import batchInputHistory from './batch-input-history'
|
|
|
25
25
|
import transferExtend from './transfer-list'
|
|
26
26
|
import addressBookmarkExtend from './address-bookmark'
|
|
27
27
|
import widgetsExtend from './widgets'
|
|
28
|
+
import mcpHandlerExtend from './mcp-handler'
|
|
28
29
|
import workspaceExtend from './workspace'
|
|
29
30
|
import isColorDark from '../common/is-color-dark'
|
|
30
31
|
import { getReverseColor } from '../common/reverse-color'
|
|
@@ -298,6 +299,7 @@ batchInputHistory(Store)
|
|
|
298
299
|
transferExtend(Store)
|
|
299
300
|
addressBookmarkExtend(Store)
|
|
300
301
|
widgetsExtend(Store)
|
|
302
|
+
mcpHandlerExtend(Store)
|
|
301
303
|
workspaceExtend(Store)
|
|
302
304
|
|
|
303
305
|
export const StateStore = Store
|
package/client/store/widgets.js
CHANGED
|
@@ -16,6 +16,10 @@ export default Store => {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
Store.prototype.runWidget = async (widgetId, config) => {
|
|
19
|
+
// If this is MCP server widget, initialize MCP handler first
|
|
20
|
+
if (widgetId === 'mcp-server') {
|
|
21
|
+
window.store.initMcpHandler()
|
|
22
|
+
}
|
|
19
23
|
return window.pre.runGlobalAsync('runWidget', widgetId, config)
|
|
20
24
|
}
|
|
21
25
|
|