dsh-remote 0.8.1 → 0.8.3

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 CHANGED
@@ -106,6 +106,7 @@ window.__ModuleLoader__.load({
106
106
  const [auditLines, setAuditLines] = React.useState([])
107
107
  const [sshEntries, setSshEntries] = React.useState(null)
108
108
  const [workspaces, setWorkspaces] = React.useState([])
109
+ const [advOpen, setAdvOpen] = React.useState(false) // 高级配置折叠
109
110
 
110
111
  const testConn = () => {
111
112
  if (!form.host.trim() || tst === 'testing') return
@@ -171,15 +172,21 @@ window.__ModuleLoader__.load({
171
172
 
172
173
  const setF = (k) => (ev) => setForm({ ...form, [k]: ev.target.value })
173
174
  const setFb = (k) => (ev) => setForm({ ...form, [k]: ev.target.checked })
174
- const startEdit = (m) => setForm({
175
- name: m.name, host: m.host, port: String(m.port || 22), username: m.username || 'root',
176
- password: '', privateKeyPath: m.privateKeyPath || '', passphrase: '', workspace: m.workspace || '',
177
- hostKeyMode: m.hostKeyMode || '', useAgent: !!m.useAgent, keyboardInteractive: !!m.keyboardInteractive,
178
- encryptPassword: !!(m.credentialBackend && m.credentialBackend !== 'plain'),
179
- proxyHost: (m.proxy && m.proxy.host) || '', proxyPort: String((m.proxy && m.proxy.port) || 22),
180
- proxyUser: (m.proxy && m.proxy.username) || '', proxyPassword: '', proxyKey: (m.proxy && m.proxy.privateKeyPath) || '',
181
- id: m.id,
182
- })
175
+ const startEdit = (m) => {
176
+ setForm({
177
+ name: m.name, host: m.host, port: String(m.port || 22), username: m.username || 'root',
178
+ password: '', privateKeyPath: m.privateKeyPath || '', passphrase: '', workspace: m.workspace || '',
179
+ hostKeyMode: m.hostKeyMode || '', useAgent: !!m.useAgent, keyboardInteractive: !!m.keyboardInteractive,
180
+ encryptPassword: !!(m.credentialBackend && m.credentialBackend !== 'plain'),
181
+ proxyHost: (m.proxy && m.proxy.host) || '', proxyPort: String((m.proxy && m.proxy.port) || 22),
182
+ proxyUser: (m.proxy && m.proxy.username) || '', proxyPassword: '', proxyKey: (m.proxy && m.proxy.privateKeyPath) || '',
183
+ id: m.id,
184
+ })
185
+ // 编辑时若该机器用了高级配置(私钥/agent/跳板/钥匙串等),自动展开折叠区
186
+ if (m.privateKeyPath || m.useAgent || m.keyboardInteractive || m.workspace || (m.proxy && m.proxy.host) || (m.credentialBackend && m.credentialBackend !== 'plain')) {
187
+ setAdvOpen(true)
188
+ }
189
+ }
183
190
  const clearForm = () => setForm({ name: '', host: '', port: '22', username: 'root', password: '', privateKeyPath: '', passphrase: '', workspace: '', hostKeyMode: '', useAgent: false, keyboardInteractive: false, encryptPassword: false, proxyHost: '', proxyPort: '22', proxyUser: '', proxyPassword: '', proxyKey: '', id: '' })
184
191
  const save = (action) => {
185
192
  setBusy(true); setErr(''); setMsg('')
@@ -230,10 +237,10 @@ window.__ModuleLoader__.load({
230
237
  .catch((e) => setErr(String((e && e.message) || e)))
231
238
  }
232
239
 
233
- const row = (label, ctrl, k) => React.createElement('div', { key: k, style: { display: 'flex', gap: 6, alignItems: 'center' } },
240
+ const row = (label, ctrl, k) => React.createElement('div', { key: k, style: { display: 'flex', gap: 6, alignItems: 'center', marginBottom: 8 } },
234
241
  React.createElement('label', { style: { width: 84, fontSize: 12, opacity: 0.8, flexShrink: 0 } }, label), ctrl)
235
242
 
236
- return React.createElement('div', { style: { padding: 16, display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 820 } },
243
+ return React.createElement('div', { style: { padding: 16, display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 860 } },
237
244
  React.createElement('div', { style: { fontSize: 15, fontWeight: 600 } }, '远程工作区(dsh-remote)'),
238
245
  React.createElement('div', { style: { fontSize: 12, opacity: 0.8 } },
239
246
  '维护多台 SSH 机器(密码/私钥/agent/跳板机/钥匙串)。路径在新建/选择工作区时选:「本机」走系统文件夹对话框;「远程」选一台机器在远程目录中选择。'),
@@ -251,7 +258,7 @@ window.__ModuleLoader__.load({
251
258
  m.id === currentId ? React.createElement('span', { style: { color: T.ok, fontSize: 12 } }, ' · 当前') : null),
252
259
  React.createElement('button', { style: buttonS, onClick: () => startEdit(m) }, '编辑'),
253
260
  React.createElement('button', { style: buttonS, onClick: () => del(m.id) }, '删除'),
254
- React.createElement('button', { style: buttonS, onClick: () => useNow(m.id), disabled: m.id === currentId }, '设为当前')))
261
+ React.createElement('button', { style: { ...buttonS, padding: '6px 10px', whiteSpace: 'nowrap' }, onClick: () => useNow(m.id), disabled: m.id === currentId }, '设为当前')))
255
262
  : React.createElement('div', { style: { opacity: 0.6, fontSize: 12 } }, '还没有机器。在下方添加。'),
256
263
  ),
257
264
  React.createElement('div', { style: boxS },
@@ -268,31 +275,39 @@ window.__ModuleLoader__.load({
268
275
  row('端口', React.createElement('input', { value: form.port, onChange: setF('port'), placeholder: '22', style: { ...inputS, width: 70 } }), 'p'),
269
276
  row('用户', React.createElement('input', { value: form.username, onChange: setF('username'), placeholder: 'root', style: inputS }), 'u'),
270
277
  row('密码', React.createElement('input', { type: 'password', value: form.password, onChange: setF('password'), placeholder: 'SSH 无 key 时用(不回显)', style: inputS }), 'w'),
271
- row('私钥路径', React.createElement('input', { value: form.privateKeyPath, onChange: setF('privateKeyPath'), placeholder: '留空用密码或 agent', style: inputS }), 'k'),
272
- row('Passphrase', React.createElement('input', { type: 'password', value: form.passphrase, onChange: setF('passphrase'), placeholder: '私钥加密时使用', style: inputS }), 'pp'),
273
- row('默认工作区', React.createElement('input', { value: form.workspace, onChange: setF('workspace'), placeholder: '可选:该机器的默认远程目录', style: inputS }), 'ws'),
274
- row('HostKey 模式', React.createElement('select', { value: form.hostKeyMode, onChange: setF('hostKeyMode'), style: { ...inputS, maxWidth: 220 } },
275
- React.createElement('option', { value: '' }, '(默认 accept-new)'),
276
- React.createElement('option', { value: 'accept-new' }, 'accept-new(信任首次,之后校验)'),
277
- React.createElement('option', { value: 'verify' }, 'verify(严格:拒绝陌生主机)'),
278
- React.createElement('option', { value: 'off' }, 'off(不校验,不推荐)')), 'hkm'),
279
- React.createElement('div', { style: { display: 'flex', gap: 16, paddingLeft: 90, flexWrap: 'wrap' } },
280
- React.createElement('label', { style: { fontSize: 12, display: 'flex', gap: 4, alignItems: 'center' } },
281
- React.createElement('input', { type: 'checkbox', checked: form.useAgent, onChange: setFb('useAgent') }), 'SSH agent(SSH_AUTH_SOCK)'),
282
- React.createElement('label', { style: { fontSize: 12, display: 'flex', gap: 4, alignItems: 'center' } },
283
- React.createElement('input', { type: 'checkbox', checked: form.keyboardInteractive, onChange: setFb('keyboardInteractive') }), 'keyboard-interactive(OTP/动态码)'),
284
- React.createElement('label', { style: { fontSize: 12, display: 'flex', gap: 4, alignItems: 'center' } },
285
- React.createElement('input', { type: 'checkbox', checked: form.encryptPassword, onChange: setFb('encryptPassword') }), '加密保存密码(系统钥匙串)')),
286
- row('跳板机', React.createElement('input', { value: form.proxyHost, onChange: setF('proxyHost'), placeholder: '经此主机中转(可选)', style: inputS }), 'ph'),
287
- row('跳板端口/用户', React.createElement('div', { style: { display: 'flex', gap: 6, flex: 1 } },
288
- React.createElement('input', { value: form.proxyPort, onChange: setF('proxyPort'), placeholder: '22', style: { ...inputS, width: 60 } }),
289
- React.createElement('input', { value: form.proxyUser, onChange: setF('proxyUser'), placeholder: '跳板用户', style: inputS }),
290
- React.createElement('input', { type: 'password', value: form.proxyPassword, onChange: setF('proxyPassword'), placeholder: '跳板密码', style: inputS }),
291
- React.createElement('input', { value: form.proxyKey, onChange: setF('proxyKey'), placeholder: '跳板私钥', style: inputS })), 'pu'),
292
- React.createElement('div', { style: { display: 'flex', gap: 8, justifyContent: 'flex-end', alignItems: 'center' } },
278
+ // 高级配置折叠开关
279
+ React.createElement('button', {
280
+ type: 'button',
281
+ onClick: () => setAdvOpen(!advOpen),
282
+ style: { ...buttonS, width: '100%', marginBottom: advOpen ? 10 : 0, color: advOpen ? T.primary : 'inherit', fontWeight: advOpen ? 600 : 400, borderStyle: 'dashed' },
283
+ }, advOpen ? '▲ 收起高级配置' : '▼ 高级配置(私钥 / 跳板机 / agent 等)'),
284
+ advOpen ? React.createElement('div', { key: 'adv', style: { paddingTop: 2 } },
285
+ row('私钥路径', React.createElement('input', { value: form.privateKeyPath, onChange: setF('privateKeyPath'), placeholder: '留空用密码或 agent', style: inputS }), 'k'),
286
+ row('Passphrase', React.createElement('input', { type: 'password', value: form.passphrase, onChange: setF('passphrase'), placeholder: '私钥加密时使用', style: inputS }), 'pp'),
287
+ row('默认工作区', React.createElement('input', { value: form.workspace, onChange: setF('workspace'), placeholder: '可选:该机器的默认远程目录', style: inputS }), 'ws'),
288
+ row('HostKey 模式', React.createElement('select', { value: form.hostKeyMode, onChange: setF('hostKeyMode'), style: { ...inputS, maxWidth: 220 } },
289
+ React.createElement('option', { value: '' }, '(默认 accept-new)'),
290
+ React.createElement('option', { value: 'accept-new' }, 'accept-new(信任首次,之后校验)'),
291
+ React.createElement('option', { value: 'verify' }, 'verify(严格:拒绝陌生主机)'),
292
+ React.createElement('option', { value: 'off' }, 'off(不校验,不推荐)')), 'hkm'),
293
+ React.createElement('div', { style: { display: 'flex', gap: 16, paddingLeft: 90, flexWrap: 'wrap', marginBottom: 8 } },
294
+ React.createElement('label', { style: { fontSize: 12, display: 'flex', gap: 4, alignItems: 'center' } },
295
+ React.createElement('input', { type: 'checkbox', checked: form.useAgent, onChange: setFb('useAgent') }), 'SSH agent(SSH_AUTH_SOCK)'),
296
+ React.createElement('label', { style: { fontSize: 12, display: 'flex', gap: 4, alignItems: 'center' } },
297
+ React.createElement('input', { type: 'checkbox', checked: form.keyboardInteractive, onChange: setFb('keyboardInteractive') }), 'keyboard-interactive(OTP/动态码)'),
298
+ React.createElement('label', { style: { fontSize: 12, display: 'flex', gap: 4, alignItems: 'center' } },
299
+ React.createElement('input', { type: 'checkbox', checked: form.encryptPassword, onChange: setFb('encryptPassword') }), '加密保存密码(系统钥匙串)')),
300
+ row('跳板机', React.createElement('input', { value: form.proxyHost, onChange: setF('proxyHost'), placeholder: '经此主机中转(可选)', style: inputS }), 'ph'),
301
+ row('端口/用户', React.createElement('div', { style: { display: 'flex', gap: 6, flex: 1, flexWrap: 'wrap' } },
302
+ React.createElement('input', { value: form.proxyPort, onChange: setF('proxyPort'), placeholder: '端口(22)', style: { ...inputS, minWidth: 90, maxWidth: 120 } }),
303
+ React.createElement('input', { value: form.proxyUser, onChange: setF('proxyUser'), placeholder: '跳板用户', style: { ...inputS, minWidth: 140 } }),
304
+ React.createElement('input', { type: 'password', value: form.proxyPassword, onChange: setF('proxyPassword'), placeholder: '跳板密码', style: { ...inputS, minWidth: 140 } })), 'pu'),
305
+ row('跳板私钥', React.createElement('input', { value: form.proxyKey, onChange: setF('proxyKey'), placeholder: '跳板私钥路径(可选)', style: inputS }), 'pk'),
306
+ ) : null,
307
+ React.createElement('div', { style: { display: 'flex', gap: 8, justifyContent: 'flex-end', alignItems: 'center', flexWrap: 'wrap', marginTop: 4 } },
293
308
  msg ? React.createElement('span', { style: { color: T.ok, fontSize: 12, marginRight: 'auto' } }, msg) : null,
294
309
  form.id ? React.createElement('button', { style: buttonS, onClick: () => save('delete') }, '删除') : null,
295
- React.createElement('button', { style: buttonS, onClick: () => { clearForm(); setErr(''); setTst('idle') } }, form.id ? '取消编辑' : '清空'),
310
+ React.createElement('button', { style: buttonS, onClick: () => { clearForm(); setErr(''); setTst('idle'); setAdvOpen(false) } }, form.id ? '取消编辑' : '清空'),
296
311
  React.createElement('button', { style: { ...buttonS, fontFamily: 'monospace', whiteSpace: 'nowrap' }, onClick: testConn, disabled: busy || !form.host.trim() || tst === 'testing' },
297
312
  tst === 'testing' ? '连接中…' : tst === 'ok' ? '✓ 连接成功' : '测试连接'),
298
313
  React.createElement('button', { style: { ...buttonS, fontWeight: 700 }, onClick: () => save(form.id ? 'update' : 'add'), disabled: busy || !form.host.trim() }, busy ? '保存中…' : '保存'),
@@ -363,7 +378,7 @@ window.__ModuleLoader__.load({
363
378
  React.createElement('a', { href: 'https://github.com/flymysql/dsh-remote', target: '_blank', rel: 'noopener noreferrer', style: { color: 'inherit' } }, '⭐ 去 GitHub 点个 Star'),
364
379
  React.createElement('span', {}, '·'),
365
380
  React.createElement('a', { href: 'https://github.com/flymysql/dsh-remote/issues/new', target: '_blank', rel: 'noopener noreferrer', style: { color: 'inherit' } }, '💬 反馈建议 / 提 issue'),
366
- React.createElement('span', { style: { marginLeft: 'auto', opacity: 0.5 } }, 'dsh-remote v0.8.0'),
381
+ React.createElement('span', { style: { marginLeft: 'auto', opacity: 0.5 } }, 'dsh-remote v0.8.3'),
367
382
  ),
368
383
  )
369
384
  }
@@ -665,7 +680,7 @@ window.__ModuleLoader__.load({
665
680
  React.createElement('span', { style: { fontSize: 11, opacity: 0.6, whiteSpace: 'nowrap' } }, '最近:'),
666
681
  recent.map((w, i) => React.createElement('button', { key: w + i, onClick: () => { setPath(w); setErr(''); continueSuggest(w) }, style: { ...buttonS, padding: '2px 8px', fontSize: 11, fontFamily: 'monospace' } }, w))) : null,
667
682
  React.createElement('div', { ref: suggestRef, style: { position: 'relative' } },
668
- React.createElement('div', { style: { display: 'flex', gap: 6 } },
683
+ React.createElement('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap' } },
669
684
  React.createElement('input', { value: path, onChange: (e) => onPathChange(e.target.value), onFocus: () => loadSuggestions(path), placeholder: (machineId ? '输入远程路径(自动补全)' : '先选远程机器'), disabled: !machineId, style: { ...inputS, flex: 1, minWidth: 120 } }),
670
685
  React.createElement('button', { style: { ...buttonS, whiteSpace: 'nowrap' }, onClick: () => { if (machineId) setPopOpen(true) }, disabled: !machineId }, '浏览…'),
671
686
  React.createElement('button', { style: { ...buttonS, whiteSpace: 'nowrap' }, onClick: pickHome, disabled: !machineId }, '~ 主目录'),
package/lib/index.js CHANGED
@@ -101,8 +101,9 @@ export const Config = z.object({
101
101
  encoding: z.string().default('utf-8'),
102
102
  /** Update mode: `manual` (default) only checks when asked; `auto` checks on
103
103
  * load and periodically, applying a newer npm release automatically;
104
- * `off` disables version checks entirely. */
105
- updateMode: z.enum(['manual', 'auto', 'off']).default('manual'),
104
+ * `off` disables version checks entirely. (schemastery 3.18 has no .enum —
105
+ * keep string and validate in code.) */
106
+ updateMode: z.string().default('manual'),
106
107
  /** How often (ms) auto mode checks the npm registry for a newer release. */
107
108
  updateCheckIntervalMs: z.number().step(1).min(60000).default(6 * 3600 * 1000),
108
109
  })
@@ -2361,12 +2362,13 @@ export async function apply(ctx, config) {
2361
2362
  const current = readVersion()
2362
2363
  const latest = await fetchLatestVersion()
2363
2364
  if (latest === null) return sendJson(res, 200, { ok: false, current, error: '无法连接 npm registry' })
2365
+ const rawMode = readUpdateMode() || config.updateMode || 'manual'
2364
2366
  return sendJson(res, 200, {
2365
2367
  ok: true,
2366
2368
  current,
2367
2369
  latest,
2368
2370
  updateAvailable: gtVersion(latest, current),
2369
- updateMode: readUpdateMode() || config.updateMode,
2371
+ updateMode: ['manual', 'auto', 'off'].includes(rawMode) ? rawMode : 'manual',
2370
2372
  updatedMarker: existsSync(path.join(selfDir(), '.dsh-remote-updated')),
2371
2373
  })
2372
2374
  },
@@ -2411,7 +2413,8 @@ export async function apply(ctx, config) {
2411
2413
  // ── auto-update: check on load + on an interval; apply silently ───────────
2412
2414
  // Failures are swallowed (never break the plugin). The persisted override
2413
2415
  // (settings UI) wins over the profile-config default.
2414
- const effectiveUpdateMode = readUpdateMode() || config.updateMode
2416
+ const rawMode = readUpdateMode() || config.updateMode || 'manual'
2417
+ const effectiveUpdateMode = ['manual', 'auto', 'off'].includes(rawMode) ? rawMode : 'manual'
2415
2418
  if (effectiveUpdateMode === 'auto') {
2416
2419
  const currentVersion = readVersion()
2417
2420
  const checkAndApply = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "Remote-work assistant for DeepSeek Harness: connect SSH (password/key/agent/keyboard-interactive/proxy jump), pick a remote workspace, operate on it with 21 rw_* tools (incl. rw_edit/rw_stat/rw_mkdir/rw_remove/rw_move/rw_forward), conflict-aware mirror sync, port forwarding, side-bar remote file editing, audit log, keychain passwords.",
5
5
  "license": "MIT",
6
6
  "author": "flymysql <flyphp@outlook.com>",