@tunnelbox/core 0.1.0 → 0.1.7
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 +16 -11
- package/README.zh-CN.md +28 -0
- package/dist/index.js +1630 -0
- package/locales/de-DE.json +161 -0
- package/locales/en-US.json +161 -0
- package/locales/es-ES.json +161 -0
- package/locales/fr-FR.json +161 -0
- package/locales/ja-JP.json +161 -0
- package/locales/ko-KR.json +161 -0
- package/locales/zh-CN.json +161 -0
- package/locales/zh-TW.json +161 -0
- package/package.json +18 -4
- package/src/env.ts +15 -0
- package/src/i18n.ts +114 -0
- package/src/index.ts +11 -9
- package/src/qr.ts +84 -18
- package/src/state.ts +101 -105
- package/src/types.ts +156 -127
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
{
|
|
2
|
+
"agent.err.createSession": "创建会话失败",
|
|
3
|
+
"agent.err.httpClient": "无法访问 opencode HTTP client",
|
|
4
|
+
"agent.err.unknownPerm": "未知的权限请求: {id}",
|
|
5
|
+
"agent.err.unknownQuestion": "未知的提问请求: {id}",
|
|
6
|
+
"agent.log.fallback404": "回包首选端点 404,回退另一端点: {message}",
|
|
7
|
+
"agent.log.gotPermReply": "收到手机权限回复 (id={id} {reply} {version})",
|
|
8
|
+
"agent.log.gotQuestionReply": "收到手机提问回复 (id={id} {status} {version})",
|
|
9
|
+
"agent.log.handleFailed": "处理 {type} 失败: {message}",
|
|
10
|
+
"agent.log.httpOk": "opencode HTTP {status} {path}",
|
|
11
|
+
"agent.log.pendingResent": "session.pending: 补发 {session} 积压请求 {count} 条",
|
|
12
|
+
"agent.log.unhandledType": "未处理的消息类型: {type}",
|
|
13
|
+
"events.log.fwdPermission": "转发权限请求到手机 (id={id} tool={tool} {version})",
|
|
14
|
+
"events.log.permissionReplied": "opencode 回执 permission.replied (id={id} {response})",
|
|
15
|
+
"events.log.questionRejected": "opencode 回执 question.rejected (id={id})",
|
|
16
|
+
"events.log.questionReplied": "opencode 回执 question.replied (id={id})",
|
|
17
|
+
"index.error.connectRelay": "无法连接中继,请检查中继地址后重试",
|
|
18
|
+
"index.error.pairTimeout": "等待配对码超时(中继未响应)",
|
|
19
|
+
"index.log.claimed": "已绑定到手机账号,之后手机从「我的电脑」直接连接,无需再扫码",
|
|
20
|
+
"index.log.connectedRelay": "已连接中继 {url}",
|
|
21
|
+
"index.log.disconnected": "与中继断开,正在自动重连…",
|
|
22
|
+
"index.log.exit": "已退出",
|
|
23
|
+
"index.log.pairCode": "生成配对码: {code}",
|
|
24
|
+
"index.log.pairingStillClaimed": "此配对码仅供同账号的新手机重新配对;本机已绑定,仍可从手机「我的电脑」直接连接。",
|
|
25
|
+
"index.log.pairingPreparing": "正在连接中继并生成配对码…",
|
|
26
|
+
"index.log.permissionToPhone": "权限请求 → 手机审批 (id={id} tool={tool})",
|
|
27
|
+
"index.log.permissionTuiFallback": "app 离线,权限请求由 opencode TUI 兜底 (id={id} tool={tool})",
|
|
28
|
+
"index.log.relayError": "中继错误: {msg}",
|
|
29
|
+
"index.log.resetIdentity": "被账号解绑,正在重置身份并重新生成配对码…",
|
|
30
|
+
"index.log.unknown": "未知",
|
|
31
|
+
"install.codeLine": "配对码: {code}",
|
|
32
|
+
"install.commandInstalled": "已安装命令: {path}",
|
|
33
|
+
"install.done": "完成。已尝试连接中继输出本机配对信息:",
|
|
34
|
+
"install.failConnect": "(未能即时连接中继打印配对信息——中继不可达或当前 Node 过旧。",
|
|
35
|
+
"install.failConnectHint": "重启 opencode 后可用 --print-logs 查看终端二维码,或在 opencode 内输入“显示手机配对码”或 /pair。)",
|
|
36
|
+
"install.hintFresh": "提示:配对码为一次性,约 10 分钟有效;若稍后才启动 opencode,以启动时终端打印或 /pair 取得的最新配对信息为准。",
|
|
37
|
+
"install.pluginInstalled": "已安装插件: {path}",
|
|
38
|
+
"install.qrUnavailable": "(二维码渲染不可用,请使用上方配对码)",
|
|
39
|
+
"install.qrcodeEnsure": "安装 qrcode 到 opencode 配置目录…",
|
|
40
|
+
"install.qrcodeFail": "qrcode 安装失败(不影响使用,仅显示配对码): {err}",
|
|
41
|
+
"install.relayError": "中继返回错误: {msg}",
|
|
42
|
+
"install.scanHint": "手机打开 tunnelbox App「扫一扫配对」,扫描上方二维码即可连接:",
|
|
43
|
+
"pair.heading": "tunnelbox 手机配对",
|
|
44
|
+
"pair.howTo": "在手机上打开 tunnelbox App,用「扫一扫配对」扫描上方二维码,或手动输入配对码。",
|
|
45
|
+
"pair.oneTime": "配对码为一次性,使用后即失效;如需新码,可要求“刷新配对码”。",
|
|
46
|
+
"pair.promptBody": "请帮我查看本机手机远程配对信息:显示配对码与可供扫码的二维码。二维码请放在等宽代码块里并逐字原样保留,方便我用手机扫码。",
|
|
47
|
+
"pair.promptDescription": "显示手机远程配对的配对码与扫码二维码",
|
|
48
|
+
"pair.scanIntro": "**扫一扫配对**:手机打开 tunnelbox App「扫一扫配对」,扫描下方二维码即可自动连接。",
|
|
49
|
+
"perm.promptAuthorize": "opencode 请求授权",
|
|
50
|
+
"perm.promptPatterns": "opencode 请求访问:{list}",
|
|
51
|
+
"perm.promptResource": "opencode 请求访问资源:{list}",
|
|
52
|
+
"perm.promptTool": "opencode 请求执行 {tool}",
|
|
53
|
+
"qr.codeLabel": "配对码",
|
|
54
|
+
"qr.fileWritten": "配对信息已写入: {path}",
|
|
55
|
+
"qr.openHint": "打开 tunnelbox App,用「扫一扫配对」扫描上方二维码,或手动输入配对码。",
|
|
56
|
+
"qr.titleNoType": "tunnelbox 手机配对",
|
|
57
|
+
"qr.titleWithType": "tunnelbox {type} 手机配对",
|
|
58
|
+
"qr.unavailable": "(未安装 qrcode,请使用上方配对码)",
|
|
59
|
+
"claude.errBusy": "该会话正在生成中,请等待完成或先停止",
|
|
60
|
+
"claude.errTurn": "生成出错",
|
|
61
|
+
"claude.pairingStillClaimed": "已绑定手机账号:此配对码仅用于同账号新设备重新配对,当前设备可直接从手机「我的电脑」连接",
|
|
62
|
+
"claude.pairForced": "--pair:已强制申请配对码(仅本次)",
|
|
63
|
+
"claude.pairUnclaimed": "未检测到已绑定账号,开始配对引导:",
|
|
64
|
+
"claude.claimedHint": "已绑定手机账号:从手机「我的电脑」直接连接即可",
|
|
65
|
+
"claude.errStartFailed": "启动失败:",
|
|
66
|
+
"claude.msgStarted": "Claude Code 适配器已启动(Ctrl+C 退出)",
|
|
67
|
+
"claude.warnInvalidMode": "忽略无效 TUNNELBOX_CLAUDE_MODE: {raw}(允许 {list})",
|
|
68
|
+
"claude.warnInvalidThinking": "忽略无效 TUNNELBOX_CLAUDE_THINKING: {raw}(off|adaptive|enabled[:budget])",
|
|
69
|
+
"claude.cmdNew": "新建会话",
|
|
70
|
+
"claude.cmdHelp": "显示帮助",
|
|
71
|
+
"claude.errNoRelay": "未配置中继地址:构建请用 --env 注入默认,运行可用 TUNNELBOX_RELAY_URL 覆盖",
|
|
72
|
+
"claude.logConnected": "已连接中继 {url}",
|
|
73
|
+
"claude.logMsgUncaught": "消息处理未捕获异常: {message}",
|
|
74
|
+
"claude.reasonDisconnect": "中继断开(fail-closed 自动拒绝)",
|
|
75
|
+
"claude.reasonUnbind": "账号解绑(fail-closed 自动拒绝)",
|
|
76
|
+
"claude.logDisconnected": "与中继断开,正在自动重连…",
|
|
77
|
+
"claude.logPairCode": "生成配对码: {code}",
|
|
78
|
+
"claude.logUnhandledType": "未处理的消息类型: {type}",
|
|
79
|
+
"claude.logHandleFailed": "处理 {type} 失败: {message}",
|
|
80
|
+
"claude.newSessionTitle": "新会话",
|
|
81
|
+
"claude.helpText": "支持的命令:\n- /new 新建会话\n- /help 显示帮助",
|
|
82
|
+
"claude.logPromptFailed": "session.prompt 失败: {message}",
|
|
83
|
+
"claude.logAbortSent": "已发送中止 (session={session})",
|
|
84
|
+
"claude.logAbortFailed": "中止失败 (session={session}): {message}",
|
|
85
|
+
"claude.errBadWorkspaceEmpty": "缺少工作区路径",
|
|
86
|
+
"claude.errBadWorkspaceNotDir": "该目录不存在或不可用",
|
|
87
|
+
"claude.logWorkspaceSwitched": "切换工作区: {path}",
|
|
88
|
+
"claude.logUnknownQuestionReply": "未匹配的结构化提问应答 (id={id})",
|
|
89
|
+
"claude.logFlushedQuestions": "已取消 {count} 个待作答提问: {reason}",
|
|
90
|
+
"claude.logFlushedApprovals": "已拒绝 {count} 个待作答请求: {reason}",
|
|
91
|
+
"claude.logPendingResent": "session.pending 补发待办 {count} 条 (session={session})",
|
|
92
|
+
"claude.logSetModeFailed": "setPermissionMode 失败 (session={session}): {message}",
|
|
93
|
+
"claude.errWorkspaceMissing": "工作区不存在: {dir}",
|
|
94
|
+
"claude.logSessionError": "会话出错 (session={session}): {message}",
|
|
95
|
+
"claude.logOfflineDenyTool": "中继离线,自动拒绝工具 {tool}",
|
|
96
|
+
"claude.errOfflineDeny": "中继离线,自动拒绝(fail-closed)",
|
|
97
|
+
"claude.logAuqDisabled": "AskUserQuestion 已关闭(TUNNELBOX_CLAUDE_ASK_USER_QUESTION=0),自动拒绝",
|
|
98
|
+
"claude.errAuqDisabled": "AskUserQuestion 提问未启用",
|
|
99
|
+
"claude.logAuqParseFailed": "AskUserQuestion input 解析失败,自动拒绝(fail-closed)",
|
|
100
|
+
"claude.errAuqParse": "无法解析题目",
|
|
101
|
+
"claude.logAuqSkipped": "AskUserQuestion 未作答/取消 (session={session})",
|
|
102
|
+
"claude.errAuqCancelled": "用户未选择(cancelled)",
|
|
103
|
+
"claude.logAuqDone": "AskUserQuestion 作答完成 (session={session})",
|
|
104
|
+
"claude.promptRunTool": "Claude Code 请求执行 {tool}",
|
|
105
|
+
"claude.errApprovalTimeout": "审批超时,自动拒绝(fail-closed)",
|
|
106
|
+
"claude.errUserDenied": "用户拒绝",
|
|
107
|
+
"claude.errAborted": "请求已中止",
|
|
108
|
+
"claude.logPermToPhone": "审批请求(tool={tool}) → 发送到手机权限卡 permissionID={id}",
|
|
109
|
+
"claude.errRelayUnreachable": "中继不可达,自动拒绝(fail-closed)",
|
|
110
|
+
"claude.logQuestionOffline": "中继离线,结构化提问自动取消",
|
|
111
|
+
"claude.logQuestionToPhone": "结构化提问({count} 题) → 发送到手机 questionID={id}",
|
|
112
|
+
"claude.logDialogOffline": "中继离线,对话框自动取消({tool})",
|
|
113
|
+
"claude.logDialogToPhone": "对话框请求(kind={kind} tool={tool}) → 发送到手机权限卡 permissionID={id}",
|
|
114
|
+
"claude.logAuqDialogParse": "AskUserQuestion payload 解析失败,保持默认行为(session={session})",
|
|
115
|
+
"claude.logDialogUnmapped": "request_user_dialog(kind={kind}) 未接入映射,保持默认行为(session={session})",
|
|
116
|
+
"claude.promptMcpAuth": "MCP「{server}」请求授权",
|
|
117
|
+
"claude.logElicitUnmapped": "MCP elicitation(server={server}, mode={mode}) 未接入,自动拒绝(session={session})",
|
|
118
|
+
"claude.logResetIdentity": "被账号解绑,正在重置身份并重新生成配对码…",
|
|
119
|
+
"claude.dialogConfirm": "Claude Code 请求确认({kind})",
|
|
120
|
+
"claude.resultTruncated": "…(结果过长已截断)",
|
|
121
|
+
"claude.streamError": "claude 流式出错",
|
|
122
|
+
"claude.errListSessions": "读取 Claude 会话列表失败",
|
|
123
|
+
"claude.errReadHistory": "读取会话历史失败({id})",
|
|
124
|
+
"setup.langSet": "已设置运行语言",
|
|
125
|
+
"setup.langHint": "重启适配器后按该语言输出(运行时可被 TUNNELBOX_LANG 覆盖)。状态文件:",
|
|
126
|
+
"setup.langCurrent": "当前运行语言",
|
|
127
|
+
"hermes.errNoRelay": "未配置中继地址:构建请用 --env 注入默认,运行可用 TUNNELBOX_RELAY_URL 覆盖",
|
|
128
|
+
"hermes.logConnected": "已连接中继 {url}",
|
|
129
|
+
"hermes.logMsgUncaught": "消息处理未捕获异常: {message}",
|
|
130
|
+
"hermes.logDisconnected": "与中继断开,正在自动重连…",
|
|
131
|
+
"hermes.logPairCode": "生成配对码: {code}",
|
|
132
|
+
"hermes.logUnhandledType": "未处理的消息类型: {type}",
|
|
133
|
+
"hermes.logHandleFailed": "处理 {type} 失败: {message}",
|
|
134
|
+
"hermes.logClaimed": "已绑定到手机账号,之后手机从「我的电脑」直接连接",
|
|
135
|
+
"hermes.logResetIdentity": "被账号解绑,正在重置身份并重新生成配对码…",
|
|
136
|
+
"hermes.cmdNew": "新建会话",
|
|
137
|
+
"hermes.cmdHelp": "显示帮助",
|
|
138
|
+
"hermes.newSessionTitle": "新会话",
|
|
139
|
+
"hermes.helpText": "支持的命令:\n- /new 新建会话\n- /help 显示帮助",
|
|
140
|
+
"hermes.errBusy": "该会话正在生成中,请等待完成或先停止",
|
|
141
|
+
"hermes.errBadWorkspace": "该目录不存在或不可用",
|
|
142
|
+
"hermes.logWorkspaceSwitched": "切换工作区: {path}",
|
|
143
|
+
"hermes.errWorkspaceMissing": "工作区不存在: {dir}",
|
|
144
|
+
"hermes.errStartFailed": "启动失败: {message}",
|
|
145
|
+
"hermes.msgStarted": "Hermes 适配器已启动(Ctrl+C 退出)",
|
|
146
|
+
"hermes.logAbortSent": "已发送中止 (session={session})",
|
|
147
|
+
"hermes.logPromptFailed": "session.prompt 失败: {message}",
|
|
148
|
+
"hermes.logTurnError": "回合出错 (session={session}): {message}",
|
|
149
|
+
"hermes.logPairForced": "--pair:已强制申请配对码(仅本次)",
|
|
150
|
+
"hermes.pairingStillClaimed": "已绑定手机账号:此配对码仅用于同账号新设备重新配对,当前设备可直接从手机「我的电脑」连接",
|
|
151
|
+
"hermes.logPairNoRelay": "未配置中继地址,无法申请配对码",
|
|
152
|
+
"hermes.errSpawnHermes": "无法启动 Hermes({bin}):请确认已安装({err})",
|
|
153
|
+
"hermes.errHermesExit": "hermes 退出({code}): {err}",
|
|
154
|
+
"hermes.logSessionUpdated": "会话已更新 (session={session})",
|
|
155
|
+
"hermes.errNoInject": "手机消息注入不可用(当前会话非交互式或进程未就绪)",
|
|
156
|
+
"hermes.pairNoRelay": "无法连接中继(relay 不可达或 websockets 缺失)",
|
|
157
|
+
"hermes.pairTimeout": "申请配对码超时(中继未响应)",
|
|
158
|
+
"hermes.errToolPair": "tunnelbox_pair 失败: {err}",
|
|
159
|
+
"hermes.errToolStatus": "tunnelbox_status 失败: {err}",
|
|
160
|
+
"hermes.cliUsage": "用法: /tunnelbox <pair|status>\n pair 向中继申请手机配对码并打印\n status 显示中继/绑定/连接状态"
|
|
161
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
{
|
|
2
|
+
"agent.err.createSession": "建立會話失敗",
|
|
3
|
+
"agent.err.httpClient": "無法存取 opencode HTTP client",
|
|
4
|
+
"agent.err.unknownPerm": "未知的權限請求: {id}",
|
|
5
|
+
"agent.err.unknownQuestion": "未知的提問請求: {id}",
|
|
6
|
+
"agent.log.fallback404": "回包首選端點 404,改走另一端點: {message}",
|
|
7
|
+
"agent.log.gotPermReply": "收到手機權限回覆 (id={id} {reply} {version})",
|
|
8
|
+
"agent.log.gotQuestionReply": "收到手機提問回覆 (id={id} {status} {version})",
|
|
9
|
+
"agent.log.handleFailed": "處理 {type} 失敗: {message}",
|
|
10
|
+
"agent.log.httpOk": "opencode HTTP {status} {path}",
|
|
11
|
+
"agent.log.pendingResent": "session.pending: 補發 {session} 積壓請求 {count} 條",
|
|
12
|
+
"agent.log.unhandledType": "未處理的訊息類型: {type}",
|
|
13
|
+
"events.log.fwdPermission": "轉發權限請求到手機 (id={id} tool={tool} {version})",
|
|
14
|
+
"events.log.permissionReplied": "opencode 回執 permission.replied (id={id} {response})",
|
|
15
|
+
"events.log.questionRejected": "opencode 回執 question.rejected (id={id})",
|
|
16
|
+
"events.log.questionReplied": "opencode 回執 question.replied (id={id})",
|
|
17
|
+
"index.error.connectRelay": "無法連接中繼,請檢查中繼位址後重試",
|
|
18
|
+
"index.error.pairTimeout": "等待配對碼逾時(中繼未回應)",
|
|
19
|
+
"index.log.claimed": "已綁定到手機帳號,之後手機直接從「我的電腦」連接,不需再掃碼",
|
|
20
|
+
"index.log.connectedRelay": "已連接中繼 {url}",
|
|
21
|
+
"index.log.disconnected": "與中繼斷線,正在自動重連…",
|
|
22
|
+
"index.log.exit": "已退出",
|
|
23
|
+
"index.log.pairCode": "產生配對碼: {code}",
|
|
24
|
+
"index.log.pairingStillClaimed": "此配對碼僅供同帳號的新手機重新配對;本機已綁定,仍可從手機「我的電腦」直接連接。",
|
|
25
|
+
"index.log.pairingPreparing": "正在連線中繼並產生配對碼…",
|
|
26
|
+
"index.log.permissionToPhone": "權限請求 → 手機審批 (id={id} tool={tool})",
|
|
27
|
+
"index.log.permissionTuiFallback": "app 離線,權限請求由 opencode TUI 代管 (id={id} tool={tool})",
|
|
28
|
+
"index.log.relayError": "中繼錯誤: {msg}",
|
|
29
|
+
"index.log.resetIdentity": "已解除帳號綁定,正在重設身分並重新產生配對碼…",
|
|
30
|
+
"index.log.unknown": "未知",
|
|
31
|
+
"install.codeLine": "配對碼: {code}",
|
|
32
|
+
"install.commandInstalled": "已安裝指令: {path}",
|
|
33
|
+
"install.done": "完成。已嘗試連接中繼輸出本機配對資訊:",
|
|
34
|
+
"install.failConnect": "(未能即時連接中繼列印配對資訊——中繼無法連線或目前 Node 過舊。",
|
|
35
|
+
"install.failConnectHint": "重啟 opencode 後可用 --print-logs 查看終端 QR Code,或是在 opencode 內輸入「顯示手機配對碼」或 /pair。)",
|
|
36
|
+
"install.hintFresh": "提示:配對碼為一次性,約 10 分鐘有效;若稍後才啟動 opencode,以啟動時終端列印或 /pair 取得的最新配對資訊為準。",
|
|
37
|
+
"install.pluginInstalled": "已安裝外掛: {path}",
|
|
38
|
+
"install.qrUnavailable": "(QR Code 無法顯示,請使用上方配對碼)",
|
|
39
|
+
"install.qrcodeEnsure": "正在將 qrcode 安裝到 opencode 設定目錄…",
|
|
40
|
+
"install.qrcodeFail": "qrcode 安裝失敗(不影響使用,僅顯示配對碼): {err}",
|
|
41
|
+
"install.relayError": "中繼傳回錯誤: {msg}",
|
|
42
|
+
"install.scanHint": "手機開啟 tunnelbox App「掃描配對」,掃描上方 QR Code 即可連接:",
|
|
43
|
+
"pair.heading": "tunnelbox 手機配對",
|
|
44
|
+
"pair.howTo": "在手機上開啟 tunnelbox App,用「掃描配對」掃描上方 QR Code,或手動輸入配對碼。",
|
|
45
|
+
"pair.oneTime": "配對碼為一次性,使用後即失效;如需新碼,可要求「重新整理配對碼」。",
|
|
46
|
+
"pair.promptBody": "請幫我查看本機手機遠端配對資訊:顯示配對碼與可供掃碼的 QR Code。QR Code 請放在等寬程式碼區塊內並逐字原樣保留,方便我用手機掃描。",
|
|
47
|
+
"pair.promptDescription": "顯示手機遠端配對的配對碼與掃碼 QR Code",
|
|
48
|
+
"pair.scanIntro": "**掃碼配對**:手機開啟 tunnelbox App「掃描配對」,掃描下方 QR Code 即可自動連接。",
|
|
49
|
+
"perm.promptAuthorize": "opencode 請求授權",
|
|
50
|
+
"perm.promptPatterns": "opencode 請求存取:{list}",
|
|
51
|
+
"perm.promptResource": "opencode 請求存取資源:{list}",
|
|
52
|
+
"perm.promptTool": "opencode 請求執行 {tool}",
|
|
53
|
+
"qr.codeLabel": "配對碼",
|
|
54
|
+
"qr.fileWritten": "配對資訊已寫入: {path}",
|
|
55
|
+
"qr.openHint": "開啟 tunnelbox App,用「掃描配對」掃描上方 QR Code,或手動輸入配對碼。",
|
|
56
|
+
"qr.titleNoType": "tunnelbox 手機配對",
|
|
57
|
+
"qr.titleWithType": "tunnelbox {type} 手機配對",
|
|
58
|
+
"qr.unavailable": "(未安裝 qrcode,請使用上方配對碼)",
|
|
59
|
+
"claude.errBusy": "該會話正在產生中,請等待完成或先停止",
|
|
60
|
+
"claude.errTurn": "產生出錯",
|
|
61
|
+
"claude.pairingStillClaimed": "已綁定手機帳號:此配對碼僅供同帳號新裝置重新配對,目前裝置可直接從手機「我的電腦」連接",
|
|
62
|
+
"claude.pairForced": "--pair:已強制申請配對碼(僅本次)",
|
|
63
|
+
"claude.pairUnclaimed": "未偵測到已綁定帳號,開始配對引導:",
|
|
64
|
+
"claude.claimedHint": "已綁定手機帳號:從手機「我的電腦」直接連接即可",
|
|
65
|
+
"claude.errStartFailed": "啟動失敗:",
|
|
66
|
+
"claude.msgStarted": "Claude Code 介面卡已啟動(Ctrl+C 結束)",
|
|
67
|
+
"claude.warnInvalidMode": "忽略無效 TUNNELBOX_CLAUDE_MODE: {raw}(允許 {list})",
|
|
68
|
+
"claude.warnInvalidThinking": "忽略無效 TUNNELBOX_CLAUDE_THINKING: {raw}(off|adaptive|enabled[:budget])",
|
|
69
|
+
"claude.cmdNew": "新開會話",
|
|
70
|
+
"claude.cmdHelp": "顯示說明",
|
|
71
|
+
"claude.errNoRelay": "未設定中繼位址:建置請用 --env 注入預設,執行可用 TUNNELBOX_RELAY_URL 覆寫",
|
|
72
|
+
"claude.logConnected": "已連線中繼 {url}",
|
|
73
|
+
"claude.logMsgUncaught": "訊息處理未捕獲例外: {message}",
|
|
74
|
+
"claude.reasonDisconnect": "中繼斷線(fail-closed 自動拒絕)",
|
|
75
|
+
"claude.reasonUnbind": "帳號解除綁定(fail-closed 自動拒絕)",
|
|
76
|
+
"claude.logDisconnected": "與中繼斷線,正在自動重連…",
|
|
77
|
+
"claude.logPairCode": "產生配對碼: {code}",
|
|
78
|
+
"claude.logUnhandledType": "未處理的訊息類型: {type}",
|
|
79
|
+
"claude.logHandleFailed": "處理 {type} 失敗: {message}",
|
|
80
|
+
"claude.newSessionTitle": "新會話",
|
|
81
|
+
"claude.helpText": "支援的指令:\n- /new 新開會話\n- /help 顯示說明",
|
|
82
|
+
"claude.logPromptFailed": "session.prompt 失敗: {message}",
|
|
83
|
+
"claude.logAbortSent": "已送出中止 (session={session})",
|
|
84
|
+
"claude.logAbortFailed": "中止失敗 (session={session}): {message}",
|
|
85
|
+
"claude.errBadWorkspaceEmpty": "缺少工作區路徑",
|
|
86
|
+
"claude.errBadWorkspaceNotDir": "該目錄不存在或不可用",
|
|
87
|
+
"claude.logWorkspaceSwitched": "切換工作區: {path}",
|
|
88
|
+
"claude.logUnknownQuestionReply": "未相符的結構化提問應答 (id={id})",
|
|
89
|
+
"claude.logFlushedQuestions": "已取消 {count} 個待回答提問: {reason}",
|
|
90
|
+
"claude.logFlushedApprovals": "已拒絕 {count} 個待作答請求: {reason}",
|
|
91
|
+
"claude.logPendingResent": "session.pending 補發待辦 {count} 筆 (session={session})",
|
|
92
|
+
"claude.logSetModeFailed": "setPermissionMode 失敗 (session={session}): {message}",
|
|
93
|
+
"claude.errWorkspaceMissing": "工作區不存在: {dir}",
|
|
94
|
+
"claude.logSessionError": "會話出錯 (session={session}): {message}",
|
|
95
|
+
"claude.logOfflineDenyTool": "中繼離線,自動拒絕工具 {tool}",
|
|
96
|
+
"claude.errOfflineDeny": "中繼離線,自動拒絕(fail-closed)",
|
|
97
|
+
"claude.logAuqDisabled": "AskUserQuestion 已關閉(TUNNELBOX_CLAUDE_ASK_USER_QUESTION=0),自動拒絕",
|
|
98
|
+
"claude.errAuqDisabled": "AskUserQuestion 提問未啟用",
|
|
99
|
+
"claude.logAuqParseFailed": "AskUserQuestion input 解析失敗,自動拒絕(fail-closed)",
|
|
100
|
+
"claude.errAuqParse": "無法解析題目",
|
|
101
|
+
"claude.logAuqSkipped": "AskUserQuestion 未作答/取消 (session={session})",
|
|
102
|
+
"claude.errAuqCancelled": "使用者未選擇(cancelled)",
|
|
103
|
+
"claude.logAuqDone": "AskUserQuestion 作答完成 (session={session})",
|
|
104
|
+
"claude.promptRunTool": "Claude Code 請求執行 {tool}",
|
|
105
|
+
"claude.errApprovalTimeout": "審批逾時,自動拒絕(fail-closed)",
|
|
106
|
+
"claude.errUserDenied": "使用者拒絕",
|
|
107
|
+
"claude.errAborted": "請求已中止",
|
|
108
|
+
"claude.logPermToPhone": "審批請求(tool={tool}) → 傳送到手機權限卡 permissionID={id}",
|
|
109
|
+
"claude.errRelayUnreachable": "中繼不可達,自動拒絕(fail-closed)",
|
|
110
|
+
"claude.logQuestionOffline": "中繼離線,結構化提問自動取消",
|
|
111
|
+
"claude.logQuestionToPhone": "結構化提問({count} 題) → 傳送到手機 questionID={id}",
|
|
112
|
+
"claude.logDialogOffline": "中繼離線,對話框自動取消({tool})",
|
|
113
|
+
"claude.logDialogToPhone": "對話框請求(kind={kind} tool={tool}) → 傳送到手機權限卡 permissionID={id}",
|
|
114
|
+
"claude.logAuqDialogParse": "AskUserQuestion payload 解析失敗,維持預設行為(session={session})",
|
|
115
|
+
"claude.logDialogUnmapped": "request_user_dialog(kind={kind}) 未接入對應,維持預設行為(session={session})",
|
|
116
|
+
"claude.promptMcpAuth": "MCP「{server}」請求授權",
|
|
117
|
+
"claude.logElicitUnmapped": "MCP elicitation(server={server}, mode={mode}) 未接入,自動拒絕(session={session})",
|
|
118
|
+
"claude.logResetIdentity": "被帳號解除綁定,正在重設身分並重新產生配對碼…",
|
|
119
|
+
"claude.dialogConfirm": "Claude Code 請求確認({kind})",
|
|
120
|
+
"claude.resultTruncated": "…(結果過長已截斷)",
|
|
121
|
+
"claude.streamError": "claude 串流出錯",
|
|
122
|
+
"claude.errListSessions": "讀取 Claude 會話列表失敗",
|
|
123
|
+
"claude.errReadHistory": "讀取會話歷史失敗({id})",
|
|
124
|
+
"setup.langSet": "已設定執行語言",
|
|
125
|
+
"setup.langHint": "重新啟動介面卡後將以該語言輸出(執行時可被 TUNNELBOX_LANG 覆寫)。狀態檔:",
|
|
126
|
+
"setup.langCurrent": "目前執行語言",
|
|
127
|
+
"hermes.errNoRelay": "未設定中繼位址:建置請用 --env 注入預設,執行可用 TUNNELBOX_RELAY_URL 覆寫",
|
|
128
|
+
"hermes.logConnected": "已連線中繼 {url}",
|
|
129
|
+
"hermes.logMsgUncaught": "訊息處理未捕獲例外: {message}",
|
|
130
|
+
"hermes.logDisconnected": "與中繼斷線,正在自動重新連線…",
|
|
131
|
+
"hermes.logPairCode": "產生配對碼: {code}",
|
|
132
|
+
"hermes.logUnhandledType": "未處理的訊息類型: {type}",
|
|
133
|
+
"hermes.logHandleFailed": "處理 {type} 失敗: {message}",
|
|
134
|
+
"hermes.logClaimed": "已綁定到手機帳號,之後手機從「我的電腦」直接連線",
|
|
135
|
+
"hermes.logResetIdentity": "被帳號解綁,正在重設身分並重新產生配對碼…",
|
|
136
|
+
"hermes.cmdNew": "新增會話",
|
|
137
|
+
"hermes.cmdHelp": "顯示說明",
|
|
138
|
+
"hermes.newSessionTitle": "新會話",
|
|
139
|
+
"hermes.helpText": "支援的命令:\n- /new 新增會話\n- /help 顯示說明",
|
|
140
|
+
"hermes.errBusy": "此會話正在產生中,請等待完成或先停止",
|
|
141
|
+
"hermes.errBadWorkspace": "此目錄不存在或無法使用",
|
|
142
|
+
"hermes.logWorkspaceSwitched": "切換工作區: {path}",
|
|
143
|
+
"hermes.errWorkspaceMissing": "工作區不存在: {dir}",
|
|
144
|
+
"hermes.errStartFailed": "啟動失敗: {message}",
|
|
145
|
+
"hermes.msgStarted": "Hermes 介接器已啟動(Ctrl+C 退出)",
|
|
146
|
+
"hermes.logAbortSent": "已傳送中止 (session={session})",
|
|
147
|
+
"hermes.logPromptFailed": "session.prompt 失敗: {message}",
|
|
148
|
+
"hermes.logTurnError": "回合出錯 (session={session}): {message}",
|
|
149
|
+
"hermes.logPairForced": "--pair:已強制申請配對碼(僅本次)",
|
|
150
|
+
"hermes.pairingStillClaimed": "已綁定手機帳號:此配對碼僅供同帳號新裝置重新配對,目前裝置可直接從手機「我的電腦」連線",
|
|
151
|
+
"hermes.logPairNoRelay": "未設定中繼位址,無法申請配對碼",
|
|
152
|
+
"hermes.errSpawnHermes": "無法啟動 Hermes({bin}):請確認已安裝({err})",
|
|
153
|
+
"hermes.errHermesExit": "hermes 退出({code}): {err}",
|
|
154
|
+
"hermes.logSessionUpdated": "會話已更新 (session={session})",
|
|
155
|
+
"hermes.errNoInject": "手機訊息注入不可用(目前工作階段非互動式或處理程序未就緒)",
|
|
156
|
+
"hermes.pairNoRelay": "無法連線中繼(relay 不可達或 websockets 缺失)",
|
|
157
|
+
"hermes.pairTimeout": "申請配對碼逾時(中繼未回應)",
|
|
158
|
+
"hermes.errToolPair": "tunnelbox_pair 失敗: {err}",
|
|
159
|
+
"hermes.errToolStatus": "tunnelbox_status 失敗: {err}",
|
|
160
|
+
"hermes.cliUsage": "用法: /tunnelbox <pair|status>\n pair 向中繼申請手機配對碼並列印\n status 顯示中繼/綁定/連線狀態"
|
|
161
|
+
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tunnelbox/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "tunnelbox 共享核心:协议类型、中继客户端、状态持久化、二维码配对",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
7
|
"types": "./src/index.ts",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
10
13
|
},
|
|
11
14
|
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"locales",
|
|
12
17
|
"src"
|
|
13
18
|
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
14
22
|
"scripts": {
|
|
15
|
-
"type-check": "tsc --noEmit"
|
|
23
|
+
"type-check": "tsc --noEmit",
|
|
24
|
+
"check:i18n": "node scripts/check-locales.mjs",
|
|
25
|
+
"build": "node scripts/build.mjs",
|
|
26
|
+
"build:dev": "node scripts/build.mjs",
|
|
27
|
+
"build:test": "node scripts/build.mjs",
|
|
28
|
+
"build:prod": "node scripts/build.mjs"
|
|
16
29
|
},
|
|
17
30
|
"dependencies": {
|
|
18
31
|
"qrcode": "^1.5.3"
|
|
@@ -20,6 +33,7 @@
|
|
|
20
33
|
"devDependencies": {
|
|
21
34
|
"@types/node": "^22.0.0",
|
|
22
35
|
"@types/qrcode": "^1.5.5",
|
|
36
|
+
"esbuild": "^0.21.5",
|
|
23
37
|
"typescript": "^5.4.5"
|
|
24
38
|
}
|
|
25
39
|
}
|
package/src/env.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 构建期注入的环境默认中继地址。
|
|
3
|
+
*
|
|
4
|
+
* 设计口径:源码不写死任何中继 URL;构建/安装时 `--env <development|test|production>`
|
|
5
|
+
* 读取 plugin/env/.env.<env>,经 esbuild `define: { __TB_DEFAULT_RELAY__ }` 注入产物。
|
|
6
|
+
* 未注入(源码直跑)或运行时显式 TUNNELBOX_RELAY_URL / 配置已给时,返回 ""。
|
|
7
|
+
*/
|
|
8
|
+
declare const __TB_DEFAULT_RELAY__: string | undefined;
|
|
9
|
+
|
|
10
|
+
/** 构建注入的中继默认值;无注入返回空串。运行期优先级:显式配置/env > 本默认。 */
|
|
11
|
+
export function injectedRelayUrl(): string {
|
|
12
|
+
return typeof __TB_DEFAULT_RELAY__ === "string" && __TB_DEFAULT_RELAY__
|
|
13
|
+
? __TB_DEFAULT_RELAY__.replace(/\/+$/, "")
|
|
14
|
+
: "";
|
|
15
|
+
}
|
package/src/i18n.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 多语言支持(与 App 端 web/src/locales 保持同一套语言标签)。
|
|
3
|
+
*
|
|
4
|
+
* 文案唯一数据源:`plugin/core/locales/*.json`(zh-CN/en-US/ja-JP/ko-KR/fr-FR/de-DE/es-ES/zh-TW)。
|
|
5
|
+
* 运行时由 esbuild 将 JSON 内联打包进适配器单文件 dist;install-local 直接读同一 JSON 文件。
|
|
6
|
+
*
|
|
7
|
+
* 语言优先级(resolveLang):
|
|
8
|
+
* 显式配置(opts.language) > 环境变量 TUNNELBOX_LANG > 持久化 state.lang > 系统 LANG/LC_ALL > en-US 默认
|
|
9
|
+
*/
|
|
10
|
+
import zhCN from "../locales/zh-CN.json";
|
|
11
|
+
import zhTW from "../locales/zh-TW.json";
|
|
12
|
+
import enUS from "../locales/en-US.json";
|
|
13
|
+
import jaJP from "../locales/ja-JP.json";
|
|
14
|
+
import koKR from "../locales/ko-KR.json";
|
|
15
|
+
import frFR from "../locales/fr-FR.json";
|
|
16
|
+
import deDE from "../locales/de-DE.json";
|
|
17
|
+
import esES from "../locales/es-ES.json";
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_LANG = "en-US";
|
|
20
|
+
|
|
21
|
+
export const LANG_TAGS = [
|
|
22
|
+
"zh-CN",
|
|
23
|
+
"zh-TW",
|
|
24
|
+
"en-US",
|
|
25
|
+
"ja-JP",
|
|
26
|
+
"ko-KR",
|
|
27
|
+
"fr-FR",
|
|
28
|
+
"de-DE",
|
|
29
|
+
"es-ES",
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
export type Lang = (typeof LANG_TAGS)[number];
|
|
33
|
+
|
|
34
|
+
/** 文案字典:与 check-locales 脚本配合,保证 8 个文件 key 集合一致。 */
|
|
35
|
+
const dicts: Record<string, Record<string, string>> = {
|
|
36
|
+
"zh-CN": zhCN as unknown as Record<string, string>,
|
|
37
|
+
"zh-TW": zhTW as unknown as Record<string, string>,
|
|
38
|
+
"en-US": enUS as unknown as Record<string, string>,
|
|
39
|
+
"ja-JP": jaJP as unknown as Record<string, string>,
|
|
40
|
+
"ko-KR": koKR as unknown as Record<string, string>,
|
|
41
|
+
"fr-FR": frFR as unknown as Record<string, string>,
|
|
42
|
+
"de-DE": deDE as unknown as Record<string, string>,
|
|
43
|
+
"es-ES": esES as unknown as Record<string, string>,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const zhKeys = zhCN as unknown as Record<string, string>;
|
|
47
|
+
export type LocaleKey = keyof typeof zhKeys & string;
|
|
48
|
+
|
|
49
|
+
export interface TranslateVars {
|
|
50
|
+
[name: string]: string | number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 规范化语言标签:zh-CN/zh/zh_Hans→zh-CN,zh-TW/zh-Hant→zh-TW,其余按基语言映射;不支持→默认。 */
|
|
54
|
+
export function normalizeLang(raw?: string | null): Lang {
|
|
55
|
+
if (!raw) return DEFAULT_LANG;
|
|
56
|
+
const s = raw.trim().replace(/_/g, "-").toLowerCase();
|
|
57
|
+
if (!s) return DEFAULT_LANG;
|
|
58
|
+
if (s.startsWith("zh")) {
|
|
59
|
+
if (s.startsWith("zh-tw") || s.includes("hant")) return "zh-TW";
|
|
60
|
+
return "zh-CN";
|
|
61
|
+
}
|
|
62
|
+
const base = s.split("-")[0];
|
|
63
|
+
const map: Record<string, Lang> = {
|
|
64
|
+
en: "en-US",
|
|
65
|
+
ja: "ja-JP",
|
|
66
|
+
ko: "ko-KR",
|
|
67
|
+
fr: "fr-FR",
|
|
68
|
+
de: "de-DE",
|
|
69
|
+
es: "es-ES",
|
|
70
|
+
};
|
|
71
|
+
return map[base] ?? DEFAULT_LANG;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 从系统环境变量(LANG/LC_ALL/LC_MESSAGES)探测语言;拿不到返回 null。 */
|
|
75
|
+
export function detectSystemLang(): string | null {
|
|
76
|
+
for (const k of ["LC_ALL", "LC_MESSAGES", "LANG"]) {
|
|
77
|
+
const v = process.env[k];
|
|
78
|
+
if (v && v.trim()) return v.trim().split(".")[0];
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ResolveLangOptions {
|
|
84
|
+
/** 显式配置(如 opencode.json 插件 options.language) */
|
|
85
|
+
config?: string | null;
|
|
86
|
+
/** 持久化语言(如 remote-state.json 的 lang) */
|
|
87
|
+
saved?: string | null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 按优先级解析语言:config > TUNNELBOX_LANG > saved > 系统检测 > en-US。 */
|
|
91
|
+
export function resolveLang(opts: ResolveLangOptions = {}): Lang {
|
|
92
|
+
const env = process.env.TUNNELBOX_LANG;
|
|
93
|
+
if (opts.config) return normalizeLang(opts.config);
|
|
94
|
+
if (env) return normalizeLang(env);
|
|
95
|
+
if (opts.saved) return normalizeLang(opts.saved);
|
|
96
|
+
return normalizeLang(detectSystemLang());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type Translator = (key: LocaleKey, vars?: TranslateVars) => string;
|
|
100
|
+
|
|
101
|
+
export function t(lang: Lang, key: string, vars?: TranslateVars): string {
|
|
102
|
+
const dict = dicts[lang] ?? dicts[DEFAULT_LANG];
|
|
103
|
+
let text = dict[key] ?? dicts[DEFAULT_LANG][key] ?? key;
|
|
104
|
+
if (vars) {
|
|
105
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
106
|
+
text = text.replaceAll(`{${k}}`, String(v));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return text;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function makeT(lang: Lang): Translator {
|
|
113
|
+
return (key, vars) => t(lang, key, vars);
|
|
114
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @tunnelbox/core —— 共享核心。
|
|
3
|
-
* 供各智能体适配器(opencode 插件 / dsh / claude-code / codex / …)复用:
|
|
4
|
-
* 统一协议类型、中继客户端(静默重连)、状态持久化、二维码配对。
|
|
5
|
-
*/
|
|
6
|
-
export * from "./types";
|
|
7
|
-
export * from "./relay";
|
|
8
|
-
export * from "./state";
|
|
9
|
-
export * from "./
|
|
1
|
+
/**
|
|
2
|
+
* @tunnelbox/core —— 共享核心。
|
|
3
|
+
* 供各智能体适配器(opencode 插件 / dsh / claude-code / codex / …)复用:
|
|
4
|
+
* 统一协议类型、中继客户端(静默重连)、状态持久化、二维码配对。
|
|
5
|
+
*/
|
|
6
|
+
export * from "./types";
|
|
7
|
+
export * from "./relay";
|
|
8
|
+
export * from "./state";
|
|
9
|
+
export * from "./env";
|
|
10
|
+
export * from "./qr";
|
|
11
|
+
export * from "./i18n";
|