@myrialabs/ptykit 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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +260 -0
  3. package/dist/client/fit.d.ts +29 -0
  4. package/dist/client/fit.d.ts.map +1 -0
  5. package/dist/client/fit.js +45 -0
  6. package/dist/client/fit.js.map +1 -0
  7. package/dist/client/index.d.ts +10 -0
  8. package/dist/client/index.d.ts.map +1 -0
  9. package/dist/client/index.js +9 -0
  10. package/dist/client/index.js.map +1 -0
  11. package/dist/client/persistence.d.ts +15 -0
  12. package/dist/client/persistence.d.ts.map +1 -0
  13. package/dist/client/persistence.js +47 -0
  14. package/dist/client/persistence.js.map +1 -0
  15. package/dist/client/pty-kit-client.d.ts +122 -0
  16. package/dist/client/pty-kit-client.d.ts.map +1 -0
  17. package/dist/client/pty-kit-client.js +245 -0
  18. package/dist/client/pty-kit-client.js.map +1 -0
  19. package/dist/client/terminal.d.ts +77 -0
  20. package/dist/client/terminal.d.ts.map +1 -0
  21. package/dist/client/terminal.js +112 -0
  22. package/dist/client/terminal.js.map +1 -0
  23. package/dist/client/ws-core.d.ts +88 -0
  24. package/dist/client/ws-core.d.ts.map +1 -0
  25. package/dist/client/ws-core.js +324 -0
  26. package/dist/client/ws-core.js.map +1 -0
  27. package/dist/core/backend.d.ts +52 -0
  28. package/dist/core/backend.d.ts.map +1 -0
  29. package/dist/core/backend.js +11 -0
  30. package/dist/core/backend.js.map +1 -0
  31. package/dist/core/detect.d.ts +21 -0
  32. package/dist/core/detect.d.ts.map +1 -0
  33. package/dist/core/detect.js +82 -0
  34. package/dist/core/detect.js.map +1 -0
  35. package/dist/core/env.d.ts +30 -0
  36. package/dist/core/env.d.ts.map +1 -0
  37. package/dist/core/env.js +68 -0
  38. package/dist/core/env.js.map +1 -0
  39. package/dist/core/index.d.ts +11 -0
  40. package/dist/core/index.d.ts.map +1 -0
  41. package/dist/core/index.js +10 -0
  42. package/dist/core/index.js.map +1 -0
  43. package/dist/core/pty-kit.d.ts +90 -0
  44. package/dist/core/pty-kit.d.ts.map +1 -0
  45. package/dist/core/pty-kit.js +187 -0
  46. package/dist/core/pty-kit.js.map +1 -0
  47. package/dist/core/scrollback.d.ts +43 -0
  48. package/dist/core/scrollback.d.ts.map +1 -0
  49. package/dist/core/scrollback.js +79 -0
  50. package/dist/core/scrollback.js.map +1 -0
  51. package/dist/core/session.d.ts +100 -0
  52. package/dist/core/session.d.ts.map +1 -0
  53. package/dist/core/session.js +264 -0
  54. package/dist/core/session.js.map +1 -0
  55. package/dist/core/shell.d.ts +24 -0
  56. package/dist/core/shell.d.ts.map +1 -0
  57. package/dist/core/shell.js +55 -0
  58. package/dist/core/shell.js.map +1 -0
  59. package/dist/index.d.ts +11 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +11 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/server/connection.d.ts +38 -0
  64. package/dist/server/connection.d.ts.map +1 -0
  65. package/dist/server/connection.js +67 -0
  66. package/dist/server/connection.js.map +1 -0
  67. package/dist/server/ids.d.ts +2 -0
  68. package/dist/server/ids.d.ts.map +1 -0
  69. package/dist/server/ids.js +7 -0
  70. package/dist/server/ids.js.map +1 -0
  71. package/dist/server/index.d.ts +6 -0
  72. package/dist/server/index.d.ts.map +1 -0
  73. package/dist/server/index.js +6 -0
  74. package/dist/server/index.js.map +1 -0
  75. package/dist/server/pty-kit-server.d.ts +101 -0
  76. package/dist/server/pty-kit-server.d.ts.map +1 -0
  77. package/dist/server/pty-kit-server.js +361 -0
  78. package/dist/server/pty-kit-server.js.map +1 -0
  79. package/dist/server/transport-bun.d.ts +26 -0
  80. package/dist/server/transport-bun.d.ts.map +1 -0
  81. package/dist/server/transport-bun.js +79 -0
  82. package/dist/server/transport-bun.js.map +1 -0
  83. package/dist/server/transport-node.d.ts +20 -0
  84. package/dist/server/transport-node.d.ts.map +1 -0
  85. package/dist/server/transport-node.js +77 -0
  86. package/dist/server/transport-node.js.map +1 -0
  87. package/dist/shared/index.d.ts +260 -0
  88. package/dist/shared/index.d.ts.map +1 -0
  89. package/dist/shared/index.js +85 -0
  90. package/dist/shared/index.js.map +1 -0
  91. package/package.json +108 -0
  92. package/src/client/svelte/PtyTerminal.svelte +146 -0
  93. package/src/client/svelte/index.d.ts +84 -0
  94. package/src/client/svelte/index.js +4 -0
  95. package/src/client/svelte/svelte-compile.test.ts +11 -0
@@ -0,0 +1,264 @@
1
+ /**
2
+ * A single PTY session: process handle + output pipeline + scrollback binding.
3
+ *
4
+ * Output pipeline (R5, R7a):
5
+ * 1. Persist every chunk to the headless terminal FIRST — even with zero
6
+ * listeners — so scrollback stays accurate while clients are disconnected.
7
+ * 2. Micro-task batch high-frequency output (`queueMicrotask`).
8
+ * 3. Stamp a monotonic `seq` per flush for client-side dedup.
9
+ * 4. Fan out the batched chunk to every listener.
10
+ *
11
+ * Lifecycle: idle `\r` fallback after 350ms of silence (R18); kill = Ctrl+C
12
+ * then SIGKILL after 1s, or a direct signal (R19).
13
+ */
14
+ import { buildPtyEnv } from './env.js';
15
+ import { resolveShell } from './shell.js';
16
+ import { silentLogger } from '../shared/index.js';
17
+ export class Session {
18
+ sessionId;
19
+ namespace;
20
+ streamId;
21
+ createdAt = new Date();
22
+ cwd;
23
+ cols;
24
+ rows;
25
+ lastActivityAt = new Date();
26
+ status = 'active';
27
+ exitCode;
28
+ /** Monotonic sequence, incremented once per flushed batch (R5). */
29
+ outputSeq = 0;
30
+ handle;
31
+ buffers;
32
+ logger;
33
+ killGraceMs;
34
+ dataListeners = new Set();
35
+ exitListeners = new Set();
36
+ pendingOutput = '';
37
+ flushScheduled = false;
38
+ receivedInitialOutput = false;
39
+ dataSub;
40
+ exitSub;
41
+ idleTimer = null;
42
+ killTimer = null;
43
+ constructor(config) {
44
+ this.sessionId = config.sessionId;
45
+ this.namespace = config.namespace;
46
+ this.streamId = config.streamId;
47
+ this.cwd = config.cwd;
48
+ this.cols = config.cols;
49
+ this.rows = config.rows;
50
+ this.buffers = config.buffers;
51
+ this.logger = config.logger ?? silentLogger;
52
+ this.killGraceMs = config.killGraceMs ?? 1000;
53
+ const { shell, args } = resolveShell(config.shell);
54
+ const env = buildPtyEnv(config.env, { cols: this.cols, rows: this.rows });
55
+ this.handle = config.backend.spawn(shell, args, {
56
+ name: 'xterm-256color',
57
+ cols: this.cols,
58
+ rows: this.rows,
59
+ cwd: this.cwd,
60
+ env,
61
+ });
62
+ // Allocate scrollback up front so persist-first always has a target.
63
+ this.buffers.create(this.sessionId, this.cols, this.rows);
64
+ this.dataSub = this.handle.onData((data) => this.onData(data));
65
+ this.exitSub = this.handle.onExit((event) => this.onExit(event));
66
+ // Some shells do not paint the first prompt until they receive input.
67
+ // Only nudge if the PTY stayed completely silent after spawn; otherwise
68
+ // it duplicates the initial prompt on normal shells (R18).
69
+ const idleFallbackMs = config.idleFallbackMs ?? 350;
70
+ this.idleTimer = setTimeout(() => {
71
+ this.idleTimer = null;
72
+ if (this.receivedInitialOutput || this.status === 'exited')
73
+ return;
74
+ try {
75
+ this.handle.write('\r');
76
+ }
77
+ catch (err) {
78
+ this.logger.error('session', 'idle fallback write failed', err);
79
+ }
80
+ }, idleFallbackMs);
81
+ }
82
+ get pid() {
83
+ return this.handle.pid;
84
+ }
85
+ info() {
86
+ return {
87
+ sessionId: this.sessionId,
88
+ namespace: this.namespace,
89
+ streamId: this.streamId,
90
+ pid: this.pid,
91
+ cwd: this.cwd,
92
+ cols: this.cols,
93
+ rows: this.rows,
94
+ createdAt: this.createdAt,
95
+ lastActivityAt: this.lastActivityAt,
96
+ status: this.status,
97
+ exitCode: this.exitCode,
98
+ };
99
+ }
100
+ onData(data) {
101
+ this.receivedInitialOutput = true;
102
+ this.lastActivityAt = new Date();
103
+ // Persist FIRST, even with zero listeners (R7a).
104
+ this.buffers.write(this.sessionId, data);
105
+ // Micro-task batch high-frequency output (R5).
106
+ this.pendingOutput += data;
107
+ if (this.flushScheduled)
108
+ return;
109
+ this.flushScheduled = true;
110
+ queueMicrotask(() => {
111
+ const output = this.pendingOutput;
112
+ this.pendingOutput = '';
113
+ this.flushScheduled = false;
114
+ this.outputSeq++;
115
+ const seq = this.outputSeq;
116
+ for (const listener of this.dataListeners) {
117
+ try {
118
+ listener(output, seq);
119
+ }
120
+ catch (err) {
121
+ this.logger.error('session', 'data listener error', err);
122
+ }
123
+ }
124
+ });
125
+ }
126
+ onExit(event) {
127
+ this.status = 'exited';
128
+ this.exitCode = event.exitCode;
129
+ if (this.idleTimer) {
130
+ clearTimeout(this.idleTimer);
131
+ this.idleTimer = null;
132
+ }
133
+ for (const listener of this.exitListeners) {
134
+ try {
135
+ listener(event);
136
+ }
137
+ catch (err) {
138
+ this.logger.error('session', 'exit listener error', err);
139
+ }
140
+ }
141
+ }
142
+ // ---- Listener management (R7c: callers clear before reattaching) --------
143
+ addDataListener(listener) {
144
+ this.dataListeners.add(listener);
145
+ }
146
+ removeDataListener(listener) {
147
+ this.dataListeners.delete(listener);
148
+ }
149
+ addExitListener(listener) {
150
+ this.exitListeners.add(listener);
151
+ }
152
+ removeExitListener(listener) {
153
+ this.exitListeners.delete(listener);
154
+ }
155
+ /** Clear ALL listeners — call before attaching fresh ones to avoid double output (R7c). */
156
+ clearListeners() {
157
+ this.dataListeners.clear();
158
+ this.exitListeners.clear();
159
+ }
160
+ get dataListenerCount() {
161
+ return this.dataListeners.size;
162
+ }
163
+ // ---- I/O ----------------------------------------------------------------
164
+ write(data) {
165
+ if (this.status === 'exited')
166
+ return false;
167
+ try {
168
+ this.handle.write(data);
169
+ this.lastActivityAt = new Date();
170
+ return true;
171
+ }
172
+ catch (err) {
173
+ this.logger.error('session', `write to ${this.sessionId} failed`, err);
174
+ return false;
175
+ }
176
+ }
177
+ resize(cols, rows) {
178
+ if (this.status === 'exited')
179
+ return false;
180
+ try {
181
+ this.handle.resize(cols, rows);
182
+ this.cols = cols;
183
+ this.rows = rows;
184
+ // Keep scrollback in sync with PTY dimensions (R15).
185
+ this.buffers.resize(this.sessionId, cols, rows);
186
+ return true;
187
+ }
188
+ catch (err) {
189
+ this.logger.error('session', `resize of ${this.sessionId} failed`, err);
190
+ return false;
191
+ }
192
+ }
193
+ /** Send Ctrl+C (R19). */
194
+ cancel() {
195
+ if (this.status === 'exited')
196
+ return;
197
+ try {
198
+ this.handle.write('\x03');
199
+ }
200
+ catch (err) {
201
+ this.logger.error('session', `cancel of ${this.sessionId} failed`, err);
202
+ }
203
+ }
204
+ /** Kill: Ctrl+C then SIGKILL after the grace window, or a direct signal (R19). */
205
+ kill(signal) {
206
+ try {
207
+ if (signal === 'SIGKILL' || signal === '9') {
208
+ this.handle.kill('SIGKILL');
209
+ return;
210
+ }
211
+ if (signal === 'SIGTERM' || signal === '15') {
212
+ this.handle.kill('SIGTERM');
213
+ return;
214
+ }
215
+ this.handle.write('\x03');
216
+ this.killTimer = setTimeout(() => {
217
+ this.killTimer = null;
218
+ if (this.status === 'exited')
219
+ return;
220
+ try {
221
+ this.handle.kill('SIGKILL');
222
+ }
223
+ catch {
224
+ // already dead
225
+ }
226
+ }, this.killGraceMs);
227
+ }
228
+ catch (err) {
229
+ this.logger.error('session', `kill of ${this.sessionId} failed`, err);
230
+ }
231
+ }
232
+ serialize() {
233
+ return this.buffers.serialize(this.sessionId);
234
+ }
235
+ clearScreen() {
236
+ this.buffers.clear(this.sessionId);
237
+ }
238
+ bufferLength() {
239
+ return this.buffers.length(this.sessionId);
240
+ }
241
+ /** Tear down PTY subscriptions and timers. Does NOT dispose scrollback. */
242
+ dispose() {
243
+ try {
244
+ this.dataSub.dispose();
245
+ }
246
+ catch {
247
+ /* ignore */
248
+ }
249
+ try {
250
+ this.exitSub.dispose();
251
+ }
252
+ catch {
253
+ /* ignore */
254
+ }
255
+ if (this.idleTimer)
256
+ clearTimeout(this.idleTimer);
257
+ if (this.killTimer)
258
+ clearTimeout(this.killTimer);
259
+ this.idleTimer = null;
260
+ this.killTimer = null;
261
+ this.clearListeners();
262
+ }
263
+ }
264
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/core/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shell resolution and working-directory handling (R1).
3
+ *
4
+ * Unix: `process.env.SHELL || /bin/bash`, interactive (no `-c`).
5
+ * Windows: `powershell.exe -NoLogo`, interactive (no `-Command`).
6
+ * Defaults: `xterm-256color`, 80×24.
7
+ */
8
+ import type { CheckShellResponse } from '../shared/index.js';
9
+ export declare const isWindows: boolean;
10
+ /** A resolved shell invocation. */
11
+ export interface ShellInvocation {
12
+ shell: string;
13
+ args: string[];
14
+ }
15
+ /** Resolve the interactive shell + args for this platform (R1). */
16
+ export declare function resolveShell(preferred?: string): ShellInvocation;
17
+ /**
18
+ * Resolve a requested working directory to an existing absolute path, falling
19
+ * back to the user's home directory, then `process.cwd()`.
20
+ */
21
+ export declare function resolveCwd(requested?: string): string;
22
+ /** Report shell availability for the `check-shell` operation. */
23
+ export declare function checkShell(): CheckShellResponse;
24
+ //# sourceMappingURL=shell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/core/shell.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,eAAO,MAAM,SAAS,SAA+B,CAAC;AAEtD,mCAAmC;AACnC,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;CACf;AAED,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe,CAKhE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAWrD;AAED,iEAAiE;AACjE,wBAAgB,UAAU,IAAI,kBAAkB,CAmB/C"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Shell resolution and working-directory handling (R1).
3
+ *
4
+ * Unix: `process.env.SHELL || /bin/bash`, interactive (no `-c`).
5
+ * Windows: `powershell.exe -NoLogo`, interactive (no `-Command`).
6
+ * Defaults: `xterm-256color`, 80×24.
7
+ */
8
+ import { existsSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ import { resolve } from 'node:path';
11
+ export const isWindows = process.platform === 'win32';
12
+ /** Resolve the interactive shell + args for this platform (R1). */
13
+ export function resolveShell(preferred) {
14
+ if (isWindows) {
15
+ return { shell: preferred || 'powershell.exe', args: ['-NoLogo'] };
16
+ }
17
+ return { shell: preferred || process.env.SHELL || '/bin/bash', args: [] };
18
+ }
19
+ /**
20
+ * Resolve a requested working directory to an existing absolute path, falling
21
+ * back to the user's home directory, then `process.cwd()`.
22
+ */
23
+ export function resolveCwd(requested) {
24
+ const home = (isWindows ? process.env.USERPROFILE : process.env.HOME) || homedir();
25
+ if (requested && requested !== '~') {
26
+ const expanded = requested.startsWith('~')
27
+ ? requested.replace('~', home || process.cwd())
28
+ : resolve(requested);
29
+ if (existsSync(expanded))
30
+ return expanded;
31
+ }
32
+ return home || process.cwd();
33
+ }
34
+ /** Report shell availability for the `check-shell` operation. */
35
+ export function checkShell() {
36
+ if (isWindows) {
37
+ return {
38
+ available: true,
39
+ path: 'powershell.exe',
40
+ platform: process.platform,
41
+ isWindows: true,
42
+ shellType: 'PowerShell',
43
+ };
44
+ }
45
+ const path = process.env.SHELL || '/bin/bash';
46
+ const shellType = (path.split('/').pop() || 'shell').replace(/^\w/, (c) => c.toUpperCase());
47
+ return {
48
+ available: true,
49
+ path,
50
+ platform: process.platform,
51
+ isWindows: false,
52
+ shellType,
53
+ };
54
+ }
55
+ //# sourceMappingURL=shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/core/shell.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,MAAM,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAQtD,mEAAmE;AACnE,MAAM,UAAU,YAAY,CAAC,SAAkB;IAC9C,IAAI,SAAS,EAAE,CAAC;QACf,OAAO,EAAE,KAAK,EAAE,SAAS,IAAI,gBAAgB,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;IACpE,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAC3E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,SAAkB;IAC5C,MAAM,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;IAEnF,IAAI,SAAS,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC;YACzC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YAC/C,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACtB,IAAI,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,QAAQ,CAAC;IAC3C,CAAC;IAED,OAAO,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;AAC9B,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,UAAU;IACzB,IAAI,SAAS,EAAE,CAAC;QACf,OAAO;YACN,SAAS,EAAE,IAAI;YACf,IAAI,EAAE,gBAAgB;YACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,SAAS,EAAE,IAAI;YACf,SAAS,EAAE,YAAY;SACvB,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,WAAW,CAAC;IAC9C,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAC5F,OAAO;QACN,SAAS,EAAE,IAAI;QACf,IAAI;QACJ,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,SAAS,EAAE,KAAK;QAChB,SAAS;KACT,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ptykit — production-grade PTY sessions over WebSocket for Node & Bun.
3
+ *
4
+ * This is the main entry (`ptykit`): the core session engine plus the
5
+ * WebSocket transport server. The browser client lives at `ptykit/client`
6
+ * and the Svelte adapter at `ptykit/svelte`.
7
+ */
8
+ export * from './shared/index.js';
9
+ export * from './core/index.js';
10
+ export * from './server/index.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ptykit — production-grade PTY sessions over WebSocket for Node & Bun.
3
+ *
4
+ * This is the main entry (`ptykit`): the core session engine plus the
5
+ * WebSocket transport server. The browser client lives at `ptykit/client`
6
+ * and the Svelte adapter at `ptykit/svelte`.
7
+ */
8
+ export * from './shared/index.js';
9
+ export * from './core/index.js';
10
+ export * from './server/index.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Transport-agnostic connection + room model.
3
+ *
4
+ * A `PtyKitConnection` is one WebSocket client. The transport (Bun or Node)
5
+ * supplies the concrete object; the server logic only depends on this shape.
6
+ * Integrator-supplied identity rides on `data` (set at upgrade time) and is what
7
+ * the `authorize` hook inspects.
8
+ */
9
+ import type { WireFrame } from '../shared/index.js';
10
+ export interface PtyKitConnection {
11
+ /** Stable per-connection id. */
12
+ readonly id: string;
13
+ /** Integrator-attached identity/context, set at upgrade (e.g. `{ user }`). */
14
+ data: Record<string, unknown>;
15
+ /** Send a frame to this client. */
16
+ send(frame: WireFrame): void;
17
+ /** Close the underlying socket. */
18
+ close(): void;
19
+ }
20
+ /**
21
+ * Room registry for collaborative broadcast (R11).
22
+ *
23
+ * Output for a session broadcasts to its room (default = namespace); every
24
+ * connection in the room receives it and filters by `sessionId` client-side.
25
+ * N clients ↔ 1 session.
26
+ */
27
+ export declare class RoomRegistry {
28
+ private readonly rooms;
29
+ private readonly membership;
30
+ join(conn: PtyKitConnection, room: string): void;
31
+ /** Remove a connection from every room it joined (on disconnect). */
32
+ leaveAll(conn: PtyKitConnection): void;
33
+ /** Broadcast a frame to every connection in a room. */
34
+ broadcast(room: string, frame: WireFrame): void;
35
+ /** Number of connections currently in a room. */
36
+ size(room: string): number;
37
+ }
38
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/server/connection.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,MAAM,WAAW,gBAAgB;IAChC,gCAAgC;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,mCAAmC;IACnC,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,mCAAmC;IACnC,KAAK,IAAI,IAAI,CAAC;CACd;AAED;;;;;;GAMG;AACH,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4C;IAClE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA4C;IAEvE,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAgBhD,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAatC,uDAAuD;IACvD,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI;IAY/C,iDAAiD;IACjD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAG1B"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Transport-agnostic connection + room model.
3
+ *
4
+ * A `PtyKitConnection` is one WebSocket client. The transport (Bun or Node)
5
+ * supplies the concrete object; the server logic only depends on this shape.
6
+ * Integrator-supplied identity rides on `data` (set at upgrade time) and is what
7
+ * the `authorize` hook inspects.
8
+ */
9
+ /**
10
+ * Room registry for collaborative broadcast (R11).
11
+ *
12
+ * Output for a session broadcasts to its room (default = namespace); every
13
+ * connection in the room receives it and filters by `sessionId` client-side.
14
+ * N clients ↔ 1 session.
15
+ */
16
+ export class RoomRegistry {
17
+ rooms = new Map();
18
+ membership = new Map();
19
+ join(conn, room) {
20
+ let members = this.rooms.get(room);
21
+ if (!members) {
22
+ members = new Set();
23
+ this.rooms.set(room, members);
24
+ }
25
+ members.add(conn);
26
+ let joined = this.membership.get(conn);
27
+ if (!joined) {
28
+ joined = new Set();
29
+ this.membership.set(conn, joined);
30
+ }
31
+ joined.add(room);
32
+ }
33
+ /** Remove a connection from every room it joined (on disconnect). */
34
+ leaveAll(conn) {
35
+ const joined = this.membership.get(conn);
36
+ if (!joined)
37
+ return;
38
+ for (const room of joined) {
39
+ const members = this.rooms.get(room);
40
+ if (members) {
41
+ members.delete(conn);
42
+ if (members.size === 0)
43
+ this.rooms.delete(room);
44
+ }
45
+ }
46
+ this.membership.delete(conn);
47
+ }
48
+ /** Broadcast a frame to every connection in a room. */
49
+ broadcast(room, frame) {
50
+ const members = this.rooms.get(room);
51
+ if (!members)
52
+ return;
53
+ for (const conn of members) {
54
+ try {
55
+ conn.send(frame);
56
+ }
57
+ catch {
58
+ // A dead socket will be cleaned up on its close event.
59
+ }
60
+ }
61
+ }
62
+ /** Number of connections currently in a room. */
63
+ size(room) {
64
+ return this.rooms.get(room)?.size ?? 0;
65
+ }
66
+ }
67
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/server/connection.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH;;;;;;GAMG;AACH,MAAM,OAAO,YAAY;IACP,KAAK,GAAG,IAAI,GAAG,EAAiC,CAAC;IACjD,UAAU,GAAG,IAAI,GAAG,EAAiC,CAAC;IAEvE,IAAI,CAAC,IAAsB,EAAE,IAAY;QACxC,IAAI,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;YACpB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC/B,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAElB,IAAI,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;YACnB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACnC,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IAED,qEAAqE;IACrE,QAAQ,CAAC,IAAsB;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,OAAO,EAAE,CAAC;gBACb,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACrB,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;oBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACjD,CAAC;QACF,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,uDAAuD;IACvD,SAAS,CAAC,IAAY,EAAE,KAAgB;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACJ,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;YAAC,MAAM,CAAC;gBACR,uDAAuD;YACxD,CAAC;QACF,CAAC;IACF,CAAC;IAED,iDAAiD;IACjD,IAAI,CAAC,IAAY;QAChB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC;IACxC,CAAC;CACD"}
@@ -0,0 +1,2 @@
1
+ export declare function nextConnectionId(): string;
2
+ //# sourceMappingURL=ids.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../../src/server/ids.ts"],"names":[],"mappings":"AAGA,wBAAgB,gBAAgB,IAAI,MAAM,CAGzC"}
@@ -0,0 +1,7 @@
1
+ /** Monotonic connection-id generator (process-local). */
2
+ let counter = 0;
3
+ export function nextConnectionId() {
4
+ counter += 1;
5
+ return `conn-${counter}-${Math.random().toString(36).slice(2, 8)}`;
6
+ }
7
+ //# sourceMappingURL=ids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids.js","sourceRoot":"","sources":["../../src/server/ids.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,IAAI,OAAO,GAAG,CAAC,CAAC;AAEhB,MAAM,UAAU,gBAAgB;IAC/B,OAAO,IAAI,CAAC,CAAC;IACb,OAAO,QAAQ,OAAO,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AACpE,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * WebSocket transport server — barrel.
3
+ */
4
+ export { PtyKitServer, createPtyKitServer, type PtyKitServerOptions, type AuthorizeContext, type AuthorizeHook, type AuthorizeOperation, type RoomContext, type RoomResolver, type UpgradeHook, } from './pty-kit-server.js';
5
+ export { RoomRegistry, type PtyKitConnection } from './connection.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,GAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * WebSocket transport server — barrel.
3
+ */
4
+ export { PtyKitServer, createPtyKitServer, } from './pty-kit-server.js';
5
+ export { RoomRegistry } from './connection.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACN,YAAY,EACZ,kBAAkB,GAQlB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAyB,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * `createPtyKitServer` — the WebSocket transport over a `PtyKit` manager.
3
+ *
4
+ * WebSocket only (R9). Control plane = RPC request/response keyed by
5
+ * `requestId`; data plane = events broadcast to a collaborative room (R11) and
6
+ * filtered client-side by `sessionId`. Access is enforced by the `authorize`
7
+ * hook with anti-hijack ownership checks (R10).
8
+ */
9
+ import type { PtyKit } from '../core/pty-kit.js';
10
+ import { type Logger } from '../shared/index.js';
11
+ import { type PtyKitConnection } from './connection.js';
12
+ /** The operation an `authorize` check guards. */
13
+ export type AuthorizeOperation = 'create' | 'attach' | 'write' | 'resize' | 'kill';
14
+ export interface AuthorizeContext {
15
+ operation: AuthorizeOperation;
16
+ namespace: string;
17
+ sessionId?: string;
18
+ conn: PtyKitConnection;
19
+ }
20
+ /** Decide whether a connection may perform an operation. Return `false` to deny. */
21
+ export type AuthorizeHook = (ctx: AuthorizeContext) => boolean | Promise<boolean>;
22
+ export interface RoomContext {
23
+ namespace: string;
24
+ sessionId?: string;
25
+ conn: PtyKitConnection;
26
+ }
27
+ /** Resolve the broadcast room for a context. Default: the namespace. */
28
+ export type RoomResolver = (ctx: RoomContext) => string;
29
+ /** Per-connection identity factory, run at upgrade. Return `false` to reject. */
30
+ export type UpgradeHook = (request: any) => Record<string, unknown> | false | Promise<Record<string, unknown> | false>;
31
+ export interface PtyKitServerOptions {
32
+ /** WebSocket path to accept upgrades on. Default `'/'`. */
33
+ path?: string;
34
+ /**
35
+ * Access hook (R10). **Defaults to allow-all** for local DX — production
36
+ * deployments MUST provide one. See the README security note.
37
+ */
38
+ authorize?: AuthorizeHook;
39
+ /** Collaborative room resolver (R11). Default `(ctx) => ctx.namespace`. */
40
+ room?: RoomResolver;
41
+ /** Attach identity to a connection at upgrade; return `false` to reject. */
42
+ onUpgrade?: UpgradeHook;
43
+ /** Diagnostics sink. Off by default. */
44
+ logger?: Logger;
45
+ }
46
+ export declare class PtyKitServer {
47
+ readonly manager: PtyKit;
48
+ readonly path: string;
49
+ private readonly rooms;
50
+ private readonly options;
51
+ private readonly logger;
52
+ private readonly resolveRoom;
53
+ private bunTransport?;
54
+ private nodeTransport?;
55
+ constructor(manager: PtyKit, options?: PtyKitServerOptions);
56
+ /** Resolve connection identity at upgrade. Returns `false` to reject. */
57
+ resolveUpgrade(request: any): Promise<Record<string, unknown> | false>;
58
+ handleOpen(_conn: PtyKitConnection): void;
59
+ handleClose(conn: PtyKitConnection): void;
60
+ /** Process one inbound raw message (string or bytes) from a connection. */
61
+ handleMessage(conn: PtyKitConnection, raw: string | ArrayBuffer | Uint8Array): Promise<void>;
62
+ private dispatch;
63
+ private respond;
64
+ private respondError;
65
+ private authorize;
66
+ /** Look up a session, deriving the namespace for an ownership check. */
67
+ private requireSession;
68
+ private broadcast;
69
+ /**
70
+ * Install the single room-broadcast listener pair for a session, after
71
+ * clearing any previous listeners (R7c) so switching/returning clients never
72
+ * double up output. Output broadcasts to the whole room (collaborative);
73
+ * clients filter by `sessionId`.
74
+ */
75
+ private wireBroadcast;
76
+ private handleEvent;
77
+ private handleRpc;
78
+ private opCreateSession;
79
+ private opResize;
80
+ private opCancel;
81
+ private opKill;
82
+ private opClear;
83
+ private opPtyStatus;
84
+ private opListSessions;
85
+ private opStreamStatus;
86
+ private opMissedOutput;
87
+ private opReconnect;
88
+ /** Mount onto a Node `http.Server` (uses the optional `ws` package). */
89
+ attach(httpServer: any): Promise<void>;
90
+ /** Bun `fetch` handler — upgrades matching requests; returns undefined otherwise. */
91
+ get fetch(): (request: Request, bunServer: any) => Promise<Response | undefined>;
92
+ /** Bun `websocket` handlers object to pass to `Bun.serve`. */
93
+ get websocket(): {
94
+ open: (ws: any) => void;
95
+ message: (ws: any, message: string | ArrayBuffer | Uint8Array) => void;
96
+ close: (ws: any) => void;
97
+ };
98
+ }
99
+ /** Create a WebSocket server over a `PtyKit` manager. */
100
+ export declare function createPtyKitServer(manager: PtyKit, options?: PtyKitServerOptions): PtyKitServer;
101
+ //# sourceMappingURL=pty-kit-server.d.ts.map