dsh-remote 0.3.1 → 0.3.2
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/lib/client.js +238 -248
- package/package.json +5 -2
package/lib/client.js
CHANGED
|
@@ -1,249 +1,239 @@
|
|
|
1
|
-
// dsh-remote — client half.
|
|
2
|
-
// Settings →
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
React.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
username: form.username.trim() || 'root',
|
|
59
|
-
password: form.password || undefined,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
),
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
.finally(() => setLoading(false))
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
React.createElement('div', { style: {
|
|
198
|
-
|
|
199
|
-
'
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
React.createElement(
|
|
212
|
-
'
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
yield slots.register({ name: 'conversation.hero.workspace.directoryFlow', id: 'dsh-remote', priority: -100 }, RemoteDirectoryFlow)
|
|
240
|
-
yield slots.register({ name: 'sidebar.workspaces.directoryFlow', id: 'dsh-remote', priority: -100 }, RemoteDirectoryFlow)
|
|
241
|
-
}),
|
|
242
|
-
)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
exports.name = name
|
|
246
|
-
exports.apply = apply
|
|
247
|
-
return module.exports
|
|
248
|
-
},
|
|
1
|
+
// dsh-remote — client half.
|
|
2
|
+
// Settings → 远程工作区 + a unified workspace directory picker that fills
|
|
3
|
+
// ui-workspace's two directory-flow holes (sidebar + conversation hero).
|
|
4
|
+
//
|
|
5
|
+
// The picker has two tabs, styled to match the harness:
|
|
6
|
+
// • 本机 — drives the SAME local Host backend as the official picker
|
|
7
|
+
// (ctx.workspaces.listDirectory), so local dirs still work.
|
|
8
|
+
// • 远程 — browses the remote over /dsh-remote/ls (SFTP); on confirm it asks
|
|
9
|
+
// the host to build a real LOCAL mirror (/dsh-remote/mirror) then
|
|
10
|
+
// onPicked(localMirror) so the DSH workspaceRegistry adopts the local dir.
|
|
11
|
+
//
|
|
12
|
+
// Client entries must be classic scripts registered via window.__ModuleLoader__.load
|
|
13
|
+
// ({ id, factory }); the factory receives a synchronous `require`.
|
|
14
|
+
window.__ModuleLoader__.load({
|
|
15
|
+
id: 'dsh-remote',
|
|
16
|
+
factory: (require) => {
|
|
17
|
+
var module = { exports: {} }
|
|
18
|
+
var exports = module.exports
|
|
19
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
20
|
+
|
|
21
|
+
const React = require('react')
|
|
22
|
+
const name = 'dsh-remote'
|
|
23
|
+
|
|
24
|
+
async function api(method, path, body) {
|
|
25
|
+
const opts = { method, headers: {} }
|
|
26
|
+
if (body) {
|
|
27
|
+
opts.headers['Content-Type'] = 'application/json'
|
|
28
|
+
opts.body = JSON.stringify(body)
|
|
29
|
+
}
|
|
30
|
+
const res = await fetch(path, opts)
|
|
31
|
+
const data = await res.json().catch(() => ({}))
|
|
32
|
+
if (!res.ok) throw new Error((data && (data.error || data.message)) || 'HTTP ' + res.status)
|
|
33
|
+
return data
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const T = { bg: 'rgba(128,128,128,0.07)', border: 'rgba(128,128,128,0.35)', danger: '#e06c75', ok: '#4caf7d', radius: 8 }
|
|
37
|
+
|
|
38
|
+
const inputS = { flex: 1, padding: '6px 10px', borderRadius: T.radius, border: '1px solid ' + T.border, background: T.bg }
|
|
39
|
+
const buttonS = { padding: '6px 12px', borderRadius: T.radius, border: '1px solid ' + T.border, background: T.bg, cursor: 'pointer' }
|
|
40
|
+
|
|
41
|
+
// ── Settings → 远程工作区 ────────────────────────────────────────────────
|
|
42
|
+
function RemoteWorkspacePage() {
|
|
43
|
+
const [status, setStatus] = React.useState(null)
|
|
44
|
+
const [form, setForm] = React.useState({ host: '', port: '22', username: '', password: '', privateKeyPath: '' })
|
|
45
|
+
const [busy, setBusy] = React.useState(false)
|
|
46
|
+
const [browse, setBrowse] = React.useState({ path: '', out: '' })
|
|
47
|
+
const [error, setError] = React.useState('')
|
|
48
|
+
const [okMsg, setOkMsg] = React.useState('')
|
|
49
|
+
|
|
50
|
+
const loadStatus = () => api('GET', '/dsh-remote/status').then(setStatus).catch((e) => setError(String((e && e.message) || e)))
|
|
51
|
+
React.useEffect(() => { loadStatus() }, [])
|
|
52
|
+
|
|
53
|
+
const setField = (k) => (ev) => setForm({ ...form, [k]: ev.target.value })
|
|
54
|
+
|
|
55
|
+
const connect = () => {
|
|
56
|
+
setBusy(true); setError(''); setOkMsg('')
|
|
57
|
+
api('POST', '/dsh-remote/connect', {
|
|
58
|
+
host: form.host.trim(), port: Number(form.port) || 22, username: form.username.trim() || 'root',
|
|
59
|
+
password: form.password || undefined, privateKeyPath: form.privateKeyPath.trim() || undefined,
|
|
60
|
+
})
|
|
61
|
+
.then((res) => { setStatus(res); if (res && res.workspace) setBrowse({ path: res.workspace, out: '' }); setOkMsg(res.ok ? '已连接 ✓ 可浏览远程目录。' : '') })
|
|
62
|
+
.catch((e) => setError(String((e && e.message) || e)))
|
|
63
|
+
.finally(() => setBusy(false))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const doBrowse = (p) => {
|
|
67
|
+
const target = p !== undefined ? p : browse.path.trim()
|
|
68
|
+
setBusy(true); setError('')
|
|
69
|
+
api('GET', '/dsh-remote/ls?path=' + encodeURIComponent(target || ''))
|
|
70
|
+
.then((res) => setBrowse({ path: target, out: res.text }))
|
|
71
|
+
.catch((e) => setError(String((e && e.message) || e)))
|
|
72
|
+
.finally(() => setBusy(false))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const setWorkspace = () => {
|
|
76
|
+
const p = browse.path.trim(); if (!p) return
|
|
77
|
+
setBusy(true); setError(''); setOkMsg('')
|
|
78
|
+
api('POST', '/dsh-remote/workspace', { path: p })
|
|
79
|
+
.then((res) => { setStatus(res); setOkMsg('远程工作区已设为 ' + p) })
|
|
80
|
+
.catch((e) => setError(String((e && e.message) || e)))
|
|
81
|
+
.finally(() => setBusy(false))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const row = (t, v, k) => React.createElement(
|
|
85
|
+
'div', { key: k },
|
|
86
|
+
React.createElement('label', { style: { fontSize: 12, opacity: 0.75 } }, t + ': '), v,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return React.createElement(
|
|
90
|
+
'div',
|
|
91
|
+
{ style: { padding: 16, display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 720 } },
|
|
92
|
+
React.createElement('div', { style: { fontWeight: 600, fontSize: 15 } }, '远程工作区(dsh-remote)'),
|
|
93
|
+
React.createElement('div', { style: { fontSize: 12, opacity: 0.8 } },
|
|
94
|
+
'连接远程主机后即可用 rw_* 工具操作;选中的远程目录会镜像成一个本地工作区(可与本地工作区并存)。'),
|
|
95
|
+
status
|
|
96
|
+
? React.createElement('div', { style: { display: 'flex', gap: 14, flexWrap: 'wrap', fontSize: 13 } },
|
|
97
|
+
React.createElement('span', null, '远程: ', React.createElement('code', null, (status.username || '?') + '@' + (status.host || '?'))),
|
|
98
|
+
React.createElement('span', null, '连接: ', React.createElement('code', null, status.connected ? '是' : '否')),
|
|
99
|
+
React.createElement('span', null, '工作区: ', React.createElement('code', null, status.workspace || '(未设置)')),
|
|
100
|
+
status.localMirror ? React.createElement('span', null, '本地镜像: ', React.createElement('code', null, status.localMirror)) : null,
|
|
101
|
+
)
|
|
102
|
+
: null,
|
|
103
|
+
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 } },
|
|
104
|
+
row('主机', React.createElement('input', { value: form.host, onChange: setField('host'), placeholder: 'IP 或 hostname', style: inputS }), 'h'),
|
|
105
|
+
row('端口', React.createElement('input', { value: form.port, onChange: setField('port'), placeholder: '22', style: { ...inputS, width: 70 } }), 'p'),
|
|
106
|
+
row('用户', React.createElement('input', { value: form.username, onChange: setField('username'), placeholder: 'root', style: inputS }), 'u'),
|
|
107
|
+
row('私钥路径', React.createElement('input', { value: form.privateKeyPath, onChange: setField('privateKeyPath'), placeholder: '如 C:/Users/me/.ssh/id_rsa', style: inputS }), 'k'),
|
|
108
|
+
row('密码', React.createElement('input', { type: 'password', value: form.password, onChange: setField('password'), placeholder: 'SSH 无 key 时用', style: inputS }), 'w'),
|
|
109
|
+
),
|
|
110
|
+
React.createElement('button', { onClick: connect, disabled: busy, style: { ...buttonS, alignSelf: 'flex-start' } }, busy ? '连接中…' : '连接远程'),
|
|
111
|
+
React.createElement('div', { style: { display: 'flex', gap: 8 } },
|
|
112
|
+
React.createElement('input', { value: browse.path, onChange: (e) => setBrowse({ ...browse, path: e.target.value }), placeholder: '远程路径,如 /home/dev/project', style: inputS }),
|
|
113
|
+
React.createElement('button', { onClick: () => doBrowse(), disabled: busy, style: buttonS }, '列目录'),
|
|
114
|
+
React.createElement('button', { onClick: setWorkspace, disabled: busy || !browse.path.trim(), style: buttonS }, '设为远程工作区'),
|
|
115
|
+
),
|
|
116
|
+
error ? React.createElement('div', { style: { color: T.danger, whiteSpace: 'pre-wrap', fontSize: 13 } }, error) : null,
|
|
117
|
+
okMsg ? React.createElement('div', { style: { color: T.ok, fontSize: 13 } }, okMsg) : null,
|
|
118
|
+
browse.out
|
|
119
|
+
? React.createElement('pre', { style: { fontSize: 12, background: T.bg, padding: 10, borderRadius: T.radius, maxHeight: 320, overflow: 'auto', whiteSpace: 'pre-wrap' } }, browse.out)
|
|
120
|
+
: null,
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Unified workspace directory picker (fills the directory-flow holes) ──
|
|
125
|
+
let LOCAL_WORKSPACES = null // ctx.workspaces, bound in apply()
|
|
126
|
+
|
|
127
|
+
function parseLs(text) {
|
|
128
|
+
const out = []
|
|
129
|
+
for (const ln of String(text || '').split('\n')) {
|
|
130
|
+
const m = ln.match(/^(d[-r][rwx-]{8})\s+[^ ]+\s+[^ ]+\s+[^ ]+\s+[^ ]+\s+\d{4,}-\d{2}-\d{2}([T ]+\S+)?\s+(.+)$/)
|
|
131
|
+
if (!m) continue
|
|
132
|
+
out.push((m[1].charAt(0) === 'd' ? '📁' : '📄') + m[3])
|
|
133
|
+
}
|
|
134
|
+
if (!out.length && String(text || '').trim()) out.push(...String(text).trim().split('\n').slice(1).map((l) => '📄 ' + l.trim()).filter(Boolean))
|
|
135
|
+
return out
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function DirPicker(props) {
|
|
139
|
+
const { open, busy, onPicked, onCancel } = props
|
|
140
|
+
const [tab, setTab] = React.useState('local')
|
|
141
|
+
const [path, setPath] = React.useState('')
|
|
142
|
+
const [items, setItems] = React.useState(null)
|
|
143
|
+
const [err, setErr] = React.useState('')
|
|
144
|
+
const [loading, setLoading] = React.useState(false)
|
|
145
|
+
|
|
146
|
+
const load = (target, useLocal) => {
|
|
147
|
+
const p = target !== undefined ? target : path
|
|
148
|
+
setLoading(true); setErr('')
|
|
149
|
+
if (useLocal) {
|
|
150
|
+
const w = LOCAL_WORKSPACES || {}
|
|
151
|
+
const list = w.listDirectory || (() => Promise.reject(new Error('本机目录服务不可用')))
|
|
152
|
+
Promise.resolve(list(p || undefined)).then((lv) => {
|
|
153
|
+
const names = (lv && lv.entries) || (lv && lv.children) || (lv && lv.list) || []
|
|
154
|
+
setPath(p || (lv && lv.path) || '')
|
|
155
|
+
setItems((Array.isArray(names) ? names : []).map((e) => (e && e.name ? e.name : String(e))))
|
|
156
|
+
}).catch((e) => setErr(String((e && e.message) || e))).finally(() => setLoading(false))
|
|
157
|
+
} else {
|
|
158
|
+
api('GET', '/dsh-remote/ls?path=' + encodeURIComponent(p || '')).then((res) => {
|
|
159
|
+
setPath(res && res.path ? res.path : p)
|
|
160
|
+
setItems(parseLs(res && res.text))
|
|
161
|
+
}).catch((e) => setErr(String((e && e.message) || e))).finally(() => setLoading(false))
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
React.useEffect(() => { if (open) load('', tab === 'local') }, [open])
|
|
166
|
+
|
|
167
|
+
const switchTab = (t) => { setTab(t); setPath(''); setItems(null); setErr(''); load('', t === 'local') }
|
|
168
|
+
const goUp = () => {
|
|
169
|
+
const sep = tab === 'local' ? '\\' : '/'
|
|
170
|
+
const idx = String(path).replace(/[\\/]+$/, '').lastIndexOf(sep)
|
|
171
|
+
const parent = idx <= 0 ? (tab === 'local' ? (path.slice(0, 3) || 'C:\\') : '/') : String(path).slice(0, idx)
|
|
172
|
+
load(parent, tab === 'local')
|
|
173
|
+
}
|
|
174
|
+
const enter = (name) => {
|
|
175
|
+
const sep = tab === 'local' ? '\\' : '/'
|
|
176
|
+
const np = path === '' || path.endsWith(sep) ? path + name : path + sep + name
|
|
177
|
+
load(np, tab === 'local')
|
|
178
|
+
}
|
|
179
|
+
const pickCurrent = () => {
|
|
180
|
+
if (loading || busy) return
|
|
181
|
+
if (tab === 'local') { onPicked(path); return }
|
|
182
|
+
api('POST', '/dsh-remote/mirror', { path })
|
|
183
|
+
.then((res) => (res && res.localMirror ? onPicked(res.localMirror) : setErr((res && res.error) || '')))
|
|
184
|
+
.catch((e) => setErr(String((e && e.message) || e)))
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!open) return null
|
|
188
|
+
const isDir = (label) => String(label || '').charAt(0) === '📁'
|
|
189
|
+
const cell = { ...buttonS, textAlign: 'center' }
|
|
190
|
+
const active = { ...cell, fontWeight: 700 }
|
|
191
|
+
return React.createElement(
|
|
192
|
+
'div', { style: { padding: 14, minWidth: 440, maxWidth: 680 } },
|
|
193
|
+
React.createElement('div', { style: { display: 'flex', gap: 6, marginBottom: 8 } },
|
|
194
|
+
React.createElement('button', { onClick: () => switchTab('local'), style: tab === 'local' ? active : cell }, '本机'),
|
|
195
|
+
React.createElement('button', { onClick: () => switchTab('remote'), style: tab === 'remote' ? active : cell }, '远程'),
|
|
196
|
+
),
|
|
197
|
+
React.createElement('div', { style: { display: 'flex', gap: 6, marginBottom: 8 } },
|
|
198
|
+
React.createElement('input', { value: path, onChange: (e) => setPath(e.target.value), placeholder: tab === 'local' ? '本机目录路径' : '远程路径,如 /home/dev/project', style: inputS }),
|
|
199
|
+
React.createElement('button', { onClick: () => load(path, tab === 'local'), style: buttonS }, '载入'),
|
|
200
|
+
React.createElement('button', { onClick: goUp, style: buttonS }, '上一级'),
|
|
201
|
+
),
|
|
202
|
+
err ? React.createElement('div', { style: { color: T.danger, marginBottom: 6, fontSize: 12 } }, err) : null,
|
|
203
|
+
React.createElement(
|
|
204
|
+
'div', { style: { border: '1px solid ' + T.border, borderRadius: T.radius, padding: 8, maxHeight: 280, overflow: 'auto', fontSize: 13 } },
|
|
205
|
+
loading
|
|
206
|
+
? '加载中…'
|
|
207
|
+
: (items && items.length
|
|
208
|
+
? items.slice(0, 400).map((n, i) => React.createElement('div', { key: i, onClick: () => (isDir(n) ? enter(n.slice(2)) : null), style: { padding: '3px 6px', borderRadius: 4, cursor: isDir(n) ? 'pointer' : 'default', color: isDir(n) ? T.ok : 'inherit' } }, isDir(n) ? n : ' ' + n))
|
|
209
|
+
: React.createElement('div', { style: { opacity: 0.6 } }, items === null ? '(输入路径后点「载入」)' : '(空)')),
|
|
210
|
+
),
|
|
211
|
+
React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 8, justifyContent: 'flex-end' } },
|
|
212
|
+
React.createElement('button', { onClick: onCancel, style: { background: 'transparent' } }, '取消'),
|
|
213
|
+
React.createElement('button', { onClick: pickCurrent, disabled: loading || busy || !path.trim(), style: buttonS }, busy ? '处理中…' : tab === 'local' ? '选用此目录' : '设为远程工作区'),
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function apply(ctx) {
|
|
219
|
+
const slots = ctx.get('slots')
|
|
220
|
+
if (slots === undefined) return
|
|
221
|
+
LOCAL_WORKSPACES = (ctx && ctx.workspaces) || null
|
|
222
|
+
slots.inject('settings.section', () =>
|
|
223
|
+
slots.register({ name: 'settings.section', id: 'dsh-remote', priority: 40, label: () => '远程工作区' }, () => React.createElement(RemoteWorkspacePage, null)),
|
|
224
|
+
)
|
|
225
|
+
slots.inject(
|
|
226
|
+
'conversation.hero.workspace.directoryFlow',
|
|
227
|
+
() =>
|
|
228
|
+
slots.inject('sidebar.workspaces.directoryFlow', function* () {
|
|
229
|
+
yield slots.register({ name: 'conversation.hero.workspace.directoryFlow', id: 'dsh-remote', priority: -100 }, DirPicker)
|
|
230
|
+
yield slots.register({ name: 'sidebar.workspaces.directoryFlow', id: 'dsh-remote', priority: -100 }, DirPicker)
|
|
231
|
+
}),
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
exports.name = name
|
|
236
|
+
exports.apply = apply
|
|
237
|
+
return module.exports
|
|
238
|
+
},
|
|
249
239
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Remote-work assistant for DeepSeek Harness: connect SSH (password or key), pick a remote workspace, operate on it with rw_pick_workspace / rw_list_dir / rw_read_file / rw_exec / rw_sync tools, and mirror it to a real local directory (SFTP) so the DSH native workspace can adopt it.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"client": {
|
|
32
32
|
"inject": [
|
|
33
|
-
"@deepseek-ai/dsh-client-runtime"
|
|
33
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-workspace"
|
|
34
35
|
],
|
|
35
36
|
"platform": "web"
|
|
36
37
|
}
|
|
@@ -40,6 +41,8 @@
|
|
|
40
41
|
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
|
|
41
42
|
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
42
43
|
"@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
|
|
44
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
45
|
+
"@deepseek-ai/dsh-client-ui-workspace": "^0.1.0-rc.6",
|
|
43
46
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
44
47
|
},
|
|
45
48
|
"dependencies": {
|