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.
- package/README.md +72 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +128 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/src/config.d.ts +24 -0
- package/dist/src/config.js +32 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/face.d.ts +60 -0
- package/dist/src/face.js +472 -0
- package/dist/src/face.js.map +1 -0
- package/dist/src/frames.d.ts +1 -0
- package/dist/src/frames.js +3 -0
- package/dist/src/frames.js.map +1 -0
- package/dist/src/ipc.d.ts +27 -0
- package/dist/src/ipc.js +136 -0
- package/dist/src/ipc.js.map +1 -0
- package/dist/src/loader.d.ts +2 -0
- package/dist/src/loader.js +87 -0
- package/dist/src/loader.js.map +1 -0
- package/dist/src/main.d.ts +2 -0
- package/dist/src/main.js +201 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/patterns.d.ts +6 -0
- package/dist/src/patterns.js +14 -0
- package/dist/src/patterns.js.map +1 -0
- package/dist/src/pty.d.ts +17 -0
- package/dist/src/pty.js +84 -0
- package/dist/src/pty.js.map +1 -0
- package/dist/src/scroll.d.ts +36 -0
- package/dist/src/scroll.js +56 -0
- package/dist/src/scroll.js.map +1 -0
- package/dist/src/state.d.ts +35 -0
- package/dist/src/state.js +158 -0
- package/dist/src/state.js.map +1 -0
- package/dist/src/theme.d.ts +15 -0
- package/dist/src/theme.js +51 -0
- package/dist/src/theme.js.map +1 -0
- package/dist/src/types.d.ts +58 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils.d.ts +14 -0
- package/dist/src/utils.js +26 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +33 -0
package/dist/src/ipc.js
ADDED
|
@@ -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,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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"}
|
package/dist/src/main.js
ADDED
|
@@ -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,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;
|
package/dist/src/pty.js
ADDED
|
@@ -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":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAW9C,MAAM,UAAU,UAAU;IACxB,mCAAmC;IACnC,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IAE5D,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,SAAS,CAAC;IAEnD,iEAAiE;IACjE,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1F,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAE9B,oCAAoC;IACpC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG;QACjB,GAAG,IAAI,oBAAoB;QAC3B,uBAAuB;QACvB,0BAA0B;KAC3B,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,CAAC;YAAC,UAAU,CAAC,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;YAAC,OAAO,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,IAAI,KAAK,CACb,kFAAkF;QAClF,qCAAqC,CACtC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,IAAI,CAAC;QAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AACxD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CACzB,IAAY,EACZ,IAAY,EACZ,OAAiB,EAAE;IAEnB,MAAM,UAAU,GAAG,UAAU,EAAE,CAAC;IAChC,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAElF,IAAI,IAAc,CAAC;IACnB,IAAI,CAAC;QACH,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,EAAE;YACtC,IAAI,EAAE,gBAAgB;YACtB,IAAI;YACJ,IAAI;YACJ,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;YAClB,GAAG,EAAE,OAAO,CAAC,GAA6B;SAC3C,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,IAAI,KAAK,CACb,4BAA4B,UAAU,MAAM,GAAG,IAAI;YACnD,4BAA4B,UAAU,OAAO,UAAU,YAAY,CACpE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM,CAAC,EAA0B;YAC/B,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,CAAC,EAAyD;YAC9D,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClB,CAAC;QAED,KAAK,CAAC,IAAY;YAChB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;QAED,MAAM,CAAC,CAAS,EAAE,CAAS;YACzB,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpB,CAAC;QAED,IAAI;YACF,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;QAED,IAAI,GAAG;YACL,OAAO,IAAI,CAAC,GAAG,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -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;
|