cc-face 0.1.10
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 +102 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +142 -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 +59 -0
- package/dist/src/face.js +493 -0
- package/dist/src/face.js.map +1 -0
- package/dist/src/frames.d.ts +1 -0
- package/dist/src/frames.js +6 -0
- package/dist/src/frames.js.map +1 -0
- package/dist/src/generator.d.ts +16 -0
- package/dist/src/generator.js +122 -0
- package/dist/src/generator.js.map +1 -0
- package/dist/src/ipc.d.ts +34 -0
- package/dist/src/ipc.js +203 -0
- package/dist/src/ipc.js.map +1 -0
- package/dist/src/loader.d.ts +12 -0
- package/dist/src/loader.js +63 -0
- package/dist/src/loader.js.map +1 -0
- package/dist/src/main.d.ts +2 -0
- package/dist/src/main.js +210 -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 +36 -0
- package/dist/src/state.js +164 -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 +60 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils.d.ts +8 -0
- package/dist/src/utils.js +13 -0
- package/dist/src/utils.js.map +1 -0
- package/faces/idle/config.json +7 -0
- package/faces/idle/img/1.jpeg +0 -0
- package/faces/idle/img/10.jpeg +0 -0
- package/faces/idle/img/11.jpeg +0 -0
- package/faces/idle/img/12.jpeg +0 -0
- package/faces/idle/img/13.jpeg +0 -0
- package/faces/idle/img/14.jpeg +0 -0
- package/faces/idle/img/15.jpeg +0 -0
- package/faces/idle/img/16.jpeg +0 -0
- package/faces/idle/img/17.jpeg +0 -0
- package/faces/idle/img/18.jpeg +0 -0
- package/faces/idle/img/19.jpeg +0 -0
- package/faces/idle/img/2.jpeg +0 -0
- package/faces/idle/img/20.jpeg +0 -0
- package/faces/idle/img/21.jpeg +0 -0
- package/faces/idle/img/22.jpeg +0 -0
- package/faces/idle/img/23.jpeg +0 -0
- package/faces/idle/img/24.jpeg +0 -0
- package/faces/idle/img/25.jpeg +0 -0
- package/faces/idle/img/26.jpeg +0 -0
- package/faces/idle/img/27.jpeg +0 -0
- package/faces/idle/img/28.jpeg +0 -0
- package/faces/idle/img/29.jpeg +0 -0
- package/faces/idle/img/3.jpeg +0 -0
- package/faces/idle/img/30.jpeg +0 -0
- package/faces/idle/img/4.jpeg +0 -0
- package/faces/idle/img/5.jpeg +0 -0
- package/faces/idle/img/6.jpeg +0 -0
- package/faces/idle/img/7.jpeg +0 -0
- package/faces/idle/img/8.jpeg +0 -0
- package/faces/idle/img/9.jpeg +0 -0
- package/faces/listening/config.json +5 -0
- package/faces/listening/img/1.jpeg +0 -0
- package/faces/listening/img/10.jpeg +0 -0
- package/faces/listening/img/11.jpeg +0 -0
- package/faces/listening/img/12.jpeg +0 -0
- package/faces/listening/img/13.jpeg +0 -0
- package/faces/listening/img/14.jpeg +0 -0
- package/faces/listening/img/15.jpeg +0 -0
- package/faces/listening/img/16.jpeg +0 -0
- package/faces/listening/img/17.jpeg +0 -0
- package/faces/listening/img/18.jpeg +0 -0
- package/faces/listening/img/19.jpeg +0 -0
- package/faces/listening/img/2.jpeg +0 -0
- package/faces/listening/img/20.jpeg +0 -0
- package/faces/listening/img/21.jpeg +0 -0
- package/faces/listening/img/22.jpeg +0 -0
- package/faces/listening/img/23.jpeg +0 -0
- package/faces/listening/img/24.jpeg +0 -0
- package/faces/listening/img/25.jpeg +0 -0
- package/faces/listening/img/26.jpeg +0 -0
- package/faces/listening/img/27.jpeg +0 -0
- package/faces/listening/img/3.jpeg +0 -0
- package/faces/listening/img/4.jpeg +0 -0
- package/faces/listening/img/5.jpeg +0 -0
- package/faces/listening/img/6.jpeg +0 -0
- package/faces/listening/img/7.jpeg +0 -0
- package/faces/listening/img/8.jpeg +0 -0
- package/faces/listening/img/9.jpeg +0 -0
- package/faces/thinking/config.json +5 -0
- package/faces/thinking/img/1.jpeg +0 -0
- package/faces/thinking/img/10.jpeg +0 -0
- package/faces/thinking/img/11.jpeg +0 -0
- package/faces/thinking/img/12.jpeg +0 -0
- package/faces/thinking/img/13.jpeg +0 -0
- package/faces/thinking/img/14.jpeg +0 -0
- package/faces/thinking/img/15.jpeg +0 -0
- package/faces/thinking/img/16.jpeg +0 -0
- package/faces/thinking/img/17.jpeg +0 -0
- package/faces/thinking/img/18.jpeg +0 -0
- package/faces/thinking/img/19.jpeg +0 -0
- package/faces/thinking/img/2.jpeg +0 -0
- package/faces/thinking/img/20.jpeg +0 -0
- package/faces/thinking/img/3.jpeg +0 -0
- package/faces/thinking/img/4.jpeg +0 -0
- package/faces/thinking/img/5.jpeg +0 -0
- package/faces/thinking/img/6.jpeg +0 -0
- package/faces/thinking/img/7.jpeg +0 -0
- package/faces/thinking/img/8.jpeg +0 -0
- package/faces/thinking/img/9.jpeg +0 -0
- package/faces/typing/config.json +5 -0
- package/faces/typing/img/1.jpeg +0 -0
- package/faces/typing/img/10.jpeg +0 -0
- package/faces/typing/img/11.jpeg +0 -0
- package/faces/typing/img/12.jpeg +0 -0
- package/faces/typing/img/13.jpeg +0 -0
- package/faces/typing/img/14.jpeg +0 -0
- package/faces/typing/img/15.jpeg +0 -0
- package/faces/typing/img/16.jpeg +0 -0
- package/faces/typing/img/17.jpeg +0 -0
- package/faces/typing/img/18.jpeg +0 -0
- package/faces/typing/img/19.jpeg +0 -0
- package/faces/typing/img/2.jpeg +0 -0
- package/faces/typing/img/20.jpeg +0 -0
- package/faces/typing/img/21.jpeg +0 -0
- package/faces/typing/img/22.jpeg +0 -0
- package/faces/typing/img/23.jpeg +0 -0
- package/faces/typing/img/24.jpeg +0 -0
- package/faces/typing/img/25.jpeg +0 -0
- package/faces/typing/img/26.jpeg +0 -0
- package/faces/typing/img/27.jpeg +0 -0
- package/faces/typing/img/28.jpeg +0 -0
- package/faces/typing/img/29.jpeg +0 -0
- package/faces/typing/img/3.jpeg +0 -0
- package/faces/typing/img/30.jpeg +0 -0
- package/faces/typing/img/31.jpeg +0 -0
- package/faces/typing/img/32.jpeg +0 -0
- package/faces/typing/img/4.jpeg +0 -0
- package/faces/typing/img/5.jpeg +0 -0
- package/faces/typing/img/6.jpeg +0 -0
- package/faces/typing/img/7.jpeg +0 -0
- package/faces/typing/img/8.jpeg +0 -0
- package/faces/typing/img/9.jpeg +0 -0
- package/package.json +36 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import sharp from 'sharp';
|
|
4
|
+
// ASCII gradient matching ascii_convert.py (dark → light)
|
|
5
|
+
const ASCII_CHARS = '@%#*+=-:. ';
|
|
6
|
+
const ASPECT_RATIO_FACTOR = 0.55;
|
|
7
|
+
let decodedExpressions = null;
|
|
8
|
+
/**
|
|
9
|
+
* Decode all JPEGs from faces/{name}/img/ into grayscale pixel buffers.
|
|
10
|
+
* Called once at startup. Caches results in memory.
|
|
11
|
+
*/
|
|
12
|
+
export async function decodeAllFrames(facesDir, expressionNames) {
|
|
13
|
+
decodedExpressions = new Map();
|
|
14
|
+
for (const name of expressionNames) {
|
|
15
|
+
const imgDir = join(facesDir, name, 'img');
|
|
16
|
+
const configPath = join(facesDir, name, 'config.json');
|
|
17
|
+
if (!existsSync(imgDir))
|
|
18
|
+
continue;
|
|
19
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
20
|
+
// Sort JPEG files numerically
|
|
21
|
+
const files = readdirSync(imgDir)
|
|
22
|
+
.filter(f => /\.jpe?g$/i.test(f))
|
|
23
|
+
.sort((a, b) => {
|
|
24
|
+
const na = parseInt(a);
|
|
25
|
+
const nb = parseInt(b);
|
|
26
|
+
return na - nb;
|
|
27
|
+
});
|
|
28
|
+
if (files.length === 0)
|
|
29
|
+
continue;
|
|
30
|
+
const decoded = [];
|
|
31
|
+
for (const file of files) {
|
|
32
|
+
const imgPath = join(imgDir, file);
|
|
33
|
+
const { data, info } = await sharp(imgPath)
|
|
34
|
+
.grayscale()
|
|
35
|
+
.negate() // invert: white→black (same as PIL ImageOps.invert)
|
|
36
|
+
.raw()
|
|
37
|
+
.toBuffer({ resolveWithObject: true });
|
|
38
|
+
decoded.push({
|
|
39
|
+
pixels: data,
|
|
40
|
+
srcWidth: info.width,
|
|
41
|
+
srcHeight: info.height,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
decodedExpressions.set(name, { name, frames: decoded, config });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Generate ASCII frames for an expression at the given terminal width.
|
|
49
|
+
* Uses cached decoded pixel data — no disk I/O.
|
|
50
|
+
* @param maxRows - optional cap on art height (e.g. visible rows in terminal)
|
|
51
|
+
*/
|
|
52
|
+
export function generateAsciiFrames(expressionName, targetWidth, maxRows) {
|
|
53
|
+
if (!decodedExpressions)
|
|
54
|
+
return null;
|
|
55
|
+
const decoded = decodedExpressions.get(expressionName);
|
|
56
|
+
if (!decoded)
|
|
57
|
+
return null;
|
|
58
|
+
const frames = decoded.frames.map(df => {
|
|
59
|
+
return rasterizeFrame(df, targetWidth, maxRows);
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
name: expressionName,
|
|
63
|
+
frames,
|
|
64
|
+
config: decoded.config,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if decoded frames are available (sharp loaded + JPEGs decoded).
|
|
69
|
+
*/
|
|
70
|
+
export function hasDecodedFrames() {
|
|
71
|
+
return decodedExpressions !== null && decodedExpressions.size > 0;
|
|
72
|
+
}
|
|
73
|
+
// Quantization level boundaries — precomputed for the ASCII gradient length
|
|
74
|
+
const NUM_LEVELS = ASCII_CHARS.length; // 10
|
|
75
|
+
const LEVEL_STEP = 255 / (NUM_LEVELS - 1); // ~28.33
|
|
76
|
+
// ── Internal ───────────────────────────────────────────────────────
|
|
77
|
+
function rasterizeFrame(df, targetWidth, maxRows) {
|
|
78
|
+
const { pixels, srcWidth, srcHeight } = df;
|
|
79
|
+
const aspectRatio = srcHeight / srcWidth;
|
|
80
|
+
let targetHeight = Math.max(1, Math.floor(targetWidth * aspectRatio * ASPECT_RATIO_FACTOR));
|
|
81
|
+
// Clamp to maxRows — avoids rasterizing rows that will be clipped by the terminal
|
|
82
|
+
if (maxRows !== undefined && maxRows > 0) {
|
|
83
|
+
targetHeight = Math.min(targetHeight, maxRows);
|
|
84
|
+
}
|
|
85
|
+
// Downsample source pixels into a float buffer at target resolution,
|
|
86
|
+
// then apply Floyd-Steinberg dithering in-place before quantizing.
|
|
87
|
+
const buf = new Float32Array(targetWidth * targetHeight);
|
|
88
|
+
for (let y = 0; y < targetHeight; y++) {
|
|
89
|
+
for (let x = 0; x < targetWidth; x++) {
|
|
90
|
+
const srcX = Math.min(Math.floor(x * srcWidth / targetWidth), srcWidth - 1);
|
|
91
|
+
const srcY = Math.min(Math.floor(y * srcHeight / targetHeight), srcHeight - 1);
|
|
92
|
+
buf[y * targetWidth + x] = pixels[srcY * srcWidth + srcX];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Floyd-Steinberg dithering: distribute quantization error to neighbors
|
|
96
|
+
const art = [];
|
|
97
|
+
const rowBuf = new Array(targetWidth);
|
|
98
|
+
for (let y = 0; y < targetHeight; y++) {
|
|
99
|
+
for (let x = 0; x < targetWidth; x++) {
|
|
100
|
+
const idx = y * targetWidth + x;
|
|
101
|
+
const oldVal = buf[idx];
|
|
102
|
+
// Quantize to nearest ASCII level
|
|
103
|
+
const charIndex = Math.max(0, Math.min(NUM_LEVELS - 1, Math.round(oldVal / LEVEL_STEP)));
|
|
104
|
+
const newVal = charIndex * LEVEL_STEP;
|
|
105
|
+
const error = oldVal - newVal;
|
|
106
|
+
// Distribute error to unvisited neighbors
|
|
107
|
+
if (x + 1 < targetWidth)
|
|
108
|
+
buf[idx + 1] += error * 7 / 16;
|
|
109
|
+
if (y + 1 < targetHeight) {
|
|
110
|
+
if (x - 1 >= 0)
|
|
111
|
+
buf[idx + targetWidth - 1] += error * 3 / 16;
|
|
112
|
+
buf[idx + targetWidth] += error * 5 / 16;
|
|
113
|
+
if (x + 1 < targetWidth)
|
|
114
|
+
buf[idx + targetWidth + 1] += error * 1 / 16;
|
|
115
|
+
}
|
|
116
|
+
rowBuf[x] = ASCII_CHARS[charIndex];
|
|
117
|
+
}
|
|
118
|
+
art.push(rowBuf.join(''));
|
|
119
|
+
}
|
|
120
|
+
return { art, colors: [] };
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { FaceState } from './types.js';
|
|
2
|
+
export declare function getSocketPath(): string;
|
|
3
|
+
export declare function writeDiscovery(socketPath: string): void;
|
|
4
|
+
export declare function readDiscovery(): string | null;
|
|
5
|
+
export declare function clearDiscovery(socketPath: string): void;
|
|
6
|
+
export declare class StateServer {
|
|
7
|
+
private server;
|
|
8
|
+
private clients;
|
|
9
|
+
private socketPath;
|
|
10
|
+
constructor(socketPath: string);
|
|
11
|
+
broadcast(state: FaceState): void;
|
|
12
|
+
broadcastShutdown(): void;
|
|
13
|
+
private sendToAll;
|
|
14
|
+
close(): void;
|
|
15
|
+
}
|
|
16
|
+
export declare class StateClient {
|
|
17
|
+
private socket;
|
|
18
|
+
private resolveSocketPath;
|
|
19
|
+
private onState;
|
|
20
|
+
private onDisconnect?;
|
|
21
|
+
private onConnect?;
|
|
22
|
+
private onShutdown?;
|
|
23
|
+
private buffer;
|
|
24
|
+
private retryTimer;
|
|
25
|
+
private closed;
|
|
26
|
+
constructor(socketPath: string | (() => string | null), onState: (state: FaceState) => void, opts?: {
|
|
27
|
+
onDisconnect?: () => void;
|
|
28
|
+
onConnect?: () => void;
|
|
29
|
+
onShutdown?: () => void;
|
|
30
|
+
});
|
|
31
|
+
private connect;
|
|
32
|
+
private scheduleRetry;
|
|
33
|
+
close(): void;
|
|
34
|
+
}
|
package/dist/src/ipc.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
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}/cc-face-${process.pid}.sock`;
|
|
9
|
+
}
|
|
10
|
+
// ── Discovery file ──────────────────────────────────────────────────
|
|
11
|
+
// A well-known file that the CC wrapper writes its socket path to,
|
|
12
|
+
// so the face terminal can find it without needing --sock.
|
|
13
|
+
const DISCOVERY_PATH = `${os.tmpdir()}/cc-face.active`;
|
|
14
|
+
export function writeDiscovery(socketPath) {
|
|
15
|
+
fs.writeFileSync(DISCOVERY_PATH, socketPath);
|
|
16
|
+
}
|
|
17
|
+
export function readDiscovery() {
|
|
18
|
+
try {
|
|
19
|
+
const socketPath = fs.readFileSync(DISCOVERY_PATH, 'utf-8').trim();
|
|
20
|
+
if (!socketPath)
|
|
21
|
+
return null;
|
|
22
|
+
// Extract PID from socket path (cc-face-<pid>.sock) and check if alive
|
|
23
|
+
const match = socketPath.match(/cc-face-(\d+)\.sock$/);
|
|
24
|
+
if (match) {
|
|
25
|
+
const pid = Number(match[1]);
|
|
26
|
+
try {
|
|
27
|
+
process.kill(pid, 0);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// PID is dead — clean up stale files
|
|
31
|
+
try {
|
|
32
|
+
fs.unlinkSync(DISCOVERY_PATH);
|
|
33
|
+
}
|
|
34
|
+
catch { /* ignore */ }
|
|
35
|
+
try {
|
|
36
|
+
fs.unlinkSync(socketPath);
|
|
37
|
+
}
|
|
38
|
+
catch { /* ignore */ }
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return socketPath;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function clearDiscovery(socketPath) {
|
|
49
|
+
try {
|
|
50
|
+
const current = fs.readFileSync(DISCOVERY_PATH, 'utf-8').trim();
|
|
51
|
+
if (current === socketPath) {
|
|
52
|
+
fs.unlinkSync(DISCOVERY_PATH);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// ignore — file may not exist or already cleared
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// ── Server (wrapper process — terminal 1) ───────────────────────────
|
|
60
|
+
export class StateServer {
|
|
61
|
+
server;
|
|
62
|
+
clients = new Set();
|
|
63
|
+
socketPath;
|
|
64
|
+
constructor(socketPath) {
|
|
65
|
+
this.socketPath = socketPath;
|
|
66
|
+
// Remove stale socket file if it exists
|
|
67
|
+
try {
|
|
68
|
+
fs.unlinkSync(socketPath);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// doesn't exist — fine
|
|
72
|
+
}
|
|
73
|
+
this.server = net.createServer((socket) => {
|
|
74
|
+
this.clients.add(socket);
|
|
75
|
+
socket.on('close', () => this.clients.delete(socket));
|
|
76
|
+
socket.on('error', () => this.clients.delete(socket));
|
|
77
|
+
});
|
|
78
|
+
this.server.listen(socketPath);
|
|
79
|
+
}
|
|
80
|
+
broadcast(state) {
|
|
81
|
+
this.sendToAll({ state, ts: Date.now() });
|
|
82
|
+
}
|
|
83
|
+
broadcastShutdown() {
|
|
84
|
+
this.sendToAll({ state: 'idle', ts: Date.now(), shutdown: true });
|
|
85
|
+
}
|
|
86
|
+
sendToAll(msg) {
|
|
87
|
+
const line = JSON.stringify(msg) + '\n';
|
|
88
|
+
for (const socket of this.clients) {
|
|
89
|
+
try {
|
|
90
|
+
socket.write(line);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
this.clients.delete(socket);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
close() {
|
|
98
|
+
for (const socket of this.clients) {
|
|
99
|
+
try {
|
|
100
|
+
socket.destroy();
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// ignore
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
this.clients.clear();
|
|
107
|
+
this.server.close();
|
|
108
|
+
try {
|
|
109
|
+
fs.unlinkSync(this.socketPath);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// ignore
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// ── Client (face renderer — terminal 2) ─────────────────────────────
|
|
117
|
+
export class StateClient {
|
|
118
|
+
socket = null;
|
|
119
|
+
resolveSocketPath;
|
|
120
|
+
onState;
|
|
121
|
+
onDisconnect;
|
|
122
|
+
onConnect;
|
|
123
|
+
onShutdown;
|
|
124
|
+
buffer = '';
|
|
125
|
+
retryTimer = null;
|
|
126
|
+
closed = false;
|
|
127
|
+
constructor(socketPath, onState, opts) {
|
|
128
|
+
this.resolveSocketPath = typeof socketPath === 'string'
|
|
129
|
+
? () => socketPath
|
|
130
|
+
: socketPath;
|
|
131
|
+
this.onState = onState;
|
|
132
|
+
this.onDisconnect = opts?.onDisconnect;
|
|
133
|
+
this.onConnect = opts?.onConnect;
|
|
134
|
+
this.onShutdown = opts?.onShutdown;
|
|
135
|
+
this.connect();
|
|
136
|
+
}
|
|
137
|
+
connect() {
|
|
138
|
+
if (this.closed)
|
|
139
|
+
return;
|
|
140
|
+
const resolved = this.resolveSocketPath();
|
|
141
|
+
if (!resolved) {
|
|
142
|
+
this.scheduleRetry();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.socket = net.createConnection(resolved);
|
|
146
|
+
this.socket.on('connect', () => {
|
|
147
|
+
this.buffer = '';
|
|
148
|
+
this.onConnect?.();
|
|
149
|
+
});
|
|
150
|
+
this.socket.on('data', (data) => {
|
|
151
|
+
this.buffer += data.toString();
|
|
152
|
+
const lines = this.buffer.split('\n');
|
|
153
|
+
// Keep the incomplete last chunk in the buffer
|
|
154
|
+
this.buffer = lines.pop();
|
|
155
|
+
for (const line of lines) {
|
|
156
|
+
if (!line)
|
|
157
|
+
continue;
|
|
158
|
+
try {
|
|
159
|
+
const msg = JSON.parse(line);
|
|
160
|
+
if (msg.shutdown) {
|
|
161
|
+
this.onShutdown?.();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.onState(msg.state);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// malformed JSON — skip
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
this.socket.on('close', () => {
|
|
172
|
+
if (this.closed)
|
|
173
|
+
return;
|
|
174
|
+
this.onDisconnect?.();
|
|
175
|
+
this.scheduleRetry();
|
|
176
|
+
});
|
|
177
|
+
this.socket.on('error', () => {
|
|
178
|
+
if (this.closed)
|
|
179
|
+
return;
|
|
180
|
+
// Will trigger 'close' which handles reconnection
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
scheduleRetry() {
|
|
184
|
+
if (this.closed)
|
|
185
|
+
return;
|
|
186
|
+
this.retryTimer = setTimeout(() => {
|
|
187
|
+
this.retryTimer = null;
|
|
188
|
+
this.connect();
|
|
189
|
+
}, config.ipcRetryMs);
|
|
190
|
+
}
|
|
191
|
+
close() {
|
|
192
|
+
this.closed = true;
|
|
193
|
+
if (this.retryTimer !== null) {
|
|
194
|
+
clearTimeout(this.retryTimer);
|
|
195
|
+
this.retryTimer = null;
|
|
196
|
+
}
|
|
197
|
+
if (this.socket) {
|
|
198
|
+
this.socket.destroy();
|
|
199
|
+
this.socket = null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# 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,12 @@
|
|
|
1
|
+
import { FaceDefinition } from './types.js';
|
|
2
|
+
import { hasDecodedFrames } from './generator.js';
|
|
3
|
+
export { hasDecodedFrames };
|
|
4
|
+
/**
|
|
5
|
+
* Decode all JPEGs at startup. Call once before buildFace().
|
|
6
|
+
*/
|
|
7
|
+
export declare function initGenerator(): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Build a FaceDefinition at the given terminal dimensions.
|
|
10
|
+
* Generates ASCII art dynamically from decoded JPEG pixel data.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildFace(terminalWidth: number, terminalRows?: number): FaceDefinition;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { dirname, join } from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { decodeAllFrames, generateAsciiFrames, hasDecodedFrames } from './generator.js';
|
|
5
|
+
export { hasDecodedFrames };
|
|
6
|
+
// ── Locate faces/ directory ──────────────────────────────────────────
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
function findProjectRoot() {
|
|
10
|
+
let dir = __dirname;
|
|
11
|
+
while (dir !== dirname(dir)) {
|
|
12
|
+
if (existsSync(join(dir, 'package.json')))
|
|
13
|
+
return dir;
|
|
14
|
+
dir = dirname(dir);
|
|
15
|
+
}
|
|
16
|
+
throw new Error('Could not find project root (no package.json found)');
|
|
17
|
+
}
|
|
18
|
+
const FACES_DIR = join(findProjectRoot(), 'faces');
|
|
19
|
+
// ── Expression names ─────────────────────────────────────────────────
|
|
20
|
+
const ALL_EXPRESSIONS = ['idle', 'typing', 'thinking', 'listening'];
|
|
21
|
+
// ── Generator init ──────────────────────────────────────────────────
|
|
22
|
+
/**
|
|
23
|
+
* Decode all JPEGs at startup. Call once before buildFace().
|
|
24
|
+
*/
|
|
25
|
+
export async function initGenerator() {
|
|
26
|
+
await decodeAllFrames(FACES_DIR, ALL_EXPRESSIONS);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Terminal chars are ~2× taller than wide. Source images are square (960×960).
|
|
30
|
+
* ASCII height = width * ASPECT_RATIO_FACTOR (0.55). To make the face fill
|
|
31
|
+
* available height: maxWidth = availableRows / ASPECT_RATIO_FACTOR.
|
|
32
|
+
*/
|
|
33
|
+
const ASPECT_RATIO_FACTOR = 0.55;
|
|
34
|
+
/**
|
|
35
|
+
* Build a FaceDefinition at the given terminal dimensions.
|
|
36
|
+
* Generates ASCII art dynamically from decoded JPEG pixel data.
|
|
37
|
+
*/
|
|
38
|
+
export function buildFace(terminalWidth, terminalRows) {
|
|
39
|
+
// Reserve 2 rows for status line + padding
|
|
40
|
+
const availableRows = terminalRows ? Math.max(1, terminalRows - 2) : undefined;
|
|
41
|
+
// Derive width from height so the face appears square, then clamp to terminal width
|
|
42
|
+
let effectiveWidth;
|
|
43
|
+
if (availableRows !== undefined) {
|
|
44
|
+
const widthFromHeight = Math.floor(availableRows / ASPECT_RATIO_FACTOR);
|
|
45
|
+
effectiveWidth = Math.min(terminalWidth, widthFromHeight);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
effectiveWidth = terminalWidth;
|
|
49
|
+
}
|
|
50
|
+
const expressions = {};
|
|
51
|
+
for (const name of ALL_EXPRESSIONS) {
|
|
52
|
+
const expr = generateAsciiFrames(name, effectiveWidth, availableRows);
|
|
53
|
+
if (!expr) {
|
|
54
|
+
throw new Error(`Failed to generate frames for expression "${name}"`);
|
|
55
|
+
}
|
|
56
|
+
expressions[name] = expr;
|
|
57
|
+
}
|
|
58
|
+
const idleFrame = expressions['idle'].frames[0];
|
|
59
|
+
const height = idleFrame.art.length;
|
|
60
|
+
const width = idleFrame.art[0].length;
|
|
61
|
+
return { name: 'default', width, height, expressions };
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,wEAAwE;AAExE,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,SAAS,eAAe;IACtB,IAAI,GAAG,GAAG,SAAS,CAAC;IACpB,OAAO,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QACtD,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,EAAE,EAAE,OAAO,CAAC,CAAC;AAEnD,wEAAwE;AAExE,MAAM,eAAe,GAAgB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;AAEjF,uEAAuE;AAEvE;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,eAAe,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;AACpD,CAAC;AAED;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,aAAqB,EAAE,YAAqB;IACpE,2CAA2C;IAC3C,MAAM,aAAa,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE/E,oFAAoF;IACpF,IAAI,cAAsB,CAAC;IAC3B,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,GAAG,mBAAmB,CAAC,CAAC;QACxE,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;IAC5D,CAAC;SAAM,CAAC;QACN,cAAc,GAAG,aAAa,CAAC;IACjC,CAAC;IAED,MAAM,WAAW,GAA+B,EAAE,CAAC;IAEnD,KAAK,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,EAAE,cAAc,EAAE,aAAa,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,6CAA6C,IAAI,GAAG,CAAC,CAAC;QACxE,CAAC;QACD,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3B,CAAC;IAED,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;IACpC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAEtC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AACzD,CAAC"}
|