handmux 0.17.8 → 0.18.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 (50) hide show
  1. package/README.md +18 -2
  2. package/README.zh-CN.md +18 -2
  3. package/bin/handmux.js +25 -1
  4. package/package.json +5 -4
  5. package/public/assets/index-CvNuQ-bC.js +330 -0
  6. package/public/assets/index-DjJf1FX9.css +32 -0
  7. package/public/index.html +2 -2
  8. package/src/agents/claude.js +1 -0
  9. package/src/agents/codex.js +1 -0
  10. package/src/agents/scanUtils.js +3 -4
  11. package/src/claudeEvents.js +23 -6
  12. package/src/cli/i18n/en.js +87 -2
  13. package/src/cli/i18n/zh.js +87 -2
  14. package/src/cli/options.js +23 -5
  15. package/src/cli/prompt.js +1 -1
  16. package/src/cli/setupModel.js +11 -2
  17. package/src/cli/setupWizard.js +19 -18
  18. package/src/cli/shortcutEditor.js +288 -0
  19. package/src/cli/supervisor.js +1 -0
  20. package/src/cli/workspaceCmd.js +300 -0
  21. package/src/config.js +3 -0
  22. package/src/httpApi.js +6 -1
  23. package/src/internalPreviewGateway.js +487 -0
  24. package/src/keyNames.js +10 -0
  25. package/src/orphans.js +3 -2
  26. package/src/routes/sessions.js +13 -1
  27. package/src/routes/system.js +13 -2
  28. package/src/routes/terminal.js +3 -13
  29. package/src/routes/workspace.js +267 -0
  30. package/src/server.js +46 -5
  31. package/src/shortcutConfig.js +75 -0
  32. package/src/tmux/commands.js +17 -17
  33. package/src/tmux/format.js +45 -0
  34. package/src/workspace/agentRunner.js +185 -0
  35. package/src/workspace/atomicJson.js +32 -0
  36. package/src/workspace/capture.js +87 -0
  37. package/src/workspace/checkpointer.js +179 -0
  38. package/src/workspace/environment.js +75 -0
  39. package/src/workspace/lock.js +128 -0
  40. package/src/workspace/mapping.js +124 -0
  41. package/src/workspace/operations.js +252 -0
  42. package/src/workspace/paths.js +16 -0
  43. package/src/workspace/planner.js +247 -0
  44. package/src/workspace/restore.js +279 -0
  45. package/src/workspace/runtime.js +314 -0
  46. package/src/workspace/schema.js +136 -0
  47. package/src/workspace/store.js +382 -0
  48. package/src/workspace/tmuxAdapter.js +554 -0
  49. package/public/assets/index-BVccqWdO.css +0 -32
  50. package/public/assets/index-zXIn4X1S.js +0 -330
package/README.md CHANGED
@@ -69,10 +69,11 @@ handmux start --tunnel cloudflare # instant public URL (cloudflared auto-insta
69
69
 
70
70
  - **Claude Code / Codex, deeply** — an inbox status ledger, thumb-approve permissions & plans, per-agent usage bars.
71
71
  - **Chat view (experimental)** — read and drive a Claude session as a chat instead of a terminal: bubbles with Markdown, tool cards with colored diffs, question cards you answer with a tap, warm colour tones. Experimental — may be unstable: enable it in Settings → 启用对话视图 / Enable chat view, then switch views from the window bar.
72
- - **Command & chat modes** — one bottom bar, two modes: type straight into the terminal, or talk to the agent in natural language. Preset ESC/Tab/Ctrl+C, custom ⌃⇧⌥ key-combos, and saved/recent commands split global or per-window (slash-commands included).
72
+ - **Command & chat modes** — one bottom bar, two modes: type straight into the terminal, or talk to the agent in natural language. Both default quick bars include `Ctrl+C`. `handmux shortcuts` configures shared key/text items; each phone's ⚙ editor shows the effective quick-bar order, interleaves shared and local items, and can remove a shared item from that device only with immediate undo. Adding a phone-local item confirms success and returns directly to the list. Server changes apply live and phones reload them on foreground—no restart or polling required.
73
73
  - **Script push** — notify your phone from any script or CI step with `handmux push`; target all devices, a named session, or a specific device.
74
+ - **Workspace recovery** — handmux silently keeps the metadata needed to rebuild your latest tmux workspace. After a computer or tmux-server restart, restore it beside any new sessions from the phone or with `handmux restore`; existing sessions are never replaced.
74
75
  - **Git viewer** — changes / commit history / any branch / full-screen colored diff, multi-repo tabs, read-only, never touches your tree.
75
- - **Site preview** — a static folder, or a running HTTP/HTTPS service by port (routing / APIs / live-reload intact), in a phone or desktop viewport.
76
+ - **Site preview** — a static folder, or a running HTTP/HTTPS service by port (routing / APIs / live-reload intact), in a phone or desktop viewport. Set its bare preview domain once in `handmux setup`.
76
77
  - **Docs** — tap a path in the terminal to open it; Markdown rendered, font zoom, sentence-by-sentence read-aloud.
77
78
  - **Select & copy text** — long-press to select in the terminal, drag iOS-style handles to fine-tune, copy the selection / a whole line / a whole paragraph.
78
79
  - **Files both ways** — multi-select upload from the chat box, download, share in, copy any absolute path.
@@ -80,6 +81,21 @@ handmux start --tunnel cloudflare # instant public URL (cloudflared auto-insta
80
81
  - **Built for flaky networks** — backoff reconnect, connection-lost banner, offline page, polling that pauses in the background; a reflow-safe cursor.
81
82
  - **Zero-install PWA** — runs in the browser; add to home screen for full-screen. Multilingual — English, 简体 / 繁體中文, 日本語, 한국어.
82
83
 
84
+ ## Workspace recovery
85
+
86
+ handmux continuously maintains two redundant copies of the latest workspace metadata. They are not browsing history: ordinary changes and deletions handmux can confirm simply update the current state. A selectable checkpoint is archived only when the computer or tmux environment changes. If the final tmux session disappears outside handmux, tmux cannot distinguish an intentional deletion from a crash, so handmux retains the last state and can offer recovery immediately, without waiting for a new tmux server or session. Every checkpoint from the latest 24 hours is kept; older history is then trimmed to the newest 10, while the latest valid checkpoint never expires just because of age.
87
+
88
+ After such a restart, the phone shows **Restore last workspace** for one hour when a checkpoint has work left to restore; if tmux has no sessions it opens the confirmation directly. Choosing **Ignore this backup** suppresses that checkpoint only on that phone; an ordinary close does not. When recovery finishes, the phone reports the actual sessions, windows, and panes restored, but does not automatically open or bind them; choose **Bind restored sessions** if you want them on that phone. The CLI remains available after the phone prompt expires:
89
+
90
+ ```bash
91
+ handmux restore --dry-run # preview the latest plan
92
+ handmux restore # restore; TTY picker, otherwise latest
93
+ handmux restore --list # list retained checkpoints
94
+ handmux restore --checkpoint <id> --session api # select history / restore one session
95
+ ```
96
+
97
+ Restore is additive and idempotent. It never stops, renames, replaces, or changes the topology of a current session; a name collision becomes `name-restored`, then `name-restored-2`. Windows, panes, working directories and layouts are rebuilt where safe. Only verified Claude Code/Codex sessions are resumed from their persisted session IDs; ordinary panes reopen as shells in their saved directories, without replaying commands or scrollback. Metadata lives under `~/.handmux/workspaces/`; it can include paths, tmux names/layout and agent session IDs, but not pane output.
98
+
83
99
  ## Script push
84
100
 
85
101
  Send a push notification to your phone from any script, CI step, or build hook:
package/README.zh-CN.md CHANGED
@@ -69,10 +69,11 @@ handmux start --tunnel cloudflare # 即时公网地址(自动装 cloudflared)
69
69
 
70
70
  - **Claude Code / Codex 深度**——收件箱状态台账、拇指批授权批计划、各 agent 用量条。
71
71
  - **对话视图(实验性)**——把 Claude 会话当成聊天来看、来驱动,而不是终端:气泡 + Markdown 正文、带彩色 diff 的工具卡、点按即答的问题卡、暖色配色。实验性功能,可能不稳定:在设置里开启「启用对话视图(实验性功能)」后,从窗口栏切换视图。
72
- - **命令 / 聊天双模式**——底部一栏两种模式:直接敲进终端,或用自然语言发给 agent。预置 ESC/Tab/Ctrl+C、自定义 ⌃⇧⌥ 组合键,常用 / 最近命令分全局或按窗口(含斜杠命令)。
72
+ - **命令 / 聊天双模式**——底部一栏两种模式:直接敲进终端,或用自然语言发给 agent;两种默认快捷栏都有 `Ctrl+C`。`handmux shortcuts` 配置所有设备共享的按键 / 文字项;每台手机的 ⚙ 编辑器按快捷栏实际顺序统一显示共享与本机项,可自由混排,也可只从本机移除共享项并即时撤销。本机新增成功后会明确提示并直接返回列表。服务端修改立即生效,手机回到前台自动读取,无需重启或轮询。
73
73
  - **脚本推送**——用 `handmux push` 从脚本或 CI 步骤推消息到手机,可指定全部设备、某个会话或某台设备。
74
+ - **工作区恢复**——handmux 静默保存重建最新 tmux 工作区所需的元数据。电脑或 tmux server 重启后,可从手机或 `handmux restore` 把旧工作区恢复到新会话旁边,绝不替换现有会话。
74
75
  - **Git 查看器**——改动 / 提交历史 / 任意分支 / 全屏彩色 diff,多仓库分页,只读不动工作区。
75
- - **站点预览**——挑目录预览静态站,或按端口预览正跑的 HTTP/HTTPS 服务(路由 / 接口 / HMR 全保留)
76
+ - **站点预览**——挑目录预览静态站,或按端口预览正跑的 HTTP/HTTPS 服务(路由 / 接口 / HMR 全保留);动态预览域名在 `handmux setup` 里一次配好即可。
76
77
  - **文档**——终端里点路径即开;Markdown 排版、字号缩放、逐句高亮朗读。
77
78
  - **选中 · 拷贝**——终端里长按选中,拖 iOS 式手柄精调,一键拷贝选区 / 整行 / 整段。
78
79
  - **文件双向传**——聊天框多选上传、下载、系统分享进来、复制绝对路径。
@@ -80,6 +81,21 @@ handmux start --tunnel cloudflare # 即时公网地址(自动装 cloudflared)
80
81
  - **专治弱网**——退避重连、掉线横幅、离线兜底页、后台暂停轮询;光标不乱跳。
81
82
  - **零安装 PWA**——浏览器直接跑,可加主屏全屏运行;多语言(English、简体 / 繁體中文、日本語、한국어)。
82
83
 
84
+ ## 工作区恢复
85
+
86
+ handmux 会持续维护最新工作区元数据的两份容灾副本。它们不是操作历史:日常变动和 handmux 能确认的主动删除只会更新当前状态。只有电脑或 tmux 环境换代时才归档可选择的 checkpoint。若最后一个 tmux 会话在 handmux 外消失,tmux 无法区分主动删除与崩溃;为保留崩溃恢复能力,handmux 会保留最后状态,并立即提供恢复,无需等待新 tmux server 或 session 出现。最近 24 小时内的全部保留;更早历史再裁到最新 10 份,最新有效 checkpoint 不会只因过期而消失。
87
+
88
+ 重启后若 checkpoint 里还有内容待恢复,手机会在一小时内显示「恢复上次工作区」;若 tmux 当前没有任何会话,则直接打开确认弹窗。在手机上忽略后,该 checkpoint 只在这台手机上不再提示。恢复完成后会汇总实际恢复的会话、窗口和窗格,但不会自动打开或绑定;需要时可点「重新绑定会话」选择要显示在这台手机上的会话。手机提示过期后,CLI 仍一直可用:
89
+
90
+ ```bash
91
+ handmux restore --dry-run # 预览最新恢复计划
92
+ handmux restore # 恢复;TTY 交互选择,非 TTY 用最新
93
+ handmux restore --list # 列出保留的 checkpoint
94
+ handmux restore --checkpoint <id> --session api # 选历史 / 只恢复一个会话
95
+ ```
96
+
97
+ 恢复是只新增、可重复执行的:不会停止、改名、替换或改变当前会话的拓扑;同名时依次改为 `name-restored`、`name-restored-2`。在安全可表达的范围内重建窗口、窗格、工作目录和布局。只有经过验证的 Claude Code / Codex 会话会用已持久化的 session ID 续接;普通 pane 只在原目录打开 shell,不会重放命令或保存的终端输出。元数据位于 `~/.handmux/workspaces/`,可能包含路径、tmux 名称/布局和 agent session ID,但不包含 pane 输出。
98
+
83
99
  ## 脚本推送
84
100
 
85
101
  在电脑上运行任意脚本、CI 步骤或构建钩子时,推送通知到手机:
package/bin/handmux.js CHANGED
@@ -32,6 +32,7 @@ import { checkTmux, MIN_TMUX, tmuxInstallHint } from '../src/cli/tmuxVersion.js'
32
32
  import { readState, clearState, isAlive, acquireLifecycleLock, pocketHome, logPath, configPath, claudeStatePath } from '../src/cli/state.js';
33
33
  import { scanSupervisorPids, terminateSupervisorPids } from '../src/cli/supervisorProcesses.js';
34
34
  import { runSetup } from '../src/cli/setupWizard.js';
35
+ import { commitShortcuts, reportShortcutCommit, runShortcutEditor } from '../src/cli/shortcutEditor.js';
35
36
  import { hooksStatus, installHooks, uninstallHooks } from '../src/cli/claudeHooks.js';
36
37
  import { codexHooksStatus, installCodexHooks, uninstallCodexHooks } from '../src/cli/codexHooks.js';
37
38
  import { statusLineStatus, installStatusLine, uninstallStatusLine, composeHint, refreshStatusLineScript } from '../src/cli/statusLine.js';
@@ -40,6 +41,7 @@ import { probe } from '../src/cli/probe.js';
40
41
  import { notifyUpdate, runUpdateCheck, isBrewInstall, PKG_NAME } from '../src/cli/updateCheck.js';
41
42
  import { t, initLocale, setLocale } from '../src/cli/i18n/index.js';
42
43
  import { runPush } from '../src/cli/pushCmd.js';
44
+ import { runWorkspaceCommand } from '../src/cli/workspaceCmd.js';
43
45
 
44
46
  const HOME = homedir();
45
47
  const SELF = fileURLToPath(import.meta.url);
@@ -49,7 +51,7 @@ const SELF_REAL = (() => { try { return fs.realpathSync(SELF); } catch { return
49
51
  const HOOKS_SRC = path.resolve(path.dirname(SELF), '../hooks'); // server/hooks (bundled scripts)
50
52
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
51
53
 
52
- const { command, flags } = parseArgs(process.argv.slice(2));
54
+ const { command, flags, positionals = [], unknownShortFlags = [] } = parseArgs(process.argv.slice(2));
53
55
 
54
56
  // Resolve the CLI language ONCE, up front, so every command (help, errors, access block) prints in it.
55
57
  // Priority: --lang > config `lang` > shell locale (LANG/LC_*) > English. The config peek is lenient — a
@@ -144,8 +146,10 @@ async function main() {
144
146
  case 'status': await status(); process.exit(process.exitCode || 0);
145
147
  case 'logs': return logs();
146
148
  case 'push': process.exitCode = await pushCmd(); return;
149
+ case 'restore': process.exitCode = await runWorkspaceCommand({ flags, positionals, unknownShortFlags, home: HOME }); return;
147
150
  case 'config': return configCmd();
148
151
  case 'setup': return setupCmd();
152
+ case 'shortcuts': return shortcutsCmd();
149
153
  case 'hooks': return hooksCmd();
150
154
  case 'service': return withLifecycleLock(serviceCmd);
151
155
  case 'update': case 'upgrade': return updateCmd();
@@ -493,6 +497,26 @@ async function setupCmd() {
493
497
  console.log(t(running ? 'setup.laterRestart' : 'setup.later'));
494
498
  }
495
499
 
500
+ async function shortcutsCmd() {
501
+ const target = flags.config ? path.resolve(flags.config) : configPath(HOME);
502
+ let res;
503
+ try {
504
+ res = await runShortcutEditor({
505
+ target,
506
+ commit: (file, shortcuts) => commitShortcuts({ home: HOME, target: file, shortcuts }),
507
+ });
508
+ } catch (error) {
509
+ console.error(error.ownerPid
510
+ ? t('lifecycle.busy', { pid: error.ownerPid })
511
+ : t('err.badConfig', { path: target, msg: error.message }));
512
+ process.exitCode = 2;
513
+ return;
514
+ }
515
+ if (!res) return;
516
+ if (res.error) { process.exitCode = 2; return; }
517
+ process.exitCode = reportShortcutCommit(res) || process.exitCode;
518
+ }
519
+
496
520
  // Offer to enable the Claude statusLine usage capturer — it feeds the phone Usage page's 5h/weekly bars
497
521
  // (Claude Code's statusLine stdin is the only documented local source of those %). Opt-in and NON-
498
522
  // DESTRUCTIVE: auto-installs only when there's no statusLine yet; if the user already has one we print a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handmux",
3
- "version": "0.17.8",
3
+ "version": "0.18.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",
@@ -40,7 +40,8 @@
40
40
  "handmux": "node bin/handmux.js",
41
41
  "bundle": "node scripts/bundle-web.mjs",
42
42
  "prepack": "node scripts/bundle-web.mjs",
43
- "test": "vitest run"
43
+ "test": "vitest run",
44
+ "test:workspace-docker": "HANDMUX_WORKSPACE_DOCKER=1 vitest run test/workspaceDocker.test.js"
44
45
  },
45
46
  "dependencies": {
46
47
  "@clack/prompts": "0.11.0",
@@ -57,13 +58,13 @@
57
58
  "vitest": "^2.0.0"
58
59
  },
59
60
  "whatsNew": [
61
+ {"version":"0.18.0","date":"2026-07-22","zh":"Tmux 工作区恢复 · 快捷栏自由定制","en":"Tmux workspace recovery · customizable shortcuts"},
60
62
  {"version":"0.17.8","date":"2026-07-20","zh":"分屏地图显示尺寸 · iPhone 交互修复","en":"Pane dimensions in the map · iPhone interaction fixes"},
61
63
  {"version":"0.17.7","date":"2026-07-20","zh":"单实例启动兜底 · WSL 状态命令修复","en":"Single-instance startup guard · WSL status fix"},
62
64
  {"version":"0.17.6","date":"2026-07-20","zh":"推送结果可追踪 · 对话与权限操作更可靠","en":"Traceable push results · safer chats and permissions"},
63
65
  {"version":"0.17.5","date":"2026-07-20","zh":"通知开启修复 · 长会话与设置可靠性提升","en":"Notification setup fixed · stronger chat and settings reliability"},
64
66
  {"version":"0.17.4","date":"2026-07-19","zh":"HTTPS 本机预览修复 · 通知链接安全加固","en":"HTTPS localhost previews fixed · safer notification links"},
65
67
  {"version":"0.17.3","date":"2026-07-19","zh":"三修官方一键安装的 Claude 识别,真机已验证","en":"Third fix for native-install Claude detection, device-verified"},
66
- {"version":"0.17.0","date":"2026-07-18","zh":"对话视图(实验性)· 通知记录 · 本机地址一键预览","en":"Chat view (experimental) · notification inbox · one-tap localhost preview"},
67
- {"version":"0.16.0","date":"2026-07-15","zh":"分屏布局地图 · 手机也能建/删窗格","en":"Split-layout map · create & close panes from the phone"}
68
+ {"version":"0.17.0","date":"2026-07-18","zh":"对话视图(实验性)· 通知记录 · 本机地址一键预览","en":"Chat view (experimental) · notification inbox · one-tap localhost preview"}
68
69
  ]
69
70
  }