claude-face 0.1.0

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 (44) hide show
  1. package/README.md +72 -0
  2. package/dist/bin/cli.d.ts +2 -0
  3. package/dist/bin/cli.js +128 -0
  4. package/dist/bin/cli.js.map +1 -0
  5. package/dist/src/config.d.ts +24 -0
  6. package/dist/src/config.js +32 -0
  7. package/dist/src/config.js.map +1 -0
  8. package/dist/src/face.d.ts +60 -0
  9. package/dist/src/face.js +472 -0
  10. package/dist/src/face.js.map +1 -0
  11. package/dist/src/frames.d.ts +1 -0
  12. package/dist/src/frames.js +3 -0
  13. package/dist/src/frames.js.map +1 -0
  14. package/dist/src/ipc.d.ts +27 -0
  15. package/dist/src/ipc.js +136 -0
  16. package/dist/src/ipc.js.map +1 -0
  17. package/dist/src/loader.d.ts +2 -0
  18. package/dist/src/loader.js +87 -0
  19. package/dist/src/loader.js.map +1 -0
  20. package/dist/src/main.d.ts +2 -0
  21. package/dist/src/main.js +201 -0
  22. package/dist/src/main.js.map +1 -0
  23. package/dist/src/patterns.d.ts +6 -0
  24. package/dist/src/patterns.js +14 -0
  25. package/dist/src/patterns.js.map +1 -0
  26. package/dist/src/pty.d.ts +17 -0
  27. package/dist/src/pty.js +84 -0
  28. package/dist/src/pty.js.map +1 -0
  29. package/dist/src/scroll.d.ts +36 -0
  30. package/dist/src/scroll.js +56 -0
  31. package/dist/src/scroll.js.map +1 -0
  32. package/dist/src/state.d.ts +35 -0
  33. package/dist/src/state.js +158 -0
  34. package/dist/src/state.js.map +1 -0
  35. package/dist/src/theme.d.ts +15 -0
  36. package/dist/src/theme.js +51 -0
  37. package/dist/src/theme.js.map +1 -0
  38. package/dist/src/types.d.ts +58 -0
  39. package/dist/src/types.js +3 -0
  40. package/dist/src/types.js.map +1 -0
  41. package/dist/src/utils.d.ts +14 -0
  42. package/dist/src/utils.js +26 -0
  43. package/dist/src/utils.js.map +1 -0
  44. package/package.json +33 -0
@@ -0,0 +1,136 @@
1
+ import * as net from 'node:net';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import { config } from './config.js';
5
+ // ── Socket path ─────────────────────────────────────────────────────
6
+ export function getSocketPath() {
7
+ const tmpdir = os.tmpdir();
8
+ return `${tmpdir}/claude-face.sock`;
9
+ }
10
+ // ── Server (wrapper process — terminal 1) ───────────────────────────
11
+ export class StateServer {
12
+ server;
13
+ clients = new Set();
14
+ socketPath;
15
+ constructor(socketPath) {
16
+ this.socketPath = socketPath;
17
+ // Remove stale socket file if it exists
18
+ try {
19
+ fs.unlinkSync(socketPath);
20
+ }
21
+ catch {
22
+ // doesn't exist — fine
23
+ }
24
+ this.server = net.createServer((socket) => {
25
+ this.clients.add(socket);
26
+ socket.on('close', () => this.clients.delete(socket));
27
+ socket.on('error', () => this.clients.delete(socket));
28
+ });
29
+ this.server.listen(socketPath);
30
+ }
31
+ broadcast(state) {
32
+ const msg = { state, ts: Date.now() };
33
+ const line = JSON.stringify(msg) + '\n';
34
+ for (const socket of this.clients) {
35
+ try {
36
+ socket.write(line);
37
+ }
38
+ catch {
39
+ this.clients.delete(socket);
40
+ }
41
+ }
42
+ }
43
+ close() {
44
+ for (const socket of this.clients) {
45
+ try {
46
+ socket.destroy();
47
+ }
48
+ catch {
49
+ // ignore
50
+ }
51
+ }
52
+ this.clients.clear();
53
+ this.server.close();
54
+ try {
55
+ fs.unlinkSync(this.socketPath);
56
+ }
57
+ catch {
58
+ // ignore
59
+ }
60
+ }
61
+ }
62
+ // ── Client (face renderer — terminal 2) ─────────────────────────────
63
+ export class StateClient {
64
+ socket = null;
65
+ socketPath;
66
+ onState;
67
+ onDisconnect;
68
+ onConnect;
69
+ buffer = '';
70
+ retryTimer = null;
71
+ closed = false;
72
+ constructor(socketPath, onState, opts) {
73
+ this.socketPath = socketPath;
74
+ this.onState = onState;
75
+ this.onDisconnect = opts?.onDisconnect;
76
+ this.onConnect = opts?.onConnect;
77
+ this.connect();
78
+ }
79
+ connect() {
80
+ if (this.closed)
81
+ return;
82
+ this.socket = net.createConnection(this.socketPath);
83
+ this.socket.on('connect', () => {
84
+ this.buffer = '';
85
+ this.onConnect?.();
86
+ });
87
+ this.socket.on('data', (data) => {
88
+ this.buffer += data.toString();
89
+ const lines = this.buffer.split('\n');
90
+ // Keep the incomplete last chunk in the buffer
91
+ this.buffer = lines.pop();
92
+ for (const line of lines) {
93
+ if (!line)
94
+ continue;
95
+ try {
96
+ const msg = JSON.parse(line);
97
+ this.onState(msg.state);
98
+ }
99
+ catch {
100
+ // malformed JSON — skip
101
+ }
102
+ }
103
+ });
104
+ this.socket.on('close', () => {
105
+ if (this.closed)
106
+ return;
107
+ this.onDisconnect?.();
108
+ this.scheduleRetry();
109
+ });
110
+ this.socket.on('error', () => {
111
+ if (this.closed)
112
+ return;
113
+ // Will trigger 'close' which handles reconnection
114
+ });
115
+ }
116
+ scheduleRetry() {
117
+ if (this.closed)
118
+ return;
119
+ this.retryTimer = setTimeout(() => {
120
+ this.retryTimer = null;
121
+ this.connect();
122
+ }, config.ipcRetryMs);
123
+ }
124
+ close() {
125
+ this.closed = true;
126
+ if (this.retryTimer !== null) {
127
+ clearTimeout(this.retryTimer);
128
+ this.retryTimer = null;
129
+ }
130
+ if (this.socket) {
131
+ this.socket.destroy();
132
+ this.socket = null;
133
+ }
134
+ }
135
+ }
136
+ //# sourceMappingURL=ipc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipc.js","sourceRoot":"","sources":["../../src/ipc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { FaceDefinition } from './types.js';
2
+ export declare function loadDefaultFace(): FaceDefinition;
@@ -0,0 +1,87 @@
1
+ import { readFileSync, existsSync, readdirSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ // ── Locate faces/ directory ──────────────────────────────────────────
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ function findProjectRoot() {
8
+ let dir = __dirname;
9
+ while (dir !== dirname(dir)) {
10
+ if (existsSync(join(dir, 'package.json')))
11
+ return dir;
12
+ dir = dirname(dir);
13
+ }
14
+ throw new Error('Could not find project root (no package.json found)');
15
+ }
16
+ const FACES_DIR = join(findProjectRoot(), 'faces');
17
+ // ── Parser ───────────────────────────────────────────────────────────
18
+ /**
19
+ * Load an expression from a folder: faces/{name}/
20
+ *
21
+ * Reads config.json for animation config, then loads all .txt files
22
+ * sorted numerically as individual frames.
23
+ */
24
+ function loadExpression(name) {
25
+ const dir = join(FACES_DIR, name);
26
+ // Read animation config
27
+ const configPath = join(dir, 'config.json');
28
+ const configRaw = readFileSync(configPath, 'utf-8');
29
+ const config = JSON.parse(configRaw);
30
+ // Scan for .txt frame files, sort numerically
31
+ const files = readdirSync(dir)
32
+ .filter((f) => f.endsWith('.txt'))
33
+ .sort((a, b) => parseInt(a) - parseInt(b));
34
+ if (files.length === 0) {
35
+ throw new Error(`Expression "${name}" has no frame .txt files`);
36
+ }
37
+ const frames = files.map((file) => {
38
+ const raw = readFileSync(join(dir, file), 'utf-8');
39
+ const lines = raw.split('\n');
40
+ // Trim trailing empty line (from trailing newline in file)
41
+ while (lines.length > 0 && lines[lines.length - 1].trim() === '')
42
+ lines.pop();
43
+ while (lines.length > 0 && lines[0].trim() === '')
44
+ lines.shift();
45
+ return lines;
46
+ });
47
+ if (frames[0].length === 0) {
48
+ throw new Error(`Expression "${name}" frame 01.txt is empty`);
49
+ }
50
+ // Infer width from longest line across all frames
51
+ let maxWidth = 0;
52
+ for (const lines of frames) {
53
+ for (const line of lines) {
54
+ if (line.length > maxWidth)
55
+ maxWidth = line.length;
56
+ }
57
+ }
58
+ // Pad all lines to maxWidth and build Frame objects
59
+ const expressionFrames = frames.map((lines) => ({
60
+ art: lines.map((line) => line.length >= maxWidth ? line.slice(0, maxWidth) : line + ' '.repeat(maxWidth - line.length)),
61
+ colors: [],
62
+ }));
63
+ return {
64
+ name,
65
+ frames: expressionFrames,
66
+ config,
67
+ };
68
+ }
69
+ // ── Build FaceDefinition ─────────────────────────────────────────────
70
+ const ALL_EXPRESSIONS = ['idle', 'typing', 'thinking', 'listening'];
71
+ export function loadDefaultFace() {
72
+ const expressions = {};
73
+ for (const name of ALL_EXPRESSIONS) {
74
+ expressions[name] = loadExpression(name);
75
+ }
76
+ // Dimensions from idle (the reference expression)
77
+ const idleFrame = expressions['idle'].frames[0];
78
+ const height = idleFrame.art.length;
79
+ const width = idleFrame.art[0].length;
80
+ return {
81
+ name: 'default',
82
+ width,
83
+ height,
84
+ expressions,
85
+ };
86
+ }
87
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { CliOptions } from './types.js';
2
+ export declare function run(options: CliOptions): Promise<void>;
@@ -0,0 +1,201 @@
1
+ import { spawnClaude } from './pty.js';
2
+ import { StateMachine } from './state.js';
3
+ import { FaceRenderer } from './face.js';
4
+ import { getTheme } from './theme.js';
5
+ import { defaultFace } from './frames.js';
6
+ import { StateServer, StateClient, getSocketPath } from './ipc.js';
7
+ // ── Required expression keys ────────────────────────────────────────
8
+ const REQUIRED_EXPRESSIONS = ['idle', 'typing', 'thinking', 'listening'];
9
+ // ── Face validation ─────────────────────────────────────────────────
10
+ function validateFace(face) {
11
+ if (!face.name || typeof face.name !== 'string') {
12
+ throw new Error('Face definition must have a "name" string property.');
13
+ }
14
+ if (typeof face.width !== 'number' || face.width <= 0) {
15
+ throw new Error('Face definition must have a positive "width" number.');
16
+ }
17
+ if (typeof face.height !== 'number' || face.height <= 0) {
18
+ throw new Error('Face definition must have a positive "height" number.');
19
+ }
20
+ if (!face.expressions || typeof face.expressions !== 'object') {
21
+ throw new Error('Face definition must have an "expressions" object.');
22
+ }
23
+ // Check all required expression keys are present
24
+ for (const key of REQUIRED_EXPRESSIONS) {
25
+ if (!(key in face.expressions)) {
26
+ throw new Error(`Face definition is missing required expression "${key}". ` +
27
+ `Required expressions: ${REQUIRED_EXPRESSIONS.join(', ')}.`);
28
+ }
29
+ const expr = face.expressions[key];
30
+ if (!expr.frames || !Array.isArray(expr.frames) || expr.frames.length === 0) {
31
+ throw new Error(`Expression "${key}" must have a non-empty "frames" array.`);
32
+ }
33
+ // Validate animation config
34
+ const config = expr.config;
35
+ if (!config || typeof config !== 'object') {
36
+ throw new Error(`Expression "${key}" must have a "config" object.`);
37
+ }
38
+ if (typeof config.loopDuration !== 'number' || config.loopDuration <= 0) {
39
+ throw new Error(`Expression "${key}": config.loopDuration must be a positive number.`);
40
+ }
41
+ if (typeof config.gap !== 'number' || config.gap < 0) {
42
+ throw new Error(`Expression "${key}": config.gap must be a non-negative number.`);
43
+ }
44
+ if (typeof config.reverse !== 'boolean') {
45
+ throw new Error(`Expression "${key}": config.reverse must be a boolean.`);
46
+ }
47
+ // Validate each frame
48
+ for (let i = 0; i < expr.frames.length; i++) {
49
+ const frame = expr.frames[i];
50
+ if (!frame.art || !Array.isArray(frame.art)) {
51
+ throw new Error(`Expression "${key}", frame ${i}: must have an "art" array of strings.`);
52
+ }
53
+ if (frame.art.length !== face.height) {
54
+ throw new Error(`Expression "${key}", frame ${i}: art has ${frame.art.length} rows ` +
55
+ `but face.height is ${face.height}.`);
56
+ }
57
+ for (let r = 0; r < frame.art.length; r++) {
58
+ if (frame.art[r].length !== face.width) {
59
+ throw new Error(`Expression "${key}", frame ${i}, row ${r}: length is ` +
60
+ `${frame.art[r].length} but face.width is ${face.width}.`);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+ // ── Load face definition ────────────────────────────────────────────
67
+ async function loadFace(facePath) {
68
+ if (facePath) {
69
+ try {
70
+ const imported = await import(facePath);
71
+ const face = imported.default ?? imported.face ?? imported;
72
+ validateFace(face);
73
+ return face;
74
+ }
75
+ catch (err) {
76
+ if (err instanceof Error) {
77
+ throw new Error(`Failed to load face from "${facePath}": ${err.message}`);
78
+ }
79
+ throw err;
80
+ }
81
+ }
82
+ return defaultFace;
83
+ }
84
+ // ── Main entry point ────────────────────────────────────────────────
85
+ export async function run(options) {
86
+ // ── Face-only mode ──────────────────────────────────────────────
87
+ if (options.faceOnly) {
88
+ return runFaceOnly(options);
89
+ }
90
+ // ── Load face ───────────────────────────────────────────────────
91
+ const face = await loadFace(options.facePath);
92
+ // ── --no-face mode (plain passthrough) ──────────────────────────
93
+ if (options.noFace) {
94
+ const cols = process.stdout.columns ?? 80;
95
+ const rows = process.stdout.rows ?? 24;
96
+ const pty = spawnClaude(cols, rows, []);
97
+ process.stdin.setRawMode(true);
98
+ process.stdin.resume();
99
+ process.stdin.on('data', (data) => pty.write(data.toString()));
100
+ pty.onData((data) => process.stdout.write(data));
101
+ pty.onExit(({ exitCode }) => {
102
+ process.stdin.setRawMode(false);
103
+ process.exit(exitCode);
104
+ });
105
+ return;
106
+ }
107
+ // ── Wrapper mode (default) ──────────────────────────────────────
108
+ // Start IPC server
109
+ const socketPath = getSocketPath();
110
+ const server = new StateServer(socketPath);
111
+ process.stderr.write(`Face server listening. In another terminal run: claude-face --face-only\n`);
112
+ // Spawn Claude PTY at full terminal size
113
+ const cols = process.stdout.columns ?? 80;
114
+ const rows = process.stdout.rows ?? 24;
115
+ const pty = spawnClaude(cols, rows, []);
116
+ // State machine — broadcast state changes over IPC
117
+ const stateMachine = new StateMachine((event) => {
118
+ server.broadcast(event.next);
119
+ });
120
+ // Wire PTY data → stdout + state machine
121
+ pty.onData((data) => {
122
+ process.stdout.write(data);
123
+ stateMachine.feed(data);
124
+ });
125
+ // Wire stdin → PTY + notify state machine of human input
126
+ process.stdin.setRawMode(true);
127
+ process.stdin.resume();
128
+ process.stdin.on('data', (data) => {
129
+ stateMachine.notifyHumanInput();
130
+ pty.write(data.toString());
131
+ });
132
+ // Handle SIGWINCH (terminal resize)
133
+ process.on('SIGWINCH', () => {
134
+ const newCols = process.stdout.columns ?? 80;
135
+ const newRows = process.stdout.rows ?? 24;
136
+ pty.resize(newCols, newRows);
137
+ });
138
+ // Cleanup
139
+ const cleanup = () => {
140
+ stateMachine.destroy();
141
+ server.close();
142
+ try {
143
+ process.stdin.setRawMode(false);
144
+ }
145
+ catch {
146
+ // stdin may already be destroyed
147
+ }
148
+ };
149
+ pty.onExit(({ exitCode }) => {
150
+ cleanup();
151
+ process.exit(exitCode);
152
+ });
153
+ process.on('SIGINT', () => {
154
+ cleanup();
155
+ process.exit(0);
156
+ });
157
+ process.on('SIGTERM', () => {
158
+ cleanup();
159
+ process.exit(0);
160
+ });
161
+ }
162
+ // ── Face-only mode ──────────────────────────────────────────────────
163
+ async function runFaceOnly(options) {
164
+ const face = await loadFace(options.facePath);
165
+ const theme = getTheme(options.theme);
166
+ const renderer = new FaceRenderer(face, theme);
167
+ const socketPath = getSocketPath();
168
+ renderer.setState('idle');
169
+ renderer.showMessage('Waiting for claude-face...');
170
+ // Connect to wrapper's IPC server
171
+ const client = new StateClient(socketPath, (state) => {
172
+ renderer.setState(state);
173
+ }, {
174
+ onDisconnect() {
175
+ renderer.showMessage('Disconnected. Waiting for claude-face...');
176
+ },
177
+ onConnect() {
178
+ renderer.setState('idle');
179
+ },
180
+ });
181
+ // Handle resize — invalidate pre-rendered frame cache
182
+ process.on('SIGWINCH', () => {
183
+ renderer.invalidateCache();
184
+ });
185
+ // Cleanup
186
+ const cleanup = () => {
187
+ renderer.destroy();
188
+ client.close();
189
+ // Show cursor on exit
190
+ process.stdout.write('\x1b[?25h');
191
+ };
192
+ process.on('SIGINT', () => {
193
+ cleanup();
194
+ process.exit(0);
195
+ });
196
+ process.on('SIGTERM', () => {
197
+ cleanup();
198
+ process.exit(0);
199
+ });
200
+ }
201
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { FaceState } from './types.js';
2
+ export interface PatternEntry {
3
+ state: FaceState;
4
+ patterns: RegExp[];
5
+ }
6
+ export declare const realtimePatterns: PatternEntry[];
@@ -0,0 +1,14 @@
1
+ // ── Realtime Patterns ────────────────────────────────────────────────
2
+ // Checked during streaming (each feed() call). Priority = array order.
3
+ export const realtimePatterns = [
4
+ {
5
+ state: 'thinking',
6
+ patterns: [
7
+ /[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/, // braille spinners
8
+ /[·✢✳✶✻✽]/, // tweakcc phases
9
+ /Thinking/,
10
+ /Reasoning/,
11
+ ],
12
+ },
13
+ ];
14
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../src/patterns.ts"],"names":[],"mappings":"AASA,wEAAwE;AACxE,uEAAuE;AAEvE,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C;QACE,KAAK,EAAE,UAAU;QACjB,QAAQ,EAAE;YACR,cAAc,EAAK,mBAAmB;YACtC,UAAU,EAAe,iBAAiB;YAC1C,UAAU;YACV,WAAW;SACZ;KACF;CACF,CAAC"}
@@ -0,0 +1,17 @@
1
+ export interface PtyHandle {
2
+ onData(cb: (data: string) => void): void;
3
+ onExit(cb: (exit: {
4
+ exitCode: number;
5
+ signal?: number;
6
+ }) => void): void;
7
+ write(data: string): void;
8
+ resize(cols: number, rows: number): void;
9
+ kill(): void;
10
+ pid: number;
11
+ }
12
+ export declare function findClaude(): string;
13
+ export declare function resolveSymlink(p: string): string | null;
14
+ /**
15
+ * Spawn `claude` CLI in a pseudo-terminal.
16
+ */
17
+ export declare function spawnClaude(cols: number, rows: number, args?: string[]): PtyHandle;
@@ -0,0 +1,84 @@
1
+ import * as pty from 'node-pty';
2
+ import { accessSync, constants, readlinkSync } from 'node:fs';
3
+ import { execSync } from 'node:child_process';
4
+ export function findClaude() {
5
+ // 1. Explicit override via env var
6
+ if (process.env.CLAUDE_PATH)
7
+ return process.env.CLAUDE_PATH;
8
+ if (process.platform === 'win32')
9
+ return 'cmd.exe';
10
+ // 2. Resolve via user's login shell (picks up ~/.local/bin etc.)
11
+ try {
12
+ const resolved = execSync('which claude', { encoding: 'utf-8', env: process.env }).trim();
13
+ if (resolved)
14
+ return resolved;
15
+ }
16
+ catch { /* fall through */ }
17
+ // 3. Check common install locations
18
+ const home = process.env.HOME ?? '';
19
+ const candidates = [
20
+ `${home}/.local/bin/claude`,
21
+ '/usr/local/bin/claude',
22
+ '/opt/homebrew/bin/claude',
23
+ ];
24
+ for (const p of candidates) {
25
+ try {
26
+ accessSync(p, constants.X_OK);
27
+ return p;
28
+ }
29
+ catch { /* next */ }
30
+ }
31
+ throw new Error('Could not find "claude" CLI. Ensure Claude Code is installed and on your PATH,\n' +
32
+ 'or set CLAUDE_PATH=/path/to/claude.');
33
+ }
34
+ export function resolveSymlink(p) {
35
+ try {
36
+ return readlinkSync(p);
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ /**
43
+ * Spawn `claude` CLI in a pseudo-terminal.
44
+ */
45
+ export function spawnClaude(cols, rows, args = []) {
46
+ const claudePath = findClaude();
47
+ const shellArgs = process.platform === 'win32' ? ['/c', 'claude', ...args] : args;
48
+ let proc;
49
+ try {
50
+ proc = pty.spawn(claudePath, shellArgs, {
51
+ name: 'xterm-256color',
52
+ cols,
53
+ rows,
54
+ cwd: process.cwd(),
55
+ env: process.env,
56
+ });
57
+ }
58
+ catch (err) {
59
+ const msg = err instanceof Error ? err.message : String(err);
60
+ throw new Error(`Failed to spawn PTY for "${claudePath}": ${msg}\n` +
61
+ `Tip: verify with: ls -la ${claudePath} && ${claudePath} --version`);
62
+ }
63
+ return {
64
+ onData(cb) {
65
+ proc.onData(cb);
66
+ },
67
+ onExit(cb) {
68
+ proc.onExit(cb);
69
+ },
70
+ write(data) {
71
+ proc.write(data);
72
+ },
73
+ resize(c, r) {
74
+ proc.resize(c, r);
75
+ },
76
+ kill() {
77
+ proc.kill();
78
+ },
79
+ get pid() {
80
+ return proc.pid;
81
+ },
82
+ };
83
+ }
84
+ //# sourceMappingURL=pty.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pty.js","sourceRoot":"","sources":["../../src/pty.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * ANSI scroll-region management.
3
+ *
4
+ * Uses DECSTBM (`ESC [ top ; bottom r`) to split the terminal into a
5
+ * fixed face region at the top and a scrollable content region below.
6
+ */
7
+ export interface ScrollDimensions {
8
+ faceRows: number;
9
+ contentRows: number;
10
+ totalRows: number;
11
+ totalCols: number;
12
+ }
13
+ /**
14
+ * Return current terminal dimensions split by face height.
15
+ */
16
+ export declare function getScrollDimensions(faceHeight: number): ScrollDimensions;
17
+ /**
18
+ * Set the terminal scroll region so that the top `faceHeight` rows are
19
+ * fixed and only the rows below them scroll.
20
+ *
21
+ * Returns a cleanup function that resets the scroll region.
22
+ */
23
+ export declare function setupScrollRegion(faceHeight: number): () => void;
24
+ /**
25
+ * Reset scroll region to the full terminal and show cursor.
26
+ */
27
+ export declare function teardownScrollRegion(): void;
28
+ /**
29
+ * Save cursor position and move to row 0, col 0 (top-left) for face
30
+ * rendering. Uses 1-indexed ANSI coordinates.
31
+ */
32
+ export declare function moveCursorToFace(): void;
33
+ /**
34
+ * Restore the previously saved cursor position.
35
+ */
36
+ export declare function restoreCursor(): void;