tmex-cli 0.12.3-beta1 → 0.12.3

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 (73) hide show
  1. package/CHANGELOG.md +3 -11
  2. package/dist/cli-node.js +98 -287
  3. package/dist/runtime/{cpufeatures-nvqtn6ne.node → cpufeatures-dxrn1j88.node} +0 -0
  4. package/dist/runtime/server.js +255 -31
  5. package/dist/runtime/{sshcrypto-rfw667cw.node → sshcrypto-fjcj736m.node} +0 -0
  6. package/package.json +1 -1
  7. package/resources/fe-dist/assets/DevicePage-EoPdR0a8.js +15 -0
  8. package/resources/fe-dist/assets/{DevicesPage-BXVj9MYn.js → DevicesPage-DaO7f0iC.js} +2 -2
  9. package/resources/fe-dist/assets/{FilePage-DFvyeBg8.js → FilePage-BQsLFpIe.js} +2 -2
  10. package/resources/fe-dist/assets/{SettingsPage-B8daCB8Y.js → SettingsPage-DNq70CL7.js} +5 -10
  11. package/resources/fe-dist/assets/{arc-B20iCBM1.js → arc-Bw8pvFsh.js} +2 -2
  12. package/resources/fe-dist/assets/architectureDiagram-3BPJPVTR-ClXPEeos.js +37 -0
  13. package/resources/fe-dist/assets/{blockDiagram-GPEHLZMM-BqP9dRVx.js → blockDiagram-GPEHLZMM-Sp24bCmA.js} +2 -2
  14. package/resources/fe-dist/assets/{c4Diagram-AAUBKEIU-DbqKeZkc.js → c4Diagram-AAUBKEIU-DjyOwpgl.js} +2 -2
  15. package/resources/fe-dist/assets/{card-DnWixV3q.js → card-CMyk5OFt.js} +2 -2
  16. package/resources/fe-dist/assets/channel-B9WrZCGJ.js +2 -0
  17. package/resources/fe-dist/assets/{chunk-2J33WTMH-BHZUy1d7.js → chunk-2J33WTMH-_i-tgxvN.js} +2 -2
  18. package/resources/fe-dist/assets/{chunk-4BX2VUAB-QKjwiFAV.js → chunk-4BX2VUAB-CYDjMAJT.js} +2 -2
  19. package/resources/fe-dist/assets/{chunk-55IACEB6-aUtPslwp.js → chunk-55IACEB6-CGcgNWHA.js} +2 -2
  20. package/resources/fe-dist/assets/{chunk-727SXJPM-DetOPTEw.js → chunk-727SXJPM-CVF1o51L.js} +2 -2
  21. package/resources/fe-dist/assets/{chunk-AQP2D5EJ-Cj4pjkf1.js → chunk-AQP2D5EJ-BzXiGlmX.js} +2 -2
  22. package/resources/fe-dist/assets/{chunk-FMBD7UC4-DuMfjqmS.js → chunk-FMBD7UC4-DG7P62CK.js} +2 -2
  23. package/resources/fe-dist/assets/{chunk-ND2GUHAM-De6BOrhf.js → chunk-ND2GUHAM-C82kWDQB.js} +2 -2
  24. package/resources/fe-dist/assets/{chunk-QZHKN3VN-C6UKMTXf.js → chunk-QZHKN3VN-FB0r6Osl.js} +2 -2
  25. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-B1-CPF_4.js +2 -0
  26. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-B1-CPF_4.js +2 -0
  27. package/resources/fe-dist/assets/cose-bilkent-S5V4N54A--v8cfu0h.js +2 -0
  28. package/resources/fe-dist/assets/{dagre-BM42HDAG-DLE28RsS.js → dagre-BM42HDAG-BTLOD-KX.js} +2 -2
  29. package/resources/fe-dist/assets/{diagram-2AECGRRQ-CrloCw3m.js → diagram-2AECGRRQ-29BFdqPY.js} +2 -2
  30. package/resources/fe-dist/assets/{diagram-5GNKFQAL-BGEyXIkF.js → diagram-5GNKFQAL-t_wHTq5Q.js} +2 -2
  31. package/resources/fe-dist/assets/{diagram-KO2AKTUF-CZa19ANY.js → diagram-KO2AKTUF-JNh9Pmt9.js} +2 -2
  32. package/resources/fe-dist/assets/{diagram-LMA3HP47-DC7BsuGl.js → diagram-LMA3HP47-DAifhNss.js} +2 -2
  33. package/resources/fe-dist/assets/{diagram-OG6HWLK6-BH4wPrwj.js → diagram-OG6HWLK6-BOqtQL5W.js} +2 -2
  34. package/resources/fe-dist/assets/{erDiagram-TEJ5UH35-CsQIlfSm.js → erDiagram-TEJ5UH35-W-aJKAOK.js} +2 -2
  35. package/resources/fe-dist/assets/{flowDiagram-I6XJVG4X-CkjuxggR.js → flowDiagram-I6XJVG4X-DEt1TjbJ.js} +2 -2
  36. package/resources/fe-dist/assets/ganttDiagram-6RSMTGT7-CrYu9aI_.js +293 -0
  37. package/resources/fe-dist/assets/{gitGraphDiagram-PVQCEYII-0Ni5AA_m.js → gitGraphDiagram-PVQCEYII-BlltYekf.js} +2 -2
  38. package/resources/fe-dist/assets/index-CXIj7HZi.css +1 -0
  39. package/resources/fe-dist/assets/{index-96GlCNDm.js → index-FNpsxYPN.js} +53 -53
  40. package/resources/fe-dist/assets/{infoDiagram-5YYISTIA-tIpZWSDh.js → infoDiagram-5YYISTIA-nhdfbNPF.js} +2 -2
  41. package/resources/fe-dist/assets/{ishikawaDiagram-YF4QCWOH-BLnWokk9.js → ishikawaDiagram-YF4QCWOH-CwluZjLD.js} +2 -2
  42. package/resources/fe-dist/assets/{journeyDiagram-JHISSGLW-NBzojNKg.js → journeyDiagram-JHISSGLW-BkvLX_wz.js} +2 -2
  43. package/resources/fe-dist/assets/{kanban-definition-UN3LZRKU-BFDAtm6I.js → kanban-definition-UN3LZRKU-DU_bbXWH.js} +2 -2
  44. package/resources/fe-dist/assets/{linear-B5DOEe_O.js → linear-ByJrBbSQ.js} +2 -2
  45. package/resources/fe-dist/assets/{markdown-preview-DD3cdI4r.js → markdown-preview-D9O-wO1U.js} +4 -4
  46. package/resources/fe-dist/assets/{mermaid.core-BWq4oZoC.js → mermaid.core-DR6r9S_a.js} +6 -6
  47. package/resources/fe-dist/assets/{mindmap-definition-RKZ34NQL-Bi2fp38o.js → mindmap-definition-RKZ34NQL-BYXsfrSb.js} +2 -2
  48. package/resources/fe-dist/assets/{pieDiagram-4H26LBE5-CKK8p15L.js → pieDiagram-4H26LBE5-leRJNTgW.js} +2 -2
  49. package/resources/fe-dist/assets/{quadrantDiagram-W4KKPZXB-QslRPIFi.js → quadrantDiagram-W4KKPZXB-BfxS6APJ.js} +2 -2
  50. package/resources/fe-dist/assets/{requirementDiagram-4Y6WPE33-BRo6E1BH.js → requirementDiagram-4Y6WPE33-D1ViYHCD.js} +2 -2
  51. package/resources/fe-dist/assets/{sankeyDiagram-5OEKKPKP-B16TNk4Z.js → sankeyDiagram-5OEKKPKP-CFjujjK9.js} +2 -2
  52. package/resources/fe-dist/assets/{sequenceDiagram-3UESZ5HK-DiZuSmvo.js → sequenceDiagram-3UESZ5HK-DYKl7nA9.js} +2 -2
  53. package/resources/fe-dist/assets/{stateDiagram-AJRCARHV-BOn6CbQ6.js → stateDiagram-AJRCARHV-C3e0GwoJ.js} +2 -2
  54. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-Bj3cAhsB.js +2 -0
  55. package/resources/fe-dist/assets/terminal-settings-panel-DLuBkEEZ.js +26 -0
  56. package/resources/fe-dist/assets/{timeline-definition-PNZ67QCA-B74c_y_8.js → timeline-definition-PNZ67QCA-DO38TAIg.js} +2 -2
  57. package/resources/fe-dist/assets/{vennDiagram-CIIHVFJN-BZvPBIfU.js → vennDiagram-CIIHVFJN-Co9hBQx5.js} +2 -2
  58. package/resources/fe-dist/assets/{wardley-L42UT6IY-CgvxGMxD.js → wardley-L42UT6IY-lf2QB-Cd.js} +2 -2
  59. package/resources/fe-dist/assets/{wardleyDiagram-YWT4CUSO-BI54Re1E.js → wardleyDiagram-YWT4CUSO-fJgWAjlp.js} +2 -2
  60. package/resources/fe-dist/assets/{xychartDiagram-2RQKCTM6-C9AmQfwS.js → xychartDiagram-2RQKCTM6-DdxhqP-F.js} +2 -2
  61. package/resources/fe-dist/index.html +2 -2
  62. package/resources/gateway-drizzle/0009_lying_lethal_legion.sql +9 -0
  63. package/resources/gateway-drizzle/meta/_journal.json +7 -0
  64. package/resources/fe-dist/assets/DevicePage-Cc6BDsCB.js +0 -25
  65. package/resources/fe-dist/assets/architectureDiagram-3BPJPVTR-DkJzy--B.js +0 -37
  66. package/resources/fe-dist/assets/channel-B9gGKu0d.js +0 -2
  67. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-C5AFJpT6.js +0 -2
  68. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-C5AFJpT6.js +0 -2
  69. package/resources/fe-dist/assets/cose-bilkent-S5V4N54A-giRkzps1.js +0 -2
  70. package/resources/fe-dist/assets/ganttDiagram-6RSMTGT7-eEGLUumD.js +0 -293
  71. package/resources/fe-dist/assets/index-BhU3LRKb.css +0 -1
  72. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-DA0F4iLh.js +0 -2
  73. package/resources/fe-dist/assets/terminal-settings-panel-DVSfGH3m.js +0 -9
package/CHANGELOG.md CHANGED
@@ -1,25 +1,17 @@
1
- # 0.12.3-beta1
1
+ # 0.12.3
2
2
 
3
3
  _2026-06-15_
4
4
 
5
5
  ## English
6
6
 
7
- ### Fixes
8
-
9
- - Bun detection is now reliable across install methods (Homebrew, the official installer, etc.). It no longer reports Bun as "not installed" when your shell startup prints to the terminal.
10
-
11
7
  ### New
12
8
 
13
- - You can now point tmex at a specific Bun binary via `--bun-path <path>` (or the `TMEX_BUN_PATH` environment variable) for `init`, `doctor`, and `upgrade`. The chosen Bun is remembered and reused on later upgrades.
9
+ - Custom terminal shortcuts: build your own quick-action bar for the terminal — add, remove, drag to reorder, rename, and capture key combinations by pressing them. Your shortcuts are saved on the server and shared across all your browsers. Built-in special actions (paste, toggle the text-input keyboard, start a new Agent session, scroll the terminal to the bottom) can be added too, and an optional icon mode shows keys as Apple-style symbols (⌃⇧⏎). A live preview lets you see the bar before you save.
14
10
 
15
11
  ---
16
12
 
17
13
  ## 中文
18
14
 
19
- ### 修复
20
-
21
- - 修复 Bun 检测:Homebrew、官方安装脚本等各种方式装的 Bun 现在都能被正确识别,不再因为 shell 启动时向终端打印内容而误判为「未安装」。
22
-
23
15
  ### 新增
24
16
 
25
- - `init` / `doctor` / `upgrade` 现在支持用 `--bun-path <路径>`(或环境变量 `TMEX_BUN_PATH`)显式指定 Bun 可执行文件;指定后的 Bun 会被记住并在后续升级中复用。
17
+ - 自定义终端快捷键:可以自己定制终端的快捷按钮栏——增删、拖拽排序、重命名,还能直接按下组合键来录入。快捷键保存在服务器、多端共享。内置特殊动作(粘贴、切换文本框键盘、新建 Agent 会话、终端回到最下方)也能一键加入;并可选「图标模式」用苹果风格符号(⌃⇧⏎)显示按键。编辑时提供实时预览,保存后生效。
package/dist/cli-node.js CHANGED
@@ -29,9 +29,9 @@ var MESSAGES = {
29
29
  "cli.help": `tmex CLI
30
30
 
31
31
  Usage:
32
- tmex init [--no-interactive --install-dir <path> --host <host> --port <port> --db-path <path> --autostart <true|false> --bun-path <path>]
33
- tmex doctor [--install-dir <path>] [--json] [--bun-path <path>]
34
- tmex upgrade [--version <version>] [--install-dir <path>] [--bun-path <path>]
32
+ tmex init [--no-interactive --install-dir <path> --host <host> --port <port> --db-path <path> --autostart <true|false>]
33
+ tmex doctor [--install-dir <path>] [--json]
34
+ tmex upgrade [--version <version>] [--install-dir <path>]
35
35
  tmex uninstall [--install-dir <path>] [--yes] [--purge]
36
36
 
37
37
  Global flags:
@@ -52,8 +52,6 @@ Global flags:
52
52
  "bun.versionExecFailed": "Failed to execute bun --version. Please verify Bun installation.",
53
53
  "bun.versionTooLow": "Bun version too low: current {{version}}, required >= {{minVersion}}",
54
54
  "bun.checkFailed": "Bun check failed.",
55
- "bun.explicitInvalid": "Specified bun path is invalid or not executable: {{path}}",
56
- "bun.unsafePath": "Unsafe bun path (contains shell metacharacters): {{path}}",
57
55
  "service.install.unsupportedPlatform": "Automatic service installation is not supported on this platform: {{platform}}",
58
56
  "service.systemd.daemonReloadFailed": "systemctl daemon-reload failed: {{detail}}",
59
57
  "service.systemd.enableFailed": "systemctl enable failed: {{detail}}",
@@ -130,9 +128,9 @@ Global flags:
130
128
  "cli.help": `tmex CLI
131
129
 
132
130
  用法:
133
- tmex init [--no-interactive --install-dir <path> --host <host> --port <port> --db-path <path> --autostart <true|false> --bun-path <path>]
134
- tmex doctor [--install-dir <path>] [--json] [--bun-path <path>]
135
- tmex upgrade [--version <version>] [--install-dir <path>] [--bun-path <path>]
131
+ tmex init [--no-interactive --install-dir <path> --host <host> --port <port> --db-path <path> --autostart <true|false>]
132
+ tmex doctor [--install-dir <path>] [--json]
133
+ tmex upgrade [--version <version>] [--install-dir <path>]
136
134
  tmex uninstall [--install-dir <path>] [--yes] [--purge]
137
135
 
138
136
  全局参数:
@@ -153,8 +151,6 @@ Global flags:
153
151
  "bun.versionExecFailed": "无法执行 bun --version,请检查 Bun 安装是否完整。",
154
152
  "bun.versionTooLow": "Bun 版本过低:当前 {{version}},要求 >= {{minVersion}}",
155
153
  "bun.checkFailed": "Bun 检查失败。",
156
- "bun.explicitInvalid": "指定的 bun 路径无效或不可执行:{{path}}",
157
- "bun.unsafePath": "bun 路径包含 shell 特殊字符,不安全:{{path}}",
158
154
  "service.install.unsupportedPlatform": "当前平台不支持自动安装服务:{{platform}}",
159
155
  "service.systemd.daemonReloadFailed": "systemctl daemon-reload 失败:{{detail}}",
160
156
  "service.systemd.enableFailed": "systemctl enable 失败:{{detail}}",
@@ -263,7 +259,7 @@ function t(key, vars) {
263
259
  // src/lib/bun.ts
264
260
  import { existsSync } from "node:fs";
265
261
  import { homedir as homedir2 } from "node:os";
266
- import { isAbsolute, join } from "node:path";
262
+ import { join } from "node:path";
267
263
 
268
264
  // src/lib/process.ts
269
265
  import { spawn } from "node:child_process";
@@ -273,21 +269,10 @@ async function runCommand(command, args, options = {}) {
273
269
  const child = spawn(command, args, {
274
270
  cwd: options.cwd,
275
271
  env: options.env,
276
- stdio: stdio === "pipe" ? ["ignore", "pipe", "pipe"] : stdio
272
+ stdio
277
273
  });
278
274
  let stdout = "";
279
275
  let stderr = "";
280
- let settled = false;
281
- let timer = null;
282
- const finish = (fn) => {
283
- if (settled)
284
- return;
285
- settled = true;
286
- if (timer) {
287
- clearTimeout(timer);
288
- }
289
- fn();
290
- };
291
276
  if (stdio === "pipe") {
292
277
  child.stdout?.on("data", (chunk) => {
293
278
  stdout += chunk.toString();
@@ -296,21 +281,13 @@ async function runCommand(command, args, options = {}) {
296
281
  stderr += chunk.toString();
297
282
  });
298
283
  }
299
- if (options.timeoutMs && options.timeoutMs > 0) {
300
- timer = setTimeout(() => {
301
- child.kill("SIGKILL");
302
- finish(() => reject(new Error(`Command timed out after ${options.timeoutMs}ms: ${command}`)));
303
- }, options.timeoutMs);
304
- }
305
- child.on("error", (error) => {
306
- finish(() => reject(error));
307
- });
284
+ child.on("error", reject);
308
285
  child.on("close", (code) => {
309
- finish(() => resolve2({
286
+ resolve2({
310
287
  code: code ?? 1,
311
288
  stdout,
312
289
  stderr
313
- }));
290
+ });
314
291
  });
315
292
  });
316
293
  }
@@ -339,239 +316,63 @@ function compareSemver(left, right) {
339
316
  return 0;
340
317
  }
341
318
 
342
- // src/lib/validate.ts
343
- function asString(value) {
344
- if (typeof value === "string")
345
- return value;
346
- return;
347
- }
348
- function asBoolean(value) {
349
- if (typeof value === "boolean")
350
- return value;
351
- if (typeof value !== "string")
352
- return;
353
- const normalized = value.trim().toLowerCase();
354
- if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "y") {
355
- return true;
356
- }
357
- if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "n") {
358
- return false;
359
- }
360
- return;
361
- }
362
- function parsePort(value) {
363
- const port = Number(value);
364
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
365
- throw new Error(t("errors.validate.invalidPort", { value }));
366
- }
367
- return port;
368
- }
369
- function assertNonEmpty(value, fieldName) {
370
- const trimmed = value.trim();
371
- if (!trimmed) {
372
- throw new Error(t("errors.validate.emptyField", { field: fieldName }));
373
- }
374
- return trimmed;
375
- }
376
-
377
319
  // src/lib/bun.ts
378
- var ESC = 27;
379
- var CSI_OPEN = 91;
380
- var OSC_OPEN = 93;
381
- var BEL = 7;
382
- var ST_TAIL = 92;
383
- var LF = 10;
384
- var CR = 13;
385
- var DEL = 127;
386
- var SLASH = 47;
387
- var PROBE_TIMEOUT_MS = 5000;
388
- function sanitizeBunPath(raw) {
389
- let stripped = "";
390
- for (let i = 0;i < raw.length; i += 1) {
391
- const code = raw.charCodeAt(i);
392
- if (code === ESC) {
393
- const next = raw.charCodeAt(i + 1);
394
- if (next === CSI_OPEN) {
395
- i += 1;
396
- while (i + 1 < raw.length) {
397
- const c = raw.charCodeAt(i + 1);
398
- i += 1;
399
- if (c >= 64 && c <= 126) {
400
- break;
401
- }
402
- }
403
- } else if (next === OSC_OPEN) {
404
- i += 1;
405
- while (i + 1 < raw.length) {
406
- const c = raw.charCodeAt(i + 1);
407
- if (c === BEL) {
408
- i += 1;
409
- break;
410
- }
411
- if (c === ESC && raw.charCodeAt(i + 2) === ST_TAIL) {
412
- i += 2;
413
- break;
414
- }
415
- i += 1;
416
- }
417
- }
418
- continue;
419
- }
420
- stripped += raw[i];
421
- }
422
- const lines = [];
423
- let current = "";
424
- const flush = () => {
425
- let cleaned = "";
426
- for (let i = 0;i < current.length; i += 1) {
427
- const code = current.charCodeAt(i);
428
- if (code <= 31 || code === DEL) {
429
- continue;
430
- }
431
- cleaned += current[i];
432
- }
433
- cleaned = cleaned.trim();
434
- if (cleaned.length > 0) {
435
- lines.push(cleaned);
436
- }
437
- current = "";
438
- };
439
- for (let i = 0;i < stripped.length; i += 1) {
440
- const code = stripped.charCodeAt(i);
441
- if (code === LF || code === CR) {
442
- flush();
443
- continue;
444
- }
445
- current += stripped[i];
446
- }
447
- flush();
448
- if (lines.length === 0) {
449
- return "";
450
- }
451
- for (let i = lines.length - 1;i >= 0; i -= 1) {
452
- if (lines[i].charCodeAt(0) === SLASH) {
453
- return lines[i];
454
- }
455
- }
456
- return lines[lines.length - 1];
457
- }
458
- function readExplicitBunPath(flags) {
459
- return asString(flags["bun-path"]) || process.env.TMEX_BUN_PATH;
460
- }
461
- function isBunRuntime() {
462
- const bunVersion = process.versions.bun;
463
- return typeof bunVersion === "string" && bunVersion.length > 0;
464
- }
465
- function hardCandidatePaths() {
466
- return [
467
- join(homedir2(), ".bun", "bin", "bun"),
468
- "/opt/homebrew/bin/bun",
469
- "/usr/local/bin/bun",
470
- "/home/linuxbrew/.linuxbrew/bin/bun"
471
- ];
472
- }
473
- async function locateBunFromShellWith(shell) {
474
- const result = await runCommand(shell, ["-lic", "command -v bun"], {
475
- stdio: "pipe",
476
- timeoutMs: PROBE_TIMEOUT_MS
477
- }).catch(() => null);
320
+ async function locateBunFromShell() {
321
+ const result = await runCommand("zsh", ["-lic", "command -v bun"], { stdio: "pipe" }).catch(() => null);
478
322
  if (!result || result.code !== 0) {
479
323
  return null;
480
324
  }
481
- const bin = sanitizeBunPath(result.stdout);
482
- if (!bin || !isAbsolute(bin) || !existsSync(bin)) {
325
+ const bin = result.stdout.trim();
326
+ if (!bin) {
483
327
  return null;
484
328
  }
485
329
  return bin;
486
330
  }
487
- async function locateBunFromShell() {
488
- const shells = [];
489
- const envShell = process.env.SHELL?.trim();
490
- if (envShell) {
491
- shells.push(envShell);
492
- }
493
- for (const fallbackShell of ["zsh", "bash"]) {
494
- if (!shells.includes(fallbackShell)) {
495
- shells.push(fallbackShell);
496
- }
331
+ async function findBunBinary() {
332
+ const zshBin = await locateBunFromShell();
333
+ if (zshBin) {
334
+ return zshBin;
497
335
  }
498
- for (const shell of shells) {
499
- const found = await locateBunFromShellWith(shell);
500
- if (found) {
501
- return found;
502
- }
336
+ const fallback = join(homedir2(), ".bun", "bin", "bun");
337
+ if (existsSync(fallback)) {
338
+ return fallback;
339
+ }
340
+ const direct = await runCommand("bun", ["--version"], { stdio: "pipe" }).catch(() => null);
341
+ if (direct?.code === 0) {
342
+ return "bun";
503
343
  }
504
344
  return null;
505
345
  }
506
- async function probeBunCandidates(metaBunPath) {
507
- const candidates = [];
508
- const add = (value) => {
509
- if (!value) {
510
- return;
511
- }
512
- const sanitized = sanitizeBunPath(value);
513
- if (sanitized && !candidates.includes(sanitized)) {
514
- candidates.push(sanitized);
515
- }
516
- };
517
- if (isBunRuntime()) {
518
- add(process.execPath);
519
- }
520
- add(metaBunPath);
521
- add(await locateBunFromShell());
522
- candidates.push("bun");
523
- for (const candidate of hardCandidatePaths()) {
524
- add(candidate);
346
+ async function checkBunVersion(minVersion = MIN_BUN_VERSION) {
347
+ const bunPath = await findBunBinary();
348
+ if (!bunPath) {
349
+ return {
350
+ ok: false,
351
+ reason: t("bun.notFound")
352
+ };
525
353
  }
526
- return candidates;
527
- }
528
- async function validateBunAt(candidate, minVersion) {
529
- const versionResult = await runCommand(candidate, ["--version"], {
530
- stdio: "pipe",
531
- timeoutMs: PROBE_TIMEOUT_MS
532
- }).catch(() => null);
354
+ const versionResult = await runCommand(bunPath, ["--version"], { stdio: "pipe" }).catch(() => null);
533
355
  if (!versionResult || versionResult.code !== 0) {
534
- return { ok: false, path: candidate, reason: t("bun.versionExecFailed") };
356
+ return {
357
+ ok: false,
358
+ reason: t("bun.versionExecFailed"),
359
+ path: bunPath
360
+ };
535
361
  }
536
- const version = sanitizeBunPath(versionResult.stdout);
362
+ const version = versionResult.stdout.trim();
537
363
  if (compareSemver(version, minVersion) < 0) {
538
364
  return {
539
365
  ok: false,
540
- path: candidate,
366
+ path: bunPath,
541
367
  version,
542
368
  reason: t("bun.versionTooLow", { version, minVersion })
543
369
  };
544
370
  }
545
- return { ok: true, path: candidate, version };
546
- }
547
- async function checkBunVersion(minVersion = MIN_BUN_VERSION, opts = {}) {
548
- if (opts.explicitPath !== undefined && opts.explicitPath !== "") {
549
- const explicit = sanitizeBunPath(opts.explicitPath);
550
- if (!explicit || !isAbsolute(explicit) || !existsSync(explicit)) {
551
- const reported = explicit || opts.explicitPath;
552
- return {
553
- ok: false,
554
- path: reported,
555
- reason: t("bun.explicitInvalid", { path: reported })
556
- };
557
- }
558
- return await validateBunAt(explicit, minVersion);
559
- }
560
- const candidates = await probeBunCandidates(opts.metaBunPath);
561
- let firstFailure = null;
562
- for (const candidate of candidates) {
563
- if (candidate !== "bun" && isAbsolute(candidate) && !existsSync(candidate)) {
564
- continue;
565
- }
566
- const result = await validateBunAt(candidate, minVersion);
567
- if (result.ok) {
568
- return result;
569
- }
570
- if (!firstFailure) {
571
- firstFailure = result;
572
- }
573
- }
574
- return firstFailure ?? { ok: false, reason: t("bun.notFound") };
371
+ return {
372
+ ok: true,
373
+ path: bunPath,
374
+ version
375
+ };
575
376
  }
576
377
 
577
378
  // src/lib/env-file.ts
@@ -967,6 +768,41 @@ function serviceHint(serviceName) {
967
768
  return t("service.hint.none");
968
769
  }
969
770
 
771
+ // src/lib/validate.ts
772
+ function asString(value) {
773
+ if (typeof value === "string")
774
+ return value;
775
+ return;
776
+ }
777
+ function asBoolean(value) {
778
+ if (typeof value === "boolean")
779
+ return value;
780
+ if (typeof value !== "string")
781
+ return;
782
+ const normalized = value.trim().toLowerCase();
783
+ if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "y") {
784
+ return true;
785
+ }
786
+ if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "n") {
787
+ return false;
788
+ }
789
+ return;
790
+ }
791
+ function parsePort(value) {
792
+ const port = Number(value);
793
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
794
+ throw new Error(t("errors.validate.invalidPort", { value }));
795
+ }
796
+ return port;
797
+ }
798
+ function assertNonEmpty(value, fieldName) {
799
+ const trimmed = value.trim();
800
+ if (!trimmed) {
801
+ throw new Error(t("errors.validate.emptyField", { field: fieldName }));
802
+ }
803
+ return trimmed;
804
+ }
805
+
970
806
  // src/commands/doctor.ts
971
807
  async function checkCommandExists(bin, args, id, okMessage, failMessage) {
972
808
  const result = await runCommand(bin, args, { stdio: "pipe" }).catch(() => null);
@@ -1007,12 +843,7 @@ async function runDoctor(parsed) {
1007
843
  message: t("doctor.platform.supported", { platform: process.platform })
1008
844
  });
1009
845
  }
1010
- const explicitBunPath = readExplicitBunPath(parsed.flags);
1011
- const meta = await pathExists(installLayout.metaPath) ? await readJsonFile(installLayout.metaPath).catch(() => null) : null;
1012
- const bun = await checkBunVersion(undefined, {
1013
- explicitPath: explicitBunPath,
1014
- metaBunPath: meta?.bunPath
1015
- });
846
+ const bun = await checkBunVersion();
1016
847
  if (bun.ok) {
1017
848
  checks.push({
1018
849
  id: "bun",
@@ -1115,8 +946,11 @@ async function runDoctor(parsed) {
1115
946
  });
1116
947
  }
1117
948
  let serviceName = asString(parsed.flags["service-name"]) || "tmex";
1118
- if (meta?.serviceName) {
1119
- serviceName = meta.serviceName;
949
+ if (await pathExists(installLayout.metaPath)) {
950
+ const meta = await readJsonFile(installLayout.metaPath).catch(() => null);
951
+ if (meta?.serviceName) {
952
+ serviceName = meta.serviceName;
953
+ }
1120
954
  }
1121
955
  const status = await getServiceStatus(serviceName, installDir);
1122
956
  if (status.manager === "none") {
@@ -1174,13 +1008,12 @@ async function runDoctor(parsed) {
1174
1008
 
1175
1009
  // src/commands/init.ts
1176
1010
  import { readdir } from "node:fs/promises";
1177
- import { dirname as dirname5, resolve as resolve6 } from "node:path";
1011
+ import { dirname as dirname4, resolve as resolve6 } from "node:path";
1178
1012
 
1179
1013
  // src/lib/install.ts
1180
1014
  import { randomBytes } from "node:crypto";
1181
1015
  import { chmod, copyFile, rm as rm2 } from "node:fs/promises";
1182
- import { homedir as homedir4 } from "node:os";
1183
- import { dirname as dirname4, isAbsolute as isAbsolute2, join as join4, resolve as resolve5 } from "node:path";
1016
+ import { resolve as resolve5 } from "node:path";
1184
1017
  function generateMasterKey() {
1185
1018
  return randomBytes(32).toString("base64");
1186
1019
  }
@@ -1217,20 +1050,6 @@ async function deployRuntimeFiles(packageLayout, installLayout) {
1217
1050
  await copyDirectory(packageLayout.resourceDrizzlePath, installLayout.drizzleDir);
1218
1051
  }
1219
1052
  async function writeRunScript(installLayout, bunPath) {
1220
- for (let i = 0;i < bunPath.length; i += 1) {
1221
- const code = bunPath.charCodeAt(i);
1222
- if (code === 34 || code === 96 || code === 36 || code === 92 || code === 10 || code === 13) {
1223
- throw new Error(t("bun.unsafePath", { path: bunPath }));
1224
- }
1225
- }
1226
- const homeBunBin = join4(homedir4(), ".bun", "bin");
1227
- const bunDir = isAbsolute2(bunPath) ? dirname4(bunPath) : "";
1228
- const extraPathDirs = [
1229
- bunDir,
1230
- "/opt/homebrew/bin",
1231
- "/usr/local/bin",
1232
- "/home/linuxbrew/.linuxbrew/bin"
1233
- ].filter((dir, index, arr) => dir.length > 0 && dir !== homeBunBin && arr.indexOf(dir) === index);
1234
1053
  const lines = [
1235
1054
  "#!/usr/bin/env bash",
1236
1055
  "set -euo pipefail",
@@ -1246,7 +1065,6 @@ async function writeRunScript(installLayout, bunPath) {
1246
1065
  'if [[ -n "${HOME:-}" ]] && [[ -d "${HOME}/.bun/bin" ]]; then',
1247
1066
  ' export PATH="${HOME}/.bun/bin:${PATH:-}"',
1248
1067
  "fi",
1249
- `export PATH="${[...extraPathDirs, "${PATH:-}"].join(":")}"`,
1250
1068
  "",
1251
1069
  `export TMEX_FE_DIST_DIR="${installLayout.feDir}"`,
1252
1070
  `export TMEX_MIGRATIONS_DIR="${installLayout.drizzleDir}"`,
@@ -1336,9 +1154,9 @@ async function promptConfirm(ctx, message, defaultValue) {
1336
1154
  }
1337
1155
 
1338
1156
  // src/lib/version.ts
1339
- import { join as join5 } from "node:path";
1157
+ import { join as join4 } from "node:path";
1340
1158
  async function readPackageVersion(packageRoot) {
1341
- const pkg = await readJsonFile(join5(packageRoot, "package.json"));
1159
+ const pkg = await readJsonFile(join4(packageRoot, "package.json"));
1342
1160
  return pkg.version || "0.0.0";
1343
1161
  }
1344
1162
 
@@ -1410,8 +1228,7 @@ async function buildInitConfig(parsed) {
1410
1228
  }
1411
1229
  async function runInit(parsed) {
1412
1230
  const config = await buildInitConfig(parsed);
1413
- const explicitBunPath = readExplicitBunPath(parsed.flags);
1414
- const bun = await checkBunVersion(undefined, { explicitPath: explicitBunPath });
1231
+ const bun = await checkBunVersion();
1415
1232
  if (!bun.ok || !bun.path) {
1416
1233
  throw new Error(bun.reason || t("bun.checkFailed"));
1417
1234
  }
@@ -1427,7 +1244,7 @@ async function runInit(parsed) {
1427
1244
  const packageLayout = await resolvePackageLayout(import.meta.url);
1428
1245
  const installLayout = createInstallLayout(config.installDir);
1429
1246
  await ensureInstallDir(config.installDir, config.force);
1430
- await ensureDir(dirname5(config.databasePath));
1247
+ await ensureDir(dirname4(config.databasePath));
1431
1248
  await deployRuntimeFiles(packageLayout, installLayout);
1432
1249
  const masterKey = generateMasterKey();
1433
1250
  const envValues = buildAppEnvValues({
@@ -1456,8 +1273,7 @@ async function runInit(parsed) {
1456
1273
  autostart: config.autostart,
1457
1274
  installDir: config.installDir,
1458
1275
  updatedAt: new Date().toISOString(),
1459
- cliVersion,
1460
- bunPath: bun.path
1276
+ cliVersion
1461
1277
  };
1462
1278
  await writeInstallMeta(installLayout, meta);
1463
1279
  console.log(`[tmex] ${t("init.done")}`);
@@ -1529,7 +1345,7 @@ async function runUninstall(parsed) {
1529
1345
  // src/commands/upgrade.ts
1530
1346
  import { mkdtemp, rm as rm4 } from "node:fs/promises";
1531
1347
  import { tmpdir } from "node:os";
1532
- import { join as join6 } from "node:path";
1348
+ import { join as join5 } from "node:path";
1533
1349
  async function delegateUpgrade(parsed, targetVersion) {
1534
1350
  const args = ["--yes", `tmex-cli@${targetVersion}`, "upgrade", "--apply-current-package"];
1535
1351
  const passthrough = ["install-dir", "service-name", "yes", "lang"];
@@ -1585,17 +1401,13 @@ async function runUpgrade(parsed) {
1585
1401
  if (!await pathExists(installLayout.metaPath)) {
1586
1402
  throw new Error(t("upgrade.missingMeta", { path: installLayout.metaPath }));
1587
1403
  }
1588
- const meta = await readJsonFile(installLayout.metaPath);
1589
- const explicitBunPath = readExplicitBunPath(parsed.flags);
1590
- const bun = await checkBunVersion(undefined, {
1591
- explicitPath: explicitBunPath,
1592
- metaBunPath: meta.bunPath
1593
- });
1404
+ const bun = await checkBunVersion();
1594
1405
  if (!bun.ok || !bun.path) {
1595
1406
  throw new Error(bun.reason || t("bun.checkFailed"));
1596
1407
  }
1408
+ const meta = await readJsonFile(installLayout.metaPath);
1597
1409
  const packageLayout = await resolvePackageLayout(import.meta.url);
1598
- const backupDir = await mkdtemp(join6(tmpdir(), "tmex-upgrade-"));
1410
+ const backupDir = await mkdtemp(join5(tmpdir(), "tmex-upgrade-"));
1599
1411
  try {
1600
1412
  await stopService(meta.serviceName, installDir);
1601
1413
  await backupInstallArtifacts(installLayout, backupDir);
@@ -1604,7 +1416,6 @@ async function runUpgrade(parsed) {
1604
1416
  const cliVersion = await readPackageVersion(packageLayout.packageRoot);
1605
1417
  meta.updatedAt = new Date().toISOString();
1606
1418
  meta.cliVersion = cliVersion;
1607
- meta.bunPath = bun.path;
1608
1419
  await writeInstallMeta(installLayout, meta);
1609
1420
  await installService({
1610
1421
  serviceName: meta.serviceName,