wherdr 1.3.0 → 1.3.2

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 (141) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/{BB3uOVbO.js → 8ksjcuDu.js} +51 -41
  3. package/.output/public/_nuxt/{AgentView.DE2GDt4O.css → AgentView.C4YOwvZG.css} +1 -1
  4. package/.output/public/_nuxt/B7QP1TFs.js +23 -0
  5. package/.output/public/_nuxt/{Bta8at9w.js → BOpBIziZ.js} +2 -2
  6. package/.output/public/_nuxt/BWXdFfeg.js +17 -0
  7. package/.output/public/_nuxt/COFCNFpG.js +83 -0
  8. package/.output/public/_nuxt/{C3BWhslt.js → Cd8j9O5w.js} +1 -1
  9. package/.output/public/_nuxt/CkZ8t6uB.js +1 -0
  10. package/.output/public/_nuxt/{OAXEskzA.js → D4Yz4rat.js} +4 -4
  11. package/.output/public/_nuxt/{C3P2P5Mv.js → D7sxNXx1.js} +1 -1
  12. package/.output/public/_nuxt/DViwOclv.js +1 -0
  13. package/.output/public/_nuxt/DoD7Qb5P.js +30 -0
  14. package/.output/public/_nuxt/DyBZDSCV.js +2 -0
  15. package/.output/public/_nuxt/builds/latest.json +1 -1
  16. package/.output/public/_nuxt/builds/meta/7afbf062-5369-4002-b20d-7a582ff6e0f1.json +1 -0
  17. package/.output/public/_nuxt/{entry.SHBiCXBL.css → entry.Crt1r4l3.css} +1 -1
  18. package/.output/public/_nuxt/hePW80VL.js +1 -0
  19. package/.output/public/sw.js +1 -1
  20. package/.output/server/chunks/_/branding.mjs +1 -1
  21. package/.output/server/chunks/_/package.mjs +96 -0
  22. package/.output/server/chunks/_/plugins.mjs +11 -3
  23. package/.output/server/chunks/_/projectBoard.mjs +1 -1
  24. package/.output/server/chunks/_/projectsPlugin.mjs +1 -1
  25. package/.output/server/chunks/_/selfupdate.mjs +82 -0
  26. package/.output/server/chunks/_/updates.mjs +50 -0
  27. package/.output/server/chunks/_/worktrees.mjs +1 -1
  28. package/.output/server/chunks/_/ws.mjs +1 -1
  29. package/.output/server/chunks/nitro/nitro.mjs +1728 -917
  30. package/.output/server/chunks/raw/bump-homebrew.mjs +7 -0
  31. package/.output/server/chunks/raw/herdr-plugin.mjs +1 -1
  32. package/.output/server/chunks/routes/api/_..._.mjs +5 -1
  33. package/.output/server/chunks/routes/api/agents.post.mjs +5 -1
  34. package/.output/server/chunks/routes/api/ask-tab.post.mjs +5 -1
  35. package/.output/server/chunks/routes/api/attach.post.mjs +5 -1
  36. package/.output/server/chunks/routes/api/auth/disable.post.mjs +5 -1
  37. package/.output/server/chunks/routes/api/auth/lock-all.post.mjs +5 -1
  38. package/.output/server/chunks/routes/api/auth/lock.post.mjs +5 -1
  39. package/.output/server/chunks/routes/api/auth/login/options.post.mjs +5 -1
  40. package/.output/server/chunks/routes/api/auth/login/verify.post.mjs +5 -1
  41. package/.output/server/chunks/routes/api/auth/max-session.post.mjs +5 -1
  42. package/.output/server/chunks/routes/api/auth/register/options.post.mjs +5 -1
  43. package/.output/server/chunks/routes/api/auth/register/verify.post.mjs +5 -1
  44. package/.output/server/chunks/routes/api/auth/status.get.mjs +5 -1
  45. package/.output/server/chunks/routes/api/changes.get.mjs +5 -1
  46. package/.output/server/chunks/routes/api/chat/image.get.mjs +5 -1
  47. package/.output/server/chunks/routes/api/chat.get.mjs +5 -1
  48. package/.output/server/chunks/routes/api/choose.post.mjs +5 -1
  49. package/.output/server/chunks/routes/api/claude-statusline.post.mjs +5 -1
  50. package/.output/server/chunks/routes/api/close/status.get.mjs +5 -1
  51. package/.output/server/chunks/routes/api/close.post.mjs +5 -1
  52. package/.output/server/chunks/routes/api/codex-update.post.mjs +5 -1
  53. package/.output/server/chunks/routes/api/commands.get.mjs +5 -1
  54. package/.output/server/chunks/routes/api/config.get.mjs +5 -1
  55. package/.output/server/chunks/routes/api/dirs.get.mjs +5 -1
  56. package/.output/server/chunks/routes/api/dismiss.post.mjs +5 -1
  57. package/.output/server/chunks/routes/api/effort.post.mjs +5 -1
  58. package/.output/server/chunks/routes/api/efforts.get.mjs +5 -1
  59. package/.output/server/chunks/routes/api/file.post.mjs +5 -1
  60. package/.output/server/chunks/routes/api/gitroot.get.mjs +5 -1
  61. package/.output/server/chunks/routes/api/health.get.mjs +36 -0
  62. package/.output/server/chunks/routes/api/index.get.mjs +5 -1
  63. package/.output/server/chunks/routes/api/input.post.mjs +5 -1
  64. package/.output/server/chunks/routes/api/interrupt.post.mjs +5 -1
  65. package/.output/server/chunks/routes/api/isgit.get.mjs +5 -1
  66. package/.output/server/chunks/routes/api/machine/awake.get.mjs +5 -1
  67. package/.output/server/chunks/routes/api/machine/awake.post.mjs +5 -1
  68. package/.output/server/chunks/routes/api/machine/order.get.mjs +5 -1
  69. package/.output/server/chunks/routes/api/machine/order.post.mjs +5 -1
  70. package/.output/server/chunks/routes/api/machine/rename.post.mjs +5 -1
  71. package/.output/server/chunks/routes/api/menu.post.mjs +5 -1
  72. package/.output/server/chunks/routes/api/model.post.mjs +5 -1
  73. package/.output/server/chunks/routes/api/models.get.mjs +5 -1
  74. package/.output/server/chunks/routes/api/nav.post.mjs +5 -1
  75. package/.output/server/chunks/routes/api/onboarding.get.mjs +5 -1
  76. package/.output/server/chunks/routes/api/onboarding.post.mjs +5 -1
  77. package/.output/server/chunks/routes/api/phone.get.mjs +5 -6
  78. package/.output/server/chunks/routes/api/phone.post.mjs +5 -6
  79. package/.output/server/chunks/routes/api/plugins/actions.get.mjs +5 -1
  80. package/.output/server/chunks/routes/api/plugins/invoke.post.mjs +5 -1
  81. package/.output/server/chunks/routes/api/plugins/profiles.get.mjs +5 -1
  82. package/.output/server/chunks/routes/api/plugins/projects.get.mjs +5 -1
  83. package/.output/server/chunks/routes/api/plugins/projects.post.mjs +5 -1
  84. package/.output/server/chunks/routes/api/plugins/thread-limits.get.mjs +6 -2
  85. package/.output/server/chunks/routes/api/plugins/thread-limits.post.mjs +6 -2
  86. package/.output/server/chunks/routes/api/project/report.get.mjs +5 -1
  87. package/.output/server/chunks/routes/api/prompt.post.mjs +6 -2
  88. package/.output/server/chunks/routes/api/push/quiet.post.mjs +6 -2
  89. package/.output/server/chunks/routes/api/push/subscribe.post.mjs +6 -2
  90. package/.output/server/chunks/routes/api/push/test.post.mjs +6 -2
  91. package/.output/server/chunks/routes/api/push/unsubscribe.post.mjs +6 -2
  92. package/.output/server/chunks/routes/api/quotas.get.mjs +6 -2
  93. package/.output/server/chunks/routes/api/rename.post.mjs +5 -1
  94. package/.output/server/chunks/routes/api/requeue.post.mjs +6 -2
  95. package/.output/server/chunks/routes/api/restart.get.mjs +5 -1
  96. package/.output/server/chunks/routes/api/restart.post.mjs +5 -1
  97. package/.output/server/chunks/routes/api/reveal.post.mjs +5 -1
  98. package/.output/server/chunks/routes/api/screen-key.post.mjs +6 -2
  99. package/.output/server/chunks/routes/api/screen.get.mjs +12 -6
  100. package/.output/server/chunks/routes/api/search.get.mjs +6 -2
  101. package/.output/server/chunks/routes/api/seen.post.mjs +6 -2
  102. package/.output/server/chunks/routes/api/sessions.get.mjs +6 -2
  103. package/.output/server/chunks/routes/api/sessions.post.mjs +6 -2
  104. package/.output/server/chunks/routes/api/space.post.mjs +6 -2
  105. package/.output/server/chunks/routes/api/state.get.mjs +6 -2
  106. package/.output/server/chunks/routes/api/unqueue.post.mjs +6 -2
  107. package/.output/server/chunks/routes/api/update/status.get.mjs +38 -0
  108. package/.output/server/chunks/routes/api/update.get.mjs +11 -50
  109. package/.output/server/chunks/routes/api/update.post.mjs +41 -0
  110. package/.output/server/chunks/routes/api/upload.post.mjs +5 -1
  111. package/.output/server/chunks/routes/api/worktrees/remove.post.mjs +5 -1
  112. package/.output/server/chunks/routes/api/worktrees.get.mjs +5 -1
  113. package/.output/server/chunks/routes/favicon.ico.get.mjs +5 -1
  114. package/.output/server/chunks/routes/icons/_name_.get.mjs +6 -2
  115. package/.output/server/chunks/routes/renderer.mjs +7 -3
  116. package/.output/server/chunks/routes/uploads/_...name_.get.mjs +6 -2
  117. package/.output/server/chunks/routes/ws/_..._.mjs +6 -2
  118. package/.output/server/chunks/routes/ws/events.mjs +6 -2
  119. package/.output/server/chunks/routes/ws/mirror.mjs +6 -2
  120. package/.output/server/chunks/routes/ws/term.mjs +8 -4
  121. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  122. package/.output/server/index.mjs +6 -2
  123. package/.output/server/package.json +1 -1
  124. package/README.md +216 -45
  125. package/bin/lib/brew.mjs +86 -0
  126. package/bin/lib/commands.mjs +74 -16
  127. package/bin/lib/core.mjs +23 -3
  128. package/bin/lib/tailnet.d.mts +12 -3
  129. package/bin/lib/tailnet.mjs +103 -25
  130. package/bin/lib/updater.mjs +267 -0
  131. package/package.json +2 -1
  132. package/.output/public/_nuxt/01adBkx4.js +0 -1
  133. package/.output/public/_nuxt/BJsf0WRn.js +0 -2
  134. package/.output/public/_nuxt/BpQH8Ho1.js +0 -1
  135. package/.output/public/_nuxt/Cc2bsOcu.js +0 -83
  136. package/.output/public/_nuxt/DKmoLFfn.js +0 -30
  137. package/.output/public/_nuxt/DLLQgP0d.js +0 -1
  138. package/.output/public/_nuxt/DcCZr0Ya.js +0 -17
  139. package/.output/public/_nuxt/DsD2R-37.js +0 -23
  140. package/.output/public/_nuxt/builds/meta/ff11fc8b-1e70-4261-a76e-a52155efb196.json +0 -1
  141. package/.output/server/chunks/_/phone.mjs +0 -343
@@ -21,8 +21,12 @@ import { spawn, execFile } from 'node:child_process';
21
21
  import net from 'node:net';
22
22
  import readline from 'node:readline';
23
23
  import fs$1 from 'node:fs/promises';
24
- import { fileURLToPath } from 'node:url';
24
+ import dns from 'node:dns';
25
+ import { promisify } from 'node:util';
26
+ import QRCode from 'qrcode-terminal/vendor/QRCode/index.js';
27
+ import QRErrorCorrectLevel from 'qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js';
25
28
  import { verifyAuthenticationResponse, generateAuthenticationOptions, verifyRegistrationResponse, generateRegistrationOptions } from '@simplewebauthn/server';
29
+ import { fileURLToPath } from 'node:url';
26
30
 
27
31
  crypto.webcrypto?.subtle || {};
28
32
  const randomUUID = () => {
@@ -5892,12 +5896,12 @@ function createRouter$1(options = {}) {
5892
5896
  }
5893
5897
  return {
5894
5898
  ctx,
5895
- lookup: (path) => lookup(ctx, normalizeTrailingSlash(path)),
5899
+ lookup: (path) => lookup$1(ctx, normalizeTrailingSlash(path)),
5896
5900
  insert: (path, data) => insert(ctx, normalizeTrailingSlash(path), data),
5897
5901
  remove: (path) => remove(ctx, normalizeTrailingSlash(path))
5898
5902
  };
5899
5903
  }
5900
- function lookup(ctx, path) {
5904
+ function lookup$1(ctx, path) {
5901
5905
  const staticPathNode = ctx.staticRoutesMap[path];
5902
5906
  if (staticPathNode) {
5903
5907
  return staticPathNode.data;
@@ -8286,7 +8290,7 @@ function createStorage(options = {}) {
8286
8290
  }
8287
8291
  context.watching = true;
8288
8292
  for (const mountpoint in context.mounts) {
8289
- context.unwatch[mountpoint] = await watch(
8293
+ context.unwatch[mountpoint] = await watch$1(
8290
8294
  context.mounts[mountpoint],
8291
8295
  onChange,
8292
8296
  mountpoint
@@ -8582,7 +8586,7 @@ function createStorage(options = {}) {
8582
8586
  }
8583
8587
  context.mounts[base] = driver;
8584
8588
  if (context.watching) {
8585
- Promise.resolve(watch(driver, onChange, base)).then((unwatcher) => {
8589
+ Promise.resolve(watch$1(driver, onChange, base)).then((unwatcher) => {
8586
8590
  context.unwatch[base] = unwatcher;
8587
8591
  }).catch(console.error);
8588
8592
  }
@@ -8629,7 +8633,7 @@ function createStorage(options = {}) {
8629
8633
  };
8630
8634
  return storage;
8631
8635
  }
8632
- function watch(driver, onChange, base) {
8636
+ function watch$1(driver, onChange, base) {
8633
8637
  return driver.watch ? driver.watch((event, key) => onChange(event, base + key)) : () => {
8634
8638
  };
8635
8639
  }
@@ -8642,79 +8646,83 @@ async function dispose(driver) {
8642
8646
  const _assets = {
8643
8647
  ["scripts:build-nerd-font.py"]: {
8644
8648
  import: () => import('../raw/build-nerd-font.mjs').then(r => r.default || r),
8645
- meta: {"type":"text/plain; charset=utf-8","etag":"\"13aa-ys9YUI0kyQcBWrUDXjdBVLF8Z3E\"","mtime":"2026-10-08T06:32:16.322Z"}
8649
+ meta: {"type":"text/plain; charset=utf-8","etag":"\"13aa-ys9YUI0kyQcBWrUDXjdBVLF8Z3E\"","mtime":"2026-10-10T00:23:41.677Z"}
8646
8650
  },
8647
8651
  ["scripts:build-terminal-font.py"]: {
8648
8652
  import: () => import('../raw/build-terminal-font.mjs').then(r => r.default || r),
8649
- meta: {"type":"text/plain; charset=utf-8","etag":"\"cf5-BDqHg2xDFu+6dg84HBWtyBtrEoU\"","mtime":"2026-10-08T06:32:16.322Z"}
8653
+ meta: {"type":"text/plain; charset=utf-8","etag":"\"cf5-BDqHg2xDFu+6dg84HBWtyBtrEoU\"","mtime":"2026-10-10T00:23:41.677Z"}
8654
+ },
8655
+ ["scripts:bump-homebrew.sh"]: {
8656
+ import: () => import('../raw/bump-homebrew.mjs').then(r => r.default || r),
8657
+ meta: {"type":"application/x-sh","etag":"\"61b-suAgb/6azMZO/UmEjog8x0wZsQA\"","mtime":"2026-10-10T00:23:41.677Z"}
8650
8658
  },
8651
8659
  ["scripts:check-leaks.mjs"]: {
8652
8660
  import: () => import('../raw/check-leaks.mjs').then(r => r.default || r),
8653
- meta: {"type":"text/javascript; charset=utf-8","etag":"\"35b3-41HZueHfw7xuicx4q83qwdTEpbc\"","mtime":"2026-10-08T06:32:16.322Z"}
8661
+ meta: {"type":"text/javascript; charset=utf-8","etag":"\"35b3-41HZueHfw7xuicx4q83qwdTEpbc\"","mtime":"2026-10-10T00:23:41.677Z"}
8654
8662
  },
8655
8663
  ["scripts:dev.wherdr.open.plist"]: {
8656
8664
  import: () => import('../raw/dev.wherdr.open.mjs').then(r => r.default || r),
8657
- meta: {"type":"text/plain; charset=utf-8","etag":"\"1de-12VO2xq63mciP26rEzRh3cGfEkI\"","mtime":"2026-10-08T06:32:16.322Z"}
8665
+ meta: {"type":"text/plain; charset=utf-8","etag":"\"1de-12VO2xq63mciP26rEzRh3cGfEkI\"","mtime":"2026-10-10T00:23:41.677Z"}
8658
8666
  },
8659
8667
  ["scripts:docker-entrypoint.sh"]: {
8660
8668
  import: () => import('../raw/docker-entrypoint.mjs').then(r => r.default || r),
8661
- meta: {"type":"application/x-sh","etag":"\"3f3-8om3/mMriIIJYO+YcdXNJsv8tkI\"","mtime":"2026-10-08T06:32:16.322Z"}
8669
+ meta: {"type":"application/x-sh","etag":"\"3f3-8om3/mMriIIJYO+YcdXNJsv8tkI\"","mtime":"2026-10-10T00:23:41.677Z"}
8662
8670
  },
8663
8671
  ["scripts:herdr-plugin.sh"]: {
8664
8672
  import: () => import('../raw/herdr-plugin.mjs').then(r => r.default || r),
8665
- meta: {"type":"application/x-sh","etag":"\"887f-F/dD9YvP8KxGS4qYILnJsc+sKuw\"","mtime":"2026-10-08T06:32:16.322Z"}
8673
+ meta: {"type":"application/x-sh","etag":"\"994f-jk8Je2eDp1XdX/Sk4mufv2vrJOc\"","mtime":"2026-10-10T00:23:41.677Z"}
8666
8674
  },
8667
8675
  ["scripts:install-claude-statusline.sh"]: {
8668
8676
  import: () => import('../raw/install-claude-statusline.mjs').then(r => r.default || r),
8669
- meta: {"type":"application/x-sh","etag":"\"db6-p7KGlvbZ+Qjt+JiT5yVtU4w3kfg\"","mtime":"2026-10-08T06:32:16.322Z"}
8677
+ meta: {"type":"application/x-sh","etag":"\"db6-p7KGlvbZ+Qjt+JiT5yVtU4w3kfg\"","mtime":"2026-10-10T00:23:41.677Z"}
8670
8678
  },
8671
8679
  ["scripts:install-host-open.sh"]: {
8672
8680
  import: () => import('../raw/install-host-open.mjs').then(r => r.default || r),
8673
- meta: {"type":"application/x-sh","etag":"\"103a-/zplHeQSS471/Vz3quwajHmzFQs\"","mtime":"2026-10-08T06:32:16.322Z"}
8681
+ meta: {"type":"application/x-sh","etag":"\"103a-/zplHeQSS471/Vz3quwajHmzFQs\"","mtime":"2026-10-10T00:23:41.677Z"}
8674
8682
  },
8675
8683
  ["scripts:pack-output.mjs"]: {
8676
8684
  import: () => import('../raw/pack-output.mjs').then(r => r.default || r),
8677
- meta: {"type":"text/javascript; charset=utf-8","etag":"\"54b-eU4eNNIxIecG1kwaLdtiTYODSSI\"","mtime":"2026-10-08T06:32:16.322Z"}
8685
+ meta: {"type":"text/javascript; charset=utf-8","etag":"\"54b-eU4eNNIxIecG1kwaLdtiTYODSSI\"","mtime":"2026-10-10T00:23:41.677Z"}
8678
8686
  },
8679
8687
  ["scripts:reveal.sh"]: {
8680
8688
  import: () => import('../raw/reveal.mjs').then(r => r.default || r),
8681
- meta: {"type":"application/x-sh","etag":"\"72e-h/LTEU7exXdiR3GKqhj71UOwAqQ\"","mtime":"2026-10-08T06:32:16.322Z"}
8689
+ meta: {"type":"application/x-sh","etag":"\"72e-h/LTEU7exXdiR3GKqhj71UOwAqQ\"","mtime":"2026-10-10T00:23:41.677Z"}
8682
8690
  },
8683
8691
  ["scripts:wherdr-open.sh"]: {
8684
8692
  import: () => import('../raw/wherdr-open.mjs').then(r => r.default || r),
8685
- meta: {"type":"application/x-sh","etag":"\"b5b-U5OyQk3hziU9KiUyB/1FT5TkOxk\"","mtime":"2026-10-08T06:32:16.322Z"}
8693
+ meta: {"type":"application/x-sh","etag":"\"b5b-U5OyQk3hziU9KiUyB/1FT5TkOxk\"","mtime":"2026-10-10T00:23:41.677Z"}
8686
8694
  },
8687
8695
  ["server:branding:favicon.ico"]: {
8688
8696
  import: () => import('../raw/favicon.mjs').then(r => r.default || r),
8689
- meta: {"type":"image/vnd.microsoft.icon","etag":"\"3aee-Oem7XTWv+9HEwv9u/3K5QcIjS3E\"","mtime":"2026-10-08T06:32:16.324Z"}
8697
+ meta: {"type":"image/vnd.microsoft.icon","etag":"\"3aee-Oem7XTWv+9HEwv9u/3K5QcIjS3E\"","mtime":"2026-10-10T00:23:41.681Z"}
8690
8698
  },
8691
8699
  ["server:branding:icons:apple-touch-icon.png"]: {
8692
8700
  import: () => import('../raw/apple-touch-icon.mjs').then(r => r.default || r),
8693
- meta: {"type":"image/png","etag":"\"1f8-AVRrBnAhalXrGkFPGdvl0fM7DH8\"","mtime":"2026-10-08T06:32:16.324Z"}
8701
+ meta: {"type":"image/png","etag":"\"1f8-AVRrBnAhalXrGkFPGdvl0fM7DH8\"","mtime":"2026-10-10T00:23:41.681Z"}
8694
8702
  },
8695
8703
  ["server:branding:icons:favicon-32.png"]: {
8696
8704
  import: () => import('../raw/favicon-32.mjs').then(r => r.default || r),
8697
- meta: {"type":"image/png","etag":"\"a3-hAEc+Ymq1aYS3kFLyZK/H18Pge8\"","mtime":"2026-10-08T06:32:16.324Z"}
8705
+ meta: {"type":"image/png","etag":"\"a3-hAEc+Ymq1aYS3kFLyZK/H18Pge8\"","mtime":"2026-10-10T00:23:41.681Z"}
8698
8706
  },
8699
8707
  ["server:branding:icons:icon-192.png"]: {
8700
8708
  import: () => import('../raw/icon-192.mjs').then(r => r.default || r),
8701
- meta: {"type":"image/png","etag":"\"222-kioUoPaSCUaRhKKWw0PRtFbAhxk\"","mtime":"2026-10-08T06:32:16.324Z"}
8709
+ meta: {"type":"image/png","etag":"\"222-kioUoPaSCUaRhKKWw0PRtFbAhxk\"","mtime":"2026-10-10T00:23:41.681Z"}
8702
8710
  },
8703
8711
  ["server:branding:icons:icon-512-maskable.png"]: {
8704
8712
  import: () => import('../raw/icon-512-maskable.mjs').then(r => r.default || r),
8705
- meta: {"type":"image/png","etag":"\"84b-mCXgbBkXk7sqOhmJ8hjp80SN3Qk\"","mtime":"2026-10-08T06:32:16.324Z"}
8713
+ meta: {"type":"image/png","etag":"\"84b-mCXgbBkXk7sqOhmJ8hjp80SN3Qk\"","mtime":"2026-10-10T00:23:41.681Z"}
8706
8714
  },
8707
8715
  ["server:branding:icons:icon-512.png"]: {
8708
8716
  import: () => import('../raw/icon-512.mjs').then(r => r.default || r),
8709
- meta: {"type":"image/png","etag":"\"8fc-ZwRMqAqQZeLLx4iNTZxs09C4Q1I\"","mtime":"2026-10-08T06:32:16.324Z"}
8717
+ meta: {"type":"image/png","etag":"\"8fc-ZwRMqAqQZeLLx4iNTZxs09C4Q1I\"","mtime":"2026-10-10T00:23:41.681Z"}
8710
8718
  },
8711
8719
  ["server:branding:icons:icon-maskable.svg"]: {
8712
8720
  import: () => import('../raw/icon-maskable.mjs').then(r => r.default || r),
8713
- meta: {"type":"image/svg+xml","etag":"\"4a1-7XtnuN5ZOwiwIjpgpj+/Rg9LssY\"","mtime":"2026-10-08T06:32:16.324Z"}
8721
+ meta: {"type":"image/svg+xml","etag":"\"4a1-7XtnuN5ZOwiwIjpgpj+/Rg9LssY\"","mtime":"2026-10-10T00:23:41.681Z"}
8714
8722
  },
8715
8723
  ["server:branding:icons:icon.svg"]: {
8716
8724
  import: () => import('../raw/icon.mjs').then(r => r.default || r),
8717
- meta: {"type":"image/svg+xml","etag":"\"49c-O1XxkWwKTf6VBoC0i4TEPdGPsCw\"","mtime":"2026-10-08T06:32:16.324Z"}
8725
+ meta: {"type":"image/svg+xml","etag":"\"49c-O1XxkWwKTf6VBoC0i4TEPdGPsCw\"","mtime":"2026-10-10T00:23:41.681Z"}
8718
8726
  }
8719
8727
  };
8720
8728
 
@@ -9617,7 +9625,7 @@ function _expandFromEnv(value) {
9617
9625
  const _inlineRuntimeConfig = {
9618
9626
  "app": {
9619
9627
  "baseURL": "/",
9620
- "buildId": "ff11fc8b-1e70-4261-a76e-a52155efb196",
9628
+ "buildId": "7afbf062-5369-4002-b20d-7a582ff6e0f1",
9621
9629
  "buildAssetsDir": "/_nuxt/",
9622
9630
  "cdnURL": ""
9623
9631
  },
@@ -10193,6 +10201,7 @@ const HERDR_CHILD_ENV = Object.fromEntries(
10193
10201
  const AGENT_KINDS = (env.AGENT_KINDS || "pi,claude,codex,gemini,cursor,devin,agy,cline,omp,mastracode,opencode,copilot,kimi,kiro,droid,amp,grok,hermes,kilo,qodercli,qwen,letta,maki,muse").split(",").map((s) => s.trim()).filter(Boolean);
10194
10202
  const APP_URL_FILE = path$1.join(DATA_DIR, "app-url.json");
10195
10203
  const ENV_APP_URL = /^https:\/\//i.test(env.APP_URL || "") ? env.APP_URL : "";
10204
+ const BOOT_APP_URL = env.APP_URL || "";
10196
10205
  function savedAppUrl() {
10197
10206
  var _a;
10198
10207
  try {
@@ -10205,8 +10214,13 @@ function savedAppUrl() {
10205
10214
  if (!ENV_APP_URL && savedAppUrl()) env.APP_URL = savedAppUrl();
10206
10215
  const appUrl = () => env.APP_URL || "mailto:herdr-web@localhost";
10207
10216
  const HOST_LABEL = (env.HOST_LABEL || os.hostname()).trim().slice(0, 40);
10217
+ function machineLabel(tailnetName, fallback = HOST_LABEL, containerName = IN_DOCKER && !(env.HOST_LABEL || "").trim()) {
10218
+ const short = (tailnetName || "").split(".")[0].trim().slice(0, 40);
10219
+ return containerName && short ? short : fallback;
10220
+ }
10208
10221
  const PASSKEY_USER = (env.PASSKEY_USER || "wherdr").trim().slice(0, 64);
10209
10222
  const POLL_MS = Number(env.POLL_MS || 1e3);
10223
+ const TAILNET_REFRESH_MS = Number(env.TAILNET_REFRESH_MS || 3e4);
10210
10224
  const NOTIFY_SETTLE_MS = Number(env.NOTIFY_SETTLE_MS || 4e3);
10211
10225
  const UPLOAD_DIR = env.UPLOAD_DIR || path$1.join(HOME, ".cache/herdr-web/uploads");
10212
10226
  const UPLOAD_TTL_MS = 7 * 864e5;
@@ -10551,7 +10565,7 @@ async function agentPrompt(target, text) {
10551
10565
  await herdr("agent.prompt", { target, text });
10552
10566
  }
10553
10567
  }
10554
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
10568
+ const sleep$1 = (ms) => new Promise((r) => setTimeout(r, ms));
10555
10569
 
10556
10570
  var __defProp$5 = Object.defineProperty;
10557
10571
  var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -12831,6 +12845,19 @@ async function ompCommandTemplates(fs, home) {
12831
12845
  return out;
12832
12846
  }
12833
12847
 
12848
+ const PASTE_MIN_LINES = 12;
12849
+ const PASTE_MIN_CHARS = 1500;
12850
+ const lineCount = (s) => s ? s.split("\n").length : 0;
12851
+ const isLongPaste = (s) => {
12852
+ const t = s.trim();
12853
+ return lineCount(t) > PASTE_MIN_LINES || t.length > PASTE_MIN_CHARS;
12854
+ };
12855
+ const BLOCK = /<pasted_content(?:\s[^>]*)?>\n?([\s\S]*?)\n?<\/pasted_content(?:\s[^>]*)?>/g;
12856
+ function pastedBlocks(raw) {
12857
+ if (!raw.includes("<pasted_content")) return [];
12858
+ return [...raw.matchAll(BLOCK)].map((m) => m[1].trim()).filter(isLongPaste);
12859
+ }
12860
+
12834
12861
  const WINDOW_BYTES = 2 * 1024 * 1024;
12835
12862
  const MAX_READ_BYTES = 24 * 1024 * 1024;
12836
12863
  const PAGE_ITEMS = 120;
@@ -12849,6 +12876,10 @@ function humanText(t) {
12849
12876
  if (u !== s) return u;
12850
12877
  return isNoise(s) ? null : s;
12851
12878
  }
12879
+ const pastedOf = (raw) => {
12880
+ const b = pastedBlocks(raw).map((s) => clip(s));
12881
+ return b.length ? { pasted: b } : {};
12882
+ };
12852
12883
  const normMsg = (t) => stripImageTags(t).replace(/\s+/g, " ").trim().toLowerCase();
12853
12884
  function sameMsg(queued, said) {
12854
12885
  const q = normMsg(queued).slice(0, 60);
@@ -13026,10 +13057,11 @@ function parseClaude(lines, home = "") {
13026
13057
  const ts = d.timestamp || null;
13027
13058
  if (d.type === "attachment" && d.attachment && d.attachment.type === "queued_command" && d.attachment.commandMode === "prompt" && (d.attachment.origin || {}).kind === "human") {
13028
13059
  const parts = Array.isArray(d.attachment.prompt) ? d.attachment.prompt : [{ type: "text", text: String(d.attachment.prompt || "") }];
13029
- const text = stripImageTags(unwrapPasted(parts.filter((p) => p.type === "text").map((p) => p.text).join("\n")));
13060
+ const raw = parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
13061
+ const text = stripImageTags(unwrapPasted(raw));
13030
13062
  const images = parts.filter((p) => p.type === "image").length;
13031
13063
  if (text || images) {
13032
- items.push({ role: "user", text: clip(text), images, ref: images ? ref : void 0, ts: d.attachment.timestamp || ts });
13064
+ items.push({ role: "user", text: clip(text), images, ref: images ? ref : void 0, ts: d.attachment.timestamp || ts, ...pastedOf(raw) });
13033
13065
  pendingCmd = null;
13034
13066
  said(text, d.attachment.timestamp || ts, images);
13035
13067
  }
@@ -13080,7 +13112,7 @@ function parseClaude(lines, home = "") {
13080
13112
  } else {
13081
13113
  const text2 = humanText(content);
13082
13114
  if (text2) {
13083
- items.push({ role: "user", text: clip(text2), ts });
13115
+ items.push({ role: "user", text: clip(text2), ts, ...pastedOf(content) });
13084
13116
  pendingCmd = null;
13085
13117
  said(text2, ts);
13086
13118
  noteSaid(d);
@@ -13090,6 +13122,7 @@ function parseClaude(lines, home = "") {
13090
13122
  }
13091
13123
  if (!Array.isArray(content)) continue;
13092
13124
  let text = "";
13125
+ let raw = "";
13093
13126
  let images = 0;
13094
13127
  let toolImages = content.filter((p) => p && p.type === "image").length;
13095
13128
  for (const part of content) {
@@ -13107,12 +13140,13 @@ function parseClaude(lines, home = "") {
13107
13140
  if (special(String(part.text || ""), ts)) continue;
13108
13141
  const t = humanText(part.text);
13109
13142
  if (t === null) continue;
13143
+ raw += "\n" + String(part.text || "");
13110
13144
  if (/^\[Request interrupted/.test(t)) items.push({ role: "system", text: "Interrupted", ts });
13111
13145
  else text += (text ? "\n" : "") + unwrapPasted(t.replace(/\[Image #\d+\]\s*/g, ""));
13112
13146
  } else if (part.type === "image") images++;
13113
13147
  }
13114
13148
  if (text.trim() || images) {
13115
- items.push({ role: "user", text: clip(text.trim()), images, ref: images ? ref : void 0, ts });
13149
+ items.push({ role: "user", text: clip(text.trim()), images, ref: images ? ref : void 0, ts, ...pastedOf(raw) });
13116
13150
  pendingCmd = null;
13117
13151
  said(text, ts, images);
13118
13152
  noteSaid(d);
@@ -13960,14 +13994,25 @@ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key,
13960
13994
  var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
13961
13995
  const fsp$3 = fs.promises;
13962
13996
  const LOCAL_LABEL_FILE = path$1.join(DATA_DIR, "machine-label.txt");
13963
- function readLocalLabel() {
13997
+ function renamedLabel() {
13964
13998
  try {
13965
- return fs.readFileSync(LOCAL_LABEL_FILE, "utf8").trim().slice(0, 40) || HOST_LABEL;
13999
+ return fs.readFileSync(LOCAL_LABEL_FILE, "utf8").trim().slice(0, 40);
13966
14000
  } catch {
13967
- return HOST_LABEL;
14001
+ return "";
13968
14002
  }
13969
14003
  }
13970
14004
  const localMachineLabel = () => localMachine.label;
14005
+ let tailnetLabel = "";
14006
+ function adoptTailnetName(name) {
14007
+ const label = machineLabel(name);
14008
+ if (label === HOST_LABEL || label === tailnetLabel) return;
14009
+ tailnetLabel = label;
14010
+ if (renamedLabel() || localMachine.label === label) return;
14011
+ localMachine.label = label;
14012
+ for (const other of sessions.values()) if (!(other instanceof RemoteMachine)) other.label = label;
14013
+ log(`machine name: ${label} (from Tailscale)`);
14014
+ changed();
14015
+ }
13971
14016
  function localOs() {
13972
14017
  const p = os.platform();
13973
14018
  return p === "darwin" ? "Darwin" : p === "linux" ? "Linux" : p;
@@ -13975,7 +14020,7 @@ function localOs() {
13975
14020
  const localMachine = {
13976
14021
  key: LOCAL,
13977
14022
  profileId: null,
13978
- label: readLocalLabel(),
14023
+ label: renamedLabel() || HOST_LABEL,
13979
14024
  local: true,
13980
14025
  target: null,
13981
14026
  session: HERDR_SESSION || "default",
@@ -14011,7 +14056,7 @@ function localMachineId() {
14011
14056
  }
14012
14057
  return selfId;
14013
14058
  }
14014
- const selfNames = () => [HOST_LABEL, os.hostname(), ...SELF_HOSTS].filter(Boolean);
14059
+ const selfNames = () => [HOST_LABEL, os.hostname(), tailnetLabel, ...SELF_HOSTS].filter(Boolean);
14015
14060
  class SkipMachine extends Error {
14016
14061
  }
14017
14062
  const PROBE_SCRIPT = `echo "home=$HOME"
@@ -14917,6 +14962,23 @@ function parseWaitScreen(text, { choices = false } = {}) {
14917
14962
  return { kind, title: title ? title.slice(0, 200) : null, lines: body, actions };
14918
14963
  }
14919
14964
 
14965
+ const ROW = /^\s*Settings((?:\s{2,}[A-Z][\w-]*)+)\s*$/;
14966
+ function tabsOfRow(line) {
14967
+ const m = ROW.exec(line);
14968
+ if (!m) return null;
14969
+ const tabs = m[1].trim().split(/\s{2,}/);
14970
+ return tabs.length >= 2 ? tabs : null;
14971
+ }
14972
+ function settingsRowIndex(lines) {
14973
+ return lines.findIndex((l) => tabsOfRow(l) !== null);
14974
+ }
14975
+ function settingsTabs(text) {
14976
+ if (!text) return null;
14977
+ const lines = String(text).split("\n");
14978
+ const i = settingsRowIndex(lines);
14979
+ return i < 0 ? null : tabsOfRow(lines[i]);
14980
+ }
14981
+
14920
14982
  const SGR$1 = /\x1b\[([0-9;]*)m/g;
14921
14983
  const OTHER_ESC = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
14922
14984
  const TOP = /^\s*▔{3,}(?:.*▔)?\s*$/;
@@ -15041,6 +15103,7 @@ function parseMenu(text) {
15041
15103
  const region = rows.slice(top + 1);
15042
15104
  while (region.length && !region[region.length - 1].text.trim()) region.pop();
15043
15105
  if (!region.length) return null;
15106
+ if (settingsRowIndex(region.map((r) => r.text)) >= 0) return null;
15044
15107
  const joined = (from) => parseLegend(region.slice(from).map((r) => r.text.trim()).join(" "));
15045
15108
  let legendAt = -1;
15046
15109
  let legend = null;
@@ -15344,6 +15407,12 @@ const QUEUED_TTL_MS = 60 * 60 * 1e3;
15344
15407
  const norm$3 = (t) => String(t || "").replace(/\s+/g, " ").trim().toLowerCase();
15345
15408
  const bashText = (t) => String(t || "").replace(/^\s*(?:!!?|\$\$?)\s*/, "");
15346
15409
  const isOmpRun = (agent, text) => agent === "omp" && /^\s*(?:!|\$\$?\s)/.test(String(text || ""));
15410
+ function ompRunShown(agent, text, shell) {
15411
+ if (!shell || !isOmpRun(agent, text) || /^\s*\$/.test(text) !== Boolean(shell.python)) return false;
15412
+ const want = bashText(text).replace(/\s+/g, "").toLowerCase().slice(0, 80);
15413
+ const shown = String(shell.command || "").replace(/\s+/g, "").toLowerCase().slice(0, 80);
15414
+ return Boolean(want && shown) && (shown.startsWith(want) || shown.length >= 8 && want.startsWith(shown));
15415
+ }
15347
15416
  function queuedDone(q, items, idle, now, agent) {
15348
15417
  if (now - q.at > QUEUED_TTL_MS) return true;
15349
15418
  if (idle && q.turnSeen && isOmpRun(agent, q.text)) return true;
@@ -15593,22 +15662,25 @@ function unansweredTurn(items, text) {
15593
15662
  const wanted = msgText(text);
15594
15663
  return (wanted ? sameMsg(wanted, last.text) : photosLanded(text, last)) ? last : null;
15595
15664
  }
15665
+ const empty = (box) => !box || box === "!";
15596
15666
  async function takeBackInterrupted(d) {
15667
+ var _a;
15597
15668
  let box = "";
15598
- for (let i = 0; i < 3 && !box; i++) {
15669
+ for (let i = 0; i < 3 && empty(box); i++) {
15599
15670
  box = inputBox(await d.screen().catch(() => "")) || "";
15600
- if (!box) await d.sleep(250);
15671
+ if (empty(box)) await d.sleep(250);
15601
15672
  }
15602
- if (!box) return null;
15673
+ if (empty(box)) return null;
15603
15674
  const last = unansweredLast(await d.chat());
15604
- if (!last || !boxHolds(box, msgText(last.text))) return null;
15675
+ const mine = last && boxHolds(box, msgText(last.text)) ? last : (((_a = d.pending) == null ? void 0 : _a.call(d)) || []).map(msgText).filter((t) => boxHolds(box, t)).map((text) => ({ role: "user", text, ts: null }))[0];
15676
+ if (!mine) return null;
15605
15677
  const lines = box.split("\n").length;
15606
15678
  for (let i = 0; i < 2; i++) {
15607
- if (i && !boxHolds(box, msgText(last.text))) return null;
15679
+ if (i && !boxHolds(box, msgText(mine.text))) return null;
15608
15680
  await d.keys(clearKeys(lines));
15609
15681
  await d.sleep(150);
15610
15682
  box = inputBox(await d.screen().catch(() => "")) || "";
15611
- if (!box) return last;
15683
+ if (empty(box)) return mine;
15612
15684
  }
15613
15685
  return null;
15614
15686
  }
@@ -16140,7 +16212,7 @@ async function createAgent(body) {
16140
16212
  created = await hx("worktree.create", params, 3e4);
16141
16213
  } catch (e) {
16142
16214
  if (e.code !== "not_git_worktree") throw e;
16143
- await sleep(1e3);
16215
+ await sleep$1(1e3);
16144
16216
  created = await hx("worktree.create", params, 3e4);
16145
16217
  }
16146
16218
  } else if (!target) {
@@ -16165,7 +16237,7 @@ async function createAgent(body) {
16165
16237
  const pathSetup = 'export PATH="$HOME/.local/bin:$HOME/.kimi-code/bin:$HOME/.bun/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"';
16166
16238
  await herdr("pane.send_input", { pane_id: paneId, text: pathSetup });
16167
16239
  await herdr("pane.send_input", { pane_id: paneId, keys: ["enter"] });
16168
- await sleep(400);
16240
+ await sleep$1(400);
16169
16241
  let lastErr = null;
16170
16242
  for (let i = 0; i < 16; i++) {
16171
16243
  try {
@@ -16180,7 +16252,7 @@ async function createAgent(body) {
16180
16252
  break;
16181
16253
  }
16182
16254
  if (lastErr.code === "timeout" || lastErr.code === "unreachable" || /name/i.test(lastErr.code)) break;
16183
- await sleep(500);
16255
+ await sleep$1(500);
16184
16256
  }
16185
16257
  }
16186
16258
  if (lastErr) {
@@ -16266,7 +16338,7 @@ async function closePanel(paneId) {
16266
16338
  if (!panelOpen(r.read && r.read.text, p.agent)) return sent;
16267
16339
  await herdr("pane.send_input", { pane_id: paneId, keys: ["esc"] });
16268
16340
  sent = true;
16269
- await sleep(400);
16341
+ await sleep$1(400);
16270
16342
  }
16271
16343
  return sent;
16272
16344
  }
@@ -16305,10 +16377,184 @@ async function readUpload(name) {
16305
16377
  return { type: ext === "jpg" ? "image/jpeg" : `image/${ext}`, body: await fsp$2.readFile(path$1.join(UPLOAD_DIR, name)) };
16306
16378
  }
16307
16379
 
16380
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
16381
+ function createPaneSizes(deps) {
16382
+ const entries = /* @__PURE__ */ new Map();
16383
+ const log = deps.log || (() => {
16384
+ });
16385
+ function save() {
16386
+ if (!deps.save) return;
16387
+ const out = {};
16388
+ for (const [pane, e] of entries) if (e.original) out[pane] = e.original;
16389
+ deps.save(out);
16390
+ }
16391
+ function drop(pane, e) {
16392
+ if (entries.get(pane) !== e) return;
16393
+ entries.delete(pane);
16394
+ save();
16395
+ }
16396
+ function keepReason(e, now) {
16397
+ const o = e.original;
16398
+ if (!now) return "pane gone";
16399
+ if (o.terminal && now.terminal && o.terminal !== now.terminal) return "another terminal";
16400
+ if (now.rows === o.rows && now.cols === o.cols) return "already at its size";
16401
+ const set = e.set;
16402
+ if (set && (now.rows !== set.rows || now.exact && now.cols !== set.cols)) return `resized by another client to ${now.cols}x${now.rows}`;
16403
+ return null;
16404
+ }
16405
+ async function restore(pane, e) {
16406
+ var _a;
16407
+ const o = e.original;
16408
+ const why = keepReason(e, await deps.read(pane).catch(() => null));
16409
+ if (why) return log(`term ${pane} size kept (${why})`);
16410
+ for (let attempt = 0; attempt < 2; attempt++) {
16411
+ if (attempt) await sleep((_a = deps.retryMs) != null ? _a : 1500);
16412
+ if (e.clients > 0) return;
16413
+ if (await deps.resize(pane, o.cols, o.rows).catch(() => false)) return;
16414
+ }
16415
+ log(`term ${pane} could not get its ${o.cols}x${o.rows} back`);
16416
+ }
16417
+ function schedule(pane, e, ms) {
16418
+ clearTimeout(e.timer);
16419
+ e.timer = setTimeout(() => {
16420
+ e.timer = void 0;
16421
+ e.restoring = restore(pane, e).catch(() => {
16422
+ }).finally(() => {
16423
+ e.restoring = null;
16424
+ if (e.clients === 0 && !e.timer) drop(pane, e);
16425
+ });
16426
+ }, ms);
16427
+ }
16428
+ async function hold(pane) {
16429
+ let e = entries.get(pane);
16430
+ if (e) {
16431
+ clearTimeout(e.timer);
16432
+ e.timer = void 0;
16433
+ e.clients++;
16434
+ } else {
16435
+ const fresh = { original: null, ready: Promise.resolve(), clients: 1, set: null, restoring: null };
16436
+ fresh.ready = deps.read(pane).catch(() => null).then((size) => {
16437
+ fresh.original = size;
16438
+ if (size) save();
16439
+ });
16440
+ entries.set(pane, fresh);
16441
+ e = fresh;
16442
+ }
16443
+ const entry = e;
16444
+ await entry.ready;
16445
+ if (entry.restoring) await entry.restoring;
16446
+ let released = false;
16447
+ return {
16448
+ resized(cols, rows) {
16449
+ if (!released) entry.set = { cols, rows };
16450
+ },
16451
+ release(clean) {
16452
+ if (released) return;
16453
+ released = true;
16454
+ if (--entry.clients > 0) return;
16455
+ if (!entry.original) return drop(pane, entry);
16456
+ schedule(pane, entry, clean ? deps.closeMs : deps.graceMs);
16457
+ }
16458
+ };
16459
+ }
16460
+ function adopt(sizes) {
16461
+ for (const [pane, size] of Object.entries(sizes)) {
16462
+ if (entries.has(pane)) continue;
16463
+ const e = { original: size, ready: Promise.resolve(), clients: 0, set: null, restoring: null };
16464
+ entries.set(pane, e);
16465
+ schedule(pane, e, deps.graceMs);
16466
+ }
16467
+ }
16468
+ function stop() {
16469
+ for (const e of entries.values()) clearTimeout(e.timer);
16470
+ }
16471
+ return { hold, adopt, stop, held: () => [...entries.keys()] };
16472
+ }
16473
+ function parseSttySize(out) {
16474
+ const m = /^\s*(\d{1,4})\s+(\d{1,4})\s*$/.exec(out);
16475
+ if (!m) return null;
16476
+ const rows = Number(m[1]);
16477
+ const cols = Number(m[2]);
16478
+ return rows > 0 && cols > 0 ? { cols, rows } : null;
16479
+ }
16480
+ function cleanSavedSizes(raw, isPane) {
16481
+ const out = {};
16482
+ if (!raw || typeof raw !== "object") return out;
16483
+ for (const [pane, v] of Object.entries(raw)) {
16484
+ const s = v;
16485
+ if (!isPane(pane) || !s || !Number.isInteger(s.cols) || !Number.isInteger(s.rows)) continue;
16486
+ if (s.cols < 10 || s.cols > 400 || s.rows < 5 || s.rows > 200) continue;
16487
+ out[pane] = { cols: s.cols, rows: s.rows, exact: s.exact === true, ...typeof s.terminal === "string" ? { terminal: s.terminal } : {} };
16488
+ }
16489
+ return out;
16490
+ }
16491
+
16308
16492
  const clampInt = (v, lo, hi, dflt) => {
16309
16493
  const n = parseInt(String(v), 10);
16310
16494
  return Number.isFinite(n) ? Math.min(hi, Math.max(lo, n)) : dflt;
16311
16495
  };
16496
+ const TTY_SIZE_SCRIPT = 't=$(ps -o tty= -p "$1" 2>/dev/null | tr -d " "); [ -n "$t" ] && [ -c "/dev/$t" ] && stty size < "/dev/$t"';
16497
+ function ttySize(pane, pid) {
16498
+ const machine = machineOfPane(pane);
16499
+ if (!machine) return Promise.resolve("");
16500
+ if (machine.exec) return machine.exec(TTY_SIZE_SCRIPT, [String(pid)], { timeoutMs: 4e3 }).then((r) => r.code === 0 ? r.stdout.toString("utf8") : "", () => "");
16501
+ if (IN_DOCKER) return Promise.resolve("");
16502
+ return new Promise((resolve) => execFile("sh", ["-c", TTY_SIZE_SCRIPT, "sh", String(pid)], { timeout: 2e3 }, (err, stdout) => resolve(err ? "" : String(stdout))));
16503
+ }
16504
+ async function readPaneSize(pane) {
16505
+ var _a, _b, _c, _d, _e, _f, _g, _h;
16506
+ const p = (_a = await herdr("pane.get", { pane_id: pane }, 3e3).catch(() => null)) == null ? void 0 : _a.pane;
16507
+ if (!p) return null;
16508
+ const terminal = typeof p.terminal_id === "string" ? { terminal: p.terminal_id } : {};
16509
+ const rows = Number((_b = p.scroll) == null ? void 0 : _b.viewport_rows) || 0;
16510
+ const pid = Number((_d = (_c = await herdr("pane.process_info", { pane_id: pane }, 3e3).catch(() => null)) == null ? void 0 : _c.process_info) == null ? void 0 : _d.shell_pid);
16511
+ if (Number.isInteger(pid) && pid > 1) {
16512
+ const tty = parseSttySize(await ttySize(pane, pid));
16513
+ if (tty && (!rows || tty.rows === rows)) return { ...tty, exact: true, ...terminal };
16514
+ }
16515
+ const local = splitId(pane).local;
16516
+ const rect = (_h = (_g = (_f = (_e = await herdr("pane.layout", { pane_id: pane }, 3e3).catch(() => null)) == null ? void 0 : _e.layout) == null ? void 0 : _f.panes) == null ? void 0 : _g.find((x) => x.pane_id === local)) == null ? void 0 : _h.rect;
16517
+ const cols = Number(rect == null ? void 0 : rect.width) || 0;
16518
+ if (!rows || !cols) return null;
16519
+ return { cols, rows, exact: false, ...terminal };
16520
+ }
16521
+ const sizesFile = () => path$1.join(DATA_DIR, "term-sizes.json");
16522
+ const delay = (name, dflt) => {
16523
+ const n = Number(process.env[name]);
16524
+ return Number.isFinite(n) && n >= 0 ? n : dflt;
16525
+ };
16526
+ let sizes = null;
16527
+ const paneSizes = () => sizes || (sizes = createPaneSizes({
16528
+ read: readPaneSize,
16529
+ resize: (pane, cols, rows) => resizePane(pane, cols, rows),
16530
+ // Longer than the 1.5 s left to a closed control session to go away.
16531
+ closeMs: delay("WHERDR_TERM_CLOSE_MS", 2e3),
16532
+ graceMs: delay("WHERDR_TERM_GRACE_MS", 3e4),
16533
+ save(held) {
16534
+ const file = sizesFile();
16535
+ try {
16536
+ if (!Object.keys(held).length) return fs.rmSync(file, { force: true });
16537
+ fs.mkdirSync(DATA_DIR, { recursive: true });
16538
+ fs.writeFileSync(`${file}.tmp`, JSON.stringify(held) + "\n", { mode: 384 });
16539
+ fs.renameSync(`${file}.tmp`, file);
16540
+ } catch (e) {
16541
+ log(`term sizes: ${e.message}`);
16542
+ }
16543
+ },
16544
+ log
16545
+ }));
16546
+ function startPaneSizes() {
16547
+ let saved = null;
16548
+ try {
16549
+ saved = JSON.parse(fs.readFileSync(sizesFile(), "utf8"));
16550
+ } catch {
16551
+ return;
16552
+ }
16553
+ paneSizes().adopt(cleanSavedSizes(saved, (id) => PANE_RE.test(id)));
16554
+ }
16555
+ function stopPaneSizes() {
16556
+ sizes == null ? void 0 : sizes.stop();
16557
+ }
16312
16558
  function openTerm(ws, url) {
16313
16559
  const pane = url.searchParams.get("pane") || "";
16314
16560
  if (!PANE_RE.test(pane)) {
@@ -16325,30 +16571,53 @@ function openTerm(ws, url) {
16325
16571
  const rows = clampInt(url.searchParams.get("rows"), 5, 200, 24);
16326
16572
  const args = ["terminal", "session", "control", splitId(pane).local, "--cols", String(cols), "--rows", String(rows)];
16327
16573
  if (url.searchParams.get("takeover") === "1") args.push("--takeover");
16328
- const child = machine.spawnHerdr(args);
16329
16574
  const sess = { pane, visible: true };
16330
16575
  termSessions.add(sess);
16331
- log(`term ${pane} opened ${cols}x${rows}${args.includes("--takeover") ? " (takeover)" : ""}${machine.local ? "" : ` on ${machine.label}`}`);
16332
- readline.createInterface({ input: child.stdout }).on("line", (line) => {
16333
- if (ws.isOpen()) ws.send(line);
16334
- });
16335
- readline.createInterface({ input: child.stderr }).on("line", (line) => {
16336
- log(`term ${pane} stderr: ${line}`);
16337
- if (ws.isOpen()) ws.send(JSON.stringify({ type: "herdr.stderr", message: line }));
16338
- });
16339
- child.on("error", (e) => {
16340
- if (ws.isOpen()) ws.send(JSON.stringify({ type: "terminal.closed", reason: `herdr: ${e.message}` }));
16341
- ws.close(4500, "herdr error");
16342
- });
16343
- child.on("exit", (code) => {
16344
- termSessions.delete(sess);
16345
- if (ws.isOpen()) ws.close(4e3, `herdr exit ${code}`);
16346
- });
16347
- child.stdin.on("error", () => {
16348
- });
16576
+ let child = null;
16577
+ let hold = null;
16578
+ let closed = false;
16579
+ let size = { cols, rows };
16580
+ let attached = false;
16581
+ const early = [];
16349
16582
  const toChild = (obj) => {
16350
- if (child.stdin.writable) child.stdin.write(JSON.stringify(obj) + "\n");
16583
+ if (!child) {
16584
+ if (early.length < 64) early.push(obj);
16585
+ } else if (child.stdin.writable) {
16586
+ child.stdin.write(JSON.stringify(obj) + "\n");
16587
+ }
16351
16588
  };
16589
+ function start() {
16590
+ const c = machine.spawnHerdr(args);
16591
+ child = c;
16592
+ log(`term ${pane} opened ${cols}x${rows}${args.includes("--takeover") ? " (takeover)" : ""}${machine.local ? "" : ` on ${machine.label}`}`);
16593
+ readline.createInterface({ input: c.stdout }).on("line", (line) => {
16594
+ if (!attached && line.includes('"terminal.frame"')) {
16595
+ attached = true;
16596
+ hold == null ? void 0 : hold.resized(size.cols, size.rows);
16597
+ }
16598
+ if (ws.isOpen()) ws.send(line);
16599
+ });
16600
+ readline.createInterface({ input: c.stderr }).on("line", (line) => {
16601
+ log(`term ${pane} stderr: ${line}`);
16602
+ if (ws.isOpen()) ws.send(JSON.stringify({ type: "herdr.stderr", message: line }));
16603
+ });
16604
+ c.on("error", (e) => {
16605
+ if (ws.isOpen()) ws.send(JSON.stringify({ type: "terminal.closed", reason: `herdr: ${e.message}` }));
16606
+ ws.close(4500, "herdr error");
16607
+ });
16608
+ c.on("exit", (code) => {
16609
+ termSessions.delete(sess);
16610
+ if (ws.isOpen()) ws.close(4e3, `herdr exit ${code}`);
16611
+ });
16612
+ c.stdin.on("error", () => {
16613
+ });
16614
+ for (const m of early.splice(0)) toChild(m);
16615
+ }
16616
+ paneSizes().hold(pane).then((h) => {
16617
+ if (closed) return h.release(true);
16618
+ hold = h;
16619
+ start();
16620
+ });
16352
16621
  return {
16353
16622
  onMessage(raw) {
16354
16623
  let m;
@@ -16362,9 +16631,12 @@ function openTerm(ws, url) {
16362
16631
  if (typeof m.text === "string" && m.text.length <= 65536) toChild({ type: "terminal.input", text: m.text });
16363
16632
  else if (typeof m.bytes === "string" && m.bytes.length <= 9e4) toChild({ type: "terminal.input", bytes: m.bytes });
16364
16633
  break;
16365
- case "terminal.resize":
16366
- toChild({ type: "terminal.resize", cols: clampInt(m.cols, 10, 400, cols), rows: clampInt(m.rows, 5, 200, rows) });
16634
+ case "terminal.resize": {
16635
+ size = { cols: clampInt(m.cols, 10, 400, cols), rows: clampInt(m.rows, 5, 200, rows) };
16636
+ if (attached) hold == null ? void 0 : hold.resized(size.cols, size.rows);
16637
+ toChild({ type: "terminal.resize", ...size });
16367
16638
  break;
16639
+ }
16368
16640
  case "terminal.scroll":
16369
16641
  if (m.direction === "up" || m.direction === "down") {
16370
16642
  toChild({ type: "terminal.scroll", direction: m.direction, lines: clampInt(m.lines, 1, 500, 3) });
@@ -16388,13 +16660,18 @@ function openTerm(ws, url) {
16388
16660
  break;
16389
16661
  }
16390
16662
  },
16391
- onClose() {
16663
+ onClose(clean = false) {
16664
+ closed = true;
16392
16665
  termSessions.delete(sess);
16393
- toChild({ type: "terminal.release" });
16394
- child.stdin.end();
16395
- setTimeout(() => {
16396
- if (child.exitCode === null) child.kill("SIGTERM");
16397
- }, 1500);
16666
+ const c = child;
16667
+ if (c) {
16668
+ toChild({ type: "terminal.release" });
16669
+ c.stdin.end();
16670
+ setTimeout(() => {
16671
+ if (c.exitCode === null) c.kill("SIGTERM");
16672
+ }, 1500);
16673
+ }
16674
+ hold == null ? void 0 : hold.release(clean && sess.visible);
16398
16675
  }
16399
16676
  };
16400
16677
  }
@@ -16555,14 +16832,14 @@ async function waitMenu(paneId, ok, timeoutMs = 6e3) {
16555
16832
  const m = parseModelMenu(await screen(paneId));
16556
16833
  if (m && ok(m)) return m;
16557
16834
  if (Date.now() > until) return null;
16558
- await sleep(250);
16835
+ await sleep$1(250);
16559
16836
  }
16560
16837
  }
16561
16838
  async function closeMenu(paneId) {
16562
16839
  for (let i = 0; i < 3; i++) {
16563
16840
  if (!parseModelMenu(await screen(paneId))) return;
16564
16841
  await herdr("pane.send_input", { pane_id: paneId, keys: ["esc"] });
16565
- await sleep(400);
16842
+ await sleep$1(400);
16566
16843
  }
16567
16844
  }
16568
16845
  async function openMenu(p) {
@@ -16590,7 +16867,7 @@ async function readAllOptions(paneId, first) {
16590
16867
  for (let i = 0; i < 8 && m.scrollDown; i++) {
16591
16868
  const last = m.options[m.options.length - 1].n;
16592
16869
  await herdr("pane.send_input", { pane_id: paneId, keys: moveKeys(m.cursor, last + 1) });
16593
- await sleep(350);
16870
+ await sleep$1(350);
16594
16871
  const next = await waitMenu(paneId, (x) => x.kind === "model", 2e3);
16595
16872
  if (!next || next.cursor <= last) break;
16596
16873
  const before = all.size;
@@ -16672,7 +16949,7 @@ async function setModel(paneId, wanted) {
16672
16949
  const confirm = p.agent === "claude" ? switchConfirmKeys(text, confirmAs) : null;
16673
16950
  if (confirm) await herdr("pane.send_input", { pane_id: p.id, keys: confirm });
16674
16951
  clear = confirm || parseModelMenu(text) ? 0 : clear + 1;
16675
- await sleep(confirm ? 400 : 250);
16952
+ await sleep$1(confirm ? 400 : 250);
16676
16953
  }
16677
16954
  if (clear < 2) throw new HerdrError("stale", "The /model menu changed \u2014 try again.");
16678
16955
  const info = { id: null, label: confirmAs, effort, at: new Date(started).toISOString() };
@@ -16695,14 +16972,14 @@ async function waitOmpSelector(paneId, ok, timeoutMs = 6e3) {
16695
16972
  const s = await ompSelector(paneId).catch(() => null);
16696
16973
  if (s && ok(s)) return s;
16697
16974
  if (Date.now() > until) return null;
16698
- await sleep(250);
16975
+ await sleep$1(250);
16699
16976
  }
16700
16977
  }
16701
16978
  async function closeOmpSelector(paneId) {
16702
16979
  for (let i = 0; i < 3; i++) {
16703
16980
  if (!await ompSelector(paneId).catch(() => null)) return;
16704
16981
  await herdr("pane.send_input", { pane_id: paneId, keys: ["esc"] });
16705
- await sleep(400);
16982
+ await sleep$1(400);
16706
16983
  }
16707
16984
  }
16708
16985
  const OMP_SELECTOR_COLS = 160;
@@ -16742,7 +17019,7 @@ async function waitOmpModel(p, id, since) {
16742
17019
  const info = await transcripts.model(p).catch(() => null);
16743
17020
  if (info && info.id === id && info.at && Date.parse(info.at) >= since - 1500) return true;
16744
17021
  if (Date.now() > until) return false;
16745
- await sleep(300);
17022
+ await sleep$1(300);
16746
17023
  }
16747
17024
  }
16748
17025
  async function ompSetModel(paneId, wanted) {
@@ -16766,7 +17043,7 @@ async function ompSetModel(paneId, wanted) {
16766
17043
  }
16767
17044
  for (const ch of typed) {
16768
17045
  await herdr("pane.send_input", { pane_id: p.id, keys: [ch === " " ? "space" : ch] });
16769
- await sleep(80);
17046
+ await sleep$1(80);
16770
17047
  }
16771
17048
  s = await waitOmpSelector(p.id, (x) => x.search.length > 0, 3e3) || s;
16772
17049
  at = rank(s);
@@ -16777,7 +17054,7 @@ async function ompSetModel(paneId, wanted) {
16777
17054
  const delta = at - s.cursor;
16778
17055
  if (delta === 0) break;
16779
17056
  await herdr("pane.send_input", { pane_id: p.id, keys: [delta > 0 ? "down" : "up"] });
16780
- await sleep(120);
17057
+ await sleep$1(120);
16781
17058
  if (n === 79) throw new HerdrError("stale", "The model selector did not follow \u2014 nothing was changed.");
16782
17059
  }
16783
17060
  s = (_b = (_a = await waitOmpSelector(p.id, (x) => x.cursor === at && x.options[at].label === id, 3e3)) != null ? _a : await ompSelector(p.id)) != null ? _b : s;
@@ -16836,7 +17113,7 @@ async function waitSlider(paneId, ok, timeoutMs) {
16836
17113
  const s = parseClaudeEffortScreen(await screen(paneId).catch(() => ""));
16837
17114
  if (s && ok(s)) return s;
16838
17115
  if (Date.now() > until) return null;
16839
- await sleep(250);
17116
+ await sleep$1(250);
16840
17117
  }
16841
17118
  }
16842
17119
  async function sliderGone(paneId, timeoutMs = 3e3) {
@@ -16844,7 +17121,7 @@ async function sliderGone(paneId, timeoutMs = 3e3) {
16844
17121
  let clear = 0;
16845
17122
  while (clear < 2 && Date.now() < until) {
16846
17123
  clear = parseClaudeEffortScreen(await screen(paneId).catch(() => "")) ? 0 : clear + 1;
16847
- if (clear < 2) await sleep(250);
17124
+ if (clear < 2) await sleep$1(250);
16848
17125
  }
16849
17126
  return clear >= 2;
16850
17127
  }
@@ -16853,7 +17130,7 @@ async function closeEffortSlider(paneId) {
16853
17130
  if (!parseClaudeEffortScreen(await screen(paneId).catch(() => ""))) return;
16854
17131
  await herdr("pane.send_input", { pane_id: paneId, keys: ["esc"] }).catch(() => {
16855
17132
  });
16856
- await sleep(400);
17133
+ await sleep$1(400);
16857
17134
  }
16858
17135
  }
16859
17136
  async function openEffortSlider(p) {
@@ -16887,7 +17164,7 @@ async function waitOmpEffort(p, model, prev, shown, sinceAt, hasTranscript) {
16887
17164
  if (now && prev === "auto" && shown !== "auto" && Date.now() - pressed > OMP_REDRAW_MS) return { level: now, at: sinceAt, shown: now };
16888
17165
  }
16889
17166
  if (Date.now() - pressed > 3e3) return null;
16890
- await sleep(200);
17167
+ await sleep$1(200);
16891
17168
  }
16892
17169
  }
16893
17170
  async function ompCycleTo(p, before, level) {
@@ -17000,7 +17277,7 @@ async function setEffort(paneId, level) {
17000
17277
  let clear = 0;
17001
17278
  while (clear < 2 && Date.now() < until) {
17002
17279
  clear = parseModelMenu(await screen(p.id)) ? 0 : clear + 1;
17003
- await sleep(250);
17280
+ await sleep$1(250);
17004
17281
  }
17005
17282
  if (clear < 2) throw new HerdrError("stale", "The /model menu changed \u2014 try again.");
17006
17283
  } catch (e) {
@@ -17033,15 +17310,26 @@ function compareVersions(a, b) {
17033
17310
  for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] - y[i];
17034
17311
  return 0;
17035
17312
  }
17313
+ const MODES = ["docker", "docker-build", "npm", "npm-global", "brew", "plugin"];
17036
17314
  function installMode(env) {
17037
- return env === "docker" || env === "docker-build" || env === "npm" ? env : "native";
17315
+ var _a;
17316
+ return (_a = MODES.find((m) => m === env)) != null ? _a : "native";
17038
17317
  }
17039
- function updateCommand(mode) {
17318
+ function updateCommand(mode, repo) {
17040
17319
  if (mode === "docker") return "docker compose pull && docker compose up -d";
17041
17320
  if (mode === "docker-build") return "git pull && docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build";
17042
17321
  if (mode === "npm") return "npx wherdr@latest";
17322
+ if (mode === "npm-global") return "npm install -g wherdr@latest && wherdr restart";
17323
+ if (mode === "brew") return "brew upgrade wherdr && brew services restart wherdr";
17324
+ if (mode === "plugin") return `herdr plugin install ${repo} --yes`;
17043
17325
  return "git pull && npm ci && npm run build";
17044
17326
  }
17327
+ function oneTapMode(mode) {
17328
+ return mode === "plugin" || mode === "npm-global" || mode === "brew";
17329
+ }
17330
+ function updateRunning(job, now) {
17331
+ return !!job && !["done", "rolled-back", "failed"].includes(job.state) && now - job.updatedAt < 10 * 6e4;
17332
+ }
17045
17333
 
17046
17334
  const CODEX_RELEASE_NOTES = "https://github.com/openai/codex/releases/latest";
17047
17335
  const CODEX_STANDALONE_COMMAND = `sh -c 'curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 sh'`;
@@ -17895,7 +18183,8 @@ async function choicesFor(p, rev, strict, watch = false) {
17895
18183
  askChecks.set(p.id, checks);
17896
18184
  choices = completeClaudeAsk(choices, await transcripts.pendingClaudeQuestions(p).catch(() => []), checks);
17897
18185
  } else if (!choices) askChecks.delete(p.id);
17898
- out = { choices, screen: menu ? null : parseWaitScreen(text, { choices: Boolean(choices) }), menu };
18186
+ const settings = p.agent === "claude" && settingsTabs(text) !== null;
18187
+ out = { choices, screen: menu || settings ? null : parseWaitScreen(text, { choices: Boolean(choices) }), menu };
17899
18188
  noteScreen(p.id, p.agent, text);
17900
18189
  if (choices && (choices.detail || isPermissionQuestion(choices.question))) {
17901
18190
  const named = p.agent === "omp" && choices.detail && choices.detail.tool !== "tool" ? choices.detail.tool : null;
@@ -18098,7 +18387,7 @@ function sendPrompt(p, text) {
18098
18387
  keys: async (keys) => {
18099
18388
  await herdr("pane.send_input", { pane_id: p.id, keys });
18100
18389
  },
18101
- sleep,
18390
+ sleep: sleep$1,
18102
18391
  sentSince: async (since) => {
18103
18392
  const r2 = await transcripts.chat(p, {});
18104
18393
  return (r2.items || []).filter((i) => i.role === "user" && i.ts && Date.parse(i.ts) >= since - 1e3).map((i) => i.text);
@@ -18167,12 +18456,13 @@ function takeBackFromClaude(p) {
18167
18456
  await herdr("pane.send_input", { pane_id: p.id, keys });
18168
18457
  },
18169
18458
  chat: async () => (await transcripts.chat(p, { fresh: true })).items || [],
18170
- sleep
18459
+ sleep: sleep$1,
18460
+ pending: () => (queued.get(p.id) || []).map((q) => q.text)
18171
18461
  }).catch(() => null);
18172
18462
  }
18173
18463
  async function cancelTurn(p, text) {
18174
18464
  if (!unansweredTurn((await transcripts.chat(p, { fresh: true })).items || [], text)) return null;
18175
- const r = await interruptAgent({ call: (m, params, t) => herdr(m, params, t), sleep, now: Date.now }, p);
18465
+ const r = await interruptAgent({ call: (m, params, t) => herdr(m, params, t), sleep: sleep$1, now: Date.now }, p);
18176
18466
  if (!r.stopped) return null;
18177
18467
  const item = await takeBackFromClaude(p);
18178
18468
  if (!item) return null;
@@ -18208,7 +18498,7 @@ function reconcileQueued(p) {
18208
18498
  }
18209
18499
  const unqueueBusy = /* @__PURE__ */ new Set();
18210
18500
  async function deliveryDone(paneId) {
18211
- for (let i = 0; i < 60 && deliverBusy.has(paneId); i++) await sleep(100);
18501
+ for (let i = 0; i < 60 && deliverBusy.has(paneId); i++) await sleep$1(100);
18212
18502
  }
18213
18503
  async function cancelQueued(paneId, text, id) {
18214
18504
  const p = findPane(paneId);
@@ -18254,7 +18544,7 @@ async function cancelQueued(paneId, text, id) {
18254
18544
  return { queue: r2.queue || [], items: r2.items || [] };
18255
18545
  },
18256
18546
  prompt: (t) => agentPrompt(p.id, t),
18257
- sleep,
18547
+ sleep: sleep$1,
18258
18548
  original: (e) => {
18259
18549
  var _a2, _b2;
18260
18550
  return !e.text && e.images ? ((_a2 = ownPhotos.shift()) == null ? void 0 : _a2.text) || fromHistory(e) : ((_b2 = own.find((q) => q !== mine && sameMsg(e.text, msgText(q.text)))) == null ? void 0 : _b2.text) || fromHistory(e) || e.text;
@@ -18383,7 +18673,7 @@ function paneCommand(id) {
18383
18673
  return (c == null ? void 0 : c.cmd) || "";
18384
18674
  }
18385
18675
  async function enrich(next, snap, machine) {
18386
- var _a2, _b2;
18676
+ var _a2, _b2, _c;
18387
18677
  const firstSnapshot = !seenMachines.has(machine);
18388
18678
  const prevStatus = new Map((((_a2 = mstates.get(machine)) == null ? void 0 : _a2.panes) || []).map((p) => [p.id, p.status || void 0]));
18389
18679
  const live = new Set(next.panes.map((p) => p.id));
@@ -18418,8 +18708,14 @@ async function enrich(next, snap, machine) {
18418
18708
  }
18419
18709
  if (queued.has(p.id)) {
18420
18710
  reconcileQueued(p);
18421
- const list = queued.get(p.id);
18422
18711
  const before = findPane(p.id);
18712
+ const run = p.agent === "omp" ? (_c = ompStatuses.get(p.id)) == null ? void 0 : _c.shell : null;
18713
+ if (run) {
18714
+ const left = queued.get(p.id).filter((q) => q.held || q.failed || !ompRunShown(p.agent, q.text, run));
18715
+ if (left.length) queued.set(p.id, left);
18716
+ else queued.delete(p.id);
18717
+ }
18718
+ const list = queued.get(p.id);
18423
18719
  const menu = Boolean(before && (before.menu || before.prompt || before.screen)) || restarting(p.id);
18424
18720
  if (list && TURN.has(p.status || "")) {
18425
18721
  for (const q of list) if (!q.held && !q.failed) q.turnSeen = true;
@@ -19130,1059 +19426,1568 @@ function stopNotices() {
19130
19426
  listeners.clear();
19131
19427
  }
19132
19428
 
19133
- const fsp = fs.promises;
19134
- const REFUSED = {
19135
- archive: "Archives cannot be read by the agent: extract the files first",
19136
- video: "Videos cannot be read by the agent",
19137
- audio: "Audio files cannot be read by the agent",
19138
- office: "Office documents cannot be read by the agent: export to PDF or text first",
19139
- executable: "Programs and binary files cannot be read by the agent",
19140
- binary: "Binary files cannot be read by the agent",
19141
- pdf: "This agent cannot read PDFs: only Claude Code can",
19142
- too_large: "File too large for the agent",
19143
- empty: "Empty file"
19429
+ // The phone address: wherdr published on the tailnet by `tailscale serve`
19430
+ // (private, only your devices). Reads Tailscale's state, publishes and
19431
+ // unpublishes wherdr's port, and checks that the address really answers.
19432
+ // Used by the server (Settings › Phone, server/utils/phone.ts) and by
19433
+ // `wherdr phone` / `wherdr panel`. No dependency on the rest of bin/lib nor
19434
+ // on packages: the server bundles this file.
19435
+
19436
+ const exec = promisify(execFile);
19437
+
19438
+ const LINKS = {
19439
+ download: 'https://tailscale.com/download',
19440
+ https: 'https://login.tailscale.com/admin/dns',
19441
+ operator: 'https://tailscale.com/kb/1242/tailscale-serve',
19144
19442
  };
19145
- async function saveAttachment(data, originalName, paneId) {
19146
- const pane = findPane(paneId);
19147
- const m = machineOfPane(paneId);
19148
- if (!pane || !m) throw new HerdrError("bad_pane", "Pane not found");
19149
- const check = checkAttachment(pane.agent, { name: originalName, type: "", size: data.length }, data.subarray(0, SNIFF_BYTES));
19150
- if (check.ok && check.kind === "image") throw new HerdrError("unsupported", "Images go through the photo upload");
19151
- if (!check.ok) throw new HerdrError(check.reason === "too_large" ? "too_large" : "unsupported", REFUSED[check.reason]);
19152
- const stored = storedAttachmentName(originalName, /* @__PURE__ */ new Date(), crypto.randomBytes(3).toString("hex"));
19153
- if (!isStoredAttachmentName(stored)) throw new HerdrError("bad_name", "Invalid file name");
19154
- let file;
19155
- if (m instanceof RemoteMachine) {
19156
- if (m.status !== "online") throw new HerdrError("unreachable", fmt("{machine} is unreachable", { machine: m.label }));
19157
- file = await m.putUpload(stored, data, "file");
19158
- } else {
19159
- file = path$1.join(ATTACH_DIR, stored);
19160
- if (path$1.dirname(file) !== path$1.resolve(ATTACH_DIR)) throw new HerdrError("bad_name", "Invalid file name");
19161
- await fsp.mkdir(ATTACH_DIR, { recursive: true, mode: 448 });
19162
- await fsp.writeFile(file, data, { mode: 384, flag: "wx" });
19443
+
19444
+ // The tailscale CLI: the macOS app's binary, Homebrew, system folders, then the PATH.
19445
+ function tailscaleBin(env = process.env, exists = existsSync) {
19446
+ const dirs = ['/Applications/Tailscale.app/Contents/MacOS/Tailscale', '/opt/homebrew/bin/tailscale', '/usr/local/bin/tailscale', '/usr/bin/tailscale'];
19447
+ for (const dir of (env.PATH || '').split(path$1.delimiter)) if (dir) dirs.push(path$1.join(dir, 'tailscale'));
19448
+ return dirs.find(p => exists(p)) || null
19449
+ }
19450
+
19451
+ // Tailscale's own socket on Linux. Its local API answers the same JSON as the
19452
+ // CLI: a Docker container has no `tailscale` command, but docker-compose.yml
19453
+ // mounts this folder, so wherdr reads the state there. Anyone may read; the
19454
+ // API also accepts changes from Tailscale's operator, but wherdr only ever
19455
+ // sends GET requests to it (localApi below).
19456
+ const TAILSCALED_SOCKET = '/var/run/tailscale/tailscaled.sock';
19457
+ function tailscaledSocket(env = process.env, exists = existsSync) {
19458
+ const file = env.WHERDR_TAILSCALED_SOCKET || TAILSCALED_SOCKET;
19459
+ return exists(file) ? file : null
19460
+ }
19461
+
19462
+ // GET on Tailscale's local API → the body, '' on any failure.
19463
+ function localApi(socketPath, route, timeoutMs = 4000) {
19464
+ const { promise, resolve } = Promise.withResolvers();
19465
+ const req = http.get({ socketPath, path: `/localapi/v0/${route}`, headers: { host: 'local-tailscaled.sock' }, timeout: timeoutMs }, (res) => {
19466
+ let text = '';
19467
+ res.setEncoding('utf8');
19468
+ res.on('data', (c) => { if (text.length < 1 << 20) text += c; });
19469
+ res.on('end', () => resolve(res.statusCode === 200 ? text : ''));
19470
+ res.on('error', () => resolve(''));
19471
+ });
19472
+ req.on('timeout', () => req.destroy());
19473
+ req.on('error', () => resolve(''));
19474
+ return promise
19475
+ }
19476
+
19477
+ // `tailscale status --json` → { name, connected, https }: this machine's
19478
+ // tailnet name (machine.tailnet.ts.net) or null, whether Tailscale is up,
19479
+ // and whether the tailnet issues HTTPS certificates.
19480
+ function tailnetStatus(statusJson) {
19481
+ let s = null;
19482
+ try { s = JSON.parse(statusJson); } catch {}
19483
+ const dns = s?.Self?.DNSName;
19484
+ return {
19485
+ name: typeof dns === 'string' && dns ? dns.replace(/\.$/, '') : null,
19486
+ connected: s?.BackendState === 'Running',
19487
+ https: Array.isArray(s?.CertDomains) && s.CertDomains.length > 0,
19488
+ }
19489
+ }
19490
+
19491
+ // The HTTPS entries of a serve configuration that proxy to wherdr's local
19492
+ // port on this machine's own name: [{ hostPort, httpsPort, funnel }], or null
19493
+ // when the configuration could not be read (Tailscale stopped, not JSON).
19494
+ // Background entries (`--bg`) and those of a `tailscale serve` still running
19495
+ // in a terminal (Foreground) both count. `funnel`: also open to the Internet.
19496
+ function wherdrEntries(name, port, serveJson) {
19497
+ let config = null;
19498
+ try { config = JSON.parse(serveJson); } catch {}
19499
+ if (!name || !config || typeof config !== 'object' || Array.isArray(config)) return null
19500
+ const target = new RegExp(`^(https?://)?(127\\.0\\.0\\.1|localhost):${port}/?$`);
19501
+ const out = [];
19502
+ for (const part of [config, ...Object.values(config.Foreground || {})]) {
19503
+ for (const [hostPort, entry] of Object.entries(part?.Web || {})) {
19504
+ const colon = hostPort.lastIndexOf(':');
19505
+ if (hostPort.slice(0, colon).toLowerCase() !== name.toLowerCase()) continue
19506
+ if (!Object.values(entry?.Handlers || {}).some(h => target.test(h?.Proxy || ''))) continue
19507
+ out.push({ hostPort, httpsPort: hostPort.slice(colon + 1), funnel: Boolean(part.AllowFunnel?.[hostPort]) });
19508
+ }
19163
19509
  }
19164
- log(`file received ${stored} (${Math.round(data.length / 1024)} KB, ${check.kind})${m instanceof RemoteMachine ? ` -> ${m.label}` : ""}`);
19165
- return { path: file, name: stored, kind: check.kind, size: data.length, ref: attachmentRef(pane.agent, check.kind, file) };
19510
+ return out
19166
19511
  }
19167
- async function cleanAttachments() {
19512
+
19513
+ const addressOf = (name, httpsPort) => `https://${name}${httpsPort === '443' ? '' : `:${httpsPort}`}/`;
19514
+
19515
+ // Phone address: the `tailscale serve` entry whose proxy targets wherdr's
19516
+ // local port (served on any HTTPS port), else the address publishing would
19517
+ // give (same port) with served: false. Null without a tailnet name.
19518
+ function phoneAddress(name, port, serveJson) {
19519
+ if (!name) return null
19520
+ const entries = wherdrEntries(name, port, serveJson) || [];
19521
+ const hit = entries.find(e => !e.funnel) || entries[0];
19522
+ if (hit) return { url: addressOf(name, hit.httpsPort), served: true, httpsPort: hit.httpsPort, ...(hit.funnel ? { funnel: true } : {}) }
19523
+ return { url: `https://${name}:${port}/`, served: false, httpsPort: String(port) }
19524
+ }
19525
+
19526
+ // The addresses wherdr adopts by itself (server/utils/phone.ts): what
19527
+ // `tailscale serve` publishes for its port on this machine's name, read from
19528
+ // Tailscale, which only the owner of the machine configures. null: unknown
19529
+ // (nothing is changed then); []: nothing is published. An address that
19530
+ // `tailscale funnel` also opens to the Internet is never adopted.
19531
+ function servedAddresses(name, port, serveJson) {
19532
+ const entries = wherdrEntries(name, port, serveJson);
19533
+ return entries && entries.filter(e => !e.funnel).map(e => addressOf(name, e.httpsPort))
19534
+ }
19535
+
19536
+ // HTTPS ports already published by `tailscale serve` (for anything).
19537
+ function servedPorts(serveJson) {
19168
19538
  try {
19169
- for (const n of await fsp.readdir(ATTACH_DIR)) {
19170
- const f = path$1.join(ATTACH_DIR, n);
19171
- const st = await fsp.lstat(f);
19172
- if (st.isFile() && Date.now() - st.mtimeMs > UPLOAD_TTL_MS) await fsp.unlink(f);
19173
- }
19539
+ const web = JSON.parse(serveJson || '{}')?.Web || {};
19540
+ return Object.keys(web).map(k => k.slice(k.lastIndexOf(':') + 1))
19541
+ } catch { return [] }
19542
+ }
19543
+
19544
+ // The command that publishes wherdr's port, as typed by hand (Docker mode).
19545
+ // `httpsPort`: the tailnet port, wherdr's own by default.
19546
+ const publishCommand = (port, httpsPort = port) => `tailscale serve --bg --https=${httpsPort} http://127.0.0.1:${port}`;
19547
+ const publishArgs = port => ['serve', '--bg', `--https=${port}`, `http://127.0.0.1:${port}`];
19548
+ const unpublishArgs = httpsPort => ['serve', `--https=${httpsPort}`, 'off'];
19549
+
19550
+ // A tailnet address typed by hand: https://machine.tailnet.ts.net[:port]/ → its
19551
+ // normalized form, or null.
19552
+ function tailnetUrl(text) {
19553
+ try {
19554
+ const u = new URL(String(text || '').trim());
19555
+ if (u.protocol !== 'https:' || !/^[a-z0-9-]+(\.[a-z0-9-]+)*\.ts\.net$/i.test(u.hostname) || u.username || u.password) return null
19556
+ return `https://${u.host.toLowerCase()}/`
19557
+ } catch { return null }
19558
+ }
19559
+
19560
+ // A failed `tailscale serve` → { code, link } for the known causes, else null.
19561
+ // code: 'operator' (Linux: the user may not change Tailscale's settings),
19562
+ // 'https' (no HTTPS certificates on the tailnet), 'offline' (not connected).
19563
+ function serveError(output) {
19564
+ const text = String(output || '');
19565
+ const link = /https:\/\/login\.tailscale\.com\/\S+/.exec(text)?.[0];
19566
+ if (/access denied|permission denied|--operator|not permitted/i.test(text)) return { code: 'operator', link: LINKS.operator }
19567
+ if (/https?\b.*(not enabled|disabled)|serve.*not enabled|enable https|certificates?.*(not enabled|disabled)|\/f\/serve/i.test(text)) return { code: 'https', link: link || LINKS.https }
19568
+ if (/needslogin|not logged in|logged out|tailscale is stopped|is tailscale running|not running|failed to connect to local tailscale/i.test(text)) return { code: 'offline', link: LINKS.download }
19569
+ return null
19570
+ }
19571
+
19572
+ // Tailscale's state for wherdr's port: { installed, bin, socket, connected,
19573
+ // https, name, phone, taken, served }. Read with the CLI, or without one
19574
+ // (Docker) from Tailscale's socket.
19575
+ async function inspect(port, bin = tailscaleBin(), socket = bin ? null : tailscaledSocket()) {
19576
+ if (!bin && !socket) return { installed: false }
19577
+ const run = args => exec(bin, args, { encoding: 'utf8', timeout: 8000 }).then(r => r.stdout, () => '');
19578
+ const status = tailnetStatus(bin ? await run(['status', '--json']) : await localApi(socket, 'status?peers=false'));
19579
+ const serve = !status.name ? '' : bin ? await run(['serve', 'status', '--json']) : await localApi(socket, 'serve-config');
19580
+ const phone = phoneAddress(status.name, port, serve);
19581
+ // Publishing on wherdr's port would replace whatever else is served there.
19582
+ const taken = Boolean(phone && !phone.served && servedPorts(serve).includes(String(port)));
19583
+ return { installed: true, bin, socket, ...status, phone, taken, served: servedAddresses(status.name, port, serve) }
19584
+ }
19585
+
19586
+ // The tailnet's DNS suffix (tailnet.ts.net) from /etc/resolv.conf's search
19587
+ // line: a Docker container copies it from the host when MagicDNS is on.
19588
+ function tailnetDomain(resolvConf) {
19589
+ for (const line of String(resolvConf || '').split('\n')) {
19590
+ const m = /^\s*search\s+(.+)$/.exec(line);
19591
+ const hit = m?.[1].split(/\s+/).find(d => /^[a-z0-9-]+\.ts\.net$/i.test(d));
19592
+ if (hit) return hit.toLowerCase()
19593
+ }
19594
+ return null
19595
+ }
19596
+
19597
+ // Tailscale's DNS (MagicDNS): a Docker container does not use it, but reaches it.
19598
+ const MAGIC_DNS = '100.100.100.100';
19599
+
19600
+ // The system's lookup, then MagicDNS for *.ts.net names.
19601
+ function lookup(host, opts, cb) {
19602
+ dns.lookup(host, opts, (err, address, family) => {
19603
+ if (!err || !/\.ts\.net$/i.test(host)) return cb(err, address, family)
19604
+ const resolver = new dns.Resolver({ timeout: 3000, tries: 1 });
19605
+ resolver.setServers([MAGIC_DNS]);
19606
+ resolver.resolve4(host, (e2, list) => {
19607
+ if (e2 || !list?.length) return cb(err)
19608
+ if (opts?.all) cb(null, list.map(a => ({ address: a, family: 4 })));
19609
+ else cb(null, list[0], 4);
19610
+ });
19611
+ });
19612
+ }
19613
+
19614
+ // Why a request failed: 'dns' (name not found), 'refused', 'timeout', 'cert'
19615
+ // (invalid certificate), 'tls' (no HTTPS on that port), or 'network'.
19616
+ function failureCause(err) {
19617
+ const code = String(err?.code || '');
19618
+ const msg = String(err?.message || '');
19619
+ if (code === 'ENOTFOUND' || code === 'EAI_AGAIN' || code === 'EAI_NONAME') return 'dns'
19620
+ if (code === 'ECONNREFUSED') return 'refused'
19621
+ if (code === 'ETIMEDOUT' || msg === 'timeout') return 'timeout'
19622
+ if (/CERT|SELF_SIGNED|UNABLE_TO_VERIFY|ALTNAME/.test(code)) return 'cert'
19623
+ if (code === 'EPROTO' || /wrong version number|ssl3_get_record|packet length/i.test(msg)) return 'tls'
19624
+ return 'network'
19625
+ }
19626
+
19627
+ // GET of `route` on the address → { status, text }, or { cause } when it failed.
19628
+ function fetchText(url, route, timeoutMs) {
19629
+ const { promise, resolve } = Promise.withResolvers();
19630
+ let u;
19631
+ try { u = new URL(route, url); } catch { return Promise.resolve({ cause: 'network' }) }
19632
+ // Accept JSON: a refused host then answers { code: 'host' }, not the HTML page.
19633
+ const req = https.get(u, { lookup, timeout: timeoutMs, headers: { accept: 'application/json' } }, (res) => {
19634
+ let text = '';
19635
+ res.setEncoding('utf8');
19636
+ res.on('data', (c) => { if (text.length < 65536) text += c; });
19637
+ res.on('end', () => resolve({ status: res.statusCode || 0, text }));
19638
+ res.on('error', e => resolve({ cause: failureCause(e) }));
19639
+ });
19640
+ req.on('timeout', () => req.destroy(new Error('timeout')));
19641
+ req.on('error', e => resolve({ cause: failureCause(e) }));
19642
+ return promise
19643
+ }
19644
+
19645
+ // What an answer of /api/health means: 'ok' (wherdr, and it accepts this
19646
+ // address), 'host' (wherdr's host check refused it), or null: not a wherdr
19647
+ // that has this route (versions up to 1.3.1, or another program).
19648
+ function healthReach(status, text) {
19649
+ if (status >= 200 && status < 300 && /"name"\s*:\s*"wherdr"/.test(text)) return 'ok'
19650
+ if (status === 403 && /"code"\s*:\s*"host"/.test(text)) return 'host'
19651
+ return null
19652
+ }
19653
+
19654
+ // reachable() with the details: { reach, cause?, status? }: `cause` when
19655
+ // unreachable (failureCause), `status` the HTTP code when something answered.
19656
+ // /api/health is asked first: it goes through wherdr's host check, which the
19657
+ // static manifest does not (it answers for any host name). The manifest only
19658
+ // recognizes an older wherdr without that route.
19659
+ async function probe(url, timeoutMs = 8000) {
19660
+ const health = await fetchText(url, 'api/health', timeoutMs);
19661
+ if (health.cause) return { reach: 'unreachable', cause: health.cause }
19662
+ const reach = healthReach(health.status, health.text);
19663
+ if (reach) return { reach, status: health.status }
19664
+ const manifest = await fetchText(url, 'manifest.webmanifest', timeoutMs);
19665
+ if (manifest.cause) return { reach: 'unreachable', cause: manifest.cause }
19666
+ const ok = manifest.status >= 200 && manifest.status < 300 && manifest.text.includes('wherdr');
19667
+ return { reach: ok ? 'ok' : 'other', status: ok ? manifest.status : health.status }
19668
+ }
19669
+
19670
+ // Runs `tailscale <args>` without a terminal → { code, output }. On a tailnet
19671
+ // without HTTPS, `tailscale serve` prints a link and waits: the timeout ends it.
19672
+ function runTailscale(bin, args, timeoutMs = 30_000) {
19673
+ return exec(bin, args, { encoding: 'utf8', timeout: timeoutMs }).then(
19674
+ r => ({ code: 0, output: `${r.stdout}${r.stderr}` }),
19675
+ e => ({ code: typeof e.code === 'number' ? e.code : -1, output: `${e.stdout || ''}${e.stderr || ''}${e.message || ''}` }),
19676
+ )
19677
+ }
19678
+
19679
+ const PHONE_GRACE_MS = 9e4;
19680
+ const TRANSIENT = { timeout: true, tls: true, refused: true, cert: true, network: true };
19681
+ function phoneReach(probe, since, now = Date.now()) {
19682
+ if (probe.reach !== "unreachable") return probe.reach;
19683
+ return probe.cause && TRANSIENT[probe.cause] && now - since < PHONE_GRACE_MS ? "pending" : "unreachable";
19684
+ }
19685
+
19686
+ function qrSvg(text) {
19687
+ const qr = new QRCode(-1, QRErrorCorrectLevel.M);
19688
+ qr.addData(text);
19689
+ qr.make();
19690
+ const n = qr.getModuleCount();
19691
+ let d = "";
19692
+ for (let r = 0; r < n; r++) {
19693
+ for (let col = 0; col < n; col++) if (qr.isDark(r, col)) d += `M${col + 2} ${r + 2}h1v1h-1z`;
19694
+ }
19695
+ return { size: n + 4, path: d };
19696
+ }
19697
+
19698
+ function hostname(value) {
19699
+ try {
19700
+ if (!value || /[\s/@?#]/.test(value)) return null;
19701
+ const url = new URL(`http://${value}`);
19702
+ if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) return null;
19703
+ return url.hostname.replace(/^\[|\]$/g, "").toLowerCase().replace(/\.$/, "");
19174
19704
  } catch {
19705
+ return null;
19175
19706
  }
19176
19707
  }
19177
-
19178
- const _mZt23IdRC2vLuqDtWT6mb7m9BgHXq3U5QrnQ7Z5Hc4c = defineNitroPlugin((nitroApp) => {
19179
- initVapid();
19180
- startMachines();
19181
- startPolling();
19182
- startThreadLimits();
19183
- startNotices();
19184
- cleanUploads();
19185
- cleanAttachments();
19186
- const purge = setInterval(() => {
19187
- cleanUploads();
19188
- cleanAttachments();
19189
- }, 6 * 3600 * 1e3);
19190
- log(`herdr-web \u2014 socket ${HERDR_SOCK}${HERDR_SESSION ? ` (session ${HERDR_SESSION})` : ""}`);
19191
- nitroApp.hooks.hook("close", () => {
19192
- stopPolling();
19193
- stopNotices();
19194
- stopMachines();
19195
- clearInterval(purge);
19708
+ let servedHosts = [];
19709
+ function setServedHosts(urls) {
19710
+ servedHosts = urls.flatMap((url) => {
19711
+ try {
19712
+ return [new URL(url).hostname.toLowerCase()];
19713
+ } catch {
19714
+ return [];
19715
+ }
19196
19716
  });
19197
- });
19717
+ }
19718
+ function allowedHosts(appUrl2 = process.env.APP_URL || "", extra = process.env.HERDR_WEB_ALLOWED_HOSTS || "", interfaces = os.networkInterfaces(), served = servedHosts) {
19719
+ const names = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", os.hostname().toLowerCase(), ...served]);
19720
+ try {
19721
+ if (appUrl2) names.add(new URL(appUrl2).hostname.toLowerCase().replace(/^\[|\]$/g, ""));
19722
+ } catch {
19723
+ }
19724
+ for (const addresses of Object.values(interfaces)) {
19725
+ for (const address of addresses || []) names.add(address.address.toLowerCase().replace(/%.+$/, ""));
19726
+ }
19727
+ for (const item of extra.split(",")) {
19728
+ const name = hostname(item.trim());
19729
+ if (name) names.add(name);
19730
+ }
19731
+ return names;
19732
+ }
19733
+ function hostAllowed(host, names = allowedHosts()) {
19734
+ const name = hostname(host || "");
19735
+ return Boolean(name && names.has(name));
19736
+ }
19737
+ function hostRefusalIsHtml(path, accept) {
19738
+ return !path.startsWith("/api/") && /\btext\/html\b/i.test(Array.isArray(accept) ? accept.join(",") : accept || "");
19739
+ }
19740
+ function phoneFixCommand(install = process.env.WHERDR_INSTALL || "") {
19741
+ if (install === "brew" || install === "npm-global") return "wherdr phone";
19742
+ if (install === "docker" || install === "docker-build" || install === "plugin") return "curl -fsSL https://wherdr.dev/install | sh";
19743
+ return "npx wherdr phone";
19744
+ }
19745
+ function hostRefusedPage(install) {
19746
+ const command = phoneFixCommand(install);
19747
+ return `<!doctype html>
19748
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
19749
+ <meta name="robots" content="noindex"><title>wherdr \xB7 address not enabled</title>
19750
+ <style>
19751
+ :root{color-scheme:dark}*{box-sizing:border-box}
19752
+ body{margin:0;min-height:100vh;display:grid;place-items:center;background:#0d0f12;color:#e8eaed;font:15px/1.5 Inter,system-ui,-apple-system,sans-serif;padding:24px max(16px,env(safe-area-inset-right)) 24px max(16px,env(safe-area-inset-left))}
19753
+ main{max-width:480px;border:1px solid #3a3f47;padding:24px}
19754
+ .tag{font:11px/1 "JetBrains Mono",ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;color:#9aa0a8;margin:0 0 16px}
19755
+ h1{font:600 20px/1.25 Archivo,system-ui,sans-serif;margin:0 0 8px}
19756
+ p{margin:0 0 12px}hr{border:0;border-top:1px solid #3a3f47;margin:20px 0}
19757
+ code{font:13px/1.45 "JetBrains Mono",ui-monospace,monospace}
19758
+ pre{margin:0;padding:10px 12px;border:1px solid #3a3f47;background:#15181d;white-space:pre-wrap;overflow-wrap:anywhere;user-select:all;-webkit-user-select:all}
19759
+ </style></head><body><main>
19760
+ <p class="tag">wherdr \xB7 403</p>
19761
+ <h1>This address isn't enabled yet.</h1>
19762
+ <p>wherdr enables by itself the address <code>tailscale serve</code> publishes for it. On the machine that runs wherdr, run this, then reload:</p>
19763
+ <pre><code>${command}</code></pre>
19764
+ <hr>
19765
+ <h1 lang="fr">Cette adresse n\u2019est pas encore activ\xE9e.</h1>
19766
+ <p lang="fr">wherdr active tout seul l\u2019adresse que <code>tailscale serve</code> publie pour lui. Sur la machine qui fait tourner wherdr, lance ceci, puis recharge :</p>
19767
+ <pre><code>${command}</code></pre>
19768
+ </main></body></html>
19769
+ `;
19770
+ }
19771
+ function crossSiteRequest(headers) {
19772
+ const site = headers["sec-fetch-site"];
19773
+ return typeof site === "string" && site !== "same-origin" && site !== "none";
19774
+ }
19198
19775
 
19199
- const plugins = [
19200
- _9as7ch5sSe9R7BrbrDE2GGZpuA6oP9uMAcL7iRtRgrw,
19201
- _mZt23IdRC2vLuqDtWT6mb7m9BgHXq3U5QrnQ7Z5Hc4c
19202
- ];
19776
+ const MAX_SESSION_DAYS = [1, 7, 30, 90, 365];
19777
+ const DEFAULT_MAX_SESSION_DAYS = 7;
19778
+ const DAY_MS = 24 * 3600 * 1e3;
19779
+ function isMaxSessionDays(v) {
19780
+ return MAX_SESSION_DAYS.includes(v);
19781
+ }
19203
19782
 
19204
- const assets = {
19205
- "/manifest-en.webmanifest": {
19206
- "type": "application/manifest+json",
19207
- "etag": "\"260-q1JnCK0/ecdKwgeTve6N4I6/mWI\"",
19208
- "mtime": "2026-10-08T06:32:45.042Z",
19209
- "size": 608,
19210
- "path": "../public/manifest-en.webmanifest"
19211
- },
19212
- "/manifest.webmanifest": {
19213
- "type": "application/manifest+json",
19214
- "etag": "\"26e-ykoYuj4V8la457yChW3oQTur8RU\"",
19215
- "mtime": "2026-10-08T06:32:45.042Z",
19216
- "size": 622,
19217
- "path": "../public/manifest.webmanifest"
19218
- },
19219
- "/sw.js": {
19783
+ var __defProp = Object.defineProperty;
19784
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
19785
+ var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
19786
+ const SESSION_MS = 12 * 3600 * 1e3;
19787
+ const RENEW_MS = 10 * 60 * 1e3;
19788
+ const CHALLENGE_MS = 5 * 60 * 1e3;
19789
+ const AUTH_COOKIE = "hw_session";
19790
+ const CHALLENGE_COOKIE = "hw_challenge";
19791
+ class AuthError extends Error {
19792
+ constructor(code, message) {
19793
+ super(message);
19794
+ __publicField(this, "code");
19795
+ this.code = code;
19796
+ }
19797
+ }
19798
+ function createAuth({ dataDir, log: log2 = () => {
19799
+ }, passkeyUser = "herdr" }) {
19800
+ const FILE = path$1.join(dataDir, "auth.json");
19801
+ let db = { secret: null, generation: 0, credentials: [] };
19802
+ try {
19803
+ db = { ...db, ...JSON.parse(fs.readFileSync(FILE, "utf8")) };
19804
+ } catch {
19805
+ }
19806
+ const save = () => {
19807
+ fs.mkdirSync(dataDir, { recursive: true });
19808
+ fs.writeFileSync(FILE, JSON.stringify(db, null, 2) + "\n", { mode: 384 });
19809
+ };
19810
+ if (!db.secret) {
19811
+ db.secret = crypto.randomBytes(32).toString("hex");
19812
+ save();
19813
+ }
19814
+ const bootstrapToken = crypto.randomBytes(24).toString("base64url");
19815
+ if (!db.credentials.length) log2(`First passkey: bootstrap token ${bootstrapToken}`);
19816
+ const enabled = () => db.credentials.length > 0;
19817
+ const sign = (v) => crypto.createHmac("sha256", db.secret).update(v).digest("base64url");
19818
+ const safeEq = (a, b) => {
19819
+ const x = Buffer.from(String(a));
19820
+ const y = Buffer.from(String(b));
19821
+ return x.length === y.length && crypto.timingSafeEqual(x, y);
19822
+ };
19823
+ const readCookie = (req) => {
19824
+ const c = String(req.headers.cookie || "").split(/;\s*/).find((s) => s.startsWith(`${AUTH_COOKIE}=`));
19825
+ if (!c) return null;
19826
+ try {
19827
+ return decodeURIComponent(c.slice(AUTH_COOKIE.length + 1));
19828
+ } catch {
19829
+ return null;
19830
+ }
19831
+ };
19832
+ const challengeClient = (req) => {
19833
+ const c = String(req.headers.cookie || "").split(/;\s*/).find((s) => s.startsWith(`${CHALLENGE_COOKIE}=`));
19834
+ return (c == null ? void 0 : c.slice(CHALLENGE_COOKIE.length + 1)) || null;
19835
+ };
19836
+ const checkBootstrap = (body) => {
19837
+ if (!enabled() && !safeEq(String((body == null ? void 0 : body.bootstrapToken) || ""), bootstrapToken)) {
19838
+ throw new AuthError("bootstrap", "Invalid bootstrap token (see the server logs)");
19839
+ }
19840
+ };
19841
+ const maxSessionMs = () => {
19842
+ var _a;
19843
+ return ((_a = db.maxSessionDays) != null ? _a : DEFAULT_MAX_SESSION_DAYS) * DAY_MS;
19844
+ };
19845
+ function session(req, now = Date.now()) {
19846
+ const v = readCookie(req);
19847
+ if (!v) return null;
19848
+ const parts = v.split(".");
19849
+ if (parts.length !== 3 && parts.length !== 4) return null;
19850
+ const mac = parts.pop();
19851
+ if (!mac || parts[1] !== String(db.generation) || !safeEq(mac, sign(parts.join(".")))) return null;
19852
+ const exp = Number(parts[0]);
19853
+ const since = parts.length === 3 ? Number(parts[2]) : exp - SESSION_MS;
19854
+ if (!(exp >= now) || !Number.isFinite(since)) return null;
19855
+ if (now > since + maxSessionMs() + SESSION_MS) return null;
19856
+ return { exp, since };
19857
+ }
19858
+ function isUnlocked(req) {
19859
+ return !enabled() || session(req) !== null;
19860
+ }
19861
+ const secure = (req) => req.headers["x-forwarded-proto"] === "https" || String(req.headers.origin || "").startsWith("https://") ? "; Secure" : "";
19862
+ function sessionCookie(req, now = Date.now(), since = now) {
19863
+ const exp = now + SESSION_MS;
19864
+ const payload = `${exp}.${db.generation}.${since}`;
19865
+ return `${AUTH_COOKIE}=${payload}.${sign(payload)}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${SESSION_MS / 1e3}${secure(req)}`;
19866
+ }
19867
+ const clearCookie = (req) => `${AUTH_COOKIE}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0${secure(req)}`;
19868
+ function renew(req, now = Date.now()) {
19869
+ if (!enabled()) return null;
19870
+ const s = session(req, now);
19871
+ if (s === null || s.exp - now > SESSION_MS - RENEW_MS) return null;
19872
+ return { cookie: sessionCookie(req, now, s.since), expiresAt: now + SESSION_MS };
19873
+ }
19874
+ function relying(req) {
19875
+ const origin = String(req.headers.origin || "");
19876
+ const { hostname } = new URL(origin);
19877
+ return { origin, rpID: hostname };
19878
+ }
19879
+ const challenges = /* @__PURE__ */ new Map();
19880
+ const challengeKey = (kind, client) => `${kind}:${client}`;
19881
+ const issueChallenge = (req, kind, value) => {
19882
+ const client = crypto.randomBytes(24).toString("base64url");
19883
+ for (const [key, challenge] of challenges) if (challenge.exp < Date.now()) challenges.delete(key);
19884
+ challenges.set(challengeKey(kind, client), { value, exp: Date.now() + CHALLENGE_MS });
19885
+ return `${CHALLENGE_COOKIE}=${client}; Path=/api/auth/; HttpOnly; SameSite=Strict; Max-Age=${CHALLENGE_MS / 1e3}${secure(req)}`;
19886
+ };
19887
+ const takeChallenge = (req, kind) => {
19888
+ const client = challengeClient(req);
19889
+ const key = challengeKey(kind, client || "");
19890
+ const c = challenges.get(key);
19891
+ challenges.delete(key);
19892
+ if (!c || c.exp < Date.now()) throw new AuthError("challenge", "Challenge expired, try again");
19893
+ return c.value;
19894
+ };
19895
+ async function registerOptions(req, body = {}) {
19896
+ if (enabled() && !isUnlocked(req)) throw new AuthError("locked", "Unlock first");
19897
+ checkBootstrap(body);
19898
+ const { rpID } = relying(req);
19899
+ const opts = await generateRegistrationOptions({
19900
+ rpName: "wherdr",
19901
+ rpID,
19902
+ userName: passkeyUser,
19903
+ userDisplayName: passkeyUser,
19904
+ attestationType: "none",
19905
+ excludeCredentials: db.credentials.map((c) => ({ id: c.id, transports: c.transports })),
19906
+ authenticatorSelection: { residentKey: "preferred", userVerification: "required" }
19907
+ });
19908
+ return { ...opts, __cookie: issueChallenge(req, "reg", opts.challenge) };
19909
+ }
19910
+ async function registerVerify(req, body) {
19911
+ if (enabled() && !isUnlocked(req)) throw new AuthError("locked", "Unlock first");
19912
+ checkBootstrap(body);
19913
+ const { origin, rpID } = relying(req);
19914
+ const v = await verifyRegistrationResponse({
19915
+ response: body.response,
19916
+ expectedChallenge: takeChallenge(req, "reg"),
19917
+ expectedOrigin: origin,
19918
+ expectedRPID: rpID,
19919
+ requireUserVerification: true
19920
+ });
19921
+ if (!v.verified || !v.registrationInfo) throw new AuthError("rejected", "Key rejected");
19922
+ const { credential } = v.registrationInfo;
19923
+ db.credentials.push({
19924
+ id: credential.id,
19925
+ publicKey: Buffer.from(credential.publicKey).toString("base64url"),
19926
+ counter: credential.counter,
19927
+ transports: credential.transports || [],
19928
+ name: String(body.name || "Device").slice(0, 40),
19929
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
19930
+ });
19931
+ save();
19932
+ log2(`lock: key registered (${db.credentials.length})`);
19933
+ return { ok: true, __cookie: sessionCookie(req) };
19934
+ }
19935
+ async function loginOptions(req) {
19936
+ const { rpID } = relying(req);
19937
+ const opts = await generateAuthenticationOptions({
19938
+ rpID,
19939
+ userVerification: "required",
19940
+ allowCredentials: db.credentials.map((c) => ({ id: c.id, transports: c.transports }))
19941
+ });
19942
+ return { ...opts, __cookie: issueChallenge(req, "auth", opts.challenge) };
19943
+ }
19944
+ async function loginVerify(req, body) {
19945
+ const { origin, rpID } = relying(req);
19946
+ const cred = db.credentials.find((c) => c.id === (body.response && body.response.id));
19947
+ if (!cred) throw new AuthError("unknown_key", "Key unknown on this server");
19948
+ const v = await verifyAuthenticationResponse({
19949
+ response: body.response,
19950
+ expectedChallenge: takeChallenge(req, "auth"),
19951
+ expectedOrigin: origin,
19952
+ expectedRPID: rpID,
19953
+ requireUserVerification: true,
19954
+ credential: { id: cred.id, publicKey: Buffer.from(cred.publicKey, "base64url"), counter: cred.counter, transports: cred.transports }
19955
+ });
19956
+ if (!v.verified) throw new AuthError("rejected", "Unlock rejected");
19957
+ cred.counter = v.authenticationInfo.newCounter;
19958
+ cred.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
19959
+ save();
19960
+ return { ok: true, __cookie: sessionCookie(req) };
19961
+ }
19962
+ function disable(req) {
19963
+ if (!isUnlocked(req)) throw new AuthError("locked", "Unlock first");
19964
+ db.credentials = [];
19965
+ db.generation += 1;
19966
+ save();
19967
+ log2("lock disabled");
19968
+ return { ok: true, __cookie: clearCookie(req) };
19969
+ }
19970
+ function lockAll(req) {
19971
+ if (!enabled() || !isUnlocked(req)) throw new AuthError("locked", "Unlock first");
19972
+ db.generation += 1;
19973
+ save();
19974
+ log2("lock: all devices locked");
19975
+ return { ok: true, __cookie: clearCookie(req) };
19976
+ }
19977
+ function setMaxSession(req, days) {
19978
+ if (!enabled() || !isUnlocked(req)) throw new AuthError("locked", "Unlock first");
19979
+ if (!isMaxSessionDays(days)) throw new AuthError("invalid", "Invalid duration");
19980
+ db.maxSessionDays = days;
19981
+ save();
19982
+ return { ok: true, maxSessionDays: days };
19983
+ }
19984
+ function status(req, { resume = false, now = Date.now() } = {}) {
19985
+ var _a, _b;
19986
+ const devices = db.credentials.map((c) => ({ name: c.name, createdAt: c.createdAt, lastUsed: c.lastUsed || null }));
19987
+ if (!enabled()) return { enabled: false, unlocked: true, devices };
19988
+ const s = session(req, now);
19989
+ const deadline = s ? s.since + maxSessionMs() : 0;
19990
+ if (!s || resume && now >= deadline) {
19991
+ return { enabled: true, unlocked: false, expiresAt: null, now, devices, ...s ? { __cookie: clearCookie(req) } : {} };
19992
+ }
19993
+ const renewed = renew(req, now);
19994
+ return {
19995
+ enabled: true,
19996
+ unlocked: true,
19997
+ expiresAt: (_a = renewed == null ? void 0 : renewed.expiresAt) != null ? _a : s.exp,
19998
+ deadline,
19999
+ now,
20000
+ maxSessionDays: (_b = db.maxSessionDays) != null ? _b : DEFAULT_MAX_SESSION_DAYS,
20001
+ devices,
20002
+ ...renewed ? { __cookie: renewed.cookie } : {}
20003
+ };
20004
+ }
20005
+ return {
20006
+ isUnlocked,
20007
+ status,
20008
+ renew,
20009
+ registerOptions,
20010
+ registerVerify,
20011
+ loginOptions,
20012
+ loginVerify,
20013
+ disable,
20014
+ lockAll,
20015
+ setMaxSession,
20016
+ lock: (req) => ({ ok: true, __cookie: clearCookie(req) }),
20017
+ // For tests.
20018
+ _sessionCookie: sessionCookie,
20019
+ _db: () => db,
20020
+ _bootstrapToken: () => bootstrapToken,
20021
+ _takeChallenge: takeChallenge
20022
+ };
20023
+ }
20024
+
20025
+ const auth = createAuth({ dataDir: DATA_DIR, log, passkeyUser: PASSKEY_USER });
20026
+ const reqOf = (event) => ({ headers: getRequestHeaders(event) });
20027
+ function sameOrigin(headers) {
20028
+ const o = headers.origin;
20029
+ if (!o) return false;
20030
+ try {
20031
+ return new URL(o).host === headers.host;
20032
+ } catch {
20033
+ return false;
20034
+ }
20035
+ }
20036
+ const BODY_MAX = 256 * 1024;
20037
+ function sendError(event, status, body) {
20038
+ setResponseStatus(event, status);
20039
+ setResponseHeader(event, "cache-control", "no-store");
20040
+ return body;
20041
+ }
20042
+ function defineApi(fn, opts = {}) {
20043
+ return defineEventHandler(async (event) => {
20044
+ const url = getRequestURL(event);
20045
+ try {
20046
+ let body = {};
20047
+ if (event.method === "POST") {
20048
+ const headers = getRequestHeaders(event);
20049
+ const ctype = String(headers["content-type"] || "");
20050
+ const typeOk = opts.raw ? opts.raw.test(ctype) : ctype.startsWith("application/json");
20051
+ if (!sameOrigin(headers) || !typeOk) return sendError(event, 403, { error: "Origin refused" });
20052
+ const max = opts.raw ? opts.max || 20 * 1024 * 1024 : BODY_MAX;
20053
+ const tooLarge = opts.raw ? opts.tooLarge || "Image too large (20 MB max)" : "Request too large";
20054
+ if (Number(headers["content-length"] || 0) > max) throw new HerdrError("too_large", tooLarge);
20055
+ const raw = await readRawBody(event, false);
20056
+ if (raw && raw.length > max) throw new HerdrError("too_large", tooLarge);
20057
+ if (opts.raw) body = { data: raw || Buffer.alloc(0), ctype };
20058
+ else {
20059
+ try {
20060
+ body = raw && raw.length ? JSON.parse(raw.toString("utf8")) : {};
20061
+ } catch {
20062
+ throw new HerdrError("bad_json", "Invalid JSON");
20063
+ }
20064
+ }
20065
+ }
20066
+ const out = await fn(event, body);
20067
+ setResponseHeader(event, "cache-control", "no-store");
20068
+ if (out && typeof out === "object" && "__cookie" in out) {
20069
+ const o = out;
20070
+ setResponseHeader(event, "set-cookie", String(o.__cookie));
20071
+ delete o.__cookie;
20072
+ }
20073
+ return out;
20074
+ } catch (e) {
20075
+ const err = e;
20076
+ const known = e instanceof HerdrError || url.pathname.startsWith("/api/auth/") && Boolean(err.code);
20077
+ const code = known ? err.code : "internal";
20078
+ if (!known) log("error", event.method, url.pathname, e);
20079
+ const status = err.code === "unreachable" ? 503 : err.code === "locked" ? 401 : 400;
20080
+ return sendError(event, status, { error: err.message, code });
20081
+ }
20082
+ });
20083
+ }
20084
+ function sendImage(event, img) {
20085
+ setResponseHeader(event, "content-type", img.type);
20086
+ setResponseHeader(event, "cache-control", "private, max-age=86400");
20087
+ setResponseHeader(event, "x-content-type-options", "nosniff");
20088
+ setResponseHeader(event, "content-security-policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox");
20089
+ return img.body;
20090
+ }
20091
+
20092
+ const PORT = String(process.env.NITRO_PORT || process.env.PORT || 3e3);
20093
+ const LOOPBACK = { "localhost": true, "127.0.0.1": true, "::1": true };
20094
+ function phoneAccess(event) {
20095
+ const req = reqOf(event);
20096
+ const lock = auth.status(req);
20097
+ if (lock.enabled && lock.unlocked) return true;
20098
+ const headers = getRequestHeaders(event);
20099
+ let host = "";
20100
+ try {
20101
+ host = new URL(`http://${headers.host || ""}`).hostname.replace(/^\[|\]$/g, "").toLowerCase();
20102
+ } catch {
20103
+ return false;
20104
+ }
20105
+ const proxied = Object.keys(headers).some((k) => k === "x-forwarded-for" || k === "forwarded" || k.startsWith("tailscale-"));
20106
+ const peer = (event.node.req.socket.remoteAddress || "").replace(/^::ffff:/, "");
20107
+ const localPeer = /^127\./.test(peer) || peer === "::1" || IN_DOCKER && peer === dockerGateway();
20108
+ return Boolean(LOOPBACK[host]) && !proxied && localPeer;
20109
+ }
20110
+ function dockerGateway() {
20111
+ try {
20112
+ const row = fs.readFileSync("/proc/net/route", "utf8").split("\n").map((l) => l.trim().split(/\s+/)).find((f) => f[1] === "00000000");
20113
+ const hex = row == null ? void 0 : row[2];
20114
+ return hex ? [3, 2, 1, 0].map((i) => String(Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16))).join(".") : "";
20115
+ } catch {
20116
+ return "";
20117
+ }
20118
+ }
20119
+ let adopted = (() => {
20120
+ var _a;
20121
+ try {
20122
+ return ((_a = JSON.parse(fs.readFileSync(APP_URL_FILE, "utf8"))) == null ? void 0 : _a.source) === "serve";
20123
+ } catch {
20124
+ return false;
20125
+ }
20126
+ })();
20127
+ function saveAppUrl(url, fromServe = false) {
20128
+ if (process.env.APP_URL === url && (adopted || !fromServe)) return;
20129
+ fs.mkdirSync(DATA_DIR, { recursive: true });
20130
+ fs.writeFileSync(APP_URL_FILE, `${JSON.stringify(fromServe ? { url, source: "serve" } : { url }, null, 2)}
20131
+ `);
20132
+ adopted = fromServe;
20133
+ if (process.env.APP_URL === url) return;
20134
+ process.env.APP_URL = url;
20135
+ initVapid();
20136
+ log(`APP_URL set to the phone address ${url}`);
20137
+ }
20138
+ function forgetAppUrl() {
20139
+ fs.rmSync(APP_URL_FILE, { force: true });
20140
+ const was = process.env.APP_URL;
20141
+ if (BOOT_APP_URL) process.env.APP_URL = BOOT_APP_URL;
20142
+ else delete process.env.APP_URL;
20143
+ initVapid();
20144
+ log(`phone address ${was} is no longer published by tailscale serve: forgotten`);
20145
+ }
20146
+ function applyServed(served) {
20147
+ if (!served) return;
20148
+ setServedHosts(served);
20149
+ if (ENV_APP_URL) return;
20150
+ const current = process.env.APP_URL || "";
20151
+ if (served.length) saveAppUrl(served.includes(current) ? current : served[0], true);
20152
+ else if (adopted) {
20153
+ if (current !== BOOT_APP_URL) forgetAppUrl();
20154
+ adopted = false;
20155
+ }
20156
+ }
20157
+ const nameListeners = [];
20158
+ function onTailnetName(f) {
20159
+ nameListeners.push(f);
20160
+ }
20161
+ function tellName(name) {
20162
+ if (name) for (const f of nameListeners) f(name);
20163
+ }
20164
+ let adoptedAt = 0;
20165
+ let adopting = null;
20166
+ function adoptServed(maxAgeMs = 0) {
20167
+ if (adopting) return adopting;
20168
+ if (maxAgeMs && Date.now() - adoptedAt < maxAgeMs) return Promise.resolve();
20169
+ const bin = tailscaleBin();
20170
+ adopting = inspect(PORT, bin, bin ? null : tailscaledSocket()).then((net) => {
20171
+ applyServed(net.served);
20172
+ tellName(net.name);
20173
+ }, (e) => {
20174
+ log(`tailscale serve could not be read: ${(e == null ? void 0 : e.message) || e}`);
20175
+ }).finally(() => {
20176
+ adoptedAt = Date.now();
20177
+ adopting = null;
20178
+ });
20179
+ return adopting;
20180
+ }
20181
+ let watch = null;
20182
+ function startTailnetWatch() {
20183
+ void adoptServed();
20184
+ watch = setInterval(() => {
20185
+ void adoptServed();
20186
+ }, TAILNET_REFRESH_MS);
20187
+ }
20188
+ function stopTailnetWatch() {
20189
+ if (watch) clearInterval(watch);
20190
+ watch = null;
20191
+ }
20192
+ let typed = "";
20193
+ const waitSince = /* @__PURE__ */ new Map();
20194
+ async function phoneStatus() {
20195
+ var _a, _b, _c, _d, _e, _f, _g;
20196
+ const bin = tailscaleBin();
20197
+ const socket = bin ? null : tailscaledSocket();
20198
+ const out = {
20199
+ mode: bin ? "native" : IN_DOCKER ? "docker" : "missing",
20200
+ platform: process.platform,
20201
+ port: PORT,
20202
+ connected: false,
20203
+ https: false,
20204
+ url: null,
20205
+ served: false,
20206
+ funnel: false,
20207
+ taken: false,
20208
+ suggested: null,
20209
+ command: publishCommand(PORT),
20210
+ reach: null,
20211
+ reachCause: null,
20212
+ reachStatus: null,
20213
+ checkedAt: null,
20214
+ appUrl: "",
20215
+ appUrlFromEnv: Boolean(ENV_APP_URL),
20216
+ qr: null
20217
+ };
20218
+ if (bin || socket) {
20219
+ const net = await inspect(PORT, bin, socket);
20220
+ applyServed(net.served);
20221
+ tellName(net.name);
20222
+ out.connected = Boolean(net.connected && net.name);
20223
+ out.https = Boolean(net.https);
20224
+ out.served = Boolean((_a = net.phone) == null ? void 0 : _a.served);
20225
+ out.funnel = Boolean((_b = net.phone) == null ? void 0 : _b.funnel);
20226
+ out.taken = Boolean(net.taken);
20227
+ out.suggested = (_d = (_c = net.phone) == null ? void 0 : _c.url) != null ? _d : null;
20228
+ out.url = out.served ? net.phone.url : null;
20229
+ }
20230
+ if (out.mode === "docker") {
20231
+ if (!out.suggested) {
20232
+ let resolv = "";
20233
+ try {
20234
+ resolv = fs.readFileSync("/etc/resolv.conf", "utf8");
20235
+ } catch {
20236
+ }
20237
+ const domain = tailnetDomain(resolv);
20238
+ const machine = (process.env.HOST_LABEL || "").trim().toLowerCase().replace(/[^a-z0-9-]/g, "-");
20239
+ out.suggested = domain && machine ? `https://${machine}.${domain}:${PORT}/` : null;
20240
+ }
20241
+ if (!out.url) out.url = typed || tailnetUrl(process.env.APP_URL);
20242
+ const httpsPort = httpsPortOf(out.url || out.suggested);
20243
+ if (httpsPort) out.command = publishCommand(PORT, httpsPort);
20244
+ }
20245
+ if (out.url) {
20246
+ let p = await probe(out.url, 5e3);
20247
+ if (!out.served && (p.reach === "ok" || p.reach === "host") && !ENV_APP_URL) {
20248
+ saveAppUrl(out.url);
20249
+ if (p.reach === "host") p = await probe(out.url, 5e3);
20250
+ }
20251
+ const since = (_e = waitSince.get(out.url)) != null ? _e : Date.now();
20252
+ if (p.reach === "ok") waitSince.delete(out.url);
20253
+ else waitSince.set(out.url, since);
20254
+ out.reach = phoneReach(p, since);
20255
+ out.reachCause = (_f = p.cause) != null ? _f : null;
20256
+ out.reachStatus = (_g = p.status) != null ? _g : null;
20257
+ out.checkedAt = Date.now();
20258
+ }
20259
+ out.appUrl = process.env.APP_URL || "";
20260
+ if (out.reach === "ok" && out.url) out.qr = qrSvg(out.url);
20261
+ return out;
20262
+ }
20263
+ function httpsPortOf(url) {
20264
+ try {
20265
+ return url ? new URL(url).port : "";
20266
+ } catch {
20267
+ return "";
20268
+ }
20269
+ }
20270
+ const fail = async (error, more = {}) => ({ ok: false, error, ...more, status: await phoneStatus() });
20271
+ async function phoneAction(body) {
20272
+ var _a;
20273
+ if (body.action === "address") {
20274
+ const url = tailnetUrl(body.url);
20275
+ if (!url) return fail("address");
20276
+ typed = url;
20277
+ return { ok: true, status: await phoneStatus() };
20278
+ }
20279
+ if (body.action !== "publish" && body.action !== "unpublish") return fail("failed", { detail: "Unknown action" });
20280
+ const bin = tailscaleBin();
20281
+ if (!bin) return fail("offline", { link: "https://tailscale.com/download" });
20282
+ const net = await inspect(PORT, bin);
20283
+ if (!net.connected || !net.phone) return fail("offline", { link: "https://tailscale.com/download" });
20284
+ let args;
20285
+ if (body.action === "publish") {
20286
+ if (net.phone.served) return { ok: true, status: await phoneStatus() };
20287
+ if (net.taken) return fail("taken");
20288
+ args = publishArgs(PORT);
20289
+ } else {
20290
+ if (!net.phone.served) return { ok: true, status: await phoneStatus() };
20291
+ args = unpublishArgs(net.phone.httpsPort);
20292
+ }
20293
+ const r = await runTailscale(bin, args);
20294
+ waitSince.clear();
20295
+ log(`tailscale ${args.join(" ")} \u2192 ${r.code}`);
20296
+ if (r.code !== 0) {
20297
+ const known = serveError(r.output);
20298
+ return fail((_a = known == null ? void 0 : known.code) != null ? _a : "failed", { link: known == null ? void 0 : known.link, detail: r.output.trim().slice(-600) });
20299
+ }
20300
+ return { ok: true, status: await phoneStatus() };
20301
+ }
20302
+
20303
+ const fsp = fs.promises;
20304
+ const REFUSED = {
20305
+ archive: "Archives cannot be read by the agent: extract the files first",
20306
+ video: "Videos cannot be read by the agent",
20307
+ audio: "Audio files cannot be read by the agent",
20308
+ office: "Office documents cannot be read by the agent: export to PDF or text first",
20309
+ executable: "Programs and binary files cannot be read by the agent",
20310
+ binary: "Binary files cannot be read by the agent",
20311
+ pdf: "This agent cannot read PDFs: only Claude Code can",
20312
+ too_large: "File too large for the agent",
20313
+ empty: "Empty file"
20314
+ };
20315
+ async function saveAttachment(data, originalName, paneId) {
20316
+ const pane = findPane(paneId);
20317
+ const m = machineOfPane(paneId);
20318
+ if (!pane || !m) throw new HerdrError("bad_pane", "Pane not found");
20319
+ const check = checkAttachment(pane.agent, { name: originalName, type: "", size: data.length }, data.subarray(0, SNIFF_BYTES));
20320
+ if (check.ok && check.kind === "image") throw new HerdrError("unsupported", "Images go through the photo upload");
20321
+ if (!check.ok) throw new HerdrError(check.reason === "too_large" ? "too_large" : "unsupported", REFUSED[check.reason]);
20322
+ const stored = storedAttachmentName(originalName, /* @__PURE__ */ new Date(), crypto.randomBytes(3).toString("hex"));
20323
+ if (!isStoredAttachmentName(stored)) throw new HerdrError("bad_name", "Invalid file name");
20324
+ let file;
20325
+ if (m instanceof RemoteMachine) {
20326
+ if (m.status !== "online") throw new HerdrError("unreachable", fmt("{machine} is unreachable", { machine: m.label }));
20327
+ file = await m.putUpload(stored, data, "file");
20328
+ } else {
20329
+ file = path$1.join(ATTACH_DIR, stored);
20330
+ if (path$1.dirname(file) !== path$1.resolve(ATTACH_DIR)) throw new HerdrError("bad_name", "Invalid file name");
20331
+ await fsp.mkdir(ATTACH_DIR, { recursive: true, mode: 448 });
20332
+ await fsp.writeFile(file, data, { mode: 384, flag: "wx" });
20333
+ }
20334
+ log(`file received ${stored} (${Math.round(data.length / 1024)} KB, ${check.kind})${m instanceof RemoteMachine ? ` -> ${m.label}` : ""}`);
20335
+ return { path: file, name: stored, kind: check.kind, size: data.length, ref: attachmentRef(pane.agent, check.kind, file) };
20336
+ }
20337
+ async function cleanAttachments() {
20338
+ try {
20339
+ for (const n of await fsp.readdir(ATTACH_DIR)) {
20340
+ const f = path$1.join(ATTACH_DIR, n);
20341
+ const st = await fsp.lstat(f);
20342
+ if (st.isFile() && Date.now() - st.mtimeMs > UPLOAD_TTL_MS) await fsp.unlink(f);
20343
+ }
20344
+ } catch {
20345
+ }
20346
+ }
20347
+
20348
+ const _mZt23IdRC2vLuqDtWT6mb7m9BgHXq3U5QrnQ7Z5Hc4c = defineNitroPlugin((nitroApp) => {
20349
+ initVapid();
20350
+ startMachines();
20351
+ startPolling();
20352
+ startThreadLimits();
20353
+ startNotices();
20354
+ startPaneSizes();
20355
+ onTailnetName(adoptTailnetName);
20356
+ startTailnetWatch();
20357
+ cleanUploads();
20358
+ cleanAttachments();
20359
+ const purge = setInterval(() => {
20360
+ cleanUploads();
20361
+ cleanAttachments();
20362
+ }, 6 * 3600 * 1e3);
20363
+ log(`herdr-web \u2014 socket ${HERDR_SOCK}${HERDR_SESSION ? ` (session ${HERDR_SESSION})` : ""}`);
20364
+ nitroApp.hooks.hook("close", () => {
20365
+ stopPolling();
20366
+ stopNotices();
20367
+ stopPaneSizes();
20368
+ stopTailnetWatch();
20369
+ stopMachines();
20370
+ clearInterval(purge);
20371
+ });
20372
+ });
20373
+
20374
+ const plugins = [
20375
+ _9as7ch5sSe9R7BrbrDE2GGZpuA6oP9uMAcL7iRtRgrw,
20376
+ _mZt23IdRC2vLuqDtWT6mb7m9BgHXq3U5QrnQ7Z5Hc4c
20377
+ ];
20378
+
20379
+ const assets = {
20380
+ "/manifest-en.webmanifest": {
20381
+ "type": "application/manifest+json",
20382
+ "etag": "\"260-q1JnCK0/ecdKwgeTve6N4I6/mWI\"",
20383
+ "mtime": "2026-10-10T00:24:14.956Z",
20384
+ "size": 608,
20385
+ "path": "../public/manifest-en.webmanifest"
20386
+ },
20387
+ "/manifest.webmanifest": {
20388
+ "type": "application/manifest+json",
20389
+ "etag": "\"26e-ykoYuj4V8la457yChW3oQTur8RU\"",
20390
+ "mtime": "2026-10-10T00:24:14.956Z",
20391
+ "size": 622,
20392
+ "path": "../public/manifest.webmanifest"
20393
+ },
20394
+ "/sw.js": {
19220
20395
  "type": "text/javascript; charset=utf-8",
19221
- "etag": "\"1b1d-/btQUHyWtp3Iq2xFXUUw0LtDuSI\"",
19222
- "mtime": "2026-10-08T06:32:45.494Z",
20396
+ "etag": "\"1b1d-wHdIux7mWagbphlmnadZ+jdW3jQ\"",
20397
+ "mtime": "2026-10-10T00:24:15.363Z",
19223
20398
  "size": 6941,
19224
20399
  "path": "../public/sw.js"
19225
20400
  },
19226
- "/_nuxt/01adBkx4.js": {
19227
- "type": "text/javascript; charset=utf-8",
19228
- "etag": "\"3747-VER4El4EhiCM+icoIO5q+cqLbEs\"",
19229
- "mtime": "2026-10-08T06:32:45.027Z",
19230
- "size": 14151,
19231
- "path": "../public/_nuxt/01adBkx4.js"
19232
- },
19233
- "/_nuxt/AgentView.DE2GDt4O.css": {
20401
+ "/_nuxt/AgentView.C4YOwvZG.css": {
19234
20402
  "type": "text/css; charset=utf-8",
19235
- "etag": "\"15c8-GxeP4UBrftfPzQaa+JukFd9f7XU\"",
19236
- "mtime": "2026-10-08T06:32:45.025Z",
20403
+ "etag": "\"15c8-yoJUdtWy2mP7U+kxJ717a6Bzxgg\"",
20404
+ "mtime": "2026-10-10T00:24:14.946Z",
19237
20405
  "size": 5576,
19238
- "path": "../public/_nuxt/AgentView.DE2GDt4O.css"
20406
+ "path": "../public/_nuxt/AgentView.C4YOwvZG.css"
19239
20407
  },
19240
- "/_nuxt/Bd17z0YL.js": {
20408
+ "/_nuxt/B7QP1TFs.js": {
19241
20409
  "type": "text/javascript; charset=utf-8",
19242
- "etag": "\"1615-CPDn06/EO3pj6KudXz5uMZ0FkhI\"",
19243
- "mtime": "2026-10-08T06:32:45.026Z",
19244
- "size": 5653,
19245
- "path": "../public/_nuxt/Bd17z0YL.js"
20410
+ "etag": "\"1118a-Gc+7ipGEaJc4tPQJbl761DHnbVc\"",
20411
+ "mtime": "2026-10-10T00:24:14.946Z",
20412
+ "size": 70026,
20413
+ "path": "../public/_nuxt/B7QP1TFs.js"
19246
20414
  },
19247
20415
  "/_nuxt/BDNMzG2s.js": {
19248
20416
  "type": "text/javascript; charset=utf-8",
19249
20417
  "etag": "\"54-MasMfSk/A98C3Gn9uIOxtFxkWNw\"",
19250
- "mtime": "2026-10-08T06:32:45.026Z",
20418
+ "mtime": "2026-10-10T00:24:14.946Z",
19251
20419
  "size": 84,
19252
20420
  "path": "../public/_nuxt/BDNMzG2s.js"
19253
20421
  },
19254
- "/_nuxt/BpQH8Ho1.js": {
19255
- "type": "text/javascript; charset=utf-8",
19256
- "etag": "\"e3-flLwB9P4XX6B82smMHA/xJ0MHtI\"",
19257
- "mtime": "2026-10-08T06:32:45.026Z",
19258
- "size": 227,
19259
- "path": "../public/_nuxt/BpQH8Ho1.js"
19260
- },
19261
- "/_nuxt/BJsf0WRn.js": {
19262
- "type": "text/javascript; charset=utf-8",
19263
- "etag": "\"1f353-ahQQna7JD89rzZUp6bwRShpjlQE\"",
19264
- "mtime": "2026-10-08T06:32:45.026Z",
19265
- "size": 127827,
19266
- "path": "../public/_nuxt/BJsf0WRn.js"
19267
- },
19268
- "/_nuxt/C3BWhslt.js": {
19269
- "type": "text/javascript; charset=utf-8",
19270
- "etag": "\"238-HWxHiDrmJpEthIOLdUDCstni3R0\"",
19271
- "mtime": "2026-10-08T06:32:45.026Z",
19272
- "size": 568,
19273
- "path": "../public/_nuxt/C3BWhslt.js"
19274
- },
19275
- "/_nuxt/Bta8at9w.js": {
20422
+ "/_nuxt/Bd17z0YL.js": {
19276
20423
  "type": "text/javascript; charset=utf-8",
19277
- "etag": "\"16d0e-LpSBQLvrB7mKZ2B8Eicj7etAGdk\"",
19278
- "mtime": "2026-10-08T06:32:45.026Z",
19279
- "size": 93454,
19280
- "path": "../public/_nuxt/Bta8at9w.js"
20424
+ "etag": "\"1615-CPDn06/EO3pj6KudXz5uMZ0FkhI\"",
20425
+ "mtime": "2026-10-10T00:24:14.946Z",
20426
+ "size": 5653,
20427
+ "path": "../public/_nuxt/Bd17z0YL.js"
19281
20428
  },
19282
- "/_nuxt/C3P2P5Mv.js": {
20429
+ "/_nuxt/BOpBIziZ.js": {
19283
20430
  "type": "text/javascript; charset=utf-8",
19284
- "etag": "\"13f-wmU8nH/Vpt70BJg9v94dElo7ZD4\"",
19285
- "mtime": "2026-10-08T06:32:45.026Z",
19286
- "size": 319,
19287
- "path": "../public/_nuxt/C3P2P5Mv.js"
20431
+ "etag": "\"17047-w7HRRR9UP7E4j/0+gMWSMH60Me4\"",
20432
+ "mtime": "2026-10-10T00:24:14.946Z",
20433
+ "size": 94279,
20434
+ "path": "../public/_nuxt/BOpBIziZ.js"
19288
20435
  },
19289
20436
  "/_nuxt/CWwyK2XK.js": {
19290
20437
  "type": "text/javascript; charset=utf-8",
19291
20438
  "etag": "\"7e5e-4BG2J9cvS3sx6WQ360LFqNaThyA\"",
19292
- "mtime": "2026-10-08T06:32:45.026Z",
20439
+ "mtime": "2026-10-10T00:24:14.947Z",
19293
20440
  "size": 32350,
19294
20441
  "path": "../public/_nuxt/CWwyK2XK.js"
19295
20442
  },
19296
20443
  "/_nuxt/CXG-cOdh.js": {
19297
20444
  "type": "text/javascript; charset=utf-8",
19298
20445
  "etag": "\"d4e-hM6T4bjr/qfJVvSNRqFH3ePHBQY\"",
19299
- "mtime": "2026-10-08T06:32:45.026Z",
20446
+ "mtime": "2026-10-10T00:24:14.947Z",
19300
20447
  "size": 3406,
19301
20448
  "path": "../public/_nuxt/CXG-cOdh.js"
19302
20449
  },
20450
+ "/_nuxt/BWXdFfeg.js": {
20451
+ "type": "text/javascript; charset=utf-8",
20452
+ "etag": "\"39363-ZuVw+u9xjhW/x3aFJmung4q3Pdg\"",
20453
+ "mtime": "2026-10-10T00:24:14.946Z",
20454
+ "size": 234339,
20455
+ "path": "../public/_nuxt/BWXdFfeg.js"
20456
+ },
20457
+ "/_nuxt/Cd8j9O5w.js": {
20458
+ "type": "text/javascript; charset=utf-8",
20459
+ "etag": "\"238-VLnqq4ND3a28No8coqBgt+ad3DA\"",
20460
+ "mtime": "2026-10-10T00:24:14.947Z",
20461
+ "size": 568,
20462
+ "path": "../public/_nuxt/Cd8j9O5w.js"
20463
+ },
20464
+ "/_nuxt/COFCNFpG.js": {
20465
+ "type": "text/javascript; charset=utf-8",
20466
+ "etag": "\"16ee4-rnyLLYmsFymH3XyCdaXwcJEEGuQ\"",
20467
+ "mtime": "2026-10-10T00:24:14.947Z",
20468
+ "size": 93924,
20469
+ "path": "../public/_nuxt/COFCNFpG.js"
20470
+ },
19303
20471
  "/_nuxt/Ci8N4mWR.js": {
19304
20472
  "type": "text/javascript; charset=utf-8",
19305
20473
  "etag": "\"2426-fCrTYUyhtdHnTmu6ZJeEttvxVMo\"",
19306
- "mtime": "2026-10-08T06:32:45.026Z",
20474
+ "mtime": "2026-10-10T00:24:14.947Z",
19307
20475
  "size": 9254,
19308
20476
  "path": "../public/_nuxt/Ci8N4mWR.js"
19309
20477
  },
19310
- "/_nuxt/Cc2bsOcu.js": {
20478
+ "/_nuxt/CkZ8t6uB.js": {
19311
20479
  "type": "text/javascript; charset=utf-8",
19312
- "etag": "\"167bb-Yplb3rDxDI61yGvuj1HlDh0KLSA\"",
19313
- "mtime": "2026-10-08T06:32:45.026Z",
19314
- "size": 92091,
19315
- "path": "../public/_nuxt/Cc2bsOcu.js"
20480
+ "etag": "\"3743-91mhngVNRfpvaYKVJ/QoYV6yh1A\"",
20481
+ "mtime": "2026-10-10T00:24:14.947Z",
20482
+ "size": 14147,
20483
+ "path": "../public/_nuxt/CkZ8t6uB.js"
19316
20484
  },
19317
- "/_nuxt/DLLQgP0d.js": {
20485
+ "/_nuxt/D7sxNXx1.js": {
19318
20486
  "type": "text/javascript; charset=utf-8",
19319
- "etag": "\"ed-o6vDEpQGmmIRAyMcrFDkG+VrA/Y\"",
19320
- "mtime": "2026-10-08T06:32:45.026Z",
19321
- "size": 237,
19322
- "path": "../public/_nuxt/DLLQgP0d.js"
20487
+ "etag": "\"13f-Neu4hxK7KXpqJx1TOH9mlMOz1nI\"",
20488
+ "mtime": "2026-10-10T00:24:14.947Z",
20489
+ "size": 319,
20490
+ "path": "../public/_nuxt/D7sxNXx1.js"
20491
+ },
20492
+ "/_nuxt/DViwOclv.js": {
20493
+ "type": "text/javascript; charset=utf-8",
20494
+ "etag": "\"10a-+H4vdCMSTz83KYVa6X+gWZCDDow\"",
20495
+ "mtime": "2026-10-10T00:24:14.947Z",
20496
+ "size": 266,
20497
+ "path": "../public/_nuxt/DViwOclv.js"
19323
20498
  },
19324
- "/_nuxt/DKmoLFfn.js": {
20499
+ "/_nuxt/DyBZDSCV.js": {
19325
20500
  "type": "text/javascript; charset=utf-8",
19326
- "etag": "\"46c8d-r+hKEnyapfmY9BqbhbIwqAOtZic\"",
19327
- "mtime": "2026-10-08T06:32:45.026Z",
19328
- "size": 289933,
19329
- "path": "../public/_nuxt/DKmoLFfn.js"
20501
+ "etag": "\"1f34c-1/LEO6HIPrMJPOnrvbeL2tadMf4\"",
20502
+ "mtime": "2026-10-10T00:24:14.947Z",
20503
+ "size": 127820,
20504
+ "path": "../public/_nuxt/DyBZDSCV.js"
19330
20505
  },
19331
- "/_nuxt/OAXEskzA.js": {
20506
+ "/_nuxt/DoD7Qb5P.js": {
19332
20507
  "type": "text/javascript; charset=utf-8",
19333
- "etag": "\"16078-aY0cqSl8r/5U/CQ3AmOeG9tu02M\"",
19334
- "mtime": "2026-10-08T06:32:45.026Z",
19335
- "size": 90232,
19336
- "path": "../public/_nuxt/OAXEskzA.js"
20508
+ "etag": "\"4c573-s0WTgDKba5SDqDXhmDa67SGcDHk\"",
20509
+ "mtime": "2026-10-10T00:24:14.947Z",
20510
+ "size": 312691,
20511
+ "path": "../public/_nuxt/DoD7Qb5P.js"
19337
20512
  },
19338
20513
  "/_nuxt/archivo-latin-ext-wght-normal.C4zznr8T.woff2": {
19339
20514
  "type": "font/woff2",
19340
20515
  "etag": "\"7f60-cwHN4xD2RhU0GVBfKzpzYZ7Al6U\"",
19341
- "mtime": "2026-10-08T06:32:45.026Z",
20516
+ "mtime": "2026-10-10T00:24:14.947Z",
19342
20517
  "size": 32608,
19343
20518
  "path": "../public/_nuxt/archivo-latin-ext-wght-normal.C4zznr8T.woff2"
19344
20519
  },
19345
20520
  "/_nuxt/archivo-latin-wght-normal.E0tuGl4L.woff2": {
19346
20521
  "type": "font/woff2",
19347
20522
  "etag": "\"8870-ZUSfFgU3jYpWYbszVY1rxpynehc\"",
19348
- "mtime": "2026-10-08T06:32:45.027Z",
20523
+ "mtime": "2026-10-10T00:24:14.947Z",
19349
20524
  "size": 34928,
19350
20525
  "path": "../public/_nuxt/archivo-latin-wght-normal.E0tuGl4L.woff2"
19351
20526
  },
19352
- "/_nuxt/DcCZr0Ya.js": {
19353
- "type": "text/javascript; charset=utf-8",
19354
- "etag": "\"38e5a-Uo9XwZ1+xuqsHYDa4j5Bu2NslJg\"",
19355
- "mtime": "2026-10-08T06:32:45.026Z",
19356
- "size": 233050,
19357
- "path": "../public/_nuxt/DcCZr0Ya.js"
19358
- },
19359
- "/_nuxt/DsD2R-37.js": {
19360
- "type": "text/javascript; charset=utf-8",
19361
- "etag": "\"10e62-R7H/EMJ87MyD3bNOlEB5xgb5Nwg\"",
19362
- "mtime": "2026-10-08T06:32:45.026Z",
19363
- "size": 69218,
19364
- "path": "../public/_nuxt/DsD2R-37.js"
20527
+ "/_nuxt/archivo-vietnamese-wght-normal.XAtsl5Q_.woff2": {
20528
+ "type": "font/woff2",
20529
+ "etag": "\"33b8-4zjQHgOy7y9EDRZs7TDAFDM4Ams\"",
20530
+ "mtime": "2026-10-10T00:24:14.947Z",
20531
+ "size": 13240,
20532
+ "path": "../public/_nuxt/archivo-vietnamese-wght-normal.XAtsl5Q_.woff2"
19365
20533
  },
19366
20534
  "/_nuxt/SiL1FJ5s.js": {
19367
20535
  "type": "text/javascript; charset=utf-8",
19368
20536
  "etag": "\"176ad-/hlyeMkW67OV1A4G2nhIsYJX+vE\"",
19369
- "mtime": "2026-10-08T06:32:45.026Z",
20537
+ "mtime": "2026-10-10T00:24:14.947Z",
19370
20538
  "size": 95917,
19371
20539
  "path": "../public/_nuxt/SiL1FJ5s.js"
19372
20540
  },
19373
- "/_nuxt/BB3uOVbO.js": {
20541
+ "/_nuxt/D4Yz4rat.js": {
19374
20542
  "type": "text/javascript; charset=utf-8",
19375
- "etag": "\"9b3fe-t16VtBij8ejO++N7E7Yrk5VsoC0\"",
19376
- "mtime": "2026-10-08T06:32:45.026Z",
19377
- "size": 635902,
19378
- "path": "../public/_nuxt/BB3uOVbO.js"
20543
+ "etag": "\"16234-4R0MEqvg4VFo319EzlQAELnsvis\"",
20544
+ "mtime": "2026-10-10T00:24:14.947Z",
20545
+ "size": 90676,
20546
+ "path": "../public/_nuxt/D4Yz4rat.js"
19379
20547
  },
19380
- "/_nuxt/archivo-vietnamese-wght-normal.XAtsl5Q_.woff2": {
19381
- "type": "font/woff2",
19382
- "etag": "\"33b8-4zjQHgOy7y9EDRZs7TDAFDM4Ams\"",
19383
- "mtime": "2026-10-08T06:32:45.027Z",
19384
- "size": 13240,
19385
- "path": "../public/_nuxt/archivo-vietnamese-wght-normal.XAtsl5Q_.woff2"
20548
+ "/_nuxt/8ksjcuDu.js": {
20549
+ "type": "text/javascript; charset=utf-8",
20550
+ "etag": "\"9d965-5D6213Qjb51iD9KHvuiyUPU3qew\"",
20551
+ "mtime": "2026-10-10T00:24:14.946Z",
20552
+ "size": 645477,
20553
+ "path": "../public/_nuxt/8ksjcuDu.js"
19386
20554
  },
19387
20555
  "/_nuxt/bLP7Jj-b.js": {
19388
20556
  "type": "text/javascript; charset=utf-8",
19389
20557
  "etag": "\"eab-MDKLGS0aoyNdipefSjbM7x6w6Y8\"",
19390
- "mtime": "2026-10-08T06:32:45.027Z",
20558
+ "mtime": "2026-10-10T00:24:14.948Z",
19391
20559
  "size": 3755,
19392
20560
  "path": "../public/_nuxt/bLP7Jj-b.js"
19393
20561
  },
19394
20562
  "/_nuxt/error-404.Bb87HomL.css": {
19395
20563
  "type": "text/css; charset=utf-8",
19396
20564
  "etag": "\"97d-4h9VfBznldxjqagfMldh1hDncR0\"",
19397
- "mtime": "2026-10-08T06:32:45.027Z",
20565
+ "mtime": "2026-10-10T00:24:14.948Z",
19398
20566
  "size": 2429,
19399
20567
  "path": "../public/_nuxt/error-404.Bb87HomL.css"
19400
20568
  },
19401
20569
  "/_nuxt/error-500.Bwd7zAaE.css": {
19402
20570
  "type": "text/css; charset=utf-8",
19403
20571
  "etag": "\"772-nqXXACYtFiPK+D42BNu4uEECARA\"",
19404
- "mtime": "2026-10-08T06:32:45.027Z",
20572
+ "mtime": "2026-10-10T00:24:14.948Z",
19405
20573
  "size": 1906,
19406
20574
  "path": "../public/_nuxt/error-500.Bwd7zAaE.css"
19407
20575
  },
19408
- "/_nuxt/inter-cyrillic-ext-wght-normal.BOeWTOD4.woff2": {
19409
- "type": "font/woff2",
19410
- "etag": "\"6568-cF1iUGbboMFZ8TfnP5HiMgl9II0\"",
19411
- "mtime": "2026-10-08T06:32:45.027Z",
19412
- "size": 25960,
19413
- "path": "../public/_nuxt/inter-cyrillic-ext-wght-normal.BOeWTOD4.woff2"
20576
+ "/_nuxt/hePW80VL.js": {
20577
+ "type": "text/javascript; charset=utf-8",
20578
+ "etag": "\"2cc-fA8td6k29UVF6JoPfhOPkceTK1M\"",
20579
+ "mtime": "2026-10-10T00:24:14.948Z",
20580
+ "size": 716,
20581
+ "path": "../public/_nuxt/hePW80VL.js"
19414
20582
  },
19415
20583
  "/_nuxt/inter-cyrillic-wght-normal.DqGufNeO.woff2": {
19416
20584
  "type": "font/woff2",
19417
20585
  "etag": "\"493c-n3Oy9D6jvzfMjpClqox+Zo7ERQQ\"",
19418
- "mtime": "2026-10-08T06:32:45.027Z",
20586
+ "mtime": "2026-10-10T00:24:14.948Z",
19419
20587
  "size": 18748,
19420
20588
  "path": "../public/_nuxt/inter-cyrillic-wght-normal.DqGufNeO.woff2"
19421
20589
  },
20590
+ "/_nuxt/inter-cyrillic-ext-wght-normal.BOeWTOD4.woff2": {
20591
+ "type": "font/woff2",
20592
+ "etag": "\"6568-cF1iUGbboMFZ8TfnP5HiMgl9II0\"",
20593
+ "mtime": "2026-10-10T00:24:14.948Z",
20594
+ "size": 25960,
20595
+ "path": "../public/_nuxt/inter-cyrillic-ext-wght-normal.BOeWTOD4.woff2"
20596
+ },
19422
20597
  "/_nuxt/inter-greek-ext-wght-normal.DlzME5K_.woff2": {
19423
20598
  "type": "font/woff2",
19424
20599
  "etag": "\"2be0-BP5iTzJeB8nLqYAgKpWNi5o1Zm8\"",
19425
- "mtime": "2026-10-08T06:32:45.027Z",
20600
+ "mtime": "2026-10-10T00:24:14.948Z",
19426
20601
  "size": 11232,
19427
20602
  "path": "../public/_nuxt/inter-greek-ext-wght-normal.DlzME5K_.woff2"
19428
20603
  },
20604
+ "/_nuxt/entry.Crt1r4l3.css": {
20605
+ "type": "text/css; charset=utf-8",
20606
+ "etag": "\"60b9f-TZJSjtJYwO9YgW0BvzvwELvXTcA\"",
20607
+ "mtime": "2026-10-10T00:24:14.948Z",
20608
+ "size": 396191,
20609
+ "path": "../public/_nuxt/entry.Crt1r4l3.css"
20610
+ },
19429
20611
  "/_nuxt/inter-greek-wght-normal.CkhJZR-_.woff2": {
19430
20612
  "type": "font/woff2",
19431
20613
  "etag": "\"4a34-xor/hj4YNqI52zFecXnUbzQ4Xs4\"",
19432
- "mtime": "2026-10-08T06:32:45.027Z",
20614
+ "mtime": "2026-10-10T00:24:14.948Z",
19433
20615
  "size": 18996,
19434
20616
  "path": "../public/_nuxt/inter-greek-wght-normal.CkhJZR-_.woff2"
19435
20617
  },
20618
+ "/_nuxt/inter-latin-ext-wght-normal.DO1Apj_S.woff2": {
20619
+ "type": "font/woff2",
20620
+ "etag": "\"14c4c-zz61D7IQFMB9QxHvTAOk/Vh4ibQ\"",
20621
+ "mtime": "2026-10-10T00:24:14.948Z",
20622
+ "size": 85068,
20623
+ "path": "../public/_nuxt/inter-latin-ext-wght-normal.DO1Apj_S.woff2"
20624
+ },
19436
20625
  "/_nuxt/inter-latin-wght-normal.Dx4kXJAl.woff2": {
19437
20626
  "type": "font/woff2",
19438
20627
  "etag": "\"bc80-8R1ym7Ck2DUNLqPQ/AYs9u8tUpg\"",
19439
- "mtime": "2026-10-08T06:32:45.027Z",
20628
+ "mtime": "2026-10-10T00:24:14.948Z",
19440
20629
  "size": 48256,
19441
20630
  "path": "../public/_nuxt/inter-latin-wght-normal.Dx4kXJAl.woff2"
19442
20631
  },
19443
- "/_nuxt/inter-latin-ext-wght-normal.DO1Apj_S.woff2": {
20632
+ "/_nuxt/inter-vietnamese-wght-normal.CBcvBZtf.woff2": {
19444
20633
  "type": "font/woff2",
19445
- "etag": "\"14c4c-zz61D7IQFMB9QxHvTAOk/Vh4ibQ\"",
19446
- "mtime": "2026-10-08T06:32:45.027Z",
19447
- "size": 85068,
19448
- "path": "../public/_nuxt/inter-latin-ext-wght-normal.DO1Apj_S.woff2"
20634
+ "etag": "\"280c-nBythjoDQ0+5wVAendJ6wU7Xz2M\"",
20635
+ "mtime": "2026-10-10T00:24:14.948Z",
20636
+ "size": 10252,
20637
+ "path": "../public/_nuxt/inter-vietnamese-wght-normal.CBcvBZtf.woff2"
19449
20638
  },
19450
20639
  "/_nuxt/jetbrains-mono-cyrillic-wght-normal.D73BlboJ.woff2": {
19451
20640
  "type": "font/woff2",
19452
20641
  "etag": "\"2f4c-WiAGfn140d4QND3ayQWaCHF8rbE\"",
19453
- "mtime": "2026-10-08T06:32:45.027Z",
20642
+ "mtime": "2026-10-10T00:24:14.948Z",
19454
20643
  "size": 12108,
19455
20644
  "path": "../public/_nuxt/jetbrains-mono-cyrillic-wght-normal.D73BlboJ.woff2"
19456
20645
  },
19457
- "/_nuxt/entry.SHBiCXBL.css": {
19458
- "type": "text/css; charset=utf-8",
19459
- "etag": "\"5f413-f3n+Sg6VQFoGZlw+vPer7+m6eZY\"",
19460
- "mtime": "2026-10-08T06:32:45.027Z",
19461
- "size": 390163,
19462
- "path": "../public/_nuxt/entry.SHBiCXBL.css"
19463
- },
19464
20646
  "/_nuxt/jetbrains-mono-greek-wght-normal.Bw9x6K1M.woff2": {
19465
20647
  "type": "font/woff2",
19466
20648
  "etag": "\"232c-Dnz9DhH4c266e6TziU1pxRkV6FY\"",
19467
- "mtime": "2026-10-08T06:32:45.027Z",
20649
+ "mtime": "2026-10-10T00:24:14.948Z",
19468
20650
  "size": 9004,
19469
20651
  "path": "../public/_nuxt/jetbrains-mono-greek-wght-normal.Bw9x6K1M.woff2"
19470
20652
  },
19471
- "/_nuxt/inter-vietnamese-wght-normal.CBcvBZtf.woff2": {
19472
- "type": "font/woff2",
19473
- "etag": "\"280c-nBythjoDQ0+5wVAendJ6wU7Xz2M\"",
19474
- "mtime": "2026-10-08T06:32:45.027Z",
19475
- "size": 10252,
19476
- "path": "../public/_nuxt/inter-vietnamese-wght-normal.CBcvBZtf.woff2"
19477
- },
19478
20653
  "/_nuxt/jetbrains-mono-latin-ext-wght-normal.DBQx-q_a.woff2": {
19479
20654
  "type": "font/woff2",
19480
20655
  "etag": "\"3b5c-HLF7Wvs2Z1IA1cPRs6jnor8OUQ4\"",
19481
- "mtime": "2026-10-08T06:32:45.027Z",
20656
+ "mtime": "2026-10-10T00:24:14.949Z",
19482
20657
  "size": 15196,
19483
20658
  "path": "../public/_nuxt/jetbrains-mono-latin-ext-wght-normal.DBQx-q_a.woff2"
19484
20659
  },
19485
- "/_nuxt/jetbrains-mono-vietnamese-wght-normal.Bt-aOZkq.woff2": {
19486
- "type": "font/woff2",
19487
- "etag": "\"1d50-/Re0MyD6BV8h81wBPVijGZH5GBs\"",
19488
- "mtime": "2026-10-08T06:32:45.027Z",
19489
- "size": 7504,
19490
- "path": "../public/_nuxt/jetbrains-mono-vietnamese-wght-normal.Bt-aOZkq.woff2"
19491
- },
19492
20660
  "/_nuxt/jetbrains-mono-latin-wght-normal.B9CIFXIH.woff2": {
19493
20661
  "type": "font/woff2",
19494
20662
  "etag": "\"9dd4-5yd+cUUhzrXxdMyYebUeD0qml1M\"",
19495
- "mtime": "2026-10-08T06:32:45.027Z",
20663
+ "mtime": "2026-10-10T00:24:14.949Z",
19496
20664
  "size": 40404,
19497
20665
  "path": "../public/_nuxt/jetbrains-mono-latin-wght-normal.B9CIFXIH.woff2"
19498
20666
  },
19499
20667
  "/_nuxt/wherdr-nerd-codicons.CtEsfSAf.woff2": {
19500
20668
  "type": "font/woff2",
19501
20669
  "etag": "\"d9b0-kmO/x6KCTFRlVipOvl+5XcArGUw\"",
19502
- "mtime": "2026-10-08T06:32:45.027Z",
20670
+ "mtime": "2026-10-10T00:24:14.949Z",
19503
20671
  "size": 55728,
19504
20672
  "path": "../public/_nuxt/wherdr-nerd-codicons.CtEsfSAf.woff2"
19505
20673
  },
19506
- "/_nuxt/wherdr-nerd-extension.Dai_ky39.woff2": {
20674
+ "/_nuxt/jetbrains-mono-vietnamese-wght-normal.Bt-aOZkq.woff2": {
19507
20675
  "type": "font/woff2",
19508
- "etag": "\"f76c-CjERGtjtOrkpSXy7JyZNl1CAu0U\"",
19509
- "mtime": "2026-10-08T06:32:45.027Z",
19510
- "size": 63340,
19511
- "path": "../public/_nuxt/wherdr-nerd-extension.Dai_ky39.woff2"
20676
+ "etag": "\"1d50-/Re0MyD6BV8h81wBPVijGZH5GBs\"",
20677
+ "mtime": "2026-10-10T00:24:14.949Z",
20678
+ "size": 7504,
20679
+ "path": "../public/_nuxt/jetbrains-mono-vietnamese-wght-normal.Bt-aOZkq.woff2"
19512
20680
  },
19513
20681
  "/_nuxt/wherdr-nerd-awesome.DQ3_iAfa.woff2": {
19514
20682
  "type": "font/woff2",
19515
20683
  "etag": "\"30178-02mpQKEDb74Ec8LtlhKceB1vHO8\"",
19516
- "mtime": "2026-10-08T06:32:45.027Z",
20684
+ "mtime": "2026-10-10T00:24:14.949Z",
19517
20685
  "size": 196984,
19518
20686
  "path": "../public/_nuxt/wherdr-nerd-awesome.DQ3_iAfa.woff2"
19519
20687
  },
20688
+ "/_nuxt/wherdr-nerd-extension.Dai_ky39.woff2": {
20689
+ "type": "font/woff2",
20690
+ "etag": "\"f76c-CjERGtjtOrkpSXy7JyZNl1CAu0U\"",
20691
+ "mtime": "2026-10-10T00:24:14.949Z",
20692
+ "size": 63340,
20693
+ "path": "../public/_nuxt/wherdr-nerd-extension.Dai_ky39.woff2"
20694
+ },
19520
20695
  "/_nuxt/wherdr-nerd-material-1.BdvqkCqW.woff2": {
19521
20696
  "type": "font/woff2",
19522
20697
  "etag": "\"1e130-1GKpbVoJoRf3AxaKTZS8FjxkOd4\"",
19523
- "mtime": "2026-10-08T06:32:45.027Z",
20698
+ "mtime": "2026-10-10T00:24:14.949Z",
19524
20699
  "size": 123184,
19525
20700
  "path": "../public/_nuxt/wherdr-nerd-material-1.BdvqkCqW.woff2"
19526
20701
  },
20702
+ "/_nuxt/wherdr-nerd-devicons.ZMSFX1sW.woff2": {
20703
+ "type": "font/woff2",
20704
+ "etag": "\"30f78-2RuGTMLnbTBdhlI9ExApyiUeD8c\"",
20705
+ "mtime": "2026-10-10T00:24:14.949Z",
20706
+ "size": 200568,
20707
+ "path": "../public/_nuxt/wherdr-nerd-devicons.ZMSFX1sW.woff2"
20708
+ },
19527
20709
  "/_nuxt/wherdr-nerd-material-2.C_flbBWO.woff2": {
19528
20710
  "type": "font/woff2",
19529
20711
  "etag": "\"1f95c-q6dAP964z2dgEvWzAYjTAzBtzBc\"",
19530
- "mtime": "2026-10-08T06:32:45.028Z",
20712
+ "mtime": "2026-10-10T00:24:14.949Z",
19531
20713
  "size": 129372,
19532
20714
  "path": "../public/_nuxt/wherdr-nerd-material-2.C_flbBWO.woff2"
19533
20715
  },
19534
20716
  "/_nuxt/wherdr-nerd-material-3.8xz7iX-Y.woff2": {
19535
20717
  "type": "font/woff2",
19536
20718
  "etag": "\"2248c-3GBJ1KjkT+xYPhza9V/DKaILa7Y\"",
19537
- "mtime": "2026-10-08T06:32:45.027Z",
20719
+ "mtime": "2026-10-10T00:24:14.949Z",
19538
20720
  "size": 140428,
19539
20721
  "path": "../public/_nuxt/wherdr-nerd-material-3.8xz7iX-Y.woff2"
19540
20722
  },
19541
- "/_nuxt/wherdr-nerd-devicons.ZMSFX1sW.woff2": {
19542
- "type": "font/woff2",
19543
- "etag": "\"30f78-2RuGTMLnbTBdhlI9ExApyiUeD8c\"",
19544
- "mtime": "2026-10-08T06:32:45.027Z",
19545
- "size": 200568,
19546
- "path": "../public/_nuxt/wherdr-nerd-devicons.ZMSFX1sW.woff2"
19547
- },
19548
20723
  "/_nuxt/wherdr-nerd-material-4.CO_PudDR.woff2": {
19549
20724
  "type": "font/woff2",
19550
20725
  "etag": "\"1fa80-YcJvpFD/M3vTC4y6SMmp7s8a9gw\"",
19551
- "mtime": "2026-10-08T06:32:45.028Z",
20726
+ "mtime": "2026-10-10T00:24:14.949Z",
19552
20727
  "size": 129664,
19553
20728
  "path": "../public/_nuxt/wherdr-nerd-material-4.CO_PudDR.woff2"
19554
20729
  },
19555
- "/_nuxt/wherdr-nerd-powerline.B_kAMxXr.woff2": {
19556
- "type": "font/woff2",
19557
- "etag": "\"1bc0-iXm39wQwNbm2VjpVBOn72grfku0\"",
19558
- "mtime": "2026-10-08T06:32:45.028Z",
19559
- "size": 7104,
19560
- "path": "../public/_nuxt/wherdr-nerd-powerline.B_kAMxXr.woff2"
19561
- },
19562
20730
  "/_nuxt/wherdr-nerd-octicons.Hl4VEx8m.woff2": {
19563
20731
  "type": "font/woff2",
19564
20732
  "etag": "\"f890-FkAsW3iqENK+lU5UlSze0yqnE2s\"",
19565
- "mtime": "2026-10-08T06:32:45.028Z",
20733
+ "mtime": "2026-10-10T00:24:14.949Z",
19566
20734
  "size": 63632,
19567
20735
  "path": "../public/_nuxt/wherdr-nerd-octicons.Hl4VEx8m.woff2"
19568
20736
  },
20737
+ "/_nuxt/wherdr-nerd-powerline.B_kAMxXr.woff2": {
20738
+ "type": "font/woff2",
20739
+ "etag": "\"1bc0-iXm39wQwNbm2VjpVBOn72grfku0\"",
20740
+ "mtime": "2026-10-10T00:24:14.950Z",
20741
+ "size": 7104,
20742
+ "path": "../public/_nuxt/wherdr-nerd-powerline.B_kAMxXr.woff2"
20743
+ },
19569
20744
  "/_nuxt/wherdr-symbols.C1LsPKmg.woff2": {
19570
20745
  "type": "font/woff2",
19571
20746
  "etag": "\"1304-Lwyr1XiDl3FK38nmS/6Pk9wfiWc\"",
19572
- "mtime": "2026-10-08T06:32:45.028Z",
20747
+ "mtime": "2026-10-10T00:24:14.950Z",
19573
20748
  "size": 4868,
19574
20749
  "path": "../public/_nuxt/wherdr-symbols.C1LsPKmg.woff2"
19575
20750
  },
19576
20751
  "/_nuxt/yzABg5xl.js": {
19577
20752
  "type": "text/javascript; charset=utf-8",
19578
20753
  "etag": "\"1c49-W7I7waHyF8BfftITNIl1xCbNFQw\"",
19579
- "mtime": "2026-10-08T06:32:45.028Z",
20754
+ "mtime": "2026-10-10T00:24:14.950Z",
19580
20755
  "size": 7241,
19581
20756
  "path": "../public/_nuxt/yzABg5xl.js"
19582
20757
  },
19583
20758
  "/_nuxt/builds/latest.json": {
19584
20759
  "type": "application/json",
19585
- "etag": "\"47-YaCIqL9YgAO6HM7NvW6fsU2ouiU\"",
19586
- "mtime": "2026-10-08T06:32:44.981Z",
20760
+ "etag": "\"47-gUq0w2mhEU+I4n1/G1rUjuC+PWE\"",
20761
+ "mtime": "2026-10-10T00:24:14.921Z",
19587
20762
  "size": 71,
19588
20763
  "path": "../public/_nuxt/builds/latest.json"
19589
20764
  },
19590
- "/_nuxt/builds/meta/ff11fc8b-1e70-4261-a76e-a52155efb196.json": {
20765
+ "/_nuxt/builds/meta/7afbf062-5369-4002-b20d-7a582ff6e0f1.json": {
19591
20766
  "type": "application/json",
19592
- "etag": "\"58-27ycQXI0WHq9sJZqNA3viEA0btQ\"",
19593
- "mtime": "2026-10-08T06:32:44.977Z",
19594
- "size": 88,
19595
- "path": "../public/_nuxt/builds/meta/ff11fc8b-1e70-4261-a76e-a52155efb196.json"
19596
- }
19597
- };
19598
-
19599
- const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
19600
- function normalizeWindowsPath(input = "") {
19601
- if (!input) {
19602
- return input;
19603
- }
19604
- return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
19605
- }
19606
- const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
19607
- const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
19608
- const _ROOT_FOLDER_RE = /^\/([A-Za-z]:)?$/;
19609
- function cwd() {
19610
- if (typeof process !== "undefined" && typeof process.cwd === "function") {
19611
- return process.cwd().replace(/\\/g, "/");
19612
- }
19613
- return "/";
19614
- }
19615
- const resolve = function(...arguments_) {
19616
- arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));
19617
- let resolvedPath = "";
19618
- let resolvedAbsolute = false;
19619
- for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {
19620
- const path = index >= 0 ? arguments_[index] : cwd();
19621
- if (!path || path.length === 0) {
19622
- continue;
19623
- }
19624
- resolvedPath = `${path}/${resolvedPath}`;
19625
- resolvedAbsolute = isAbsolute(path);
19626
- }
19627
- resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
19628
- if (resolvedAbsolute && !isAbsolute(resolvedPath)) {
19629
- return `/${resolvedPath}`;
19630
- }
19631
- return resolvedPath.length > 0 ? resolvedPath : ".";
19632
- };
19633
- function normalizeString(path, allowAboveRoot) {
19634
- let res = "";
19635
- let lastSegmentLength = 0;
19636
- let lastSlash = -1;
19637
- let dots = 0;
19638
- let char = null;
19639
- for (let index = 0; index <= path.length; ++index) {
19640
- if (index < path.length) {
19641
- char = path[index];
19642
- } else if (char === "/") {
19643
- break;
19644
- } else {
19645
- char = "/";
19646
- }
19647
- if (char === "/") {
19648
- if (lastSlash === index - 1 || dots === 1) ; else if (dots === 2) {
19649
- if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
19650
- if (res.length > 2) {
19651
- const lastSlashIndex = res.lastIndexOf("/");
19652
- if (lastSlashIndex === -1) {
19653
- res = "";
19654
- lastSegmentLength = 0;
19655
- } else {
19656
- res = res.slice(0, lastSlashIndex);
19657
- lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
19658
- }
19659
- lastSlash = index;
19660
- dots = 0;
19661
- continue;
19662
- } else if (res.length > 0) {
19663
- res = "";
19664
- lastSegmentLength = 0;
19665
- lastSlash = index;
19666
- dots = 0;
19667
- continue;
19668
- }
19669
- }
19670
- if (allowAboveRoot) {
19671
- res += res.length > 0 ? "/.." : "..";
19672
- lastSegmentLength = 2;
19673
- }
19674
- } else {
19675
- if (res.length > 0) {
19676
- res += `/${path.slice(lastSlash + 1, index)}`;
19677
- } else {
19678
- res = path.slice(lastSlash + 1, index);
19679
- }
19680
- lastSegmentLength = index - lastSlash - 1;
19681
- }
19682
- lastSlash = index;
19683
- dots = 0;
19684
- } else if (char === "." && dots !== -1) {
19685
- ++dots;
19686
- } else {
19687
- dots = -1;
19688
- }
19689
- }
19690
- return res;
19691
- }
19692
- const isAbsolute = function(p) {
19693
- return _IS_ABSOLUTE_RE.test(p);
19694
- };
19695
- const relative = function(from, to) {
19696
- const _from = resolve(from).replace(_ROOT_FOLDER_RE, "$1").split("/");
19697
- const _to = resolve(to).replace(_ROOT_FOLDER_RE, "$1").split("/");
19698
- if (_to[0][1] === ":" && _from[0][1] === ":" && _from[0] !== _to[0]) {
19699
- return _to.join("/");
19700
- }
19701
- const _fromCopy = [..._from];
19702
- for (const segment of _fromCopy) {
19703
- if (_to[0] !== segment) {
19704
- break;
19705
- }
19706
- _from.shift();
19707
- _to.shift();
19708
- }
19709
- return [..._from.map(() => ".."), ..._to].join("/");
19710
- };
19711
- const dirname = function(p) {
19712
- const segments = normalizeWindowsPath(p).replace(/\/$/, "").split("/").slice(0, -1);
19713
- if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) {
19714
- segments[0] += "/";
19715
- }
19716
- return segments.join("/") || (isAbsolute(p) ? "/" : ".");
19717
- };
19718
-
19719
- function readAsset (id) {
19720
- const serverDir = dirname(fileURLToPath(globalThis._importMeta_.url));
19721
- return promises.readFile(resolve(serverDir, assets[id].path))
19722
- }
19723
-
19724
- const publicAssetBases = {"/_nuxt/builds/meta/":{"maxAge":31536000},"/_nuxt/builds/":{"maxAge":1},"/_nuxt/":{"maxAge":31536000}};
19725
-
19726
- function isPublicAssetURL(id = '') {
19727
- if (assets[id]) {
19728
- return true
19729
- }
19730
- for (const base in publicAssetBases) {
19731
- if (id.startsWith(base)) { return true }
19732
- }
19733
- return false
19734
- }
19735
-
19736
- function getAsset (id) {
19737
- return assets[id]
19738
- }
19739
-
19740
- const METHODS = /* @__PURE__ */ new Set(["HEAD", "GET"]);
19741
- const EncodingMap = { gzip: ".gz", br: ".br" };
19742
- const _uN03ln = eventHandler((event) => {
19743
- if (event.method && !METHODS.has(event.method)) {
19744
- return;
19745
- }
19746
- let id = decodePath(
19747
- withLeadingSlash(withoutTrailingSlash(parseURL(event.path).pathname))
19748
- );
19749
- let asset;
19750
- const encodingHeader = String(
19751
- getRequestHeader(event, "accept-encoding") || ""
19752
- );
19753
- const encodings = [
19754
- ...encodingHeader.split(",").map((e) => EncodingMap[e.trim()]).filter(Boolean).sort(),
19755
- ""
19756
- ];
19757
- for (const encoding of encodings) {
19758
- for (const _id of [id + encoding, joinURL(id, "index.html" + encoding)]) {
19759
- const _asset = getAsset(_id);
19760
- if (_asset) {
19761
- asset = _asset;
19762
- id = _id;
19763
- break;
19764
- }
19765
- }
19766
- }
19767
- if (!asset) {
19768
- if (isPublicAssetURL(id)) {
19769
- removeResponseHeader(event, "Cache-Control");
19770
- throw createError$1({ statusCode: 404 });
19771
- }
19772
- return;
19773
- }
19774
- if (asset.encoding !== void 0) {
19775
- appendResponseHeader(event, "Vary", "Accept-Encoding");
19776
- }
19777
- const ifNotMatch = getRequestHeader(event, "if-none-match") === asset.etag;
19778
- if (ifNotMatch) {
19779
- setResponseStatus(event, 304, "Not Modified");
19780
- return "";
19781
- }
19782
- const ifModifiedSinceH = getRequestHeader(event, "if-modified-since");
19783
- const mtimeDate = new Date(asset.mtime);
19784
- if (ifModifiedSinceH && asset.mtime && new Date(ifModifiedSinceH) >= mtimeDate) {
19785
- setResponseStatus(event, 304, "Not Modified");
19786
- return "";
19787
- }
19788
- if (asset.type && !getResponseHeader(event, "Content-Type")) {
19789
- setResponseHeader(event, "Content-Type", asset.type);
19790
- }
19791
- if (asset.etag && !getResponseHeader(event, "ETag")) {
19792
- setResponseHeader(event, "ETag", asset.etag);
19793
- }
19794
- if (asset.mtime && !getResponseHeader(event, "Last-Modified")) {
19795
- setResponseHeader(event, "Last-Modified", mtimeDate.toUTCString());
19796
- }
19797
- if (asset.encoding && !getResponseHeader(event, "Content-Encoding")) {
19798
- setResponseHeader(event, "Content-Encoding", asset.encoding);
19799
- }
19800
- if (asset.size > 0 && !getResponseHeader(event, "Content-Length")) {
19801
- setResponseHeader(event, "Content-Length", asset.size);
19802
- }
19803
- return readAsset(id);
19804
- });
19805
-
19806
- const MAX_SESSION_DAYS = [1, 7, 30, 90, 365];
19807
- const DEFAULT_MAX_SESSION_DAYS = 7;
19808
- const DAY_MS = 24 * 3600 * 1e3;
19809
- function isMaxSessionDays(v) {
19810
- return MAX_SESSION_DAYS.includes(v);
19811
- }
19812
-
19813
- var __defProp = Object.defineProperty;
19814
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
19815
- var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
19816
- const SESSION_MS = 12 * 3600 * 1e3;
19817
- const RENEW_MS = 10 * 60 * 1e3;
19818
- const CHALLENGE_MS = 5 * 60 * 1e3;
19819
- const AUTH_COOKIE = "hw_session";
19820
- const CHALLENGE_COOKIE = "hw_challenge";
19821
- class AuthError extends Error {
19822
- constructor(code, message) {
19823
- super(message);
19824
- __publicField(this, "code");
19825
- this.code = code;
20767
+ "etag": "\"58-LQMKUB4Jlz4hjuTcu/d4TGQA1Kc\"",
20768
+ "mtime": "2026-10-10T00:24:14.916Z",
20769
+ "size": 88,
20770
+ "path": "../public/_nuxt/builds/meta/7afbf062-5369-4002-b20d-7a582ff6e0f1.json"
19826
20771
  }
19827
- }
19828
- function createAuth({ dataDir, log: log2 = () => {
19829
- }, passkeyUser = "herdr" }) {
19830
- const FILE = path$1.join(dataDir, "auth.json");
19831
- let db = { secret: null, generation: 0, credentials: [] };
19832
- try {
19833
- db = { ...db, ...JSON.parse(fs.readFileSync(FILE, "utf8")) };
19834
- } catch {
20772
+ };
20773
+
20774
+ const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
20775
+ function normalizeWindowsPath(input = "") {
20776
+ if (!input) {
20777
+ return input;
19835
20778
  }
19836
- const save = () => {
19837
- fs.mkdirSync(dataDir, { recursive: true });
19838
- fs.writeFileSync(FILE, JSON.stringify(db, null, 2) + "\n", { mode: 384 });
19839
- };
19840
- if (!db.secret) {
19841
- db.secret = crypto.randomBytes(32).toString("hex");
19842
- save();
20779
+ return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
20780
+ }
20781
+ const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
20782
+ const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
20783
+ const _ROOT_FOLDER_RE = /^\/([A-Za-z]:)?$/;
20784
+ function cwd() {
20785
+ if (typeof process !== "undefined" && typeof process.cwd === "function") {
20786
+ return process.cwd().replace(/\\/g, "/");
19843
20787
  }
19844
- const bootstrapToken = crypto.randomBytes(24).toString("base64url");
19845
- if (!db.credentials.length) log2(`First passkey: bootstrap token ${bootstrapToken}`);
19846
- const enabled = () => db.credentials.length > 0;
19847
- const sign = (v) => crypto.createHmac("sha256", db.secret).update(v).digest("base64url");
19848
- const safeEq = (a, b) => {
19849
- const x = Buffer.from(String(a));
19850
- const y = Buffer.from(String(b));
19851
- return x.length === y.length && crypto.timingSafeEqual(x, y);
19852
- };
19853
- const readCookie = (req) => {
19854
- const c = String(req.headers.cookie || "").split(/;\s*/).find((s) => s.startsWith(`${AUTH_COOKIE}=`));
19855
- if (!c) return null;
19856
- try {
19857
- return decodeURIComponent(c.slice(AUTH_COOKIE.length + 1));
19858
- } catch {
19859
- return null;
19860
- }
19861
- };
19862
- const challengeClient = (req) => {
19863
- const c = String(req.headers.cookie || "").split(/;\s*/).find((s) => s.startsWith(`${CHALLENGE_COOKIE}=`));
19864
- return (c == null ? void 0 : c.slice(CHALLENGE_COOKIE.length + 1)) || null;
19865
- };
19866
- const checkBootstrap = (body) => {
19867
- if (!enabled() && !safeEq(String((body == null ? void 0 : body.bootstrapToken) || ""), bootstrapToken)) {
19868
- throw new AuthError("bootstrap", "Invalid bootstrap token (see the server logs)");
20788
+ return "/";
20789
+ }
20790
+ const resolve = function(...arguments_) {
20791
+ arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));
20792
+ let resolvedPath = "";
20793
+ let resolvedAbsolute = false;
20794
+ for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {
20795
+ const path = index >= 0 ? arguments_[index] : cwd();
20796
+ if (!path || path.length === 0) {
20797
+ continue;
19869
20798
  }
19870
- };
19871
- const maxSessionMs = () => {
19872
- var _a;
19873
- return ((_a = db.maxSessionDays) != null ? _a : DEFAULT_MAX_SESSION_DAYS) * DAY_MS;
19874
- };
19875
- function session(req, now = Date.now()) {
19876
- const v = readCookie(req);
19877
- if (!v) return null;
19878
- const parts = v.split(".");
19879
- if (parts.length !== 3 && parts.length !== 4) return null;
19880
- const mac = parts.pop();
19881
- if (!mac || parts[1] !== String(db.generation) || !safeEq(mac, sign(parts.join(".")))) return null;
19882
- const exp = Number(parts[0]);
19883
- const since = parts.length === 3 ? Number(parts[2]) : exp - SESSION_MS;
19884
- if (!(exp >= now) || !Number.isFinite(since)) return null;
19885
- if (now > since + maxSessionMs() + SESSION_MS) return null;
19886
- return { exp, since };
19887
- }
19888
- function isUnlocked(req) {
19889
- return !enabled() || session(req) !== null;
19890
- }
19891
- const secure = (req) => req.headers["x-forwarded-proto"] === "https" || String(req.headers.origin || "").startsWith("https://") ? "; Secure" : "";
19892
- function sessionCookie(req, now = Date.now(), since = now) {
19893
- const exp = now + SESSION_MS;
19894
- const payload = `${exp}.${db.generation}.${since}`;
19895
- return `${AUTH_COOKIE}=${payload}.${sign(payload)}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${SESSION_MS / 1e3}${secure(req)}`;
19896
- }
19897
- const clearCookie = (req) => `${AUTH_COOKIE}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0${secure(req)}`;
19898
- function renew(req, now = Date.now()) {
19899
- if (!enabled()) return null;
19900
- const s = session(req, now);
19901
- if (s === null || s.exp - now > SESSION_MS - RENEW_MS) return null;
19902
- return { cookie: sessionCookie(req, now, s.since), expiresAt: now + SESSION_MS };
19903
- }
19904
- function relying(req) {
19905
- const origin = String(req.headers.origin || "");
19906
- const { hostname } = new URL(origin);
19907
- return { origin, rpID: hostname };
19908
- }
19909
- const challenges = /* @__PURE__ */ new Map();
19910
- const challengeKey = (kind, client) => `${kind}:${client}`;
19911
- const issueChallenge = (req, kind, value) => {
19912
- const client = crypto.randomBytes(24).toString("base64url");
19913
- for (const [key, challenge] of challenges) if (challenge.exp < Date.now()) challenges.delete(key);
19914
- challenges.set(challengeKey(kind, client), { value, exp: Date.now() + CHALLENGE_MS });
19915
- return `${CHALLENGE_COOKIE}=${client}; Path=/api/auth/; HttpOnly; SameSite=Strict; Max-Age=${CHALLENGE_MS / 1e3}${secure(req)}`;
19916
- };
19917
- const takeChallenge = (req, kind) => {
19918
- const client = challengeClient(req);
19919
- const key = challengeKey(kind, client || "");
19920
- const c = challenges.get(key);
19921
- challenges.delete(key);
19922
- if (!c || c.exp < Date.now()) throw new AuthError("challenge", "Challenge expired, try again");
19923
- return c.value;
19924
- };
19925
- async function registerOptions(req, body = {}) {
19926
- if (enabled() && !isUnlocked(req)) throw new AuthError("locked", "Unlock first");
19927
- checkBootstrap(body);
19928
- const { rpID } = relying(req);
19929
- const opts = await generateRegistrationOptions({
19930
- rpName: "wherdr",
19931
- rpID,
19932
- userName: passkeyUser,
19933
- userDisplayName: passkeyUser,
19934
- attestationType: "none",
19935
- excludeCredentials: db.credentials.map((c) => ({ id: c.id, transports: c.transports })),
19936
- authenticatorSelection: { residentKey: "preferred", userVerification: "required" }
19937
- });
19938
- return { ...opts, __cookie: issueChallenge(req, "reg", opts.challenge) };
19939
- }
19940
- async function registerVerify(req, body) {
19941
- if (enabled() && !isUnlocked(req)) throw new AuthError("locked", "Unlock first");
19942
- checkBootstrap(body);
19943
- const { origin, rpID } = relying(req);
19944
- const v = await verifyRegistrationResponse({
19945
- response: body.response,
19946
- expectedChallenge: takeChallenge(req, "reg"),
19947
- expectedOrigin: origin,
19948
- expectedRPID: rpID,
19949
- requireUserVerification: true
19950
- });
19951
- if (!v.verified || !v.registrationInfo) throw new AuthError("rejected", "Key rejected");
19952
- const { credential } = v.registrationInfo;
19953
- db.credentials.push({
19954
- id: credential.id,
19955
- publicKey: Buffer.from(credential.publicKey).toString("base64url"),
19956
- counter: credential.counter,
19957
- transports: credential.transports || [],
19958
- name: String(body.name || "Device").slice(0, 40),
19959
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
19960
- });
19961
- save();
19962
- log2(`lock: key registered (${db.credentials.length})`);
19963
- return { ok: true, __cookie: sessionCookie(req) };
19964
- }
19965
- async function loginOptions(req) {
19966
- const { rpID } = relying(req);
19967
- const opts = await generateAuthenticationOptions({
19968
- rpID,
19969
- userVerification: "required",
19970
- allowCredentials: db.credentials.map((c) => ({ id: c.id, transports: c.transports }))
19971
- });
19972
- return { ...opts, __cookie: issueChallenge(req, "auth", opts.challenge) };
19973
- }
19974
- async function loginVerify(req, body) {
19975
- const { origin, rpID } = relying(req);
19976
- const cred = db.credentials.find((c) => c.id === (body.response && body.response.id));
19977
- if (!cred) throw new AuthError("unknown_key", "Key unknown on this server");
19978
- const v = await verifyAuthenticationResponse({
19979
- response: body.response,
19980
- expectedChallenge: takeChallenge(req, "auth"),
19981
- expectedOrigin: origin,
19982
- expectedRPID: rpID,
19983
- requireUserVerification: true,
19984
- credential: { id: cred.id, publicKey: Buffer.from(cred.publicKey, "base64url"), counter: cred.counter, transports: cred.transports }
19985
- });
19986
- if (!v.verified) throw new AuthError("rejected", "Unlock rejected");
19987
- cred.counter = v.authenticationInfo.newCounter;
19988
- cred.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
19989
- save();
19990
- return { ok: true, __cookie: sessionCookie(req) };
20799
+ resolvedPath = `${path}/${resolvedPath}`;
20800
+ resolvedAbsolute = isAbsolute(path);
19991
20801
  }
19992
- function disable(req) {
19993
- if (!isUnlocked(req)) throw new AuthError("locked", "Unlock first");
19994
- db.credentials = [];
19995
- db.generation += 1;
19996
- save();
19997
- log2("lock disabled");
19998
- return { ok: true, __cookie: clearCookie(req) };
20802
+ resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
20803
+ if (resolvedAbsolute && !isAbsolute(resolvedPath)) {
20804
+ return `/${resolvedPath}`;
19999
20805
  }
20000
- function lockAll(req) {
20001
- if (!enabled() || !isUnlocked(req)) throw new AuthError("locked", "Unlock first");
20002
- db.generation += 1;
20003
- save();
20004
- log2("lock: all devices locked");
20005
- return { ok: true, __cookie: clearCookie(req) };
20806
+ return resolvedPath.length > 0 ? resolvedPath : ".";
20807
+ };
20808
+ function normalizeString(path, allowAboveRoot) {
20809
+ let res = "";
20810
+ let lastSegmentLength = 0;
20811
+ let lastSlash = -1;
20812
+ let dots = 0;
20813
+ let char = null;
20814
+ for (let index = 0; index <= path.length; ++index) {
20815
+ if (index < path.length) {
20816
+ char = path[index];
20817
+ } else if (char === "/") {
20818
+ break;
20819
+ } else {
20820
+ char = "/";
20821
+ }
20822
+ if (char === "/") {
20823
+ if (lastSlash === index - 1 || dots === 1) ; else if (dots === 2) {
20824
+ if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
20825
+ if (res.length > 2) {
20826
+ const lastSlashIndex = res.lastIndexOf("/");
20827
+ if (lastSlashIndex === -1) {
20828
+ res = "";
20829
+ lastSegmentLength = 0;
20830
+ } else {
20831
+ res = res.slice(0, lastSlashIndex);
20832
+ lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
20833
+ }
20834
+ lastSlash = index;
20835
+ dots = 0;
20836
+ continue;
20837
+ } else if (res.length > 0) {
20838
+ res = "";
20839
+ lastSegmentLength = 0;
20840
+ lastSlash = index;
20841
+ dots = 0;
20842
+ continue;
20843
+ }
20844
+ }
20845
+ if (allowAboveRoot) {
20846
+ res += res.length > 0 ? "/.." : "..";
20847
+ lastSegmentLength = 2;
20848
+ }
20849
+ } else {
20850
+ if (res.length > 0) {
20851
+ res += `/${path.slice(lastSlash + 1, index)}`;
20852
+ } else {
20853
+ res = path.slice(lastSlash + 1, index);
20854
+ }
20855
+ lastSegmentLength = index - lastSlash - 1;
20856
+ }
20857
+ lastSlash = index;
20858
+ dots = 0;
20859
+ } else if (char === "." && dots !== -1) {
20860
+ ++dots;
20861
+ } else {
20862
+ dots = -1;
20863
+ }
20006
20864
  }
20007
- function setMaxSession(req, days) {
20008
- if (!enabled() || !isUnlocked(req)) throw new AuthError("locked", "Unlock first");
20009
- if (!isMaxSessionDays(days)) throw new AuthError("invalid", "Invalid duration");
20010
- db.maxSessionDays = days;
20011
- save();
20012
- return { ok: true, maxSessionDays: days };
20865
+ return res;
20866
+ }
20867
+ const isAbsolute = function(p) {
20868
+ return _IS_ABSOLUTE_RE.test(p);
20869
+ };
20870
+ const relative = function(from, to) {
20871
+ const _from = resolve(from).replace(_ROOT_FOLDER_RE, "$1").split("/");
20872
+ const _to = resolve(to).replace(_ROOT_FOLDER_RE, "$1").split("/");
20873
+ if (_to[0][1] === ":" && _from[0][1] === ":" && _from[0] !== _to[0]) {
20874
+ return _to.join("/");
20013
20875
  }
20014
- function status(req, { resume = false, now = Date.now() } = {}) {
20015
- var _a, _b;
20016
- const devices = db.credentials.map((c) => ({ name: c.name, createdAt: c.createdAt, lastUsed: c.lastUsed || null }));
20017
- if (!enabled()) return { enabled: false, unlocked: true, devices };
20018
- const s = session(req, now);
20019
- const deadline = s ? s.since + maxSessionMs() : 0;
20020
- if (!s || resume && now >= deadline) {
20021
- return { enabled: true, unlocked: false, expiresAt: null, now, devices, ...s ? { __cookie: clearCookie(req) } : {} };
20876
+ const _fromCopy = [..._from];
20877
+ for (const segment of _fromCopy) {
20878
+ if (_to[0] !== segment) {
20879
+ break;
20022
20880
  }
20023
- const renewed = renew(req, now);
20024
- return {
20025
- enabled: true,
20026
- unlocked: true,
20027
- expiresAt: (_a = renewed == null ? void 0 : renewed.expiresAt) != null ? _a : s.exp,
20028
- deadline,
20029
- now,
20030
- maxSessionDays: (_b = db.maxSessionDays) != null ? _b : DEFAULT_MAX_SESSION_DAYS,
20031
- devices,
20032
- ...renewed ? { __cookie: renewed.cookie } : {}
20033
- };
20881
+ _from.shift();
20882
+ _to.shift();
20034
20883
  }
20035
- return {
20036
- isUnlocked,
20037
- status,
20038
- renew,
20039
- registerOptions,
20040
- registerVerify,
20041
- loginOptions,
20042
- loginVerify,
20043
- disable,
20044
- lockAll,
20045
- setMaxSession,
20046
- lock: (req) => ({ ok: true, __cookie: clearCookie(req) }),
20047
- // For tests.
20048
- _sessionCookie: sessionCookie,
20049
- _db: () => db,
20050
- _bootstrapToken: () => bootstrapToken,
20051
- _takeChallenge: takeChallenge
20052
- };
20884
+ return [..._from.map(() => ".."), ..._to].join("/");
20885
+ };
20886
+ const dirname = function(p) {
20887
+ const segments = normalizeWindowsPath(p).replace(/\/$/, "").split("/").slice(0, -1);
20888
+ if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) {
20889
+ segments[0] += "/";
20890
+ }
20891
+ return segments.join("/") || (isAbsolute(p) ? "/" : ".");
20892
+ };
20893
+
20894
+ function readAsset (id) {
20895
+ const serverDir = dirname(fileURLToPath(globalThis._importMeta_.url));
20896
+ return promises.readFile(resolve(serverDir, assets[id].path))
20053
20897
  }
20054
20898
 
20055
- const auth = createAuth({ dataDir: DATA_DIR, log, passkeyUser: PASSKEY_USER });
20056
- const reqOf = (event) => ({ headers: getRequestHeaders(event) });
20057
- function sameOrigin(headers) {
20058
- const o = headers.origin;
20059
- if (!o) return false;
20060
- try {
20061
- return new URL(o).host === headers.host;
20062
- } catch {
20063
- return false;
20899
+ const publicAssetBases = {"/_nuxt/builds/meta/":{"maxAge":31536000},"/_nuxt/builds/":{"maxAge":1},"/_nuxt/":{"maxAge":31536000}};
20900
+
20901
+ function isPublicAssetURL(id = '') {
20902
+ if (assets[id]) {
20903
+ return true
20064
20904
  }
20905
+ for (const base in publicAssetBases) {
20906
+ if (id.startsWith(base)) { return true }
20907
+ }
20908
+ return false
20065
20909
  }
20066
- const BODY_MAX = 256 * 1024;
20067
- function sendError(event, status, body) {
20068
- setResponseStatus(event, status);
20069
- setResponseHeader(event, "cache-control", "no-store");
20070
- return body;
20910
+
20911
+ function getAsset (id) {
20912
+ return assets[id]
20071
20913
  }
20072
- function defineApi(fn, opts = {}) {
20073
- return defineEventHandler(async (event) => {
20074
- const url = getRequestURL(event);
20075
- try {
20076
- let body = {};
20077
- if (event.method === "POST") {
20078
- const headers = getRequestHeaders(event);
20079
- const ctype = String(headers["content-type"] || "");
20080
- const typeOk = opts.raw ? opts.raw.test(ctype) : ctype.startsWith("application/json");
20081
- if (!sameOrigin(headers) || !typeOk) return sendError(event, 403, { error: "Origin refused" });
20082
- const max = opts.raw ? opts.max || 20 * 1024 * 1024 : BODY_MAX;
20083
- const tooLarge = opts.raw ? opts.tooLarge || "Image too large (20 MB max)" : "Request too large";
20084
- if (Number(headers["content-length"] || 0) > max) throw new HerdrError("too_large", tooLarge);
20085
- const raw = await readRawBody(event, false);
20086
- if (raw && raw.length > max) throw new HerdrError("too_large", tooLarge);
20087
- if (opts.raw) body = { data: raw || Buffer.alloc(0), ctype };
20088
- else {
20089
- try {
20090
- body = raw && raw.length ? JSON.parse(raw.toString("utf8")) : {};
20091
- } catch {
20092
- throw new HerdrError("bad_json", "Invalid JSON");
20093
- }
20094
- }
20095
- }
20096
- const out = await fn(event, body);
20097
- setResponseHeader(event, "cache-control", "no-store");
20098
- if (out && typeof out === "object" && "__cookie" in out) {
20099
- const o = out;
20100
- setResponseHeader(event, "set-cookie", String(o.__cookie));
20101
- delete o.__cookie;
20914
+
20915
+ const METHODS = /* @__PURE__ */ new Set(["HEAD", "GET"]);
20916
+ const EncodingMap = { gzip: ".gz", br: ".br" };
20917
+ const _uN03ln = eventHandler((event) => {
20918
+ if (event.method && !METHODS.has(event.method)) {
20919
+ return;
20920
+ }
20921
+ let id = decodePath(
20922
+ withLeadingSlash(withoutTrailingSlash(parseURL(event.path).pathname))
20923
+ );
20924
+ let asset;
20925
+ const encodingHeader = String(
20926
+ getRequestHeader(event, "accept-encoding") || ""
20927
+ );
20928
+ const encodings = [
20929
+ ...encodingHeader.split(",").map((e) => EncodingMap[e.trim()]).filter(Boolean).sort(),
20930
+ ""
20931
+ ];
20932
+ for (const encoding of encodings) {
20933
+ for (const _id of [id + encoding, joinURL(id, "index.html" + encoding)]) {
20934
+ const _asset = getAsset(_id);
20935
+ if (_asset) {
20936
+ asset = _asset;
20937
+ id = _id;
20938
+ break;
20102
20939
  }
20103
- return out;
20104
- } catch (e) {
20105
- const err = e;
20106
- const known = e instanceof HerdrError || url.pathname.startsWith("/api/auth/") && Boolean(err.code);
20107
- const code = known ? err.code : "internal";
20108
- if (!known) log("error", event.method, url.pathname, e);
20109
- const status = err.code === "unreachable" ? 503 : err.code === "locked" ? 401 : 400;
20110
- return sendError(event, status, { error: err.message, code });
20111
20940
  }
20112
- });
20113
- }
20114
- function sendImage(event, img) {
20115
- setResponseHeader(event, "content-type", img.type);
20116
- setResponseHeader(event, "cache-control", "private, max-age=86400");
20117
- setResponseHeader(event, "x-content-type-options", "nosniff");
20118
- setResponseHeader(event, "content-security-policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox");
20119
- return img.body;
20120
- }
20121
-
20122
- function hostname(value) {
20123
- try {
20124
- if (!value || /[\s/@?#]/.test(value)) return null;
20125
- const url = new URL(`http://${value}`);
20126
- if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) return null;
20127
- return url.hostname.replace(/^\[|\]$/g, "").toLowerCase().replace(/\.$/, "");
20128
- } catch {
20129
- return null;
20130
20941
  }
20131
- }
20132
- function allowedHosts(appUrl2 = process.env.APP_URL || "", extra = process.env.HERDR_WEB_ALLOWED_HOSTS || "", interfaces = os.networkInterfaces()) {
20133
- const names = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", os.hostname().toLowerCase()]);
20134
- try {
20135
- if (appUrl2) names.add(new URL(appUrl2).hostname.toLowerCase().replace(/^\[|\]$/g, ""));
20136
- } catch {
20942
+ if (!asset) {
20943
+ if (isPublicAssetURL(id)) {
20944
+ removeResponseHeader(event, "Cache-Control");
20945
+ throw createError$1({ statusCode: 404 });
20946
+ }
20947
+ return;
20137
20948
  }
20138
- for (const addresses of Object.values(interfaces)) {
20139
- for (const address of addresses || []) names.add(address.address.toLowerCase().replace(/%.+$/, ""));
20949
+ if (asset.encoding !== void 0) {
20950
+ appendResponseHeader(event, "Vary", "Accept-Encoding");
20140
20951
  }
20141
- for (const item of extra.split(",")) {
20142
- const name = hostname(item.trim());
20143
- if (name) names.add(name);
20952
+ const ifNotMatch = getRequestHeader(event, "if-none-match") === asset.etag;
20953
+ if (ifNotMatch) {
20954
+ setResponseStatus(event, 304, "Not Modified");
20955
+ return "";
20144
20956
  }
20145
- return names;
20146
- }
20147
- function hostAllowed(host, names = allowedHosts()) {
20148
- const name = hostname(host || "");
20149
- return Boolean(name && names.has(name));
20150
- }
20151
- function hostRefusalIsHtml(path, accept) {
20152
- return !path.startsWith("/api/") && /\btext\/html\b/i.test(Array.isArray(accept) ? accept.join(",") : accept || "");
20153
- }
20154
- const HOST_REFUSED_PAGE = `<!doctype html>
20155
- <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
20156
- <meta name="robots" content="noindex"><title>wherdr \xB7 address not enabled</title>
20157
- <style>
20158
- :root{color-scheme:dark}*{box-sizing:border-box}
20159
- body{margin:0;min-height:100vh;display:grid;place-items:center;background:#0d0f12;color:#e8eaed;font:15px/1.5 Inter,system-ui,-apple-system,sans-serif;padding:24px max(16px,env(safe-area-inset-right)) 24px max(16px,env(safe-area-inset-left))}
20160
- main{max-width:440px;border:1px solid #3a3f47;padding:24px}
20161
- .tag{font:11px/1 "JetBrains Mono",ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;color:#9aa0a8;margin:0 0 16px}
20162
- h1{font:600 20px/1.25 Archivo,system-ui,sans-serif;margin:0 0 8px}
20163
- p{margin:0 0 8px}hr{border:0;border-top:1px solid #3a3f47;margin:20px 0}
20164
- </style></head><body><main>
20165
- <p class="tag">wherdr \xB7 403</p>
20166
- <h1>This address isn't enabled yet.</h1>
20167
- <p>On the computer, open wherdr \u2192 Settings \u203A Phone (or press Check), then reload.</p>
20168
- <hr>
20169
- <h1 lang="fr">Cette adresse n\u2019est pas encore activ\xE9e.</h1>
20170
- <p lang="fr">Sur l\u2019ordinateur, ouvre wherdr \u2192 R\xE9glages \u203A T\xE9l\xE9phone (ou appuie sur V\xE9rifier), puis recharge.</p>
20171
- </main></body></html>
20172
- `;
20173
- function crossSiteRequest(headers) {
20174
- const site = headers["sec-fetch-site"];
20175
- return typeof site === "string" && site !== "same-origin" && site !== "none";
20176
- }
20957
+ const ifModifiedSinceH = getRequestHeader(event, "if-modified-since");
20958
+ const mtimeDate = new Date(asset.mtime);
20959
+ if (ifModifiedSinceH && asset.mtime && new Date(ifModifiedSinceH) >= mtimeDate) {
20960
+ setResponseStatus(event, 304, "Not Modified");
20961
+ return "";
20962
+ }
20963
+ if (asset.type && !getResponseHeader(event, "Content-Type")) {
20964
+ setResponseHeader(event, "Content-Type", asset.type);
20965
+ }
20966
+ if (asset.etag && !getResponseHeader(event, "ETag")) {
20967
+ setResponseHeader(event, "ETag", asset.etag);
20968
+ }
20969
+ if (asset.mtime && !getResponseHeader(event, "Last-Modified")) {
20970
+ setResponseHeader(event, "Last-Modified", mtimeDate.toUTCString());
20971
+ }
20972
+ if (asset.encoding && !getResponseHeader(event, "Content-Encoding")) {
20973
+ setResponseHeader(event, "Content-Encoding", asset.encoding);
20974
+ }
20975
+ if (asset.size > 0 && !getResponseHeader(event, "Content-Length")) {
20976
+ setResponseHeader(event, "Content-Length", asset.size);
20977
+ }
20978
+ return readAsset(id);
20979
+ });
20177
20980
 
20178
- const needsUnlock = (p) => p.startsWith("/api/") && !p.startsWith("/api/auth/") || p.startsWith("/uploads/");
20179
- const _M8hgLp = defineEventHandler((event) => {
20981
+ const HOST_RECHECK_MS = 3e3;
20982
+ const needsUnlock = (p) => p.startsWith("/api/") && !p.startsWith("/api/auth/") && p !== "/api/health" || p.startsWith("/uploads/");
20983
+ const _M8hgLp = defineEventHandler(async (event) => {
20984
+ if (!hostAllowed(event.node.req.headers.host)) await adoptServed(HOST_RECHECK_MS);
20180
20985
  if (!hostAllowed(event.node.req.headers.host)) {
20181
20986
  if (hostRefusalIsHtml(event.path.split("?")[0], event.node.req.headers.accept)) {
20182
20987
  setResponseStatus(event, 403);
20183
20988
  setResponseHeader(event, "content-type", "text/html; charset=utf-8");
20184
20989
  setResponseHeader(event, "cache-control", "no-store");
20185
- return HOST_REFUSED_PAGE;
20990
+ return hostRefusedPage();
20186
20991
  }
20187
20992
  return sendError(event, 403, { error: "Host not allowed", code: "host" });
20188
20993
  }
@@ -20230,6 +21035,7 @@ const _lazy_2uy6Z9 = () => import('../routes/api/effort.post.mjs');
20230
21035
  const _lazy_7FhJOq = () => import('../routes/api/efforts.get.mjs');
20231
21036
  const _lazy_Wg_MPT = () => import('../routes/api/file.post.mjs');
20232
21037
  const _lazy_kEWyNy = () => import('../routes/api/gitroot.get.mjs');
21038
+ const _lazy_1Vqvr9 = () => import('../routes/api/health.get.mjs');
20233
21039
  const _lazy_U4OEhz = () => import('../routes/api/input.post.mjs');
20234
21040
  const _lazy_PDyMr9 = () => import('../routes/api/interrupt.post.mjs');
20235
21041
  const _lazy_mtPGaX = () => import('../routes/api/isgit.get.mjs');
@@ -20276,6 +21082,8 @@ const _lazy_TqhtLZ = () => import('../routes/api/space.post.mjs');
20276
21082
  const _lazy_oeuOKk = () => import('../routes/api/state.get.mjs');
20277
21083
  const _lazy_ecbrXA = () => import('../routes/api/unqueue.post.mjs');
20278
21084
  const _lazy_AUmWqe = () => import('../routes/api/update.get.mjs');
21085
+ const _lazy_1DfWY6 = () => import('../routes/api/update.post.mjs');
21086
+ const _lazy_lxIhkr = () => import('../routes/api/update/status.get.mjs');
20279
21087
  const _lazy_5f22vB = () => import('../routes/api/upload.post.mjs');
20280
21088
  const _lazy_9iwHBL = () => import('../routes/api/worktrees.get.mjs');
20281
21089
  const _lazy_IA8rJi = () => import('../routes/api/worktrees/remove.post.mjs');
@@ -20320,6 +21128,7 @@ const handlers = [
20320
21128
  { route: '/api/efforts', handler: _lazy_7FhJOq, lazy: true, middleware: false, method: "get" },
20321
21129
  { route: '/api/file', handler: _lazy_Wg_MPT, lazy: true, middleware: false, method: "post" },
20322
21130
  { route: '/api/gitroot', handler: _lazy_kEWyNy, lazy: true, middleware: false, method: "get" },
21131
+ { route: '/api/health', handler: _lazy_1Vqvr9, lazy: true, middleware: false, method: "get" },
20323
21132
  { route: '/api/input', handler: _lazy_U4OEhz, lazy: true, middleware: false, method: "post" },
20324
21133
  { route: '/api/interrupt', handler: _lazy_PDyMr9, lazy: true, middleware: false, method: "post" },
20325
21134
  { route: '/api/isgit', handler: _lazy_mtPGaX, lazy: true, middleware: false, method: "get" },
@@ -20366,6 +21175,8 @@ const handlers = [
20366
21175
  { route: '/api/state', handler: _lazy_oeuOKk, lazy: true, middleware: false, method: "get" },
20367
21176
  { route: '/api/unqueue', handler: _lazy_ecbrXA, lazy: true, middleware: false, method: "post" },
20368
21177
  { route: '/api/update', handler: _lazy_AUmWqe, lazy: true, middleware: false, method: "get" },
21178
+ { route: '/api/update', handler: _lazy_1DfWY6, lazy: true, middleware: false, method: "post" },
21179
+ { route: '/api/update/status', handler: _lazy_lxIhkr, lazy: true, middleware: false, method: "get" },
20369
21180
  { route: '/api/upload', handler: _lazy_5f22vB, lazy: true, middleware: false, method: "post" },
20370
21181
  { route: '/api/worktrees', handler: _lazy_9iwHBL, lazy: true, middleware: false, method: "get" },
20371
21182
  { route: '/api/worktrees/remove', handler: _lazy_IA8rJi, lazy: true, middleware: false, method: "post" },
@@ -20837,5 +21648,5 @@ setupGracefulShutdown(listener, nitroApp);
20837
21648
  }
20838
21649
  const nodeServer = {};
20839
21650
 
20840
- export { recentDirsWithKinds as $, ATTACH_MAX as A, keysFor as B, parseChoices as C, parseOmpField as D, sameQuestion as E, sleep as F, getMachine as G, HerdrError as H, installClaudeStatusline as I, codexStatus as J, codexMenuOpen as K, terminalUpdateLine as L, MACHINE_KEY_RE as M, terminalUpdateDone as N, restarts as O, PANE_RE as P, log as Q, withPaneLock as R, builtinCommands as S, slashCommands as T, UPLOAD_DIR as U, HOME as V, localMachine as W, readSubs as X, vapidPublicKey as Y, pushReady as Z, machineConfigs as _, defineApi as a, quietActive as a$, installedAgentKinds as a0, IN_DOCKER as a1, HOST_OPEN_TARGET as a2, HOST_OPEN_KEY as a3, PANE_RE$1 as a4, splitId as a5, HOST_OPEN_USER as a6, listDirs as a7, closePanel as a8, setEffort as a9, initVapid as aA, herdrOn as aB, getState as aC, HERDR_CHILD_ENV as aD, HERDR_BIN as aE, currentMachineSlots as aF, paneBaseMachine as aG, projectOf as aH, isProjectThread as aI, limitMachineKeys as aJ, validLimit as aK, readThreadLimits as aL, writeThreadLimits as aM, closeTrailingMention as aN, restarting as aO, hasHeld as aP, INPUT_STATES as aQ, inputVisible as aR, shouldHold as aS, sendPrompt as aT, agentPrompt as aU, isBusyError as aV, HOLD_AGENTS as aW, pendingPrompts as aX, parseQuiet as aY, writeGlobalQuiet as aZ, writeSubs as a_, refreshModel as aa, listEfforts as ab, saveUpload as ac, underHome as ad, isSlashCommand as ae, addQueued as af, interruptAgent as ag, takeBackFromClaude as ah, takeBack as ai, isGitRepo as aj, projectFolderKinds as ak, baseMachines as al, DATA_DIR as am, renameMachine as an, readMenu as ao, searchKeys as ap, findEntry as aq, stepToward as ar, clickMovesOnly as as, setModel as at, listModels as au, TOP as av, HOST_LABEL as aw, getRequestHeaders as ax, ENV_APP_URL as ay, APP_URL_FILE as az, screenChoices as b, readGlobalQuiet as b0, validPushSubscription as b1, replaceSubscription as b2, pushSend as b3, readQuotas as b4, retryQueued as b5, parseWaitScreen as b6, hasTranscript as b7, SEARCH_DEADLINE_MS as b8, SEARCH_MAX_RESULTS as b9, defineRenderHandler as bA, createError$1 as bB, destr as bC, getRouteRules as bD, relative as bE, joinURL as bF, getResponseStatusText as bG, getResponseStatus as bH, useNitroApp as bI, nodeServer as bJ, spaceTitle as ba, markSeen as bb, listSessions as bc, openSession as bd, PANE_DIRECTIONS as be, cancelQueued as bf, installMode as bg, updateCommand as bh, compareVersions as bi, allMachines as bj, recentDirs as bk, joinId as bl, useStorage as bm, setResponseHeader as bn, getRouterParam as bo, readUpload as bp, defineWebSocketHandler as bq, eventClients as br, getStateJson as bs, hostAllowed as bt, sameOrigin as bu, mirrorSize as bv, openTerm as bw, useRuntimeConfig as bx, encodePath as by, joinRelativeURL as bz, createAgent as c, defineEventHandler as d, watchScreen as e, findPane as f, choicesCache as g, herdr as h, getQuery as i, saveAttachment as j, auth as k, localMachineLabel as l, machineFor as m, machineOfPane as n, fmt as o, poll as p, transcripts as q, reqOf as r, sendError as s, takenBackOf as t, uploadNames as u, withoutTakenBack as v, withOmpTab as w, withSentText as x, currentModel as y, sendImage as z };
21651
+ export { recentDirsWithKinds as $, ATTACH_MAX as A, keysFor as B, parseChoices as C, parseOmpField as D, sameQuestion as E, sleep$1 as F, getMachine as G, HerdrError as H, installClaudeStatusline as I, codexStatus as J, codexMenuOpen as K, terminalUpdateLine as L, MACHINE_KEY_RE as M, terminalUpdateDone as N, restarts as O, PANE_RE as P, log as Q, withPaneLock as R, builtinCommands as S, slashCommands as T, UPLOAD_DIR as U, HOME as V, localMachine as W, readSubs as X, vapidPublicKey as Y, pushReady as Z, machineConfigs as _, defineApi as a, readGlobalQuiet as a$, installedAgentKinds as a0, IN_DOCKER as a1, HOST_OPEN_TARGET as a2, HOST_OPEN_KEY as a3, PANE_RE$1 as a4, splitId as a5, HOST_OPEN_USER as a6, listDirs as a7, closePanel as a8, setEffort as a9, herdrOn as aA, getState as aB, HERDR_CHILD_ENV as aC, HERDR_BIN as aD, currentMachineSlots as aE, paneBaseMachine as aF, projectOf as aG, isProjectThread as aH, limitMachineKeys as aI, validLimit as aJ, readThreadLimits as aK, writeThreadLimits as aL, closeTrailingMention as aM, restarting as aN, hasHeld as aO, INPUT_STATES as aP, inputVisible as aQ, shouldHold as aR, sendPrompt as aS, agentPrompt as aT, isBusyError as aU, HOLD_AGENTS as aV, pendingPrompts as aW, parseQuiet as aX, writeGlobalQuiet as aY, writeSubs as aZ, quietActive as a_, refreshModel as aa, listEfforts as ab, saveUpload as ac, underHome as ad, isSlashCommand as ae, addQueued as af, interruptAgent as ag, takeBackFromClaude as ah, takeBack as ai, isGitRepo as aj, projectFolderKinds as ak, baseMachines as al, DATA_DIR as am, renameMachine as an, readMenu as ao, searchKeys as ap, findEntry as aq, stepToward as ar, clickMovesOnly as as, setModel as at, listModels as au, TOP as av, HOST_LABEL as aw, phoneAccess as ax, phoneStatus as ay, phoneAction as az, screenChoices as b, validPushSubscription as b0, replaceSubscription as b1, pushSend as b2, readQuotas as b3, retryQueued as b4, parseWaitScreen as b5, settingsTabs as b6, tabsOfRow as b7, hasTranscript as b8, SEARCH_DEADLINE_MS as b9, useRuntimeConfig as bA, encodePath as bB, joinRelativeURL as bC, defineRenderHandler as bD, createError$1 as bE, destr as bF, getRouteRules as bG, relative as bH, joinURL as bI, getResponseStatusText as bJ, getResponseStatus as bK, useNitroApp as bL, nodeServer as bM, SEARCH_MAX_RESULTS as ba, spaceTitle as bb, markSeen as bc, listSessions as bd, openSession as be, PANE_DIRECTIONS as bf, cancelQueued as bg, installMode as bh, oneTapMode as bi, updateCommand as bj, compareVersions as bk, updateRunning as bl, allMachines as bm, recentDirs as bn, joinId as bo, useStorage as bp, setResponseHeader as bq, getRouterParam as br, readUpload as bs, defineWebSocketHandler as bt, eventClients as bu, getStateJson as bv, hostAllowed as bw, sameOrigin as bx, mirrorSize as by, openTerm as bz, createAgent as c, defineEventHandler as d, watchScreen as e, findPane as f, choicesCache as g, herdr as h, getQuery as i, saveAttachment as j, auth as k, localMachineLabel as l, machineFor as m, machineOfPane as n, fmt as o, poll as p, transcripts as q, reqOf as r, sendError as s, takenBackOf as t, uploadNames as u, withoutTakenBack as v, withOmpTab as w, withSentText as x, currentModel as y, sendImage as z };
20841
21652
  //# sourceMappingURL=nitro.mjs.map