botmux 3.18.9 → 3.18.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  <p align="center">
8
8
  <a href="https://www.npmjs.com/package/botmux"><img src="https://img.shields.io/npm/v/botmux.svg" alt="npm"></a>
9
- <img src="https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg" alt="Node >= 22">
9
+ <img src="https://img.shields.io/badge/binary-no%20Node%20required-brightgreen.svg" alt="self-contained binary, no Node required">
10
10
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT"></a>
11
11
  <a href="https://github.com/deepcoldy/botmux"><img src="https://img.shields.io/github/stars/deepcoldy/botmux.svg?style=social" alt="Stars"></a>
12
12
  </p>
@@ -39,11 +39,30 @@ A daemon watches Lark messages and spawns an isolated session process for each n
39
39
  > About 5 minutes: a single Lark QR scan in `botmux setup` creates the app, configures all permissions, and publishes a version in one flow (add `--no-open-platform-auto` to only create the app and skip the permission + publish automation, which you then complete manually; creating the app manually / pasting credentials is a separate option inside setup).
40
40
 
41
41
  ```bash
42
- npm install -g botmux # requires Node >= 22
42
+ curl -fsSL https://raw.githubusercontent.com/deepcoldy/botmux/master/install.sh | sh
43
43
  botmux setup # one scan to create the app → pick a CLI → pick a working dir (permissions + publish auto-configured)
44
44
  botmux start # start the daemon (botmux autostart enable for auto-start on boot)
45
45
  ```
46
46
 
47
+ > botmux ships as a **self-contained single-file binary** with its runtime embedded — **neither installing nor running it needs Node on the machine** (whatever the AI coding CLI you bridge needs is its own matter). It installs to `~/.botmux/bin/botmux` (override with `BOTMUX_INSTALL_DIR`), picks the right binary for your OS/arch, verifies its SHA-256, and adds `~/.botmux/bin` to the startup file your shell actually reads (zsh / bash / fish each get the correct one), so **a new terminal has the command**.
48
+ >
49
+ > Nothing native is compiled during install (no Python / node-gyp / compiler): the PTY is already inside the binary. Supported: linux / macOS × x64 / arm64, with musl builds selected automatically on Alpine and similar. **On Windows, install inside WSL2** — the daemon needs PTY / tmux / Unix signals and does not run on native Windows; WSL2 reports as linux and is a fully supported first-class environment. An unsupported platform, or a binary that cannot run on this host, **fails with an explicit error and leaves your existing install untouched** rather than leaving you with a command that won't start.
50
+ >
51
+ > To upgrade: `botmux upgrade` (replaces the binary in place), or just **re-run the curl command** — also an in-place upgrade, and it won't append a second PATH line.
52
+
53
+ <details>
54
+ <summary>Already living in the Node ecosystem? npm works too (same binary)</summary>
55
+
56
+ ```bash
57
+ npm install -g botmux # requires Node >= 22 to run the install itself
58
+ ```
59
+
60
+ The npm package carries **the same self-contained binary** (only the one matching your os/arch is installed); its postinstall points `~/.botmux/bin/botmux` at it and writes PATH the same way. So you end up with exactly **one** botmux version — no more "two Node versions each carrying their own global botmux, fighting each other / no idea which one I just updated".
61
+
62
+ The only difference is **who installs it and who upgrades it later**: the npm path needs Node ≥ 22 to run the install itself and hands upgrades back to `npm i -g botmux@latest`; the curl path never touches Node. Once running, the two are identical — same binary, same commands.
63
+
64
+ </details>
65
+
47
66
  Then DM the bot, or run `botmux dashboard` to create a group, and start chatting. Full steps (Lark international, manual permission / publish setup after `--no-open-platform-auto`, troubleshooting) are in the **[5-Minute Quickstart](https://deepcoldy.github.io/botmux/en/quickstart)**.
48
67
 
49
68
  ## Core Scenarios
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  <p align="center">
8
8
  <a href="https://www.npmjs.com/package/botmux"><img src="https://img.shields.io/npm/v/botmux.svg" alt="npm"></a>
9
- <img src="https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg" alt="Node >= 22">
9
+ <img src="https://img.shields.io/badge/binary-no%20Node%20required-brightgreen.svg" alt="self-contained binary, no Node required">
10
10
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT"></a>
11
11
  <a href="https://github.com/deepcoldy/botmux"><img src="https://img.shields.io/github/stars/deepcoldy/botmux.svg?style=social" alt="Stars"></a>
12
12
  </p>
@@ -39,27 +39,27 @@ Daemon 监听飞书消息,为每个新会话自动 spawn 一个独立的会话
39
39
  > 约 5 分钟:`botmux setup` 一次飞书扫码就连续建好应用、配全权限、发版(加 `--no-open-platform-auto` 则只建应用、跳过权限与发版的自动配置,之后需手动完成;手动创建 / 粘贴凭证是 setup 里的另一个选项)。
40
40
 
41
41
  ```bash
42
- npm install -g botmux # 需要 Node >= 22 装包本身
42
+ curl -fsSL https://raw.githubusercontent.com/deepcoldy/botmux/master/install.sh | sh
43
43
  botmux setup # 一次扫码建应用 → 选 CLI → 选工作目录(自动配权限 + 发版)
44
44
  botmux start # 启动 daemon(botmux autostart enable 设开机自启)
45
45
  ```
46
46
 
47
- > npm 包内已经带了对应平台的**自包含二进制**(按 os/arch 只装匹配的那一个),安装时会把 `~/.botmux/bin/botmux` 指向它。所以装完只有**一个** botmux 版本,不再出现「装了两个 Node 版本、各自带一份全局 botmux 互相打架 / 不知道更新了哪个」。把 `~/.botmux/bin` 放进 PATH 即可(安装日志会提示命令)。
47
+ > botmux 本体是一个**自包含单文件二进制**,运行时已嵌在里面——**装它和跑它都不需要机器上有 Node**(你要接的 AI 编程 CLI 自己需要什么另算)。装到 `~/.botmux/bin/botmux`(`BOTMUX_INSTALL_DIR` 可改),按 OS/arch 自动选对应二进制、校验 SHA-256,并把 `~/.botmux/bin` 写进你当前 shell 的启动文件(zsh / bash / fish 各写对的那个),**开个新终端就能用**。
48
48
  >
49
- > 安装过程**不编译任何原生模块**(不需要 Python / node-gyp / 编译器):要跑的 PTY 已经嵌在那个二进制里,npm 只是把它放到位。支持 linux / macOS × x64 / arm64;**Windows 请在 WSL2 里安装**(WSL 报告为 linux,是完整支持的一等环境)。不在支持列表里的平台会在安装时**明确报错**,而不是装上一个跑不起来的命令。
49
+ > 安装过程**不编译任何原生模块**(不需要 Python / node-gyp / 编译器):PTY 已经嵌在二进制里。支持 linux / macOS × x64 / arm64(Alpine 等 musl 环境自动选 musl 版);**Windows 请在 WSL2 里安装**(daemon 依赖 PTY / tmux / Unix 信号,原生 Windows 跑不了;WSL2 报告为 linux,是完整支持的一等环境)。平台不在列表里、或下下来的二进制在本机跑不起来,安装会**明确报错并保留原有版本**,而不是装上一个起不来的命令。
50
+ >
51
+ > 升级:`botmux upgrade`(原地换二进制),或**重跑一遍上面那条 curl 命令**——同样原地升级,不会重复往启动文件里追加 PATH。
50
52
 
51
53
  <details>
52
- <summary>不想装 Node?直接下单文件可执行(连装包都不需要 Node)</summary>
53
-
54
- 同一个自包含二进制也可以脱离 npm 直接下载,**自带运行时、不依赖机器上的任何 Node**。macOS / Linux:
54
+ <summary>已经在用 Node 生态?也可以走 npm(同一个二进制)</summary>
55
55
 
56
56
  ```bash
57
- curl -fsSL https://raw.githubusercontent.com/deepcoldy/botmux/master/install.sh | sh
58
- botmux setup
59
- botmux start
57
+ npm install -g botmux # 需要 Node >= 22 装包本身
60
58
  ```
61
59
 
62
- 装到 `~/.botmux/bin/botmux`(`BOTMUX_INSTALL_DIR` 可改),自动按 OS/arch 拉对应二进制并校验 SHA-256。命令用法与 npm 版完全一致。**Windows 请在 WSL2 里安装**(daemon 依赖 PTY / tmux / Unix 信号,原生 Windows 跑不了;WSL2 报告为 linux,完整支持)。
60
+ npm 包内带的是**同一个自包含二进制**(按 os/arch 只装匹配的那一个),postinstall 把 `~/.botmux/bin/botmux` 指向它并同样写 PATH。所以装完只有**一个** botmux 版本,不再出现「装了两个 Node 版本、各自带一份全局 botmux 互相打架 / 不知道更新了哪个」。
61
+
62
+ 区别只在**谁来装、以后谁来升**:npm 路径需要 Node ≥ 22 才能执行安装本身,升级交回 `npm i -g botmux@latest`;curl 路径全程不碰 Node。跑起来之后两者完全一致——同样的二进制、同样的命令。
63
63
 
64
64
  </details>
65
65
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botmux",
3
- "version": "3.18.9",
3
+ "version": "3.18.11",
4
4
  "description": "Bridge between IM platforms and AI coding CLIs — one topic, one CLI session with live streaming",
5
5
  "type": "module",
6
6
  "files": [
@@ -80,12 +80,12 @@
80
80
  },
81
81
  "optionalDependencies": {
82
82
  "@napi-rs/canvas": "^0.1.65",
83
- "botmux-darwin-arm64": "3.18.9",
84
- "botmux-darwin-x64": "3.18.9",
85
- "botmux-linux-arm64": "3.18.9",
86
- "botmux-linux-arm64-musl": "3.18.9",
87
- "botmux-linux-x64": "3.18.9",
88
- "botmux-linux-x64-musl": "3.18.9"
83
+ "botmux-darwin-arm64": "3.18.11",
84
+ "botmux-darwin-x64": "3.18.11",
85
+ "botmux-linux-arm64": "3.18.11",
86
+ "botmux-linux-arm64-musl": "3.18.11",
87
+ "botmux-linux-x64": "3.18.11",
88
+ "botmux-linux-x64-musl": "3.18.11"
89
89
  },
90
90
  "devDependencies": {
91
91
  "@midscene/web": "^1.7.6",
@@ -16,19 +16,49 @@
16
16
  *
17
17
  * ── THE GUARD (do not loosen this) ──────────────────────────────────────────────
18
18
  * We only write the launcher for a REAL global install. Empirically measured what
19
- * npm/pnpm expose to postinstall (not assumed — probed all three cases):
19
+ * each package manager exposes to postinstall (not assumed — probed every case):
20
20
  *
21
21
  * `npm i -g botmux` → npm_config_global === "true"
22
22
  * installed as someone's local dep → npm_config_global ABSENT
23
23
  * `pnpm install` INSIDE the botmux repo → npm_config_global ABSENT
24
+ * `bun add -g botmux` → npm_config_global ABSENT (see below)
24
25
  *
25
26
  * The third case is the dangerous one: a repo-local `pnpm install` DOES run the
26
27
  * root package's postinstall. So a `!== "false"` style check would fire during
27
28
  * ordinary development and rewrite ~/.botmux/bin/botmux — hijacking the global
28
29
  * launcher of whatever fleet shares that HOME (on the dev box that is ~50 live
29
- * daemons). Hence the guard is a STRICT `=== "true"`, and there is a second,
30
+ * daemons). Hence the env check stays a STRICT `=== "true"`, and there is a second,
30
31
  * independent bail-out when we can see we are inside the source checkout.
31
32
  *
33
+ * ⚠️ THE FOURTH CASE IS WHY THE ENV CHECK ALONE IS NOT ENOUGH. Bun does pass a few
34
+ * npm_* vars to lifecycle scripts — MEASURED with a probe package whose postinstall
35
+ * dumped its own env, the complete set is:
36
+ *
37
+ * BUN_INSTALL, BUN_WHICH_IGNORE_CWD, npm_config_user_agent, npm_execpath,
38
+ * npm_node_execpath
39
+ *
40
+ * — but `npm_config_global` is NOT among them, and that is the one this guard read.
41
+ * So for a perfectly real `bun add -g botmux` the check failed and this script
42
+ * exited 0 without writing anything. MEASURED end to end: `.bun/bin/` empty, no
43
+ * launcher, the platform binary sitting in the download cache, i.e. the user had NO
44
+ * `botmux` command at all. Worse, the obvious workaround did NOT help: `bun pm -g
45
+ * trust botmux` made bun report `1 script ran` while this script still wrote
46
+ * nothing, because the env check had already failed.
47
+ *
48
+ * ⚠️ TWO SEPARATE LAYERS — do not conflate them. (a) bun BLOCKS the script by
49
+ * default (`Blocked N postinstalls`); `bun pm trust` lifts that. (b) even once it
50
+ * runs, this guard killed it. Fixing (b) is what makes `bun pm trust` an actually
51
+ * working workaround. pnpm 10/11 is a different layer again: `onlyBuiltDependencies`
52
+ * means the script does not run at all, so this fix alone does not rescue pnpm —
53
+ * that needs approve-builds/onlyBuiltDependencies or a different mechanism.
54
+ *
55
+ * So the global check is: the env says global (npm/yarn), OR THE INSTALL LOCATION
56
+ * ITSELF says global. The layout list is INLINED in `locationSaysGlobal` below —
57
+ * see the ⚠️ notes there for why importing the repo's classifier from `dist/` was
58
+ * dead code in the published package, and why the inlined version is deliberately
59
+ * stricter on Windows. A source checkout matches no global layout (verified), so
60
+ * this cannot reopen the repo-local hijack that guard 2 also covers.
61
+ *
32
62
  * ── FAIL HARD WHEN THERE IS NO BINARY ──────────────────────────────────────────
33
63
  * This used to warn and exit 0, because `bin: {botmux: "dist/cli.js"}` gave every
34
64
  * failure a Node fallback to land on. That fallback is gone (it forced the main
@@ -102,10 +132,64 @@ function isMuslLinux() {
102
132
  return false;
103
133
  }
104
134
 
105
- // ── Guard 1: only a real `npm i -g` (strict equality; see header) ───────────────
106
- if (process.env.npm_config_global !== 'true') {
135
+ // ── Guard 1: only a real global install ─────────────────────────────────────────
136
+ // Two independent positive signals (see header). The env check is unchanged and
137
+ // still strict; the location check is what makes `bun add -g` / `pnpm add -g` work,
138
+ // since those provide no npm_config_* at all.
139
+ const here = dirname(fileURLToPath(import.meta.url));
140
+ const pkgRoot = dirname(here); // scripts/ -> package root
141
+
142
+ /**
143
+ * Does the install LOCATION say this is a package-manager-owned global tree?
144
+ *
145
+ * ⚠️ WHY THIS IS INLINED INSTEAD OF IMPORTING THE REPO'S CLASSIFIER. The first
146
+ * version did `await import('./dist/utils/global-install.js')` to reuse
147
+ * `detectGlobalInstallManager`. That was DEAD CODE in the published package: #1115
148
+ * removed `dist/` from `package.json` `files`, so the import always ENOENTs, the
149
+ * catch returned false, and the guard silently fell back to env-only — leaving
150
+ * `bun add -g botmux` exactly as broken as before. MEASURED, not reasoned:
151
+ *
152
+ * git archive origin/master | tar x && npm pack
153
+ * tar tzf botmux-*.tgz | grep -c '^package/dist/' → 0
154
+ *
155
+ * and end to end, a real `bun add -g <that tarball>` then running this script the
156
+ * way bun does: exit 0, no launcher. This file is a standalone script with no build
157
+ * step, and neither `dist/` nor `src/` is published, so being SELF-CONTAINED is its
158
+ * natural form — inlining is not a compromise here.
159
+ *
160
+ * ⚠️ AND IT IS DELIBERATELY STRICTER THAN `detectGlobalInstallManager`. That
161
+ * function ends with `platform === 'win32' ? 'npm' : 'unknown'`, so on Windows it
162
+ * cannot tell a global install from a LOCAL dependency — MEASURED:
163
+ *
164
+ * detectGlobalInstallManager('C:/projects/myapp/node_modules/botmux', 'win32') → 'npm'
165
+ *
166
+ * Harmless where it is used today (a wrong `npm i -g` merely reinstalls), but in
167
+ * THIS file it would mean a Windows local dependency repoints the shared
168
+ * `~/.botmux/bin/botmux` — the exact hijack guard 1 exists to prevent. So the list
169
+ * below recognises only KNOWN GLOBAL layouts and has no platform fallback. Windows
170
+ * npm globals are unaffected: they arrive with `npm_config_global=true` and never
171
+ * reach this check.
172
+ *
173
+ * The layout patterns mirror `detectGlobalInstallManager`'s; a source guard in
174
+ * test/npm-binary-distribution.test.ts pins the two in agreement over a matrix of
175
+ * real global paths, so the duplication cannot drift unnoticed.
176
+ */
177
+ function locationSaysGlobal(root) {
178
+ const r = root.replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
179
+ // Every global layout below ends at the main package; anything else is not ours.
180
+ if (!r.endsWith('/node_modules/botmux')) return false;
181
+ return r.endsWith('/lib/node_modules/botmux') // npm, POSIX
182
+ || r.includes('/.bun/install/global/node_modules/botmux') // bun
183
+ || r.includes('/bun/install/global/node_modules/botmux') // bun, custom BUN_INSTALL
184
+ || r.includes('/.pnpm/') // pnpm virtual store
185
+ || /\/pnpm\/global\/[^/]+\/node_modules\/botmux$/.test(r) // pnpm 9 global
186
+ || /\/pnpm\/global\/v\d+\/[^/]+\/node_modules\/botmux$/.test(r) // pnpm 11 global
187
+ || /\/pnpm\/store\/v\d+\/links\/@\/botmux\//.test(r); // pnpm 11 store links
188
+ }
189
+
190
+ if (process.env.npm_config_global !== 'true' && !locationSaysGlobal(pkgRoot)) {
107
191
  // Silent: this is the overwhelmingly common case (dev installs, transitive
108
- // installs). Noise here would appear on every `pnpm install` in the repo.
192
+ // installs). Noise here would appear on every `bun install` in the repo.
109
193
  process.exit(0);
110
194
  }
111
195
 
@@ -113,8 +197,6 @@ if (process.env.npm_config_global !== 'true') {
113
197
  // Defence in depth for guard 1. If the package directory we are running from is a
114
198
  // git checkout of botmux (has .git and src/), this is a developer environment, not
115
199
  // an installed package — the launcher must not be repointed.
116
- const here = dirname(fileURLToPath(import.meta.url));
117
- const pkgRoot = dirname(here); // scripts/ -> package root
118
200
  if (existsSync(join(pkgRoot, '.git')) && existsSync(join(pkgRoot, 'src'))) {
119
201
  process.exit(0);
120
202
  }