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.
Files changed (160) hide show
  1. package/README.md +102 -0
  2. package/dist/bin/cli.d.ts +2 -0
  3. package/dist/bin/cli.js +142 -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 +59 -0
  9. package/dist/src/face.js +493 -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 +6 -0
  13. package/dist/src/frames.js.map +1 -0
  14. package/dist/src/generator.d.ts +16 -0
  15. package/dist/src/generator.js +122 -0
  16. package/dist/src/generator.js.map +1 -0
  17. package/dist/src/ipc.d.ts +34 -0
  18. package/dist/src/ipc.js +203 -0
  19. package/dist/src/ipc.js.map +1 -0
  20. package/dist/src/loader.d.ts +12 -0
  21. package/dist/src/loader.js +63 -0
  22. package/dist/src/loader.js.map +1 -0
  23. package/dist/src/main.d.ts +2 -0
  24. package/dist/src/main.js +210 -0
  25. package/dist/src/main.js.map +1 -0
  26. package/dist/src/patterns.d.ts +6 -0
  27. package/dist/src/patterns.js +14 -0
  28. package/dist/src/patterns.js.map +1 -0
  29. package/dist/src/pty.d.ts +17 -0
  30. package/dist/src/pty.js +84 -0
  31. package/dist/src/pty.js.map +1 -0
  32. package/dist/src/scroll.d.ts +36 -0
  33. package/dist/src/scroll.js +56 -0
  34. package/dist/src/scroll.js.map +1 -0
  35. package/dist/src/state.d.ts +36 -0
  36. package/dist/src/state.js +164 -0
  37. package/dist/src/state.js.map +1 -0
  38. package/dist/src/theme.d.ts +15 -0
  39. package/dist/src/theme.js +51 -0
  40. package/dist/src/theme.js.map +1 -0
  41. package/dist/src/types.d.ts +60 -0
  42. package/dist/src/types.js +3 -0
  43. package/dist/src/types.js.map +1 -0
  44. package/dist/src/utils.d.ts +8 -0
  45. package/dist/src/utils.js +13 -0
  46. package/dist/src/utils.js.map +1 -0
  47. package/faces/idle/config.json +7 -0
  48. package/faces/idle/img/1.jpeg +0 -0
  49. package/faces/idle/img/10.jpeg +0 -0
  50. package/faces/idle/img/11.jpeg +0 -0
  51. package/faces/idle/img/12.jpeg +0 -0
  52. package/faces/idle/img/13.jpeg +0 -0
  53. package/faces/idle/img/14.jpeg +0 -0
  54. package/faces/idle/img/15.jpeg +0 -0
  55. package/faces/idle/img/16.jpeg +0 -0
  56. package/faces/idle/img/17.jpeg +0 -0
  57. package/faces/idle/img/18.jpeg +0 -0
  58. package/faces/idle/img/19.jpeg +0 -0
  59. package/faces/idle/img/2.jpeg +0 -0
  60. package/faces/idle/img/20.jpeg +0 -0
  61. package/faces/idle/img/21.jpeg +0 -0
  62. package/faces/idle/img/22.jpeg +0 -0
  63. package/faces/idle/img/23.jpeg +0 -0
  64. package/faces/idle/img/24.jpeg +0 -0
  65. package/faces/idle/img/25.jpeg +0 -0
  66. package/faces/idle/img/26.jpeg +0 -0
  67. package/faces/idle/img/27.jpeg +0 -0
  68. package/faces/idle/img/28.jpeg +0 -0
  69. package/faces/idle/img/29.jpeg +0 -0
  70. package/faces/idle/img/3.jpeg +0 -0
  71. package/faces/idle/img/30.jpeg +0 -0
  72. package/faces/idle/img/4.jpeg +0 -0
  73. package/faces/idle/img/5.jpeg +0 -0
  74. package/faces/idle/img/6.jpeg +0 -0
  75. package/faces/idle/img/7.jpeg +0 -0
  76. package/faces/idle/img/8.jpeg +0 -0
  77. package/faces/idle/img/9.jpeg +0 -0
  78. package/faces/listening/config.json +5 -0
  79. package/faces/listening/img/1.jpeg +0 -0
  80. package/faces/listening/img/10.jpeg +0 -0
  81. package/faces/listening/img/11.jpeg +0 -0
  82. package/faces/listening/img/12.jpeg +0 -0
  83. package/faces/listening/img/13.jpeg +0 -0
  84. package/faces/listening/img/14.jpeg +0 -0
  85. package/faces/listening/img/15.jpeg +0 -0
  86. package/faces/listening/img/16.jpeg +0 -0
  87. package/faces/listening/img/17.jpeg +0 -0
  88. package/faces/listening/img/18.jpeg +0 -0
  89. package/faces/listening/img/19.jpeg +0 -0
  90. package/faces/listening/img/2.jpeg +0 -0
  91. package/faces/listening/img/20.jpeg +0 -0
  92. package/faces/listening/img/21.jpeg +0 -0
  93. package/faces/listening/img/22.jpeg +0 -0
  94. package/faces/listening/img/23.jpeg +0 -0
  95. package/faces/listening/img/24.jpeg +0 -0
  96. package/faces/listening/img/25.jpeg +0 -0
  97. package/faces/listening/img/26.jpeg +0 -0
  98. package/faces/listening/img/27.jpeg +0 -0
  99. package/faces/listening/img/3.jpeg +0 -0
  100. package/faces/listening/img/4.jpeg +0 -0
  101. package/faces/listening/img/5.jpeg +0 -0
  102. package/faces/listening/img/6.jpeg +0 -0
  103. package/faces/listening/img/7.jpeg +0 -0
  104. package/faces/listening/img/8.jpeg +0 -0
  105. package/faces/listening/img/9.jpeg +0 -0
  106. package/faces/thinking/config.json +5 -0
  107. package/faces/thinking/img/1.jpeg +0 -0
  108. package/faces/thinking/img/10.jpeg +0 -0
  109. package/faces/thinking/img/11.jpeg +0 -0
  110. package/faces/thinking/img/12.jpeg +0 -0
  111. package/faces/thinking/img/13.jpeg +0 -0
  112. package/faces/thinking/img/14.jpeg +0 -0
  113. package/faces/thinking/img/15.jpeg +0 -0
  114. package/faces/thinking/img/16.jpeg +0 -0
  115. package/faces/thinking/img/17.jpeg +0 -0
  116. package/faces/thinking/img/18.jpeg +0 -0
  117. package/faces/thinking/img/19.jpeg +0 -0
  118. package/faces/thinking/img/2.jpeg +0 -0
  119. package/faces/thinking/img/20.jpeg +0 -0
  120. package/faces/thinking/img/3.jpeg +0 -0
  121. package/faces/thinking/img/4.jpeg +0 -0
  122. package/faces/thinking/img/5.jpeg +0 -0
  123. package/faces/thinking/img/6.jpeg +0 -0
  124. package/faces/thinking/img/7.jpeg +0 -0
  125. package/faces/thinking/img/8.jpeg +0 -0
  126. package/faces/thinking/img/9.jpeg +0 -0
  127. package/faces/typing/config.json +5 -0
  128. package/faces/typing/img/1.jpeg +0 -0
  129. package/faces/typing/img/10.jpeg +0 -0
  130. package/faces/typing/img/11.jpeg +0 -0
  131. package/faces/typing/img/12.jpeg +0 -0
  132. package/faces/typing/img/13.jpeg +0 -0
  133. package/faces/typing/img/14.jpeg +0 -0
  134. package/faces/typing/img/15.jpeg +0 -0
  135. package/faces/typing/img/16.jpeg +0 -0
  136. package/faces/typing/img/17.jpeg +0 -0
  137. package/faces/typing/img/18.jpeg +0 -0
  138. package/faces/typing/img/19.jpeg +0 -0
  139. package/faces/typing/img/2.jpeg +0 -0
  140. package/faces/typing/img/20.jpeg +0 -0
  141. package/faces/typing/img/21.jpeg +0 -0
  142. package/faces/typing/img/22.jpeg +0 -0
  143. package/faces/typing/img/23.jpeg +0 -0
  144. package/faces/typing/img/24.jpeg +0 -0
  145. package/faces/typing/img/25.jpeg +0 -0
  146. package/faces/typing/img/26.jpeg +0 -0
  147. package/faces/typing/img/27.jpeg +0 -0
  148. package/faces/typing/img/28.jpeg +0 -0
  149. package/faces/typing/img/29.jpeg +0 -0
  150. package/faces/typing/img/3.jpeg +0 -0
  151. package/faces/typing/img/30.jpeg +0 -0
  152. package/faces/typing/img/31.jpeg +0 -0
  153. package/faces/typing/img/32.jpeg +0 -0
  154. package/faces/typing/img/4.jpeg +0 -0
  155. package/faces/typing/img/5.jpeg +0 -0
  156. package/faces/typing/img/6.jpeg +0 -0
  157. package/faces/typing/img/7.jpeg +0 -0
  158. package/faces/typing/img/8.jpeg +0 -0
  159. package/faces/typing/img/9.jpeg +0 -0
  160. 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":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,0DAA0D;AAC1D,MAAM,WAAW,GAAG,YAAY,CAAC;AACjC,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAejC,IAAI,kBAAkB,GAA0C,IAAI,CAAC;AAErE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB,EAAE,eAAyB;IAC/E,kBAAkB,GAAG,IAAI,GAAG,EAAE,CAAC;IAE/B,KAAK,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QAEvD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,SAAS;QAElC,MAAM,MAAM,GAAoB,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;QAE9E,8BAA8B;QAC9B,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC;aAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;aAChC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACb,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YACvB,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YACvB,OAAO,EAAE,GAAG,EAAE,CAAC;QACjB,CAAC,CAAC,CAAC;QAEL,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAEjC,MAAM,OAAO,GAAmB,EAAE,CAAC;QACnC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACnC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC;iBACxC,SAAS,EAAE;iBACX,MAAM,EAAE,CAAE,oDAAoD;iBAC9D,GAAG,EAAE;iBACL,QAAQ,CAAC,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;YAEzC,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM,EAAE,IAAI;gBACZ,QAAQ,EAAE,IAAI,CAAC,KAAK;gBACpB,SAAS,EAAE,IAAI,CAAC,MAAM;aACvB,CAAC,CAAC;QACL,CAAC;QAED,kBAAkB,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,cAAsB,EAAE,WAAmB,EAAE,OAAgB;IAC/F,IAAI,CAAC,kBAAkB;QAAE,OAAO,IAAI,CAAC;IACrC,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACvD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,MAAM,GAAY,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;QAC9C,OAAO,cAAc,CAAC,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,MAAM;QACN,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,kBAAkB,KAAK,IAAI,IAAI,kBAAkB,CAAC,IAAI,GAAG,CAAC,CAAC;AACpE,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,CAAE,KAAK;AAC7C,MAAM,UAAU,GAAG,GAAG,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAE,SAAS;AAErD,sEAAsE;AAEtE,SAAS,cAAc,CAAC,EAAgB,EAAE,WAAmB,EAAE,OAAgB;IAC7E,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;IAC3C,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;IACzC,IAAI,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,WAAW,GAAG,mBAAmB,CAAC,CAAC,CAAC;IAE5F,kFAAkF;IAClF,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QACzC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IAED,qEAAqE;IACrE,mEAAmE;IACnE,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC,WAAW,GAAG,YAAY,CAAC,CAAC;IACzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,QAAQ,GAAG,WAAW,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC;YAC5E,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,SAAS,GAAG,YAAY,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC;YAC/E,GAAG,CAAC,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,GAAG,QAAQ,GAAG,IAAI,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,wEAAwE;IACxE,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,MAAM,GAAG,IAAI,KAAK,CAAS,WAAW,CAAC,CAAC;IAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,GAAG,GAAG,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC;YAChC,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;YACxB,kCAAkC;YAClC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YACzF,MAAM,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;YACtC,MAAM,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;YAE9B,0CAA0C;YAC1C,IAAI,CAAC,GAAG,CAAC,GAAG,WAAW;gBACrB,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,EAAE,CAAC;YACjC,IAAI,CAAC,GAAG,CAAC,GAAG,YAAY,EAAE,CAAC;gBACzB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;oBACZ,GAAG,CAAC,GAAG,GAAG,WAAW,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,EAAE,CAAC;gBAC/C,GAAG,CAAC,GAAG,GAAG,WAAW,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,EAAE,CAAC;gBACzC,IAAI,CAAC,GAAG,CAAC,GAAG,WAAW;oBACrB,GAAG,CAAC,GAAG,GAAG,WAAW,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,EAAE,CAAC;YACjD,CAAC;YAED,MAAM,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;QACrC,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AAC7B,CAAC"}
@@ -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
+ }
@@ -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":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,uEAAuE;AAEvE,MAAM,UAAU,aAAa;IAC3B,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC;IAC3B,OAAO,GAAG,MAAM,YAAY,OAAO,CAAC,GAAG,OAAO,CAAC;AACjD,CAAC;AAED,uEAAuE;AACvE,mEAAmE;AACnE,2DAA2D;AAE3D,MAAM,cAAc,GAAG,GAAG,EAAE,CAAC,MAAM,EAAE,iBAAiB,CAAC;AAEvD,MAAM,UAAU,cAAc,CAAC,UAAkB;IAC/C,EAAE,CAAC,aAAa,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACnE,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAE7B,uEAAuE;QACvE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACvD,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7B,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACvB,CAAC;YAAC,MAAM,CAAC;gBACP,qCAAqC;gBACrC,IAAI,CAAC;oBAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;gBAC7D,IAAI,CAAC;oBAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;gBACzD,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAED,OAAO,UAAU,CAAC;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,UAAkB;IAC/C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAChE,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;YAC3B,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iDAAiD;IACnD,CAAC;AACH,CAAC;AAED,uEAAuE;AAEvE,MAAM,OAAO,WAAW;IACd,MAAM,CAAa;IACnB,OAAO,GAAG,IAAI,GAAG,EAAc,CAAC;IAChC,UAAU,CAAS;IAE3B,YAAY,UAAkB;QAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAE7B,wCAAwC;QACxC,IAAI,CAAC;YACH,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;QAED,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,MAAM,EAAE,EAAE;YACxC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACzB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YACtD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACjC,CAAC;IAED,SAAS,CAAC,KAAgB;QACxB,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;IAEO,SAAS,CAAC,GAAoB;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;QACxC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK;QACH,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC;CACF;AAED,uEAAuE;AAEvE,MAAM,OAAO,WAAW;IACd,MAAM,GAAsB,IAAI,CAAC;IACjC,iBAAiB,CAAsB;IACvC,OAAO,CAA6B;IACpC,YAAY,CAAc;IAC1B,SAAS,CAAc;IACvB,UAAU,CAAc;IACxB,MAAM,GAAG,EAAE,CAAC;IACZ,UAAU,GAAyC,IAAI,CAAC;IACxD,MAAM,GAAG,KAAK,CAAC;IAEvB,YACE,UAA0C,EAC1C,OAAmC,EACnC,IAAqF;QAErF,IAAI,CAAC,iBAAiB,GAAG,OAAO,UAAU,KAAK,QAAQ;YACrD,CAAC,CAAC,GAAG,EAAE,CAAC,UAAU;YAClB,CAAC,CAAC,UAAU,CAAC;QACf,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,YAAY,GAAG,IAAI,EAAE,YAAY,CAAC;QACvC,IAAI,CAAC,SAAS,GAAG,IAAI,EAAE,SAAS,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,IAAI,EAAE,UAAU,CAAC;QACnC,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAEO,OAAO;QACb,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QAExB,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC1C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAE7C,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;YAC7B,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;YACjB,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;QACrB,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAC9B,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACtC,+CAA+C;YAC/C,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;YAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,IAAI;oBAAE,SAAS;gBACpB,IAAI,CAAC;oBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;oBAChD,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;wBACjB,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;wBACpB,OAAO;oBACT,CAAC;oBACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAC1B,CAAC;gBAAC,MAAM,CAAC;oBACP,wBAAwB;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAC3B,IAAI,IAAI,CAAC,MAAM;gBAAE,OAAO;YACxB,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACtB,IAAI,CAAC,aAAa,EAAE,CAAC;QACvB,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAC3B,IAAI,IAAI,CAAC,MAAM;gBAAE,OAAO;YACxB,kDAAkD;QACpD,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,aAAa;QACnB,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IACxB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAC7B,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC9B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzB,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;IACH,CAAC;CACF"}
@@ -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"}
@@ -0,0 +1,2 @@
1
+ import { CliOptions } from './types.js';
2
+ export declare function run(options: CliOptions): Promise<void>;