@vibesdotdev/terminal-client 0.0.0-stage → 1.0.0-rc.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 (195) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +102 -2
  3. package/dist/ansi/index.d.ts +11 -0
  4. package/dist/ansi/index.d.ts.map +1 -0
  5. package/dist/ansi/index.js +11 -0
  6. package/dist/ansi/index.js.map +1 -0
  7. package/dist/ansi/keymap.d.ts +77 -0
  8. package/dist/ansi/keymap.d.ts.map +1 -0
  9. package/dist/ansi/keymap.js +257 -0
  10. package/dist/ansi/keymap.js.map +1 -0
  11. package/dist/api-client-plugin.d.ts +4 -0
  12. package/dist/api-client-plugin.d.ts.map +1 -0
  13. package/dist/api-client-plugin.js +16 -0
  14. package/dist/api-client-plugin.js.map +1 -0
  15. package/dist/client/http-session.d.ts +44 -0
  16. package/dist/client/http-session.d.ts.map +1 -0
  17. package/dist/client/http-session.js +457 -0
  18. package/dist/client/http-session.js.map +1 -0
  19. package/dist/client/index.d.ts +8 -0
  20. package/dist/client/index.d.ts.map +1 -0
  21. package/dist/client/index.js +8 -0
  22. package/dist/client/index.js.map +1 -0
  23. package/dist/client.d.ts +56 -0
  24. package/dist/client.d.ts.map +1 -0
  25. package/dist/client.js +169 -0
  26. package/dist/client.js.map +1 -0
  27. package/dist/index.d.ts +14 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +14 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/pty-bridge/index.d.ts +15 -0
  32. package/dist/pty-bridge/index.d.ts.map +1 -0
  33. package/dist/pty-bridge/index.js +15 -0
  34. package/dist/pty-bridge/index.js.map +1 -0
  35. package/dist/pty-bridge/protocol.d.ts +6 -0
  36. package/dist/pty-bridge/protocol.d.ts.map +1 -0
  37. package/dist/pty-bridge/protocol.js +6 -0
  38. package/dist/pty-bridge/protocol.js.map +1 -0
  39. package/dist/pty-bridge/server.d.ts +7 -0
  40. package/dist/pty-bridge/server.d.ts.map +1 -0
  41. package/dist/pty-bridge/server.js +9 -0
  42. package/dist/pty-bridge/server.js.map +1 -0
  43. package/dist/remote-session.d.ts +51 -0
  44. package/dist/remote-session.d.ts.map +1 -0
  45. package/dist/remote-session.js +326 -0
  46. package/dist/remote-session.js.map +1 -0
  47. package/dist/schemas/http.d.ts +61 -0
  48. package/dist/schemas/http.d.ts.map +1 -0
  49. package/dist/schemas/http.js +45 -0
  50. package/dist/schemas/http.js.map +1 -0
  51. package/dist/schemas/remote.d.ts +25 -0
  52. package/dist/schemas/remote.d.ts.map +1 -0
  53. package/dist/schemas/remote.js +23 -0
  54. package/dist/schemas/remote.js.map +1 -0
  55. package/dist/schemas/session.d.ts +26 -0
  56. package/dist/schemas/session.d.ts.map +1 -0
  57. package/dist/schemas/session.js +17 -0
  58. package/dist/schemas/session.js.map +1 -0
  59. package/dist/schemas/store.d.ts +29 -0
  60. package/dist/schemas/store.d.ts.map +1 -0
  61. package/dist/schemas/store.js +22 -0
  62. package/dist/schemas/store.js.map +1 -0
  63. package/dist/schemas/terminal.d.ts +296 -0
  64. package/dist/schemas/terminal.d.ts.map +1 -0
  65. package/dist/schemas/terminal.js +243 -0
  66. package/dist/schemas/terminal.js.map +1 -0
  67. package/dist/server/index.d.ts +9 -0
  68. package/dist/server/index.d.ts.map +1 -0
  69. package/dist/server/index.js +9 -0
  70. package/dist/server/index.js.map +1 -0
  71. package/dist/server/session-store.d.ts +176 -0
  72. package/dist/server/session-store.d.ts.map +1 -0
  73. package/dist/server/session-store.js +462 -0
  74. package/dist/server/session-store.js.map +1 -0
  75. package/dist/server/sse-stream.d.ts +32 -0
  76. package/dist/server/sse-stream.d.ts.map +1 -0
  77. package/dist/server/sse-stream.js +87 -0
  78. package/dist/server/sse-stream.js.map +1 -0
  79. package/dist/svelte/HostPicker.svelte +256 -0
  80. package/dist/svelte/HostPicker.svelte.d.ts +30 -0
  81. package/dist/svelte/HostPicker.svelte.d.ts.map +1 -0
  82. package/dist/svelte/README.md +52 -0
  83. package/dist/svelte/ReconnectBanner.svelte +88 -0
  84. package/dist/svelte/ReconnectBanner.svelte.d.ts +16 -0
  85. package/dist/svelte/ReconnectBanner.svelte.d.ts.map +1 -0
  86. package/dist/svelte/SessionTabs.svelte +192 -0
  87. package/dist/svelte/SessionTabs.svelte.d.ts +17 -0
  88. package/dist/svelte/SessionTabs.svelte.d.ts.map +1 -0
  89. package/dist/svelte/Terminal.svelte +567 -0
  90. package/dist/svelte/Terminal.svelte.d.ts +25 -0
  91. package/dist/svelte/Terminal.svelte.d.ts.map +1 -0
  92. package/dist/svelte/TerminalMobileToolbar.svelte +56 -0
  93. package/dist/svelte/TerminalMobileToolbar.svelte.d.ts +14 -0
  94. package/dist/svelte/TerminalMobileToolbar.svelte.d.ts.map +1 -0
  95. package/dist/svelte/TerminalWorkspace.svelte +562 -0
  96. package/dist/svelte/TerminalWorkspace.svelte.d.ts +25 -0
  97. package/dist/svelte/TerminalWorkspace.svelte.d.ts.map +1 -0
  98. package/dist/svelte/ambient.d.ts +9 -0
  99. package/dist/svelte/docs/terminal-svelte.docs.descriptor.d.ts +4 -0
  100. package/dist/svelte/docs/terminal-svelte.docs.descriptor.d.ts.map +1 -0
  101. package/dist/svelte/docs/terminal-svelte.docs.descriptor.js +74 -0
  102. package/dist/svelte/index.d.ts +27 -0
  103. package/dist/svelte/index.d.ts.map +1 -0
  104. package/dist/svelte/index.js +26 -0
  105. package/dist/svelte/styles.css +8 -0
  106. package/dist/svelte/terminal-session-authority.d.ts +17 -0
  107. package/dist/svelte/terminal-session-authority.d.ts.map +1 -0
  108. package/dist/svelte/terminal-session-authority.js +25 -0
  109. package/dist/svelte/terminal-svelte.plugin.d.ts +19 -0
  110. package/dist/svelte/terminal-svelte.plugin.d.ts.map +1 -0
  111. package/dist/svelte/terminal-svelte.plugin.js +42 -0
  112. package/dist/svelte/terminal-theme.d.ts +11 -0
  113. package/dist/svelte/terminal-theme.d.ts.map +1 -0
  114. package/dist/svelte/terminal-theme.js +116 -0
  115. package/dist/svelte/tool-widgets/TerminalToolCard.svelte +65 -0
  116. package/dist/svelte/tool-widgets/TerminalToolCard.svelte.d.ts +5 -0
  117. package/dist/svelte/tool-widgets/TerminalToolCard.svelte.d.ts.map +1 -0
  118. package/dist/svelte/tool-widgets/terminal-tool-card.d.ts +25 -0
  119. package/dist/svelte/tool-widgets/terminal-tool-card.d.ts.map +1 -0
  120. package/dist/svelte/tool-widgets/terminal-tool-card.js +116 -0
  121. package/dist/svelte/types.d.ts +160 -0
  122. package/dist/svelte/types.d.ts.map +1 -0
  123. package/dist/svelte/types.js +51 -0
  124. package/dist/svelte/workspace.d.ts +3 -0
  125. package/dist/svelte/workspace.d.ts.map +1 -0
  126. package/dist/svelte/workspace.js +2 -0
  127. package/dist/terminal-client.plugin.d.ts +3 -0
  128. package/dist/terminal-client.plugin.d.ts.map +1 -0
  129. package/dist/terminal-client.plugin.js +3 -0
  130. package/dist/terminal-client.plugin.js.map +1 -0
  131. package/dist/terminal-runtime.d.ts +30 -0
  132. package/dist/terminal-runtime.d.ts.map +1 -0
  133. package/dist/terminal-runtime.js +673 -0
  134. package/dist/terminal-runtime.js.map +1 -0
  135. package/dist/terminal.consumer.plugin.d.ts +14 -0
  136. package/dist/terminal.consumer.plugin.d.ts.map +1 -0
  137. package/dist/terminal.consumer.plugin.js +31 -0
  138. package/dist/terminal.consumer.plugin.js.map +1 -0
  139. package/dist/tools/terminal.impl.consumer.d.ts +11 -0
  140. package/dist/tools/terminal.impl.consumer.d.ts.map +1 -0
  141. package/dist/tools/terminal.impl.consumer.js +20 -0
  142. package/dist/tools/terminal.impl.consumer.js.map +1 -0
  143. package/dist/tools/terminal.schemas.d.ts +3 -0
  144. package/dist/tools/terminal.schemas.d.ts.map +1 -0
  145. package/dist/tools/terminal.schemas.js +3 -0
  146. package/dist/tools/terminal.schemas.js.map +1 -0
  147. package/dist/tools/terminal.unified.descriptor.d.ts +218 -0
  148. package/dist/tools/terminal.unified.descriptor.d.ts.map +1 -0
  149. package/dist/tools/terminal.unified.descriptor.js +36 -0
  150. package/dist/tools/terminal.unified.descriptor.js.map +1 -0
  151. package/dist/tui/index.d.ts +9 -0
  152. package/dist/tui/index.d.ts.map +1 -0
  153. package/dist/tui/index.js +8 -0
  154. package/dist/tui/index.js.map +1 -0
  155. package/dist/tui/terminal-open.descriptor.d.ts +9 -0
  156. package/dist/tui/terminal-open.descriptor.d.ts.map +1 -0
  157. package/dist/tui/terminal-open.descriptor.js +18 -0
  158. package/dist/tui/terminal-open.descriptor.js.map +1 -0
  159. package/dist/tui/terminal-open.impl.consumer.d.ts +9 -0
  160. package/dist/tui/terminal-open.impl.consumer.d.ts.map +1 -0
  161. package/dist/tui/terminal-open.impl.consumer.js +13 -0
  162. package/dist/tui/terminal-open.impl.consumer.js.map +1 -0
  163. package/dist/tui/terminal-registry.descriptor.d.ts +11 -0
  164. package/dist/tui/terminal-registry.descriptor.d.ts.map +1 -0
  165. package/dist/tui/terminal-registry.descriptor.js +17 -0
  166. package/dist/tui/terminal-registry.descriptor.js.map +1 -0
  167. package/dist/tui/terminal-registry.impl.consumer.d.ts +71 -0
  168. package/dist/tui/terminal-registry.impl.consumer.d.ts.map +1 -0
  169. package/dist/tui/terminal-registry.impl.consumer.js +190 -0
  170. package/dist/tui/terminal-registry.impl.consumer.js.map +1 -0
  171. package/dist/tui/terminal-tui.plugin.d.ts +10 -0
  172. package/dist/tui/terminal-tui.plugin.d.ts.map +1 -0
  173. package/dist/tui/terminal-tui.plugin.js +32 -0
  174. package/dist/tui/terminal-tui.plugin.js.map +1 -0
  175. package/dist/tui/terminal.descriptor.d.ts +10 -0
  176. package/dist/tui/terminal.descriptor.d.ts.map +1 -0
  177. package/dist/tui/terminal.descriptor.js +25 -0
  178. package/dist/tui/terminal.descriptor.js.map +1 -0
  179. package/dist/tui/terminal.impl.consumer.d.ts +14 -0
  180. package/dist/tui/terminal.impl.consumer.d.ts.map +1 -0
  181. package/dist/tui/terminal.impl.consumer.js +189 -0
  182. package/dist/tui/terminal.impl.consumer.js.map +1 -0
  183. package/dist/tui/terminal.state-key.d.ts +30 -0
  184. package/dist/tui/terminal.state-key.d.ts.map +1 -0
  185. package/dist/tui/terminal.state-key.js +9 -0
  186. package/dist/tui/terminal.state-key.js.map +1 -0
  187. package/dist/tui/terminal.state.d.ts +152 -0
  188. package/dist/tui/terminal.state.d.ts.map +1 -0
  189. package/dist/tui/terminal.state.js +149 -0
  190. package/dist/tui/terminal.state.js.map +1 -0
  191. package/dist/workspace.d.ts +155 -0
  192. package/dist/workspace.d.ts.map +1 -0
  193. package/dist/workspace.js +292 -0
  194. package/dist/workspace.js.map +1 -0
  195. package/package.json +228 -4
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @vibesdotdev/terminal-client — SSE stream builder for terminal output.
3
+ *
4
+ * Builds the `ReadableStream<Uint8Array>` body for `GET /api/session/{id}/output`.
5
+ * Used by both hq-web (remote PTY) and consumer-hq (local PTY): the wire format is
6
+ * identical regardless of what backs the session.
7
+ *
8
+ * Wire format:
9
+ * event: data\ndata: <base64>\n\n — a chunk of PTY output
10
+ * event: exit\ndata: {"exitCode":N|null}\n\n — session exited (final event)
11
+ *
12
+ * SERVER-ONLY.
13
+ */
14
+ import { base64ToBytes, bytesToBase64 } from '../pty-bridge/protocol.ts';
15
+ import type { StoredSession } from './session-store.ts';
16
+ export declare const toBase64: typeof bytesToBase64;
17
+ export declare const fromBase64: typeof base64ToBytes;
18
+ /**
19
+ * Build an SSE `ReadableStream` for a session's PTY output.
20
+ *
21
+ * Replays any backlog that arrived before this stream attached (so a
22
+ * slightly-late EventSource connection sees the shell prompt), then streams
23
+ * live output. A final `exit` event closes the stream.
24
+ */
25
+ export declare function buildSseStream(stored: StoredSession): ReadableStream<Uint8Array>;
26
+ /** Standard SSE response headers. */
27
+ export declare const SSE_HEADERS: {
28
+ 'content-type': string;
29
+ 'cache-control': string;
30
+ connection: string;
31
+ };
32
+ //# sourceMappingURL=sse-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-stream.d.ts","sourceRoot":"","sources":["../../src/server/sse-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EACN,aAAa,EACb,aAAa,EACb,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMxD,eAAO,MAAM,QAAQ,sBAAgB,CAAC;AACtC,eAAO,MAAM,UAAU,sBAAgB,CAAC;AAExC;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,cAAc,CAAC,UAAU,CAAC,CAwDhF;AAED,qCAAqC;AACrC,eAAO,MAAM,WAAW;;;;CAIU,CAAC"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @vibesdotdev/terminal-client — SSE stream builder for terminal output.
3
+ *
4
+ * Builds the `ReadableStream<Uint8Array>` body for `GET /api/session/{id}/output`.
5
+ * Used by both hq-web (remote PTY) and consumer-hq (local PTY): the wire format is
6
+ * identical regardless of what backs the session.
7
+ *
8
+ * Wire format:
9
+ * event: data\ndata: <base64>\n\n — a chunk of PTY output
10
+ * event: exit\ndata: {"exitCode":N|null}\n\n — session exited (final event)
11
+ *
12
+ * SERVER-ONLY.
13
+ */
14
+ import { CapabilityError, CaughtFailureSchema } from '@vibesdotdev/kernel';
15
+ import { base64ToBytes, bytesToBase64 } from "../pty-bridge/protocol.js";
16
+ // ---------------------------------------------------------------------------
17
+ // Base64 helpers (shared with the client adapter in ../client/http-session.ts)
18
+ // ---------------------------------------------------------------------------
19
+ export const toBase64 = bytesToBase64;
20
+ export const fromBase64 = base64ToBytes;
21
+ /**
22
+ * Build an SSE `ReadableStream` for a session's PTY output.
23
+ *
24
+ * Replays any backlog that arrived before this stream attached (so a
25
+ * slightly-late EventSource connection sees the shell prompt), then streams
26
+ * live output. A final `exit` event closes the stream.
27
+ */
28
+ export function buildSseStream(stored) {
29
+ const encoder = new TextEncoder();
30
+ let closed = false;
31
+ let unsubscribe = () => { };
32
+ return new ReadableStream({
33
+ start(controller) {
34
+ const send = (eventName, data) => {
35
+ if (closed)
36
+ return;
37
+ controller.enqueue(encoder.encode(`event: ${eventName}\ndata: ${data}\n\n`));
38
+ };
39
+ // Flush headers immediately, including for a quiet PTY with no backlog.
40
+ controller.enqueue(encoder.encode(': connected\n\n'));
41
+ // Replay backlog captured before this stream attached.
42
+ for (const chunk of stored.backlog)
43
+ send('data', toBase64(chunk));
44
+ unsubscribe = stored.session.onOutput((bytes) => {
45
+ send('data', toBase64(bytes));
46
+ });
47
+ const finish = (exitCode) => {
48
+ if (closed)
49
+ return;
50
+ send('exit', JSON.stringify({ exitCode }));
51
+ closed = true;
52
+ unsubscribe();
53
+ controller.close();
54
+ };
55
+ const fail = (failure) => {
56
+ if (closed)
57
+ return;
58
+ send('failure', JSON.stringify(failure.toEnvelope().error));
59
+ closed = true;
60
+ unsubscribe();
61
+ controller.close();
62
+ };
63
+ void stored.session.exited()
64
+ .then(({ exitCode }) => ({ kind: 'exit', exitCode }), (cause) => ({
65
+ kind: 'failure',
66
+ failure: CapabilityError.from(CaughtFailureSchema.parse(cause))
67
+ }))
68
+ .then((outcome) => {
69
+ if (outcome.kind === 'exit')
70
+ finish(outcome.exitCode);
71
+ else
72
+ fail(outcome.failure);
73
+ });
74
+ },
75
+ cancel() {
76
+ closed = true;
77
+ unsubscribe();
78
+ }
79
+ });
80
+ }
81
+ /** Standard SSE response headers. */
82
+ export const SSE_HEADERS = {
83
+ 'content-type': 'text/event-stream',
84
+ 'cache-control': 'no-cache, no-transform',
85
+ connection: 'keep-alive'
86
+ };
87
+ //# sourceMappingURL=sse-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-stream.js","sourceRoot":"","sources":["../../src/server/sse-stream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,256 @@
1
+ <script lang="ts">
2
+ /**
3
+ * <HostPicker> — host + shell picker for the terminal surface.
4
+ *
5
+ * Presents the list of reachable hosts (consumer: "This machine" always first;
6
+ * cloud: fleet hosts). The user picks a host and an optional command, then
7
+ * clicks "Open". Deep-linked ?host= preselects a host by id.
8
+ *
9
+ * When no hosts are available the empty state explains why and offers a
10
+ * link to add a machine — it is never a dead end.
11
+ *
12
+ * Keyboard-first: focus opens the host list; Enter/Space selects; Tab moves
13
+ * between host, command, and Open; Escape closes.
14
+ */
15
+
16
+ import { Button } from '@vibesdotdev/kit/components/ui/button';
17
+ import { Input } from '@vibesdotdev/kit/components/ui/input';
18
+ import { cn } from '@vibesdotdev/kit/utils';
19
+ import type { HostOption } from './types.js';
20
+
21
+ interface Props {
22
+ /**
23
+ * Ordered list of available hosts. The first entry with `local: true` is
24
+ * "This machine" in consumer-hq; cloud hosts follow in alphabetical order.
25
+ */
26
+ hosts: HostOption[];
27
+ /** Pre-selected host id (from ?host= deep-link). */
28
+ preselectedHostId?: string;
29
+ /** Shown in the command field as placeholder when unset. */
30
+ defaultCommand?: string;
31
+ /**
32
+ * Pre-fill the command input with this value VERBATIM (from a ?command=
33
+ * deep-link). The field stays fully editable and is NEVER auto-run — the
34
+ * user must still click "Open terminal". Empty/undefined leaves the field
35
+ * blank (placeholder = defaultCommand). Non-breaking: omitting it preserves
36
+ * the prior empty-field behavior.
37
+ */
38
+ initialCommand?: string;
39
+ /** Href of the add-machine flow (shown when hosts array is empty). */
40
+ addMachineHref?: string;
41
+ /** Called when the user confirms their selection. */
42
+ onOpen: (hostId: string, command: string) => void;
43
+ /** Called when the user presses Escape or dismisses. */
44
+ onCancel?: () => void;
45
+ }
46
+
47
+ let {
48
+ hosts,
49
+ preselectedHostId,
50
+ defaultCommand = 'sh',
51
+ initialCommand = '',
52
+ addMachineHref = '/fleet',
53
+ onOpen,
54
+ onCancel
55
+ }: Props = $props();
56
+
57
+ // Use a derived to compute the default selection from props, then allow the
58
+ // user to override it with a local $state. This avoids the Svelte 5 warning
59
+ // about $state referencing props directly in the initializer.
60
+ const _defaultSelectedId = $derived(
61
+ preselectedHostId && hosts.some((h) => h.id === preselectedHostId)
62
+ ? preselectedHostId
63
+ : (hosts[0]?.id ?? '')
64
+ );
65
+ let _manualSelection = $state<string | null>(null);
66
+ const selectedHostId = $derived(_manualSelection ?? _defaultSelectedId);
67
+ // Pre-fill verbatim from initialCommand; the field stays editable and is never
68
+ // auto-run (the user must click Open). The closure marks this as an
69
+ // intentional one-time capture for Svelte's rune analyzer.
70
+ const initialCommandValue = (() => initialCommand)();
71
+ let command = $state(initialCommandValue);
72
+ let commandEl = $state<HTMLInputElement | null>(null);
73
+
74
+ const selectedHost = $derived(hosts.find((h) => h.id === selectedHostId) ?? null);
75
+ const resolvedCommand = $derived(command.trim() || defaultCommand);
76
+ const canOpen = $derived(selectedHost !== null && resolvedCommand.length > 0);
77
+
78
+ function handleKeydown(e: KeyboardEvent) {
79
+ if (e.key === 'Escape') {
80
+ e.preventDefault();
81
+ onCancel?.();
82
+ }
83
+ }
84
+
85
+ function handleOpen() {
86
+ if (!canOpen) return;
87
+ onOpen(selectedHostId, resolvedCommand);
88
+ }
89
+
90
+ function selectHost(id: string) {
91
+ _manualSelection = id;
92
+ // Move focus to command field after host selection so the user can
93
+ // proceed without touching the mouse.
94
+ commandEl?.focus();
95
+ }
96
+
97
+ function hostLabel(host: HostOption): string {
98
+ if (host.local) return `This machine — ${host.name}`;
99
+ return host.name;
100
+ }
101
+
102
+ function hostOptionId(hostId: string): string {
103
+ return `host-opt-${encodeURIComponent(hostId)}`;
104
+ }
105
+
106
+ function hostStatusLabel(host: HostOption): string {
107
+ if (host.status === 'online') return 'Online';
108
+ if (host.status === 'degraded') return 'Limited connection';
109
+ if (host.status === 'offline') return 'Offline';
110
+ return 'Status unknown';
111
+ }
112
+
113
+ function hostClass(host: HostOption): string {
114
+ return cn(
115
+ 'flex cursor-pointer items-center gap-2.5 rounded-md border border-transparent px-3 py-2.5 outline-none transition-colors hover:border-border hover:bg-accent/60 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50',
116
+ host.id === selectedHostId && 'border-primary bg-primary/10 text-foreground',
117
+ host.status === 'offline' && 'opacity-60'
118
+ );
119
+ }
120
+
121
+ function statusDotClass(status: HostOption['status']): string {
122
+ return cn(
123
+ 'size-2 shrink-0 rounded-full bg-muted-foreground',
124
+ status === 'online' && 'bg-success',
125
+ status === 'degraded' && 'bg-warning',
126
+ status === 'offline' && 'bg-muted-foreground/70'
127
+ );
128
+ }
129
+ </script>
130
+
131
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
132
+ <div
133
+ class="w-full max-w-lg rounded-md border border-border bg-card p-5 text-sm text-card-foreground outline-none"
134
+ role="dialog"
135
+ aria-modal="true"
136
+ aria-label="Open a terminal session"
137
+ tabindex="-1"
138
+ onkeydown={handleKeydown}
139
+ >
140
+ <div class="mb-4 flex items-center justify-between gap-3">
141
+ <h2 class="m-0 text-base font-semibold text-card-foreground">Open a terminal</h2>
142
+ {#if onCancel}
143
+ <Button
144
+ variant="ghost"
145
+ size="icon-sm"
146
+ class="size-6 text-muted-foreground hover:text-foreground"
147
+ aria-label="Cancel"
148
+ onclick={onCancel}
149
+ >×</Button>
150
+ {/if}
151
+ </div>
152
+
153
+ {#if hosts.length === 0}
154
+ <!-- Empty state: never a dead end — offer the add-machine flow -->
155
+ <div class="flex flex-col items-center gap-2 py-6 text-center">
156
+ <div class="text-muted-foreground" aria-hidden="true">
157
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
158
+ <rect x="2" y="3" width="20" height="14" rx="2"/>
159
+ <path d="M8 21h8M12 17v4"/>
160
+
161
+ </svg>
162
+ </div>
163
+ <p class="m-0 text-sm font-medium text-foreground">No machines connected</p>
164
+ <p class="m-0 max-w-sm text-sm leading-normal text-muted-foreground">
165
+ Add a machine to open a remote terminal. The setup takes under a minute.
166
+ </p>
167
+ <Button variant="outline" size="sm" href={addMachineHref} class="mt-2">
168
+ Add a machine →
169
+ </Button>
170
+ </div>
171
+ {:else}
172
+ <div class="mb-4">
173
+ <span class="mb-2 flex items-center gap-1.5 text-xs font-semibold uppercase text-muted-foreground" id="host-list-label">Machine</span>
174
+ <ul
175
+ class="flex max-h-44 list-none flex-col gap-1 overflow-y-auto p-0"
176
+ role="listbox"
177
+ tabindex="0"
178
+ aria-labelledby="host-list-label"
179
+ aria-activedescendant={selectedHost ? hostOptionId(selectedHost.id) : undefined}
180
+ >
181
+ {#each hosts as host (host.id)}
182
+ <li
183
+ id={hostOptionId(host.id)}
184
+ class={hostClass(host)}
185
+ role="option"
186
+ aria-selected={host.id === selectedHostId}
187
+ tabindex={host.id === selectedHostId ? 0 : -1}
188
+ onclick={() => selectHost(host.id)}
189
+ onkeydown={(e) => {
190
+ if (e.key === 'Enter' || e.key === ' ') {
191
+ e.preventDefault();
192
+ selectHost(host.id);
193
+ }
194
+ }}
195
+ >
196
+ <span
197
+ class={statusDotClass(host.status)}
198
+ aria-label={hostStatusLabel(host)}
199
+ title={hostStatusLabel(host)}
200
+ ></span>
201
+ <span class="min-w-0 flex-1 truncate font-medium">{hostLabel(host)}</span>
202
+ {#if host.id === selectedHostId}
203
+ <span class="shrink-0 text-primary" aria-hidden="true">✓</span>
204
+ {/if}
205
+ </li>
206
+ {/each}
207
+ </ul>
208
+ </div>
209
+
210
+ <div class="mb-4">
211
+ <label class="mb-2 flex items-center gap-1.5 text-xs font-semibold uppercase text-muted-foreground" for="picker-command">
212
+ Command
213
+ <span class="font-normal normal-case text-muted-foreground/80">optional — defaults to your shell</span>
214
+ </label>
215
+ <Input
216
+ id="picker-command"
217
+ bind:ref={commandEl}
218
+ type="text"
219
+ bind:value={command}
220
+ placeholder={defaultCommand}
221
+ class="font-mono"
222
+ autocomplete="off"
223
+ autocorrect="off"
224
+ autocapitalize="off"
225
+ spellcheck={false}
226
+ onkeydown={(e) => {
227
+ if (e.key === 'Enter') {
228
+ e.preventDefault();
229
+ handleOpen();
230
+ }
231
+ }}
232
+ />
233
+ </div>
234
+
235
+ {#if selectedHost && selectedHost.status === 'offline'}
236
+ <p class="mb-4 rounded-md border border-warning/40 bg-warning/10 px-3 py-2 text-xs text-warning" role="alert">
237
+ This machine appears offline. You can still try to connect — it may recover.
238
+ </p>
239
+ {/if}
240
+
241
+ <div class="flex justify-end gap-2">
242
+ {#if onCancel}
243
+ <Button variant="outline" size="sm" onclick={onCancel}>
244
+ Cancel
245
+ </Button>
246
+ {/if}
247
+ <Button
248
+ size="sm"
249
+ disabled={!canOpen}
250
+ onclick={handleOpen}
251
+ >
252
+ Open terminal
253
+ </Button>
254
+ </div>
255
+ {/if}
256
+ </div>
@@ -0,0 +1,30 @@
1
+ import type { HostOption } from './types.ts';
2
+ interface Props {
3
+ /**
4
+ * Ordered list of available hosts. The first entry with `local: true` is
5
+ * "This machine" in consumer-hq; cloud hosts follow in alphabetical order.
6
+ */
7
+ hosts: HostOption[];
8
+ /** Pre-selected host id (from ?host= deep-link). */
9
+ preselectedHostId?: string;
10
+ /** Shown in the command field as placeholder when unset. */
11
+ defaultCommand?: string;
12
+ /**
13
+ * Pre-fill the command input with this value VERBATIM (from a ?command=
14
+ * deep-link). The field stays fully editable and is NEVER auto-run — the
15
+ * user must still click "Open terminal". Empty/undefined leaves the field
16
+ * blank (placeholder = defaultCommand). Non-breaking: omitting it preserves
17
+ * the prior empty-field behavior.
18
+ */
19
+ initialCommand?: string;
20
+ /** Href of the add-machine flow (shown when hosts array is empty). */
21
+ addMachineHref?: string;
22
+ /** Called when the user confirms their selection. */
23
+ onOpen: (hostId: string, command: string) => void;
24
+ /** Called when the user presses Escape or dismisses. */
25
+ onCancel?: () => void;
26
+ }
27
+ declare const HostPicker: import("svelte").Component<Props, {}, "">;
28
+ type HostPicker = ReturnType<typeof HostPicker>;
29
+ export default HostPicker;
30
+ //# sourceMappingURL=HostPicker.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HostPicker.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/HostPicker.svelte.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG5C,UAAU,KAAK;IACd;;;OAGG;IACH,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,oDAAoD;IACpD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAgLF,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,52 @@
1
+ # @vibesdotdev/terminal-client/svelte
2
+
3
+ Svelte 5 terminal surfaces over the canonical `runtime/execution` contract.
4
+
5
+ `Terminal` renders an injected `ExecutionSession`. `TerminalWorkspace` resolves
6
+ an execution provider, creates the real session, and reports its lifecycle.
7
+
8
+ ```svelte
9
+ <script lang="ts">
10
+ import TerminalWorkspace from '@vibesdotdev/terminal-client/svelte/TerminalWorkspace.svelte';
11
+ </script>
12
+
13
+ <TerminalWorkspace
14
+ cwd="/projects/vessel"
15
+ onLifecycle={(event) => {
16
+ if (event.phase === 'opened') persistReceipt(event);
17
+ }}
18
+ />
19
+ ```
20
+
21
+ `opened` is the success boundary. It is emitted only after `session()` resolves
22
+ and includes the real session id, environment id, execution id, requested `cwd`,
23
+ and timestamp. A Files `onOpenTerminal` callback is only the request that begins
24
+ this flow.
25
+
26
+ ## Runtime docs
27
+
28
+ Register `@vibesdotdev/terminal-client/svelte/plugin` to contribute the canonical
29
+ `terminal-svelte` `docs/topic` and the designed Kit ToolCard for `tools/tool:terminal`
30
+ (cwd, command/session, output excerpt via CodeViewer). The topic documents public
31
+ entrypoints, the execution-backed lifecycle boundary, and the ownership split.
32
+
33
+ ## Vessel surface
34
+
35
+ Hosts that compose Vessel may register
36
+ `@vibesdotdev/vessel/apps/terminal.vessel.plugin`. It contributes stable surface id
37
+ `terminal.workspace` and a browser-only lazy loader. The descriptor contains no
38
+ component import path; Vessel keeps that product-registration detail while loading
39
+ Terminal's owner component. It consumes the strict runtime-validatable state schema
40
+ from `@vibesdotdev/terminal-client/workspace` (`executionId`, `cwd`, optional
41
+ correlation/source ids) and requires `runtime/execution`, selected exactly by the
42
+ normalized `executionId`. Package presence or a different execution provider does
43
+ not make a saved Terminal anchor available.
44
+
45
+ A retained Terminal treats `executionId`, command, arguments, and `cwd` as one
46
+ live launch signature. Changing any member cancels the older open generation and
47
+ starts a session through the newly selected exact provider; same-folder provider
48
+ changes never keep the old session by accident.
49
+
50
+ The xterm attachment has its own generation lease. A transient `session=null` or
51
+ provider swap detaches output immediately, and deferred exit/disconnect callbacks
52
+ from the old session cannot overwrite the new session's visible state.
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ /**
3
+ * <ReconnectBanner> — visible reconnect / error banner.
4
+ *
5
+ * Renders in three visual states:
6
+ * - 'reconnecting': amber, shows attempt number + "Reconnecting…"
7
+ * - 'reconnected': green, confirms the session was restored with backlog replay
8
+ * - 'failed': red, explains the drop + offers "Start new session"
9
+ *
10
+ * The green 'reconnected' banner auto-dismisses after a short delay
11
+ * (the page manages this externally by setting status back to null).
12
+ */
13
+ import { Button } from '@vibesdotdev/kit/components/ui/button';
14
+ import { cn } from '@vibesdotdev/kit/utils';
15
+
16
+ type BannerStatus = 'reconnecting' | 'reconnected' | 'failed' | null;
17
+
18
+ interface Props {
19
+ status: BannerStatus;
20
+ /** Current attempt number (1-based), shown during 'reconnecting'. */
21
+ attempt?: number;
22
+ /** Delay in ms before the next reconnect attempt, shown during 'reconnecting'. */
23
+ delayMs?: number;
24
+ /** Human-readable failure reason shown in the 'failed' state. */
25
+ reason?: string;
26
+ /** Called when the user clicks "Start new session" in the failed state. */
27
+ onNewSession?: () => void;
28
+ }
29
+
30
+ let { status, attempt = 0, delayMs = 0, reason = '', onNewSession }: Props = $props();
31
+
32
+ const bannerClass = $derived(
33
+ cn(
34
+ 'flex shrink-0 items-center gap-2 border-t px-3.5 py-2 font-mono text-xs',
35
+ status === 'reconnecting' &&
36
+ 'border-border bg-warning/10 text-warning',
37
+ status === 'reconnected' &&
38
+ 'border-border bg-success/10 text-success',
39
+ status === 'failed' &&
40
+ 'border-border bg-destructive/15 text-destructive'
41
+ )
42
+ );
43
+
44
+ function formatDelay(ms: number): string {
45
+ if (ms < 1000) return `${ms} ms`;
46
+ return `${(ms / 1000).toFixed(1)} s`;
47
+ }
48
+ </script>
49
+
50
+ {#if status !== null}
51
+ <div
52
+ class={bannerClass}
53
+ role="status"
54
+ aria-live="polite"
55
+ aria-atomic="true"
56
+ >
57
+ {#if status === 'reconnecting'}
58
+ <span class="shrink-0 text-sm" aria-hidden="true">↻</span>
59
+ <span class="flex-1 leading-normal">
60
+ Connection lost — reconnecting
61
+ {#if attempt > 0}(attempt {attempt}){/if}
62
+ {#if delayMs > 0}
63
+ <span class="ml-1 opacity-70">next attempt in {formatDelay(delayMs)}</span>
64
+ {/if}
65
+ </span>
66
+ {:else if status === 'reconnected'}
67
+ <span class="shrink-0 text-sm" aria-hidden="true">✓</span>
68
+ <span class="flex-1 leading-normal">
69
+ Reattached — your session kept running
70
+ </span>
71
+ {:else if status === 'failed'}
72
+ <span class="shrink-0 text-sm" aria-hidden="true">⚠</span>
73
+ <span class="flex-1 leading-normal">
74
+ {reason || 'Connection lost and could not be restored.'}
75
+ </span>
76
+ {#if onNewSession}
77
+ <Button
78
+ variant="outline"
79
+ size="sm"
80
+ class="h-7 shrink-0 border-current bg-transparent px-2 font-mono text-xs text-inherit hover:bg-background/20 hover:text-inherit"
81
+ onclick={onNewSession}
82
+ >
83
+ Start new session
84
+ </Button>
85
+ {/if}
86
+ {/if}
87
+ </div>
88
+ {/if}
@@ -0,0 +1,16 @@
1
+ type BannerStatus = 'reconnecting' | 'reconnected' | 'failed' | null;
2
+ interface Props {
3
+ status: BannerStatus;
4
+ /** Current attempt number (1-based), shown during 'reconnecting'. */
5
+ attempt?: number;
6
+ /** Delay in ms before the next reconnect attempt, shown during 'reconnecting'. */
7
+ delayMs?: number;
8
+ /** Human-readable failure reason shown in the 'failed' state. */
9
+ reason?: string;
10
+ /** Called when the user clicks "Start new session" in the failed state. */
11
+ onNewSession?: () => void;
12
+ }
13
+ declare const ReconnectBanner: import("svelte").Component<Props, {}, "">;
14
+ type ReconnectBanner = ReturnType<typeof ReconnectBanner>;
15
+ export default ReconnectBanner;
16
+ //# sourceMappingURL=ReconnectBanner.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReconnectBanner.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/ReconnectBanner.svelte.ts"],"names":[],"mappings":"AAkBC,KAAK,YAAY,GAAG,cAAc,GAAG,aAAa,GAAG,QAAQ,GAAG,IAAI,CAAC;AAErE,UAAU,KAAK;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AA4DF,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}