@vibesdotdev/terminal-client 0.0.0-stage → 1.0.0-rc.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/LICENSE +18 -0
- package/README.md +102 -2
- package/dist/ansi/index.d.ts +11 -0
- package/dist/ansi/index.d.ts.map +1 -0
- package/dist/ansi/index.js +11 -0
- package/dist/ansi/index.js.map +1 -0
- package/dist/ansi/keymap.d.ts +77 -0
- package/dist/ansi/keymap.d.ts.map +1 -0
- package/dist/ansi/keymap.js +257 -0
- package/dist/ansi/keymap.js.map +1 -0
- package/dist/api-client-plugin.d.ts +4 -0
- package/dist/api-client-plugin.d.ts.map +1 -0
- package/dist/api-client-plugin.js +16 -0
- package/dist/api-client-plugin.js.map +1 -0
- package/dist/client/http-session.d.ts +44 -0
- package/dist/client/http-session.d.ts.map +1 -0
- package/dist/client/http-session.js +457 -0
- package/dist/client/http-session.js.map +1 -0
- package/dist/client/index.d.ts +8 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +8 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client.d.ts +56 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +169 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/pty-bridge/index.d.ts +15 -0
- package/dist/pty-bridge/index.d.ts.map +1 -0
- package/dist/pty-bridge/index.js +15 -0
- package/dist/pty-bridge/index.js.map +1 -0
- package/dist/pty-bridge/protocol.d.ts +6 -0
- package/dist/pty-bridge/protocol.d.ts.map +1 -0
- package/dist/pty-bridge/protocol.js +6 -0
- package/dist/pty-bridge/protocol.js.map +1 -0
- package/dist/pty-bridge/server.d.ts +7 -0
- package/dist/pty-bridge/server.d.ts.map +1 -0
- package/dist/pty-bridge/server.js +9 -0
- package/dist/pty-bridge/server.js.map +1 -0
- package/dist/remote-session.d.ts +51 -0
- package/dist/remote-session.d.ts.map +1 -0
- package/dist/remote-session.js +326 -0
- package/dist/remote-session.js.map +1 -0
- package/dist/schemas/http.d.ts +61 -0
- package/dist/schemas/http.d.ts.map +1 -0
- package/dist/schemas/http.js +45 -0
- package/dist/schemas/http.js.map +1 -0
- package/dist/schemas/remote.d.ts +25 -0
- package/dist/schemas/remote.d.ts.map +1 -0
- package/dist/schemas/remote.js +23 -0
- package/dist/schemas/remote.js.map +1 -0
- package/dist/schemas/session.d.ts +26 -0
- package/dist/schemas/session.d.ts.map +1 -0
- package/dist/schemas/session.js +17 -0
- package/dist/schemas/session.js.map +1 -0
- package/dist/schemas/store.d.ts +29 -0
- package/dist/schemas/store.d.ts.map +1 -0
- package/dist/schemas/store.js +22 -0
- package/dist/schemas/store.js.map +1 -0
- package/dist/schemas/terminal.d.ts +296 -0
- package/dist/schemas/terminal.d.ts.map +1 -0
- package/dist/schemas/terminal.js +243 -0
- package/dist/schemas/terminal.js.map +1 -0
- package/dist/server/index.d.ts +9 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +9 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/session-store.d.ts +176 -0
- package/dist/server/session-store.d.ts.map +1 -0
- package/dist/server/session-store.js +462 -0
- package/dist/server/session-store.js.map +1 -0
- package/dist/server/sse-stream.d.ts +32 -0
- package/dist/server/sse-stream.d.ts.map +1 -0
- package/dist/server/sse-stream.js +87 -0
- package/dist/server/sse-stream.js.map +1 -0
- package/dist/svelte/HostPicker.svelte +263 -0
- package/dist/svelte/HostPicker.svelte.d.ts +36 -0
- package/dist/svelte/HostPicker.svelte.d.ts.map +1 -0
- package/dist/svelte/README.md +75 -0
- package/dist/svelte/ReconnectBanner.svelte +88 -0
- package/dist/svelte/ReconnectBanner.svelte.d.ts +16 -0
- package/dist/svelte/ReconnectBanner.svelte.d.ts.map +1 -0
- package/dist/svelte/SessionTabs.svelte +192 -0
- package/dist/svelte/SessionTabs.svelte.d.ts +17 -0
- package/dist/svelte/SessionTabs.svelte.d.ts.map +1 -0
- package/dist/svelte/Terminal.svelte +567 -0
- package/dist/svelte/Terminal.svelte.d.ts +25 -0
- package/dist/svelte/Terminal.svelte.d.ts.map +1 -0
- package/dist/svelte/TerminalHostWorkspace.svelte +264 -0
- package/dist/svelte/TerminalHostWorkspace.svelte.d.ts +59 -0
- package/dist/svelte/TerminalHostWorkspace.svelte.d.ts.map +1 -0
- package/dist/svelte/TerminalMobileToolbar.svelte +56 -0
- package/dist/svelte/TerminalMobileToolbar.svelte.d.ts +14 -0
- package/dist/svelte/TerminalMobileToolbar.svelte.d.ts.map +1 -0
- package/dist/svelte/TerminalWorkspace.svelte +562 -0
- package/dist/svelte/TerminalWorkspace.svelte.d.ts +25 -0
- package/dist/svelte/TerminalWorkspace.svelte.d.ts.map +1 -0
- package/dist/svelte/ambient.d.ts +9 -0
- package/dist/svelte/docs/terminal-svelte.docs.descriptor.d.ts +4 -0
- package/dist/svelte/docs/terminal-svelte.docs.descriptor.d.ts.map +1 -0
- package/dist/svelte/docs/terminal-svelte.docs.descriptor.js +74 -0
- package/dist/svelte/index.d.ts +29 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +28 -0
- package/dist/svelte/styles.css +8 -0
- package/dist/svelte/terminal-host-workspace.d.ts +152 -0
- package/dist/svelte/terminal-host-workspace.d.ts.map +1 -0
- package/dist/svelte/terminal-host-workspace.js +335 -0
- package/dist/svelte/terminal-session-authority.d.ts +17 -0
- package/dist/svelte/terminal-session-authority.d.ts.map +1 -0
- package/dist/svelte/terminal-session-authority.js +25 -0
- package/dist/svelte/terminal-svelte.plugin.d.ts +21 -0
- package/dist/svelte/terminal-svelte.plugin.d.ts.map +1 -0
- package/dist/svelte/terminal-svelte.plugin.js +53 -0
- package/dist/svelte/terminal-theme.d.ts +11 -0
- package/dist/svelte/terminal-theme.d.ts.map +1 -0
- package/dist/svelte/terminal-theme.js +116 -0
- package/dist/svelte/tool-widgets/TerminalToolCard.svelte +65 -0
- package/dist/svelte/tool-widgets/TerminalToolCard.svelte.d.ts +5 -0
- package/dist/svelte/tool-widgets/TerminalToolCard.svelte.d.ts.map +1 -0
- package/dist/svelte/tool-widgets/terminal-tool-card.d.ts +25 -0
- package/dist/svelte/tool-widgets/terminal-tool-card.d.ts.map +1 -0
- package/dist/svelte/tool-widgets/terminal-tool-card.js +116 -0
- package/dist/svelte/types.d.ts +160 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +51 -0
- package/dist/svelte/workspace.d.ts +3 -0
- package/dist/svelte/workspace.d.ts.map +1 -0
- package/dist/svelte/workspace.js +2 -0
- package/dist/terminal-client.plugin.d.ts +3 -0
- package/dist/terminal-client.plugin.d.ts.map +1 -0
- package/dist/terminal-client.plugin.js +3 -0
- package/dist/terminal-client.plugin.js.map +1 -0
- package/dist/terminal-runtime.d.ts +30 -0
- package/dist/terminal-runtime.d.ts.map +1 -0
- package/dist/terminal-runtime.js +673 -0
- package/dist/terminal-runtime.js.map +1 -0
- package/dist/terminal.consumer.plugin.d.ts +14 -0
- package/dist/terminal.consumer.plugin.d.ts.map +1 -0
- package/dist/terminal.consumer.plugin.js +31 -0
- package/dist/terminal.consumer.plugin.js.map +1 -0
- package/dist/tools/terminal.impl.consumer.d.ts +11 -0
- package/dist/tools/terminal.impl.consumer.d.ts.map +1 -0
- package/dist/tools/terminal.impl.consumer.js +20 -0
- package/dist/tools/terminal.impl.consumer.js.map +1 -0
- package/dist/tools/terminal.schemas.d.ts +3 -0
- package/dist/tools/terminal.schemas.d.ts.map +1 -0
- package/dist/tools/terminal.schemas.js +3 -0
- package/dist/tools/terminal.schemas.js.map +1 -0
- package/dist/tools/terminal.unified.descriptor.d.ts +218 -0
- package/dist/tools/terminal.unified.descriptor.d.ts.map +1 -0
- package/dist/tools/terminal.unified.descriptor.js +36 -0
- package/dist/tools/terminal.unified.descriptor.js.map +1 -0
- package/dist/tui/index.d.ts +9 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +8 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/terminal-open.descriptor.d.ts +9 -0
- package/dist/tui/terminal-open.descriptor.d.ts.map +1 -0
- package/dist/tui/terminal-open.descriptor.js +18 -0
- package/dist/tui/terminal-open.descriptor.js.map +1 -0
- package/dist/tui/terminal-open.impl.consumer.d.ts +9 -0
- package/dist/tui/terminal-open.impl.consumer.d.ts.map +1 -0
- package/dist/tui/terminal-open.impl.consumer.js +13 -0
- package/dist/tui/terminal-open.impl.consumer.js.map +1 -0
- package/dist/tui/terminal-registry.descriptor.d.ts +11 -0
- package/dist/tui/terminal-registry.descriptor.d.ts.map +1 -0
- package/dist/tui/terminal-registry.descriptor.js +17 -0
- package/dist/tui/terminal-registry.descriptor.js.map +1 -0
- package/dist/tui/terminal-registry.impl.consumer.d.ts +71 -0
- package/dist/tui/terminal-registry.impl.consumer.d.ts.map +1 -0
- package/dist/tui/terminal-registry.impl.consumer.js +190 -0
- package/dist/tui/terminal-registry.impl.consumer.js.map +1 -0
- package/dist/tui/terminal-tui.plugin.d.ts +10 -0
- package/dist/tui/terminal-tui.plugin.d.ts.map +1 -0
- package/dist/tui/terminal-tui.plugin.js +32 -0
- package/dist/tui/terminal-tui.plugin.js.map +1 -0
- package/dist/tui/terminal.descriptor.d.ts +10 -0
- package/dist/tui/terminal.descriptor.d.ts.map +1 -0
- package/dist/tui/terminal.descriptor.js +25 -0
- package/dist/tui/terminal.descriptor.js.map +1 -0
- package/dist/tui/terminal.impl.consumer.d.ts +14 -0
- package/dist/tui/terminal.impl.consumer.d.ts.map +1 -0
- package/dist/tui/terminal.impl.consumer.js +189 -0
- package/dist/tui/terminal.impl.consumer.js.map +1 -0
- package/dist/tui/terminal.state-key.d.ts +30 -0
- package/dist/tui/terminal.state-key.d.ts.map +1 -0
- package/dist/tui/terminal.state-key.js +9 -0
- package/dist/tui/terminal.state-key.js.map +1 -0
- package/dist/tui/terminal.state.d.ts +152 -0
- package/dist/tui/terminal.state.d.ts.map +1 -0
- package/dist/tui/terminal.state.js +149 -0
- package/dist/tui/terminal.state.js.map +1 -0
- package/dist/workspace.d.ts +155 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +292 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +228 -4
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* <TerminalHostWorkspace> — the embeddable terminal experience.
|
|
4
|
+
*
|
|
5
|
+
* Host picker, session tabs, reconnect banner and the terminal itself, as
|
|
6
|
+
* one component that fills whatever container it is given: a full page, a
|
|
7
|
+
* dialog, or a side panel. The host app supplies the reachable hosts and a
|
|
8
|
+
* session opener (dependency-injected transport); the workspace owns every
|
|
9
|
+
* piece of terminal chrome so each surface looks and behaves the same.
|
|
10
|
+
*
|
|
11
|
+
* State lives in the rune-free `TerminalHostWorkspaceController`
|
|
12
|
+
* (`./terminal-host-workspace.ts`); this file renders its snapshot.
|
|
13
|
+
*
|
|
14
|
+
* Styling is kit theme tokens only (`bg-background`, `border-border`, …), so
|
|
15
|
+
* the workspace matches the surrounding app shell and theme.
|
|
16
|
+
*/
|
|
17
|
+
import { onDestroy, untrack, type Snippet } from 'svelte';
|
|
18
|
+
import { Button } from '@vibesdotdev/kit/components/ui/button';
|
|
19
|
+
import { Alert, AlertDescription } from '@vibesdotdev/kit/components/ui/alert';
|
|
20
|
+
import { cn } from '@vibesdotdev/kit/utils';
|
|
21
|
+
import Terminal from './Terminal.svelte';
|
|
22
|
+
import HostPicker from './HostPicker.svelte';
|
|
23
|
+
import SessionTabs from './SessionTabs.svelte';
|
|
24
|
+
import ReconnectBanner from './ReconnectBanner.svelte';
|
|
25
|
+
import {
|
|
26
|
+
TerminalHostWorkspaceController,
|
|
27
|
+
terminalHostTabBanner,
|
|
28
|
+
type TerminalHostErrorDescriber,
|
|
29
|
+
type TerminalHostSessionOpener,
|
|
30
|
+
type TerminalHostWorkspaceLayout
|
|
31
|
+
} from './terminal-host-workspace.js';
|
|
32
|
+
import type { HostOption, TabSession, TerminalTheme } from './types.js';
|
|
33
|
+
|
|
34
|
+
interface Props {
|
|
35
|
+
/** Reachable hosts, in display order. */
|
|
36
|
+
hosts: HostOption[];
|
|
37
|
+
/** Opens a session on a host. The workspace never owns a transport. */
|
|
38
|
+
openSession: TerminalHostSessionOpener;
|
|
39
|
+
/** Deep-linked host id; an unknown id shows a "not reachable" notice and the picker. */
|
|
40
|
+
initialHostId?: string;
|
|
41
|
+
/** Deep-linked command: pre-filled in the picker, never run without an explicit click. */
|
|
42
|
+
initialCommand?: string;
|
|
43
|
+
/** Open on the initial host straight away (no picker) when no command is pre-filled. */
|
|
44
|
+
autoOpen?: boolean;
|
|
45
|
+
/** Observe mode for every session: output renders, keystrokes never reach the host. */
|
|
46
|
+
readonly?: boolean;
|
|
47
|
+
/** Density only — the workspace always fills its container. */
|
|
48
|
+
layout?: TerminalHostWorkspaceLayout;
|
|
49
|
+
/** Placeholder in the picker's command field. Blank opens the host's default shell. */
|
|
50
|
+
defaultCommand?: string;
|
|
51
|
+
/** Where "Add a machine" goes in the empty picker. */
|
|
52
|
+
addMachineHref?: string;
|
|
53
|
+
/** Host discovery failure copy; shown above the workspace. */
|
|
54
|
+
hostsError?: string;
|
|
55
|
+
hostsRefreshing?: boolean;
|
|
56
|
+
/** Retry host discovery. Shows a retry button next to `hostsError`. */
|
|
57
|
+
onRetryHosts?: () => void;
|
|
58
|
+
/** Replaces the picker when `hosts` is empty (e.g. a sign-in prompt). */
|
|
59
|
+
empty?: Snippet;
|
|
60
|
+
/** Extra toolbar actions (rendered at the end of the tab strip). */
|
|
61
|
+
actions?: Snippet;
|
|
62
|
+
/** Lock the grid (both required). */
|
|
63
|
+
cols?: number;
|
|
64
|
+
rows?: number;
|
|
65
|
+
theme?: TerminalTheme;
|
|
66
|
+
describeError?: TerminalHostErrorDescriber;
|
|
67
|
+
/** Accessible name of the workspace region. */
|
|
68
|
+
label?: string;
|
|
69
|
+
class?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let {
|
|
73
|
+
hosts,
|
|
74
|
+
openSession,
|
|
75
|
+
initialHostId,
|
|
76
|
+
initialCommand,
|
|
77
|
+
autoOpen = false,
|
|
78
|
+
readonly = false,
|
|
79
|
+
layout = 'page',
|
|
80
|
+
defaultCommand = 'your shell',
|
|
81
|
+
addMachineHref,
|
|
82
|
+
hostsError = '',
|
|
83
|
+
hostsRefreshing = false,
|
|
84
|
+
onRetryHosts,
|
|
85
|
+
empty,
|
|
86
|
+
actions,
|
|
87
|
+
cols,
|
|
88
|
+
rows,
|
|
89
|
+
theme,
|
|
90
|
+
describeError,
|
|
91
|
+
label = 'Terminal',
|
|
92
|
+
class: className = ''
|
|
93
|
+
}: Props = $props();
|
|
94
|
+
|
|
95
|
+
const controller = untrack(
|
|
96
|
+
() =>
|
|
97
|
+
new TerminalHostWorkspaceController({
|
|
98
|
+
openSession: (request) => openSession(request),
|
|
99
|
+
readonly,
|
|
100
|
+
describeError: describeError ? (failure, host) => describeError(failure, host) : undefined
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
let view = $state(controller.snapshot);
|
|
104
|
+
const unsubscribe = controller.subscribe((next) => {
|
|
105
|
+
view = next;
|
|
106
|
+
});
|
|
107
|
+
untrack(() => controller.start({ hosts, initialHostId, initialCommand, autoOpen }));
|
|
108
|
+
$effect(() => controller.setHosts(hosts));
|
|
109
|
+
onDestroy(() => {
|
|
110
|
+
unsubscribe();
|
|
111
|
+
controller.dispose();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const lockedGrid = $derived(
|
|
115
|
+
cols !== undefined && rows !== undefined && cols > 0 && rows > 0 ? { cols, rows } : null
|
|
116
|
+
);
|
|
117
|
+
const activeTab = $derived(view.tabs.find((tab) => tab.key === view.activeKey));
|
|
118
|
+
const banner = $derived(terminalHostTabBanner(activeTab));
|
|
119
|
+
const stripTabs = $derived<TabSession[]>(
|
|
120
|
+
view.tabs.map((tab) => ({
|
|
121
|
+
id: tab.key,
|
|
122
|
+
command: tab.command ?? 'shell',
|
|
123
|
+
hostLabel: tab.hostLabel,
|
|
124
|
+
startedAt: 0,
|
|
125
|
+
detached: tab.status === 'reconnecting' || tab.status === 'failed',
|
|
126
|
+
exited: tab.status === 'exited'
|
|
127
|
+
}))
|
|
128
|
+
);
|
|
129
|
+
const lastGrid = new Map<string, { cols: number; rows: number }>();
|
|
130
|
+
const padding = $derived(layout === 'page' ? 'p-2 sm:p-3' : layout === 'dialog' ? 'p-2' : 'p-1');
|
|
131
|
+
|
|
132
|
+
function ready(key: string, dims: { cols: number; rows: number }): void {
|
|
133
|
+
const grid = lockedGrid ?? dims;
|
|
134
|
+
lastGrid.set(key, grid);
|
|
135
|
+
controller.terminalReady(key, grid);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function startOver(): void {
|
|
139
|
+
if (activeTab?.status === 'failed' && activeTab.session === null) {
|
|
140
|
+
controller.retry(activeTab.key, lastGrid.get(activeTab.key) ?? lockedGrid ?? { cols: 80, rows: 24 });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
controller.showPicker();
|
|
144
|
+
}
|
|
145
|
+
</script>
|
|
146
|
+
|
|
147
|
+
<section
|
|
148
|
+
class={cn('flex h-full min-h-0 w-full min-w-0 flex-col overflow-hidden bg-background text-foreground', className)}
|
|
149
|
+
aria-label={label}
|
|
150
|
+
data-terminal-host-workspace
|
|
151
|
+
data-layout={layout}
|
|
152
|
+
data-picker-open={view.pickerOpen ? 'true' : 'false'}
|
|
153
|
+
data-active-status={activeTab?.status ?? 'none'}
|
|
154
|
+
data-banner-status={banner.status ?? 'none'}
|
|
155
|
+
>
|
|
156
|
+
{#if view.tabs.length > 0 || actions}
|
|
157
|
+
<header class="flex shrink-0 items-stretch border-b border-border">
|
|
158
|
+
{#if view.tabs.length > 0}
|
|
159
|
+
<div class="-mb-px min-w-0 flex-1">
|
|
160
|
+
<SessionTabs
|
|
161
|
+
tabs={stripTabs}
|
|
162
|
+
activeId={view.activeKey}
|
|
163
|
+
onSelect={(key) => controller.select(key)}
|
|
164
|
+
onClose={(key) => controller.close(key)}
|
|
165
|
+
onNewTab={() => controller.showPicker()}
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
{:else}
|
|
169
|
+
<div class="flex-1"></div>
|
|
170
|
+
{/if}
|
|
171
|
+
{#if actions}
|
|
172
|
+
<div class="flex shrink-0 items-center gap-2 px-2 py-1">{@render actions()}</div>
|
|
173
|
+
{/if}
|
|
174
|
+
</header>
|
|
175
|
+
{/if}
|
|
176
|
+
|
|
177
|
+
{#if hostsError}
|
|
178
|
+
<Alert class="shrink-0 rounded-none border-x-0 border-t-0" data-host-discovery-error>
|
|
179
|
+
<AlertDescription class="flex flex-wrap items-center justify-between gap-2">
|
|
180
|
+
<span>{hostsError}</span>
|
|
181
|
+
{#if onRetryHosts}
|
|
182
|
+
<Button variant="outline" size="sm" onclick={onRetryHosts} disabled={hostsRefreshing}>
|
|
183
|
+
{hostsRefreshing ? 'Refreshing…' : 'Retry'}
|
|
184
|
+
</Button>
|
|
185
|
+
{/if}
|
|
186
|
+
</AlertDescription>
|
|
187
|
+
</Alert>
|
|
188
|
+
{/if}
|
|
189
|
+
|
|
190
|
+
<div class="relative min-h-0 min-w-0 flex-1">
|
|
191
|
+
{#each view.tabs as tab (tab.key)}
|
|
192
|
+
{@const active = tab.key === view.activeKey}
|
|
193
|
+
<div
|
|
194
|
+
class={cn('absolute inset-0 flex min-h-0 flex-col', padding, !active && 'invisible')}
|
|
195
|
+
aria-hidden={!active}
|
|
196
|
+
data-terminal-tab={tab.key}
|
|
197
|
+
data-terminal-tab-status={tab.status}
|
|
198
|
+
data-terminal-tab-host={tab.hostId}
|
|
199
|
+
>
|
|
200
|
+
{#if tab.readonly}
|
|
201
|
+
<p class="mb-2 shrink-0 rounded-md border border-warning/40 bg-warning/10 px-3 py-1.5 text-xs text-warning" role="status">
|
|
202
|
+
Observe mode — input is disabled.
|
|
203
|
+
</p>
|
|
204
|
+
{/if}
|
|
205
|
+
<div class="min-h-0 flex-1">
|
|
206
|
+
<Terminal
|
|
207
|
+
session={tab.session}
|
|
208
|
+
{theme}
|
|
209
|
+
cols={lockedGrid?.cols}
|
|
210
|
+
rows={lockedGrid?.rows}
|
|
211
|
+
connectionState={tab.session ? 'live' : 'connecting'}
|
|
212
|
+
readonly={tab.readonly || tab.status !== 'live' || !tab.inputReady}
|
|
213
|
+
onReady={(dims) => ready(tab.key, dims)}
|
|
214
|
+
onInputReady={() => controller.inputReady(tab.key)}
|
|
215
|
+
onExit={(result) => controller.exited(tab.key, result.exitCode)}
|
|
216
|
+
onDisconnect={(reason) => controller.disconnected(tab.key, reason)}
|
|
217
|
+
/>
|
|
218
|
+
</div>
|
|
219
|
+
{#if active}
|
|
220
|
+
<ReconnectBanner
|
|
221
|
+
status={banner.status}
|
|
222
|
+
attempt={banner.attempt}
|
|
223
|
+
delayMs={banner.delayMs}
|
|
224
|
+
reason={banner.reason}
|
|
225
|
+
onNewSession={startOver}
|
|
226
|
+
/>
|
|
227
|
+
{/if}
|
|
228
|
+
</div>
|
|
229
|
+
{/each}
|
|
230
|
+
|
|
231
|
+
{#if view.pickerOpen}
|
|
232
|
+
<div
|
|
233
|
+
class={cn(
|
|
234
|
+
'absolute inset-0 z-10 flex items-center justify-center overflow-auto',
|
|
235
|
+
layout === 'panel' ? 'p-2' : 'p-4',
|
|
236
|
+
view.tabs.length > 0 && 'bg-background/80 backdrop-blur-sm'
|
|
237
|
+
)}
|
|
238
|
+
data-terminal-picker
|
|
239
|
+
>
|
|
240
|
+
<div class="flex w-full max-w-lg flex-col gap-3">
|
|
241
|
+
{#if view.requestedHostUnavailable}
|
|
242
|
+
<Alert variant="destructive" data-requested-host-unavailable>
|
|
243
|
+
<AlertDescription>The requested machine is not reachable. Pick another machine below.</AlertDescription>
|
|
244
|
+
</Alert>
|
|
245
|
+
{/if}
|
|
246
|
+
{#if hosts.length === 0 && empty}
|
|
247
|
+
{@render empty()}
|
|
248
|
+
{:else}
|
|
249
|
+
<HostPicker
|
|
250
|
+
{hosts}
|
|
251
|
+
preselectedHostId={view.pickerHostId}
|
|
252
|
+
initialCommand={view.pickerCommand}
|
|
253
|
+
{defaultCommand}
|
|
254
|
+
{addMachineHref}
|
|
255
|
+
allowDefaultShell
|
|
256
|
+
onOpen={(hostId, command) => controller.open(hostId, command)}
|
|
257
|
+
onCancel={view.tabs.length > 0 ? () => controller.hidePicker() : undefined}
|
|
258
|
+
/>
|
|
259
|
+
{/if}
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
{/if}
|
|
263
|
+
</div>
|
|
264
|
+
</section>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <TerminalHostWorkspace> — the embeddable terminal experience.
|
|
3
|
+
*
|
|
4
|
+
* Host picker, session tabs, reconnect banner and the terminal itself, as
|
|
5
|
+
* one component that fills whatever container it is given: a full page, a
|
|
6
|
+
* dialog, or a side panel. The host app supplies the reachable hosts and a
|
|
7
|
+
* session opener (dependency-injected transport); the workspace owns every
|
|
8
|
+
* piece of terminal chrome so each surface looks and behaves the same.
|
|
9
|
+
*
|
|
10
|
+
* State lives in the rune-free `TerminalHostWorkspaceController`
|
|
11
|
+
* (`./terminal-host-workspace.ts`); this file renders its snapshot.
|
|
12
|
+
*
|
|
13
|
+
* Styling is kit theme tokens only (`bg-background`, `border-border`, …), so
|
|
14
|
+
* the workspace matches the surrounding app shell and theme.
|
|
15
|
+
*/
|
|
16
|
+
import { type Snippet } from 'svelte';
|
|
17
|
+
import { type TerminalHostErrorDescriber, type TerminalHostSessionOpener, type TerminalHostWorkspaceLayout } from './terminal-host-workspace.ts';
|
|
18
|
+
import type { HostOption, TerminalTheme } from './types.ts';
|
|
19
|
+
interface Props {
|
|
20
|
+
/** Reachable hosts, in display order. */
|
|
21
|
+
hosts: HostOption[];
|
|
22
|
+
/** Opens a session on a host. The workspace never owns a transport. */
|
|
23
|
+
openSession: TerminalHostSessionOpener;
|
|
24
|
+
/** Deep-linked host id; an unknown id shows a "not reachable" notice and the picker. */
|
|
25
|
+
initialHostId?: string;
|
|
26
|
+
/** Deep-linked command: pre-filled in the picker, never run without an explicit click. */
|
|
27
|
+
initialCommand?: string;
|
|
28
|
+
/** Open on the initial host straight away (no picker) when no command is pre-filled. */
|
|
29
|
+
autoOpen?: boolean;
|
|
30
|
+
/** Observe mode for every session: output renders, keystrokes never reach the host. */
|
|
31
|
+
readonly?: boolean;
|
|
32
|
+
/** Density only — the workspace always fills its container. */
|
|
33
|
+
layout?: TerminalHostWorkspaceLayout;
|
|
34
|
+
/** Placeholder in the picker's command field. Blank opens the host's default shell. */
|
|
35
|
+
defaultCommand?: string;
|
|
36
|
+
/** Where "Add a machine" goes in the empty picker. */
|
|
37
|
+
addMachineHref?: string;
|
|
38
|
+
/** Host discovery failure copy; shown above the workspace. */
|
|
39
|
+
hostsError?: string;
|
|
40
|
+
hostsRefreshing?: boolean;
|
|
41
|
+
/** Retry host discovery. Shows a retry button next to `hostsError`. */
|
|
42
|
+
onRetryHosts?: () => void;
|
|
43
|
+
/** Replaces the picker when `hosts` is empty (e.g. a sign-in prompt). */
|
|
44
|
+
empty?: Snippet;
|
|
45
|
+
/** Extra toolbar actions (rendered at the end of the tab strip). */
|
|
46
|
+
actions?: Snippet;
|
|
47
|
+
/** Lock the grid (both required). */
|
|
48
|
+
cols?: number;
|
|
49
|
+
rows?: number;
|
|
50
|
+
theme?: TerminalTheme;
|
|
51
|
+
describeError?: TerminalHostErrorDescriber;
|
|
52
|
+
/** Accessible name of the workspace region. */
|
|
53
|
+
label?: string;
|
|
54
|
+
class?: string;
|
|
55
|
+
}
|
|
56
|
+
declare const TerminalHostWorkspace: import("svelte").Component<Props, {}, "">;
|
|
57
|
+
type TerminalHostWorkspace = ReturnType<typeof TerminalHostWorkspace>;
|
|
58
|
+
export default TerminalHostWorkspace;
|
|
59
|
+
//# sourceMappingURL=TerminalHostWorkspace.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TerminalHostWorkspace.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/TerminalHostWorkspace.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;OAcI;AACJ,OAAO,EAAsB,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAQ1D,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,MAAM,8BAA8B,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAc,aAAa,EAAE,MAAM,YAAY,CAAC;AAGvE,UAAU,KAAK;IACd,yCAAyC;IACzC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,uEAAuE;IACvE,WAAW,EAAE,yBAAyB,CAAC;IACvC,wFAAwF;IACxF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,2BAA2B,CAAC;IACrC,uFAAuF;IACvF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,yEAAyE;IACzE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,aAAa,CAAC,EAAE,0BAA0B,CAAC;IAC3C,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAkKF,QAAA,MAAM,qBAAqB,2CAAwC,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from '@vibesdotdev/kit/components/ui/button';
|
|
3
|
+
import { resolveSequence, type KeyChord } from '../ansi/keymap.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
onInput?: (sequence: string) => void;
|
|
7
|
+
session?: { write: (data: string | Uint8Array) => Promise<void> | void } | null;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
ctrlActive?: boolean;
|
|
10
|
+
altActive?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
let { onInput, session = null, disabled = false, class: className = '',
|
|
14
|
+
ctrlActive = $bindable(false), altActive = $bindable(false)
|
|
15
|
+
}: Props = $props();
|
|
16
|
+
const keys = [
|
|
17
|
+
{ key: 'Escape', label: 'Escape', text: 'Esc' },
|
|
18
|
+
{ key: 'Tab', label: 'Tab', text: 'Tab' },
|
|
19
|
+
{ key: 'c', ctrl: true, label: 'Interrupt command', text: '^C' },
|
|
20
|
+
{ key: 'd', ctrl: true, label: 'End input', text: '^D' },
|
|
21
|
+
{ key: '|', label: 'Pipe', text: '|' },
|
|
22
|
+
{ key: '\\', label: 'Backslash', text: '\\' },
|
|
23
|
+
{ key: '~', label: 'Tilde', text: '~' },
|
|
24
|
+
{ key: 'ArrowUp', label: 'Up Arrow', text: '↑' },
|
|
25
|
+
{ key: 'ArrowDown', label: 'Down Arrow', text: '↓' },
|
|
26
|
+
{ key: 'ArrowLeft', label: 'Left Arrow', text: '←' },
|
|
27
|
+
{ key: 'ArrowRight', label: 'Right Arrow', text: '→' },
|
|
28
|
+
{ key: 'PageUp', label: 'Page Up', text: 'PgUp' },
|
|
29
|
+
{ key: 'PageDown', label: 'Page Down', text: 'PgDn' }
|
|
30
|
+
];
|
|
31
|
+
function emit(chord: KeyChord) {
|
|
32
|
+
if (disabled) return;
|
|
33
|
+
const sequence = resolveSequence({ ...chord, ctrl: chord.ctrl ?? ctrlActive, alt: chord.alt ?? altActive });
|
|
34
|
+
ctrlActive = false;
|
|
35
|
+
altActive = false;
|
|
36
|
+
if (onInput) onInput(sequence);
|
|
37
|
+
else if (session) void session.write(sequence);
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<div
|
|
42
|
+
class="flex shrink-0 items-center gap-1 overflow-x-auto border-t bg-background p-1.5 font-mono text-xs text-foreground touch-manipulation {className}"
|
|
43
|
+
role="toolbar"
|
|
44
|
+
aria-label="Terminal touch accessory keyboard"
|
|
45
|
+
>
|
|
46
|
+
<Button type="button" variant={ctrlActive ? 'secondary' : 'ghost'} size="sm"
|
|
47
|
+
class="min-h-11 shrink-0" {disabled} aria-pressed={ctrlActive} aria-label="Control key"
|
|
48
|
+
onpointerdown={event => event.preventDefault()} onclick={() => ctrlActive = !ctrlActive}>Ctrl</Button>
|
|
49
|
+
<Button type="button" variant={altActive ? 'secondary' : 'ghost'} size="sm"
|
|
50
|
+
class="min-h-11 shrink-0" {disabled} aria-pressed={altActive} aria-label="Alt key"
|
|
51
|
+
onpointerdown={event => event.preventDefault()} onclick={() => altActive = !altActive}>Alt</Button>
|
|
52
|
+
{#each keys as key (key.label)}
|
|
53
|
+
<Button type="button" variant="ghost" size="sm" class="min-h-11 shrink-0" {disabled}
|
|
54
|
+
aria-label={key.label} onpointerdown={event => event.preventDefault()} onclick={() => emit(key)}>{key.text}</Button>
|
|
55
|
+
{/each}
|
|
56
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
onInput?: (sequence: string) => void;
|
|
3
|
+
session?: {
|
|
4
|
+
write: (data: string | Uint8Array) => Promise<void> | void;
|
|
5
|
+
} | null;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
ctrlActive?: boolean;
|
|
8
|
+
altActive?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const TerminalMobileToolbar: import("svelte").Component<Props, {}, "ctrlActive" | "altActive">;
|
|
12
|
+
type TerminalMobileToolbar = ReturnType<typeof TerminalMobileToolbar>;
|
|
13
|
+
export default TerminalMobileToolbar;
|
|
14
|
+
//# sourceMappingURL=TerminalMobileToolbar.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TerminalMobileToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/TerminalMobileToolbar.svelte.ts"],"names":[],"mappings":"AAOC,UAAU,KAAK;IACd,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AA2CF,QAAA,MAAM,qBAAqB,mEAAwC,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
|