botmux 2.14.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  2. package/dist/adapters/cli/claude-code.js +13 -5
  3. package/dist/adapters/cli/claude-code.js.map +1 -1
  4. package/dist/cli.js +78 -17
  5. package/dist/cli.js.map +1 -1
  6. package/dist/core/session-manager.js +2 -2
  7. package/dist/core/session-manager.js.map +1 -1
  8. package/dist/daemon.d.ts.map +1 -1
  9. package/dist/daemon.js +107 -30
  10. package/dist/daemon.js.map +1 -1
  11. package/dist/im/lark/client.d.ts +7 -0
  12. package/dist/im/lark/client.d.ts.map +1 -1
  13. package/dist/im/lark/client.js +37 -0
  14. package/dist/im/lark/client.js.map +1 -1
  15. package/dist/im/lark/event-dispatcher.d.ts +17 -8
  16. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  17. package/dist/im/lark/event-dispatcher.js +54 -15
  18. package/dist/im/lark/event-dispatcher.js.map +1 -1
  19. package/dist/im/lark/message-parser.d.ts.map +1 -1
  20. package/dist/im/lark/message-parser.js +6 -1
  21. package/dist/im/lark/message-parser.js.map +1 -1
  22. package/dist/services/session-store.d.ts +12 -0
  23. package/dist/services/session-store.d.ts.map +1 -1
  24. package/dist/services/session-store.js +19 -2
  25. package/dist/services/session-store.js.map +1 -1
  26. package/dist/setup/detect-platform.d.ts +14 -0
  27. package/dist/setup/detect-platform.d.ts.map +1 -0
  28. package/dist/setup/detect-platform.js +139 -0
  29. package/dist/setup/detect-platform.js.map +1 -0
  30. package/dist/setup/ensure-fonts.d.ts +13 -0
  31. package/dist/setup/ensure-fonts.d.ts.map +1 -0
  32. package/dist/setup/ensure-fonts.js +225 -0
  33. package/dist/setup/ensure-fonts.js.map +1 -0
  34. package/dist/setup/ensure-tmux.d.ts +11 -0
  35. package/dist/setup/ensure-tmux.d.ts.map +1 -0
  36. package/dist/setup/ensure-tmux.js +151 -0
  37. package/dist/setup/ensure-tmux.js.map +1 -0
  38. package/dist/setup/index.d.ts +9 -0
  39. package/dist/setup/index.d.ts.map +1 -0
  40. package/dist/setup/index.js +35 -0
  41. package/dist/setup/index.js.map +1 -0
  42. package/dist/utils/bot-mention-dedup.d.ts +26 -0
  43. package/dist/utils/bot-mention-dedup.d.ts.map +1 -0
  44. package/dist/utils/bot-mention-dedup.js +56 -0
  45. package/dist/utils/bot-mention-dedup.js.map +1 -0
  46. package/dist/utils/screenshot-renderer.d.ts.map +1 -1
  47. package/dist/utils/screenshot-renderer.js +12 -5
  48. package/dist/utils/screenshot-renderer.js.map +1 -1
  49. package/package.json +1 -1
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Ensure the screenshot-renderer (`@napi-rs/canvas`) has CJK / Latin / emoji
3
+ * fonts available. Downloads missing categories from GitHub release
4
+ * artifacts to `~/.botmux/fonts/` so registration (`GlobalFonts.registerFromPath`)
5
+ * can find them at runtime.
6
+ *
7
+ * macOS is skipped entirely — PingFang + Menlo + Apple Color Emoji are
8
+ * preinstalled.
9
+ *
10
+ * Failures are NOT fatal: missing fonts only degrade the screenshot rendered
11
+ * for Lark (CJK becomes tofu, emoji becomes monochrome). The daemon should
12
+ * still come up. Tmux is the load-bearing dep, fonts are nice-to-have.
13
+ */
14
+ import { existsSync, mkdirSync, createWriteStream, statSync, unlinkSync, renameSync } from 'node:fs';
15
+ import { homedir } from 'node:os';
16
+ import { join } from 'node:path';
17
+ import { get as httpsGet } from 'node:https';
18
+ import { detectPlatform } from './detect-platform.js';
19
+ const FONT_DIR = join(homedir(), '.botmux', 'fonts');
20
+ /** Download targets — pinned to specific release tags so the URLs don't rot.
21
+ *
22
+ * Sources (all permissive licenses, GitHub-hosted):
23
+ * - notofonts/noto-cjk @ Sans2.004 (Mono variant) — OFL
24
+ * - JetBrains/JetBrainsMono @ v2.304 — Apache 2.0 (replaces DejaVu since
25
+ * DejaVu only ships .tar.bz2 release artifacts, not individual TTFs)
26
+ * - googlefonts/noto-emoji @ v2.047 — OFL
27
+ */
28
+ const FONT_SPECS = [
29
+ {
30
+ category: 'CJK',
31
+ files: [
32
+ {
33
+ name: 'NotoSansMonoCJKsc-Regular.otf',
34
+ url: 'https://github.com/notofonts/noto-cjk/raw/Sans2.004/Sans/Mono/NotoSansMonoCJKsc-Regular.otf',
35
+ minBytes: 1_000_000,
36
+ },
37
+ {
38
+ name: 'NotoSansMonoCJKsc-Bold.otf',
39
+ url: 'https://github.com/notofonts/noto-cjk/raw/Sans2.004/Sans/Mono/NotoSansMonoCJKsc-Bold.otf',
40
+ minBytes: 1_000_000,
41
+ },
42
+ ],
43
+ systemPaths: [
44
+ '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc',
45
+ '/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc',
46
+ '/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc',
47
+ '/usr/share/fonts/opentype/noto/NotoSansMonoCJK-Regular.ttc',
48
+ ],
49
+ },
50
+ {
51
+ category: 'Latin',
52
+ files: [
53
+ {
54
+ name: 'JetBrainsMono-Regular.ttf',
55
+ url: 'https://github.com/JetBrains/JetBrainsMono/raw/v2.304/fonts/ttf/JetBrainsMono-Regular.ttf',
56
+ minBytes: 100_000,
57
+ },
58
+ {
59
+ name: 'JetBrainsMono-Bold.ttf',
60
+ url: 'https://github.com/JetBrains/JetBrainsMono/raw/v2.304/fonts/ttf/JetBrainsMono-Bold.ttf',
61
+ minBytes: 100_000,
62
+ },
63
+ ],
64
+ systemPaths: [
65
+ '/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf',
66
+ '/usr/share/fonts/dejavu/DejaVuSansMono.ttf',
67
+ '/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf',
68
+ '/usr/share/fonts/liberation/LiberationMono-Regular.ttf',
69
+ '/usr/share/fonts/truetype/jetbrains-mono/JetBrainsMono-Regular.ttf',
70
+ ],
71
+ },
72
+ {
73
+ category: 'Emoji',
74
+ files: [
75
+ {
76
+ name: 'NotoColorEmoji.ttf',
77
+ url: 'https://github.com/googlefonts/noto-emoji/raw/v2.047/fonts/NotoColorEmoji.ttf',
78
+ minBytes: 5_000_000,
79
+ },
80
+ ],
81
+ systemPaths: [
82
+ '/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf',
83
+ '/usr/share/fonts/noto/NotoColorEmoji.ttf',
84
+ '/usr/share/fonts/google-noto-emoji/NotoColorEmoji.ttf',
85
+ ],
86
+ },
87
+ ];
88
+ /** Public: paths the screenshot-renderer should also probe. */
89
+ export function botmuxFontDir() {
90
+ return FONT_DIR;
91
+ }
92
+ function categoryAlreadyOk(spec) {
93
+ // 1. System path present?
94
+ if (spec.systemPaths.some(p => existsSync(p)))
95
+ return true;
96
+ // 2. We already downloaded it on a prior run?
97
+ return spec.files.every(f => {
98
+ const p = join(FONT_DIR, f.name);
99
+ if (!existsSync(p))
100
+ return false;
101
+ try {
102
+ return statSync(p).size >= f.minBytes;
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ });
108
+ }
109
+ /** Download a URL to disk, following redirects (GitHub raw → CDN).
110
+ * All FONT_SPECS URLs are https, so we don't bother with an http fallback.
111
+ * Hard timeout (default 60s) covers connect + transfer; on expiry the
112
+ * request is destroyed and the partial file is unlinked. */
113
+ function downloadFile(url, destPath, timeoutMs = 60_000) {
114
+ return new Promise((resolve, reject) => {
115
+ const tmp = destPath + '.part';
116
+ let settled = false;
117
+ let activeReq = null;
118
+ const cleanup = () => {
119
+ try {
120
+ unlinkSync(tmp);
121
+ }
122
+ catch { /* may not exist */ }
123
+ };
124
+ const finish = (err) => {
125
+ if (settled)
126
+ return;
127
+ settled = true;
128
+ clearTimeout(timer);
129
+ if (activeReq)
130
+ try {
131
+ activeReq.destroy();
132
+ }
133
+ catch { /* ignore */ }
134
+ if (err) {
135
+ cleanup();
136
+ reject(err);
137
+ }
138
+ else
139
+ resolve();
140
+ };
141
+ const timer = setTimeout(() => finish(new Error(`下载超时 (${timeoutMs}ms): ${url}`)), timeoutMs);
142
+ const followRedirect = (current, hops) => {
143
+ if (hops > 5)
144
+ return finish(new Error(`太多重定向: ${url}`));
145
+ const u = new URL(current);
146
+ if (u.protocol !== 'https:')
147
+ return finish(new Error(`仅支持 https://, got ${u.protocol} for ${current}`));
148
+ activeReq = httpsGet(u, { headers: { 'user-agent': 'botmux-font-installer' } }, (res) => {
149
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
150
+ res.resume();
151
+ followRedirect(new URL(res.headers.location, u).toString(), hops + 1);
152
+ return;
153
+ }
154
+ if (res.statusCode !== 200) {
155
+ res.resume();
156
+ return finish(new Error(`HTTP ${res.statusCode} for ${current}`));
157
+ }
158
+ const out = createWriteStream(tmp);
159
+ res.pipe(out);
160
+ out.on('error', (err) => finish(err));
161
+ out.on('finish', () => {
162
+ out.close(() => {
163
+ try {
164
+ renameSync(tmp, destPath);
165
+ finish();
166
+ }
167
+ catch (err) {
168
+ finish(err);
169
+ }
170
+ });
171
+ });
172
+ });
173
+ activeReq.on('error', (err) => finish(err));
174
+ };
175
+ followRedirect(url, 0);
176
+ });
177
+ }
178
+ async function downloadCategory(spec) {
179
+ if (!existsSync(FONT_DIR))
180
+ mkdirSync(FONT_DIR, { recursive: true });
181
+ for (const f of spec.files) {
182
+ const dest = join(FONT_DIR, f.name);
183
+ // Skip if already present and big enough.
184
+ try {
185
+ if (existsSync(dest) && statSync(dest).size >= f.minBytes)
186
+ continue;
187
+ }
188
+ catch { /* fall through to redownload */ }
189
+ console.log(` 下载 ${spec.category}: ${f.name} ...`);
190
+ await downloadFile(f.url, dest);
191
+ const sz = statSync(dest).size;
192
+ if (sz < f.minBytes) {
193
+ try {
194
+ unlinkSync(dest);
195
+ }
196
+ catch { /* ignore */ }
197
+ throw new Error(`${f.name} 下载完毕但大小异常 (${sz} < ${f.minBytes})`);
198
+ }
199
+ }
200
+ }
201
+ export async function ensureFonts(info) {
202
+ const platform = info ?? detectPlatform();
203
+ const result = { fontDir: FONT_DIR, ready: [], failed: [] };
204
+ // macOS: skip — system fonts cover everything we need.
205
+ if (platform.os === 'darwin') {
206
+ result.ready.push('CJK', 'Latin', 'Emoji');
207
+ return result;
208
+ }
209
+ for (const spec of FONT_SPECS) {
210
+ if (categoryAlreadyOk(spec)) {
211
+ result.ready.push(spec.category);
212
+ continue;
213
+ }
214
+ try {
215
+ await downloadCategory(spec);
216
+ result.ready.push(spec.category);
217
+ }
218
+ catch (err) {
219
+ console.warn(`⚠️ 字体下载失败 [${spec.category}]: ${err?.message ?? err}`);
220
+ result.failed.push(spec.category);
221
+ }
222
+ }
223
+ return result;
224
+ }
225
+ //# sourceMappingURL=ensure-fonts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensure-fonts.js","sourceRoot":"","sources":["../../src/setup/ensure-fonts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type PackageManager, type PlatformInfo } from './detect-platform.js';
2
+ export interface TmuxResult {
3
+ installed: boolean;
4
+ version?: string;
5
+ /** True iff we ran an installer (vs. tmux was already present). */
6
+ freshInstall: boolean;
7
+ /** Which strategy actually ran the install. */
8
+ strategy?: PackageManager;
9
+ }
10
+ export declare function ensureTmux(info?: PlatformInfo): Promise<TmuxResult>;
11
+ //# sourceMappingURL=ensure-tmux.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensure-tmux.d.ts","sourceRoot":"","sources":["../../src/setup/ensure-tmux.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAkB,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAE9F,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,YAAY,EAAE,OAAO,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AA0ED,wBAAsB,UAAU,CAAC,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CA2DzE"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Ensure tmux is installed before the daemon starts. Strategy (first one
3
+ * that fits wins):
4
+ *
5
+ * 1. Already installed → done.
6
+ * 2. brew available → `brew install tmux` (no sudo)
7
+ * 3. conda/mamba available → `conda install -y -c conda-forge tmux` (no sudo)
8
+ * 4. Linux + system pkg manager:
9
+ * a. NOPASSWD sudo or running as root → run non-interactively
10
+ * b. Has TTY → run interactively (sudo will prompt for password)
11
+ * c. No TTY (autostart / pm2 fork) → skip and throw with manual command
12
+ * 5. Otherwise → throw with manual command.
13
+ *
14
+ * The caller (cli.ts) treats a thrown error as fatal: tmux is non-negotiable
15
+ * for the /adopt + multi-pane Web terminal experience, and the user explicitly
16
+ * opted into hard-fail-on-missing.
17
+ */
18
+ import { execSync, spawnSync } from 'node:child_process';
19
+ import { detectPlatform } from './detect-platform.js';
20
+ function probeTmuxVersion() {
21
+ try {
22
+ const out = execSync('tmux -V', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 3000 });
23
+ return out.trim();
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ }
29
+ /** Wrap a system command with the appropriate sudo prefix for the current
30
+ * platform context, or return undefined if we cannot escalate (no
31
+ * passwordless sudo and no TTY to prompt on). */
32
+ function sudoPrefix(cmd, info) {
33
+ if (info.isRoot)
34
+ return cmd;
35
+ if (info.passwordlessSudo)
36
+ return ['sudo', '-n', ...cmd];
37
+ if (info.hasTty)
38
+ return ['sudo', ...cmd];
39
+ return undefined;
40
+ }
41
+ /** Build the install argv for a given package manager. Pure: returns argv[]
42
+ * ready for spawnSync, no side effects. Returns undefined if escalation
43
+ * isn't possible. */
44
+ function buildInstallArgv(pm, pkg, info) {
45
+ switch (pm) {
46
+ case 'brew': return ['brew', 'install', pkg];
47
+ case 'conda': return ['conda', 'install', '-y', '-c', 'conda-forge', pkg];
48
+ case 'apt': return sudoPrefix(['apt-get', 'install', '-y', pkg], info);
49
+ case 'dnf': return sudoPrefix(['dnf', 'install', '-y', pkg], info);
50
+ case 'yum': return sudoPrefix(['yum', 'install', '-y', pkg], info);
51
+ case 'pacman': return sudoPrefix(['pacman', '-S', '--noconfirm', pkg], info);
52
+ case 'apk': return sudoPrefix(['apk', 'add', pkg], info);
53
+ case 'zypper': return sudoPrefix(['zypper', 'install', '-y', pkg], info);
54
+ case 'unknown': return undefined;
55
+ }
56
+ }
57
+ /** apt-get specifically needs an updated package list on minimal images
58
+ * before the install will find tmux. This is NOT part of buildInstallArgv
59
+ * (which is pure) — it runs once just before the apt install attempt.
60
+ * Failure here is non-fatal; the actual install will fail loudly if it
61
+ * can't find the package. */
62
+ function aptUpdateBeforeInstall(info) {
63
+ const argv = sudoPrefix(['apt-get', 'update'], info);
64
+ if (!argv)
65
+ return;
66
+ try {
67
+ spawnSync(argv[0], argv.slice(1), { stdio: 'inherit', timeout: 120_000 });
68
+ }
69
+ catch { /* best-effort */ }
70
+ }
71
+ /** Suggest the manual command we'd have run, for the failure message. */
72
+ function suggestManualCommand(pm, pkg) {
73
+ switch (pm) {
74
+ case 'brew': return `brew install ${pkg}`;
75
+ case 'conda': return `conda install -y -c conda-forge ${pkg}`;
76
+ case 'apt': return `sudo apt-get update && sudo apt-get install -y ${pkg}`;
77
+ case 'dnf': return `sudo dnf install -y ${pkg}`;
78
+ case 'yum': return `sudo yum install -y ${pkg}`;
79
+ case 'pacman': return `sudo pacman -S --noconfirm ${pkg}`;
80
+ case 'apk': return `sudo apk add ${pkg}`;
81
+ case 'zypper': return `sudo zypper install -y ${pkg}`;
82
+ default: return `(请手动安装 ${pkg})`;
83
+ }
84
+ }
85
+ function runInstall(argv) {
86
+ const result = spawnSync(argv[0], argv.slice(1), {
87
+ stdio: 'inherit',
88
+ timeout: 10 * 60_000, // 10 min — apt-get on slow networks
89
+ });
90
+ return result.status === 0;
91
+ }
92
+ export async function ensureTmux(info) {
93
+ const platform = info ?? detectPlatform();
94
+ // Step 1: already installed?
95
+ const existing = probeTmuxVersion();
96
+ if (existing) {
97
+ return { installed: true, version: existing, freshInstall: false };
98
+ }
99
+ console.log('⚠️ tmux 未检测到,正在安装...');
100
+ // Step 2..4: walk the package-manager preference list.
101
+ const tried = [];
102
+ for (const pm of platform.packageManagers) {
103
+ if (pm === 'unknown')
104
+ continue;
105
+ const argv = buildInstallArgv(pm, 'tmux', platform);
106
+ if (!argv) {
107
+ tried.push(`${pm}(跳过:当前用户无 sudo 且无 TTY)`);
108
+ continue;
109
+ }
110
+ if (pm === 'apt')
111
+ aptUpdateBeforeInstall(platform);
112
+ console.log(` 尝试 ${pm}: ${argv.join(' ')}`);
113
+ if (runInstall(argv)) {
114
+ const v = probeTmuxVersion();
115
+ if (v) {
116
+ console.log(`✅ tmux ${v} 安装完成 (via ${pm})`);
117
+ return { installed: true, version: v, freshInstall: true, strategy: pm };
118
+ }
119
+ tried.push(`${pm}(命令成功但 tmux -V 仍失败)`);
120
+ }
121
+ else {
122
+ tried.push(`${pm}(命令返回非零)`);
123
+ }
124
+ }
125
+ // Build a useful failure message with the most relevant manual command.
126
+ const preferred = platform.packageManagers.find(p => p !== 'unknown') ?? 'unknown';
127
+ const manual = suggestManualCommand(preferred, 'tmux');
128
+ const lines = [
129
+ '❌ 自动安装 tmux 失败',
130
+ '',
131
+ '已尝试:',
132
+ ...tried.map(t => ` - ${t}`),
133
+ '',
134
+ '请手动安装后重试:',
135
+ ` ${manual}`,
136
+ ];
137
+ // macOS without Homebrew → guide the user to install brew first.
138
+ if (platform.os === 'darwin' && !platform.packageManagers.includes('brew')) {
139
+ lines.push('');
140
+ lines.push('macOS 推荐先安装 Homebrew:');
141
+ lines.push(' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"');
142
+ lines.push('安装完成后重试 `botmux start`,会走 brew 自动装 tmux。');
143
+ }
144
+ if (!platform.hasTty && !platform.isRoot && !platform.passwordlessSudo && platform.os === 'linux') {
145
+ lines.push('');
146
+ lines.push('提示:当前不是交互式 TTY 且 sudo 需要密码,systemd/pm2 自启场景下无法弹密码。');
147
+ lines.push('请在 shell 中手动跑一次 `botmux start`,或配置 NOPASSWD sudoers 后再启用 autostart。');
148
+ }
149
+ throw new Error(lines.join('\n'));
150
+ }
151
+ //# sourceMappingURL=ensure-tmux.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensure-tmux.js","sourceRoot":"","sources":["../../src/setup/ensure-tmux.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { type TmuxResult } from './ensure-tmux.js';
2
+ import { type FontResult } from './ensure-fonts.js';
3
+ export interface DependenciesReport {
4
+ tmux: TmuxResult;
5
+ fonts: FontResult;
6
+ }
7
+ export { botmuxFontDir } from './ensure-fonts.js';
8
+ export declare function ensureDependencies(): Promise<DependenciesReport>;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/setup/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAsBtE"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Dependency bootstrap. Called from `botmux start` and `botmux restart` so
3
+ * a fresh machine that just `npm i -g botmux`'d gets tmux + screenshot fonts
4
+ * provisioned without manual setup.
5
+ *
6
+ * - tmux is required: a failed install throws so cli.ts can exit non-zero.
7
+ * - fonts are nice-to-have: failures only print a warning.
8
+ */
9
+ import { detectPlatform } from './detect-platform.js';
10
+ import { ensureTmux } from './ensure-tmux.js';
11
+ import { ensureFonts } from './ensure-fonts.js';
12
+ export { botmuxFontDir } from './ensure-fonts.js';
13
+ export async function ensureDependencies() {
14
+ const platform = detectPlatform();
15
+ // tmux first — it's the load-bearing dep. Throws on failure.
16
+ const tmux = await ensureTmux(platform);
17
+ if (!tmux.freshInstall) {
18
+ console.log(`✓ tmux ${tmux.version} (existing)`);
19
+ }
20
+ // Fonts second — best-effort.
21
+ const fonts = await ensureFonts(platform);
22
+ if (fonts.failed.length === 0) {
23
+ if (platform.os === 'darwin') {
24
+ console.log('✓ 字体: 系统字体已就绪 (macOS)');
25
+ }
26
+ else {
27
+ console.log(`✓ 字体: ${fonts.ready.join(' / ')} 已就绪`);
28
+ }
29
+ }
30
+ else {
31
+ console.warn(`⚠️ 字体部分缺失: ${fonts.failed.join(' / ')} —— 飞书截图中相关字符可能渲染为方块`);
32
+ }
33
+ return { tmux, fonts };
34
+ }
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/setup/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAmB,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAmB,MAAM,mBAAmB,CAAC;AAOjE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAC;IAElC,6DAA6D;IAC7D,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IACxC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;IACnD,CAAC;IAED,8BAA8B;IAC9B,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC1C,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,IAAI,QAAQ,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC9E,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACzB,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Cross-path dedup for bot-to-bot @mentions.
3
+ *
4
+ * Two routes can deliver the same `botmux send --mention <peer>` to a target
5
+ * daemon:
6
+ * 1. Lark WSClient → event-dispatcher → handleThreadReply
7
+ * 2. signal-file watcher → processBotMentionSignal
8
+ *
9
+ * Whichever fires first records the inbound message_id here; the other side
10
+ * checks before forwarding to the worker so we never enqueue the same turn
11
+ * twice. Keyed by inbound message_id (not session/anchor) so it is robust to
12
+ * reordering and to stale `lastMessageAt` from prior unrelated turns.
13
+ *
14
+ * In-memory only (one instance per daemon process). Entries expire after
15
+ * `TTL_MS` to keep the map bounded; 30s is well past any realistic gap
16
+ * between the two paths.
17
+ */
18
+ /** Mark this messageId as already routed to the worker by one of the two
19
+ * paths. Calling twice for the same id is a no-op. */
20
+ export declare function markBotMentionMessageHandled(messageId: string | undefined): void;
21
+ /** Did we already route this messageId? Returns true if the other path got
22
+ * there first within the TTL window. */
23
+ export declare function isBotMentionMessageHandled(messageId: string | undefined): boolean;
24
+ /** Test seam — drop everything. */
25
+ export declare function _resetForTest(): void;
26
+ //# sourceMappingURL=bot-mention-dedup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot-mention-dedup.d.ts","sourceRoot":"","sources":["../../src/utils/bot-mention-dedup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH;uDACuD;AACvD,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAIhF;AAED;yCACyC;AACzC,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CASjF;AAED,mCAAmC;AACnC,wBAAgB,aAAa,IAAI,IAAI,CAEpC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Cross-path dedup for bot-to-bot @mentions.
3
+ *
4
+ * Two routes can deliver the same `botmux send --mention <peer>` to a target
5
+ * daemon:
6
+ * 1. Lark WSClient → event-dispatcher → handleThreadReply
7
+ * 2. signal-file watcher → processBotMentionSignal
8
+ *
9
+ * Whichever fires first records the inbound message_id here; the other side
10
+ * checks before forwarding to the worker so we never enqueue the same turn
11
+ * twice. Keyed by inbound message_id (not session/anchor) so it is robust to
12
+ * reordering and to stale `lastMessageAt` from prior unrelated turns.
13
+ *
14
+ * In-memory only (one instance per daemon process). Entries expire after
15
+ * `TTL_MS` to keep the map bounded; 30s is well past any realistic gap
16
+ * between the two paths.
17
+ */
18
+ const TTL_MS = 30_000;
19
+ const seen = new Map(); // messageId → expiresAt (epoch ms)
20
+ /** Mark this messageId as already routed to the worker by one of the two
21
+ * paths. Calling twice for the same id is a no-op. */
22
+ export function markBotMentionMessageHandled(messageId) {
23
+ if (!messageId)
24
+ return;
25
+ gc();
26
+ seen.set(messageId, Date.now() + TTL_MS);
27
+ }
28
+ /** Did we already route this messageId? Returns true if the other path got
29
+ * there first within the TTL window. */
30
+ export function isBotMentionMessageHandled(messageId) {
31
+ if (!messageId)
32
+ return false;
33
+ const expiresAt = seen.get(messageId);
34
+ if (expiresAt === undefined)
35
+ return false;
36
+ if (expiresAt < Date.now()) {
37
+ seen.delete(messageId);
38
+ return false;
39
+ }
40
+ return true;
41
+ }
42
+ /** Test seam — drop everything. */
43
+ export function _resetForTest() {
44
+ seen.clear();
45
+ }
46
+ function gc() {
47
+ // Cheap incremental GC: only sweep when we cross a small threshold.
48
+ if (seen.size < 64)
49
+ return;
50
+ const now = Date.now();
51
+ for (const [k, v] of seen) {
52
+ if (v < now)
53
+ seen.delete(k);
54
+ }
55
+ }
56
+ //# sourceMappingURL=bot-mention-dedup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot-mention-dedup.js","sourceRoot":"","sources":["../../src/utils/bot-mention-dedup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,GAAG,MAAM,CAAC;AAEtB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,mCAAmC;AAE3E;uDACuD;AACvD,MAAM,UAAU,4BAA4B,CAAC,SAA6B;IACxE,IAAI,CAAC,SAAS;QAAE,OAAO;IACvB,EAAE,EAAE,CAAC;IACL,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED;yCACyC;AACzC,MAAM,UAAU,0BAA0B,CAAC,SAA6B;IACtE,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC1C,IAAI,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,KAAK,EAAE,CAAC;AACf,CAAC;AAED,SAAS,EAAE;IACT,oEAAoE;IACpE,IAAI,IAAI,CAAC,IAAI,GAAG,EAAE;QAAE,OAAO;IAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,GAAG,GAAG;YAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC"}
@@ -1 +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;AAsJ5D,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"}
1
+ {"version":3,"file":"screenshot-renderer.d.ts","sourceRoot":"","sources":["../../src/utils/screenshot-renderer.ts"],"names":[],"mappings":"AAUA,OAAO,aAAa,MAAM,iBAAiB,CAAC;AAC5C,KAAK,QAAQ,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAC;AA4J5D,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"}
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { createCanvas, GlobalFonts } from '@napi-rs/canvas';
7
7
  import { existsSync } from 'node:fs';
8
+ import { homedir } from 'node:os';
8
9
  import { dirname, join } from 'node:path';
9
10
  import { fileURLToPath } from 'node:url';
10
11
  // ─── Font registration ──────────────────────────────────────────────────────
@@ -39,8 +40,11 @@ function ensureFontRegistered() {
39
40
  }
40
41
  }
41
42
  // 2. CJK monospace — primary for Latin + Han glyphs.
42
- // Linux: Noto Sans CJK(需要 fonts-noto-cjk 包);macOS: 系统自带 PingFang/Hiragino。
43
+ // Linux: Noto Sans CJK(需要 fonts-noto-cjk 包,或 botmux setup 自动下载到 ~/.botmux/fonts/);
44
+ // macOS: 系统自带 PingFang/Hiragino。
45
+ const userFontDir = join(homedir(), '.botmux', 'fonts');
43
46
  const cjkCandidates = [
47
+ { regular: join(userFontDir, 'NotoSansMonoCJKsc-Regular.otf'), bold: join(userFontDir, 'NotoSansMonoCJKsc-Bold.otf'), family: 'Noto Sans Mono CJK SC' },
44
48
  { regular: '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc', bold: '/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc', family: 'Noto Sans Mono CJK SC' },
45
49
  { regular: '/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc', bold: '/usr/share/fonts/noto-cjk/NotoSansCJK-Bold.ttc', family: 'Noto Sans Mono CJK SC' },
46
50
  { regular: '/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc', bold: '/usr/share/fonts/google-noto-cjk/NotoSansCJK-Bold.ttc', family: 'Noto Sans Mono CJK SC' },
@@ -58,18 +62,20 @@ function ensureFontRegistered() {
58
62
  break;
59
63
  }
60
64
  }
61
- // 3. Latin monospace — DejaVu/Liberation cover dingbats (❯, ✓, etc.) and
62
- // most box-drawing/geometric symbols not in CJK font.
65
+ // 3. Latin monospace — DejaVu/Liberation/JetBrains Mono cover dingbats (❯,
66
+ // ✓, etc.) and most box-drawing/geometric symbols not in CJK font.
63
67
  const latinCandidates = [
68
+ [join(userFontDir, 'JetBrainsMono-Regular.ttf'), 'JetBrains Mono'],
64
69
  ['/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf', 'DejaVu Sans Mono'],
65
70
  ['/usr/share/fonts/dejavu/DejaVuSansMono.ttf', 'DejaVu Sans Mono'],
66
71
  ['/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf', 'Liberation Mono'],
67
72
  ['/usr/share/fonts/liberation/LiberationMono-Regular.ttf', 'Liberation Mono'],
73
+ ['/usr/share/fonts/truetype/jetbrains-mono/JetBrainsMono-Regular.ttf', 'JetBrains Mono'],
68
74
  ];
69
75
  for (const [p, name] of latinCandidates) {
70
76
  if (tryRegister(p)) {
71
- // Best-effort bold companion
72
- tryRegister(p.replace(/Regular\.ttf$/, 'Bold.ttf'));
77
+ // Best-effort bold companion. Same dir, replace -Regular with -Bold.
78
+ tryRegister(p.replace(/-Regular\.ttf$/, '-Bold.ttf'));
73
79
  tryRegister(p.replace(/SansMono\.ttf$/, 'SansMono-Bold.ttf'));
74
80
  fontFamilyChain.push(name);
75
81
  break;
@@ -78,6 +84,7 @@ function ensureFontRegistered() {
78
84
  // 4. Color emoji — Noto Color Emoji on Linux, Apple Color Emoji on macOS.
79
85
  // skia handles CBDT/CBLC + COLR/CPAL color formats.
80
86
  const emojiCandidates = [
87
+ [join(userFontDir, 'NotoColorEmoji.ttf'), 'Noto Color Emoji'],
81
88
  ['/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf', 'Noto Color Emoji'],
82
89
  ['/usr/share/fonts/noto/NotoColorEmoji.ttf', 'Noto Color Emoji'],
83
90
  ['/usr/share/fonts/google-noto-emoji/NotoColorEmoji.ttf', 'Noto Color Emoji'],