@aigne/afs-ui 1.11.0-beta.12
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/LICENSE.md +26 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/aup-protocol.cjs +235 -0
- package/dist/aup-protocol.d.cts +78 -0
- package/dist/aup-protocol.d.cts.map +1 -0
- package/dist/aup-protocol.d.mts +78 -0
- package/dist/aup-protocol.d.mts.map +1 -0
- package/dist/aup-protocol.mjs +235 -0
- package/dist/aup-protocol.mjs.map +1 -0
- package/dist/aup-registry.cjs +2489 -0
- package/dist/aup-registry.mjs +2487 -0
- package/dist/aup-registry.mjs.map +1 -0
- package/dist/aup-spec.cjs +1467 -0
- package/dist/aup-spec.mjs +1466 -0
- package/dist/aup-spec.mjs.map +1 -0
- package/dist/aup-types.cjs +165 -0
- package/dist/aup-types.d.cts +157 -0
- package/dist/aup-types.d.cts.map +1 -0
- package/dist/aup-types.d.mts +157 -0
- package/dist/aup-types.d.mts.map +1 -0
- package/dist/aup-types.mjs +157 -0
- package/dist/aup-types.mjs.map +1 -0
- package/dist/backend.cjs +14 -0
- package/dist/backend.d.cts +104 -0
- package/dist/backend.d.cts.map +1 -0
- package/dist/backend.d.mts +104 -0
- package/dist/backend.d.mts.map +1 -0
- package/dist/backend.mjs +13 -0
- package/dist/backend.mjs.map +1 -0
- package/dist/degradation.cjs +85 -0
- package/dist/degradation.d.cts +17 -0
- package/dist/degradation.d.cts.map +1 -0
- package/dist/degradation.d.mts +17 -0
- package/dist/degradation.d.mts.map +1 -0
- package/dist/degradation.mjs +84 -0
- package/dist/degradation.mjs.map +1 -0
- package/dist/index.cjs +36 -0
- package/dist/index.d.cts +12 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.mjs +13 -0
- package/dist/runtime.cjs +117 -0
- package/dist/runtime.d.cts +59 -0
- package/dist/runtime.d.cts.map +1 -0
- package/dist/runtime.d.mts +59 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +118 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/session.cjs +159 -0
- package/dist/session.d.cts +80 -0
- package/dist/session.d.cts.map +1 -0
- package/dist/session.d.mts +80 -0
- package/dist/session.d.mts.map +1 -0
- package/dist/session.mjs +159 -0
- package/dist/session.mjs.map +1 -0
- package/dist/snapshot.cjs +162 -0
- package/dist/snapshot.mjs +163 -0
- package/dist/snapshot.mjs.map +1 -0
- package/dist/term-page.cjs +264 -0
- package/dist/term-page.mjs +264 -0
- package/dist/term-page.mjs.map +1 -0
- package/dist/term.cjs +295 -0
- package/dist/term.d.cts +84 -0
- package/dist/term.d.cts.map +1 -0
- package/dist/term.d.mts +84 -0
- package/dist/term.d.mts.map +1 -0
- package/dist/term.mjs +296 -0
- package/dist/term.mjs.map +1 -0
- package/dist/tty.cjs +136 -0
- package/dist/tty.d.cts +53 -0
- package/dist/tty.d.cts.map +1 -0
- package/dist/tty.d.mts +53 -0
- package/dist/tty.d.mts.map +1 -0
- package/dist/tty.mjs +135 -0
- package/dist/tty.mjs.map +1 -0
- package/dist/ui-provider.cjs +4615 -0
- package/dist/ui-provider.d.cts +307 -0
- package/dist/ui-provider.d.cts.map +1 -0
- package/dist/ui-provider.d.mts +307 -0
- package/dist/ui-provider.d.mts.map +1 -0
- package/dist/ui-provider.mjs +4616 -0
- package/dist/ui-provider.mjs.map +1 -0
- package/dist/web-page/core.cjs +1388 -0
- package/dist/web-page/core.mjs +1387 -0
- package/dist/web-page/core.mjs.map +1 -0
- package/dist/web-page/css.cjs +1699 -0
- package/dist/web-page/css.mjs +1698 -0
- package/dist/web-page/css.mjs.map +1 -0
- package/dist/web-page/icons.cjs +248 -0
- package/dist/web-page/icons.mjs +248 -0
- package/dist/web-page/icons.mjs.map +1 -0
- package/dist/web-page/overlay-themes.cjs +514 -0
- package/dist/web-page/overlay-themes.mjs +513 -0
- package/dist/web-page/overlay-themes.mjs.map +1 -0
- package/dist/web-page/renderers/action.cjs +72 -0
- package/dist/web-page/renderers/action.mjs +72 -0
- package/dist/web-page/renderers/action.mjs.map +1 -0
- package/dist/web-page/renderers/broadcast.cjs +160 -0
- package/dist/web-page/renderers/broadcast.mjs +160 -0
- package/dist/web-page/renderers/broadcast.mjs.map +1 -0
- package/dist/web-page/renderers/calendar.cjs +137 -0
- package/dist/web-page/renderers/calendar.mjs +137 -0
- package/dist/web-page/renderers/calendar.mjs.map +1 -0
- package/dist/web-page/renderers/canvas.cjs +173 -0
- package/dist/web-page/renderers/canvas.mjs +173 -0
- package/dist/web-page/renderers/canvas.mjs.map +1 -0
- package/dist/web-page/renderers/cdn-loader.cjs +25 -0
- package/dist/web-page/renderers/cdn-loader.mjs +25 -0
- package/dist/web-page/renderers/cdn-loader.mjs.map +1 -0
- package/dist/web-page/renderers/chart.cjs +101 -0
- package/dist/web-page/renderers/chart.mjs +101 -0
- package/dist/web-page/renderers/chart.mjs.map +1 -0
- package/dist/web-page/renderers/deck.cjs +390 -0
- package/dist/web-page/renderers/deck.mjs +390 -0
- package/dist/web-page/renderers/deck.mjs.map +1 -0
- package/dist/web-page/renderers/device.cjs +1015 -0
- package/dist/web-page/renderers/device.mjs +1015 -0
- package/dist/web-page/renderers/device.mjs.map +1 -0
- package/dist/web-page/renderers/editor.cjs +127 -0
- package/dist/web-page/renderers/editor.mjs +127 -0
- package/dist/web-page/renderers/editor.mjs.map +1 -0
- package/dist/web-page/renderers/finance-chart.cjs +178 -0
- package/dist/web-page/renderers/finance-chart.mjs +178 -0
- package/dist/web-page/renderers/finance-chart.mjs.map +1 -0
- package/dist/web-page/renderers/frame.cjs +274 -0
- package/dist/web-page/renderers/frame.mjs +274 -0
- package/dist/web-page/renderers/frame.mjs.map +1 -0
- package/dist/web-page/renderers/globe.cjs +119 -0
- package/dist/web-page/renderers/globe.mjs +119 -0
- package/dist/web-page/renderers/globe.mjs.map +1 -0
- package/dist/web-page/renderers/input.cjs +137 -0
- package/dist/web-page/renderers/input.mjs +137 -0
- package/dist/web-page/renderers/input.mjs.map +1 -0
- package/dist/web-page/renderers/list.cjs +1243 -0
- package/dist/web-page/renderers/list.mjs +1243 -0
- package/dist/web-page/renderers/list.mjs.map +1 -0
- package/dist/web-page/renderers/map.cjs +126 -0
- package/dist/web-page/renderers/map.mjs +126 -0
- package/dist/web-page/renderers/map.mjs.map +1 -0
- package/dist/web-page/renderers/media.cjs +106 -0
- package/dist/web-page/renderers/media.mjs +106 -0
- package/dist/web-page/renderers/media.mjs.map +1 -0
- package/dist/web-page/renderers/moonphase.cjs +105 -0
- package/dist/web-page/renderers/moonphase.mjs +105 -0
- package/dist/web-page/renderers/moonphase.mjs.map +1 -0
- package/dist/web-page/renderers/natal-chart.cjs +222 -0
- package/dist/web-page/renderers/natal-chart.mjs +222 -0
- package/dist/web-page/renderers/natal-chart.mjs.map +1 -0
- package/dist/web-page/renderers/overlay.cjs +531 -0
- package/dist/web-page/renderers/overlay.mjs +531 -0
- package/dist/web-page/renderers/overlay.mjs.map +1 -0
- package/dist/web-page/renderers/table.cjs +74 -0
- package/dist/web-page/renderers/table.mjs +74 -0
- package/dist/web-page/renderers/table.mjs.map +1 -0
- package/dist/web-page/renderers/terminal.cjs +30 -0
- package/dist/web-page/renderers/terminal.mjs +30 -0
- package/dist/web-page/renderers/terminal.mjs.map +1 -0
- package/dist/web-page/renderers/text.cjs +109 -0
- package/dist/web-page/renderers/text.mjs +109 -0
- package/dist/web-page/renderers/text.mjs.map +1 -0
- package/dist/web-page/renderers/ticker.cjs +133 -0
- package/dist/web-page/renderers/ticker.mjs +133 -0
- package/dist/web-page/renderers/ticker.mjs.map +1 -0
- package/dist/web-page/renderers/time.cjs +69 -0
- package/dist/web-page/renderers/time.mjs +69 -0
- package/dist/web-page/renderers/time.mjs.map +1 -0
- package/dist/web-page/renderers/unknown.cjs +20 -0
- package/dist/web-page/renderers/unknown.mjs +20 -0
- package/dist/web-page/renderers/unknown.mjs.map +1 -0
- package/dist/web-page/renderers/view.cjs +161 -0
- package/dist/web-page/renderers/view.mjs +161 -0
- package/dist/web-page/renderers/view.mjs.map +1 -0
- package/dist/web-page/renderers/wm.cjs +669 -0
- package/dist/web-page/renderers/wm.mjs +669 -0
- package/dist/web-page/renderers/wm.mjs.map +1 -0
- package/dist/web-page/skeleton.cjs +103 -0
- package/dist/web-page/skeleton.mjs +103 -0
- package/dist/web-page/skeleton.mjs.map +1 -0
- package/dist/web-page.cjs +114 -0
- package/dist/web-page.d.cts +19 -0
- package/dist/web-page.d.cts.map +1 -0
- package/dist/web-page.d.mts +19 -0
- package/dist/web-page.d.mts.map +1 -0
- package/dist/web-page.mjs +115 -0
- package/dist/web-page.mjs.map +1 -0
- package/dist/web.cjs +827 -0
- package/dist/web.d.cts +144 -0
- package/dist/web.d.cts.map +1 -0
- package/dist/web.d.mts +144 -0
- package/dist/web.d.mts.map +1 -0
- package/dist/web.mjs +828 -0
- package/dist/web.mjs.map +1 -0
- package/dist/wm-state.cjs +172 -0
- package/dist/wm-state.mjs +171 -0
- package/dist/wm-state.mjs.map +1 -0
- package/package.json +59 -0
package/dist/term.d.mts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { PromptOptions, PromptResult, ReadOptions, UIBackend, ViewportInfo, WriteOptions } from "./backend.mjs";
|
|
2
|
+
import { TTYInputSource } from "./tty.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/term.d.ts
|
|
5
|
+
interface TermBackendOptions {
|
|
6
|
+
/** Port to listen on (0 = OS-assigned random port) */
|
|
7
|
+
port?: number;
|
|
8
|
+
/** Host to bind to */
|
|
9
|
+
host?: string;
|
|
10
|
+
/** For testing: custom input source (bypasses WebSocket) */
|
|
11
|
+
inputSource?: TTYInputSource & {
|
|
12
|
+
push?: (line: string) => void;
|
|
13
|
+
};
|
|
14
|
+
/** For testing: custom output handler (bypasses WebSocket) */
|
|
15
|
+
stdout?: {
|
|
16
|
+
write(data: string): boolean;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* TermBackend — HTTP + WebSocket based xterm.js terminal.
|
|
21
|
+
*
|
|
22
|
+
* Serves an xterm.js web page and communicates via WebSocket.
|
|
23
|
+
* Line-based I/O: client sends complete lines on Enter, server writes output text.
|
|
24
|
+
*/
|
|
25
|
+
declare class TermBackend implements UIBackend {
|
|
26
|
+
readonly type = "term";
|
|
27
|
+
readonly supportedFormats: string[];
|
|
28
|
+
readonly capabilities: string[];
|
|
29
|
+
private port;
|
|
30
|
+
private host;
|
|
31
|
+
private server;
|
|
32
|
+
private wss;
|
|
33
|
+
private clients;
|
|
34
|
+
private sockets;
|
|
35
|
+
private inputSource;
|
|
36
|
+
private outputHandler;
|
|
37
|
+
/** Queue for messages sent before any client connects */
|
|
38
|
+
private pendingMessages;
|
|
39
|
+
/** Pending prompt resolve */
|
|
40
|
+
private promptResolve;
|
|
41
|
+
/** Pending prompt message — re-sent on client reconnect */
|
|
42
|
+
private pendingPromptMessage;
|
|
43
|
+
/** Viewport info from last resize */
|
|
44
|
+
private viewportCols;
|
|
45
|
+
private viewportRows;
|
|
46
|
+
private testMode;
|
|
47
|
+
private _url;
|
|
48
|
+
/** Per-client session tracking */
|
|
49
|
+
private sessionForClient;
|
|
50
|
+
private createSessionCallback;
|
|
51
|
+
constructor(options?: TermBackendOptions);
|
|
52
|
+
/** URL of the running server, or null if not started. */
|
|
53
|
+
get url(): string | null;
|
|
54
|
+
/** Register a factory that creates a session for each new WebSocket client. */
|
|
55
|
+
setSessionFactory(fn: (endpoint: string, requestedSessionId?: string, requestedSessionToken?: string, caps?: Record<string, unknown>) => {
|
|
56
|
+
sessionId: string;
|
|
57
|
+
sessionToken?: string;
|
|
58
|
+
}): void;
|
|
59
|
+
/** Start the HTTP + WebSocket server. */
|
|
60
|
+
listen(): Promise<{
|
|
61
|
+
port: number;
|
|
62
|
+
host: string;
|
|
63
|
+
}>;
|
|
64
|
+
/** Shut down the server and disconnect all clients. */
|
|
65
|
+
close(): Promise<void>;
|
|
66
|
+
write(content: string, options?: WriteOptions): Promise<void>;
|
|
67
|
+
read(options?: ReadOptions): Promise<string>;
|
|
68
|
+
prompt(options: PromptOptions): Promise<PromptResult>;
|
|
69
|
+
notify(message: string): Promise<void>;
|
|
70
|
+
clear(): Promise<void>;
|
|
71
|
+
hasPendingInput(): boolean;
|
|
72
|
+
getViewport(): ViewportInfo;
|
|
73
|
+
dispose(): Promise<void>;
|
|
74
|
+
private onConnection;
|
|
75
|
+
private onMessage;
|
|
76
|
+
private broadcast;
|
|
77
|
+
private isLoopbackHost;
|
|
78
|
+
private isAllowedWsOrigin;
|
|
79
|
+
/** TTY-style prompt for test mode */
|
|
80
|
+
private ttyStylePrompt;
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { TermBackend, TermBackendOptions };
|
|
84
|
+
//# sourceMappingURL=term.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"term.d.mts","names":[],"sources":["../src/term.ts"],"mappings":";;;;UAciB,kBAAA;;EAEf,IAAA;EAFiC;EAIjC,IAAA;EAE4B;EAA5B,WAAA,GAAc,cAAA;IAAmB,IAAA,IAAQ,IAAA;EAAA;EAA3B;EAEd,MAAA;IAAW,KAAA,CAAM,IAAA;EAAA;AAAA;;;;AASnB;;;cAAa,WAAA,YAAuB,SAAA;EAAA,SACzB,IAAA;EAAA,SACA,gBAAA;EAAA,SACA,YAAA;EAAA,QAED,IAAA;EAAA,QACA,IAAA;EAAA,QACA,MAAA;EAAA,QACA,GAAA;EAAA,QACA,OAAA;EAAA,QACA,OAAA;EAAA,QAEA,WAAA;EAAA,QACA,aAAA;EAmMO;EAAA,QAhMP,eAAA;EAgNS;EAAA,QA7MT,aAAA;EAnBmC;EAAA,QAqBnC,oBAAA;EArB0B;EAAA,QAwB1B,YAAA;EAAA,QACA,YAAA;EAAA,QAEA,QAAA;EAAA,QACA,IAAA;EAtBA;EAAA,QAyBA,gBAAA;EAAA,QACA,qBAAA;cASI,OAAA,GAAS,kBAAA;EA/Bb;EAAA,IA2DJ,GAAA,CAAA;EAxDI;EA6DR,iBAAA,CACE,EAAA,GACE,QAAA,UACA,kBAAA,WACA,qBAAA,WACA,IAAA,GAAO,MAAA;IACF,SAAA;IAAmB,YAAA;EAAA;EAvDpB;EA6DF,MAAA,CAAA,GAAU,OAAA;IAAU,IAAA;IAAc,IAAA;EAAA;;EA0ClC,KAAA,CAAA,GAAS,OAAA;EA0BT,KAAA,CAAM,OAAA,UAAiB,OAAA,GAAU,YAAA,GAAe,OAAA;EAahD,IAAA,CAAK,OAAA,GAAU,WAAA,GAAc,OAAA;EAQ7B,MAAA,CAAO,OAAA,EAAS,aAAA,GAAgB,OAAA,CAAQ,YAAA;EAyBxC,MAAA,CAAO,OAAA,WAAkB,OAAA;EAQzB,KAAA,CAAA,GAAS,OAAA;EAQf,eAAA,CAAA;EAIA,WAAA,CAAA,GAAe,YAAA;EAIT,OAAA,CAAA,GAAW,OAAA;EAAA,QAMT,YAAA;EAAA,QAkDA,SAAA;EAAA,QAwBA,SAAA;EAAA,QAQA,cAAA;EAAA,QAIA,iBAAA;EAtOkB;EAAA,QAkPZ,cAAA;AAAA"}
|
package/dist/term.mjs
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { TERM_CLIENT_HTML } from "./term-page.mjs";
|
|
2
|
+
import { createMockInputSource } from "./tty.mjs";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { WebSocketServer } from "ws";
|
|
5
|
+
|
|
6
|
+
//#region src/term.ts
|
|
7
|
+
/**
|
|
8
|
+
* TermBackend — HTTP + WebSocket based xterm.js terminal.
|
|
9
|
+
*
|
|
10
|
+
* Serves an xterm.js web page and communicates via WebSocket.
|
|
11
|
+
* Line-based I/O: client sends complete lines on Enter, server writes output text.
|
|
12
|
+
*/
|
|
13
|
+
var TermBackend = class {
|
|
14
|
+
type = "term";
|
|
15
|
+
supportedFormats = ["text"];
|
|
16
|
+
capabilities = ["text"];
|
|
17
|
+
port;
|
|
18
|
+
host;
|
|
19
|
+
server = null;
|
|
20
|
+
wss = null;
|
|
21
|
+
clients = /* @__PURE__ */ new Set();
|
|
22
|
+
sockets = /* @__PURE__ */ new Set();
|
|
23
|
+
inputSource;
|
|
24
|
+
outputHandler;
|
|
25
|
+
/** Queue for messages sent before any client connects */
|
|
26
|
+
pendingMessages = [];
|
|
27
|
+
/** Pending prompt resolve */
|
|
28
|
+
promptResolve = null;
|
|
29
|
+
/** Pending prompt message — re-sent on client reconnect */
|
|
30
|
+
pendingPromptMessage = null;
|
|
31
|
+
/** Viewport info from last resize */
|
|
32
|
+
viewportCols = 80;
|
|
33
|
+
viewportRows = 24;
|
|
34
|
+
testMode;
|
|
35
|
+
_url = null;
|
|
36
|
+
/** Per-client session tracking */
|
|
37
|
+
sessionForClient = /* @__PURE__ */ new Map();
|
|
38
|
+
createSessionCallback = null;
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
this.port = options.port ?? 0;
|
|
41
|
+
this.host = options.host ?? "localhost";
|
|
42
|
+
if (options.inputSource) {
|
|
43
|
+
this.testMode = true;
|
|
44
|
+
this.inputSource = options.inputSource;
|
|
45
|
+
this.outputHandler = options.stdout ? (data) => {
|
|
46
|
+
options.stdout.write(data);
|
|
47
|
+
} : () => {};
|
|
48
|
+
} else {
|
|
49
|
+
this.testMode = false;
|
|
50
|
+
this.inputSource = createMockInputSource();
|
|
51
|
+
this.outputHandler = (data) => {
|
|
52
|
+
const payload = JSON.stringify({
|
|
53
|
+
type: "output",
|
|
54
|
+
data
|
|
55
|
+
});
|
|
56
|
+
if (this.clients.size === 0) this.pendingMessages.push(payload);
|
|
57
|
+
else this.broadcast(payload);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** URL of the running server, or null if not started. */
|
|
62
|
+
get url() {
|
|
63
|
+
return this._url;
|
|
64
|
+
}
|
|
65
|
+
/** Register a factory that creates a session for each new WebSocket client. */
|
|
66
|
+
setSessionFactory(fn) {
|
|
67
|
+
this.createSessionCallback = fn;
|
|
68
|
+
}
|
|
69
|
+
/** Start the HTTP + WebSocket server. */
|
|
70
|
+
async listen() {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const server = createServer((req, res) => {
|
|
73
|
+
if (req.url === "/" || req.url === "/index.html") {
|
|
74
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
75
|
+
res.end(TERM_CLIENT_HTML);
|
|
76
|
+
} else {
|
|
77
|
+
res.writeHead(404);
|
|
78
|
+
res.end("Not Found");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
server.on("connection", (socket) => {
|
|
82
|
+
this.sockets.add(socket);
|
|
83
|
+
socket.on("close", () => this.sockets.delete(socket));
|
|
84
|
+
});
|
|
85
|
+
server.on("error", (err) => {
|
|
86
|
+
server.close();
|
|
87
|
+
reject(err);
|
|
88
|
+
});
|
|
89
|
+
const bindHost = this.host === "localhost" ? "127.0.0.1" : this.host;
|
|
90
|
+
server.listen({
|
|
91
|
+
port: this.port,
|
|
92
|
+
host: bindHost,
|
|
93
|
+
exclusive: true
|
|
94
|
+
}, () => {
|
|
95
|
+
this.server = server;
|
|
96
|
+
const addr = server.address();
|
|
97
|
+
if (typeof addr === "object" && addr) this.port = addr.port;
|
|
98
|
+
this._url = `http://127.0.0.1:${this.port}`;
|
|
99
|
+
this.wss = new WebSocketServer({ server });
|
|
100
|
+
this.wss.on("connection", (ws, req) => this.onConnection(ws, req));
|
|
101
|
+
resolve({
|
|
102
|
+
port: this.port,
|
|
103
|
+
host: this.host
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** Shut down the server and disconnect all clients. */
|
|
109
|
+
async close() {
|
|
110
|
+
for (const ws of this.clients) ws.terminate();
|
|
111
|
+
this.clients.clear();
|
|
112
|
+
this.sessionForClient.clear();
|
|
113
|
+
if (this.wss) {
|
|
114
|
+
this.wss.close();
|
|
115
|
+
this.wss = null;
|
|
116
|
+
}
|
|
117
|
+
if (this.server) {
|
|
118
|
+
this.server.close();
|
|
119
|
+
for (const socket of this.sockets) socket.destroy();
|
|
120
|
+
this.sockets.clear();
|
|
121
|
+
this.server = null;
|
|
122
|
+
}
|
|
123
|
+
this._url = null;
|
|
124
|
+
}
|
|
125
|
+
async write(content, options) {
|
|
126
|
+
if (options?.format && options.format !== "text") throw new Error(`Term backend does not support format: ${options.format}`);
|
|
127
|
+
if (this.testMode) {
|
|
128
|
+
this.outputHandler(content);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
this.outputHandler(content);
|
|
132
|
+
}
|
|
133
|
+
async read(options) {
|
|
134
|
+
const timeout = options?.timeout ?? 0;
|
|
135
|
+
if (timeout > 0) return withTimeout(this.inputSource.readLine(), timeout);
|
|
136
|
+
return this.inputSource.readLine();
|
|
137
|
+
}
|
|
138
|
+
async prompt(options) {
|
|
139
|
+
if (this.testMode) return this.ttyStylePrompt(options);
|
|
140
|
+
const msg = JSON.stringify({
|
|
141
|
+
type: "prompt",
|
|
142
|
+
message: options.message,
|
|
143
|
+
promptType: options.type,
|
|
144
|
+
options: options.options
|
|
145
|
+
});
|
|
146
|
+
this.pendingPromptMessage = msg;
|
|
147
|
+
if (this.clients.size > 0) this.broadcast(msg);
|
|
148
|
+
else this.pendingMessages.push(msg);
|
|
149
|
+
return new Promise((resolve) => {
|
|
150
|
+
this.promptResolve = resolve;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async notify(message) {
|
|
154
|
+
if (this.testMode) {
|
|
155
|
+
this.outputHandler(`${message}\n`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
this.broadcast(JSON.stringify({
|
|
159
|
+
type: "notify",
|
|
160
|
+
message
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
async clear() {
|
|
164
|
+
if (this.testMode) {
|
|
165
|
+
this.outputHandler("\x1B[2J\x1B[H");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.broadcast(JSON.stringify({ type: "clear" }));
|
|
169
|
+
}
|
|
170
|
+
hasPendingInput() {
|
|
171
|
+
return this.inputSource.hasPending();
|
|
172
|
+
}
|
|
173
|
+
getViewport() {
|
|
174
|
+
return {
|
|
175
|
+
cols: this.viewportCols,
|
|
176
|
+
rows: this.viewportRows
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
async dispose() {
|
|
180
|
+
await this.close();
|
|
181
|
+
}
|
|
182
|
+
onConnection(ws, req) {
|
|
183
|
+
const origin = req.headers.origin;
|
|
184
|
+
if (typeof origin === "string" && origin && !this.isAllowedWsOrigin(origin)) {
|
|
185
|
+
ws.close(1008, "Invalid origin");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.clients.add(ws);
|
|
189
|
+
let sessionId;
|
|
190
|
+
if (this.createSessionCallback) {
|
|
191
|
+
const created = this.createSessionCallback(this.type);
|
|
192
|
+
sessionId = created.sessionId;
|
|
193
|
+
this.sessionForClient.set(ws, sessionId);
|
|
194
|
+
ws.send(JSON.stringify({
|
|
195
|
+
type: "session",
|
|
196
|
+
sessionId,
|
|
197
|
+
sessionToken: created.sessionToken ?? null
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
for (const msg of this.pendingMessages) ws.send(msg);
|
|
201
|
+
this.pendingMessages = [];
|
|
202
|
+
if (this.pendingPromptMessage && this.promptResolve) ws.send(this.pendingPromptMessage);
|
|
203
|
+
ws.on("message", (data) => {
|
|
204
|
+
try {
|
|
205
|
+
const msg = JSON.parse(data.toString());
|
|
206
|
+
this.onMessage(msg);
|
|
207
|
+
} catch {}
|
|
208
|
+
});
|
|
209
|
+
ws.on("close", () => {
|
|
210
|
+
this.clients.delete(ws);
|
|
211
|
+
if (sessionId) this.sessionForClient.delete(ws);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
onMessage(msg) {
|
|
215
|
+
switch (msg.type) {
|
|
216
|
+
case "line": {
|
|
217
|
+
const content = String(msg.content ?? "");
|
|
218
|
+
this.inputSource.push?.(content);
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case "prompt_response":
|
|
222
|
+
if (this.promptResolve) {
|
|
223
|
+
const resolve = this.promptResolve;
|
|
224
|
+
this.promptResolve = null;
|
|
225
|
+
this.pendingPromptMessage = null;
|
|
226
|
+
resolve(msg.value);
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
case "resize":
|
|
230
|
+
if (typeof msg.cols === "number") this.viewportCols = msg.cols;
|
|
231
|
+
if (typeof msg.rows === "number") this.viewportRows = msg.rows;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
broadcast(data) {
|
|
236
|
+
for (const ws of this.clients) if (ws.readyState === 1) ws.send(data);
|
|
237
|
+
}
|
|
238
|
+
isLoopbackHost(hostname) {
|
|
239
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
240
|
+
}
|
|
241
|
+
isAllowedWsOrigin(origin) {
|
|
242
|
+
try {
|
|
243
|
+
const u = new URL(origin);
|
|
244
|
+
if (!this.isLoopbackHost(u.hostname)) return false;
|
|
245
|
+
return (u.port || (u.protocol === "https:" ? "443" : "80")) === String(this.port);
|
|
246
|
+
} catch {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/** TTY-style prompt for test mode */
|
|
251
|
+
async ttyStylePrompt(options) {
|
|
252
|
+
const { message, type } = options;
|
|
253
|
+
switch (type) {
|
|
254
|
+
case "text":
|
|
255
|
+
case "password":
|
|
256
|
+
this.outputHandler(`${message} `);
|
|
257
|
+
return (await this.read()).trim();
|
|
258
|
+
case "confirm":
|
|
259
|
+
this.outputHandler(`${message} (y/n) `);
|
|
260
|
+
return (await this.read()).trim().toLowerCase().startsWith("y");
|
|
261
|
+
case "select": {
|
|
262
|
+
if (!options.options || options.options.length === 0) throw new Error("select prompt requires options");
|
|
263
|
+
this.outputHandler(`${message}\n`);
|
|
264
|
+
for (let i = 0; i < options.options.length; i++) this.outputHandler(` ${i + 1}. ${options.options[i]}\n`);
|
|
265
|
+
this.outputHandler("Choice: ");
|
|
266
|
+
const input = await this.read();
|
|
267
|
+
const idx = Number.parseInt(input.trim(), 10) - 1;
|
|
268
|
+
if (idx >= 0 && idx < options.options.length) return options.options[idx];
|
|
269
|
+
return options.options[0];
|
|
270
|
+
}
|
|
271
|
+
case "multiselect":
|
|
272
|
+
if (!options.options || options.options.length === 0) throw new Error("multiselect prompt requires options");
|
|
273
|
+
this.outputHandler(`${message}\n`);
|
|
274
|
+
for (let i = 0; i < options.options.length; i++) this.outputHandler(` ${i + 1}. ${options.options[i]}\n`);
|
|
275
|
+
this.outputHandler("Choices (comma-separated): ");
|
|
276
|
+
return (await this.read()).split(",").map((s) => Number.parseInt(s.trim(), 10) - 1).filter((i) => i >= 0 && i < options.options.length).map((i) => options.options[i]);
|
|
277
|
+
default: throw new Error(`Unknown prompt type: ${type}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
function withTimeout(promise, ms) {
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
const timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Input timeout")), ms);
|
|
284
|
+
promise.then((val) => {
|
|
285
|
+
clearTimeout(timer);
|
|
286
|
+
resolve(val);
|
|
287
|
+
}, (err) => {
|
|
288
|
+
clearTimeout(timer);
|
|
289
|
+
reject(err);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
//#endregion
|
|
295
|
+
export { TermBackend };
|
|
296
|
+
//# sourceMappingURL=term.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"term.mjs","names":[],"sources":["../src/term.ts"],"sourcesContent":["import { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport type { Socket } from \"node:net\";\nimport { type WebSocket, WebSocketServer } from \"ws\";\nimport type {\n PromptOptions,\n PromptResult,\n ReadOptions,\n UIBackend,\n ViewportInfo,\n WriteOptions,\n} from \"./backend.js\";\nimport { TERM_CLIENT_HTML } from \"./term-page.js\";\nimport { createMockInputSource, type TTYInputSource } from \"./tty.js\";\n\nexport interface TermBackendOptions {\n /** Port to listen on (0 = OS-assigned random port) */\n port?: number;\n /** Host to bind to */\n host?: string;\n /** For testing: custom input source (bypasses WebSocket) */\n inputSource?: TTYInputSource & { push?: (line: string) => void };\n /** For testing: custom output handler (bypasses WebSocket) */\n stdout?: { write(data: string): boolean };\n}\n\n/**\n * TermBackend — HTTP + WebSocket based xterm.js terminal.\n *\n * Serves an xterm.js web page and communicates via WebSocket.\n * Line-based I/O: client sends complete lines on Enter, server writes output text.\n */\nexport class TermBackend implements UIBackend {\n readonly type = \"term\";\n readonly supportedFormats = [\"text\"];\n readonly capabilities = [\"text\"];\n\n private port: number;\n private host: string;\n private server: Server | null = null;\n private wss: WebSocketServer | null = null;\n private clients = new Set<WebSocket>();\n private sockets = new Set<Socket>();\n\n private inputSource: TTYInputSource & { push?: (line: string) => void };\n private outputHandler: (data: string) => void;\n\n /** Queue for messages sent before any client connects */\n private pendingMessages: string[] = [];\n\n /** Pending prompt resolve */\n private promptResolve: ((value: PromptResult) => void) | null = null;\n /** Pending prompt message — re-sent on client reconnect */\n private pendingPromptMessage: string | null = null;\n\n /** Viewport info from last resize */\n private viewportCols = 80;\n private viewportRows = 24;\n\n private testMode: boolean;\n private _url: string | null = null;\n\n /** Per-client session tracking */\n private sessionForClient = new Map<WebSocket, string>();\n private createSessionCallback:\n | ((\n endpoint: string,\n requestedSessionId?: string,\n requestedSessionToken?: string,\n caps?: Record<string, unknown>,\n ) => { sessionId: string; sessionToken?: string })\n | null = null;\n\n constructor(options: TermBackendOptions = {}) {\n this.port = options.port ?? 0;\n this.host = options.host ?? \"localhost\";\n\n if (options.inputSource) {\n this.testMode = true;\n this.inputSource = options.inputSource;\n this.outputHandler = options.stdout\n ? (data) => {\n options.stdout!.write(data);\n }\n : () => {};\n } else {\n this.testMode = false;\n const queue = createMockInputSource();\n this.inputSource = queue;\n this.outputHandler = (data) => {\n const payload = JSON.stringify({ type: \"output\", data });\n if (this.clients.size === 0) {\n this.pendingMessages.push(payload);\n } else {\n this.broadcast(payload);\n }\n };\n }\n }\n\n /** URL of the running server, or null if not started. */\n get url(): string | null {\n return this._url;\n }\n\n /** Register a factory that creates a session for each new WebSocket client. */\n setSessionFactory(\n fn: (\n endpoint: string,\n requestedSessionId?: string,\n requestedSessionToken?: string,\n caps?: Record<string, unknown>,\n ) => { sessionId: string; sessionToken?: string },\n ): void {\n this.createSessionCallback = fn;\n }\n\n /** Start the HTTP + WebSocket server. */\n async listen(): Promise<{ port: number; host: string }> {\n return new Promise((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n if (req.url === \"/\" || req.url === \"/index.html\") {\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(TERM_CLIENT_HTML);\n } else {\n res.writeHead(404);\n res.end(\"Not Found\");\n }\n });\n\n server.on(\"connection\", (socket: Socket) => {\n this.sockets.add(socket);\n socket.on(\"close\", () => this.sockets.delete(socket));\n });\n\n server.on(\"error\", (err) => {\n server.close();\n reject(err);\n });\n\n const bindHost = this.host === \"localhost\" ? \"127.0.0.1\" : this.host;\n server.listen({ port: this.port, host: bindHost, exclusive: true }, () => {\n this.server = server;\n\n const addr = server.address();\n if (typeof addr === \"object\" && addr) {\n this.port = addr.port;\n }\n\n this._url = `http://127.0.0.1:${this.port}`;\n\n this.wss = new WebSocketServer({ server });\n this.wss.on(\"connection\", (ws, req) => this.onConnection(ws, req));\n\n resolve({ port: this.port, host: this.host });\n });\n });\n }\n\n /** Shut down the server and disconnect all clients. */\n async close(): Promise<void> {\n for (const ws of this.clients) {\n ws.terminate();\n }\n this.clients.clear();\n this.sessionForClient.clear();\n\n if (this.wss) {\n this.wss.close();\n this.wss = null;\n }\n\n if (this.server) {\n this.server.close();\n for (const socket of this.sockets) {\n socket.destroy();\n }\n this.sockets.clear();\n this.server = null;\n }\n\n this._url = null;\n }\n\n // ─── UIBackend Interface ──────────────────────────────────\n\n async write(content: string, options?: WriteOptions): Promise<void> {\n if (options?.format && options.format !== \"text\") {\n throw new Error(`Term backend does not support format: ${options.format}`);\n }\n\n if (this.testMode) {\n this.outputHandler(content);\n return;\n }\n\n this.outputHandler(content);\n }\n\n async read(options?: ReadOptions): Promise<string> {\n const timeout = options?.timeout ?? 0;\n if (timeout > 0) {\n return withTimeout(this.inputSource.readLine(), timeout);\n }\n return this.inputSource.readLine();\n }\n\n async prompt(options: PromptOptions): Promise<PromptResult> {\n if (this.testMode) {\n return this.ttyStylePrompt(options);\n }\n\n const msg = JSON.stringify({\n type: \"prompt\",\n message: options.message,\n promptType: options.type,\n options: options.options,\n });\n\n this.pendingPromptMessage = msg;\n\n if (this.clients.size > 0) {\n this.broadcast(msg);\n } else {\n this.pendingMessages.push(msg);\n }\n\n return new Promise((resolve) => {\n this.promptResolve = resolve;\n });\n }\n\n async notify(message: string): Promise<void> {\n if (this.testMode) {\n this.outputHandler(`${message}\\n`);\n return;\n }\n this.broadcast(JSON.stringify({ type: \"notify\", message }));\n }\n\n async clear(): Promise<void> {\n if (this.testMode) {\n this.outputHandler(\"\\x1b[2J\\x1b[H\");\n return;\n }\n this.broadcast(JSON.stringify({ type: \"clear\" }));\n }\n\n hasPendingInput(): boolean {\n return this.inputSource.hasPending();\n }\n\n getViewport(): ViewportInfo {\n return { cols: this.viewportCols, rows: this.viewportRows };\n }\n\n async dispose(): Promise<void> {\n await this.close();\n }\n\n // ─── Private ──────────────────────────────────────────────\n\n private onConnection(ws: WebSocket, req: IncomingMessage): void {\n const origin = req.headers.origin;\n if (typeof origin === \"string\" && origin && !this.isAllowedWsOrigin(origin)) {\n ws.close(1008, \"Invalid origin\");\n return;\n }\n\n this.clients.add(ws);\n\n // Allocate session for this client\n let sessionId: string | undefined;\n if (this.createSessionCallback) {\n const created = this.createSessionCallback(this.type);\n sessionId = created.sessionId;\n this.sessionForClient.set(ws, sessionId);\n ws.send(\n JSON.stringify({\n type: \"session\",\n sessionId,\n sessionToken: created.sessionToken ?? null,\n }),\n );\n }\n\n // Flush pending messages\n for (const msg of this.pendingMessages) {\n ws.send(msg);\n }\n this.pendingMessages = [];\n\n // Re-send pending prompt to reconnecting client\n if (this.pendingPromptMessage && this.promptResolve) {\n ws.send(this.pendingPromptMessage);\n }\n\n ws.on(\"message\", (data) => {\n try {\n const msg = JSON.parse(data.toString()) as Record<string, unknown>;\n this.onMessage(msg);\n } catch {\n // Ignore malformed messages\n }\n });\n\n ws.on(\"close\", () => {\n this.clients.delete(ws);\n if (sessionId) this.sessionForClient.delete(ws);\n });\n }\n\n private onMessage(msg: Record<string, unknown>): void {\n switch (msg.type) {\n case \"line\": {\n const content = String(msg.content ?? \"\");\n this.inputSource.push?.(content);\n break;\n }\n case \"prompt_response\": {\n if (this.promptResolve) {\n const resolve = this.promptResolve;\n this.promptResolve = null;\n this.pendingPromptMessage = null;\n resolve(msg.value as PromptResult);\n }\n break;\n }\n case \"resize\": {\n if (typeof msg.cols === \"number\") this.viewportCols = msg.cols;\n if (typeof msg.rows === \"number\") this.viewportRows = msg.rows;\n break;\n }\n }\n }\n\n private broadcast(data: string): void {\n for (const ws of this.clients) {\n if (ws.readyState === 1 /* OPEN */) {\n ws.send(data);\n }\n }\n }\n\n private isLoopbackHost(hostname: string): boolean {\n return hostname === \"localhost\" || hostname === \"127.0.0.1\" || hostname === \"::1\";\n }\n\n private isAllowedWsOrigin(origin: string): boolean {\n try {\n const u = new URL(origin);\n if (!this.isLoopbackHost(u.hostname)) return false;\n const port = u.port || (u.protocol === \"https:\" ? \"443\" : \"80\");\n return port === String(this.port);\n } catch {\n return false;\n }\n }\n\n /** TTY-style prompt for test mode */\n private async ttyStylePrompt(options: PromptOptions): Promise<PromptResult> {\n const { message, type } = options;\n\n switch (type) {\n case \"text\":\n case \"password\": {\n this.outputHandler(`${message} `);\n const input = await this.read();\n return input.trim();\n }\n case \"confirm\": {\n this.outputHandler(`${message} (y/n) `);\n const input = await this.read();\n return input.trim().toLowerCase().startsWith(\"y\");\n }\n case \"select\": {\n if (!options.options || options.options.length === 0) {\n throw new Error(\"select prompt requires options\");\n }\n this.outputHandler(`${message}\\n`);\n for (let i = 0; i < options.options.length; i++) {\n this.outputHandler(` ${i + 1}. ${options.options[i]}\\n`);\n }\n this.outputHandler(\"Choice: \");\n const input = await this.read();\n const idx = Number.parseInt(input.trim(), 10) - 1;\n if (idx >= 0 && idx < options.options.length) {\n return options.options[idx]!;\n }\n return options.options[0]!;\n }\n case \"multiselect\": {\n if (!options.options || options.options.length === 0) {\n throw new Error(\"multiselect prompt requires options\");\n }\n this.outputHandler(`${message}\\n`);\n for (let i = 0; i < options.options.length; i++) {\n this.outputHandler(` ${i + 1}. ${options.options[i]}\\n`);\n }\n this.outputHandler(\"Choices (comma-separated): \");\n const input = await this.read();\n const indices = input\n .split(\",\")\n .map((s) => Number.parseInt(s.trim(), 10) - 1)\n .filter((i) => i >= 0 && i < options.options!.length);\n return indices.map((i) => options.options![i]!);\n }\n default:\n throw new Error(`Unknown prompt type: ${type}`);\n }\n }\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(\"Input timeout\")), ms);\n promise.then(\n (val) => {\n clearTimeout(timer);\n resolve(val);\n },\n (err) => {\n clearTimeout(timer);\n reject(err);\n },\n );\n });\n}\n"],"mappings":"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"}
|
package/dist/tty.cjs
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/tty.ts
|
|
3
|
+
var TTYBackend = class {
|
|
4
|
+
type = "tty";
|
|
5
|
+
supportedFormats = ["text"];
|
|
6
|
+
capabilities = ["text"];
|
|
7
|
+
stdout;
|
|
8
|
+
inputSource;
|
|
9
|
+
inputTimeout;
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.stdout = options.stdout ?? process.stdout;
|
|
12
|
+
this.inputSource = options.inputSource ?? createStdinSource();
|
|
13
|
+
this.inputTimeout = options.inputTimeout ?? 0;
|
|
14
|
+
}
|
|
15
|
+
async write(content, options) {
|
|
16
|
+
if (options?.format && options.format !== "text") throw new Error(`TTY backend does not support format: ${options.format}`);
|
|
17
|
+
this.stdout.write(content);
|
|
18
|
+
}
|
|
19
|
+
async read(options) {
|
|
20
|
+
const timeout = options?.timeout ?? this.inputTimeout;
|
|
21
|
+
if (timeout > 0) return withTimeout(this.inputSource.readLine(), timeout);
|
|
22
|
+
return this.inputSource.readLine();
|
|
23
|
+
}
|
|
24
|
+
async prompt(options) {
|
|
25
|
+
const { message, type } = options;
|
|
26
|
+
switch (type) {
|
|
27
|
+
case "text":
|
|
28
|
+
case "password":
|
|
29
|
+
this.stdout.write(`${message} `);
|
|
30
|
+
return (await this.read()).trim();
|
|
31
|
+
case "confirm":
|
|
32
|
+
this.stdout.write(`${message} (y/n) `);
|
|
33
|
+
return (await this.read()).trim().toLowerCase().startsWith("y");
|
|
34
|
+
case "select": {
|
|
35
|
+
if (!options.options || options.options.length === 0) throw new Error("select prompt requires options");
|
|
36
|
+
this.stdout.write(`${message}\n`);
|
|
37
|
+
for (let i = 0; i < options.options.length; i++) this.stdout.write(` ${i + 1}. ${options.options[i]}\n`);
|
|
38
|
+
this.stdout.write("Choice: ");
|
|
39
|
+
const input = await this.read();
|
|
40
|
+
const idx = Number.parseInt(input.trim(), 10) - 1;
|
|
41
|
+
if (idx >= 0 && idx < options.options.length) return options.options[idx];
|
|
42
|
+
return options.options[0];
|
|
43
|
+
}
|
|
44
|
+
case "multiselect":
|
|
45
|
+
if (!options.options || options.options.length === 0) throw new Error("multiselect prompt requires options");
|
|
46
|
+
this.stdout.write(`${message}\n`);
|
|
47
|
+
for (let i = 0; i < options.options.length; i++) this.stdout.write(` ${i + 1}. ${options.options[i]}\n`);
|
|
48
|
+
this.stdout.write("Choices (comma-separated): ");
|
|
49
|
+
return (await this.read()).split(",").map((s) => Number.parseInt(s.trim(), 10) - 1).filter((i) => i >= 0 && i < options.options.length).map((i) => options.options[i]);
|
|
50
|
+
default: throw new Error(`Unknown prompt type: ${type}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async notify(message) {
|
|
54
|
+
this.stdout.write(`${message}\n`);
|
|
55
|
+
}
|
|
56
|
+
async clear() {
|
|
57
|
+
this.stdout.write("\x1B[2J\x1B[H");
|
|
58
|
+
}
|
|
59
|
+
hasPendingInput() {
|
|
60
|
+
return this.inputSource.hasPending();
|
|
61
|
+
}
|
|
62
|
+
getViewport() {
|
|
63
|
+
if (typeof process !== "undefined" && process.stdout && "columns" in process.stdout) return {
|
|
64
|
+
cols: process.stdout.columns,
|
|
65
|
+
rows: process.stdout.rows
|
|
66
|
+
};
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
async dispose() {}
|
|
70
|
+
};
|
|
71
|
+
/** Create a stdin-based input source (production use) */
|
|
72
|
+
function createStdinSource() {
|
|
73
|
+
const pendingLines = [];
|
|
74
|
+
let pendingResolve = null;
|
|
75
|
+
if (typeof process !== "undefined" && process.stdin) {
|
|
76
|
+
process.stdin.setEncoding("utf8");
|
|
77
|
+
process.stdin.on("data", (data) => {
|
|
78
|
+
const lines = data.split("\n").filter((l) => l.length > 0);
|
|
79
|
+
for (const line of lines) if (pendingResolve) {
|
|
80
|
+
const resolve = pendingResolve;
|
|
81
|
+
pendingResolve = null;
|
|
82
|
+
resolve(line);
|
|
83
|
+
} else pendingLines.push(line);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
readLine() {
|
|
88
|
+
if (pendingLines.length > 0) return Promise.resolve(pendingLines.shift());
|
|
89
|
+
return new Promise((resolve) => {
|
|
90
|
+
pendingResolve = resolve;
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
hasPending() {
|
|
94
|
+
return pendingLines.length > 0;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** Create a mock input source for testing */
|
|
99
|
+
function createMockInputSource(inputs = []) {
|
|
100
|
+
const queue = [...inputs];
|
|
101
|
+
let pendingResolve = null;
|
|
102
|
+
return {
|
|
103
|
+
readLine() {
|
|
104
|
+
if (queue.length > 0) return Promise.resolve(queue.shift());
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
pendingResolve = resolve;
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
hasPending() {
|
|
110
|
+
return queue.length > 0;
|
|
111
|
+
},
|
|
112
|
+
push(line) {
|
|
113
|
+
if (pendingResolve) {
|
|
114
|
+
const resolve = pendingResolve;
|
|
115
|
+
pendingResolve = null;
|
|
116
|
+
resolve(line);
|
|
117
|
+
} else queue.push(line);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function withTimeout(promise, ms) {
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
const timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Input timeout")), ms);
|
|
124
|
+
promise.then((val) => {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
resolve(val);
|
|
127
|
+
}, (err) => {
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
reject(err);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
//#endregion
|
|
135
|
+
exports.TTYBackend = TTYBackend;
|
|
136
|
+
exports.createMockInputSource = createMockInputSource;
|
package/dist/tty.d.cts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { PromptOptions, PromptResult, ReadOptions, UIBackend, ViewportInfo, WriteOptions } from "./backend.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/tty.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* TTY Backend — stdin/stdout based terminal I/O.
|
|
6
|
+
*
|
|
7
|
+
* For testing, provide mock stdin/stdout via options.
|
|
8
|
+
*/
|
|
9
|
+
interface TTYBackendOptions {
|
|
10
|
+
/** Custom writable stream (defaults to process.stdout) */
|
|
11
|
+
stdout?: {
|
|
12
|
+
write(data: string): boolean;
|
|
13
|
+
};
|
|
14
|
+
/** Custom input source for testing */
|
|
15
|
+
inputSource?: TTYInputSource;
|
|
16
|
+
/** Input timeout in ms (0 = no timeout) */
|
|
17
|
+
inputTimeout?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Input source abstraction for testability.
|
|
21
|
+
* In production, reads from process.stdin.
|
|
22
|
+
* In tests, provides programmatic input.
|
|
23
|
+
*/
|
|
24
|
+
interface TTYInputSource {
|
|
25
|
+
/** Read a line of input */
|
|
26
|
+
readLine(): Promise<string>;
|
|
27
|
+
/** Check if input is available */
|
|
28
|
+
hasPending(): boolean;
|
|
29
|
+
}
|
|
30
|
+
declare class TTYBackend implements UIBackend {
|
|
31
|
+
readonly type = "tty";
|
|
32
|
+
readonly supportedFormats: string[];
|
|
33
|
+
readonly capabilities: string[];
|
|
34
|
+
private stdout;
|
|
35
|
+
private inputSource;
|
|
36
|
+
private inputTimeout;
|
|
37
|
+
constructor(options?: TTYBackendOptions);
|
|
38
|
+
write(content: string, options?: WriteOptions): Promise<void>;
|
|
39
|
+
read(options?: ReadOptions): Promise<string>;
|
|
40
|
+
prompt(options: PromptOptions): Promise<PromptResult>;
|
|
41
|
+
notify(message: string): Promise<void>;
|
|
42
|
+
clear(): Promise<void>;
|
|
43
|
+
hasPendingInput(): boolean;
|
|
44
|
+
getViewport(): ViewportInfo;
|
|
45
|
+
dispose(): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/** Create a mock input source for testing */
|
|
48
|
+
declare function createMockInputSource(inputs?: string[]): TTYInputSource & {
|
|
49
|
+
push(line: string): void;
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { TTYBackend, TTYBackendOptions, TTYInputSource, createMockInputSource };
|
|
53
|
+
//# sourceMappingURL=tty.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tty.d.cts","names":[],"sources":["../src/tty.ts"],"mappings":";;;;;AAcA;;;UAAiB,iBAAA;EAEf;EAAA,MAAA;IAAW,KAAA,CAAM,IAAA;EAAA;EAEH;EAAd,WAAA,GAAc,cAAA;EAEF;EAAZ,YAAA;AAAA;;;;;;UAQe,cAAA;EAIL;EAFV,QAAA,IAAY,OAAA;EAKD;EAHX,UAAA;AAAA;AAAA,cAGW,UAAA,YAAsB,SAAA;EAAA,SACxB,IAAA;EAAA,SACA,gBAAA;EAAA,SACA,YAAA;EAAA,QAED,MAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;cAEI,OAAA,GAAS,iBAAA;EAMf,KAAA,CAAM,OAAA,UAAiB,OAAA,GAAU,YAAA,GAAe,OAAA;EAOhD,IAAA,CAAK,OAAA,GAAU,WAAA,GAAc,OAAA;EAQ7B,MAAA,CAAO,OAAA,EAAS,aAAA,GAAgB,OAAA,CAAQ,YAAA;EAoDxC,MAAA,CAAO,OAAA,WAAkB,OAAA;EAIzB,KAAA,CAAA,GAAS,OAAA;EAKf,eAAA,CAAA;EAIA,WAAA,CAAA,GAAe,YAAA;EAUT,OAAA,CAAA,GAAW,OAAA;AAAA;;iBA0CH,qBAAA,CAAsB,MAAA,cAAwB,cAAA;EAC5D,IAAA,CAAK,IAAA;AAAA"}
|
package/dist/tty.d.mts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { PromptOptions, PromptResult, ReadOptions, UIBackend, ViewportInfo, WriteOptions } from "./backend.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/tty.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* TTY Backend — stdin/stdout based terminal I/O.
|
|
6
|
+
*
|
|
7
|
+
* For testing, provide mock stdin/stdout via options.
|
|
8
|
+
*/
|
|
9
|
+
interface TTYBackendOptions {
|
|
10
|
+
/** Custom writable stream (defaults to process.stdout) */
|
|
11
|
+
stdout?: {
|
|
12
|
+
write(data: string): boolean;
|
|
13
|
+
};
|
|
14
|
+
/** Custom input source for testing */
|
|
15
|
+
inputSource?: TTYInputSource;
|
|
16
|
+
/** Input timeout in ms (0 = no timeout) */
|
|
17
|
+
inputTimeout?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Input source abstraction for testability.
|
|
21
|
+
* In production, reads from process.stdin.
|
|
22
|
+
* In tests, provides programmatic input.
|
|
23
|
+
*/
|
|
24
|
+
interface TTYInputSource {
|
|
25
|
+
/** Read a line of input */
|
|
26
|
+
readLine(): Promise<string>;
|
|
27
|
+
/** Check if input is available */
|
|
28
|
+
hasPending(): boolean;
|
|
29
|
+
}
|
|
30
|
+
declare class TTYBackend implements UIBackend {
|
|
31
|
+
readonly type = "tty";
|
|
32
|
+
readonly supportedFormats: string[];
|
|
33
|
+
readonly capabilities: string[];
|
|
34
|
+
private stdout;
|
|
35
|
+
private inputSource;
|
|
36
|
+
private inputTimeout;
|
|
37
|
+
constructor(options?: TTYBackendOptions);
|
|
38
|
+
write(content: string, options?: WriteOptions): Promise<void>;
|
|
39
|
+
read(options?: ReadOptions): Promise<string>;
|
|
40
|
+
prompt(options: PromptOptions): Promise<PromptResult>;
|
|
41
|
+
notify(message: string): Promise<void>;
|
|
42
|
+
clear(): Promise<void>;
|
|
43
|
+
hasPendingInput(): boolean;
|
|
44
|
+
getViewport(): ViewportInfo;
|
|
45
|
+
dispose(): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/** Create a mock input source for testing */
|
|
48
|
+
declare function createMockInputSource(inputs?: string[]): TTYInputSource & {
|
|
49
|
+
push(line: string): void;
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { TTYBackend, TTYBackendOptions, TTYInputSource, createMockInputSource };
|
|
53
|
+
//# sourceMappingURL=tty.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tty.d.mts","names":[],"sources":["../src/tty.ts"],"mappings":";;;;;AAcA;;;UAAiB,iBAAA;EAEf;EAAA,MAAA;IAAW,KAAA,CAAM,IAAA;EAAA;EAEH;EAAd,WAAA,GAAc,cAAA;EAEF;EAAZ,YAAA;AAAA;;;;;;UAQe,cAAA;EAIL;EAFV,QAAA,IAAY,OAAA;EAKD;EAHX,UAAA;AAAA;AAAA,cAGW,UAAA,YAAsB,SAAA;EAAA,SACxB,IAAA;EAAA,SACA,gBAAA;EAAA,SACA,YAAA;EAAA,QAED,MAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;cAEI,OAAA,GAAS,iBAAA;EAMf,KAAA,CAAM,OAAA,UAAiB,OAAA,GAAU,YAAA,GAAe,OAAA;EAOhD,IAAA,CAAK,OAAA,GAAU,WAAA,GAAc,OAAA;EAQ7B,MAAA,CAAO,OAAA,EAAS,aAAA,GAAgB,OAAA,CAAQ,YAAA;EAoDxC,MAAA,CAAO,OAAA,WAAkB,OAAA;EAIzB,KAAA,CAAA,GAAS,OAAA;EAKf,eAAA,CAAA;EAIA,WAAA,CAAA,GAAe,YAAA;EAUT,OAAA,CAAA,GAAW,OAAA;AAAA;;iBA0CH,qBAAA,CAAsB,MAAA,cAAwB,cAAA;EAC5D,IAAA,CAAK,IAAA;AAAA"}
|