@mp-lb/termkit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # @mp-lb/termkit
2
+
3
+ Easily orchestrate terminal sessions in React. Render a `<Terminal />`, and manage
4
+ all your live sessions — including their attention bells — with a small set of
5
+ global hooks.
6
+
7
+ ## Mental model
8
+
9
+ - A **session** is a long-lived shell (a PTY) living in the **daemon**
10
+ (`@mp-lb/termkit-server`), addressed by a stable id you choose.
11
+ - A `<Terminal session={id} />` is just a **view** onto a session. Mounting it
12
+ attaches (reconnecting to the existing shell if there is one, via a replayed
13
+ screen snapshot); unmounting detaches but leaves the shell running.
14
+ - The `<TermkitProvider>` holds the **global registry** of sessions and polls the
15
+ daemon for the live list. So there is **no one-to-one** requirement between the
16
+ terminals on screen and the sessions that are alive — you can have background
17
+ sessions with no terminal, and you can see/manage them from anywhere.
18
+
19
+ ## Two ways to use a session
20
+
21
+ - **Local:** drop `<Terminal session="abc" />` somewhere. Rendering it creates or
22
+ reattaches session `abc`. Its lifecycle reads as component-local — but the
23
+ session itself outlives the component.
24
+ - **Global:** use the hooks (`useSessions`, `useRingingSessions`,
25
+ `useTermkitActions`, …) anywhere under the provider to list every session, watch
26
+ which ones are **belling** for attention, and act on them — whether or not a
27
+ terminal is mounted for them.
28
+
29
+ ## Setup
30
+
31
+ ```tsx
32
+ import { TermkitProvider } from "@mp-lb/termkit"
33
+
34
+ const config = {
35
+ // Where the byte stream lives. `params` is forwarded to the daemon as session
36
+ // metadata and drives its resolveCwd.
37
+ connectionUrl: ({ sessionId, params }) =>
38
+ `ws://${location.host}/_terminal?` +
39
+ new URLSearchParams({ session: sessionId, ...params }),
40
+ // The control plane: list / kill / clear-bell. Wire to your backend.
41
+ transport: {
42
+ list: () => trpc.terminal.list.query(),
43
+ kill: (sessionId) => trpc.terminal.kill.mutate({ sessionId }),
44
+ seen: (sessionId) => trpc.terminal.seen.mutate({ sessionId }),
45
+ },
46
+ }
47
+
48
+ <TermkitProvider config={config}>
49
+ <App />
50
+ </TermkitProvider>
51
+ ```
52
+
53
+ ## Rendering a terminal
54
+
55
+ ```tsx
56
+ import { Terminal } from "@mp-lb/termkit"
57
+
58
+ <Terminal session="global" autoFocus />
59
+ <Terminal session={tab.id} params={{ slug: "myproject" }} />
60
+ ```
61
+
62
+ Props: `session` (required, stable id), `params`, `autoFocus`, `dismissOnView`
63
+ (default `true` — clear this session's bell when you view it), `pendingCommand`
64
+ (auto-typed once after the prompt settles), `onExitFocus` (called on
65
+ Ctrl+Shift+Enter, after termkit blurs the terminal), `className`, `style`.
66
+
67
+ ## Bells & session hooks
68
+
69
+ ```tsx
70
+ const sessions = useSessions() // LiveSession[] — the global list
71
+ const ringing = useRingingSessions() // Set<string> of ids belling for attention
72
+ const isRinging = useIsRinging(id) // one session
73
+ const status = useSessionStatus(id) // connecting | ready | disconnected | dead
74
+ const { focus, kill, dismissBell, dismissAllBells, setPendingCommand } =
75
+ useTermkitActions()
76
+ ```
77
+
78
+ Bells clear automatically when you view a session (`dismissOnView`), or explicitly
79
+ via `dismissBell(id)` / `dismissAllBells()`.
80
+
81
+ ## Theming
82
+
83
+ The terminal background resolves from `terminalOptions.background`, then the CSS
84
+ custom property `--termkit-bg`, then `--background`, then a dark fallback — so it
85
+ blends into the host theme.
86
+
87
+ ## Migrating dash onto termkit
88
+
89
+ termkit is a superset of what `dash` does today; the mapping is mechanical:
90
+
91
+ | dash | termkit |
92
+ | --- | --- |
93
+ | `<Terminal slot=… slug=… instanceId=… />` | `<Terminal session=… params={{ slug, instanceId }} />` |
94
+ | `useTerminalStore` (zustand) | the provider store + `useTermkitActions` |
95
+ | `live-sessions.ts` poller + `useLiveSessions` | built into `<TermkitProvider>` + `useSessions` |
96
+ | `useRingingSessionIds` | `useRingingSessions` |
97
+ | `markTerminalSeen` on focus/view | `dismissOnView` (default on) + `dismissBell` |
98
+ | `LiveSession.slug` / `.instanceId` | `LiveSession.meta.slug` / `.meta.instanceId` |
99
+ | exit-chord → focus `[data-session-tab]` | `onExitFocus` callback |
100
+ | `useKnownSessionIds` / `useSessionSummaries` | **stay in dash** — built on `useSessions()` (project/slug grouping is dash domain) |
101
+ | daemon (`server/terminal-daemon.ts`) | `createTerminalDaemon({ resolveCwd })` from `@mp-lb/termkit-server` |
102
+ | tRPC `terminal` router | `createTerminalRouter(client)` from `@mp-lb/termkit-trpc`, or keep dash's |
103
+
104
+ The dash-specific seams — the `~/Code` working-directory resolution and the
105
+ project/instance groupings — are deliberately **not** baked in: the daemon takes a
106
+ `resolveCwd` callback, and per-project rollups stay in dash, layered over the
107
+ generic `useSessions()` list.
@@ -0,0 +1,13 @@
1
+ import "@xterm/xterm/css/xterm.css";
2
+ export interface TerminalProps {
3
+ session: string;
4
+ params?: Record<string, string>;
5
+ autoFocus?: boolean;
6
+ dismissOnView?: boolean;
7
+ pendingCommand?: string;
8
+ onExitFocus?: () => void;
9
+ className?: string;
10
+ style?: React.CSSProperties;
11
+ }
12
+ export declare function Terminal({ session, params, autoFocus, dismissOnView, pendingCommand, onExitFocus, className, style, }: TerminalProps): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=Terminal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Terminal.d.ts","sourceRoot":"","sources":["../src/Terminal.tsx"],"names":[],"mappings":"AAGA,OAAO,4BAA4B,CAAA;AASnC,MAAM,WAAW,aAAa;IAK5B,OAAO,EAAE,MAAM,CAAA;IAIf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAE/B,SAAS,CAAC,EAAE,OAAO,CAAA;IAKnB,aAAa,CAAC,EAAE,OAAO,CAAA;IAGvB,cAAc,CAAC,EAAE,MAAM,CAAA;IAIvB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAA;CAC5B;AAYD,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EACP,MAAM,EACN,SAAiB,EACjB,aAAoB,EACpB,cAAc,EACd,WAAW,EACX,SAAS,EACT,KAAK,GACN,EAAE,aAAa,2CAoQf"}
@@ -0,0 +1,289 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import { Terminal as XTerm } from "@xterm/xterm";
4
+ import { FitAddon } from "@xterm/addon-fit";
5
+ import "@xterm/xterm/css/xterm.css";
6
+ import { CLOSE_SESSION_GONE, HEARTBEAT_INTERVAL_MS, HEARTBEAT_PONG_GRACE_MS, } from "@mp-lb/termkit-core";
7
+ import { useTermkitContext } from "./context.js";
8
+ function resolveBackground(explicit) {
9
+ if (explicit)
10
+ return explicit;
11
+ const root = getComputedStyle(document.documentElement);
12
+ return (root.getPropertyValue("--termkit-bg").trim() ||
13
+ root.getPropertyValue("--background").trim() ||
14
+ "#0a0a0a");
15
+ }
16
+ export function Terminal({ session, params, autoFocus = false, dismissOnView = true, pendingCommand, onExitFocus, className, style, }) {
17
+ const { store, config } = useTermkitContext();
18
+ const containerRef = useRef(null);
19
+ useEffect(() => {
20
+ const sessionId = session;
21
+ const { attach, detach, focus, blur, setStatus, setPendingCommand, consumePendingCommand } = store.getState();
22
+ attach(sessionId);
23
+ if (pendingCommand !== undefined)
24
+ setPendingCommand(sessionId, pendingCommand);
25
+ // Clear the bell when viewing the session, if dismissOnView is on. No-op
26
+ // otherwise. Safe on a brand-new session: the daemon no-ops seen on an unknown id.
27
+ const markSeen = () => {
28
+ if (dismissOnView)
29
+ config.transport.seen(sessionId).catch(() => { });
30
+ };
31
+ // Capture the node once so the cleanup closure references the same element even
32
+ // if the ref is repointed before teardown runs.
33
+ const container = containerRef.current;
34
+ const opts = config.terminalOptions ?? {};
35
+ const term = new XTerm({
36
+ fontFamily: opts.fontFamily ?? '"IBM Plex Mono", monospace',
37
+ fontSize: opts.fontSize ?? 13,
38
+ cursorBlink: opts.cursorBlink ?? true,
39
+ theme: { background: resolveBackground(opts.background) },
40
+ });
41
+ const fitAddon = new FitAddon();
42
+ term.loadAddon(fitAddon);
43
+ term.open(container);
44
+ fitAddon.fit();
45
+ // If a bell lands while you're actively looking at this session — its terminal
46
+ // is visible and the window has focus — it's already "seen", so clear it now
47
+ // instead of making you switch away and back. Bells that arrive while you're
48
+ // elsewhere are left to stand. term.dispose() tears this down.
49
+ term.onBell(() => {
50
+ if (document.hasFocus())
51
+ markSeen();
52
+ });
53
+ if (autoFocus) {
54
+ focus(sessionId);
55
+ term.focus();
56
+ }
57
+ else if (term.textarea) {
58
+ term.textarea.tabIndex = -1;
59
+ }
60
+ // Subscribe to store focus changes so external callers (focus/blur actions) work.
61
+ const unsubFocus = store.subscribe((state, prev) => {
62
+ const isFocused = state.focusedSlot === sessionId;
63
+ if (isFocused === (prev.focusedSlot === sessionId))
64
+ return;
65
+ if (isFocused) {
66
+ if (term.textarea)
67
+ term.textarea.tabIndex = 0;
68
+ term.focus();
69
+ }
70
+ else {
71
+ term.blur();
72
+ if (term.textarea)
73
+ term.textarea.tabIndex = -1;
74
+ }
75
+ });
76
+ // Wire textarea focus back to the store. Focusing a session also marks its
77
+ // bell seen — viewing the session is how you clear the badge.
78
+ const onFocus = () => {
79
+ focus(sessionId);
80
+ markSeen();
81
+ };
82
+ const onBlur = () => blur(sessionId);
83
+ term.textarea?.addEventListener("focus", onFocus);
84
+ term.textarea?.addEventListener("blur", onBlur);
85
+ // Ctrl+Shift+Enter exits terminal focus. Handled here (capture on the container,
86
+ // before xterm sees it) because global capture listeners don't reliably fire for
87
+ // Enter when xterm has focus. We blur and hand control back to the host.
88
+ const onExitChord = (e) => {
89
+ if (e.key === "Enter" && e.shiftKey && e.ctrlKey) {
90
+ e.preventDefault();
91
+ e.stopPropagation();
92
+ blur(sessionId);
93
+ onExitFocus?.();
94
+ }
95
+ };
96
+ container.addEventListener("keydown", onExitChord, true);
97
+ const url = config.connectionUrl({ sessionId, params: params ?? {} });
98
+ // The PTY lives in the daemon, a process that outlives dev-server restarts. So a
99
+ // dropped socket is transient, not the end of the session — reconnect, and on
100
+ // attach the daemon replays a snapshot of the current screen. `socket` is
101
+ // reassigned across reconnects; term.onData / resize read it through the closure.
102
+ let socket = null;
103
+ let reconnectTimer = null;
104
+ let disposed = false;
105
+ let attachedBefore = false;
106
+ // App-level heartbeat. A half-open socket (laptop sleep/wake, Wi-Fi change, a
107
+ // proxy hiccup) stays readyState===OPEN on both ends, so the terminal freezes
108
+ // with no 'close' to trigger a reconnect: keystrokes vanish into a dead socket
109
+ // and no output comes back. We round-trip a ping→pong over the full path; if no
110
+ // pong lands within the grace window we force-close, which runs onclose and
111
+ // reconnects to a fresh snapshot. (The daemon runs its own ping/terminate too;
112
+ // this catches the browser↔proxy hop a server-side ping can't observe.)
113
+ let heartbeatTimer = null;
114
+ let pongTimer = null;
115
+ const stopHeartbeat = () => {
116
+ if (heartbeatTimer !== null) {
117
+ clearInterval(heartbeatTimer);
118
+ heartbeatTimer = null;
119
+ }
120
+ if (pongTimer !== null) {
121
+ clearTimeout(pongTimer);
122
+ pongTimer = null;
123
+ }
124
+ };
125
+ const startHeartbeat = () => {
126
+ stopHeartbeat();
127
+ heartbeatTimer = setInterval(() => {
128
+ if (socket?.readyState !== WebSocket.OPEN)
129
+ return;
130
+ socket.send(JSON.stringify({ type: "ping" }));
131
+ if (pongTimer === null) {
132
+ pongTimer = setTimeout(() => {
133
+ pongTimer = null;
134
+ // Still OPEN but unresponsive → half-open. Close to trigger reconnect.
135
+ try {
136
+ socket?.close();
137
+ }
138
+ catch { /* already closing */ }
139
+ }, HEARTBEAT_PONG_GRACE_MS);
140
+ }
141
+ }, HEARTBEAT_INTERVAL_MS);
142
+ };
143
+ const onPong = () => {
144
+ if (pongTimer !== null) {
145
+ clearTimeout(pongTimer);
146
+ pongTimer = null;
147
+ }
148
+ };
149
+ // Auto-type queue. We can't type the moment the socket attaches: the shell is
150
+ // still starting, so bytes get echoed in cooked mode and then redrawn when the
151
+ // line editor initialises — the command appears twice. Instead we wait for the
152
+ // prompt to finish painting, detected as output going quiet. `quiet` fires once
153
+ // the PTY has been silent for QUIET_MS after we first saw output; `cap` is a
154
+ // hard fallback so a prompt that never goes quiet (a clock/spinner in PS1)
155
+ // doesn't strand the command forever.
156
+ let pendingCmd = null;
157
+ let quietTimer = null;
158
+ let capTimer = null;
159
+ const QUIET_MS = 150;
160
+ const CAP_MS = 3000;
161
+ const send = (msg) => {
162
+ if (socket?.readyState === WebSocket.OPEN)
163
+ socket.send(JSON.stringify(msg));
164
+ };
165
+ const clearAutoType = () => {
166
+ if (quietTimer !== null) {
167
+ clearTimeout(quietTimer);
168
+ quietTimer = null;
169
+ }
170
+ if (capTimer !== null) {
171
+ clearTimeout(capTimer);
172
+ capTimer = null;
173
+ }
174
+ };
175
+ const flushPendingCmd = () => {
176
+ if (pendingCmd === null)
177
+ return;
178
+ const cmd = pendingCmd;
179
+ pendingCmd = null;
180
+ clearAutoType();
181
+ send({ type: "input", data: cmd + "\r" });
182
+ };
183
+ // Called on every chunk of PTY output while a command is queued: the shell is
184
+ // actively painting, so (re)start the quiet countdown — once it elapses with no
185
+ // further output, the prompt is settled and it's safe to type.
186
+ const noteOutput = () => {
187
+ if (pendingCmd === null)
188
+ return;
189
+ if (quietTimer !== null)
190
+ clearTimeout(quietTimer);
191
+ quietTimer = setTimeout(flushPendingCmd, QUIET_MS);
192
+ };
193
+ const sendResize = () => send({ type: "resize", cols: term.cols, rows: term.rows });
194
+ const connect = () => {
195
+ if (disposed)
196
+ return;
197
+ const ws = new WebSocket(url);
198
+ // Control frames (currently just PONG) arrive as binary; PTY output is text.
199
+ ws.binaryType = "arraybuffer";
200
+ socket = ws;
201
+ ws.onopen = () => {
202
+ startHeartbeat();
203
+ // On a reconnect the daemon sends a fresh full-screen snapshot; clear first
204
+ // so it repaints cleanly rather than stacking on stale content. (First
205
+ // attach: the terminal is already empty, so nothing to clear.)
206
+ const firstAttach = !attachedBefore;
207
+ if (attachedBefore)
208
+ term.reset();
209
+ attachedBefore = true;
210
+ setStatus(sessionId, "ready");
211
+ sendResize();
212
+ // Run the queued command once, on the very first attach. Wait for the prompt
213
+ // to finish painting (see noteOutput/flushPendingCmd) so it lands on the real
214
+ // prompt instead of racing shell startup. The cap timer is a backstop.
215
+ if (firstAttach) {
216
+ const cmd = consumePendingCommand(sessionId);
217
+ if (cmd) {
218
+ pendingCmd = cmd;
219
+ capTimer = setTimeout(flushPendingCmd, CAP_MS);
220
+ }
221
+ }
222
+ markSeen();
223
+ };
224
+ ws.onmessage = (e) => {
225
+ // Binary = control frame (pong), never PTY output. Keeps the data path
226
+ // untouched: text frames are still written straight to the terminal.
227
+ if (typeof e.data !== "string") {
228
+ if (e.data instanceof ArrayBuffer) {
229
+ try {
230
+ const msg = JSON.parse(new TextDecoder().decode(e.data));
231
+ if (msg.type === "pong")
232
+ onPong();
233
+ }
234
+ catch { /* unknown control frame — ignore */ }
235
+ }
236
+ return;
237
+ }
238
+ term.write(e.data);
239
+ noteOutput();
240
+ };
241
+ ws.onclose = (e) => {
242
+ stopHeartbeat();
243
+ // 4001 = the daemon deliberately ended this session. That's terminal, not a
244
+ // transient drop, so don't reconnect — doing so would spawn a fresh shell
245
+ // for a session the user meant to be gone.
246
+ if (e.code === CLOSE_SESSION_GONE) {
247
+ disposed = true;
248
+ setStatus(sessionId, "dead");
249
+ return;
250
+ }
251
+ setStatus(sessionId, "disconnected");
252
+ if (!disposed && reconnectTimer === null)
253
+ reconnectTimer = setTimeout(() => {
254
+ reconnectTimer = null;
255
+ connect();
256
+ }, 800);
257
+ };
258
+ };
259
+ connect();
260
+ term.onData((data) => send({ type: "input", data }));
261
+ const observer = new ResizeObserver(() => {
262
+ fitAddon.fit();
263
+ sendResize();
264
+ });
265
+ observer.observe(container);
266
+ return () => {
267
+ disposed = true;
268
+ if (reconnectTimer !== null)
269
+ clearTimeout(reconnectTimer);
270
+ stopHeartbeat();
271
+ clearAutoType();
272
+ blur(sessionId);
273
+ detach(sessionId);
274
+ unsubFocus();
275
+ observer.disconnect();
276
+ // Drop the close handler before closing so unmount doesn't trigger a reconnect.
277
+ if (socket) {
278
+ socket.onclose = null;
279
+ socket.close();
280
+ }
281
+ container.removeEventListener("keydown", onExitChord, true);
282
+ term.textarea?.removeEventListener("focus", onFocus);
283
+ term.textarea?.removeEventListener("blur", onBlur);
284
+ term.dispose();
285
+ };
286
+ }, [session]); // eslint-disable-line react-hooks/exhaustive-deps
287
+ return (_jsx("div", { ref: containerRef, className: className, style: { width: "100%", height: "100%", ...style } }));
288
+ }
289
+ //# sourceMappingURL=Terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Terminal.js","sourceRoot":"","sources":["../src/Terminal.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { type ReactNode } from "react";
2
+ import { type TermkitStore } from "./store.js";
3
+ import type { TermkitConfig } from "./types.js";
4
+ interface TermkitContextValue {
5
+ store: TermkitStore;
6
+ config: TermkitConfig;
7
+ }
8
+ export interface TermkitProviderProps {
9
+ config: TermkitConfig;
10
+ children: ReactNode;
11
+ }
12
+ export declare function TermkitProvider({ config, children }: TermkitProviderProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function useTermkitContext(): TermkitContextValue;
14
+ export {};
15
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AACpF,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,UAAU,mBAAmB;IAC3B,KAAK,EAAE,YAAY,CAAA;IACnB,MAAM,EAAE,aAAa,CAAA;CACtB;AAID,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,aAAa,CAAA;IACrB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAOD,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,oBAAoB,2CAiCzE;AAED,wBAAgB,iBAAiB,IAAI,mBAAmB,CAIvD"}
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect, useRef } from "react";
3
+ import { createTermkitStore } from "./store.js";
4
+ const TermkitContext = createContext(null);
5
+ // Holds the global session registry and config for everything below it. Mount it
6
+ // once near your app root. The registry lives here (not in each <Terminal>), which
7
+ // is what makes sessions outlive the terminals that render them: a <Terminal> is
8
+ // just a view onto a session, and the provider's poller keeps the live list fresh
9
+ // whether or not any terminal is currently mounted for it.
10
+ export function TermkitProvider({ config, children }) {
11
+ // Create the store once. Config is captured at first render; treat it as stable
12
+ // (define it outside render or memoize it host-side, like any context value).
13
+ const valueRef = useRef(null);
14
+ if (valueRef.current === null) {
15
+ valueRef.current = { store: createTermkitStore(config.transport), config };
16
+ }
17
+ const { store } = valueRef.current;
18
+ // The single poller: one fetch loop publishes the live snapshot every consumer
19
+ // reads. Cheap on the daemon (an in-memory map read), so a couple of seconds
20
+ // keeps bells and counts responsive without being chatty.
21
+ const intervalMs = config.pollIntervalMs ?? 2000;
22
+ useEffect(() => {
23
+ let cancelled = false;
24
+ async function tick() {
25
+ try {
26
+ const sessions = await config.transport.list();
27
+ if (!cancelled)
28
+ store.getState().setLive(sessions);
29
+ }
30
+ catch {
31
+ /* transient — keep the last snapshot until the next tick */
32
+ }
33
+ }
34
+ tick();
35
+ const id = setInterval(tick, intervalMs);
36
+ return () => {
37
+ cancelled = true;
38
+ clearInterval(id);
39
+ };
40
+ // config.transport is captured once with the store; re-poll only if cadence changes.
41
+ }, [intervalMs, store, config.transport]);
42
+ return _jsx(TermkitContext.Provider, { value: valueRef.current, children: children });
43
+ }
44
+ export function useTermkitContext() {
45
+ const ctx = useContext(TermkitContext);
46
+ if (!ctx)
47
+ throw new Error("useTermkit must be used within a <TermkitProvider>");
48
+ return ctx;
49
+ }
50
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAkB,MAAM,OAAO,CAAA;AACpF,OAAO,EAAE,kBAAkB,EAAqB,MAAM,YAAY,CAAA;AAQlE,MAAM,cAAc,GAAG,aAAa,CAA6B,IAAI,CAAC,CAAA;AAOtE,iFAAiF;AACjF,mFAAmF;AACnF,iFAAiF;AACjF,kFAAkF;AAClF,2DAA2D;AAC3D,MAAM,UAAU,eAAe,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAwB;IACxE,gFAAgF;IAChF,8EAA8E;IAC9E,MAAM,QAAQ,GAAG,MAAM,CAA6B,IAAI,CAAC,CAAA;IACzD,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC9B,QAAQ,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5E,CAAC;IACD,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAA;IAElC,+EAA+E;IAC/E,6EAA6E;IAC7E,0DAA0D;IAC1D,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,IAAI,IAAI,CAAA;IAChD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAG,KAAK,CAAA;QACrB,KAAK,UAAU,IAAI;YACjB,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,CAAA;gBAC9C,IAAI,CAAC,SAAS;oBAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YACpD,CAAC;YAAC,MAAM,CAAC;gBACP,4DAA4D;YAC9D,CAAC;QACH,CAAC;QACD,IAAI,EAAE,CAAA;QACN,MAAM,EAAE,GAAG,WAAW,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;QACxC,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAA;YAChB,aAAa,CAAC,EAAE,CAAC,CAAA;QACnB,CAAC,CAAA;QACD,qFAAqF;IACvF,CAAC,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAA;IAEzC,OAAO,KAAC,cAAc,CAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,CAAC,OAAO,YAAG,QAAQ,GAA2B,CAAA;AAC/F,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,MAAM,GAAG,GAAG,UAAU,CAAC,cAAc,CAAC,CAAA;IACtC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAA;IAC/E,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { LiveSession } from "@mp-lb/termkit-core";
2
+ import type { SessionStatus } from "./types.js";
3
+ export declare function useSessions(): LiveSession[];
4
+ export declare function useSession(sessionId: string): LiveSession | undefined;
5
+ export declare function useRingingSessions(): Set<string>;
6
+ export declare function useIsRinging(sessionId: string): boolean;
7
+ export declare function useSessionStatus(sessionId: string): SessionStatus | undefined;
8
+ export declare function useDanglingSessions(known: Set<string>): LiveSession[];
9
+ export interface TermkitActions {
10
+ focus: (sessionId: string) => void;
11
+ blur: (sessionId: string) => void;
12
+ kill: (sessionId: string) => void;
13
+ dismissBell: (sessionId: string) => void;
14
+ dismissAllBells: () => void;
15
+ setPendingCommand: (sessionId: string, command: string) => void;
16
+ }
17
+ export declare function useTermkitActions(): TermkitActions;
18
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAK/C,wBAAgB,WAAW,IAAI,WAAW,EAAE,CAG3C;AAGD,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAGrE;AAID,wBAAgB,kBAAkB,IAAI,GAAG,CAAC,MAAM,CAAC,CAMhD;AAID,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEvD;AAID,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAG7E;AAMD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,WAAW,EAAE,CAGrE;AAED,MAAM,WAAW,cAAc;IAE7B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAEjC,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAEjC,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAExC,eAAe,EAAE,MAAM,IAAI,CAAA;IAE3B,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAChE;AAID,wBAAgB,iBAAiB,IAAI,cAAc,CAelD"}
package/dist/hooks.js ADDED
@@ -0,0 +1,61 @@
1
+ import { useMemo } from "react";
2
+ import { useStore } from "zustand";
3
+ import { useTermkitContext } from "./context.js";
4
+ // The live snapshot of every session the daemon is running — the "global" view,
5
+ // kept fresh by the provider's poller. Independent of which <Terminal>s are
6
+ // mounted: a backgrounded agent session shows up here with no terminal on screen.
7
+ export function useSessions() {
8
+ const { store } = useTermkitContext();
9
+ return useStore(store, (s) => s.live);
10
+ }
11
+ // One live session by id, or undefined if it isn't running.
12
+ export function useSession(sessionId) {
13
+ const sessions = useSessions();
14
+ return useMemo(() => sessions.find((s) => s.sessionId === sessionId), [sessions, sessionId]);
15
+ }
16
+ // The set of session ids currently ringing for attention — the bells. Render a
17
+ // badge for any tab whose id is in here.
18
+ export function useRingingSessions() {
19
+ const sessions = useSessions();
20
+ return useMemo(() => new Set(sessions.filter((s) => s.attention).map((s) => s.sessionId)), [sessions]);
21
+ }
22
+ // Whether a specific session is ringing. Convenience over useRingingSessions for a
23
+ // single tab.
24
+ export function useIsRinging(sessionId) {
25
+ return useSession(sessionId)?.attention ?? false;
26
+ }
27
+ // A mounted <Terminal>'s client-side connection status (connecting / ready /
28
+ // disconnected / dead). Undefined if no terminal is attached for this session.
29
+ export function useSessionStatus(sessionId) {
30
+ const { store } = useTermkitContext();
31
+ return useStore(store, (s) => s.slots[sessionId]?.status);
32
+ }
33
+ // Live PTYs with no matching id in `known` — orphaned shells. Pass the set of ids
34
+ // your UI knows about (its tabs); anything live but unknown is a dangler you can
35
+ // surface for cleanup. The known-id bookkeeping is host domain, so it lives there;
36
+ // termkit just does the diff.
37
+ export function useDanglingSessions(known) {
38
+ const sessions = useSessions();
39
+ return useMemo(() => sessions.filter((s) => !known.has(s.sessionId)), [sessions, known]);
40
+ }
41
+ // Imperative session controls. Stable across renders (the store identity is
42
+ // stable), so safe to use without memoising.
43
+ export function useTermkitActions() {
44
+ const { store } = useTermkitContext();
45
+ return useMemo(() => {
46
+ const s = () => store.getState();
47
+ return {
48
+ focus: (id) => s().focus(id),
49
+ blur: (id) => s().blur(id),
50
+ kill: (id) => s().kill(id),
51
+ dismissBell: (id) => s().dismissBell(id),
52
+ dismissAllBells: () => {
53
+ for (const sess of s().live)
54
+ if (sess.attention)
55
+ s().dismissBell(sess.sessionId);
56
+ },
57
+ setPendingCommand: (id, command) => s().setPendingCommand(id, command),
58
+ };
59
+ }, [store]);
60
+ }
61
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAElC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAGhD,gFAAgF;AAChF,4EAA4E;AAC5E,kFAAkF;AAClF,MAAM,UAAU,WAAW;IACzB,MAAM,EAAE,KAAK,EAAE,GAAG,iBAAiB,EAAE,CAAA;IACrC,OAAO,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;AACvC,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAA;AAC9F,CAAC;AAED,+EAA+E;AAC/E,yCAAyC;AACzC,MAAM,UAAU,kBAAkB;IAChC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,EAC1E,CAAC,QAAQ,CAAC,CACX,CAAA;AACH,CAAC;AAED,mFAAmF;AACnF,cAAc;AACd,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,OAAO,UAAU,CAAC,SAAS,CAAC,EAAE,SAAS,IAAI,KAAK,CAAA;AAClD,CAAC;AAED,6EAA6E;AAC7E,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,MAAM,EAAE,KAAK,EAAE,GAAG,iBAAiB,EAAE,CAAA;IACrC,OAAO,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,CAAA;AAC3D,CAAC;AAED,kFAAkF;AAClF,iFAAiF;AACjF,mFAAmF;AACnF,8BAA8B;AAC9B,MAAM,UAAU,mBAAmB,CAAC,KAAkB;IACpD,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAA;AAC1F,CAAC;AAgBD,4EAA4E;AAC5E,6CAA6C;AAC7C,MAAM,UAAU,iBAAiB;IAC/B,MAAM,EAAE,KAAK,EAAE,GAAG,iBAAiB,EAAE,CAAA;IACrC,OAAO,OAAO,CAAiB,GAAG,EAAE;QAClC,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAA;QAChC,OAAO;YACL,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC;YACxC,eAAe,EAAE,GAAG,EAAE;gBACpB,KAAK,MAAM,IAAI,IAAI,CAAC,EAAE,CAAC,IAAI;oBAAE,IAAI,IAAI,CAAC,SAAS;wBAAE,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YAClF,CAAC;YACD,iBAAiB,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,iBAAiB,CAAC,EAAE,EAAE,OAAO,CAAC;SACvE,CAAA;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;AACb,CAAC"}
@@ -0,0 +1,9 @@
1
+ export { TermkitProvider } from "./context.js";
2
+ export type { TermkitProviderProps } from "./context.js";
3
+ export { Terminal } from "./Terminal.js";
4
+ export type { TerminalProps } from "./Terminal.js";
5
+ export { useSessions, useSession, useRingingSessions, useIsRinging, useSessionStatus, useDanglingSessions, useTermkitActions, } from "./hooks.js";
6
+ export type { TermkitActions } from "./hooks.js";
7
+ export type { TermkitConfig, TermkitTransport, TermkitTerminalOptions, SessionStatus, LiveSession, } from "./types.js";
8
+ export { generateSessionName, SESSION_NAMES } from "@mp-lb/termkit-core";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC9C,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAElD,OAAO,EACL,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,sBAAsB,EACtB,aAAa,EACb,WAAW,GACZ,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ // @mp-lb/termkit — easily orchestrate terminal sessions in React.
2
+ //
3
+ // Mount a <TermkitProvider> once near your app root with a connection URL and a
4
+ // transport, then render <Terminal session="..."/> anywhere. The provider holds a
5
+ // global session registry, so sessions outlive the terminals that render them —
6
+ // read the live list, watch for bells, and manage sessions with the hooks below,
7
+ // whether or not a terminal for them is currently on screen.
8
+ export { TermkitProvider } from "./context.js";
9
+ export { Terminal } from "./Terminal.js";
10
+ export { useSessions, useSession, useRingingSessions, useIsRinging, useSessionStatus, useDanglingSessions, useTermkitActions, } from "./hooks.js";
11
+ // Re-export the session-name helper so hosts can label new sessions consistently.
12
+ export { generateSessionName, SESSION_NAMES } from "@mp-lb/termkit-core";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,EAAE;AACF,gFAAgF;AAChF,kFAAkF;AAClF,gFAAgF;AAChF,iFAAiF;AACjF,6DAA6D;AAE7D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAGxC,OAAO,EACL,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAWnB,kFAAkF;AAClF,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
@@ -0,0 +1,27 @@
1
+ import { type StoreApi } from "zustand/vanilla";
2
+ import type { LiveSession } from "@mp-lb/termkit-core";
3
+ import type { SessionStatus, TermkitTransport } from "./types.js";
4
+ interface TerminalSlot {
5
+ sessionId: string;
6
+ status: SessionStatus;
7
+ }
8
+ export interface TermkitState {
9
+ slots: Record<string, TerminalSlot>;
10
+ focusedSlot: string | null;
11
+ pendingCommands: Record<string, string>;
12
+ live: LiveSession[];
13
+ attach: (sessionId: string) => void;
14
+ detach: (sessionId: string) => void;
15
+ setStatus: (sessionId: string, status: SessionStatus) => void;
16
+ focus: (sessionId: string) => void;
17
+ blur: (sessionId: string) => void;
18
+ setPendingCommand: (sessionId: string, command: string) => void;
19
+ consumePendingCommand: (sessionId: string) => string | null;
20
+ kill: (sessionId: string) => void;
21
+ dismissBell: (sessionId: string) => void;
22
+ setLive: (live: LiveSession[]) => void;
23
+ }
24
+ export type TermkitStore = StoreApi<TermkitState>;
25
+ export declare function createTermkitStore(transport: TermkitTransport): TermkitStore;
26
+ export {};
27
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAOjE,UAAU,YAAY;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,aAAa,CAAA;CACtB;AAED,MAAM,WAAW,YAAY;IAE3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IAEnC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAI1B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAIvC,IAAI,EAAE,WAAW,EAAE,CAAA;IAGnB,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,KAAK,IAAI,CAAA;IAG7D,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAGjC,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/D,qBAAqB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;IAG3D,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAGxC,OAAO,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,IAAI,CAAA;CACvC;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAA;AAEjD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,gBAAgB,GAAG,YAAY,CAwF5E"}
package/dist/store.js ADDED
@@ -0,0 +1,85 @@
1
+ import { createStore } from "zustand/vanilla";
2
+ export function createTermkitStore(transport) {
3
+ return createStore((set, get) => ({
4
+ slots: {},
5
+ focusedSlot: null,
6
+ pendingCommands: {},
7
+ live: [],
8
+ attach(sessionId) {
9
+ const existing = get().slots[sessionId];
10
+ if (existing && existing.status !== "dead")
11
+ return;
12
+ set((s) => ({
13
+ slots: { ...s.slots, [sessionId]: { sessionId, status: "connecting" } },
14
+ }));
15
+ },
16
+ detach(sessionId) {
17
+ set((s) => {
18
+ if (!(sessionId in s.slots))
19
+ return s;
20
+ const slots = { ...s.slots };
21
+ delete slots[sessionId];
22
+ return { slots, focusedSlot: s.focusedSlot === sessionId ? null : s.focusedSlot };
23
+ });
24
+ },
25
+ setStatus(sessionId, status) {
26
+ set((s) => {
27
+ const slot = s.slots[sessionId];
28
+ if (!slot)
29
+ return s;
30
+ return { slots: { ...s.slots, [sessionId]: { ...slot, status } } };
31
+ });
32
+ },
33
+ focus(sessionId) {
34
+ set({ focusedSlot: sessionId });
35
+ },
36
+ blur(sessionId) {
37
+ set((s) => ({ focusedSlot: s.focusedSlot === sessionId ? null : s.focusedSlot }));
38
+ },
39
+ setPendingCommand(sessionId, command) {
40
+ set((s) => ({ pendingCommands: { ...s.pendingCommands, [sessionId]: command } }));
41
+ },
42
+ consumePendingCommand(sessionId) {
43
+ const command = get().pendingCommands[sessionId];
44
+ if (command === undefined)
45
+ return null;
46
+ set((s) => {
47
+ const pendingCommands = { ...s.pendingCommands };
48
+ delete pendingCommands[sessionId];
49
+ return { pendingCommands };
50
+ });
51
+ return command;
52
+ },
53
+ kill(sessionId) {
54
+ // The daemon session id IS the slot, so we can always reap the PTY — no
55
+ // lookup that could miss and leave a shell running.
56
+ transport.kill(sessionId).catch(() => { });
57
+ set((s) => {
58
+ const slots = { ...s.slots };
59
+ delete slots[sessionId];
60
+ const pendingCommands = { ...s.pendingCommands };
61
+ delete pendingCommands[sessionId];
62
+ // Drop it from the live snapshot too so the UI updates before the next
63
+ // poll tick, rather than showing a session that's already gone.
64
+ return {
65
+ slots,
66
+ pendingCommands,
67
+ live: s.live.filter((l) => l.sessionId !== sessionId),
68
+ focusedSlot: s.focusedSlot === sessionId ? null : s.focusedSlot,
69
+ };
70
+ });
71
+ },
72
+ dismissBell(sessionId) {
73
+ transport.seen(sessionId).catch(() => { });
74
+ // Optimistically clear attention so the badge goes away immediately; the
75
+ // next poll confirms it.
76
+ set((s) => ({
77
+ live: s.live.map((l) => (l.sessionId === sessionId ? { ...l, attention: false } : l)),
78
+ }));
79
+ },
80
+ setLive(live) {
81
+ set({ live });
82
+ },
83
+ }));
84
+ }
85
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,iBAAiB,CAAA;AAmD5D,MAAM,UAAU,kBAAkB,CAAC,SAA2B;IAC5D,OAAO,WAAW,CAAe,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QAC9C,KAAK,EAAE,EAAE;QACT,WAAW,EAAE,IAAI;QACjB,eAAe,EAAE,EAAE;QACnB,IAAI,EAAE,EAAE;QAER,MAAM,CAAC,SAAS;YACd,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;YACvC,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM;gBAAE,OAAM;YAClD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACV,KAAK,EAAE,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE;aACxE,CAAC,CAAC,CAAA;QACL,CAAC;QAED,MAAM,CAAC,SAAS;YACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACR,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,KAAK,CAAC;oBAAE,OAAO,CAAC,CAAA;gBACrC,MAAM,KAAK,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,CAAA;gBAC5B,OAAO,KAAK,CAAC,SAAS,CAAC,CAAA;gBACvB,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;YACnF,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,SAAS,CAAC,SAAS,EAAE,MAAM;YACzB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACR,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;gBAC/B,IAAI,CAAC,IAAI;oBAAE,OAAO,CAAC,CAAA;gBACnB,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;YACpE,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,KAAK,CAAC,SAAS;YACb,GAAG,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAA;QACjC,CAAC;QAED,IAAI,CAAC,SAAS;YACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;QACnF,CAAC;QAED,iBAAiB,CAAC,SAAS,EAAE,OAAO;YAClC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,eAAe,EAAE,EAAE,GAAG,CAAC,CAAC,eAAe,EAAE,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;QACnF,CAAC;QAED,qBAAqB,CAAC,SAAS;YAC7B,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,eAAe,CAAC,SAAS,CAAC,CAAA;YAChD,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,IAAI,CAAA;YACtC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACR,MAAM,eAAe,GAAG,EAAE,GAAG,CAAC,CAAC,eAAe,EAAE,CAAA;gBAChD,OAAO,eAAe,CAAC,SAAS,CAAC,CAAA;gBACjC,OAAO,EAAE,eAAe,EAAE,CAAA;YAC5B,CAAC,CAAC,CAAA;YACF,OAAO,OAAO,CAAA;QAChB,CAAC;QAED,IAAI,CAAC,SAAS;YACZ,wEAAwE;YACxE,oDAAoD;YACpD,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YACzC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACR,MAAM,KAAK,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,CAAA;gBAC5B,OAAO,KAAK,CAAC,SAAS,CAAC,CAAA;gBACvB,MAAM,eAAe,GAAG,EAAE,GAAG,CAAC,CAAC,eAAe,EAAE,CAAA;gBAChD,OAAO,eAAe,CAAC,SAAS,CAAC,CAAA;gBACjC,uEAAuE;gBACvE,gEAAgE;gBAChE,OAAO;oBACL,KAAK;oBACL,eAAe;oBACf,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC;oBACrD,WAAW,EAAE,CAAC,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW;iBAChE,CAAA;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,WAAW,CAAC,SAAS;YACnB,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YACzC,yEAAyE;YACzE,yBAAyB;YACzB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACV,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aACtF,CAAC,CAAC,CAAA;QACL,CAAC;QAED,OAAO,CAAC,IAAI;YACV,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;QACf,CAAC;KACF,CAAC,CAAC,CAAA;AACL,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { LiveSession } from "@mp-lb/termkit-core";
2
+ export type SessionStatus = "connecting" | "ready" | "disconnected" | "dead";
3
+ export interface TermkitTransport {
4
+ list: () => Promise<LiveSession[]>;
5
+ kill: (sessionId: string) => Promise<unknown>;
6
+ seen: (sessionId: string) => Promise<unknown>;
7
+ }
8
+ export interface TermkitTerminalOptions {
9
+ fontFamily?: string;
10
+ fontSize?: number;
11
+ cursorBlink?: boolean;
12
+ background?: string;
13
+ }
14
+ export interface TermkitConfig {
15
+ connectionUrl: (args: {
16
+ sessionId: string;
17
+ params: Record<string, string>;
18
+ }) => string;
19
+ transport: TermkitTransport;
20
+ pollIntervalMs?: number;
21
+ terminalOptions?: TermkitTerminalOptions;
22
+ }
23
+ export type { LiveSession };
24
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAKtD,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,OAAO,GAAG,cAAc,GAAG,MAAM,CAAA;AAO5E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;IAClC,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7C,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAC9C;AAGD,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,OAAO,CAAA;IAGrB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAKD,MAAM,WAAW,aAAa;IAO5B,aAAa,EAAE,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,KAAK,MAAM,CAAA;IACtF,SAAS,EAAE,gBAAgB,CAAA;IAE3B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,eAAe,CAAC,EAAE,sBAAsB,CAAA;CACzC;AAED,YAAY,EAAE,WAAW,EAAE,CAAA"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@mp-lb/termkit",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "package.json",
17
+ "README.md"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "dependencies": {
23
+ "@xterm/addon-fit": "^0.11.0",
24
+ "@xterm/xterm": "^6.0.0",
25
+ "zustand": "^5.0.14",
26
+ "@mp-lb/termkit-core": "0.0.1"
27
+ },
28
+ "peerDependencies": {
29
+ "react": "^18.0.0 || ^19.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/react": "^19",
33
+ "react": "^19.2.6"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc --project tsconfig.json",
37
+ "dev": "tsc --project tsconfig.json --watch --preserveWatchOutput",
38
+ "typecheck": "tsc --project tsconfig.json --noEmit",
39
+ "test": "vitest run"
40
+ }
41
+ }