@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 +107 -0
- package/dist/Terminal.d.ts +13 -0
- package/dist/Terminal.d.ts.map +1 -0
- package/dist/Terminal.js +289 -0
- package/dist/Terminal.js.map +1 -0
- package/dist/context.d.ts +15 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +50 -0
- package/dist/context.js.map +1 -0
- package/dist/hooks.d.ts +18 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +61 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/store.d.ts +27 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +85 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
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"}
|
package/dist/Terminal.js
ADDED
|
@@ -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"}
|
package/dist/context.js
ADDED
|
@@ -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"}
|
package/dist/hooks.d.ts
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/store.d.ts
ADDED
|
@@ -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":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|