handmux 0.9.1 → 0.11.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.
- package/README.md +7 -7
- package/README.zh-CN.md +7 -7
- package/bin/handmux.js +58 -6
- package/package.json +22 -4
- package/public/assets/index-9fmzTpZ-.js +159 -0
- package/public/assets/index-CJZ28YE2.css +32 -0
- package/public/index.html +2 -2
- package/src/cli/cloudflared.js +2 -2
- package/src/cli/cpolarUrl.js +23 -0
- package/src/cli/drivers.js +38 -0
- package/src/cli/i18n/en.js +132 -34
- package/src/cli/i18n/zh.js +132 -34
- package/src/cli/natappUrl.js +11 -0
- package/src/cli/options.js +23 -1
- package/src/cli/prompt.js +17 -0
- package/src/cli/setupWizard.js +420 -90
- package/src/cli/tunnelClients.js +62 -0
- package/src/cli/updateCheck.js +37 -18
- package/src/cli/urlHost.js +12 -0
- package/src/docs.js +9 -3
- package/src/httpApi.js +20 -3
- package/public/assets/index-CrMq7ZZv.js +0 -159
- package/public/assets/index-y69e8WoM.css +0 -32
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ handmux is more than a terminal on your phone. It puts the *same* live **tmux**
|
|
|
16
16
|
<em>Real phone browser, real panes — say what you need and Claude Code writes it, then tap a filename to preview (left); a push pings you when needed, and you review the git repo & each agent's usage (right).</em>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
|
-
**[📖 Docs](https://handmux.com/docs
|
|
19
|
+
**[📖 Docs](https://handmux.com/docs)** · **[📝 Changelog](CHANGELOG.md)** · **[📦 npm](https://www.npmjs.com/package/handmux)**
|
|
20
20
|
|
|
21
21
|
## Quick start · about a minute
|
|
22
22
|
|
|
@@ -35,7 +35,7 @@ Want to reach it from **anywhere**? Add one flag for a free public HTTPS link:
|
|
|
35
35
|
handmux start --tunnel cloudflare # instant public URL (cloudflared auto-installed)
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
> Tunnel types, self-hosting, Windows/WSL2, and the full command & flag reference → see the **[docs](https://handmux.com/docs
|
|
38
|
+
> Tunnel types, self-hosting, Windows/WSL2, and the full command & flag reference → see the **[docs](https://handmux.com/docs)**.
|
|
39
39
|
|
|
40
40
|
## Why handmux
|
|
41
41
|
|
|
@@ -59,19 +59,19 @@ handmux start --tunnel cloudflare # instant public URL (cloudflared auto-insta
|
|
|
59
59
|
|
|
60
60
|
## Networking: one decision
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
No tunnel by default — the phone connects **straight to your own computer**, nothing exposed and no middleman. To reach it from outside, just ask: **does your machine already have a public address?**
|
|
63
63
|
|
|
64
64
|
- **Yes** (cloud box / public IP / forwarded port) — no tunnel needed, connect directly; fastest and most private.
|
|
65
|
-
- **No** — open a tunnel: `cloudflare` (zero-config, up in seconds, but its public
|
|
65
|
+
- **No** — open a tunnel. Each runs on **your own free third-party account** — handmux just wires it up and operates no relay of its own: `cloudflare` (zero-config, up in seconds, but its public edge can be slow or unreliable in some regions) · `cloudflare-named` (your own domain, steadier) · `natapp` / `cpolar` (domestic providers that stay reachable inside mainland China) · `ssh` self-hosted (through your own server).
|
|
66
66
|
|
|
67
|
-
> Tunnel config, server-side reverse proxy, autostart, voice/push credentials, and port previews → see the **[docs](https://handmux.com/docs
|
|
67
|
+
> Tunnel config, server-side reverse proxy, autostart, voice/push credentials, and port previews → see the **[docs](https://handmux.com/docs)**.
|
|
68
68
|
|
|
69
69
|
## Requirements
|
|
70
70
|
|
|
71
|
-
Your computer needs **Node ≥ 18** and **tmux ≥ 3.0**; the phone just needs a browser. On **Windows**, run it inside **WSL2** (a real Linux kernel + real tmux) — see the [docs](https://handmux.com/docs
|
|
71
|
+
Your computer needs **Node ≥ 18** and **tmux ≥ 3.0**; the phone just needs a browser. On **Windows**, run it inside **WSL2** (a real Linux kernel + real tmux) — see the [docs](https://handmux.com/docs#windows).
|
|
72
72
|
|
|
73
73
|
## More
|
|
74
74
|
|
|
75
|
-
**[📖 Docs](https://handmux.com/docs
|
|
75
|
+
**[📖 Docs](https://handmux.com/docs)** · **[📝 Changelog](CHANGELOG.md)** · **[🔒 Security](SECURITY.md)** · License **AGPL-3.0**
|
|
76
76
|
|
|
77
77
|
Found a security issue? Please report it privately (see [SECURITY.md](SECURITY.md)), not via a public issue.
|
package/README.zh-CN.md
CHANGED
|
@@ -16,7 +16,7 @@ handmux 不只是把终端搬上手机。它把你电脑上**正跑着的 tmux
|
|
|
16
16
|
<em>真实手机浏览器、真实 pane——左:说出需求,Claude Code 直接写好,点文件名即可预览;右:需要你时推送提醒,查看 git 仓库与各 agent 用量。</em>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
|
-
**[📖 文档](https://handmux.com/docs
|
|
19
|
+
**[📖 文档](https://handmux.com/docs)** · **[📝 更新日志](CHANGELOG.md)** · **[📦 npm](https://www.npmjs.com/package/handmux)**
|
|
20
20
|
|
|
21
21
|
## 快速上手 · 约一分钟
|
|
22
22
|
|
|
@@ -35,7 +35,7 @@ handmux start # 跑起来 —— 仅本机 / 同 wifi,不对外暴露
|
|
|
35
35
|
handmux start --tunnel cloudflare # 即时公网地址(自动装 cloudflared)
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
> 隧道类型、自建、Windows/WSL2、完整命令与参数 → 见 **[文档](https://handmux.com/docs
|
|
38
|
+
> 隧道类型、自建、Windows/WSL2、完整命令与参数 → 见 **[文档](https://handmux.com/docs)**。
|
|
39
39
|
|
|
40
40
|
## 为什么是 handmux
|
|
41
41
|
|
|
@@ -59,19 +59,19 @@ handmux start --tunnel cloudflare # 即时公网地址(自动装 cloudflared)
|
|
|
59
59
|
|
|
60
60
|
## 联网:一句话决策
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
默认不开隧道——手机**直连你自己的电脑**,什么都不暴露、也没有中间人。想从外面连,只问一句:**电脑有没有公网地址?**
|
|
63
63
|
|
|
64
64
|
- **有**(云主机 / 公网 IP / 已端口转发)—— 不用隧道,直接连,最快也最私密。
|
|
65
|
-
- **没有** ——
|
|
65
|
+
- **没有** —— 开一条隧道。每条都跑在**你自己的免费第三方账号**上,handmux 只负责接通、自身不设中转:`cloudflare`(零配置秒通,但公共边缘在国内常不稳)· `cloudflare-named`(你的域名,更稳)· `natapp` / `cpolar`(国内厂商,大陆境内可达)· `ssh` 自建(接你自己的服务器)。
|
|
66
66
|
|
|
67
|
-
> 隧道配置、服务端反向代理、开机自启、语音 / 推送凭证、端口预览等细节 → 见 **[文档](https://handmux.com/docs
|
|
67
|
+
> 隧道配置、服务端反向代理、开机自启、语音 / 推送凭证、端口预览等细节 → 见 **[文档](https://handmux.com/docs)**。
|
|
68
68
|
|
|
69
69
|
## 环境要求
|
|
70
70
|
|
|
71
|
-
电脑需 **Node ≥ 18** 与 **tmux ≥ 3.0**;手机只要浏览器。**Windows** 请装进 **WSL2**(真 Linux 内核 + 真 tmux)——见 [文档](https://handmux.com/docs
|
|
71
|
+
电脑需 **Node ≥ 18** 与 **tmux ≥ 3.0**;手机只要浏览器。**Windows** 请装进 **WSL2**(真 Linux 内核 + 真 tmux)——见 [文档](https://handmux.com/docs#windows)。
|
|
72
72
|
|
|
73
73
|
## 更多
|
|
74
74
|
|
|
75
|
-
**[📖 文档](https://handmux.com/docs
|
|
75
|
+
**[📖 文档](https://handmux.com/docs)** · **[📝 更新日志](CHANGELOG.md)** · **[🔒 安全](SECURITY.md)** · 许可证 **AGPL-3.0**
|
|
76
76
|
|
|
77
77
|
发现安全问题请私下报告(见 [SECURITY.md](SECURITY.md)),别开公开 issue。
|
package/bin/handmux.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// let you try variations for one run (e.g. --tunnel cloudflare).
|
|
8
8
|
// handmux setup — the one place to configure persistently. Interactive; writes ~/.handmux/config.json
|
|
9
9
|
// (name, tunnel, push, voice). Re-run it to change anything.
|
|
10
|
-
// `start` reads that file; with no file it uses defaults. Precedence: flag > file >
|
|
11
|
-
// overrides one value for one run and never persists. Advanced: `--config PATH` (a different file, for
|
|
10
|
+
// `start` reads that file; with no file it uses defaults. Precedence: flag > file > env (HANDMUX_*) >
|
|
11
|
+
// default — a flag overrides one value for one run and never persists. Advanced: `--config PATH` (a different file, for
|
|
12
12
|
// dev / multiple configs), `handmux config` (show what's in effect and where each value came from).
|
|
13
13
|
//
|
|
14
14
|
// Tunnels: `none` (LAN only, nothing exposed) · `cloudflare` (instant random https URL) ·
|
|
@@ -26,6 +26,7 @@ import { renderCompactQr } from '../src/cli/qr.js';
|
|
|
26
26
|
import { supervise, bareUrl, publicUrlWithToken } from '../src/cli/supervisor.js';
|
|
27
27
|
import { resolveCloudflared } from '../src/cli/cloudflared.js';
|
|
28
28
|
import { resolveTunlite, checkSshAuth } from '../src/cli/tunlite.js';
|
|
29
|
+
import { resolveNatapp, resolveCpolar } from '../src/cli/tunnelClients.js';
|
|
29
30
|
import { installService, uninstallService } from '../src/cli/service.js';
|
|
30
31
|
import { checkTmux, MIN_TMUX, tmuxInstallHint } from '../src/cli/tmuxVersion.js';
|
|
31
32
|
import { readState, clearState, isAlive, pocketHome, logPath, configPath, claudeStatePath } from '../src/cli/state.js';
|
|
@@ -115,9 +116,24 @@ async function preflightSsh(cfg) {
|
|
|
115
116
|
throw new Error(t('ssh.notSetup', { bin: cfg.tunliteBin, host: cfg.sshHost }));
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
// natapp/cpolar preflight: resolve the client binary (cpolar auto-downloads; natapp must be pre-installed),
|
|
120
|
+
// and for cpolar seed the authtoken into its own config so the detached `cpolar http` authenticates. Throws
|
|
121
|
+
// a friendly message the caller prints.
|
|
122
|
+
async function preflightNgrok(cfg) {
|
|
123
|
+
if (cfg.tunnel === 'natapp') {
|
|
124
|
+
cfg.natappBin = resolveNatapp(HOME);
|
|
125
|
+
} else {
|
|
126
|
+
cfg.cpolarBin = await resolveCpolar(HOME);
|
|
127
|
+
if (spawnSync(cfg.cpolarBin, ['authtoken', cfg.authtoken], { stdio: 'ignore' }).status !== 0) {
|
|
128
|
+
throw new Error(t('client.cpolarAuthFail'));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
118
133
|
async function main() {
|
|
119
134
|
switch (command) {
|
|
120
135
|
case 'start': return start();
|
|
136
|
+
case 'open': return openCmd();
|
|
121
137
|
case 'stop': stop(); return;
|
|
122
138
|
case 'restart': { stop(); await sleep(600); return start(); }
|
|
123
139
|
case 'status': return status();
|
|
@@ -227,6 +243,10 @@ async function start() {
|
|
|
227
243
|
try { await preflightSsh(cfg); }
|
|
228
244
|
catch (e) { console.error(t('err.generic', { msg: e.message })); process.exit(1); }
|
|
229
245
|
}
|
|
246
|
+
if (cfg.tunnel === 'natapp' || cfg.tunnel === 'cpolar') {
|
|
247
|
+
try { await preflightNgrok(cfg); }
|
|
248
|
+
catch (e) { console.error(t('err.generic', { msg: e.message })); process.exit(1); }
|
|
249
|
+
}
|
|
230
250
|
|
|
231
251
|
if (cfg.foreground) {
|
|
232
252
|
supervise(cfg, { home: HOME });
|
|
@@ -304,6 +324,10 @@ async function serviceInstall() {
|
|
|
304
324
|
console.error(t('err.generic', { msg: t('ssh.notSetup', { bin: cfg.tunliteBin, host: cfg.sshHost }) })); process.exit(1);
|
|
305
325
|
}
|
|
306
326
|
}
|
|
327
|
+
if (cfg.tunnel === 'natapp' || cfg.tunnel === 'cpolar') {
|
|
328
|
+
try { await preflightNgrok(cfg); }
|
|
329
|
+
catch (e) { console.error(t('err.generic', { msg: e.message })); process.exit(1); }
|
|
330
|
+
}
|
|
307
331
|
const payload = Buffer.from(JSON.stringify(cfg)).toString('base64');
|
|
308
332
|
const args = [process.execPath, SELF, '__supervise', '--payload', payload];
|
|
309
333
|
try { installService(args, { home: HOME }); }
|
|
@@ -313,8 +337,14 @@ async function serviceInstall() {
|
|
|
313
337
|
|
|
314
338
|
async function setupCmd() {
|
|
315
339
|
const target = flags.config ? path.resolve(flags.config) : configPath(HOME);
|
|
316
|
-
|
|
317
|
-
|
|
340
|
+
// Is an instance already up? Then the run-action reads "Save & restart" and applying means a real restart
|
|
341
|
+
// (a running supervisor won't pick up the new config on its own). Captured before the interactive setup so
|
|
342
|
+
// the label and the action agree; stop() below is a safe no-op if it died meanwhile.
|
|
343
|
+
const st = readState(HOME);
|
|
344
|
+
const running = !!(st && isAlive(st.supervisorPid));
|
|
345
|
+
const res = await runSetup({ home: HOME, target, running });
|
|
346
|
+
if (!res) { process.exit(2); }
|
|
347
|
+
const { cfg, start: doStart } = res; // hub's "save & start/restart" carries the intent — no separate confirm
|
|
318
348
|
// Offer to enable the inbox hooks when an agent is present but not yet wired (Claude 'absent', or Codex
|
|
319
349
|
// 'absent'). installAgentHooks() then wires every present agent (idempotent for any already installed).
|
|
320
350
|
const offerHooks = hooksStatus(HOME) === 'absent' || codexHooksStatus(HOME) === 'absent';
|
|
@@ -322,8 +352,12 @@ async function setupCmd() {
|
|
|
322
352
|
installAgentHooks();
|
|
323
353
|
}
|
|
324
354
|
await maybeOfferStatusLine();
|
|
325
|
-
if (
|
|
326
|
-
|
|
355
|
+
if (doStart) {
|
|
356
|
+
Object.assign(flags, cfg);
|
|
357
|
+
if (running) { stop(); await sleep(600); } // restart into the new config
|
|
358
|
+
return start();
|
|
359
|
+
}
|
|
360
|
+
console.log(t(running ? 'setup.laterRestart' : 'setup.later'));
|
|
327
361
|
}
|
|
328
362
|
|
|
329
363
|
// Offer to enable the Claude statusLine usage capturer — it feeds the phone Usage page's 5h/weekly bars
|
|
@@ -469,7 +503,25 @@ async function maybeQr(url, st) {
|
|
|
469
503
|
} catch { /* no qrcode-terminal — URL alone is fine */ }
|
|
470
504
|
}
|
|
471
505
|
|
|
506
|
+
// `open` is deliberately DECOUPLED from the server lifecycle: it never starts/stops anything. It's the
|
|
507
|
+
// desk-side quick attach — `handmux open foo` attaches session foo (creating it if missing), including
|
|
508
|
+
// sessions that were created from the phone. Inside tmux it refuses (don't nest tmux in tmux).
|
|
509
|
+
function openCmd() {
|
|
510
|
+
const name = process.argv[3];
|
|
511
|
+
if (!name || name.startsWith('-')) { console.error(t('open.usage')); process.exit(2); }
|
|
512
|
+
if (process.env.TMUX) { console.error(t('open.insideTmux')); process.exit(1); }
|
|
513
|
+
const tmux = checkTmux();
|
|
514
|
+
if (!tmux.present) {
|
|
515
|
+
console.error(t('tmux.notFound'));
|
|
516
|
+
console.error(t('tmux.install', { hint: tmuxInstallHint() }));
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
const r = spawnSync('tmux', ['new-session', '-A', '-s', name], { stdio: 'inherit' });
|
|
520
|
+
process.exit(r.status ?? 0);
|
|
521
|
+
}
|
|
522
|
+
|
|
472
523
|
function help() {
|
|
524
|
+
if (process.argv[3] === 'flags') { console.log(t('help.flags')); return; }
|
|
473
525
|
console.log(t('help.body'));
|
|
474
526
|
}
|
|
475
527
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "handmux",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A mobile vibe-coding cockpit — built on tmux: drive your live session, Claude Code
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "A mobile vibe-coding cockpit — built on tmux: drive your live session, Claude Code / Codex — anything a terminal can run — from your phone.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"author": "yuanyuanzijin",
|
|
@@ -13,7 +13,14 @@
|
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/handmux/handmux/issues"
|
|
15
15
|
},
|
|
16
|
-
"keywords": [
|
|
16
|
+
"keywords": [
|
|
17
|
+
"tmux",
|
|
18
|
+
"mobile",
|
|
19
|
+
"terminal",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"remote",
|
|
22
|
+
"pwa"
|
|
23
|
+
],
|
|
17
24
|
"engines": {
|
|
18
25
|
"node": ">=18"
|
|
19
26
|
},
|
|
@@ -36,6 +43,7 @@
|
|
|
36
43
|
"test": "vitest run"
|
|
37
44
|
},
|
|
38
45
|
"dependencies": {
|
|
46
|
+
"@clack/prompts": "^1.7.0",
|
|
39
47
|
"busboy": "^1.6.0",
|
|
40
48
|
"express": "^4.19.2",
|
|
41
49
|
"tunlite": "^0.10.0",
|
|
@@ -47,5 +55,15 @@
|
|
|
47
55
|
"devDependencies": {
|
|
48
56
|
"supertest": "^7.0.0",
|
|
49
57
|
"vitest": "^2.0.0"
|
|
50
|
-
}
|
|
58
|
+
},
|
|
59
|
+
"whatsNew": [
|
|
60
|
+
{"version":"0.11.0","date":"2026-07-08","zh":"国内可用隧道 natapp/cpolar · 配置向导重做","en":"China-usable tunnels · setup redesigned"},
|
|
61
|
+
{"version":"0.10.0","date":"2026-07-06","zh":"升级前预览新功能 · 电脑 handmux open 接管","en":"Preview an update before installing · handmux open"},
|
|
62
|
+
{"version":"0.9.1","date":"2026-07-06","zh":"「添加到主屏」引导","en":"\"Add to Home Screen\" coach"},
|
|
63
|
+
{"version":"0.9.0","date":"2026-07-05","zh":"聊天输入框多行重做","en":"Multi-line chat composer redone"},
|
|
64
|
+
{"version":"0.8.0","date":"2026-07-05","zh":"快捷栏可自定义 · 上传体验重做","en":"Customizable quick-bars · upload redone"},
|
|
65
|
+
{"version":"0.7.0","date":"2026-07-03","zh":"新增「用量」页","en":"New \"Usage\" page"},
|
|
66
|
+
{"version":"0.6.0","date":"2026-07-02","zh":"新增 Codex 支持","en":"Codex support added"},
|
|
67
|
+
{"version":"0.5.0","date":"2026-06-28","zh":"首个公开版 · 多语言界面","en":"First public release · multi-language UI"}
|
|
68
|
+
]
|
|
51
69
|
}
|