handmux 0.22.1 → 0.24.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 +3 -3
- package/README.zh-CN.md +3 -3
- package/bin/handmux.js +3 -688
- package/dist/bin/handmux.js +850 -0
- package/{hooks → dist/hooks}/handmux-notify.sh +2 -4
- package/{hooks → dist/hooks}/handmux-statusline.cjs +28 -6
- package/{hooks → dist/hooks}/handmux-write.cjs +25 -26
- package/dist/package.json +126 -0
- package/dist/public/assets/index-CicdY9E9.css +32 -0
- package/dist/public/assets/index-D4VP5t8h.js +337 -0
- package/dist/public/assets/pcm-worklet-CxcDy7PB.js +1 -0
- package/{public → dist/public}/index.html +5 -4
- package/{src → dist/src}/agents/claude.js +106 -82
- package/dist/src/agents/codex.js +217 -0
- package/{src → dist/src}/agents/index.js +9 -10
- package/dist/src/agents/processIdentity.js +80 -0
- package/dist/src/agents/scanUtils.js +262 -0
- package/dist/src/apiErrors.js +77 -0
- package/{src → dist/src}/appName.js +12 -13
- package/dist/src/asr/iflyConfig.js +7 -0
- package/dist/src/asr/iflySign.js +14 -0
- package/dist/src/auth.js +27 -0
- package/dist/src/browser/bootstrap.js +72 -0
- package/dist/src/browser/cookieProfiles.js +413 -0
- package/dist/src/browser/coordinator.js +51 -0
- package/dist/src/browser/hammerheadCompat.js +104 -0
- package/dist/src/browser/hammerheadRedirectCompat.js +37 -0
- package/dist/src/browser/manager.js +560 -0
- package/dist/src/browser/originLabel.js +15 -0
- package/dist/src/browser/profilePersistence.js +235 -0
- package/dist/src/browser/publicProxy.js +176 -0
- package/dist/src/browser/routes.js +159 -0
- package/dist/src/browser/sessionStore.js +72 -0
- package/dist/src/browser/siteVersion.js +121 -0
- package/dist/src/browser/targetPolicy.js +163 -0
- package/{src → dist/src}/browser/worker.js +14 -18
- package/dist/src/browser/workerClient.js +286 -0
- package/dist/src/browser/workerServer.js +91 -0
- package/dist/src/captureBackground.js +95 -0
- package/dist/src/claudeEvents.js +499 -0
- package/{src → dist/src}/cli/claudeHooks.js +146 -108
- package/{src → dist/src}/cli/cloudflareUrl.js +2 -3
- package/dist/src/cli/cloudflared.js +106 -0
- package/dist/src/cli/codexManaged.js +77 -0
- package/{src → dist/src}/cli/cpolarUrl.js +8 -9
- package/dist/src/cli/drivers.js +98 -0
- package/dist/src/cli/hookScaffold.js +36 -0
- package/dist/src/cli/i18n/en.js +366 -0
- package/{src → dist/src}/cli/i18n/index.js +27 -25
- package/dist/src/cli/i18n/zh.js +365 -0
- package/dist/src/cli/legacyCodexHooks.js +52 -0
- package/{src → dist/src}/cli/natappUrl.js +2 -3
- package/dist/src/cli/options.js +245 -0
- package/dist/src/cli/probe.js +16 -0
- package/{src → dist/src}/cli/prompt.js +4 -6
- package/dist/src/cli/pushCmd.js +102 -0
- package/{src → dist/src}/cli/qr.js +19 -19
- package/dist/src/cli/service.js +144 -0
- package/dist/src/cli/setupModel.js +194 -0
- package/dist/src/cli/setupWizard.js +647 -0
- package/dist/src/cli/shortcutEditor.js +351 -0
- package/dist/src/cli/sshTunnel.js +18 -0
- package/dist/src/cli/state.js +114 -0
- package/{src → dist/src}/cli/statusLine.js +63 -46
- package/dist/src/cli/supervisor.js +278 -0
- package/dist/src/cli/supervisorHealth.js +21 -0
- package/dist/src/cli/supervisorLaunch.js +148 -0
- package/dist/src/cli/supervisorProcesses.js +62 -0
- package/dist/src/cli/supervisorState.js +62 -0
- package/dist/src/cli/tmuxVersion.js +53 -0
- package/{src → dist/src}/cli/tunlite.js +7 -10
- package/dist/src/cli/tunnelClients.js +72 -0
- package/{src → dist/src}/cli/updateCheck.js +98 -69
- package/{src → dist/src}/cli/urlHost.js +8 -4
- package/dist/src/cli/workspaceCmd.js +347 -0
- package/dist/src/codexAppServer.js +2299 -0
- package/dist/src/codexConversationProjection.js +199 -0
- package/dist/src/codexDiff.js +118 -0
- package/dist/src/codexMessageIdentity.js +18 -0
- package/dist/src/codexPlan.js +30 -0
- package/dist/src/codexQueueProtocol.js +96 -0
- package/dist/src/codexStreamProtocol.js +207 -0
- package/dist/src/codexToolProtocol.js +68 -0
- package/dist/src/codexTranscriptParse.js +724 -0
- package/dist/src/codexUsageSnapshot.js +205 -0
- package/dist/src/config.js +8 -0
- package/{src → dist/src}/docPath.js +22 -24
- package/dist/src/docs.js +337 -0
- package/dist/src/git.js +272 -0
- package/dist/src/healthProtocol.js +97 -0
- package/dist/src/httpApi.js +68 -0
- package/dist/src/jsonStore.js +15 -0
- package/{src → dist/src}/keyNames.js +3 -3
- package/dist/src/notifications.js +111 -0
- package/dist/src/orphans.js +157 -0
- package/dist/src/pendingPrompt.js +194 -0
- package/dist/src/previewServer.js +202 -0
- package/dist/src/previews.js +179 -0
- package/dist/src/privateStateStore.js +89 -0
- package/dist/src/push.js +224 -0
- package/dist/src/routes/codex.js +606 -0
- package/dist/src/routes/files.js +230 -0
- package/dist/src/routes/git.js +78 -0
- package/dist/src/routes/health.js +16 -0
- package/dist/src/routes/notifications.js +16 -0
- package/dist/src/routes/previews.js +43 -0
- package/dist/src/routes/push.js +145 -0
- package/dist/src/routes/sessions.js +232 -0
- package/dist/src/routes/system.js +156 -0
- package/dist/src/routes/terminal.js +238 -0
- package/dist/src/routes/transcript.js +197 -0
- package/dist/src/routes/workspace.js +267 -0
- package/{src → dist/src}/server.js +110 -76
- package/dist/src/shortcutConfig.js +83 -0
- package/{src → dist/src}/staticCache.js +5 -3
- package/{src → dist/src}/staticCompression.js +5 -6
- package/dist/src/terminalStream.js +441 -0
- package/{src → dist/src}/tmux/commands.js +160 -167
- package/dist/src/tmux/format.js +47 -0
- package/dist/src/transcriptParse.js +240 -0
- package/dist/src/transcriptReader.js +113 -0
- package/{src → dist/src}/trimCapture.js +11 -11
- package/dist/src/uploadTypes.js +29 -0
- package/dist/src/urlPolicy.js +18 -0
- package/dist/src/usage.js +260 -0
- package/dist/src/workspace/agentRunner.js +204 -0
- package/dist/src/workspace/atomicJson.js +31 -0
- package/dist/src/workspace/capture.js +139 -0
- package/dist/src/workspace/checkpointer.js +228 -0
- package/dist/src/workspace/environment.js +71 -0
- package/dist/src/workspace/lock.js +137 -0
- package/dist/src/workspace/mapping.js +128 -0
- package/dist/src/workspace/operations.js +271 -0
- package/dist/src/workspace/paths.js +15 -0
- package/dist/src/workspace/planner.js +292 -0
- package/dist/src/workspace/restore.js +314 -0
- package/dist/src/workspace/runtime.js +318 -0
- package/dist/src/workspace/schema.js +156 -0
- package/dist/src/workspace/store.js +464 -0
- package/dist/src/workspace/tmuxAdapter.js +694 -0
- package/dist/src/workspaceProtocol.js +158 -0
- package/package.json +28 -16
- package/hooks/handmux-codex-usage.cjs +0 -141
- package/public/assets/index-ClOByNCn.js +0 -335
- package/public/assets/index-rjrtUwoJ.css +0 -32
- package/src/.gitkeep +0 -0
- package/src/agents/codex.js +0 -121
- package/src/agents/processIdentity.js +0 -72
- package/src/agents/scanUtils.js +0 -225
- package/src/asr/iflyConfig.js +0 -10
- package/src/asr/iflySign.js +0 -16
- package/src/auth.js +0 -30
- package/src/browser/bootstrap.js +0 -68
- package/src/browser/cookieProfiles.js +0 -403
- package/src/browser/coordinator.js +0 -47
- package/src/browser/hammerheadCompat.js +0 -86
- package/src/browser/hammerheadRedirectCompat.js +0 -38
- package/src/browser/manager.js +0 -556
- package/src/browser/originLabel.js +0 -17
- package/src/browser/profilePersistence.js +0 -241
- package/src/browser/publicProxy.js +0 -175
- package/src/browser/routes.js +0 -154
- package/src/browser/sessionStore.js +0 -78
- package/src/browser/siteVersion.js +0 -124
- package/src/browser/targetPolicy.js +0 -137
- package/src/browser/workerClient.js +0 -253
- package/src/browser/workerServer.js +0 -98
- package/src/captureBackground.js +0 -93
- package/src/claudeEvents.js +0 -356
- package/src/cli/cloudflared.js +0 -99
- package/src/cli/codexHooks.js +0 -123
- package/src/cli/drivers.js +0 -97
- package/src/cli/hookScaffold.js +0 -35
- package/src/cli/i18n/en.js +0 -388
- package/src/cli/i18n/zh.js +0 -387
- package/src/cli/options.js +0 -211
- package/src/cli/probe.js +0 -16
- package/src/cli/pushCmd.js +0 -60
- package/src/cli/service.js +0 -126
- package/src/cli/setupModel.js +0 -154
- package/src/cli/setupWizard.js +0 -506
- package/src/cli/shortcutEditor.js +0 -288
- package/src/cli/sshTunnel.js +0 -12
- package/src/cli/state.js +0 -90
- package/src/cli/supervisor.js +0 -183
- package/src/cli/supervisorProcesses.js +0 -57
- package/src/cli/tmuxVersion.js +0 -49
- package/src/cli/tunnelClients.js +0 -62
- package/src/cli/workspaceCmd.js +0 -300
- package/src/config.js +0 -9
- package/src/docs.js +0 -228
- package/src/git.js +0 -208
- package/src/httpApi.js +0 -69
- package/src/jsonStore.js +0 -24
- package/src/notifications.js +0 -68
- package/src/orphans.js +0 -150
- package/src/pendingPrompt.js +0 -163
- package/src/previewServer.js +0 -168
- package/src/previews.js +0 -116
- package/src/push.js +0 -183
- package/src/routes/files.js +0 -172
- package/src/routes/git.js +0 -57
- package/src/routes/notifications.js +0 -19
- package/src/routes/previews.js +0 -35
- package/src/routes/push.js +0 -119
- package/src/routes/sessions.js +0 -163
- package/src/routes/system.js +0 -145
- package/src/routes/terminal.js +0 -210
- package/src/routes/transcript.js +0 -115
- package/src/routes/workspace.js +0 -267
- package/src/shortcutConfig.js +0 -75
- package/src/terminalStream.js +0 -391
- package/src/tmux/format.js +0 -45
- package/src/transcriptParse.js +0 -180
- package/src/transcriptReader.js +0 -101
- package/src/uploadTypes.js +0 -30
- package/src/urlPolicy.js +0 -14
- package/src/usage.js +0 -261
- package/src/workspace/agentRunner.js +0 -185
- package/src/workspace/atomicJson.js +0 -32
- package/src/workspace/capture.js +0 -87
- package/src/workspace/checkpointer.js +0 -183
- package/src/workspace/environment.js +0 -75
- package/src/workspace/lock.js +0 -128
- package/src/workspace/mapping.js +0 -124
- package/src/workspace/operations.js +0 -252
- package/src/workspace/paths.js +0 -16
- package/src/workspace/planner.js +0 -247
- package/src/workspace/restore.js +0 -279
- package/src/workspace/runtime.js +0 -314
- package/src/workspace/schema.js +0 -136
- package/src/workspace/store.js +0 -382
- package/src/workspace/tmuxAdapter.js +0 -554
- /package/{public → dist/public}/fonts/JetBrainsMonoNerdFontMono-Regular.woff2 +0 -0
- /package/{public → dist/public}/fonts/TWUnifont.woff2 +0 -0
- /package/{public → dist/public}/icons/apple-touch-icon.png +0 -0
- /package/{public → dist/public}/icons/badge-96.png +0 -0
- /package/{public → dist/public}/icons/icon-192-maskable.png +0 -0
- /package/{public → dist/public}/icons/icon-192.png +0 -0
- /package/{public → dist/public}/icons/icon-512-maskable.png +0 -0
- /package/{public → dist/public}/icons/icon-512.png +0 -0
- /package/{public → dist/public}/icons/logo.svg +0 -0
- /package/{public → dist/public}/manifest.webmanifest +0 -0
- /package/{public → dist/public}/offline.html +0 -0
- /package/{public → dist/public}/sw.js +0 -0
- /package/{src → dist/src}/browser/protocol.js +0 -0
- /package/{src → dist/src}/cli/cfNamed.js +0 -0
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# handmux 上报 hook. $1 = stop | notify | prompt | end | resume | permreq | compacting | compact | stopfail.
|
|
3
3
|
# (compacting/compact/stopfail 是版本门控事件:PreCompact/PostCompact/StopFailure,只在够新的 Claude 上注册。)
|
|
4
|
-
#
|
|
5
|
-
# Codex 传 'codex')—— Claude 与 Codex 的 hook payload 字段一致(stdin JSON),所以共用这一个脚本。
|
|
6
|
-
# stdin = agent 原始 payload(JSON).
|
|
4
|
+
# stdin = Claude Code 原始 payload(JSON).
|
|
7
5
|
# (resume = PostToolUse on AskUserQuestion/ExitPlanMode:答完选项/批准计划 → 状态翻回进行中、带所选项。)
|
|
8
6
|
# (permreq = PermissionRequest:真实弹框一出现就发、带 tool_name → 比 permission_prompt 早亮「需要你」。)
|
|
9
7
|
# 只做一件事:把本次事件写进一个本地 JSON 状态文件(键=tmux pane,值=该 pane 最新事件)。不联网、
|
|
@@ -20,5 +18,5 @@ TS=$(perl -MTime::HiRes -e 'printf "%.0f", Time::HiRes::time()*1000' 2>/dev/null
|
|
|
20
18
|
HOST=$(hostname 2>/dev/null || printf '')
|
|
21
19
|
# payload 经 stdin 原样流给 node(不在 shell 里转义,避免坏数据);pane 含 '%' 直接进 JSON 字段,
|
|
22
20
|
# 不再进 URL → 彻底告别旧的 "%110 被 url-decode 丢弃" 那类坑。
|
|
23
|
-
node "$(dirname "$0")/handmux-write.cjs" "$FILE" "$PANE" "$1" "$TS" "$HOST"
|
|
21
|
+
node "$(dirname "$0")/handmux-write.cjs" "$FILE" "$PANE" "$1" "$TS" "$HOST" 2>/dev/null || true
|
|
24
22
|
exit 0
|
|
@@ -21,6 +21,22 @@ const raw = (() => { try { return fs.readFileSync(0, 'utf8'); } catch { return '
|
|
|
21
21
|
let j = {};
|
|
22
22
|
try { j = JSON.parse(raw || '{}'); } catch { /* not JSON → leave j = {} */ }
|
|
23
23
|
|
|
24
|
+
function ensurePrivateDirectory(directory) {
|
|
25
|
+
const resolved = path.resolve(directory);
|
|
26
|
+
const parsed = path.parse(resolved);
|
|
27
|
+
const parts = resolved.slice(parsed.root.length).split(path.sep).filter(Boolean);
|
|
28
|
+
const privateRoot = parts.indexOf('.handmux');
|
|
29
|
+
if (privateRoot < 0) {
|
|
30
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (let i = privateRoot; i < parts.length; i++) {
|
|
34
|
+
const current = path.join(parsed.root, ...parts.slice(0, i + 1));
|
|
35
|
+
fs.mkdirSync(current, { recursive: true, mode: 0o700 });
|
|
36
|
+
fs.chmodSync(current, 0o700);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
24
40
|
// One rate-limit window → our shape, or undefined if the field is absent (rate_limits only appears for
|
|
25
41
|
// Pro/Max plans, and only after a session's first API response).
|
|
26
42
|
function win(o) {
|
|
@@ -48,7 +64,8 @@ if (file) {
|
|
|
48
64
|
rateLimits,
|
|
49
65
|
};
|
|
50
66
|
if (snap.context === undefined) delete snap.context;
|
|
51
|
-
|
|
67
|
+
ensurePrivateDirectory(path.dirname(file));
|
|
68
|
+
if (fs.existsSync(file)) fs.chmodSync(file, 0o600);
|
|
52
69
|
let previousHasQuota = false;
|
|
53
70
|
try {
|
|
54
71
|
const previous = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
@@ -58,12 +75,13 @@ if (file) {
|
|
|
58
75
|
// previous machine-wide quota until a real rate-limit payload refreshes it instead of flashing empty.
|
|
59
76
|
if (Object.keys(rateLimits).length > 0) {
|
|
60
77
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
61
|
-
fs.writeFileSync(tmp, JSON.stringify(snap));
|
|
78
|
+
fs.writeFileSync(tmp, JSON.stringify(snap), { mode: 0o600 });
|
|
62
79
|
fs.renameSync(tmp, file); // atomic: concurrent statuslines (multiple sessions) can't tear the snapshot
|
|
80
|
+
fs.chmodSync(file, 0o600);
|
|
63
81
|
} else if (!previousHasQuota) {
|
|
64
82
|
// Create the initial "capturer active, awaiting data" marker only if no concurrent session has
|
|
65
83
|
// already published a real quota snapshot. An empty session can therefore never win that race.
|
|
66
|
-
try { fs.writeFileSync(file, JSON.stringify(snap), { flag: 'wx' }); } catch { /* file now exists */ }
|
|
84
|
+
try { fs.writeFileSync(file, JSON.stringify(snap), { flag: 'wx', mode: 0o600 }); } catch { /* file now exists */ }
|
|
67
85
|
}
|
|
68
86
|
|
|
69
87
|
// Per-session context snapshot. The global file above is last-writer-wins across ALL sessions, so it
|
|
@@ -73,12 +91,13 @@ if (file) {
|
|
|
73
91
|
const sid = typeof j.session_id === 'string' ? j.session_id : null;
|
|
74
92
|
if (sid && /^[\w-]+$/.test(sid) && snap.context) {
|
|
75
93
|
const cdir = path.join(path.dirname(file), 'context');
|
|
76
|
-
|
|
94
|
+
ensurePrivateDirectory(cdir);
|
|
77
95
|
const cfile = path.join(cdir, `${sid}.json`);
|
|
78
96
|
const csnap = { sessionId: sid, model: snap.model, usedPercent: snap.context.usedPercent, updatedAt: snap.updatedAt };
|
|
79
97
|
const ctmp = `${cfile}.${process.pid}.tmp`;
|
|
80
|
-
fs.writeFileSync(ctmp, JSON.stringify(csnap));
|
|
98
|
+
fs.writeFileSync(ctmp, JSON.stringify(csnap), { mode: 0o600 });
|
|
81
99
|
fs.renameSync(ctmp, cfile);
|
|
100
|
+
fs.chmodSync(cfile, 0o600);
|
|
82
101
|
// Best-effort prune of stale session files (ended sessions never clean up after themselves) so the
|
|
83
102
|
// dir can't grow without bound. A day is well past any live session's last statusLine render.
|
|
84
103
|
try {
|
|
@@ -86,7 +105,10 @@ if (file) {
|
|
|
86
105
|
for (const nm of fs.readdirSync(cdir)) {
|
|
87
106
|
if (!nm.endsWith('.json')) continue;
|
|
88
107
|
const fp = path.join(cdir, nm);
|
|
89
|
-
try {
|
|
108
|
+
try {
|
|
109
|
+
fs.chmodSync(fp, 0o600);
|
|
110
|
+
if (fs.statSync(fp).mtimeMs < cutoff) fs.unlinkSync(fp);
|
|
111
|
+
} catch { /* skip */ }
|
|
90
112
|
}
|
|
91
113
|
} catch { /* prune is best-effort */ }
|
|
92
114
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
const fs = require('node:fs');
|
|
17
17
|
const path = require('node:path');
|
|
18
18
|
|
|
19
|
-
const [, , file, pane, src, ts, host = ''
|
|
19
|
+
const [, , file, pane, src, ts, host = ''] = process.argv;
|
|
20
20
|
if (!file || !pane || !src) process.exit(0);
|
|
21
21
|
|
|
22
22
|
let payload = {};
|
|
@@ -35,7 +35,26 @@ const nap = (ms) => {
|
|
|
35
35
|
catch { const end = Date.now() + ms; while (Date.now() < end) { /* spin */ } }
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
function ensurePrivateDirectory(directory) {
|
|
39
|
+
const resolved = path.resolve(directory);
|
|
40
|
+
const parsed = path.parse(resolved);
|
|
41
|
+
const parts = resolved.slice(parsed.root.length).split(path.sep).filter(Boolean);
|
|
42
|
+
const privateRoot = parts.indexOf('.handmux');
|
|
43
|
+
if (privateRoot < 0) {
|
|
44
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
for (let i = privateRoot; i < parts.length; i++) {
|
|
48
|
+
const current = path.join(parsed.root, ...parts.slice(0, i + 1));
|
|
49
|
+
fs.mkdirSync(current, { recursive: true, mode: 0o700 });
|
|
50
|
+
fs.chmodSync(current, 0o700);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
ensurePrivateDirectory(path.dirname(file));
|
|
56
|
+
if (fs.existsSync(file)) fs.chmodSync(file, 0o600);
|
|
57
|
+
} catch { /* ignore */ }
|
|
39
58
|
|
|
40
59
|
function update() {
|
|
41
60
|
let obj = {};
|
|
@@ -58,30 +77,19 @@ function update() {
|
|
|
58
77
|
const curSid = obj[pane] && obj[pane].payload && obj[pane].payload.session_id;
|
|
59
78
|
const endSid = payload && payload.session_id;
|
|
60
79
|
if (!curSid || !endSid || curSid === endSid) delete obj[pane];
|
|
61
|
-
} else if (src === 'resume' && agent === 'codex') {
|
|
62
|
-
// Codex fires PostToolUse on EVERY tool call, so its resume exists purely to un-stick a pane from 需要你
|
|
63
|
-
// back to 进行中 after the user approved a PermissionRequest. Apply it ONLY as that transition — a mid-
|
|
64
|
-
// turn tool call (pane already 进行中 / 已完成) is a no-op, so we don't rewrite the entry on every
|
|
65
|
-
// command (the load Claude's matcher avoids). Claude's resume — no agent arg — is unaffected.
|
|
66
|
-
const prev = obj[pane];
|
|
67
|
-
const prevPerm = prev && (prev.src === 'permreq'
|
|
68
|
-
|| (prev.src === 'notify' && (prev.payload || {}).notification_type === 'permission_prompt'));
|
|
69
|
-
if (!prevPerm) { return; }
|
|
70
|
-
obj[pane] = { ts: Number(ts) || 0, src, host, payload, agent };
|
|
71
80
|
} else {
|
|
72
|
-
|
|
73
|
-
// Claude so legacy entries stay byte-identical and default to claude server-side.
|
|
74
|
-
obj[pane] = { ts: Number(ts) || 0, src, host, payload, ...(agent ? { agent } : {}) };
|
|
81
|
+
obj[pane] = { ts: Number(ts) || 0, src, host, payload };
|
|
75
82
|
}
|
|
76
83
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
77
|
-
fs.writeFileSync(tmp, JSON.stringify(obj));
|
|
84
|
+
fs.writeFileSync(tmp, JSON.stringify(obj), { mode: 0o600 });
|
|
78
85
|
fs.renameSync(tmp, file); // atomic: a torn write can't corrupt the file
|
|
86
|
+
fs.chmodSync(file, 0o600);
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
const lock = `${file}.lock`;
|
|
82
90
|
let held = false;
|
|
83
91
|
for (let i = 0; i < 60 && !held; i++) { // ~0.9s budget, then write lockless (best-effort)
|
|
84
|
-
try { fs.closeSync(fs.openSync(lock, 'wx')); held = true; } // O_EXCL → atomic "I hold it"
|
|
92
|
+
try { fs.closeSync(fs.openSync(lock, 'wx', 0o600)); held = true; } // O_EXCL → atomic "I hold it"
|
|
85
93
|
catch {
|
|
86
94
|
try { if (Date.now() - fs.statSync(lock).mtimeMs > 3000) fs.unlinkSync(lock); } catch { /* steal a stale lock */ }
|
|
87
95
|
nap(15);
|
|
@@ -89,12 +97,3 @@ for (let i = 0; i < 60 && !held; i++) { // ~0.9s budget, t
|
|
|
89
97
|
}
|
|
90
98
|
try { update(); } catch { /* best effort */ }
|
|
91
99
|
if (held) { try { fs.unlinkSync(lock); } catch { /* ignore */ } }
|
|
92
|
-
|
|
93
|
-
// Codex hook payloads include the exact rollout transcript_path. Capture its latest token_count into a
|
|
94
|
-
// machine-wide snapshot after the inbox update; failures are isolated so usage can never break the hook.
|
|
95
|
-
if (agent === 'codex' && typeof payload.transcript_path === 'string') {
|
|
96
|
-
try {
|
|
97
|
-
const { captureTranscript } = require('./handmux-codex-usage.cjs');
|
|
98
|
-
captureTranscript(payload.transcript_path, path.join(path.dirname(file), 'codex-usage.json'));
|
|
99
|
-
} catch { /* best effort */ }
|
|
100
|
-
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "handmux",
|
|
3
|
+
"version": "0.24.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
|
+
"type": "module",
|
|
6
|
+
"license": "AGPL-3.0-only",
|
|
7
|
+
"author": "yuanyuanzijin",
|
|
8
|
+
"homepage": "https://handmux.com",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/handmux/handmux.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/handmux/handmux/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"tmux",
|
|
18
|
+
"mobile",
|
|
19
|
+
"terminal",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"remote",
|
|
22
|
+
"pwa"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20"
|
|
26
|
+
},
|
|
27
|
+
"bin": {
|
|
28
|
+
"handmux": "bin/handmux.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"bin/handmux.js",
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"README.zh-CN.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"start": "npm run build:server && node dist/bin/handmux.js start",
|
|
39
|
+
"dev": "npm run build:server && node dist/bin/handmux.js start --foreground",
|
|
40
|
+
"handmux": "npm run build:server && node dist/bin/handmux.js",
|
|
41
|
+
"build:server": "node scripts/build-server.mjs",
|
|
42
|
+
"bundle": "node scripts/bundle-web.mjs",
|
|
43
|
+
"prepack": "npm run bundle && npm run build:server",
|
|
44
|
+
"typecheck:source": "node scripts/check-typescript-product-source.mjs",
|
|
45
|
+
"typecheck": "tsc --project tsconfig.json",
|
|
46
|
+
"test": "npm run build:server && vitest run",
|
|
47
|
+
"test:pack": "node scripts/pack-smoke.mjs",
|
|
48
|
+
"test:workspace-host": "npm run build:server && HANDMUX_WORKSPACE_HOST=1 vitest run test/workspaceHost.test.js",
|
|
49
|
+
"test:workspace-docker": "npm run build:server && HANDMUX_WORKSPACE_DOCKER=1 vitest run test/workspaceDocker.test.js"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@clack/prompts": "0.11.0",
|
|
53
|
+
"busboy": "^1.6.0",
|
|
54
|
+
"compression": "^1.8.1",
|
|
55
|
+
"express": "^4.19.2",
|
|
56
|
+
"testcafe-hammerhead": "31.7.8",
|
|
57
|
+
"tunlite": "^0.10.0",
|
|
58
|
+
"web-push": "^3.6.7",
|
|
59
|
+
"ws": "^8.21.0"
|
|
60
|
+
},
|
|
61
|
+
"optionalDependencies": {
|
|
62
|
+
"qrcode-terminal": "^0.12.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/busboy": "^1.5.4",
|
|
66
|
+
"@types/compression": "^1.8.1",
|
|
67
|
+
"@types/express": "4.17.25",
|
|
68
|
+
"@types/node": "20.19.43",
|
|
69
|
+
"@types/supertest": "7.2.1",
|
|
70
|
+
"@types/web-push": "^3.6.4",
|
|
71
|
+
"@types/ws": "^8.18.1",
|
|
72
|
+
"supertest": "^7.0.0",
|
|
73
|
+
"typescript": "6.0.3",
|
|
74
|
+
"vitest": "^2.0.0"
|
|
75
|
+
},
|
|
76
|
+
"whatsNew": [
|
|
77
|
+
{
|
|
78
|
+
"version": "0.24.0",
|
|
79
|
+
"date": "2026-08-11",
|
|
80
|
+
"zh": "Codex 任务进度与 Goal 原生上线",
|
|
81
|
+
"en": "Native Codex task progress and Goals"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"version": "0.23.0",
|
|
85
|
+
"date": "2026-08-09",
|
|
86
|
+
"zh": "Codex 托管对话正式上线",
|
|
87
|
+
"en": "Managed Codex chat is here"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"version": "0.22.1",
|
|
91
|
+
"date": "2026-08-04",
|
|
92
|
+
"zh": "网页宽度与网站版本分开控制",
|
|
93
|
+
"en": "Separate page width and site-version controls"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"version": "0.22.0",
|
|
97
|
+
"date": "2026-08-03",
|
|
98
|
+
"zh": "全屏设置 · 窗口与分屏管理更清晰",
|
|
99
|
+
"en": "Full-screen Settings · clearer window and pane management"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"version": "0.21.0",
|
|
103
|
+
"date": "2026-08-03",
|
|
104
|
+
"zh": "网页预览:手机直连、电脑代理与静态目录",
|
|
105
|
+
"en": "Web preview: direct, computer proxy, and static folders"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"version": "0.20.3",
|
|
109
|
+
"date": "2026-08-01",
|
|
110
|
+
"zh": "实时终端显示更稳定流畅",
|
|
111
|
+
"en": "More stable, smoother live terminals"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"version": "0.20.2",
|
|
115
|
+
"date": "2026-07-27",
|
|
116
|
+
"zh": "实时终端长时间运行更流畅",
|
|
117
|
+
"en": "Smoother long-running live terminals"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"version": "0.20.1",
|
|
121
|
+
"date": "2026-07-26",
|
|
122
|
+
"zh": "聊天发送修复 · 电脑键盘不中断",
|
|
123
|
+
"en": "Reliable chat sending · uninterrupted desktop input"
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|