Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

106 lines
3.0 KiB
TypeScript

type Listener = (...args: unknown[]) => void;
class MiniEmitter {
private listeners = new Map<string, Set<Listener>>();
on(event: string, fn: Listener) {
let set = this.listeners.get(event);
if (!set) {
set = new Set();
this.listeners.set(event, set);
}
set.add(fn);
return this;
}
off(event: string, fn: Listener) {
this.listeners.get(event)?.delete(fn);
return this;
}
addListener(event: string, fn: Listener) {
return this.on(event, fn);
}
removeListener(event: string, fn: Listener) {
return this.off(event, fn);
}
emit(event: string, ...args: unknown[]) {
const set = this.listeners.get(event);
if (!set) return false;
for (const fn of [...set]) fn(...args);
return set.size > 0;
}
}
export type InkStreams = {
stdout: NodeJS.WriteStream;
stdin: NodeJS.ReadStream;
pushInput: (data: string) => void;
setSize: (cols: number, rows: number) => void;
};
// Fake streams implementing the subset of the Node stream API that ink 6
// touches (see ink's Ink/App classes). The writer receives ink's ANSI output
// with bare \n rewritten to \r\n (wterm does not emulate ONLCR). Ink's
// clear-terminal sequence passes through: its 3J resets wterm's scrollback on
// every overflow repaint, leaving only the final frame's overflow scrollable.
export function createInkStreams(
writeToTerminal: (data: string) => void,
): InkStreams {
const stdoutBase = new MiniEmitter() as MiniEmitter & Record<string, unknown>;
Object.assign(stdoutBase, {
columns: 80,
rows: 24,
isTTY: true,
writable: true,
setDefaultEncoding: () => stdoutBase,
cork: () => {},
uncork: () => {},
write: (chunk: unknown, encoding?: unknown, cb?: () => void) => {
const str = typeof chunk === "string" ? chunk : String(chunk);
if (str.length > 0) {
writeToTerminal(str.replace(/\r?\n/g, "\r\n"));
}
if (typeof encoding === "function") (encoding as () => void)();
else cb?.();
return true;
},
});
const inputBuffer: string[] = [];
const stdinBase = new MiniEmitter() as MiniEmitter & Record<string, unknown>;
Object.assign(stdinBase, {
columns: 80,
rows: 24,
isTTY: true,
readable: true,
setEncoding: () => stdinBase,
setRawMode: () => stdinBase,
resume: () => stdinBase,
pause: () => stdinBase,
ref: () => stdinBase,
unref: () => stdinBase,
read: () => (inputBuffer.length > 0 ? inputBuffer.shift()! : null),
});
return {
stdout: stdoutBase as unknown as NodeJS.WriteStream,
stdin: stdinBase as unknown as NodeJS.ReadStream,
pushInput: (data: string) => {
inputBuffer.push(data);
stdinBase.emit("readable");
},
setSize: (cols: number, rows: number) => {
const changed =
stdoutBase["columns"] !== cols || stdoutBase["rows"] !== rows;
stdoutBase["columns"] = cols;
stdoutBase["rows"] = rows;
stdinBase["columns"] = cols;
stdinBase["rows"] = rows;
if (changed) stdoutBase.emit("resize");
},
};
}