@tunnelbox/core 0.1.20 → 0.1.22

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.
@@ -273,5 +273,73 @@
273
273
  "adapter.logQuestionNative": "選擇請求 → 手機選擇卡 questionID={id} (session={session})",
274
274
  "adapter.logPermissionNative": "審批請求 → 手機權限卡 permissionID={id} (session={session})",
275
275
  "adapter.checkAppServerOk": "codex app-server 可用",
276
- "adapter.checkAppServerMissing": "目前 Codex CLI 不支援 `codex app-server`(請升級 Codex CLI)"
276
+ "adapter.checkAppServerMissing": "目前 Codex CLI 不支援 `codex app-server`(請升級 Codex CLI)",
277
+ "adapter.warnCursorCliMissing": "未找到 Cursor CLI 執行檔 \"{bin}\":請安裝(https://cursor.com/docs/cli),或用 TUNNELBOX_CURSOR_BIN 指定路徑",
278
+ "adapter.checkCursorCliMissing": "未找到 Cursor CLI 執行檔({bin}):請從 https://cursor.com/docs/cli 安裝",
279
+ "adapter.checkCursorAcpHint": "已偵測到 Cursor CLI;將使用 ACP 模式(`agent acp`)提供串流/審批/選擇",
280
+ "adapter.errAcpSpawn": "啟動 cursor `agent acp` 失敗:{message}(請確認 Cursor CLI 支援 `agent acp`)",
281
+ "adapter.errAcpNoSession": "Cursor ACP 未返回工作階段 id",
282
+ "adapter.logResumeFailed": "工作階段恢復失敗,改為建立新工作階段:{message}",
283
+ "adapter.logPlanNative": "計畫審批 → 手機審批卡 permissionID={id} (session={session})",
284
+ "adapter.errTimeout": "等待逾時,自動拒絕(fail-closed)",
285
+ "adapter.planApprovalPrompt": "Cursor 請求批准一項計畫",
286
+ "adapter.checkCursorHeader": "tunnelbox-cursor 環境自檢",
287
+ "adapter.errAskBridge": "啟動 ask 橋失敗:{message}",
288
+ "adapter.logAskBridge": "MCP ask 橋已監聽 {url}",
289
+ "cursor.mcpAskDesc": "讓使用者在手機上(tunnelbox)做選擇,並阻塞等待其作答。只要需要使用者在繼續前於若干選項中做出選擇(如選餐、選模式、選方案)就呼叫它。回傳使用者所選的選項文字。",
290
+ "cursor.mcpAskTitleDesc": "手機卡片上顯示的標題(可選)",
291
+ "cursor.mcpAskQuestionsDesc": "一個或多個問題;每個問題帶可選項",
292
+ "cursor.mcpPlanDesc": "讓使用者在手機上(tunnelbox)批准一項計畫,並阻塞等待其應答。回傳計畫是否被接受。",
293
+ "cursor.mcpNoBridge": "tunnelbox ask 橋不可用",
294
+ "cursor.mcpUnanswered": "使用者未在手機上作答(離線或逾時)。請繼續,或用對話方式詢問。",
295
+ "cursor.mcpRejected": "使用者在手機上拒絕了這個提問。",
296
+ "cursor.mcpAnswersHeader": "使用者(手機)的選擇:",
297
+ "cursor.mcpAccepted": "使用者已接受該計畫。",
298
+ "cursor.mcpDenied": "使用者拒絕了該計畫。",
299
+ "cursor.mcpPlanUnanswered": "使用者未在手機上回應計畫(離線或逾時)。",
300
+ "cursor.mcpAskNoQuestions": "未提供任何問題。",
301
+ "cursor.mcpFailed": "tunnelbox ask 呼叫失敗",
302
+ "adapter.errCursorExitCode": "Cursor CLI 退出碼 {code}",
303
+ "adapter.errCursorExit": "Cursor CLI 退出({code}): {err}",
304
+ "adapter.errCursorSpawn": "無法啟動 Cursor CLI(agent):請確認已安裝並登入({message})",
305
+ "adapter.errCursorStart": "Cursor CLI 啟動失敗: {message}",
306
+ "adapter.errCursorGeneric": "Cursor 出錯",
307
+ "qr.downloadApp": "下載 tunnelbox App(Android / iOS):{url}",
308
+ "adapter.logLegacyHookRemoved": "已移除舊的 PermissionRequest hook(hooks.json)",
309
+ "adapter.errAppServerNotRunning": "codex app-server 未執行",
310
+ "adapter.errAppServerTimeout": "app-server 請求逾時:{method}",
311
+ "adapter.errAppServerStopped": "app-server 用戶端已停止",
312
+ "adapter.errAppServerExited": "codex app-server 結束(code={code} signal={signal})",
313
+ "adapter.errAppServerError": "app-server 錯誤:{message}",
314
+ "adapter.logAppServerStderr": "app-server:{line}",
315
+ "adapter.logAppServerExited": "app-server 結束(code={code} signal={signal})",
316
+ "adapter.errDshGenerate": "dsh 產生錯誤",
317
+ "adapter.unknown": "未知",
318
+ "adapter.errGatewayClientMissing": "gateway client 未建立",
319
+ "adapter.errGatewayNotConnected": "gateway 未連線",
320
+ "adapter.sessionTitleWithId": "{name} 工作階段 {id}",
321
+ "adapter.promptApproveTool": "{name} 請求批准執行 {tool}",
322
+ "index.log.serverStart": "opencode server={server} baseUrl={baseUrl} auth={auth} lang={lang}",
323
+ "agent.err.http": "HTTP {status}:{text}",
324
+ "claude.promptUrl": "URL:{url}",
325
+ "hermes.errBridgeNotRunning": "bridge loop 未執行",
326
+ "hermes.errAskNoQuestion": "未提供問題。請傳入 questions=[{question, choices?, multi_select?}, ...]。",
327
+ "hermes.toolPairDesc": "向 tunnelbox 中繼申請一次性手機配對碼(供手機 tunnelbox App 掃碼/輸碼配對)。當使用者詢問如何用手機連線/配對/掃碼/遠端控制本機時使用。",
328
+ "hermes.toolPairRefreshDesc": "是否強制申請新配對碼(預設重用/忽略)",
329
+ "hermes.toolStatusDesc": "返回 tunnelbox 外掛狀態:中繼位址、agent 標識、是否已綁定手機帳號、資料目錄與最近配對資訊。當使用者詢問與手機的 tunnelbox 連線狀態/是否綁定/中繼位址時使用。",
330
+ "hermes.toolAskDesc": "把一個問題/選擇發到使用者的 tunnelbox 手機(遠端 UI)並等待其選擇或輸入。返回 JSON:{answered: bool, user_response: string|string[]|null}。手機離線或逾時會 answered=false。",
331
+ "hermes.toolAskQuestionDesc": "要問的問題文字",
332
+ "hermes.toolAskChoicesDesc": "預置選項(可選;最多 4 個)",
333
+ "hermes.toolAskMultipleDesc": "是否多選(需提供 choices)",
334
+ "hermes.cmdDesc": "tunnelbox:手機配對 / 連線狀態",
335
+ "hermes.logConfigLoadFailed": "tunnelbox: 讀取設定失敗,使用預設值: {err}",
336
+ "hermes.logRegisterToolsFailed": "tunnelbox: 註冊工具失敗: {err}",
337
+ "hermes.logRegisterCommandFailed": "tunnelbox: 註冊命令失敗: {err}",
338
+ "hermes.logRegisterSkillFailed": "tunnelbox: 註冊技能失敗: {err}",
339
+ "hermes.logRegisterHooksFailed": "tunnelbox: 註冊 hooks 失敗: {err}",
340
+ "hermes.logRegisterApprovalFailed": "tunnelbox: 註冊 approval transport 失敗: {err}",
341
+ "hermes.logRegisterAskFailed": "tunnelbox: 註冊 tunnelbox_ask 失敗: {err}",
342
+ "hermes.logRegisterSystemPromptFailed": "tunnelbox: 註冊 system prompt 段失敗: {err}",
343
+ "hermes.logBackgroundBridgeFailed": "tunnelbox: 背景橋啟動失敗: {err}",
344
+ "hermes.systemPromptAsk": "當你需要使用者做決定/澄清/選擇,且已連接 tunnelbox 手機時,請用 `tunnelbox_ask` 工具在手機上提問,而不是自行猜測;等到使用者作答後再繼續。"
277
345
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tunnelbox/core",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "tunnelbox 共享核心:协议类型、中继客户端、状态持久化、二维码配对",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/i18n.ts CHANGED
@@ -71,12 +71,18 @@ export function normalizeLang(raw?: string | null): Lang {
71
71
  return map[base] ?? DEFAULT_LANG;
72
72
  }
73
73
 
74
- /** 从系统环境变量(LANG/LC_ALL/LC_MESSAGES)探测语言;拿不到返回 null。 */
74
+ /** 从系统环境变量(LANG/LC_ALL/LC_MESSAGES)探测语言;拿不到则用运行时默认 locale(跨平台,含 Windows);仍拿不到返回 null。 */
75
75
  export function detectSystemLang(): string | null {
76
76
  for (const k of ["LC_ALL", "LC_MESSAGES", "LANG"]) {
77
77
  const v = process.env[k];
78
78
  if (v && v.trim()) return v.trim().split(".")[0];
79
79
  }
80
+ try {
81
+ const loc = Intl.DateTimeFormat().resolvedOptions().locale;
82
+ if (loc && loc.trim()) return loc.trim();
83
+ } catch {
84
+ /* ignore */
85
+ }
80
86
  return null;
81
87
  }
82
88
 
package/src/index.ts CHANGED
@@ -11,3 +11,4 @@ export * from "./qr";
11
11
  export * from "./i18n";
12
12
  export * from "./messages";
13
13
  export * from "./cli";
14
+ export * from "./terminal";
package/src/qr.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { writePairingFile } from "./state";
6
6
  import { normalizeLang, resolveLang, t, type Lang } from "./i18n";
7
+ import { hyperlink, websiteUrl } from "./terminal";
7
8
 
8
9
  /** 加载 qrcode 渲染器(可选依赖,加载失败返回 null)。 */
9
10
  async function loadQrcode(): Promise<{
@@ -90,6 +91,7 @@ export async function printPairing(
90
91
  console.log("│ " + padVisual(codeLine, W - 2) + " │");
91
92
  console.log("└" + bar + "┘");
92
93
  console.log(" " + hint);
94
+ console.log(" " + tt("qr.downloadApp", { url: hyperlink(websiteUrl()) }));
93
95
  console.log(" " + tt("qr.validFor"));
94
96
 
95
97
  try {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * 终端链接辅助:在支持的终端把 URL 渲染为**可点击超链接**(OSC 8),否则回退纯文本。
3
+ * 供各适配器配对输出复用(官网下载引导)。默认自动探测,可用环境变量覆盖:
4
+ * - TUNNELBOX_HYPERLINK=1|0 强制开启/关闭
5
+ * - TUNNELBOX_WEBSITE_URL 覆盖官网地址
6
+ */
7
+
8
+ /** 官网默认地址(可用 TUNNELBOX_WEBSITE_URL 覆盖)。 */
9
+ export function websiteUrl(): string {
10
+ return (process.env.TUNNELBOX_WEBSITE_URL || "https://www.tunnelbox.top").trim();
11
+ }
12
+
13
+ function envFlag(raw: string | undefined): boolean | null {
14
+ const s = (raw || "").trim().toLowerCase();
15
+ if (["1", "true", "yes", "on"].includes(s)) return true;
16
+ if (["0", "false", "no", "off"].includes(s)) return false;
17
+ return null;
18
+ }
19
+
20
+ /**
21
+ * 是否支持 OSC 8 超链接:仅 TTY + 已知终端;可用 TUNNELBOX_HYPERLINK 强制。
22
+ * 覆盖的终端:Windows Terminal、iTerm2、WezTerm、VS Code、Hyper、Tabby、Warp、
23
+ * VTE 系(GNOME Terminal 等)、Konsole、kitty。
24
+ */
25
+ export function supportsHyperlinks(): boolean {
26
+ const override = envFlag(process.env.TUNNELBOX_HYPERLINK);
27
+ if (override !== null) return override;
28
+ if (!process.stdout || !process.stdout.isTTY) return false;
29
+ const env = process.env;
30
+ if (env.WT_SESSION) return true; // Windows Terminal
31
+ if (env.WEZTERM_PANE) return true;
32
+ if (env.KITTY_WINDOW_ID) return true;
33
+ if (env.VTE_VERSION) return true; // GNOME Terminal 等 VTE
34
+ if (env.KONSOLE_VERSION) return true;
35
+ const tp = env.TERM_PROGRAM || "";
36
+ return /^(iTerm\.app|WezTerm|vscode|Hyper|Apple_Terminal|Tabby|WarpTerminal)$/i.test(tp);
37
+ }
38
+
39
+ /** OSC 8 超链接;不支持时返回纯文本(默认显示 URL 本身,仍可复制)。 */
40
+ export function hyperlink(url: string, text?: string): string {
41
+ const label = text ?? url;
42
+ if (!supportsHyperlinks()) return label;
43
+ const OSC = "\u001B]8;;";
44
+ const ST = "\u001B\\";
45
+ return `${OSC}${url}${ST}${label}${OSC}${ST}`;
46
+ }