botmux 2.2.7 → 2.3.1

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 (121) hide show
  1. package/README.en.md +63 -13
  2. package/README.md +52 -14
  3. package/dist/adapters/backend/tmux-backend.d.ts +8 -0
  4. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  5. package/dist/adapters/backend/tmux-backend.js +18 -0
  6. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  7. package/dist/adapters/cli/aiden.d.ts.map +1 -1
  8. package/dist/adapters/cli/aiden.js +0 -40
  9. package/dist/adapters/cli/aiden.js.map +1 -1
  10. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  11. package/dist/adapters/cli/claude-code.js +21 -67
  12. package/dist/adapters/cli/claude-code.js.map +1 -1
  13. package/dist/adapters/cli/coco.d.ts.map +1 -1
  14. package/dist/adapters/cli/coco.js +0 -33
  15. package/dist/adapters/cli/coco.js.map +1 -1
  16. package/dist/adapters/cli/codex.d.ts.map +1 -1
  17. package/dist/adapters/cli/codex.js +0 -27
  18. package/dist/adapters/cli/codex.js.map +1 -1
  19. package/dist/adapters/cli/gemini.d.ts.map +1 -1
  20. package/dist/adapters/cli/gemini.js +1 -29
  21. package/dist/adapters/cli/gemini.js.map +1 -1
  22. package/dist/adapters/cli/opencode.d.ts.map +1 -1
  23. package/dist/adapters/cli/opencode.js +1 -44
  24. package/dist/adapters/cli/opencode.js.map +1 -1
  25. package/dist/adapters/cli/types.d.ts +11 -8
  26. package/dist/adapters/cli/types.d.ts.map +1 -1
  27. package/dist/cli.js +737 -16
  28. package/dist/cli.js.map +1 -1
  29. package/dist/config.d.ts +16 -0
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +30 -0
  32. package/dist/config.js.map +1 -1
  33. package/dist/core/command-handler.d.ts.map +1 -1
  34. package/dist/core/command-handler.js +8 -4
  35. package/dist/core/command-handler.js.map +1 -1
  36. package/dist/core/scheduler.d.ts +38 -16
  37. package/dist/core/scheduler.d.ts.map +1 -1
  38. package/dist/core/scheduler.js +335 -149
  39. package/dist/core/scheduler.js.map +1 -1
  40. package/dist/core/session-manager.d.ts +6 -0
  41. package/dist/core/session-manager.d.ts.map +1 -1
  42. package/dist/core/session-manager.js +105 -16
  43. package/dist/core/session-manager.js.map +1 -1
  44. package/dist/core/types.d.ts +26 -4
  45. package/dist/core/types.d.ts.map +1 -1
  46. package/dist/core/types.js +6 -0
  47. package/dist/core/types.js.map +1 -1
  48. package/dist/core/worker-pool.d.ts +15 -3
  49. package/dist/core/worker-pool.d.ts.map +1 -1
  50. package/dist/core/worker-pool.js +233 -31
  51. package/dist/core/worker-pool.js.map +1 -1
  52. package/dist/daemon.d.ts.map +1 -1
  53. package/dist/daemon.js +49 -10
  54. package/dist/daemon.js.map +1 -1
  55. package/dist/im/lark/card-builder.d.ts +29 -1
  56. package/dist/im/lark/card-builder.d.ts.map +1 -1
  57. package/dist/im/lark/card-builder.js +241 -55
  58. package/dist/im/lark/card-builder.js.map +1 -1
  59. package/dist/im/lark/card-handler.d.ts +1 -0
  60. package/dist/im/lark/card-handler.d.ts.map +1 -1
  61. package/dist/im/lark/card-handler.js +195 -40
  62. package/dist/im/lark/card-handler.js.map +1 -1
  63. package/dist/services/schedule-store.d.ts +20 -3
  64. package/dist/services/schedule-store.d.ts.map +1 -1
  65. package/dist/services/schedule-store.js +140 -16
  66. package/dist/services/schedule-store.js.map +1 -1
  67. package/dist/skills/definitions.d.ts +17 -0
  68. package/dist/skills/definitions.d.ts.map +1 -0
  69. package/dist/skills/definitions.js +254 -0
  70. package/dist/skills/definitions.js.map +1 -0
  71. package/dist/skills/installer.d.ts +9 -0
  72. package/dist/skills/installer.d.ts.map +1 -0
  73. package/dist/skills/installer.js +42 -0
  74. package/dist/skills/installer.js.map +1 -0
  75. package/dist/types.d.ts +84 -7
  76. package/dist/types.d.ts.map +1 -1
  77. package/dist/types.js +1 -5
  78. package/dist/types.js.map +1 -1
  79. package/dist/utils/lark-upload.d.ts +2 -0
  80. package/dist/utils/lark-upload.d.ts.map +1 -0
  81. package/dist/utils/lark-upload.js +27 -0
  82. package/dist/utils/lark-upload.js.map +1 -0
  83. package/dist/utils/screen-analyzer.d.ts +67 -0
  84. package/dist/utils/screen-analyzer.d.ts.map +1 -0
  85. package/dist/utils/screen-analyzer.js +279 -0
  86. package/dist/utils/screen-analyzer.js.map +1 -0
  87. package/dist/utils/screenshot-renderer.d.ts +11 -0
  88. package/dist/utils/screenshot-renderer.d.ts.map +1 -0
  89. package/dist/utils/screenshot-renderer.js +225 -0
  90. package/dist/utils/screenshot-renderer.js.map +1 -0
  91. package/dist/utils/terminal-renderer.d.ts +30 -0
  92. package/dist/utils/terminal-renderer.d.ts.map +1 -1
  93. package/dist/utils/terminal-renderer.js +25 -0
  94. package/dist/utils/terminal-renderer.js.map +1 -1
  95. package/dist/worker.js +372 -14
  96. package/dist/worker.js.map +1 -1
  97. package/package.json +2 -5
  98. package/dist/index.d.ts +0 -3
  99. package/dist/index.d.ts.map +0 -1
  100. package/dist/index.js +0 -16
  101. package/dist/index.js.map +0 -1
  102. package/dist/server.d.ts +0 -3
  103. package/dist/server.d.ts.map +0 -1
  104. package/dist/server.js +0 -133
  105. package/dist/server.js.map +0 -1
  106. package/dist/tools/get-thread-messages.d.ts +0 -26
  107. package/dist/tools/get-thread-messages.d.ts.map +0 -1
  108. package/dist/tools/get-thread-messages.js +0 -38
  109. package/dist/tools/get-thread-messages.js.map +0 -1
  110. package/dist/tools/index.d.ts +0 -9
  111. package/dist/tools/index.d.ts.map +0 -1
  112. package/dist/tools/index.js +0 -10
  113. package/dist/tools/index.js.map +0 -1
  114. package/dist/tools/list-bots.d.ts +0 -40
  115. package/dist/tools/list-bots.d.ts.map +0 -1
  116. package/dist/tools/list-bots.js +0 -77
  117. package/dist/tools/list-bots.js.map +0 -1
  118. package/dist/tools/send-to-thread.d.ts +0 -46
  119. package/dist/tools/send-to-thread.d.ts.map +0 -1
  120. package/dist/tools/send-to-thread.js +0 -275
  121. package/dist/tools/send-to-thread.js.map +0 -1
@@ -0,0 +1,279 @@
1
+ /**
2
+ * AI-powered screen analyzer — detects interactive TUI prompts in terminal output
3
+ * by sending snapshots to a lightweight LLM and parsing structured responses.
4
+ *
5
+ * Token-saving protocol:
6
+ * 1. Text diff — skip if snapshot unchanged
7
+ * 2. Cumulative stability — require N consecutive unchanged snapshots
8
+ * 3. AI-driven cooldown — AI returns checkAgainWhen to control re-call timing
9
+ * 4. Prompt-active guard — stop calling AI once a prompt is reported, until resolved
10
+ */
11
+ const SYSTEM_PROMPT = `You are a terminal screen analyzer. Analyze the terminal screenshot and determine if the CLI is showing a **blocking interactive prompt** that requires the user to make a selection before the CLI can proceed.
12
+
13
+ A BLOCKING interactive prompt looks like:
14
+ - A modal dialog with numbered options and a cursor (❯ or >) pointing to the selected option
15
+ - Examples: "Resume from summary / Resume full session / Don't ask me again", "Yes / No / Cancel"
16
+ - The CLI cannot proceed until the user selects an option
17
+
18
+ The following are NOT interactive prompts (return needsInteraction=false):
19
+ - Status bar text like "bypass permissions (shift+tab to cycle)" — this is a persistent status indicator, not a blocking prompt
20
+ - CLI idle state showing an input cursor (❯) waiting for the user to type a message — this is normal operation
21
+ - Progress indicators, spinners, or loading animations
22
+ - Error messages or informational output
23
+ - Any text that is part of the CLI's normal UI chrome (toolbars, status bars, mode indicators)
24
+
25
+ Return ONLY valid JSON (no markdown, no extra text):
26
+ {
27
+ "needsInteraction": boolean,
28
+ "description": "what is being asked",
29
+ "options": [{"label": "1", "text": "option text", "type": "select", "index": 0}],
30
+ "multiSelect": false,
31
+ "toggleKey": "Space",
32
+ "confirmKey": "Enter",
33
+ "checkAgainWhen": "content_changed" | "after_5s" | "after_10s" | "not_needed"
34
+ }
35
+
36
+ For each option:
37
+ - label: exact label shown in TUI. If none, use sequential numbers.
38
+ - text: full option text
39
+ - type: "select" (single pick), "toggle" (multi-select checkbox), "confirm" (submit/next/done), "input" (free text like "Type something")
40
+ - index: the 0-based position of this option in the list (0 = first navigable item, 1 = second, etc.). Count ALL navigable items from top to bottom in order, including submit/next buttons. DO NOT count non-interactive lines like descriptions or separators.
41
+
42
+ toggleKey: the key used to toggle a checkbox item. Read the hint line at the bottom (e.g. "Space to toggle", "Enter to select"). Default "Space".
43
+ confirmKey: the key used to confirm/submit. Usually "Enter".
44
+ multiSelect: true if checkboxes ([ ]/[✓]/[✗], "可多选"), false for single-select (❯ cursor).
45
+
46
+ IMPORTANT for multiSelect prompts:
47
+ - There is almost always a Submit/Next/Done button. Look for items like "Submit", "Next", "Submit answers", "Done" — they are navigable items with type "confirm".
48
+ - "Submit" may appear AFTER "Type something" as a sub-item or on the next line. It is still a separate navigable option — include it with its own index.
49
+ - If the cursor (❯) is pointing at "Submit" or "Next", that is a confirm-type option.
50
+ - Count its index correctly — it is a navigable position between "Type something" and "Chat about this".
51
+
52
+ Important: "index" is the COUNT of ↓ presses needed to reach this option from the FIRST option (index 0). The first option is always index 0.
53
+
54
+ Rules for checkAgainWhen:
55
+ - "content_changed": call again when content changes
56
+ - "after_5s" or "after_10s": check back after a delay
57
+ - "not_needed": CLI is working normally or idle — don't call until content changes substantially
58
+
59
+ If needsInteraction is false, omit description and options fields.`;
60
+ export class ScreenAnalyzer {
61
+ config;
62
+ callbacks;
63
+ timer = null;
64
+ // Stability tracking
65
+ lastSnapshot = '';
66
+ stableCount = 0;
67
+ // AI-driven cooldown
68
+ lastAnalyzedSnapshot = '';
69
+ waitingForContentChange = false;
70
+ timerCooldownUntil = 0;
71
+ // Prompt state
72
+ promptActive = false;
73
+ // Concurrency guard — prevent overlapping AI calls
74
+ _analyzing = false;
75
+ // Disposed flag
76
+ disposed = false;
77
+ constructor(config, callbacks) {
78
+ this.config = config;
79
+ this.callbacks = callbacks;
80
+ }
81
+ /** Whether an AI call is currently in flight */
82
+ get isAnalyzing() { return this._analyzing; }
83
+ start() {
84
+ if (this.timer)
85
+ return;
86
+ this.timer = setInterval(() => this.tick(), this.config.intervalMs);
87
+ this.callbacks.log('ScreenAnalyzer started');
88
+ }
89
+ dispose() {
90
+ this.disposed = true;
91
+ if (this.timer) {
92
+ clearInterval(this.timer);
93
+ this.timer = null;
94
+ }
95
+ }
96
+ /** Called externally when user has made a selection via card click */
97
+ notifySelection(selectedText) {
98
+ this.promptActive = false;
99
+ // Reset all cooldowns so we check again soon to confirm prompt is gone
100
+ this.waitingForContentChange = false;
101
+ this.timerCooldownUntil = 0;
102
+ this.stableCount = 0;
103
+ this.lastSnapshot = '';
104
+ this.callbacks.log(`ScreenAnalyzer: selection made — "${selectedText}"`);
105
+ }
106
+ async tick() {
107
+ if (this.disposed)
108
+ return;
109
+ // Don't call AI while a prompt is active — we already notified Daemon.
110
+ if (this.promptActive)
111
+ return;
112
+ // Don't overlap AI calls
113
+ if (this._analyzing)
114
+ return;
115
+ // Layer 1: Text diff — get current snapshot
116
+ const snapshot = this.callbacks.getSnapshot();
117
+ if (!snapshot)
118
+ return;
119
+ const truncated = snapshot.length > this.config.snapshotMaxChars
120
+ ? snapshot.slice(-this.config.snapshotMaxChars)
121
+ : snapshot;
122
+ if (truncated === this.lastSnapshot) {
123
+ this.stableCount++;
124
+ }
125
+ else {
126
+ this.stableCount = 1;
127
+ this.lastSnapshot = truncated;
128
+ if (this.waitingForContentChange) {
129
+ this.waitingForContentChange = false;
130
+ }
131
+ }
132
+ // Layer 2: Cumulative stability — require N consecutive unchanged snapshots
133
+ if (this.stableCount < this.config.stableCount)
134
+ return;
135
+ // Layer 3: AI-driven cooldown
136
+ if (this.waitingForContentChange && truncated === this.lastAnalyzedSnapshot)
137
+ return;
138
+ if (this.timerCooldownUntil > Date.now())
139
+ return;
140
+ // All layers passed — call AI
141
+ this._analyzing = true;
142
+ this.callbacks.onAnalyzing();
143
+ try {
144
+ await this.analyze(truncated);
145
+ }
146
+ finally {
147
+ this._analyzing = false;
148
+ }
149
+ }
150
+ async analyze(snapshot) {
151
+ this.lastAnalyzedSnapshot = snapshot;
152
+ let analysis;
153
+ try {
154
+ analysis = await this.callAI(snapshot);
155
+ if (analysis.needsInteraction) {
156
+ this.callbacks.log(`ScreenAnalyzer AI input:\n${snapshot.slice(-1500)}`);
157
+ this.callbacks.log(`ScreenAnalyzer AI response: ${JSON.stringify(analysis)}`);
158
+ }
159
+ }
160
+ catch (err) {
161
+ this.callbacks.log(`ScreenAnalyzer AI call failed: ${err.message}`);
162
+ this.waitingForContentChange = true;
163
+ return;
164
+ }
165
+ // Apply checkAgainWhen
166
+ switch (analysis.checkAgainWhen) {
167
+ case 'content_changed':
168
+ case 'not_needed':
169
+ this.waitingForContentChange = true;
170
+ break;
171
+ case 'after_5s':
172
+ this.timerCooldownUntil = Date.now() + 5_000;
173
+ this.waitingForContentChange = false;
174
+ break;
175
+ case 'after_10s':
176
+ this.timerCooldownUntil = Date.now() + 10_000;
177
+ this.waitingForContentChange = false;
178
+ break;
179
+ }
180
+ if (analysis.needsInteraction && analysis.options && analysis.options.length > 0) {
181
+ // Generate keys deterministically in code, using AI-provided index + toggleKey/confirmKey
182
+ const toggleKey = analysis.toggleKey || 'Space';
183
+ const confirmKey = analysis.confirmKey || 'Enter';
184
+ // Fallback: if multiSelect but AI didn't return any confirm option,
185
+ // add a synthetic "Submit" confirm at the end (uses Enter at current position)
186
+ if (analysis.multiSelect && !analysis.options.some(o => o.type === 'confirm')) {
187
+ const maxIndex = Math.max(...analysis.options.map(o => o.index));
188
+ analysis.options.push({
189
+ label: '✅',
190
+ text: 'Submit',
191
+ selected: false,
192
+ type: 'confirm',
193
+ index: maxIndex + 1,
194
+ });
195
+ this.callbacks.log('ScreenAnalyzer: auto-added fallback confirm button');
196
+ }
197
+ for (const opt of analysis.options) {
198
+ const keys = [];
199
+ for (let i = 0; i < opt.index; i++)
200
+ keys.push('Down');
201
+ if (opt.type === 'toggle') {
202
+ keys.push(toggleKey);
203
+ // Return to top for next toggle
204
+ for (let i = 0; i < opt.index; i++)
205
+ keys.push('Up');
206
+ }
207
+ else if (opt.type === 'select' || opt.type === 'confirm') {
208
+ keys.push(confirmKey);
209
+ }
210
+ else if (opt.type === 'input') {
211
+ keys.push(confirmKey); // select the input option
212
+ }
213
+ opt.keys = keys;
214
+ }
215
+ this.promptActive = true;
216
+ this.callbacks.onTuiPrompt(analysis.description ?? 'CLI needs your selection', analysis.options, !!analysis.multiSelect);
217
+ this.callbacks.log(`ScreenAnalyzer: TUI prompt detected — ${analysis.description}${analysis.multiSelect ? ' (multi-select)' : ''} [toggleKey=${toggleKey}, confirmKey=${confirmKey}]`);
218
+ }
219
+ }
220
+ async callAI(snapshot) {
221
+ const url = `${this.config.baseUrl.replace(/\/+$/, '')}/chat/completions`;
222
+ const body = {
223
+ model: this.config.model,
224
+ messages: [
225
+ { role: 'system', content: SYSTEM_PROMPT },
226
+ { role: 'user', content: snapshot },
227
+ ],
228
+ temperature: 0,
229
+ max_tokens: 2048,
230
+ // Extra body params from config (e.g. { thinking: { type: "disabled" } } for Ark)
231
+ ...(this.config.extraBody ?? {}),
232
+ };
233
+ const resp = await fetch(url, {
234
+ method: 'POST',
235
+ headers: {
236
+ 'Content-Type': 'application/json',
237
+ 'Authorization': `Bearer ${this.config.apiKey}`,
238
+ ...(this.config.extraHeaders ?? {}),
239
+ },
240
+ body: JSON.stringify(body),
241
+ signal: AbortSignal.timeout(15_000),
242
+ });
243
+ if (!resp.ok) {
244
+ const text = await resp.text().catch(() => '');
245
+ throw new Error(`AI API ${resp.status}: ${text.slice(0, 200)}`);
246
+ }
247
+ const data = await resp.json();
248
+ const content = data?.choices?.[0]?.message?.content ?? '';
249
+ // Parse JSON from response — handle potential markdown wrapping
250
+ const jsonStr = content.replace(/^```json?\s*/, '').replace(/\s*```$/, '').trim();
251
+ try {
252
+ const parsed = JSON.parse(jsonStr);
253
+ return {
254
+ needsInteraction: !!parsed.needsInteraction,
255
+ description: parsed.description,
256
+ multiSelect: !!parsed.multiSelect,
257
+ toggleKey: parsed.toggleKey || 'Space',
258
+ confirmKey: parsed.confirmKey || 'Enter',
259
+ options: Array.isArray(parsed.options)
260
+ ? parsed.options.map((o, i) => ({
261
+ label: o.label || String(i + 1),
262
+ // Clean text: collapse newlines/multi-line descriptions into single line
263
+ text: (o.text || '').replace(/\n+/g, ' ').trim(),
264
+ selected: !!o.selected,
265
+ type: (['select', 'toggle', 'confirm', 'input'].includes(o.type) ? o.type : 'select'),
266
+ index: typeof o.index === 'number' ? o.index : i,
267
+ }))
268
+ : undefined,
269
+ checkAgainWhen: ['content_changed', 'after_5s', 'after_10s', 'not_needed'].includes(parsed.checkAgainWhen)
270
+ ? parsed.checkAgainWhen
271
+ : 'content_changed',
272
+ };
273
+ }
274
+ catch {
275
+ throw new Error(`Failed to parse AI response as JSON: ${jsonStr.slice(0, 200)}`);
276
+ }
277
+ }
278
+ }
279
+ //# sourceMappingURL=screen-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screen-analyzer.js","sourceRoot":"","sources":["../../src/utils/screen-analyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import xtermHeadless from '@xterm/headless';
2
+ type Terminal = InstanceType<typeof xtermHeadless.Terminal>;
3
+ export interface CaptureOpts {
4
+ cols: number;
5
+ rows: number;
6
+ startY: number;
7
+ }
8
+ /** Capture a section of the terminal buffer to a PNG buffer. */
9
+ export declare function captureToPng(terminal: Terminal, opts: CaptureOpts): Buffer;
10
+ export {};
11
+ //# sourceMappingURL=screenshot-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot-renderer.d.ts","sourceRoot":"","sources":["../../src/utils/screenshot-renderer.ts"],"names":[],"mappings":"AASA,OAAO,aAAa,MAAM,iBAAiB,CAAC;AAC5C,KAAK,QAAQ,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAC;AAuJ5D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,gEAAgE;AAChE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAqE1E"}
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Renders an xterm-headless buffer to a PNG buffer using @napi-rs/canvas.
3
+ * ANSI 256-color palette + RGB true color, bold weight, inverse, default fg/bg.
4
+ * Tokyo Night theme (matches src/worker.ts web terminal).
5
+ */
6
+ import { createCanvas, GlobalFonts } from '@napi-rs/canvas';
7
+ import { existsSync } from 'node:fs';
8
+ import { dirname, join } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ // ─── Font registration ──────────────────────────────────────────────────────
11
+ // @napi-rs/canvas does NOT auto-discover system fonts on Linux; we must
12
+ // explicitly register a path. We build an ordered fallback chain so missing
13
+ // glyphs in the primary font (e.g. emoji, dingbats) get picked up by a later
14
+ // font in the chain (skia walks the family list per glyph).
15
+ const fontFamilyChain = [];
16
+ let fontInitialized = false;
17
+ function tryRegister(path, alias) {
18
+ if (!existsSync(path))
19
+ return false;
20
+ try {
21
+ GlobalFonts.registerFromPath(path, alias);
22
+ return true;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ function ensureFontRegistered() {
29
+ if (fontInitialized)
30
+ return;
31
+ fontInitialized = true;
32
+ // 1. Project-bundled font(s) under assets/fonts/ — drop a TTF here to override defaults.
33
+ const projectFontDir = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'assets', 'fonts');
34
+ for (const fname of ['BotmuxMono-Regular.ttf', 'BotmuxMono.ttf', 'BotmuxMono-Regular.otf']) {
35
+ if (tryRegister(join(projectFontDir, fname), 'BotmuxMono')) {
36
+ tryRegister(join(projectFontDir, 'BotmuxMono-Bold.ttf'), 'BotmuxMono');
37
+ fontFamilyChain.push('BotmuxMono');
38
+ break;
39
+ }
40
+ }
41
+ // 2. CJK monospace — primary for Latin + Han glyphs.
42
+ const cjkRegular = [
43
+ '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc',
44
+ '/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc',
45
+ '/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc',
46
+ '/usr/share/fonts/opentype/noto/NotoSansMonoCJK-Regular.ttc',
47
+ ];
48
+ const cjkBold = [
49
+ '/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc',
50
+ '/usr/share/fonts/noto-cjk/NotoSansCJK-Bold.ttc',
51
+ '/usr/share/fonts/google-noto-cjk/NotoSansCJK-Bold.ttc',
52
+ '/usr/share/fonts/opentype/noto/NotoSansMonoCJK-Bold.ttc',
53
+ ];
54
+ for (const p of cjkRegular) {
55
+ if (tryRegister(p)) {
56
+ for (const bp of cjkBold)
57
+ if (tryRegister(bp))
58
+ break;
59
+ fontFamilyChain.push('Noto Sans Mono CJK SC');
60
+ break;
61
+ }
62
+ }
63
+ // 3. Latin monospace — DejaVu/Liberation cover dingbats (❯, ✓, etc.) and
64
+ // most box-drawing/geometric symbols not in CJK font.
65
+ const latinCandidates = [
66
+ ['/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf', 'DejaVu Sans Mono'],
67
+ ['/usr/share/fonts/dejavu/DejaVuSansMono.ttf', 'DejaVu Sans Mono'],
68
+ ['/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf', 'Liberation Mono'],
69
+ ['/usr/share/fonts/liberation/LiberationMono-Regular.ttf', 'Liberation Mono'],
70
+ ];
71
+ for (const [p, name] of latinCandidates) {
72
+ if (tryRegister(p)) {
73
+ // Best-effort bold companion
74
+ tryRegister(p.replace(/Regular\.ttf$/, 'Bold.ttf'));
75
+ tryRegister(p.replace(/SansMono\.ttf$/, 'SansMono-Bold.ttf'));
76
+ fontFamilyChain.push(name);
77
+ break;
78
+ }
79
+ }
80
+ // 4. Color emoji — Noto Color Emoji on Linux, Apple Color Emoji on macOS.
81
+ // skia handles CBDT/CBLC + COLR/CPAL color formats.
82
+ const emojiCandidates = [
83
+ ['/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf', 'Noto Color Emoji'],
84
+ ['/usr/share/fonts/noto/NotoColorEmoji.ttf', 'Noto Color Emoji'],
85
+ ['/usr/share/fonts/google-noto-emoji/NotoColorEmoji.ttf', 'Noto Color Emoji'],
86
+ ['/Library/Fonts/Apple Color Emoji.ttc', 'Apple Color Emoji'],
87
+ ['/System/Library/Fonts/Apple Color Emoji.ttc', 'Apple Color Emoji'],
88
+ ];
89
+ for (const [p, name] of emojiCandidates) {
90
+ if (tryRegister(p)) {
91
+ fontFamilyChain.push(name);
92
+ break;
93
+ }
94
+ }
95
+ if (fontFamilyChain.length === 0)
96
+ fontFamilyChain.push('monospace');
97
+ }
98
+ function fontSpec(bold) {
99
+ const families = fontFamilyChain.map(f => `"${f}"`).join(', ');
100
+ return `${bold ? 'bold ' : ''}${FONT_SIZE}px ${families}, monospace`;
101
+ }
102
+ /** Detect whether the leading codepoint is an emoji/symbol pictograph that
103
+ * uses bitmap glyph metrics — these need vertical centering, not top-align. */
104
+ function isPictograph(ch) {
105
+ if (!ch)
106
+ return false;
107
+ const cp = ch.codePointAt(0) ?? 0;
108
+ return ((cp >= 0x1F300 && cp <= 0x1FAFF) || // emoji + extended pictographs
109
+ (cp >= 0x1F900 && cp <= 0x1F9FF) || // supplemental symbols
110
+ (cp >= 0x1F1E6 && cp <= 0x1F1FF) || // regional indicators (flags)
111
+ (cp >= 0x2600 && cp <= 0x27BF) || // misc symbols + dingbats (✓✗★⚠ etc.)
112
+ cp === 0x231A || cp === 0x231B || // ⌚ ⌛
113
+ cp === 0x23E9 || cp === 0x23EA || cp === 0x23EB || cp === 0x23EC ||
114
+ cp === 0x23F0 || cp === 0x23F3 || // ⏰ ⏳
115
+ cp === 0x25FD || cp === 0x25FE || // ◽ ◾
116
+ cp === 0x2B50 || cp === 0x2B55 || // ⭐ ⭕
117
+ cp === 0x303D || cp === 0x3297 || cp === 0x3299);
118
+ }
119
+ const BG = '#1a1b26';
120
+ const FG = '#a9b1d6';
121
+ const ANSI16 = [
122
+ '#15161e', '#f7768e', '#9ece6a', '#e0af68', '#7aa2f7', '#bb9af7', '#7dcfff', '#a9b1d6',
123
+ '#414868', '#ff7a93', '#b9f27c', '#ff9e64', '#7da6ff', '#bb9af7', '#0db9d7', '#c0caf5',
124
+ ];
125
+ const PALETTE_256 = (() => {
126
+ const p = [...ANSI16];
127
+ const ramp = [0, 95, 135, 175, 215, 255];
128
+ for (let r = 0; r < 6; r++) {
129
+ for (let g = 0; g < 6; g++) {
130
+ for (let b = 0; b < 6; b++) {
131
+ p.push(`#${ramp[r].toString(16).padStart(2, '0')}${ramp[g].toString(16).padStart(2, '0')}${ramp[b].toString(16).padStart(2, '0')}`);
132
+ }
133
+ }
134
+ }
135
+ for (let i = 0; i < 24; i++) {
136
+ const v = (8 + i * 10).toString(16).padStart(2, '0');
137
+ p.push(`#${v}${v}${v}`);
138
+ }
139
+ return p;
140
+ })();
141
+ const FONT_SIZE = 14;
142
+ const CELL_W = 8.4;
143
+ const CELL_H = 18;
144
+ const PADDING = 8;
145
+ function colorOf(cell, isBg) {
146
+ const isDefault = isBg ? cell.isBgDefault() : cell.isFgDefault();
147
+ if (isDefault)
148
+ return null;
149
+ const isRGB = isBg ? cell.isBgRGB() : cell.isFgRGB();
150
+ const isPalette = isBg ? cell.isBgPalette() : cell.isFgPalette();
151
+ const v = isBg ? cell.getBgColor() : cell.getFgColor();
152
+ if (isRGB)
153
+ return `#${v.toString(16).padStart(6, '0')}`;
154
+ if (isPalette)
155
+ return PALETTE_256[v] ?? null;
156
+ return null;
157
+ }
158
+ /** Capture a section of the terminal buffer to a PNG buffer. */
159
+ export function captureToPng(terminal, opts) {
160
+ ensureFontRegistered();
161
+ const { cols, rows, startY } = opts;
162
+ const buffer = terminal.buffer.active;
163
+ const W = Math.ceil(PADDING * 2 + cols * CELL_W);
164
+ const H = PADDING * 2 + rows * CELL_H;
165
+ const canvas = createCanvas(W, H);
166
+ const ctx = canvas.getContext('2d');
167
+ ctx.fillStyle = BG;
168
+ ctx.fillRect(0, 0, W, H);
169
+ ctx.textBaseline = 'top';
170
+ ctx.font = fontSpec(false);
171
+ let currentBold = false;
172
+ for (let row = 0; row < rows; row++) {
173
+ const line = buffer.getLine(startY + row);
174
+ if (!line)
175
+ continue;
176
+ let col = 0;
177
+ while (col < cols) {
178
+ const cell = line.getCell(col);
179
+ if (!cell) {
180
+ col++;
181
+ continue;
182
+ }
183
+ const w = cell.getWidth();
184
+ if (w === 0) {
185
+ col++;
186
+ continue;
187
+ }
188
+ const ch = cell.getChars();
189
+ const x = PADDING + col * CELL_W;
190
+ const y = PADDING + row * CELL_H;
191
+ let fg = colorOf(cell, false) ?? FG;
192
+ let bg = colorOf(cell, true);
193
+ if (cell.isInverse()) {
194
+ const tmp = bg ?? BG;
195
+ bg = fg;
196
+ fg = tmp;
197
+ }
198
+ if (bg) {
199
+ ctx.fillStyle = bg;
200
+ ctx.fillRect(x, y, CELL_W * w, CELL_H);
201
+ }
202
+ if (ch && ch !== ' ') {
203
+ const bold = !!cell.isBold();
204
+ if (bold !== currentBold) {
205
+ currentBold = bold;
206
+ ctx.font = fontSpec(bold);
207
+ }
208
+ ctx.fillStyle = fg;
209
+ // Pictographs (emoji + dingbats) use bitmap metrics that don't align with
210
+ // text top-baseline — center them vertically in the cell instead.
211
+ if (isPictograph(ch)) {
212
+ ctx.textBaseline = 'middle';
213
+ ctx.fillText(ch, x, y + CELL_H / 2);
214
+ ctx.textBaseline = 'top';
215
+ }
216
+ else {
217
+ ctx.fillText(ch, x, y + 2);
218
+ }
219
+ }
220
+ col += w;
221
+ }
222
+ }
223
+ return canvas.toBuffer('image/png');
224
+ }
225
+ //# sourceMappingURL=screenshot-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot-renderer.js","sourceRoot":"","sources":["../../src/utils/screenshot-renderer.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,25 @@
1
+ /**
2
+ * Headless terminal renderer: feeds PTY data into an xterm-headless instance
3
+ * and periodically snapshots the rendered screen for Feishu card updates.
4
+ *
5
+ * Filters out TUI chrome and preamble (logo, version, prompt echo, system
6
+ * instructions) so only the CLI's actual work output appears in the card.
7
+ *
8
+ * Supports two rendering styles:
9
+ * - CLI-style (Claude Code, Aiden): output appends to scrollback. Baseline
10
+ * tracking isolates current-turn content. Phase 1 (OUTPUT_MARKER_RE) gates
11
+ * content detection.
12
+ * - TUI-style (CoCo, Codex): cursor-positioned full-screen UI. Content can
13
+ * be overwritten when the TUI redraws (e.g. response → idle prompt).
14
+ * Viewport fallback + peak retention ensure response content is captured.
15
+ *
16
+ * Timer overlay avoidance: The PTY is intentionally wider than normal so that
17
+ * right-aligned TUI overlays (elapsed time, timeout counters) are rendered
18
+ * far to the right. Snapshots only read the first `contentCols` columns,
19
+ * cleanly excluding the overlay area — no fragile regex stripping needed.
20
+ */
21
+ import xtermHeadless from '@xterm/headless';
22
+ declare const Terminal: typeof xtermHeadless.Terminal;
1
23
  export declare class TerminalRenderer {
2
24
  private terminal;
3
25
  private lastHash;
@@ -48,7 +70,15 @@ export declare class TerminalRenderer {
48
70
  * Used for TUI viewport fallback where output markers differ.
49
71
  */
50
72
  private extractContent;
73
+ /**
74
+ * Raw viewport snapshot — no filtering, no phase gating, no chrome removal.
75
+ * Used by ScreenAnalyzer which needs the full screen including ❯ cursor lines.
76
+ */
77
+ rawSnapshot(): string;
51
78
  resize(cols: number, rows: number): void;
79
+ /** Expose the underlying xterm-headless instance for screenshot rendering. */
80
+ get xterm(): InstanceType<typeof Terminal>;
52
81
  dispose(): void;
53
82
  }
83
+ export {};
54
84
  //# sourceMappingURL=terminal-renderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"terminal-renderer.d.ts","sourceRoot":"","sources":["../../src/utils/terminal-renderer.ts"],"names":[],"mappings":"AAmFA,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,QAAQ,CAAM;IACtB,yDAAyD;IACzD,OAAO,CAAC,aAAa,CAAK;IAC1B,6EAA6E;IAC7E,OAAO,CAAC,WAAW,CAAM;IACzB;;;2DAGuD;IACvD,OAAO,CAAC,gBAAgB,CAAS;IACjC;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB,CAAQ;gBAEpB,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAItC,mDAAmD;IACnD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAWzB;;;;OAIG;IACH,WAAW,IAAI,IAAI;IAOnB;;;;;;;;;OASG;IACH,QAAQ,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE;IA4DjD;;;;OAIG;IACH,OAAO,CAAC,cAAc;IA2CtB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIxC,OAAO,IAAI,IAAI;CAGhB"}
1
+ {"version":3,"file":"terminal-renderer.d.ts","sourceRoot":"","sources":["../../src/utils/terminal-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,aAAa,MAAM,iBAAiB,CAAC;AAC5C,QAAA,MAAQ,QAAQ,+BAAkB,CAAC;AA8DnC,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,QAAQ,CAAM;IACtB,yDAAyD;IACzD,OAAO,CAAC,aAAa,CAAK;IAC1B,6EAA6E;IAC7E,OAAO,CAAC,WAAW,CAAM;IACzB;;;2DAGuD;IACvD,OAAO,CAAC,gBAAgB,CAAS;IACjC;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB,CAAQ;gBAEpB,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAItC,mDAAmD;IACnD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAWzB;;;;OAIG;IACH,WAAW,IAAI,IAAI;IAOnB;;;;;;;;;OASG;IACH,QAAQ,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE;IA4DjD;;;;OAIG;IACH,OAAO,CAAC,cAAc;IA2CtB;;;OAGG;IACH,WAAW,IAAI,MAAM;IAsBrB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIxC,8EAA8E;IAC9E,IAAI,KAAK,IAAI,YAAY,CAAC,OAAO,QAAQ,CAAC,CAA0B;IAEpE,OAAO,IAAI,IAAI;CAGhB"}
@@ -221,9 +221,34 @@ export class TerminalRenderer {
221
221
  }
222
222
  return filtered.join('\n');
223
223
  }
224
+ /**
225
+ * Raw viewport snapshot — no filtering, no phase gating, no chrome removal.
226
+ * Used by ScreenAnalyzer which needs the full screen including ❯ cursor lines.
227
+ */
228
+ rawSnapshot() {
229
+ const buffer = this.terminal.buffer.active;
230
+ const baseY = buffer.baseY;
231
+ const rows = this.terminal.rows;
232
+ const readCols = Math.min(SNAPSHOT_COLS, this.terminal.cols);
233
+ const endY = baseY + rows;
234
+ const lines = [];
235
+ for (let y = baseY; y < endY; y++) {
236
+ const line = buffer.getLine(y);
237
+ if (!line)
238
+ continue;
239
+ lines.push(cleanBoxDrawing(line.translateToString(true, 0, readCols)));
240
+ }
241
+ // Trim trailing blank lines only
242
+ while (lines.length > 0 && BLANK_RE.test(lines[lines.length - 1])) {
243
+ lines.pop();
244
+ }
245
+ return lines.join('\n');
246
+ }
224
247
  resize(cols, rows) {
225
248
  this.terminal.resize(cols, rows);
226
249
  }
250
+ /** Expose the underlying xterm-headless instance for screenshot rendering. */
251
+ get xterm() { return this.terminal; }
227
252
  dispose() {
228
253
  this.terminal.dispose();
229
254
  }