@linxin666/dsh-ssh 0.3.6 → 0.3.9

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
@@ -1,4 +1,3 @@
1
- import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
2
1
  import { closeSync, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
3
2
  import { Client } from "ssh2";
4
3
  import { homedir, tmpdir } from "node:os";
@@ -3560,7 +3559,7 @@ const inject = [
3560
3559
  * surface edits. Spelled here rather than imported: the browser half spells
3561
3560
  * the same value and must not depend on a Host package.
3562
3561
  */
3563
- const SSH_SETTINGS_NAMESPACE = settingsNamespace("dsh-ssh");
3562
+ const SSH_SETTINGS_NAMESPACE = "dsh-ssh";
3564
3563
  const Config = import_lib.default.object({
3565
3564
  announceToAgent: import_lib.default.boolean().default(false),
3566
3565
  enabled: import_lib.default.boolean().default(true),
@@ -3580,18 +3579,21 @@ const SSH_GUIDANCE = "本机已安装 dsh-ssh 插件(DSH 远程 SSH 运维)
3580
3579
  const apply = mountOnce("@linxin666/dsh-ssh", applyImpl);
3581
3580
  function applyImpl(ctx, config) {
3582
3581
  let current = () => config ?? {};
3582
+ let isSettingsBound = false;
3583
+ const store = new HostStore();
3584
+ const engine = new SshEngine(store);
3585
+ ctx.effect(() => () => {
3586
+ engine.dispose();
3587
+ }, "dsh-ssh: engine");
3583
3588
  const resolve = () => {
3584
3589
  const value = current();
3590
+ let enabled = value.enabled ?? true;
3591
+ if (enabled === false && !isSettingsBound && store.list().length > 0) enabled = true;
3585
3592
  return {
3586
3593
  announceToAgent: value.announceToAgent ?? DEFAULT_ANNOUNCE,
3587
- enabled: value.enabled ?? true
3594
+ enabled
3588
3595
  };
3589
3596
  };
3590
- const store = new HostStore();
3591
- const engine = new SshEngine(store);
3592
- ctx.effect(() => () => {
3593
- engine.dispose();
3594
- }, "dsh-ssh: engine");
3595
3597
  const { routes, upgrade } = makeRoutes({
3596
3598
  store,
3597
3599
  engine
@@ -3642,12 +3644,15 @@ function applyImpl(ctx, config) {
3642
3644
  };
3643
3645
  }, "dsh-ssh: tools");
3644
3646
  };
3645
- installSettingsSection(ctx, SSH_SETTINGS_NAMESPACE, Config, config ?? {}, {
3646
- setSource: (source) => {
3647
- current = source;
3648
- sync();
3649
- },
3650
- onChange: sync
3647
+ ctx.inject(["settings"], (settingsCtx) => {
3648
+ settingsCtx.settings.installSection(ctx, SSH_SETTINGS_NAMESPACE, Config, config ?? {}, {
3649
+ setSource: (source) => {
3650
+ isSettingsBound = true;
3651
+ current = source;
3652
+ sync();
3653
+ },
3654
+ onChange: sync
3655
+ });
3651
3656
  });
3652
3657
  sync();
3653
3658
  }
@@ -10,7 +10,8 @@
10
10
  * Export discipline (packages/client rule): the /client surface carries what
11
11
  * cordis loading needs plus types only — all value exports stay internal.
12
12
  */
13
- import type { ClientContext, SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-runtime/client';
13
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
14
+ import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-ui-settings/client';
14
15
  import { type SshKey } from './locales.ts';
15
16
  declare module '@deepseek-ai/cordis' {
16
17
  interface Context {
@@ -146,6 +146,7 @@ export declare const zh: {
146
146
  readonly 'common.error': "出错:{error}";
147
147
  readonly 'common.refresh': "刷新";
148
148
  readonly 'common.confirm': "确定";
149
+ readonly 'error.disabled': "SSH 插件在宿主端未启用。请前往「设置 → Web 插件 → SSH」打开 enabled 开关。";
149
150
  };
150
151
  export declare const en: Record<keyof typeof zh, string>;
151
152
  /** Locale key union. */
@@ -8,6 +8,7 @@
8
8
  * no dsh source changes.
9
9
  */
10
10
  import type { Context } from '@deepseek-ai/cordis';
11
+ import type { SettingsNamespace } from '@deepseek-ai/dsh-settings';
11
12
  import z from 'schemastery';
12
13
  /** Stable cordis plugin name. */
13
14
  export declare const name = "ssh";
@@ -18,7 +19,7 @@ export declare const inject: string[];
18
19
  * surface edits. Spelled here rather than imported: the browser half spells
19
20
  * the same value and must not depend on a Host package.
20
21
  */
21
- export declare const SSH_SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
22
+ export declare const SSH_SETTINGS_NAMESPACE: SettingsNamespace;
22
23
  /** Plugin config, validated by the same-named schemastery schema. */
23
24
  export interface Config {
24
25
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-ssh",
3
3
  "description": "Remote SSH operations for the dsh web GUI: host config store (~/.dsh/dsh-ssh.json, import from ~/.ssh/config), a persistent ssh2 connection pool with jump-host support, exec / PTY web terminal / SFTP transfer / local port-forward tunnels / cluster execution, plus agent tools (ssh_list, ssh_exec, ssh_upload, ssh_download, ssh_tunnel, ssh_cluster). Hot-pluggable — mounted via ~/.dsh/cordis.patch.yml + a profile node_modules symlink, no dsh source changes.",
4
- "version": "0.3.6",
4
+ "version": "0.3.9",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -26,15 +26,15 @@
26
26
  },
27
27
  "dsh": {
28
28
  "engines": {
29
- "dsh": ">=0.1.1-rc.1"
29
+ "dsh": ">=0.1.2-alpha.1"
30
30
  },
31
31
  "bundle": {
32
32
  "patch": "./cordis.patch.yml"
33
33
  },
34
34
  "client": {
35
35
  "inject": [
36
- "@deepseek-ai/dsh-client-runtime",
37
- "@deepseek-ai/dsh-client-connection",
36
+ "@deepseek-ai/dsh-client-locale",
37
+ "@deepseek-ai/dsh-client-ui-renderer",
38
38
  "@deepseek-ai/dsh-client-ui-settings"
39
39
  ],
40
40
  "platform": "web"
@@ -51,19 +51,19 @@
51
51
  "react-dom": "^18.2.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@deepseek-ai/cordis": "^4.0.1",
55
- "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2",
56
- "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
57
- "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
58
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
59
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
60
- "@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2",
61
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
62
- "@deepseek-ai/dsh-scope": "^0.1.1-rc.2",
63
- "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
64
- "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.2",
65
- "@deepseek-ai/dsh-timeout": "^0.1.1-rc.2",
66
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
54
+ "@deepseek-ai/cordis": "^4.0.2",
55
+ "@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.2",
56
+ "@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2",
57
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.2",
58
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.2",
59
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.2",
60
+ "@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2",
61
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
62
+ "@deepseek-ai/dsh-scope": "^0.1.2-alpha.2",
63
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
64
+ "@deepseek-ai/dsh-system-prompt": "^0.1.2-alpha.2",
65
+ "@deepseek-ai/dsh-timeout": "^0.1.2-alpha.2",
66
+ "@deepseek-ai/dsh-tools": "^0.1.2-alpha.2",
67
67
  "@types/node": "^22.20.0",
68
68
  "@types/react": "~18.3.1",
69
69
  "@types/react-dom": "^18.3.5",
package/src/client/api.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  type TransferStreamLine,
19
19
  type TunnelInfo,
20
20
  } from '../protocol.ts'
21
+ import { tt } from './panel/helpers.ts'
21
22
 
22
23
  /** Minimal File System Access API surface (not in all lib.dom versions). */
23
24
  interface WindowWithFileSystemAccess {
@@ -51,12 +52,15 @@ async function readJson<T>(response: Response): Promise<T> {
51
52
  try {
52
53
  body = await response.json()
53
54
  } catch {
55
+ if (response.status === 404) {
56
+ throw new SshApiError(tt('error.disabled'))
57
+ }
54
58
  throw new SshApiError(`HTTP ${response.status}: invalid JSON response`)
55
59
  }
56
60
  if (!response.ok) {
57
61
  const message = typeof body === 'object' && body !== null && typeof (body as { error?: unknown }).error === 'string'
58
62
  ? (body as { error: string }).error
59
- : `HTTP ${response.status}`
63
+ : (response.status === 404 ? tt('error.disabled') : `HTTP ${response.status}`)
60
64
  throw new SshApiError(message)
61
65
  }
62
66
  return body as T
@@ -10,11 +10,14 @@
10
10
  * Export discipline (packages/client rule): the /client surface carries what
11
11
  * cordis loading needs plus types only — all value exports stay internal.
12
12
  */
13
- import type { ClientContext, SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-runtime/client'
13
+ import type { Context as ClientContext } from '@deepseek-ai/cordis'
14
+ import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-ui-settings/client'
14
15
  // Type-only: pulls the locale plugin's Context merge (ctx.locale).
15
16
  import type {} from '@deepseek-ai/dsh-client-locale/client'
16
17
  // Type-only: pulls the settings-surface Context merge (ctx.settingsScope).
17
18
  import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
19
+ // Type-only: pulls the ctx.slots merge (the renderer owns the slot registry since 0.1.2).
20
+ import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
18
21
  // Type-only: pulls the LocaleNamespaceMap merge table.
19
22
  import type {} from '@deepseek-ai/dsh-client-ui-slots'
20
23
  import { SshApi } from './api.ts'
@@ -154,6 +154,7 @@ export const zh = {
154
154
  'common.error': '出错:{error}',
155
155
  'common.refresh': '刷新',
156
156
  'common.confirm': '确定',
157
+ 'error.disabled': 'SSH 插件在宿主端未启用。请前往「设置 → Web 插件 → SSH」打开 enabled 开关。',
157
158
  } as const
158
159
 
159
160
  export const en: Record<keyof typeof zh, string> = {
@@ -301,6 +302,7 @@ export const en: Record<keyof typeof zh, string> = {
301
302
  'common.error': 'Error: {error}',
302
303
  'common.refresh': 'Refresh',
303
304
  'common.confirm': 'Confirm',
305
+ 'error.disabled': 'The SSH plugin is disabled on the host. Please enable it in Settings → Web Plugins → SSH.',
304
306
  }
305
307
 
306
308
  /** Locale key union. */
package/src/dsh-home.ts CHANGED
@@ -39,4 +39,4 @@ export function resolveDshHome(env: NodeJS.ProcessEnv = process.env, home: strin
39
39
  /** Resolve the DSH home directory from the live environment. */
40
40
  export function dshHome(): string {
41
41
  return resolveDshHome()
42
- }
42
+ }
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { Context } from '@deepseek-ai/cordis'
12
- import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
12
+ import type { SettingsNamespace } from '@deepseek-ai/dsh-settings'
13
13
  import z from 'schemastery'
14
14
  import type {} from '@deepseek-ai/dsh-host-webserver'
15
15
  import type {} from '@deepseek-ai/dsh-system-prompt'
@@ -31,7 +31,7 @@ export const inject = ['webServer', 'tools', 'systemPrompt']
31
31
  * surface edits. Spelled here rather than imported: the browser half spells
32
32
  * the same value and must not depend on a Host package.
33
33
  */
34
- export const SSH_SETTINGS_NAMESPACE = settingsNamespace('dsh-ssh')
34
+ export const SSH_SETTINGS_NAMESPACE = 'dsh-ssh' as SettingsNamespace
35
35
 
36
36
  /** Plugin config, validated by the same-named schemastery schema. */
37
37
  export interface Config {
@@ -77,18 +77,28 @@ function applyImpl(ctx: Context, config?: Config): void {
77
77
  // The live source the surfaces read: the settings section once the web
78
78
  // settings surface is served, the composition entry otherwise.
79
79
  let current: () => Config = () => config ?? {}
80
+ let isSettingsBound = false
81
+
82
+ const store = new HostStore()
83
+ const engine = new SshEngine(store)
84
+ ctx.effect(() => () => { engine.dispose() }, 'dsh-ssh: engine')
85
+
80
86
  const resolve = (): Config => {
81
87
  const value = current()
88
+ let enabled = value.enabled ?? true
89
+ // When dsh-ssh was seeded with enabled: false (e.g. from an aggregate profile line),
90
+ // but the user has never explicitly configured settings (not bound yet)
91
+ // AND already has active host records in dsh-ssh.json, keep the plugin enabled so
92
+ // existing users are not broken upon upgrade (#1250).
93
+ if (enabled === false && !isSettingsBound && store.list().length > 0) {
94
+ enabled = true
95
+ }
82
96
  return {
83
97
  announceToAgent: value.announceToAgent ?? DEFAULT_ANNOUNCE,
84
- enabled: value.enabled ?? true,
98
+ enabled,
85
99
  }
86
100
  }
87
101
 
88
- const store = new HostStore()
89
- const engine = new SshEngine(store)
90
- ctx.effect(() => () => { engine.dispose() }, 'dsh-ssh: engine')
91
-
92
102
  // The /api/dsh-ssh route family + terminal upgrade.
93
103
  const { routes, upgrade } = makeRoutes({ store, engine })
94
104
  let disposeRoutes: (() => void) | undefined
@@ -152,15 +162,18 @@ function applyImpl(ctx: Context, config?: Config): void {
152
162
  )
153
163
  }
154
164
 
155
- installSettingsSection(ctx, SSH_SETTINGS_NAMESPACE, Config, config ?? {}, {
156
- setSource: (source) => {
157
- current = source
158
- sync()
159
- },
160
- onChange: sync,
165
+ ctx.inject(['settings'], (settingsCtx) => {
166
+ settingsCtx.settings.installSection(ctx, SSH_SETTINGS_NAMESPACE, Config, config ?? {}, {
167
+ setSource: (source) => {
168
+ isSettingsBound = true
169
+ current = source
170
+ sync()
171
+ },
172
+ onChange: sync,
173
+ })
161
174
  })
162
175
 
163
176
  // Initial registration from the composition entry (covers deployments with
164
- // no settings service, whose installSettingsSection never fires its hooks).
177
+ // no settings service, whose installSection never fires its hooks).
165
178
  sync()
166
179
  }