@tiphareth/dsh-hardssh 0.1.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.
Files changed (165) hide show
  1. package/README.md +50 -0
  2. package/cordis.patch.yml +26 -0
  3. package/lib/client.js +17780 -0
  4. package/lib/environment-BL1jddfB.js +449 -0
  5. package/lib/fs.js +478 -0
  6. package/lib/index.js +6758 -0
  7. package/lib/subprocess.js +600 -0
  8. package/lib/switch-fs-CAJpFY9C.js +193 -0
  9. package/lib/switch-fs-RrZtG2gv.js +210 -0
  10. package/lib/types/backend.d.ts +108 -0
  11. package/lib/types/base/capability.d.ts +107 -0
  12. package/lib/types/base/index.d.ts +18 -0
  13. package/lib/types/base/ledger-router.d.ts +48 -0
  14. package/lib/types/base/ledger.d.ts +82 -0
  15. package/lib/types/base/model.d.ts +108 -0
  16. package/lib/types/base/namespace.d.ts +57 -0
  17. package/lib/types/base/plugin.d.ts +102 -0
  18. package/lib/types/base/registry.d.ts +60 -0
  19. package/lib/types/base/router.d.ts +40 -0
  20. package/lib/types/client/api.d.ts +85 -0
  21. package/lib/types/client/directory-flow.d.ts +82 -0
  22. package/lib/types/client/icons.d.ts +19 -0
  23. package/lib/types/client/index.d.ts +34 -0
  24. package/lib/types/client/locales.d.ts +104 -0
  25. package/lib/types/client/manager-button.d.ts +32 -0
  26. package/lib/types/client/migrate.d.ts +20 -0
  27. package/lib/types/client/ssh/api.d.ts +89 -0
  28. package/lib/types/client/ssh/apply.d.ts +23 -0
  29. package/lib/types/client/ssh/locales.d.ts +156 -0
  30. package/lib/types/client/ssh/mount.d.ts +13 -0
  31. package/lib/types/client/ssh/panel/ClusterTab.d.ts +8 -0
  32. package/lib/types/client/ssh/panel/HostFingerprintDialog.d.ts +16 -0
  33. package/lib/types/client/ssh/panel/HostFormDialog.d.ts +13 -0
  34. package/lib/types/client/ssh/panel/HostsTab.d.ts +10 -0
  35. package/lib/types/client/ssh/panel/SessionSecretDialog.d.ts +16 -0
  36. package/lib/types/client/ssh/panel/SshPanel.d.ts +14 -0
  37. package/lib/types/client/ssh/panel/TerminalTab.d.ts +12 -0
  38. package/lib/types/client/ssh/panel/TransferTab.d.ts +8 -0
  39. package/lib/types/client/ssh/panel/TunnelsTab.d.ts +8 -0
  40. package/lib/types/client/ssh/panel/controller.d.ts +23 -0
  41. package/lib/types/client/ssh/panel/helpers.d.ts +15 -0
  42. package/lib/types/client/ssh/panel/xterm.css.d.ts +3 -0
  43. package/lib/types/client/ssh/sidebar-entry.d.ts +25 -0
  44. package/lib/types/client/state.d.ts +32 -0
  45. package/lib/types/client/text.d.ts +11 -0
  46. package/lib/types/client/workspace-badges.d.ts +38 -0
  47. package/lib/types/client/workspace-gate.d.ts +15 -0
  48. package/lib/types/client-http.d.ts +15 -0
  49. package/lib/types/core.d.ts +43 -0
  50. package/lib/types/fs.d.ts +36 -0
  51. package/lib/types/host-http.d.ts +25 -0
  52. package/lib/types/index.d.ts +53 -0
  53. package/lib/types/ledger.d.ts +132 -0
  54. package/lib/types/protocol.d.ts +101 -0
  55. package/lib/types/providers/index.d.ts +19 -0
  56. package/lib/types/providers/local/provider.d.ts +58 -0
  57. package/lib/types/providers/ssh/provider.d.ts +112 -0
  58. package/lib/types/remote/environment.d.ts +29 -0
  59. package/lib/types/remote/output.d.ts +38 -0
  60. package/lib/types/remote/remote-fs.d.ts +69 -0
  61. package/lib/types/remote/remote-process.d.ts +41 -0
  62. package/lib/types/remote/remote-subprocess.d.ts +31 -0
  63. package/lib/types/remote/remote-terminal.d.ts +41 -0
  64. package/lib/types/remote-runner.d.ts +83 -0
  65. package/lib/types/remote-search.d.ts +50 -0
  66. package/lib/types/routes.d.ts +24 -0
  67. package/lib/types/runtime/workspace-core.d.ts +59 -0
  68. package/lib/types/seam-state.d.ts +69 -0
  69. package/lib/types/shell.d.ts +8 -0
  70. package/lib/types/ssh/connection/lease.d.ts +16 -0
  71. package/lib/types/ssh/connection/pool.d.ts +57 -0
  72. package/lib/types/ssh/engine.d.ts +434 -0
  73. package/lib/types/ssh/exec/output.d.ts +31 -0
  74. package/lib/types/ssh/known-hosts.d.ts +89 -0
  75. package/lib/types/ssh/plugin.d.ts +57 -0
  76. package/lib/types/ssh/protocol.d.ts +236 -0
  77. package/lib/types/ssh/routes.d.ts +44 -0
  78. package/lib/types/ssh/store.d.ts +107 -0
  79. package/lib/types/ssh/tools.d.ts +35 -0
  80. package/lib/types/ssh/transfer/progress.d.ts +14 -0
  81. package/lib/types/ssh/vault.d.ts +110 -0
  82. package/lib/types/subprocess.d.ts +32 -0
  83. package/lib/types/switch/switch-fs.d.ts +88 -0
  84. package/lib/types/switch/switch-subprocess.d.ts +34 -0
  85. package/lib/types/tools.d.ts +12 -0
  86. package/package.json +134 -0
  87. package/src/backend.ts +624 -0
  88. package/src/base/capability.ts +86 -0
  89. package/src/base/index.ts +18 -0
  90. package/src/base/ledger-router.ts +128 -0
  91. package/src/base/ledger.ts +299 -0
  92. package/src/base/model.ts +118 -0
  93. package/src/base/namespace.ts +102 -0
  94. package/src/base/plugin.ts +170 -0
  95. package/src/base/registry.ts +109 -0
  96. package/src/base/router.ts +43 -0
  97. package/src/client/api.ts +196 -0
  98. package/src/client/css-modules.d.ts +5 -0
  99. package/src/client/directory-flow.tsx +482 -0
  100. package/src/client/icons.tsx +50 -0
  101. package/src/client/index.ts +210 -0
  102. package/src/client/locales.ts +105 -0
  103. package/src/client/manager-button.tsx +269 -0
  104. package/src/client/migrate.ts +109 -0
  105. package/src/client/ssh/api.ts +411 -0
  106. package/src/client/ssh/apply.ts +50 -0
  107. package/src/client/ssh/locales.ts +322 -0
  108. package/src/client/ssh/mount.tsx +83 -0
  109. package/src/client/ssh/panel/ClusterTab.tsx +123 -0
  110. package/src/client/ssh/panel/HostFingerprintDialog.tsx +82 -0
  111. package/src/client/ssh/panel/HostFormDialog.tsx +228 -0
  112. package/src/client/ssh/panel/HostsTab.tsx +236 -0
  113. package/src/client/ssh/panel/SessionSecretDialog.tsx +80 -0
  114. package/src/client/ssh/panel/SshPanel.tsx +77 -0
  115. package/src/client/ssh/panel/TerminalTab.tsx +176 -0
  116. package/src/client/ssh/panel/TransferTab.tsx +232 -0
  117. package/src/client/ssh/panel/TunnelsTab.tsx +177 -0
  118. package/src/client/ssh/panel/controller.ts +48 -0
  119. package/src/client/ssh/panel/helpers.ts +26 -0
  120. package/src/client/ssh/panel/panel.module.css +1006 -0
  121. package/src/client/ssh/panel/xterm.css.ts +2 -0
  122. package/src/client/ssh/sidebar-entry.ts +123 -0
  123. package/src/client/state.ts +99 -0
  124. package/src/client/text.ts +22 -0
  125. package/src/client/workspace-badges.ts +96 -0
  126. package/src/client/workspace-gate.ts +225 -0
  127. package/src/client/workspace.module.css +283 -0
  128. package/src/client-http.ts +45 -0
  129. package/src/core.ts +47 -0
  130. package/src/fs.ts +85 -0
  131. package/src/host-http.ts +74 -0
  132. package/src/index.ts +244 -0
  133. package/src/ledger.ts +416 -0
  134. package/src/protocol.ts +114 -0
  135. package/src/providers/index.ts +34 -0
  136. package/src/providers/local/provider.ts +179 -0
  137. package/src/providers/ssh/provider.ts +274 -0
  138. package/src/remote/environment.ts +123 -0
  139. package/src/remote/output.ts +142 -0
  140. package/src/remote/remote-fs.ts +532 -0
  141. package/src/remote/remote-process.ts +203 -0
  142. package/src/remote/remote-subprocess.ts +159 -0
  143. package/src/remote/remote-terminal.ts +141 -0
  144. package/src/remote-runner.ts +201 -0
  145. package/src/remote-search.ts +163 -0
  146. package/src/routes.ts +395 -0
  147. package/src/runtime/workspace-core.ts +154 -0
  148. package/src/seam-state.ts +185 -0
  149. package/src/shell.ts +10 -0
  150. package/src/ssh/connection/lease.ts +17 -0
  151. package/src/ssh/connection/pool.ts +275 -0
  152. package/src/ssh/engine.ts +1761 -0
  153. package/src/ssh/exec/output.ts +70 -0
  154. package/src/ssh/known-hosts.ts +214 -0
  155. package/src/ssh/plugin.ts +184 -0
  156. package/src/ssh/protocol.ts +227 -0
  157. package/src/ssh/routes.ts +892 -0
  158. package/src/ssh/store.ts +544 -0
  159. package/src/ssh/tools.ts +402 -0
  160. package/src/ssh/transfer/progress.ts +75 -0
  161. package/src/ssh/vault.ts +477 -0
  162. package/src/subprocess.ts +71 -0
  163. package/src/switch/switch-fs.ts +253 -0
  164. package/src/switch/switch-subprocess.ts +59 -0
  165. package/src/tools.ts +221 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Generic workspace ledger — the provider-agnostic persistence and routing
3
+ * index for workspace records. It generalizes the SSH-bound ledger's proven
4
+ * mechanics (atomic write, sync anchor index, subscription) without any
5
+ * SSH-typed fields: a record is a `WorkspaceRecord` whose `provider` ref
6
+ * names the owning provider, and anchors are plain local directories.
7
+ *
8
+ * Concrete providers keep their own specialized stores (e.g. the SSH host
9
+ * store) and reference them through `provider.connectionRef`; the ledger
10
+ * itself never touches provider configs.
11
+ *
12
+ * @module @tiphareth/dsh-hardssh/base/ledger
13
+ */
14
+ import type { WorkspaceRecord } from './model.ts';
15
+ /** One committed ledger snapshot. */
16
+ export interface LedgerSnapshot {
17
+ revision: number;
18
+ records: readonly WorkspaceRecord[];
19
+ }
20
+ /** One committed mutation delivered to subscribers. */
21
+ export type LedgerChange = {
22
+ type: 'created';
23
+ revision: number;
24
+ record: WorkspaceRecord;
25
+ } | {
26
+ type: 'renamed';
27
+ revision: number;
28
+ before: WorkspaceRecord;
29
+ record: WorkspaceRecord;
30
+ } | {
31
+ type: 'removed';
32
+ revision: number;
33
+ record: WorkspaceRecord;
34
+ };
35
+ export type LedgerListener = (change: LedgerChange) => void;
36
+ /** Normalize an anchor for comparison (Windows case-insensitive; POSIX not). */
37
+ export declare function normalizeAnchorPath(path: string): string;
38
+ /** True when `candidate` equals `anchor` or is one of its descendants (lexical). */
39
+ export declare function isPathUnderAnchor(anchor: string, candidate: string): boolean;
40
+ /**
41
+ * The generic ledger. Persisted as a JSON file; mutations serialized through
42
+ * a per-instance queue with atomic rename; a synchronous anchor index is
43
+ * maintained so the switch layer can resolve a cwd without awaiting I/O.
44
+ */
45
+ export declare class WorkspaceLedger {
46
+ private readonly fileOverride?;
47
+ private readonly anchorOverride?;
48
+ private records;
49
+ private loadPromise;
50
+ private anchorIndex;
51
+ private mutationTail;
52
+ private currentRevision;
53
+ private readonly listeners;
54
+ constructor(fileOverride?: string | undefined, anchorOverride?: string | undefined);
55
+ private file;
56
+ private anchors;
57
+ private ensureLoaded;
58
+ private readRecords;
59
+ private reindex;
60
+ private save;
61
+ private enqueueMutation;
62
+ private commit;
63
+ private emit;
64
+ load(): Promise<WorkspaceRecord[]>;
65
+ list(): Promise<WorkspaceRecord[]>;
66
+ snapshot(): Promise<LedgerSnapshot>;
67
+ snapshotSync(): LedgerSnapshot;
68
+ revision(): number;
69
+ subscribe(listener: LedgerListener): () => void;
70
+ get(id: string): Promise<WorkspaceRecord | undefined>;
71
+ findByAnchor(path: string): Promise<WorkspaceRecord | undefined>;
72
+ findByAnchorSync(path: string): WorkspaceRecord | undefined;
73
+ /** Create a record: materialize the anchor (managed mode), persist, commit. */
74
+ create(input: Omit<WorkspaceRecord, 'id' | 'createdAt' | 'updatedAt'> & {
75
+ id?: string;
76
+ }): Promise<WorkspaceRecord>;
77
+ rename(id: string, title: string): Promise<WorkspaceRecord | undefined>;
78
+ /** Remove a record. The anchor directory is left in place (caller decides). */
79
+ remove(id: string): Promise<boolean>;
80
+ anchorsRoot(): string;
81
+ }
82
+ //# sourceMappingURL=ledger.d.ts.map
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The generic workspace model — the platform-neutral core of the workspace
3
+ * base. A `WorkspaceRecord` describes ONE bound workspace: a local anchor
4
+ * directory (what sessions see as their cwd) mapped to a location on some
5
+ * provider (local disk, SSH host, container, object store, …). The record
6
+ * never holds secrets — only references into a provider-managed credential
7
+ * store.
8
+ *
9
+ * Everything in this module is provider-agnostic: it imports no SSH, no
10
+ * Cordis, no React, and no `@deepseek-ai/*` runtime package.
11
+ *
12
+ * @module @tiphareth/dsh-hardssh/base/model
13
+ */
14
+ /** Opaque workspace id (uuid or provider-assigned). */
15
+ export type WorkspaceId = string;
16
+ /** A connection reference into a provider's own credential/config store. */
17
+ export interface ConnectionRef {
18
+ /** Provider-local stable id (e.g. the SSH host record id). */
19
+ id: string;
20
+ /** Optional display alias kept for humans; never used as identity. */
21
+ alias?: string;
22
+ }
23
+ /** Identifies which provider owns a workspace and how to reach its config. */
24
+ export interface WorkspaceProviderRef {
25
+ /** Provider type id: 'local' | 'ssh' | 'docker' | 'wsl' | … (registry key). */
26
+ id: string;
27
+ /** Optional multiple-config instance within one provider. */
28
+ instanceId?: string;
29
+ /** Reference into the provider's credential/config store (no secrets). */
30
+ connectionRef?: ConnectionRef;
31
+ }
32
+ /** Where a workspace lives, interpreted by its provider. */
33
+ export interface WorkspaceLocation {
34
+ /** Path kind: 'native' | 'posix' | 'uri' | 'object-prefix' | … */
35
+ kind: string;
36
+ /** Provider-specific root (e.g. /srv/repo, C:\repo, bucket/prefix). */
37
+ root: string;
38
+ /** Provider-defined options; must be JSON-serializable. No secrets. */
39
+ options?: Record<string, unknown>;
40
+ }
41
+ /** How the workspace is anchored on the local host (the session-visible dir). */
42
+ export interface WorkspaceAnchor {
43
+ /** Absolute local anchor directory path. */
44
+ path: string;
45
+ /** 'managed' = base owns a dir under its anchor root; 'existing' = user dir. */
46
+ mode: 'managed' | 'existing';
47
+ }
48
+ /** One persisted workspace record. */
49
+ export interface WorkspaceRecord {
50
+ /** Schema identifier so future migrations can be detected. */
51
+ schemaVersion: number;
52
+ id: WorkspaceId;
53
+ /** Human-readable sidebar title. */
54
+ title: string;
55
+ provider: WorkspaceProviderRef;
56
+ location: WorkspaceLocation;
57
+ anchor?: WorkspaceAnchor;
58
+ createdAt: string;
59
+ updatedAt: string;
60
+ /** Free-form labels (env, team, …). */
61
+ labels?: Record<string, string>;
62
+ /** Provider/feature extension bag. Never holds secrets. */
63
+ extensions?: Record<string, unknown>;
64
+ }
65
+ /** One provider capability key (extensible via module augmentation). */
66
+ export interface WorkspaceCapabilityMap {
67
+ 'workspace.fs': import('./capability.ts').WorkspaceFileSystem;
68
+ 'workspace.process': import('./capability.ts').WorkspaceProcessRuntime;
69
+ 'workspace.terminal': import('./capability.ts').WorkspaceTerminalService;
70
+ 'workspace.search': import('./capability.ts').WorkspaceSearchService;
71
+ }
72
+ /** A resolved, open connection to one workspace's provider. */
73
+ export interface WorkspaceConnection {
74
+ readonly workspaceId: WorkspaceId;
75
+ readonly providerId: string;
76
+ /** Fetch a capability; undefined when this provider does not offer it. */
77
+ get<K extends keyof WorkspaceCapabilityMap>(capability: K): WorkspaceCapabilityMap[K] | undefined;
78
+ /** Connection lifecycle status. */
79
+ status(): 'connecting' | 'ready' | 'degraded' | 'closed';
80
+ /** Release every resource (connections, pool leases, PTYs). Idempotent. */
81
+ close(): Promise<void>;
82
+ }
83
+ /** Context handed to a provider when opening a workspace. */
84
+ export interface WorkspaceOpenContext {
85
+ signal?: AbortSignal;
86
+ logger?: Pick<Console, 'warn' | 'error' | 'info' | 'debug'>;
87
+ }
88
+ /** The interface every workspace provider implements. */
89
+ export interface WorkspaceProvider {
90
+ /** Static identity (registry key, capabilities, display name). */
91
+ readonly manifest: WorkspaceProviderManifest;
92
+ /** Validate a record before open (e.g. root shape, ref resolvable). */
93
+ validate(record: WorkspaceRecord): void | Promise<void>;
94
+ /** Open a connection for one record. */
95
+ open(record: WorkspaceRecord, context?: WorkspaceOpenContext): Promise<WorkspaceConnection>;
96
+ }
97
+ /** Static provider identity, checked before any plugin code runs. */
98
+ export interface WorkspaceProviderManifest {
99
+ /** Provider type id (registry key; unique across the deployment). */
100
+ id: string;
101
+ version: string;
102
+ /** Compatibility gate: big-version mismatches refuse to load. */
103
+ apiVersion: number;
104
+ displayName: string;
105
+ /** Capabilities this provider implements. */
106
+ capabilities: Array<keyof WorkspaceCapabilityMap | string>;
107
+ }
108
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Workspace namespace codec — how a routable target key is encoded.
3
+ *
4
+ * New format is a URI style that carries ONLY the workspace id and the
5
+ * provider-agnostic path:
6
+ *
7
+ * wfs://<encoded-workspace-id>/<path>
8
+ *
9
+ * - The workspace id survives provider migration (ssh → container → …), so
10
+ * a key never needs re-routing when a workspace changes providers.
11
+ * - It cannot collide with Windows drive paths (`C:\…`) or bare POSIX paths.
12
+ * - It contains no provider name, so the switch layer never branches on
13
+ * "which provider" — only on "which workspace".
14
+ *
15
+ * The legacy SSH format (`ssh:<recordId>:`) is still accepted (decode only)
16
+ * so existing persisted targets keep resolving during the migration window.
17
+ *
18
+ * @module @tiphareth/dsh-hardssh/base/namespace
19
+ */
20
+ /** The URI scheme used for explicit workspace-namespaced target keys. */
21
+ export declare const WFS_SCHEME: "wfs:";
22
+ /** ASCII-encode a workspace id for safe embedding in a URL-ish key. */
23
+ export declare function encodeWorkspaceId(id: string): string;
24
+ /** Decode a workspace id back from its encoded form. */
25
+ export declare function decodeWorkspaceId(encoded: string): string;
26
+ /** One parsed route: which workspace, and the raw path inside it. */
27
+ export interface WorkspaceRoute {
28
+ workspaceId: string;
29
+ /** The path portion as it appeared (provider-relative). */
30
+ path: string;
31
+ }
32
+ /** Namespace codec contract (make the switch layer provider-agnostic). */
33
+ export interface WorkspaceNamespaceCodec {
34
+ encode(route: WorkspaceRoute): string;
35
+ decode(input: string): WorkspaceRoute | undefined;
36
+ /** True when `input` is an explicit workspace namespace (not a plain path). */
37
+ isExplicitNamespace(input: string): boolean;
38
+ }
39
+ /**
40
+ * Encode a route as `wfs://<id>/<path>`. A leading slash on the path is
41
+ * preserved after the namespace marker.
42
+ */
43
+ export declare function encodeRoute(route: WorkspaceRoute): string;
44
+ /** Decode a `wfs://<id>/<path>` key (strict). */
45
+ export declare function decodeWfsKey(input: string): WorkspaceRoute | undefined;
46
+ /**
47
+ * Decode a legacy `ssh:<recordId>:<key>` key. Kept during the migration
48
+ * window so persisted remote targets keep resolving; the record id maps to
49
+ * the workspace id 1:1 (old record ids ARE the workspace ids).
50
+ */
51
+ export declare function decodeLegacySshKey(input: string): WorkspaceRoute | undefined;
52
+ /**
53
+ * The default codec: emits `wfs://…` and accepts both `wfs://…` and legacy
54
+ * `ssh:<id>:…`.
55
+ */
56
+ export declare const defaultNamespaceCodec: WorkspaceNamespaceCodec;
57
+ //# sourceMappingURL=namespace.d.ts.map
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Plugin registration / distribution — the extension contract that lets ANY
3
+ * third-party plugin (not just SSH) contribute workspace capability.
4
+ *
5
+ * Two plugin kinds share one manifest+activate shape:
6
+ * - **Provider plugins** register `WorkspaceProvider`s (local, ssh, docker,
7
+ * wsl, cloud devbox, …) through the plugin context.
8
+ * - **Feature plugins** attach provider-agnostic features (indexing, audit
9
+ * tools, diagnostics, UI) to workspaces that expose the capabilities they
10
+ * need — without importing any provider implementation.
11
+ *
12
+ * DSH-specific surfaces (agent tools, routes, prompt sections, client
13
+ * entries) are contributed through the runtime bridge, never through this
14
+ * core contract, so `base` stays free of Cordis/React.
15
+ *
16
+ * @module @tiphareth/dsh-hardssh/base/plugin
17
+ */
18
+ import type { WorkspaceProvider, WorkspaceRecord } from './model.ts';
19
+ import type { WorkspaceRegistry } from './registry.ts';
20
+ /** A disposable registration handle. */
21
+ export interface Disposable {
22
+ dispose(): void;
23
+ }
24
+ /** Static plugin identity, readable before any plugin code executes. */
25
+ export interface WorkspacePluginManifest {
26
+ /** Stable plugin id (dotted, reverse-dns style recommended). */
27
+ id: string;
28
+ version: string;
29
+ /** Compatibility gate; aligned with the base API major. */
30
+ apiVersion: number;
31
+ displayName: string;
32
+ /** What this plugin contributes (for capability gating / UI). */
33
+ contributes?: {
34
+ providers?: string[];
35
+ features?: string[];
36
+ clientEntries?: string[];
37
+ };
38
+ }
39
+ /** Context handed to a plugin's `activate`. */
40
+ export interface WorkspacePluginContext {
41
+ /** Register one provider; returns a disposer. */
42
+ registerProvider(provider: WorkspaceProvider): Disposable;
43
+ /** Register one provider-agnostic feature. */
44
+ registerFeature(feature: WorkspaceFeature): Disposable;
45
+ /** The shared workspace registry (list / watch workspaces). */
46
+ workspaces: WorkspaceRegistry;
47
+ /** Fired when a workspace record changes (for feature lifecycle). */
48
+ onWorkspaceChange(listener: (workspace: WorkspaceRecord) => void): Disposable;
49
+ }
50
+ /** A provider-agnostic feature attached to compatible workspaces. */
51
+ export interface WorkspaceFeature {
52
+ id: string;
53
+ /** True when this feature wants the given workspace (capability gating). */
54
+ supports(input: {
55
+ workspace: WorkspaceRecord;
56
+ capabilities: ReadonlySet<string>;
57
+ }): boolean;
58
+ /** Called once per compatible workspace while the feature is active. */
59
+ activate(context: WorkspaceFeatureContext): void | Promise<void>;
60
+ deactivate?(): void | Promise<void>;
61
+ }
62
+ /** Feature context: per-workspace connection + lifecycle. */
63
+ export interface WorkspaceFeatureContext {
64
+ workspace: WorkspaceRecord;
65
+ /** Open connection; undefined only if the provider failed to open. */
66
+ connection: import('./model.ts').WorkspaceConnection | undefined;
67
+ /** Mark the feature unusable for this workspace (degrades gracefully). */
68
+ unavailable(): void;
69
+ }
70
+ /** The full plugin shape: static manifest + activate. */
71
+ export interface WorkspacePlugin {
72
+ readonly manifest: WorkspacePluginManifest;
73
+ activate(context: WorkspacePluginContext): void | Promise<void>;
74
+ deactivate?(): void | Promise<void>;
75
+ }
76
+ /**
77
+ * Registry of loaded plugins (provider + feature plugins together). Keeps
78
+ * the workspace registry and plugin lifecycle in one place.
79
+ */
80
+ export declare class WorkspacePluginHost {
81
+ private readonly registries;
82
+ private readonly plugins;
83
+ private readonly features;
84
+ /** Per-plugin disposers collected from activate (provider/feature/workspace listeners). */
85
+ private readonly pluginDisposers;
86
+ constructor(registries: {
87
+ workspaces: WorkspaceRegistry;
88
+ });
89
+ /** Load a plugin: validate manifest, run activate. Throws on failure. */
90
+ load(plugin: WorkspacePlugin): Promise<void>;
91
+ /** Unload a plugin, disposing every contribution (providers, features, listeners). */
92
+ unload(id: string): Promise<void>;
93
+ private registerProvider;
94
+ /** Active features (for a runtime to wire up per-workspace surfaces). */
95
+ featuresFor(workspace: WorkspaceRecord, capabilities: ReadonlySet<string>): WorkspaceFeature[];
96
+ }
97
+ /**
98
+ * Minimal capability set surface used by the DSH runtime to decide which
99
+ * features apply to a workspace. Computed from the provider manifest.
100
+ */
101
+ export declare function capabilitiesOf(provider: WorkspaceProvider): ReadonlySet<string>;
102
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Workspace provider registry — the single place providers register and are
3
+ * looked up by id. The registry is provider-agnostic: SSH, local, and any
4
+ * future provider all register here, and the switch layer resolves
5
+ * workspaces through the registry without knowing which provider backs them.
6
+ *
7
+ * Registration is idempotent per (id, version, apiVersion): re-registering
8
+ * the same provider is a no-op / reuse; registering a different or
9
+ * incompatible implementation with the same id is an error.
10
+ *
11
+ * @module @tiphareth/dsh-hardssh/base/registry
12
+ */
13
+ import type { WorkspaceProvider, WorkspaceProviderManifest } from './model.ts';
14
+ /** Fired when a provider registers (for surface mounts / cache invalidation). */
15
+ export type ProviderListener = (provider: WorkspaceProvider) => void;
16
+ /** Duplicate / incompatible registration error. */
17
+ export declare class ProviderRegistrationError extends Error {
18
+ constructor(message: string);
19
+ }
20
+ /** In-memory provider registry (process-wide singleton by default). */
21
+ export declare class WorkspaceProviderRegistry {
22
+ private readonly providers;
23
+ private readonly listeners;
24
+ /** Register a provider. Throws on id collision with an incompatible impl. */
25
+ register(provider: WorkspaceProvider): () => void;
26
+ /** Look up a provider by id. */
27
+ get(id: string): WorkspaceProvider | undefined;
28
+ /** Unregister a provider by id (no-op when absent or different version). */
29
+ unregister(id: string): void;
30
+ /** All registered providers. */
31
+ list(): WorkspaceProvider[];
32
+ /** Subscribe to registrations. Returns a disposer. */
33
+ subscribe(listener: ProviderListener): () => void;
34
+ /** True when a provider id is registered. */
35
+ has(id: string): boolean;
36
+ /** Static identity of a registered provider (for manifests / UI). */
37
+ manifestOf(id: string): WorkspaceProviderManifest | undefined;
38
+ }
39
+ /** The process-wide shared registry. */
40
+ export declare const globalWorkspaceRegistry: WorkspaceProviderRegistry;
41
+ /**
42
+ * Workspace registry surface used by plugins: list / watch workspace records
43
+ * and register providers. Implemented by the runtime over the ledger + the
44
+ * provider registry.
45
+ */
46
+ export interface WorkspaceRegistry {
47
+ /** All known workspace records (provider-agnostic). */
48
+ listWorkspaces(): Promise<import('./model.ts').WorkspaceRecord[]>;
49
+ /** Subscribe to workspace record changes. Returns a disposer. */
50
+ subscribe(listener: (workspace: import('./model.ts').WorkspaceRecord) => void): () => void;
51
+ /** Register a provider (idempotent same-version; throws on conflict). */
52
+ register(provider: WorkspaceProvider): void;
53
+ /** Unregister a provider by id (no-op when absent). */
54
+ unregister?(providerId: string): void;
55
+ /** Look up a provider by id. */
56
+ provider(id: string): WorkspaceProvider | undefined;
57
+ /** All registered providers. */
58
+ providers(): WorkspaceProvider[];
59
+ }
60
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Generic workspace routing — the switch-layer contracts. A `WorkspaceRouter`
3
+ * decides, for a session cwd or a namespaced target key, which workspace
4
+ * (and thus which provider connection) owns the operation. The switch
5
+ * facades (`switch-fs.ts` / `switch-subprocess.ts` in the runtime layer)
6
+ * consume this router and never branch on provider identity.
7
+ *
8
+ * @module @tiphareth/dsh-hardssh/base/router
9
+ */
10
+ import type { WorkspaceConnection } from './model.ts';
11
+ import type { WorkspaceNamespaceCodec, WorkspaceRoute } from './namespace.ts';
12
+ /** One resolved routing decision: the owning workspace connection + raw key. */
13
+ export interface WorkspaceResolution {
14
+ /** Open connection to the owning workspace (ready or degraded). */
15
+ connection: WorkspaceConnection;
16
+ /** The raw (un-namespaced) target key for the connection's backend. */
17
+ rawKey: string;
18
+ }
19
+ /**
20
+ * Route resolver contract. Implementations combine the ledger's anchor index
21
+ * (cwd → workspace) with the namespace codec (explicit key → workspace).
22
+ */
23
+ export interface WorkspaceRouter {
24
+ /** Resolve an explicit namespaced key; undefined = unrouteable (fail closed). */
25
+ fromNamespace(key: string): WorkspaceResolution | undefined;
26
+ /** Resolve a session cwd to its workspace (undefined = local / unbound). */
27
+ fromAnchor(cwd: string | undefined): WorkspaceConnection | undefined;
28
+ /** The namespace codec this router uses (for the switch facade to encode). */
29
+ codec: WorkspaceNamespaceCodec;
30
+ /** Map a route back to a resolution (used by the switch to re-encode child keys). */
31
+ resolveRoute(route: WorkspaceRoute): WorkspaceResolution | undefined;
32
+ }
33
+ /** Convenience base: default codec wiring plus helper for resolution lookup. */
34
+ export declare abstract class BaseWorkspaceRouter implements WorkspaceRouter {
35
+ abstract readonly codec: WorkspaceNamespaceCodec;
36
+ abstract fromNamespace(key: string): WorkspaceResolution | undefined;
37
+ abstract fromAnchor(cwd: string | undefined): WorkspaceConnection | undefined;
38
+ abstract resolveRoute(route: WorkspaceRoute): WorkspaceResolution | undefined;
39
+ }
40
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Browser-side API clients: the /api/dsh-hardssh route family (SSH workspace
3
+ * CRUD, remote dir browsing, workspace file ops) plus the two /api/dsh-ssh
4
+ * endpoints the config dialog needs (host create + test). Plain fetch, same
5
+ * origin 鈥?the only data path the panel components use.
6
+ */
7
+ import { HttpApiError } from '../client-http.ts';
8
+ import { type DirListing, type FileRead, type FileWriteResult, type RemoteDirEntry, type SearchView, type SshWorkspaceRecord } from '../protocol.ts';
9
+ /** Error carrying the route's JSON error message and stable code/status. */
10
+ export declare class WorkspaceApiError extends HttpApiError {
11
+ constructor(message: string, code?: string, status?: number);
12
+ }
13
+ /** The workspace route family client. */
14
+ export declare class WorkspaceApi {
15
+ /** List the SSH-bound workspaces. */
16
+ listWorkspaces(): Promise<SshWorkspaceRecord[]>;
17
+ /** Create one SSH-bound workspace. */
18
+ createWorkspace(input: {
19
+ title: string;
20
+ alias: string;
21
+ remoteRoot: string;
22
+ }): Promise<SshWorkspaceRecord>;
23
+ /** Rename one SSH-bound workspace. */
24
+ renameWorkspace(id: string, title: string): Promise<SshWorkspaceRecord>;
25
+ /** Delete one SSH-bound workspace. */
26
+ deleteWorkspace(id: string): Promise<void>;
27
+ /** List the configured SSH hosts (from the shared dsh-ssh store). */
28
+ listHosts(): Promise<Array<{
29
+ alias: string;
30
+ host: string;
31
+ port: number;
32
+ user: string;
33
+ }>>;
34
+ /** Browse a remote directory (for the workspace picker). */
35
+ listRemoteDir(alias: string, path?: string): Promise<{
36
+ path: string;
37
+ entries: RemoteDirEntry[];
38
+ }>;
39
+ list(root: string, path: string): Promise<DirListing>;
40
+ read(root: string, path: string): Promise<FileRead>;
41
+ write(root: string, path: string, content: string, expectedMtime?: number): Promise<FileWriteResult>;
42
+ search(root: string, queryText: string): Promise<SearchView>;
43
+ }
44
+ /** The two /api/dsh-ssh endpoints the config dialog needs (host create/test). */
45
+ export interface SshHostPayload {
46
+ alias?: string;
47
+ host: string;
48
+ port?: number;
49
+ user: string;
50
+ auth?: {
51
+ kind: 'key' | 'password';
52
+ keyPath?: string;
53
+ passphrase?: string;
54
+ password?: string;
55
+ };
56
+ description?: string;
57
+ }
58
+ export interface SshHostSummary {
59
+ alias: string;
60
+ host: string;
61
+ port: number;
62
+ user: string;
63
+ auth: 'key' | 'password';
64
+ keyReady: boolean;
65
+ description?: string;
66
+ createdAt: number;
67
+ updatedAt: number;
68
+ }
69
+ export interface TestResult {
70
+ ok: boolean;
71
+ latencyMs?: number;
72
+ error?: string;
73
+ }
74
+ export declare class SshHostsApi {
75
+ list(): Promise<SshHostSummary[]>;
76
+ create(payload: SshHostPayload): Promise<SshHostSummary>;
77
+ /** Update an existing host (PATCH /api/dsh-ssh/hosts?alias=鈥?. The payload
78
+ * may carry only the fields being changed; omitted auth keeps the stored
79
+ * secret. */
80
+ update(alias: string, payload: SshHostPayload): Promise<SshHostSummary>;
81
+ /** Delete a host (DELETE /api/dsh-ssh/hosts?alias=鈥?. */
82
+ remove(alias: string): Promise<void>;
83
+ test(alias: string): Promise<TestResult>;
84
+ }
85
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The directory-flow occupant replacing the native-only "Add workspace…"
3
+ * interaction. When the operator clicks the native "Add workspace…" entry,
4
+ * this component opens a small chooser with two options:
5
+ *
6
+ * - "Local workspace": delegates to the host native directory chooser
7
+ * (`host.pickDirectory`) and hands the picked path back through the owner
8
+ * conversation — exactly the original behavior.
9
+ * - "SSH workspace": opens the SSH workspace creation dialog (pick a host,
10
+ * browse a remote directory, name it). On success it calls
11
+ * `onPicked(anchorPath)` so the host adopts the LOCAL ANCHOR directory as
12
+ * a normal workspace (the seams route that workspace remote by its anchor
13
+ * path). On cancel it calls `onCancel`.
14
+ *
15
+ * Occupancy: both directory-flow holes are `kind: 'single'`, so registering
16
+ * here replaces the native-only occupant. That is intended — we restore the
17
+ * native path ourselves via `pickDirectory`.
18
+ *
19
+ * @module dsh-hardssh/client/directory-flow
20
+ */
21
+ import { type ReactElement } from 'react';
22
+ import type { DirectoryFlowOwnerProps } from '@deepseek-ai/dsh-client-ui-workspace/client';
23
+ import type { SshWorkspaceRecord } from '../protocol.ts';
24
+ /** The injected face: host native picker + our workspace manager API. */
25
+ export interface DirectoryFlowInjected {
26
+ /** Ask the local Host to open its native single-directory chooser. */
27
+ pickDirectory: () => Promise<string | null>;
28
+ /** Create an SSH workspace (host API). */
29
+ createSshWorkspace: (input: {
30
+ title: string;
31
+ alias: string;
32
+ remoteRoot: string;
33
+ }) => Promise<SshWorkspaceRecord>;
34
+ /** List configured SSH hosts (host API). */
35
+ listHosts: () => Promise<Array<{
36
+ alias: string;
37
+ host: string;
38
+ port: number;
39
+ user: string;
40
+ }>>;
41
+ /** Create a new SSH host (dsh-ssh host store; auth is required). */
42
+ createHost: (input: {
43
+ alias: string;
44
+ host: string;
45
+ port?: number;
46
+ user: string;
47
+ auth: {
48
+ kind: 'password';
49
+ password: string;
50
+ } | {
51
+ kind: 'key';
52
+ keyPath: string;
53
+ passphrase?: string;
54
+ };
55
+ }) => Promise<{
56
+ alias: string;
57
+ host: string;
58
+ port: number;
59
+ user: string;
60
+ }>;
61
+ /** Browse a remote directory (host API). */
62
+ listRemoteDir: (alias: string, path?: string) => Promise<{
63
+ path: string;
64
+ entries: Array<{
65
+ name: string;
66
+ type: 'dir' | 'file' | 'other';
67
+ size: number;
68
+ mtimeMs: number;
69
+ }>;
70
+ }>;
71
+ /** Interactive SSH connection gate: host-key TOFU + session-password
72
+ * dialogs. Resolves true when the alias is connected (or the user
73
+ * cancelled the prompts). Used to retry browsing. */
74
+ ensureConnected: (alias: string) => Promise<boolean>;
75
+ }
76
+ /**
77
+ * The flow occupant. Each rising `open` edge shows the menu; the ref arms
78
+ * once per open so re-renders (and an adoption keeping `open` true while
79
+ * `busy`) never launch a second flow.
80
+ */
81
+ export declare function DirectoryFlow(props: DirectoryFlowOwnerProps & DirectoryFlowInjected): ReactElement | null;
82
+ //# sourceMappingURL=directory-flow.d.ts.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Workspace icons for the SSH workspace surfaces: a cloud with a return arrow
3
+ * (SSH/remote workspace) and a desktop monitor (local workspace). Inlined SVG
4
+ * (1024 viewBox, solid fill) — no icon library dependency. Both inherit the
5
+ * theme text color via `currentColor` so they stay legible in light/dark
6
+ * themes.
7
+ * @module dsh-hardssh/client/icons
8
+ */
9
+ import type { ReactElement } from 'react';
10
+ interface IconProps {
11
+ size?: number;
12
+ className?: string;
13
+ }
14
+ /** Desktop-monitor icon: a local workstation (monitor + stand). */
15
+ export declare function ComputerIcon({ size, className }: IconProps): ReactElement;
16
+ /** Cloud-with-return-arrow icon: a remote/SSH workspace. */
17
+ export declare function CloudIcon({ size, className }: IconProps): ReactElement;
18
+ export {};
19
+ //# sourceMappingURL=icons.d.ts.map