@linxin666/dsh-ssh 0.3.24 → 0.4.1
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/icon.svg +3 -0
- package/lib/client.js +128 -62
- package/lib/client.js.map +1 -1
- package/lib/index.js +34 -1050
- package/lib/types/client/index.d.ts +0 -13
- package/lib/types/client/settings-binding.d.ts +61 -0
- package/lib/types/client/sidebar-entry-core.d.ts +0 -32
- package/lib/types/http.d.ts +18 -1
- package/lib/types/index.d.ts +46 -13
- package/package.json +22 -19
- package/src/client/index.ts +18 -22
- package/src/client/panel/panel.module.css +30 -11
- package/src/client/settings-binding.ts +165 -0
- package/src/client/sidebar-entry-core.ts +10 -94
- package/src/client/sidebar-entry.ts +2 -2
- package/src/http.ts +23 -1
- package/src/index.ts +88 -55
|
@@ -11,20 +11,7 @@
|
|
|
11
11
|
* cordis loading needs plus types only — all value exports stay internal.
|
|
12
12
|
*/
|
|
13
13
|
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
14
|
-
import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
15
14
|
import { type SshKey } from './locales.ts';
|
|
16
|
-
declare module '@deepseek-ai/cordis' {
|
|
17
|
-
interface Context {
|
|
18
|
-
/**
|
|
19
|
-
* Optional rc.6 compatibility binder provided by dsh-web-settings;
|
|
20
|
-
* absent when that group plugin is not installed, so callers fall back to
|
|
21
|
-
* the official settings scope.
|
|
22
|
-
*/
|
|
23
|
-
webUiSettings?: {
|
|
24
|
-
bind<S>(spec: SettingsScopeSpec<S>): SettingsScope<S>;
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
15
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
29
16
|
interface LocaleNamespaceMap {
|
|
30
17
|
/** dsh-ssh surface copy. */
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings reader of the dsh-ssh browser half.
|
|
3
|
+
*
|
|
4
|
+
* The plugin's browser half owns one preference: the xterm `fontFamily` the
|
|
5
|
+
* panel applies to its terminals. Under the 0.1.7 settings model that field
|
|
6
|
+
* belongs to the plugin's own profile entry (its Cordis Config schema), and
|
|
7
|
+
* the browser surface addresses settings as one form per profile entry id — so
|
|
8
|
+
* the entry has to be resolved before its section can be read:
|
|
9
|
+
*
|
|
10
|
+
* - the family binder (`ctx.get('webUiSettings')`, published while
|
|
11
|
+
* dsh-web-settings is loaded) resolves the dsh-ssh namespace to its entry id
|
|
12
|
+
* through the Host bridge and hands back the native shared form;
|
|
13
|
+
* - without that group the shared configuration forms serve the form directly,
|
|
14
|
+
* and the entry is recognised by the one field this plugin's own schema
|
|
15
|
+
* declares: the entry whose resolved section carries `terminalFontFamily`.
|
|
16
|
+
*/
|
|
17
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
18
|
+
import type { ConfigForm, ConfigFormSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
19
|
+
/** Domain-owned description of one settings namespace a family surface binds. */
|
|
20
|
+
export interface ConfigFormSpec<T> {
|
|
21
|
+
/** Settings namespace the surface edits. */
|
|
22
|
+
namespace: string;
|
|
23
|
+
/**
|
|
24
|
+
* Narrow one wire section; undefined keeps the section the Host resolved.
|
|
25
|
+
* The shared form already validates against the namespace's serialized
|
|
26
|
+
* schema, so a decoder exists only to narrow beyond it.
|
|
27
|
+
*/
|
|
28
|
+
decode?: (section: unknown) => T | undefined;
|
|
29
|
+
}
|
|
30
|
+
declare module '@deepseek-ai/cordis' {
|
|
31
|
+
interface Context {
|
|
32
|
+
/**
|
|
33
|
+
* Optional family settings binder provided by dsh-web-settings; absent when
|
|
34
|
+
* that group plugin is not installed, so callers fall back to the shared
|
|
35
|
+
* configuration forms service (`ctx.configForms`).
|
|
36
|
+
*/
|
|
37
|
+
webUiSettings?: {
|
|
38
|
+
bind<S>(spec: ConfigFormSpec<S>): ConfigForm<S>;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** The read face this plugin needs from its own settings entry. */
|
|
43
|
+
export interface SettingsReader<T> {
|
|
44
|
+
/** @returns the current snapshot (stable reference until the next change). */
|
|
45
|
+
getSnapshot(): ConfigFormSnapshot<T>;
|
|
46
|
+
/** Observe snapshot replacements. */
|
|
47
|
+
subscribe(listener: () => void): () => void;
|
|
48
|
+
}
|
|
49
|
+
/** A reader plus the release of every subscription it installed. */
|
|
50
|
+
export interface SettingsBinding<T> extends SettingsReader<T> {
|
|
51
|
+
/** Release the reader's subscriptions; later calls are no-ops. */
|
|
52
|
+
dispose(): void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Bind this plugin's settings reader.
|
|
56
|
+
* @param ctx - client context carrying the family binder and/or the shared forms service.
|
|
57
|
+
* @param namespace - the family settings namespace this plugin owns.
|
|
58
|
+
* @param field - the field this plugin's own schema declares, used to recognise its entry when the family binder is absent.
|
|
59
|
+
* @returns the reader and the release of its subscriptions.
|
|
60
|
+
*/
|
|
61
|
+
export declare function bindSettingsReader<T>(ctx: ClientContext, namespace: string, field: string): SettingsBinding<T>;
|
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One trailing action button seated at the entry row's right edge (e.g. a
|
|
3
|
-
* refresh command or a collapse chevron). Rows with actions switch from the
|
|
4
|
-
* classic single-button structure to a container with a main button plus the
|
|
5
|
-
* action buttons (nested interactive elements are invalid HTML); rows without
|
|
6
|
-
* actions keep the classic structure untouched.
|
|
7
|
-
*/
|
|
8
|
-
export interface SidebarEntryAction {
|
|
9
|
-
/** Stable id stamped as data-dsh-entry-action="<id>" (CSS hooks and tests). */
|
|
10
|
-
id: string;
|
|
11
|
-
/** Inline icon markup while the row is active (always, when inactiveIcon is unset). */
|
|
12
|
-
icon: string;
|
|
13
|
-
/**
|
|
14
|
-
* Optional icon while the row is inactive. Setting it makes the action a
|
|
15
|
-
* state mirror (a collapse chevron): the core swaps the icon and stamps
|
|
16
|
-
* aria-expanded on every active-state change. Requires the active bridge.
|
|
17
|
-
*/
|
|
18
|
-
inactiveIcon?: string;
|
|
19
|
-
/** Localized accessible label (aria-label + title); receives the active state. */
|
|
20
|
-
label(active: boolean): string;
|
|
21
|
-
/**
|
|
22
|
-
* Click handler; receives the button so the caller can drive transient
|
|
23
|
-
* affordances (a refresh cooldown) without a re-render.
|
|
24
|
-
*/
|
|
25
|
-
onClick(button: HTMLButtonElement): void;
|
|
26
|
-
}
|
|
27
1
|
/** Per-package configuration for one sidebar entry row. */
|
|
28
2
|
export interface SidebarEntryOptions {
|
|
29
3
|
/** Full attribute name identifying the injected row (idempotency key), e.g. 'data-dsh-ssh-entry'. */
|
|
@@ -57,12 +31,6 @@ export interface SidebarEntryOptions {
|
|
|
57
31
|
};
|
|
58
32
|
/** Click action (open/toggle the owning panel). */
|
|
59
33
|
onToggle(): void;
|
|
60
|
-
/**
|
|
61
|
-
* Optional trailing action buttons at the row's right edge. Requires the
|
|
62
|
-
* package CSS to seat entryMain / entryAction; empty keeps the classic
|
|
63
|
-
* single-button row.
|
|
64
|
-
*/
|
|
65
|
-
actions?: readonly SidebarEntryAction[];
|
|
66
34
|
/** Family-block position: 'before' inserts ahead of sibling plugin rows, 'after' behind them. */
|
|
67
35
|
position: 'before' | 'after';
|
|
68
36
|
/**
|
package/lib/types/http.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared JSON body/response helpers for the host route families: one strict
|
|
3
3
|
* bounded body reader, one lenient bounded body reader, one JSON object
|
|
4
|
-
* narrow,
|
|
4
|
+
* narrow, one JSON writer, and the outbound request-init helper that keeps a
|
|
5
|
+
* host fetch body decodable (withIdentityEncoding). Previously the readers
|
|
6
|
+
* were copy-pasted across the
|
|
5
7
|
* package route files (routes.ts, update-routes.ts, mobile-api.ts, and each
|
|
6
8
|
* family's route module) with drifting contracts: body caps ranging 4 KiB to
|
|
7
9
|
* 1 MiB and four distinct overflow behaviors (reject, undefined, null, throw).
|
|
@@ -34,6 +36,21 @@ export declare function readJsonBody(req: IncomingMessage, opts?: {
|
|
|
34
36
|
}): Promise<unknown | null>;
|
|
35
37
|
/** Narrow a value to a JSON object, or undefined when it is not one. */
|
|
36
38
|
export declare function asJsonObject(value: unknown): Record<string, unknown> | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Request init that asks a remote origin for an uncompressed body.
|
|
41
|
+
*
|
|
42
|
+
* The DSH host boots `@deepseek-ai/dsh-http-proxy`, whose top-level import of
|
|
43
|
+
* the npm `undici` copy replaces the legacy `undici.globalDispatcher.1` slot
|
|
44
|
+
* Node's built-in `fetch()` reads. That cross-major wrapper drops the
|
|
45
|
+
* `content-encoding` header and automatic decompression, so a plain fetch
|
|
46
|
+
* resolves to raw gzip/brotli/zstd bytes — compressed noise no JSON or text
|
|
47
|
+
* consumer can read. Every host fetch that parses a remote body must request
|
|
48
|
+
* identity encoding. Caller headers survive; `accept-encoding` is forced to
|
|
49
|
+
* `identity` because a caller value has no decoder behind it in this host.
|
|
50
|
+
* @param init - the caller's request init (signal, headers, method, ...).
|
|
51
|
+
* @returns a copy with `accept-encoding: identity` merged in.
|
|
52
|
+
*/
|
|
53
|
+
export declare function withIdentityEncoding(init?: RequestInit): RequestInit;
|
|
37
54
|
/**
|
|
38
55
|
* Write one JSON response. Default headers are the family defaults
|
|
39
56
|
* (content-type and referrer-policy); caller headers are appended or
|
package/lib/types/index.d.ts
CHANGED
|
@@ -8,23 +8,27 @@
|
|
|
8
8
|
* no dsh source changes.
|
|
9
9
|
*/
|
|
10
10
|
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
-
import
|
|
12
|
-
import z from 'schemastery';
|
|
11
|
+
import z from '@deepseek-ai/schemastery';
|
|
13
12
|
/** Stable cordis plugin name. */
|
|
14
13
|
export declare const name = "ssh";
|
|
15
14
|
/** Services required before the SSH surfaces can mount. */
|
|
16
15
|
export declare const inject: string[];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
declare module '@deepseek-ai/cordis' {
|
|
17
|
+
interface Events {
|
|
18
|
+
/**
|
|
19
|
+
* Volatile config values were committed into the running instance without a
|
|
20
|
+
* remount (cordis-plugin-loader); dispatched to the owning fiber only.
|
|
21
|
+
* @param paths - the changed config paths, as key arrays.
|
|
22
|
+
* @mode emit
|
|
23
|
+
*/
|
|
24
|
+
'loader/volatile-update'(paths: readonly (readonly string[])[]): void;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Plugin config as a profile patch declares it, before schema resolution. */
|
|
24
28
|
export interface Config {
|
|
25
29
|
/**
|
|
26
|
-
* When true
|
|
27
|
-
*
|
|
30
|
+
* When true, a system-prompt section announces the SSH plugin to every agent
|
|
31
|
+
* (tools + host store). Off by default to keep prompts clean.
|
|
28
32
|
*/
|
|
29
33
|
announceToAgent?: boolean;
|
|
30
34
|
/** Master switch for the plugin (routes, tools, prompt section). */
|
|
@@ -37,7 +41,36 @@ export interface Config {
|
|
|
37
41
|
*/
|
|
38
42
|
terminalFontFamily?: string;
|
|
39
43
|
}
|
|
40
|
-
|
|
44
|
+
/** The stable reference a `volatile()` config field resolves to; its owner updates it in place. */
|
|
45
|
+
interface ConfigRef<T> {
|
|
46
|
+
/** @returns the field's current value. */
|
|
47
|
+
get(): T;
|
|
48
|
+
}
|
|
49
|
+
/** One resolved config field: a live reference, or a plain value from a hand-built context. */
|
|
50
|
+
type ConfigField<T> = ConfigRef<T> | T;
|
|
51
|
+
/** The config the Host hands to {@link apply} — the runtime face of {@link Config}. */
|
|
52
|
+
export interface ResolvedConfig {
|
|
53
|
+
announceToAgent?: ConfigField<boolean>;
|
|
54
|
+
enabled?: ConfigField<boolean>;
|
|
55
|
+
terminalFontFamily?: ConfigField<string>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Plugin config schema. Under the 0.1.7 settings model this schema IS the
|
|
59
|
+
* entry's settings page: the Host derives one form per profile entry from it
|
|
60
|
+
* and serves it through the shared configuration forms. Every field is
|
|
61
|
+
* `volatile()`, which is what puts it on that page and what lets an edit reach
|
|
62
|
+
* a running instance without a remount: the loader commits the new value into
|
|
63
|
+
* the field's reference and announces `loader/volatile-update` on this fiber.
|
|
64
|
+
*/
|
|
65
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
66
|
+
announceToAgent: z<boolean, boolean, "volatile-defined">;
|
|
67
|
+
enabled: z<boolean, boolean, "volatile-defined">;
|
|
68
|
+
terminalFontFamily: z<string, string, "volatile-defined">;
|
|
69
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
70
|
+
announceToAgent: z<boolean, boolean, "volatile-defined">;
|
|
71
|
+
enabled: z<boolean, boolean, "volatile-defined">;
|
|
72
|
+
terminalFontFamily: z<string, string, "volatile-defined">;
|
|
73
|
+
}>>, "plain">;
|
|
41
74
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
42
75
|
export declare const SSH_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-ssh \u63D2\u4EF6\uFF08DSH \u8FDC\u7A0B SSH \u8FD0\u7EF4\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300CSSH\u300D\u5165\u53E3\uFF1B\u5728 dsh-web \u63D2\u4EF6\u5168\u5BB6\u6876\u4ED3\u5E93\uFF08packages/dsh-ssh\uFF09\u7EDF\u4E00\u7EF4\u62A4\u3002\u80FD\u529B\uFF1A\u4E3B\u673A\u914D\u7F6E\u5B58 $DSH_HOME/dsh-ssh.json\uFF08\u9ED8\u8BA4 ~/.dsh\uFF09\uFF08\u53EF\u4ECE ~/.ssh/config \u5BFC\u5165\uFF09\uFF1B\u6301\u4E45\u8FDE\u63A5\u6C60\u590D\u7528\u957F\u8FDE\u63A5\uFF08\u7A7A\u95F2 30 \u5206\u949F\u81EA\u52A8\u65AD\u5F00\uFF09\uFF1Bssh_list \u5217\u51FA\u4E3B\u673A\u3001ssh_exec \u6267\u884C\u8FDC\u7A0B\u547D\u4EE4\u3001ssh_upload/ssh_download \u4F20\u8F93\u6587\u4EF6\u3001ssh_tunnel \u672C\u5730\u7AEF\u53E3\u8F6C\u53D1\uFF08\u8BBF\u95EE\u8FDC\u7A0B\u6570\u636E\u5E93/\u5185\u7F51\u670D\u52A1\uFF09\u3001ssh_cluster \u6309 aliases/environment/tags \u81F3\u5C11\u4E00\u79CD\u975E\u7A7A selector \u7B5B\u9009\u540E\u96C6\u7FA4\u5E76\u53D1\u6267\u884C\uFF1B\u652F\u6301\u5BC6\u94A5/\u5BC6\u7801/ssh-agent \u8BA4\u8BC1\u3001passphrase \u5BC6\u94A5\u3001ProxyJump \u8DF3\u677F\u673A\uFF08\u522B\u540D\u6216 [user@]host[:port] \u5730\u5740\uFF09\u4E0E OpenSSH \u8BED\u4E49\u7684 ProxyCommand\uFF08\u8DF3\u8FC7\u5821\u5792\u673A\u5BA2\u6237\u7AEF\u573A\u666F\uFF0C\u53EA\u80FD\u7531\u7528\u6237\u5728 GUI \u914D\u7F6E\uFF0Cagent \u4E0D\u53EF\u5199\uFF09\uFF1BWeb \u7EC8\u7AEF\u8D70 WebSocket\u3002\u9650\u5236\uFF1A\u4E3B\u673A\u64CD\u4F5C\u7531\u7528\u6237\u5728 GUI \u4E2D\u914D\u7F6E\u540E agent \u65B9\u53EF\u4F7F\u7528\uFF1B\u5BC6\u7801\u4EE5\u660E\u6587\u5B58\u5728\u7528\u6237\u4E3B\u76EE\u5F55\u79C1\u6709\u6587\u4EF6\uFF08\u6743\u9650 0600\uFF09\uFF1B\u547D\u4EE4\u8F93\u51FA\u539F\u6837\u8FD4\u56DE\u3001\u53EF\u80FD\u542B\u654F\u611F\u4FE1\u606F\uFF1B\u65AD\u7EBF\u91CD\u8FDE\u53EF\u80FD\u91CD\u653E\u975E\u5E42\u7B49\u547D\u4EE4\uFF1B\u4F20\u8F93/\u6267\u884C\u6D88\u8017\u771F\u5B9E\u8FDC\u7A0B\u8D44\u6E90\uFF0C\u5148\u786E\u8BA4\u518D\u64CD\u4F5C\u3002\u8DEF\u5F84\u533A\u5206\uFF1A\u672C\u673A\uFF08dsh host\uFF09\u4E0A\u7684\u6587\u4EF6\u4E0E\u547D\u4EE4\u4E00\u5F8B\u7528\u672C\u5730\u5DE5\u5177\uFF08read / write / edit / bash\uFF09\uFF0Cssh_* \u5DE5\u5177\u53EA\u9488\u5BF9\u8FDC\u7A0B\u4E3B\u673A\u4E0A\u7684\u8DEF\u5F84\u3002\u7528\u6237\u63D0\u5230\u300CSSH / \u8FDC\u7A0B\u670D\u52A1\u5668 / \u670D\u52A1\u5668\u64CD\u4F5C / \u8DF3\u677F\u673A / \u96A7\u9053 / \u90E8\u7F72 / \u4E0A\u4F20\u4E0B\u8F7D\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
|
|
43
76
|
/**
|
|
@@ -46,5 +79,5 @@ export declare const SSH_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-ssh \u63
|
|
|
46
79
|
* @param config - resolved plugin config (schema defaults applied by the loader).
|
|
47
80
|
*/
|
|
48
81
|
export declare const apply: typeof applyImpl;
|
|
49
|
-
declare function applyImpl(ctx: Context, config?:
|
|
82
|
+
declare function applyImpl(ctx: Context, config?: ResolvedConfig): void;
|
|
50
83
|
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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.
|
|
4
|
+
"version": "0.4.1",
|
|
5
|
+
"icon": "icon.svg",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"engines": {
|
|
7
8
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
},
|
|
27
28
|
"dsh": {
|
|
28
29
|
"engines": {
|
|
29
|
-
"dsh": ">=0.1.
|
|
30
|
+
"dsh": ">=0.1.7-rc.1"
|
|
30
31
|
},
|
|
31
32
|
"bundle": {
|
|
32
33
|
"patch": "./cordis.patch.yml"
|
|
@@ -41,42 +42,43 @@
|
|
|
41
42
|
}
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
45
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
44
46
|
"@xterm/addon-fit": "^0.11.0",
|
|
45
47
|
"@xterm/xterm": "^6.0.0",
|
|
46
48
|
"ssh2": "^1.17.0",
|
|
47
49
|
"ws": "^8.18.0"
|
|
48
50
|
},
|
|
49
51
|
"peerDependencies": {
|
|
52
|
+
"@deepseek-ai/dsh": ">=0.1.7-rc.1",
|
|
50
53
|
"react": "^18.2.0",
|
|
51
54
|
"react-dom": "^18.2.0"
|
|
52
55
|
},
|
|
53
56
|
"devDependencies": {
|
|
54
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
55
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.
|
|
56
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
57
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.
|
|
58
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.
|
|
59
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
60
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.
|
|
61
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
62
|
-
"@deepseek-ai/dsh-scope": "^0.1.
|
|
63
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
64
|
-
"@deepseek-ai/dsh-system-prompt": "^0.1.
|
|
65
|
-
"@deepseek-ai/dsh-timeout": "^0.1.
|
|
66
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
57
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
58
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.7-rc.1",
|
|
59
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.7-rc.1",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.1",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.7-rc.1",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.1",
|
|
63
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.1",
|
|
64
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-rc.1",
|
|
65
|
+
"@deepseek-ai/dsh-scope": "^0.1.7-rc.1",
|
|
66
|
+
"@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
|
|
67
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.7-rc.1",
|
|
68
|
+
"@deepseek-ai/dsh-timeout": "^0.1.7-rc.1",
|
|
69
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-rc.1",
|
|
67
70
|
"@types/node": "^22.20.0",
|
|
68
71
|
"@types/react": "~18.3.1",
|
|
69
72
|
"@types/react-dom": "^18.3.5",
|
|
70
73
|
"@types/ssh2": "^1.15.5",
|
|
71
74
|
"@types/ws": "^8.5.13",
|
|
72
|
-
"@vitest/coverage-v8": "^
|
|
75
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
73
76
|
"jsdom": "^25.0.0",
|
|
74
77
|
"react": "^18.3.1",
|
|
75
78
|
"react-dom": "^18.3.1",
|
|
76
|
-
"schemastery": "^3.18.0",
|
|
77
79
|
"tsdown": "0.22.2",
|
|
78
80
|
"typescript": "~5.7.2",
|
|
79
|
-
"vitest": "^
|
|
81
|
+
"vitest": "^4.1.11"
|
|
80
82
|
},
|
|
81
83
|
"files": [
|
|
82
84
|
"lib/**/*.js",
|
|
@@ -85,7 +87,8 @@
|
|
|
85
87
|
"lib/**/*.d.ts.map",
|
|
86
88
|
"src",
|
|
87
89
|
"cordis.patch.yml",
|
|
88
|
-
"README.md"
|
|
90
|
+
"README.md",
|
|
91
|
+
"icon.svg"
|
|
89
92
|
],
|
|
90
93
|
"license": "Apache-2.0",
|
|
91
94
|
"repository": {
|
package/src/client/index.ts
CHANGED
|
@@ -11,10 +11,9 @@
|
|
|
11
11
|
* cordis loading needs plus types only — all value exports stay internal.
|
|
12
12
|
*/
|
|
13
13
|
import type { Context as ClientContext } from '@deepseek-ai/cordis'
|
|
14
|
-
import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
15
14
|
// Type-only: pulls the locale plugin's Context merge (ctx.locale).
|
|
16
15
|
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
17
|
-
// Type-only: pulls the
|
|
16
|
+
// Type-only: pulls the shared-forms Context merge (ctx.configForms).
|
|
18
17
|
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
19
18
|
// Type-only: pulls the ctx.slots merge (the renderer owns the slot registry since 0.1.2).
|
|
20
19
|
import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
|
|
@@ -26,31 +25,27 @@ import { mountPanel } from './mount.tsx'
|
|
|
26
25
|
import { PanelController } from './panel/controller.ts'
|
|
27
26
|
import type { TerminalFontSource } from './panel/helpers.ts'
|
|
28
27
|
import { setRuntimeTranslate } from './panel/helpers.ts'
|
|
28
|
+
import { bindSettingsReader } from './settings-binding.ts'
|
|
29
29
|
import { mountSidebarEntry } from './sidebar-entry.ts'
|
|
30
30
|
import { reportDailyHeartbeat } from './telemetry.ts'
|
|
31
31
|
|
|
32
32
|
/** Locale namespace this plugin owns. */
|
|
33
33
|
const NS = 'dsh-ssh'
|
|
34
34
|
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Family settings namespace the terminal-font preference is addressed by
|
|
37
|
+
* (issue #577); dsh-web-settings maps it to the owning profile entry id.
|
|
38
|
+
*/
|
|
36
39
|
const SETTINGS_NS = 'dsh-ssh'
|
|
37
40
|
|
|
38
|
-
/** The dsh-ssh settings
|
|
41
|
+
/** The dsh-ssh settings section the browser half reads. */
|
|
39
42
|
interface SshClientSettings {
|
|
40
43
|
/** User-configured xterm fontFamily; empty/undefined means the CSS chain. */
|
|
41
44
|
terminalFontFamily?: string
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Optional rc.6 compatibility binder provided by dsh-web-settings;
|
|
48
|
-
* absent when that group plugin is not installed, so callers fall back to
|
|
49
|
-
* the official settings scope.
|
|
50
|
-
*/
|
|
51
|
-
webUiSettings?: { bind<S>(spec: SettingsScopeSpec<S>): SettingsScope<S> }
|
|
52
|
-
}
|
|
53
|
-
}
|
|
47
|
+
/** The one field this plugin's own Config schema declares (its entry identity on the shared settings surface). */
|
|
48
|
+
const TERMINAL_FONT_FIELD = 'terminalFontFamily' satisfies keyof SshClientSettings
|
|
54
49
|
|
|
55
50
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
56
51
|
interface LocaleNamespaceMap {
|
|
@@ -60,7 +55,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
60
55
|
}
|
|
61
56
|
|
|
62
57
|
/** Required services (fiber inject waiting — the runtime must be up first). */
|
|
63
|
-
export const inject = ['slots', 'locale', '
|
|
58
|
+
export const inject = ['slots', 'locale', 'configForms']
|
|
64
59
|
|
|
65
60
|
/** Type-only surface (export discipline: no value exports beyond the plugin contract). */
|
|
66
61
|
export type { PanelControllerSnapshot } from './panel/controller.ts'
|
|
@@ -97,18 +92,19 @@ export function apply(ctx: ClientContext): void {
|
|
|
97
92
|
|
|
98
93
|
const controller = new PanelController()
|
|
99
94
|
const api = new SshApi()
|
|
100
|
-
// Live terminal-font preference (issue #577): the
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
const
|
|
95
|
+
// Live terminal-font preference (issue #577): the field lives in this
|
|
96
|
+
// plugin's own profile entry, whose settings page the Host generates from
|
|
97
|
+
// the plugin Config schema; the panel re-applies a change to open terminals
|
|
98
|
+
// without a reconnect.
|
|
99
|
+
const settings = bindSettingsReader<SshClientSettings>(ctx, SETTINGS_NS, TERMINAL_FONT_FIELD)
|
|
105
100
|
const terminalFont: TerminalFontSource = {
|
|
106
101
|
get: () => {
|
|
107
|
-
const snapshot =
|
|
102
|
+
const snapshot = settings.getSnapshot()
|
|
108
103
|
return snapshot.status === 'ready' ? snapshot.value?.terminalFontFamily : undefined
|
|
109
104
|
},
|
|
110
|
-
subscribe: (listener) =>
|
|
105
|
+
subscribe: (listener) => settings.subscribe(listener),
|
|
111
106
|
}
|
|
107
|
+
ctx.effect(() => () => { settings.dispose() }, 'dsh-ssh: settings binding')
|
|
112
108
|
const disposers: Array<() => void> = []
|
|
113
109
|
try {
|
|
114
110
|
disposers.push(mountSidebarEntry(controller, ctx.locale))
|
|
@@ -48,20 +48,27 @@ html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [class*='centerCol']
|
|
|
48
48
|
|
|
49
49
|
/* --- sidebar entry row ------------------------------------------------------- */
|
|
50
50
|
|
|
51
|
+
/* Same geometry as the shell's own panel rows (SidebarRoot .panelRow): 2px
|
|
52
|
+
inset with a 12px radius, 8px content padding, 36px row, 14/22 type, and the
|
|
53
|
+
primary ink — so an injected row and an official row line up column for
|
|
54
|
+
column instead of each carrying its own box. */
|
|
51
55
|
.entry {
|
|
52
56
|
box-sizing: border-box;
|
|
53
57
|
display: flex;
|
|
54
58
|
align-items: center;
|
|
55
59
|
gap: 8px;
|
|
56
|
-
width: 100%;
|
|
57
60
|
min-height: 36px;
|
|
58
|
-
padding:
|
|
61
|
+
padding: 7px 8px;
|
|
62
|
+
margin: 0 2px;
|
|
59
63
|
background: transparent;
|
|
60
64
|
border: none;
|
|
61
|
-
border-radius:
|
|
62
|
-
color: var(--dsw-alias-label-
|
|
65
|
+
border-radius: 12px;
|
|
66
|
+
color: var(--dsw-alias-label-primary);
|
|
63
67
|
cursor: pointer;
|
|
64
|
-
font
|
|
68
|
+
font: inherit;
|
|
69
|
+
font-size: 14px;
|
|
70
|
+
line-height: 22px;
|
|
71
|
+
text-align: left;
|
|
65
72
|
white-space: nowrap;
|
|
66
73
|
}
|
|
67
74
|
|
|
@@ -76,19 +83,21 @@ html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [class*='centerCol']
|
|
|
76
83
|
font-weight: 600;
|
|
77
84
|
}
|
|
78
85
|
|
|
86
|
+
/* Icon box matches the shell's panel glyph exactly (16px), so the glyph column
|
|
87
|
+
and the label column land on the official rows' x positions. */
|
|
79
88
|
.entryIcon {
|
|
80
89
|
display: inline-flex;
|
|
81
90
|
align-items: center;
|
|
82
91
|
justify-content: center;
|
|
83
|
-
width:
|
|
84
|
-
height:
|
|
92
|
+
width: 16px;
|
|
93
|
+
height: 16px;
|
|
85
94
|
flex: none;
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
.entryIcon svg {
|
|
89
98
|
display: block;
|
|
90
|
-
width:
|
|
91
|
-
height:
|
|
99
|
+
width: 16px;
|
|
100
|
+
height: 16px;
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
.entryLabel {
|
|
@@ -96,7 +105,9 @@ html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [class*='centerCol']
|
|
|
96
105
|
text-overflow: ellipsis;
|
|
97
106
|
}
|
|
98
107
|
|
|
99
|
-
/* Collapsed rail: icon-only, centered, matching the shell's 56px rail.
|
|
108
|
+
/* Collapsed rail: icon-only, centered, matching the shell's 56px rail. The
|
|
109
|
+
shell asks its own panel rows for an 18px glyph on the rail, so the icon box
|
|
110
|
+
and the svg step up together with the 12px rail radius. */
|
|
100
111
|
:global([data-dsh-frame][data-sidebar-collapsed]) .entry,
|
|
101
112
|
:global([data-sidebar-collapsed]) .entry {
|
|
102
113
|
justify-content: center;
|
|
@@ -104,7 +115,15 @@ html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [class*='centerCol']
|
|
|
104
115
|
width: 36px;
|
|
105
116
|
min-height: 36px;
|
|
106
117
|
margin: 0 auto 12px;
|
|
107
|
-
border-radius:
|
|
118
|
+
border-radius: 12px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
:global([data-dsh-frame][data-sidebar-collapsed]) .entryIcon,
|
|
122
|
+
:global([data-sidebar-collapsed]) .entryIcon,
|
|
123
|
+
:global([data-dsh-frame][data-sidebar-collapsed]) .entryIcon svg,
|
|
124
|
+
:global([data-sidebar-collapsed]) .entryIcon svg {
|
|
125
|
+
width: 18px;
|
|
126
|
+
height: 18px;
|
|
108
127
|
}
|
|
109
128
|
|
|
110
129
|
:global([data-dsh-frame][data-sidebar-collapsed]) .entryLabel,
|