@code-yeongyu/senpi-pty 2026.7.25-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +56 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +130 -0
- package/dist/loader.js.map +1 -0
- package/dist/native-loader.d.ts +2 -0
- package/dist/native-loader.d.ts.map +1 -0
- package/dist/native-loader.js +2 -0
- package/dist/native-loader.js.map +1 -0
- package/dist/pipe-fallback.d.ts +58 -0
- package/dist/pipe-fallback.d.ts.map +1 -0
- package/dist/pipe-fallback.js +192 -0
- package/dist/pipe-fallback.js.map +1 -0
- package/dist/registry-detached.d.ts +5 -0
- package/dist/registry-detached.d.ts.map +1 -0
- package/dist/registry-detached.js +57 -0
- package/dist/registry-detached.js.map +1 -0
- package/dist/registry-session.d.ts +7 -0
- package/dist/registry-session.d.ts.map +1 -0
- package/dist/registry-session.js +73 -0
- package/dist/registry-session.js.map +1 -0
- package/dist/registry-types.d.ts +94 -0
- package/dist/registry-types.d.ts.map +1 -0
- package/dist/registry-types.js +9 -0
- package/dist/registry-types.js.map +1 -0
- package/dist/registry.d.ts +41 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +232 -0
- package/dist/registry.js.map +1 -0
- package/dist/screen.d.ts +33 -0
- package/dist/screen.d.ts.map +1 -0
- package/dist/screen.js +155 -0
- package/dist/screen.js.map +1 -0
- package/dist/session-exit.d.ts +8 -0
- package/dist/session-exit.d.ts.map +1 -0
- package/dist/session-exit.js +103 -0
- package/dist/session-exit.js.map +1 -0
- package/dist/session-native.d.ts +4 -0
- package/dist/session-native.d.ts.map +1 -0
- package/dist/session-native.js +94 -0
- package/dist/session-native.js.map +1 -0
- package/dist/session-options.d.ts +10 -0
- package/dist/session-options.d.ts.map +1 -0
- package/dist/session-options.js +38 -0
- package/dist/session-options.js.map +1 -0
- package/dist/session-types.d.ts +64 -0
- package/dist/session-types.d.ts.map +1 -0
- package/dist/session-types.js +2 -0
- package/dist/session-types.js.map +1 -0
- package/dist/session.d.ts +47 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +187 -0
- package/dist/session.js.map +1 -0
- package/native/check-prebuild-fresh.mjs +171 -0
- package/native/index.d.ts +25 -0
- package/native/index.js +30 -0
- package/native/prebuilds/darwin-arm64/senpi_pty.darwin-arm64.node +0 -0
- package/package.json +61 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAiB,KAAK,mBAAmB,EAAE,KAAK,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAUlH,OAAO,KAAK,EAEX,sBAAsB,EACtB,0BAA0B,EAC1B,2BAA2B,EAC3B,mBAAmB,EACnB,wBAAwB,EAExB,8BAA8B,EAC9B,sBAAsB,EACtB,qBAAqB,EACrB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EACX,2BAA2B,EAC3B,sBAAsB,EACtB,0BAA0B,EAC1B,2BAA2B,EAC3B,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,EACrB,4BAA4B,EAC5B,8BAA8B,EAC9B,sBAAsB,EACtB,qBAAqB,GACrB,MAAM,oBAAoB,CAAC;AAE5B,qBAAa,eAAe;IAC3B,QAAQ,CAAC,OAAO,EAAE,sBAAsB,CAAC;IACzC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IACvD,OAAO,CAAC,QAAQ,CAAC,6BAA6B,CAAC,CAA8B;IAC7E,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA+C;IACnE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyC;IACtE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyB;IACtD,OAAO,CAAC,aAAa,CAAsC;IAC3D,OAAO,CAAC,YAAY,CAAuC;IAC3D,OAAO,CAAC,WAAW,CAA6C;IAChE,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,aAAa,CAAmB;IACxC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,sBAAsB,CAA6B;IAE3D,YAAY,OAAO,GAAE,sBAA2B,EAAE,YAAY,GAAE,2BAAgC,EAU/F;IAED,IAAI,MAAM,IAAI,mBAAmB,CAEhC;IAED,IAAI,qBAAqB,IAAI,8BAA8B,GAAG,IAAI,CAGjE;IAED,IAAI,OAAO,IAAI,sBAAsB,GAAG,IAAI,CAE3C;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,MAAM,IAAI,aAAa,GAAG,SAAS,GAAG,QAAQ,CAEjD;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,UAAU,IAAI,mBAAmB,GAAG,IAAI,CAE3C;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,SAAS,IAAI,wBAAwB,CAGxC;IAED,KAAK,IAAI,IAAI,CA2BZ;IAED,MAAM,CAAC,OAAO,EAAE,0BAA0B,GAAG,MAAM,IAAI,CAGtD;IAED,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAItC;IAED,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,8BAA8B,CAK/D;IAED,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,8BAA8B,CAKjE;IAED,IAAI,CAAC,MAAM,GAAE,qBAAiC,GAAG,8BAA8B,CAe9E;IAED,IAAI,IAAI,8BAA8B,CAErC;IAEK,QAAQ,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAI7C;YAEa,eAAe;IAU7B,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,aAAa;CAUrB;AAED,wBAAgB,qBAAqB,CACpC,OAAO,GAAE,sBAA2B,EACpC,YAAY,GAAE,2BAAgC,GAC5C,eAAe,CAEjB"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { loadNativePty } from "./native-loader.js";
|
|
4
|
+
import { PipeFallbackSession, shouldUsePipeFallback } from "./pipe-fallback.js";
|
|
5
|
+
import { exitedOperation, normalizeOperationResult, normalizeTerminalExit, notStartedOperation, } from "./session-exit.js";
|
|
6
|
+
import { getNativeSessionFactory } from "./session-native.js";
|
|
7
|
+
import { defaultCommand, normalizeRawTailBytes, toNativeOptions, toPipeFallbackOptions } from "./session-options.js";
|
|
8
|
+
export class TerminalSession {
|
|
9
|
+
constructor(options = {}, dependencies = {}) {
|
|
10
|
+
this.dataHandlers = new Set();
|
|
11
|
+
this.exitHandlers = new Set();
|
|
12
|
+
this.backendHandle = null;
|
|
13
|
+
this.backendValue = null;
|
|
14
|
+
this.exitPromise = null;
|
|
15
|
+
this.settledExit = null;
|
|
16
|
+
this.rawTailBuffer = Buffer.alloc(0);
|
|
17
|
+
this.rawByteCount = 0;
|
|
18
|
+
this.killRequested = false;
|
|
19
|
+
this.unsubscribeBackendData = null;
|
|
20
|
+
this.options = {
|
|
21
|
+
...options,
|
|
22
|
+
args: options.args ? [...options.args] : undefined,
|
|
23
|
+
env: options.env ? { ...options.env } : undefined,
|
|
24
|
+
};
|
|
25
|
+
this.nativeLoadResult = dependencies.nativeLoadResult ?? loadNativePty();
|
|
26
|
+
this.createNativeSessionDependency = dependencies.createNativeSession;
|
|
27
|
+
this.env = dependencies.env ?? process.env;
|
|
28
|
+
this.rawTailLimit = normalizeRawTailBytes(options.rawTailBytes);
|
|
29
|
+
}
|
|
30
|
+
get native() {
|
|
31
|
+
return this.nativeLoadResult;
|
|
32
|
+
}
|
|
33
|
+
get unavailableDiagnostic() {
|
|
34
|
+
if (this.nativeLoadResult.native !== null)
|
|
35
|
+
return null;
|
|
36
|
+
return this.nativeLoadResult.diagnostic;
|
|
37
|
+
}
|
|
38
|
+
get backend() {
|
|
39
|
+
return this.backendValue;
|
|
40
|
+
}
|
|
41
|
+
get command() {
|
|
42
|
+
return this.options.command ?? defaultCommand();
|
|
43
|
+
}
|
|
44
|
+
get status() {
|
|
45
|
+
return this.exitState.status;
|
|
46
|
+
}
|
|
47
|
+
get exited() {
|
|
48
|
+
return this.settledExit !== null;
|
|
49
|
+
}
|
|
50
|
+
get isExited() {
|
|
51
|
+
return this.exited;
|
|
52
|
+
}
|
|
53
|
+
get exitResult() {
|
|
54
|
+
return this.settledExit;
|
|
55
|
+
}
|
|
56
|
+
get rawTail() {
|
|
57
|
+
return Buffer.from(this.rawTailBuffer);
|
|
58
|
+
}
|
|
59
|
+
get rawOutputBytes() {
|
|
60
|
+
return this.rawByteCount;
|
|
61
|
+
}
|
|
62
|
+
get exitState() {
|
|
63
|
+
if (this.settledExit !== null)
|
|
64
|
+
return { status: "exited", exit: this.settledExit };
|
|
65
|
+
return { status: this.backendHandle === null ? "not_started" : "running", exit: null };
|
|
66
|
+
}
|
|
67
|
+
start() {
|
|
68
|
+
if (this.settledExit !== null)
|
|
69
|
+
throw new Error("Cannot restart exited terminal session");
|
|
70
|
+
if (this.backendHandle !== null)
|
|
71
|
+
throw new Error("Terminal session has already been started");
|
|
72
|
+
const nativeFactory = this.createNativeSessionDependency ?? getNativeSessionFactory(this.nativeLoadResult);
|
|
73
|
+
if (nativeFactory && !shouldUsePipeFallback(this.nativeLoadResult, this.env)) {
|
|
74
|
+
this.backendValue = "native";
|
|
75
|
+
this.backendHandle = nativeFactory(toNativeOptions(this.options), (chunk) => this.emitData(chunk));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
this.backendValue = "pipe-fallback";
|
|
79
|
+
const fallback = new PipeFallbackSession(toPipeFallbackOptions(this.options));
|
|
80
|
+
this.backendHandle = fallback;
|
|
81
|
+
this.unsubscribeBackendData = fallback.onData((chunk) => this.emitData(chunk));
|
|
82
|
+
fallback.start();
|
|
83
|
+
}
|
|
84
|
+
const backendHandle = this.backendHandle;
|
|
85
|
+
const backendValue = this.backendValue;
|
|
86
|
+
if (backendHandle === null || backendValue === null) {
|
|
87
|
+
throw new Error("Terminal session backend did not initialize");
|
|
88
|
+
}
|
|
89
|
+
if (backendValue === "native" && backendHandle.onData) {
|
|
90
|
+
this.unsubscribeBackendData = backendHandle.onData((chunk) => this.emitData(chunk));
|
|
91
|
+
}
|
|
92
|
+
this.exitPromise = this.waitBackendExit(backendHandle, backendValue).then((exit) => this.settleExit(exit));
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
onData(handler) {
|
|
96
|
+
this.dataHandlers.add(handler);
|
|
97
|
+
return () => this.dataHandlers.delete(handler);
|
|
98
|
+
}
|
|
99
|
+
onExit(handler) {
|
|
100
|
+
if (this.settledExit !== null)
|
|
101
|
+
queueMicrotask(handler);
|
|
102
|
+
else
|
|
103
|
+
this.exitHandlers.add(handler);
|
|
104
|
+
return () => this.exitHandlers.delete(handler);
|
|
105
|
+
}
|
|
106
|
+
write(data) {
|
|
107
|
+
const handle = this.backendHandle;
|
|
108
|
+
if (this.settledExit !== null)
|
|
109
|
+
return exitedOperation("write");
|
|
110
|
+
if (handle === null)
|
|
111
|
+
return notStartedOperation("write");
|
|
112
|
+
return normalizeOperationResult(handle.write(data), "Wrote data to terminal session.");
|
|
113
|
+
}
|
|
114
|
+
resize(cols, rows) {
|
|
115
|
+
const handle = this.backendHandle;
|
|
116
|
+
if (this.settledExit !== null)
|
|
117
|
+
return exitedOperation("resize");
|
|
118
|
+
if (handle === null)
|
|
119
|
+
return notStartedOperation("resize");
|
|
120
|
+
return normalizeOperationResult(handle.resize(cols, rows), `Resized terminal session to ${cols}x${rows}.`);
|
|
121
|
+
}
|
|
122
|
+
kill(signal = "SIGTERM") {
|
|
123
|
+
const handle = this.backendHandle;
|
|
124
|
+
if (this.killRequested || this.settledExit !== null) {
|
|
125
|
+
return {
|
|
126
|
+
ok: true,
|
|
127
|
+
idempotent: true,
|
|
128
|
+
note: "Terminal session kill was already requested.",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (handle === null)
|
|
132
|
+
return notStartedOperation("kill");
|
|
133
|
+
this.killRequested = true;
|
|
134
|
+
const result = normalizeOperationResult(handle.kill(signal), `Sent ${signal} to terminal session.`);
|
|
135
|
+
if (!result.ok)
|
|
136
|
+
this.killRequested = false;
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
stop() {
|
|
140
|
+
return this.kill();
|
|
141
|
+
}
|
|
142
|
+
async waitExit() {
|
|
143
|
+
if (this.settledExit !== null)
|
|
144
|
+
return this.settledExit;
|
|
145
|
+
if (this.exitPromise === null)
|
|
146
|
+
throw new Error("Cannot wait for terminal session exit before start");
|
|
147
|
+
return await this.exitPromise;
|
|
148
|
+
}
|
|
149
|
+
async waitBackendExit(handle, backend) {
|
|
150
|
+
const wait = handle.waitExit ?? handle.wait;
|
|
151
|
+
if (!wait)
|
|
152
|
+
throw new Error("Terminal session backend does not expose waitExit or wait");
|
|
153
|
+
const exit = await wait.call(handle);
|
|
154
|
+
return normalizeTerminalExit(exit, backend, this.killRequested);
|
|
155
|
+
}
|
|
156
|
+
settleExit(exit) {
|
|
157
|
+
if (this.settledExit !== null)
|
|
158
|
+
return this.settledExit;
|
|
159
|
+
this.settledExit = exit;
|
|
160
|
+
this.unsubscribeBackendData?.();
|
|
161
|
+
this.unsubscribeBackendData = null;
|
|
162
|
+
for (const handler of this.exitHandlers)
|
|
163
|
+
handler();
|
|
164
|
+
this.exitHandlers.clear();
|
|
165
|
+
return exit;
|
|
166
|
+
}
|
|
167
|
+
emitData(chunk) {
|
|
168
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
169
|
+
this.appendRawTail(buffer);
|
|
170
|
+
for (const handler of this.dataHandlers)
|
|
171
|
+
handler(buffer);
|
|
172
|
+
}
|
|
173
|
+
appendRawTail(chunk) {
|
|
174
|
+
this.rawByteCount += chunk.byteLength;
|
|
175
|
+
if (this.rawTailLimit === 0) {
|
|
176
|
+
this.rawTailBuffer = Buffer.alloc(0);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const next = Buffer.concat([this.rawTailBuffer, chunk]);
|
|
180
|
+
this.rawTailBuffer =
|
|
181
|
+
next.byteLength <= this.rawTailLimit ? next : next.subarray(next.byteLength - this.rawTailLimit);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
export function createTerminalSession(options = {}, dependencies = {}) {
|
|
185
|
+
return new TerminalSession(options, dependencies).start();
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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","sourcesContent":["import { Buffer } from \"node:buffer\";\nimport process from \"node:process\";\nimport { loadNativePty, type NativePtyLoadResult, type NativePtyUnavailableDiagnostic } from \"./native-loader.ts\";\nimport { PipeFallbackSession, shouldUsePipeFallback } from \"./pipe-fallback.ts\";\nimport {\n\texitedOperation,\n\tnormalizeOperationResult,\n\tnormalizeTerminalExit,\n\tnotStartedOperation,\n} from \"./session-exit.ts\";\nimport { getNativeSessionFactory } from \"./session-native.ts\";\nimport { defaultCommand, normalizeRawTailBytes, toNativeOptions, toPipeFallbackOptions } from \"./session-options.ts\";\nimport type {\n\tCreateNativeTerminalSession,\n\tTerminalSessionBackend,\n\tTerminalSessionDataHandler,\n\tTerminalSessionDependencies,\n\tTerminalSessionExit,\n\tTerminalSessionExitState,\n\tTerminalSessionHandle,\n\tTerminalSessionOperationResult,\n\tTerminalSessionOptions,\n\tTerminalSessionSignal,\n} from \"./session-types.ts\";\n\nexport type {\n\tCreateNativeTerminalSession,\n\tTerminalSessionBackend,\n\tTerminalSessionDataHandler,\n\tTerminalSessionDependencies,\n\tTerminalSessionExit,\n\tTerminalSessionExitError,\n\tTerminalSessionExitState,\n\tTerminalSessionHandle,\n\tTerminalSessionNativeOptions,\n\tTerminalSessionOperationResult,\n\tTerminalSessionOptions,\n\tTerminalSessionSignal,\n} from \"./session-types.ts\";\n\nexport class TerminalSession {\n\treadonly options: TerminalSessionOptions;\n\tprivate readonly nativeLoadResult: NativePtyLoadResult;\n\tprivate readonly createNativeSessionDependency?: CreateNativeTerminalSession;\n\tprivate readonly env: Readonly<Record<string, string | undefined>>;\n\tprivate readonly rawTailLimit: number;\n\tprivate readonly dataHandlers = new Set<TerminalSessionDataHandler>();\n\tprivate readonly exitHandlers = new Set<() => void>();\n\tprivate backendHandle: TerminalSessionHandle | null = null;\n\tprivate backendValue: TerminalSessionBackend | null = null;\n\tprivate exitPromise: Promise<TerminalSessionExit> | null = null;\n\tprivate settledExit: TerminalSessionExit | null = null;\n\tprivate rawTailBuffer = Buffer.alloc(0);\n\tprivate rawByteCount = 0;\n\tprivate killRequested = false;\n\tprivate unsubscribeBackendData: (() => void) | null = null;\n\n\tconstructor(options: TerminalSessionOptions = {}, dependencies: TerminalSessionDependencies = {}) {\n\t\tthis.options = {\n\t\t\t...options,\n\t\t\targs: options.args ? [...options.args] : undefined,\n\t\t\tenv: options.env ? { ...options.env } : undefined,\n\t\t};\n\t\tthis.nativeLoadResult = dependencies.nativeLoadResult ?? loadNativePty();\n\t\tthis.createNativeSessionDependency = dependencies.createNativeSession;\n\t\tthis.env = dependencies.env ?? process.env;\n\t\tthis.rawTailLimit = normalizeRawTailBytes(options.rawTailBytes);\n\t}\n\n\tget native(): NativePtyLoadResult {\n\t\treturn this.nativeLoadResult;\n\t}\n\n\tget unavailableDiagnostic(): NativePtyUnavailableDiagnostic | null {\n\t\tif (this.nativeLoadResult.native !== null) return null;\n\t\treturn this.nativeLoadResult.diagnostic;\n\t}\n\n\tget backend(): TerminalSessionBackend | null {\n\t\treturn this.backendValue;\n\t}\n\n\tget command(): string {\n\t\treturn this.options.command ?? defaultCommand();\n\t}\n\n\tget status(): \"not_started\" | \"running\" | \"exited\" {\n\t\treturn this.exitState.status;\n\t}\n\n\tget exited(): boolean {\n\t\treturn this.settledExit !== null;\n\t}\n\n\tget isExited(): boolean {\n\t\treturn this.exited;\n\t}\n\n\tget exitResult(): TerminalSessionExit | null {\n\t\treturn this.settledExit;\n\t}\n\n\tget rawTail(): Buffer {\n\t\treturn Buffer.from(this.rawTailBuffer);\n\t}\n\n\tget rawOutputBytes(): number {\n\t\treturn this.rawByteCount;\n\t}\n\n\tget exitState(): TerminalSessionExitState {\n\t\tif (this.settledExit !== null) return { status: \"exited\", exit: this.settledExit };\n\t\treturn { status: this.backendHandle === null ? \"not_started\" : \"running\", exit: null };\n\t}\n\n\tstart(): this {\n\t\tif (this.settledExit !== null) throw new Error(\"Cannot restart exited terminal session\");\n\t\tif (this.backendHandle !== null) throw new Error(\"Terminal session has already been started\");\n\n\t\tconst nativeFactory = this.createNativeSessionDependency ?? getNativeSessionFactory(this.nativeLoadResult);\n\t\tif (nativeFactory && !shouldUsePipeFallback(this.nativeLoadResult, this.env)) {\n\t\t\tthis.backendValue = \"native\";\n\t\t\tthis.backendHandle = nativeFactory(toNativeOptions(this.options), (chunk) => this.emitData(chunk));\n\t\t} else {\n\t\t\tthis.backendValue = \"pipe-fallback\";\n\t\t\tconst fallback = new PipeFallbackSession(toPipeFallbackOptions(this.options));\n\t\t\tthis.backendHandle = fallback;\n\t\t\tthis.unsubscribeBackendData = fallback.onData((chunk) => this.emitData(chunk));\n\t\t\tfallback.start();\n\t\t}\n\n\t\tconst backendHandle = this.backendHandle;\n\t\tconst backendValue = this.backendValue;\n\t\tif (backendHandle === null || backendValue === null) {\n\t\t\tthrow new Error(\"Terminal session backend did not initialize\");\n\t\t}\n\n\t\tif (backendValue === \"native\" && backendHandle.onData) {\n\t\t\tthis.unsubscribeBackendData = backendHandle.onData((chunk) => this.emitData(chunk));\n\t\t}\n\t\tthis.exitPromise = this.waitBackendExit(backendHandle, backendValue).then((exit) => this.settleExit(exit));\n\t\treturn this;\n\t}\n\n\tonData(handler: TerminalSessionDataHandler): () => void {\n\t\tthis.dataHandlers.add(handler);\n\t\treturn () => this.dataHandlers.delete(handler);\n\t}\n\n\tonExit(handler: () => void): () => void {\n\t\tif (this.settledExit !== null) queueMicrotask(handler);\n\t\telse this.exitHandlers.add(handler);\n\t\treturn () => this.exitHandlers.delete(handler);\n\t}\n\n\twrite(data: string | Uint8Array): TerminalSessionOperationResult {\n\t\tconst handle = this.backendHandle;\n\t\tif (this.settledExit !== null) return exitedOperation(\"write\");\n\t\tif (handle === null) return notStartedOperation(\"write\");\n\t\treturn normalizeOperationResult(handle.write(data), \"Wrote data to terminal session.\");\n\t}\n\n\tresize(cols: number, rows: number): TerminalSessionOperationResult {\n\t\tconst handle = this.backendHandle;\n\t\tif (this.settledExit !== null) return exitedOperation(\"resize\");\n\t\tif (handle === null) return notStartedOperation(\"resize\");\n\t\treturn normalizeOperationResult(handle.resize(cols, rows), `Resized terminal session to ${cols}x${rows}.`);\n\t}\n\n\tkill(signal: TerminalSessionSignal = \"SIGTERM\"): TerminalSessionOperationResult {\n\t\tconst handle = this.backendHandle;\n\t\tif (this.killRequested || this.settledExit !== null) {\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tidempotent: true,\n\t\t\t\tnote: \"Terminal session kill was already requested.\",\n\t\t\t};\n\t\t}\n\t\tif (handle === null) return notStartedOperation(\"kill\");\n\n\t\tthis.killRequested = true;\n\t\tconst result = normalizeOperationResult(handle.kill(signal), `Sent ${signal} to terminal session.`);\n\t\tif (!result.ok) this.killRequested = false;\n\t\treturn result;\n\t}\n\n\tstop(): TerminalSessionOperationResult {\n\t\treturn this.kill();\n\t}\n\n\tasync waitExit(): Promise<TerminalSessionExit> {\n\t\tif (this.settledExit !== null) return this.settledExit;\n\t\tif (this.exitPromise === null) throw new Error(\"Cannot wait for terminal session exit before start\");\n\t\treturn await this.exitPromise;\n\t}\n\n\tprivate async waitBackendExit(\n\t\thandle: TerminalSessionHandle,\n\t\tbackend: TerminalSessionBackend,\n\t): Promise<TerminalSessionExit> {\n\t\tconst wait = handle.waitExit ?? handle.wait;\n\t\tif (!wait) throw new Error(\"Terminal session backend does not expose waitExit or wait\");\n\t\tconst exit = await wait.call(handle);\n\t\treturn normalizeTerminalExit(exit, backend, this.killRequested);\n\t}\n\n\tprivate settleExit(exit: TerminalSessionExit): TerminalSessionExit {\n\t\tif (this.settledExit !== null) return this.settledExit;\n\t\tthis.settledExit = exit;\n\t\tthis.unsubscribeBackendData?.();\n\t\tthis.unsubscribeBackendData = null;\n\t\tfor (const handler of this.exitHandlers) handler();\n\t\tthis.exitHandlers.clear();\n\t\treturn exit;\n\t}\n\n\tprivate emitData(chunk: Buffer | Uint8Array | string): void {\n\t\tconst buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n\t\tthis.appendRawTail(buffer);\n\t\tfor (const handler of this.dataHandlers) handler(buffer);\n\t}\n\n\tprivate appendRawTail(chunk: Buffer): void {\n\t\tthis.rawByteCount += chunk.byteLength;\n\t\tif (this.rawTailLimit === 0) {\n\t\t\tthis.rawTailBuffer = Buffer.alloc(0);\n\t\t\treturn;\n\t\t}\n\t\tconst next = Buffer.concat([this.rawTailBuffer, chunk]);\n\t\tthis.rawTailBuffer =\n\t\t\tnext.byteLength <= this.rawTailLimit ? next : next.subarray(next.byteLength - this.rawTailLimit);\n\t}\n}\n\nexport function createTerminalSession(\n\toptions: TerminalSessionOptions = {},\n\tdependencies: TerminalSessionDependencies = {},\n): TerminalSession {\n\treturn new TerminalSession(options, dependencies).start();\n}\n"]}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { constants as fsConstants } from "node:fs";
|
|
4
|
+
import { access, copyFile, mkdir, mkdtemp, readFile, rm } from "node:fs/promises";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const defaultRootDir = join(scriptDir, "..", "..", "..");
|
|
11
|
+
const NAPI_CLI_VERSION = "3.7.2";
|
|
12
|
+
|
|
13
|
+
const napiTargetByHost = new Map([
|
|
14
|
+
["darwin-arm64", "aarch64-apple-darwin"],
|
|
15
|
+
["darwin-x64", "x86_64-apple-darwin"],
|
|
16
|
+
["linux-arm64", "aarch64-unknown-linux-gnu.2.17"],
|
|
17
|
+
["linux-x64", "x86_64-unknown-linux-gnu.2.17"],
|
|
18
|
+
["win32-arm64", "aarch64-pc-windows-msvc"],
|
|
19
|
+
["win32-x64", "x86_64-pc-windows-msvc"],
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export function getHost(platform = process.platform, arch = process.arch) {
|
|
23
|
+
return `${platform}-${arch}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getVendoredPrebuildPath(rootDir, host) {
|
|
27
|
+
return join(rootDir, "packages", "pty", "native", "prebuilds", host, `senpi_pty.${host}.node`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function checkPrebuildFreshness(options = {}) {
|
|
31
|
+
const rootDir = options.rootDir ?? defaultRootDir;
|
|
32
|
+
const host = options.host ?? getHost();
|
|
33
|
+
const napiTarget = options.napiTarget ?? napiTargetByHost.get(host);
|
|
34
|
+
if (!napiTarget) {
|
|
35
|
+
throw new Error(`error: unsupported native prebuild host target ${host}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const tempDir = options.tempDir ?? (await mkdtemp(join(tmpdir(), `senpi-pty-prebuild-${host}-`)));
|
|
39
|
+
const ownsTempDir = options.tempDir === undefined;
|
|
40
|
+
try {
|
|
41
|
+
const builtFile =
|
|
42
|
+
options.builtFile ??
|
|
43
|
+
(await buildHostPrebuild({
|
|
44
|
+
host,
|
|
45
|
+
napiTarget,
|
|
46
|
+
outputDir: join(tempDir, "output"),
|
|
47
|
+
rootDir,
|
|
48
|
+
targetDir: join(rootDir, "target", "senpi-pty-prebuild", host),
|
|
49
|
+
}));
|
|
50
|
+
const vendoredFile = getVendoredPrebuildPath(rootDir, host);
|
|
51
|
+
|
|
52
|
+
if (options.update) {
|
|
53
|
+
await mkdir(dirname(vendoredFile), { recursive: true });
|
|
54
|
+
await copyFile(builtFile, vendoredFile);
|
|
55
|
+
return { builtFile, host, napiTarget, status: "updated", vendoredFile };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await assertFileExists(vendoredFile, `error: missing vendored prebuild for ${host}: ${vendoredFile}`);
|
|
59
|
+
const [builtBytes, vendoredBytes] = await Promise.all([readFile(builtFile), readFile(vendoredFile)]);
|
|
60
|
+
if (!builtBytes.equals(vendoredBytes)) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`error: stale vendored prebuild for ${host}: ${vendoredFile} differs from rebuilt ${builtFile} (${napiTarget})`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { builtFile, host, napiTarget, status: "fresh", vendoredFile };
|
|
67
|
+
} finally {
|
|
68
|
+
if (ownsTempDir) await rm(tempDir, { force: true, recursive: true });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function buildHostPrebuild({ rootDir, host, napiTarget, outputDir, targetDir }) {
|
|
73
|
+
await mkdir(outputDir, { recursive: true });
|
|
74
|
+
await run(
|
|
75
|
+
"npm",
|
|
76
|
+
[
|
|
77
|
+
"exec",
|
|
78
|
+
"--yes",
|
|
79
|
+
"--package",
|
|
80
|
+
`@napi-rs/cli@${NAPI_CLI_VERSION}`,
|
|
81
|
+
"--",
|
|
82
|
+
"napi",
|
|
83
|
+
"build",
|
|
84
|
+
"--manifest-path",
|
|
85
|
+
"crates/senpi-pty/Cargo.toml",
|
|
86
|
+
"--package-json-path",
|
|
87
|
+
"crates/senpi-pty/package.json",
|
|
88
|
+
"--target",
|
|
89
|
+
napiTarget,
|
|
90
|
+
"--target-dir",
|
|
91
|
+
targetDir,
|
|
92
|
+
"--output-dir",
|
|
93
|
+
outputDir,
|
|
94
|
+
"--release",
|
|
95
|
+
"--platform",
|
|
96
|
+
"--",
|
|
97
|
+
"--locked",
|
|
98
|
+
],
|
|
99
|
+
rootDir,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const builtFile = join(outputDir, `senpi_pty.${host}.node`);
|
|
103
|
+
await assertFileExists(builtFile, `error: napi build did not produce expected host target ${host}: ${builtFile}`);
|
|
104
|
+
return builtFile;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function run(command, args, cwd) {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const child = spawn(command, args, {
|
|
110
|
+
cwd,
|
|
111
|
+
env: { ...process.env, RUSTFLAGS: appendRustflags(process.env.RUSTFLAGS, `--remap-path-prefix=${cwd}=.`) },
|
|
112
|
+
shell: false,
|
|
113
|
+
stdio: "inherit",
|
|
114
|
+
});
|
|
115
|
+
child.on("error", reject);
|
|
116
|
+
child.on("close", (code) => {
|
|
117
|
+
if (code === 0) {
|
|
118
|
+
resolve();
|
|
119
|
+
} else {
|
|
120
|
+
reject(new Error(`error: ${command} ${args.join(" ")} failed with exit ${code ?? 1}`));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function appendRustflags(current, flag) {
|
|
127
|
+
return current ? `${current} ${flag}` : flag;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function assertFileExists(file, message) {
|
|
131
|
+
try {
|
|
132
|
+
await access(file, fsConstants.R_OK);
|
|
133
|
+
} catch {
|
|
134
|
+
throw new Error(message);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseArgs(argv) {
|
|
139
|
+
const options = {};
|
|
140
|
+
for (let i = 0; i < argv.length; i++) {
|
|
141
|
+
const arg = argv[i];
|
|
142
|
+
if (arg === "--update") {
|
|
143
|
+
options.update = true;
|
|
144
|
+
} else if (arg === "--host") {
|
|
145
|
+
options.host = argv[++i];
|
|
146
|
+
} else if (arg.startsWith("--host=")) {
|
|
147
|
+
options.host = arg.slice("--host=".length);
|
|
148
|
+
} else if (arg === "--root") {
|
|
149
|
+
options.rootDir = argv[++i];
|
|
150
|
+
} else if (arg.startsWith("--root=")) {
|
|
151
|
+
options.rootDir = arg.slice("--root=".length);
|
|
152
|
+
} else if (arg === "--built-file") {
|
|
153
|
+
options.builtFile = argv[++i];
|
|
154
|
+
} else if (arg.startsWith("--built-file=")) {
|
|
155
|
+
options.builtFile = arg.slice("--built-file=".length);
|
|
156
|
+
} else {
|
|
157
|
+
throw new Error(`error: unknown argument ${arg}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return options;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
164
|
+
try {
|
|
165
|
+
const result = await checkPrebuildFreshness(parseArgs(process.argv.slice(2)));
|
|
166
|
+
console.log(`${result.status} native prebuild for ${result.host}: ${result.vendoredFile}`);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface NativePtyBinding {
|
|
2
|
+
readonly [exportName: string]: unknown;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface NativePtyUnavailableDiagnostic {
|
|
6
|
+
readonly code: "native-unavailable";
|
|
7
|
+
readonly host: string;
|
|
8
|
+
readonly attemptedPath: string;
|
|
9
|
+
readonly message: string;
|
|
10
|
+
readonly cause?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type NativePtyLoadResult =
|
|
14
|
+
| {
|
|
15
|
+
readonly native: NativePtyBinding;
|
|
16
|
+
readonly diagnostic: null;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
readonly native: null;
|
|
20
|
+
readonly diagnostic: NativePtyUnavailableDiagnostic;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function loadNativePty(): NativePtyLoadResult;
|
|
24
|
+
|
|
25
|
+
export const nativePty: NativePtyLoadResult;
|
package/native/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
|
|
7
|
+
export function loadNativePty() {
|
|
8
|
+
const host = `${process.platform}-${process.arch}`;
|
|
9
|
+
const attemptedPath = join(dirname(fileURLToPath(import.meta.url)), "prebuilds", host, `senpi_pty.${host}.node`);
|
|
10
|
+
try {
|
|
11
|
+
return {
|
|
12
|
+
native: require(attemptedPath),
|
|
13
|
+
diagnostic: null,
|
|
14
|
+
};
|
|
15
|
+
} catch (error) {
|
|
16
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
17
|
+
return {
|
|
18
|
+
native: null,
|
|
19
|
+
diagnostic: {
|
|
20
|
+
code: "native-unavailable",
|
|
21
|
+
host,
|
|
22
|
+
attemptedPath,
|
|
23
|
+
message: `No @earendil-works/pi-pty native prebuild is available for ${host}.`,
|
|
24
|
+
cause,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const nativePty = loadNativePty();
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@code-yeongyu/senpi-pty",
|
|
3
|
+
"version": "2026.7.25-2",
|
|
4
|
+
"description": "Typed PTY session loader for Senpi persistent terminals",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./screen": {
|
|
14
|
+
"types": "./dist/screen.d.ts",
|
|
15
|
+
"import": "./dist/screen.js"
|
|
16
|
+
},
|
|
17
|
+
"./registry": {
|
|
18
|
+
"types": "./dist/registry.d.ts",
|
|
19
|
+
"import": "./dist/registry.js"
|
|
20
|
+
},
|
|
21
|
+
"./native": {
|
|
22
|
+
"types": "./native/index.d.ts",
|
|
23
|
+
"import": "./native/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"native",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"clean": "shx rm -rf dist",
|
|
34
|
+
"build": "tsgo -p tsconfig.build.json",
|
|
35
|
+
"test": "vitest --run test",
|
|
36
|
+
"check:prebuild": "node native/check-prebuild-fresh.mjs",
|
|
37
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"pty",
|
|
41
|
+
"terminal",
|
|
42
|
+
"persistent-terminal",
|
|
43
|
+
"typescript"
|
|
44
|
+
],
|
|
45
|
+
"author": "Earendil Works",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/earendil-works/pi.git",
|
|
50
|
+
"directory": "packages/pty"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=24.0.0"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@xterm/headless": "5.5.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "25.9.1"
|
|
60
|
+
}
|
|
61
|
+
}
|