handmux 0.10.0 → 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 +2 -2
- package/README.zh-CN.md +2 -2
- package/bin/handmux.js +40 -6
- package/package.json +11 -2
- package/public/assets/{index-8QjRPrZL.js → index-9fmzTpZ-.js} +51 -51
- package/public/index.html +1 -1
- 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 +129 -34
- package/src/cli/i18n/zh.js +129 -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/urlHost.js +12 -0
package/README.md
CHANGED
|
@@ -59,10 +59,10 @@ 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
67
|
> Tunnel config, server-side reverse proxy, autostart, voice/push credentials, and port previews → see the **[docs](https://handmux.com/docs)**.
|
|
68
68
|
|
package/README.zh-CN.md
CHANGED
|
@@ -59,10 +59,10 @@ 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
67
|
> 隧道配置、服务端反向代理、开机自启、语音 / 推送凭证、端口预览等细节 → 见 **[文档](https://handmux.com/docs)**。
|
|
68
68
|
|
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,6 +116,20 @@ 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();
|
|
@@ -228,6 +243,10 @@ async function start() {
|
|
|
228
243
|
try { await preflightSsh(cfg); }
|
|
229
244
|
catch (e) { console.error(t('err.generic', { msg: e.message })); process.exit(1); }
|
|
230
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
|
+
}
|
|
231
250
|
|
|
232
251
|
if (cfg.foreground) {
|
|
233
252
|
supervise(cfg, { home: HOME });
|
|
@@ -305,6 +324,10 @@ async function serviceInstall() {
|
|
|
305
324
|
console.error(t('err.generic', { msg: t('ssh.notSetup', { bin: cfg.tunliteBin, host: cfg.sshHost }) })); process.exit(1);
|
|
306
325
|
}
|
|
307
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
|
+
}
|
|
308
331
|
const payload = Buffer.from(JSON.stringify(cfg)).toString('base64');
|
|
309
332
|
const args = [process.execPath, SELF, '__supervise', '--payload', payload];
|
|
310
333
|
try { installService(args, { home: HOME }); }
|
|
@@ -314,8 +337,14 @@ async function serviceInstall() {
|
|
|
314
337
|
|
|
315
338
|
async function setupCmd() {
|
|
316
339
|
const target = flags.config ? path.resolve(flags.config) : configPath(HOME);
|
|
317
|
-
|
|
318
|
-
|
|
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
|
|
319
348
|
// Offer to enable the inbox hooks when an agent is present but not yet wired (Claude 'absent', or Codex
|
|
320
349
|
// 'absent'). installAgentHooks() then wires every present agent (idempotent for any already installed).
|
|
321
350
|
const offerHooks = hooksStatus(HOME) === 'absent' || codexHooksStatus(HOME) === 'absent';
|
|
@@ -323,8 +352,12 @@ async function setupCmd() {
|
|
|
323
352
|
installAgentHooks();
|
|
324
353
|
}
|
|
325
354
|
await maybeOfferStatusLine();
|
|
326
|
-
if (
|
|
327
|
-
|
|
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'));
|
|
328
361
|
}
|
|
329
362
|
|
|
330
363
|
// Offer to enable the Claude statusLine usage capturer — it feeds the phone Usage page's 5h/weekly bars
|
|
@@ -488,6 +521,7 @@ function openCmd() {
|
|
|
488
521
|
}
|
|
489
522
|
|
|
490
523
|
function help() {
|
|
524
|
+
if (process.argv[3] === 'flags') { console.log(t('help.flags')); return; }
|
|
491
525
|
console.log(t('help.body'));
|
|
492
526
|
}
|
|
493
527
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "handmux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
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",
|
|
@@ -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",
|
|
@@ -49,6 +57,7 @@
|
|
|
49
57
|
"vitest": "^2.0.0"
|
|
50
58
|
},
|
|
51
59
|
"whatsNew": [
|
|
60
|
+
{"version":"0.11.0","date":"2026-07-08","zh":"国内可用隧道 natapp/cpolar · 配置向导重做","en":"China-usable tunnels · setup redesigned"},
|
|
52
61
|
{"version":"0.10.0","date":"2026-07-06","zh":"升级前预览新功能 · 电脑 handmux open 接管","en":"Preview an update before installing · handmux open"},
|
|
53
62
|
{"version":"0.9.1","date":"2026-07-06","zh":"「添加到主屏」引导","en":"\"Add to Home Screen\" coach"},
|
|
54
63
|
{"version":"0.9.0","date":"2026-07-05","zh":"聊天输入框多行重做","en":"Multi-line chat composer redone"},
|