dsh-remote 0.8.60 → 0.8.61

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/index.js CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  import { createHostKeyGuard, isHostKeyKnown as _isHostKeyKnown } from './hostkey.js'
35
35
  import { compileIgnore, ignorePatternsFor, DEFAULT_IGNORE } from './ignore.js'
36
36
  import { friendlyMessage } from './errors.js'
37
- import { importableEntries, sshConfigPath, readSshConfigText, resolveMachineSshConfig, resolveLiteralSshConfig, readRawSshConfig, upsertManagedHostBlock, removeManagedHostBlock, writeSshConfig } from './sshconfig.js'
37
+ import { listAliases, sshConfigPath, readSshConfigText, resolveMachineSshConfig, resolveLiteralSshConfig, readRawSshConfig, upsertManagedHostBlock, removeManagedHostBlock, writeSshConfig } from './sshconfig.js'
38
38
  import { createRemoteFileReference, installFileReferenceOverlay, DEFAULT_EXCLUDED_DIRECTORIES } from './file-reference.js'
39
39
  import { getSecret, deleteSecret, platformBackend, persistPassword } from './credential.js'
40
40
  import { syncTree, pushTree, loadSyncState, saveSyncState, pushOneFile } from './sync.js'
@@ -3816,6 +3816,62 @@ export async function apply(ctx, config) {
3816
3816
  ...(sshConfigWarning ? { sshConfigWarning } : {}),
3817
3817
  })
3818
3818
  }
3819
+ // Issue #73: one click on a host listed from ~/.ssh/config. The settings
3820
+ // page no longer asks the user to understand "alias" vs "copy": it lists
3821
+ // the file's hosts and this action adopts one — upsert an alias machine
3822
+ // (never a duplicate), make it the current machine, and let the caller
3823
+ // connect/probe next. Nothing about the user's file is rewritten here:
3824
+ // the alias IS the source of truth (issue #50).
3825
+ if (action === 'from-ssh-config') {
3826
+ const alias = String(body.alias || body.host || '').trim()
3827
+ if (!alias) return sendJson(res, 400, { ok: false, error: 'alias required' })
3828
+ const known = listAliases(sshConfigText()).find((a) => a.alias === alias)
3829
+ if (!known) {
3830
+ return sendJson(res, 404, {
3831
+ ok: false,
3832
+ error: `~/.ssh/config 里没有 "${alias}"(改完文件后重新打开设置页即可刷新)`,
3833
+ })
3834
+ }
3835
+ const existing = machines.find((m) => m.useSshConfig && String(m.host) === alias)
3836
+ let rec = existing
3837
+ if (!rec) {
3838
+ rec = {
3839
+ id: machineId(),
3840
+ name: alias,
3841
+ host: alias,
3842
+ useSshConfig: true,
3843
+ port: Number(known.port) || 22,
3844
+ username: String(known.user || ''),
3845
+ password: '',
3846
+ privateKeyPath: '',
3847
+ passphrase: '',
3848
+ workspace: '',
3849
+ hostKeyMode: '',
3850
+ useAgent: false,
3851
+ keyboardInteractive: false,
3852
+ backend: 'native',
3853
+ sshBinary: '',
3854
+ credentialBackend: 'plain',
3855
+ recentWorkspaces: [],
3856
+ lastConnectedAt: null,
3857
+ latencyMs: null,
3858
+ }
3859
+ machines.push(rec)
3860
+ saveMachines(machines, store.currentId, false)
3861
+ }
3862
+ // Making it current is the whole point of the click: the picker and
3863
+ // every session route then act on this machine.
3864
+ const okSet = await setCurrent(rec.id)
3865
+ if (!okSet) return sendJson(res, 500, { ok: false, error: 'could not activate the machine' })
3866
+ return sendJson(res, 200, {
3867
+ ok: true,
3868
+ created: !existing,
3869
+ machine: sanitizeMachine(rec),
3870
+ machines: machinesForClient(),
3871
+ currentId: store.currentId,
3872
+ ...status(),
3873
+ })
3874
+ }
3819
3875
  if (action === 'delete') {
3820
3876
  const i = machineIndex(String(body.id || ''))
3821
3877
  if (i < 0) return sendJson(res, 404, { ok: false, error: 'machine not found' })
@@ -4104,7 +4160,22 @@ export async function apply(ctx, config) {
4104
4160
  if (req.method !== 'GET') return sendJson(res, 405, { ok: false, error: 'method not allowed' })
4105
4161
  try {
4106
4162
  const text = readSshConfigText()
4107
- return sendJson(res, 200, { ok: true, file: sshConfigPath(), present: !!text, entries: importableEntries(text) })
4163
+ // Issue #73: the settings page lists the hosts of ~/.ssh/config the way
4164
+ // VSCode Remote-SSH does, so this must include EVERY concrete alias —
4165
+ // `importableEntries` filtered out a bare `Host mybox` with no options,
4166
+ // which is a perfectly normal entry to connect to. `host` is kept as an
4167
+ // alias of `alias` so existing callers keep working.
4168
+ const entries = listAliases(text).map((a) => ({
4169
+ host: a.alias,
4170
+ alias: a.alias,
4171
+ hostName: a.hostName || a.alias,
4172
+ user: a.user || '',
4173
+ port: Number(a.port) || 22,
4174
+ identityFile: a.identityFile || '',
4175
+ proxyJump: a.proxyJump || '',
4176
+ proxyCommand: a.proxyCommand || '',
4177
+ }))
4178
+ return sendJson(res, 200, { ok: true, file: sshConfigPath(), present: !!text, entries })
4108
4179
  } catch (err) {
4109
4180
  return sendJson(res, 500, { ok: false, error: String((err && err.message) || err) })
4110
4181
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote",
3
- "version": "0.8.60",
3
+ "version": "0.8.61",
4
4
  "description": "Remote-work assistant for DeepSeek Harness: connect SSH (password/key/agent/keyboard-interactive, multi-hop ProxyJump), pick a remote workspace, operate on it with 22 rw_* tools, conflict-aware mirror sync, an embedded PTY terminal, one-click remote dsh deploy + web UI attach, port forwarding, host grouping, remote sandbox fencing, audit log, keychain passwords.",
5
5
  "license": "MIT",
6
6
  "author": "flymysql <flyphp@outlook.com>",
@@ -92,8 +92,8 @@
92
92
  "semver": "^7.8.5"
93
93
  },
94
94
  "scripts": {
95
- "test": "node --import ./test/setup-telemetry-off.mjs --test --test-timeout=40000 --test-force-exit",
96
- "test:coverage": "node scripts/coverage-union.mjs",
95
+ "test": "node --import ./test/setup-telemetry-off.mjs --test --test-timeout=40000 --test-force-exit",
96
+ "test:coverage": "node scripts/coverage-union.mjs",
97
97
  "test:coverage:gate": "node scripts/coverage-union.mjs --gate 70"
98
98
  }
99
99
  }