wherdr 1.3.1 → 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 (136) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/{WLQjK8uJ.js → 8ksjcuDu.js} +51 -41
  3. package/.output/public/_nuxt/B7QP1TFs.js +23 -0
  4. package/.output/public/_nuxt/{CiYar6hg.js → BOpBIziZ.js} +2 -2
  5. package/.output/public/_nuxt/BWXdFfeg.js +17 -0
  6. package/.output/public/_nuxt/COFCNFpG.js +83 -0
  7. package/.output/public/_nuxt/{C2T9fQ4f.js → Cd8j9O5w.js} +1 -1
  8. package/.output/public/_nuxt/{C12eXl5z.js → CkZ8t6uB.js} +1 -1
  9. package/.output/public/_nuxt/{CctS2Xn6.js → D4Yz4rat.js} +4 -4
  10. package/.output/public/_nuxt/{B6qBWHWB.js → D7sxNXx1.js} +1 -1
  11. package/.output/public/_nuxt/DViwOclv.js +1 -0
  12. package/.output/public/_nuxt/DoD7Qb5P.js +30 -0
  13. package/.output/public/_nuxt/{BMIe8p_3.js → DyBZDSCV.js} +1 -1
  14. package/.output/public/_nuxt/builds/latest.json +1 -1
  15. package/.output/public/_nuxt/builds/meta/7afbf062-5369-4002-b20d-7a582ff6e0f1.json +1 -0
  16. package/.output/public/_nuxt/{entry.cKR16VqG.css → entry.Crt1r4l3.css} +1 -1
  17. package/.output/public/_nuxt/hePW80VL.js +1 -0
  18. package/.output/public/sw.js +1 -1
  19. package/.output/server/chunks/_/branding.mjs +1 -1
  20. package/.output/server/chunks/_/package.mjs +96 -0
  21. package/.output/server/chunks/_/plugins.mjs +1 -1
  22. package/.output/server/chunks/_/projectBoard.mjs +1 -1
  23. package/.output/server/chunks/_/projectsPlugin.mjs +1 -1
  24. package/.output/server/chunks/_/selfupdate.mjs +82 -0
  25. package/.output/server/chunks/_/updates.mjs +50 -0
  26. package/.output/server/chunks/_/worktrees.mjs +1 -1
  27. package/.output/server/chunks/_/ws.mjs +1 -1
  28. package/.output/server/chunks/nitro/nitro.mjs +1670 -899
  29. package/.output/server/chunks/raw/herdr-plugin.mjs +1 -1
  30. package/.output/server/chunks/routes/api/_..._.mjs +5 -1
  31. package/.output/server/chunks/routes/api/agents.post.mjs +5 -1
  32. package/.output/server/chunks/routes/api/ask-tab.post.mjs +5 -1
  33. package/.output/server/chunks/routes/api/attach.post.mjs +5 -1
  34. package/.output/server/chunks/routes/api/auth/disable.post.mjs +5 -1
  35. package/.output/server/chunks/routes/api/auth/lock-all.post.mjs +5 -1
  36. package/.output/server/chunks/routes/api/auth/lock.post.mjs +5 -1
  37. package/.output/server/chunks/routes/api/auth/login/options.post.mjs +5 -1
  38. package/.output/server/chunks/routes/api/auth/login/verify.post.mjs +5 -1
  39. package/.output/server/chunks/routes/api/auth/max-session.post.mjs +5 -1
  40. package/.output/server/chunks/routes/api/auth/register/options.post.mjs +5 -1
  41. package/.output/server/chunks/routes/api/auth/register/verify.post.mjs +5 -1
  42. package/.output/server/chunks/routes/api/auth/status.get.mjs +5 -1
  43. package/.output/server/chunks/routes/api/changes.get.mjs +5 -1
  44. package/.output/server/chunks/routes/api/chat/image.get.mjs +5 -1
  45. package/.output/server/chunks/routes/api/chat.get.mjs +5 -1
  46. package/.output/server/chunks/routes/api/choose.post.mjs +5 -1
  47. package/.output/server/chunks/routes/api/claude-statusline.post.mjs +5 -1
  48. package/.output/server/chunks/routes/api/close/status.get.mjs +5 -1
  49. package/.output/server/chunks/routes/api/close.post.mjs +5 -1
  50. package/.output/server/chunks/routes/api/codex-update.post.mjs +5 -1
  51. package/.output/server/chunks/routes/api/commands.get.mjs +5 -1
  52. package/.output/server/chunks/routes/api/config.get.mjs +5 -1
  53. package/.output/server/chunks/routes/api/dirs.get.mjs +5 -1
  54. package/.output/server/chunks/routes/api/dismiss.post.mjs +5 -1
  55. package/.output/server/chunks/routes/api/effort.post.mjs +5 -1
  56. package/.output/server/chunks/routes/api/efforts.get.mjs +5 -1
  57. package/.output/server/chunks/routes/api/file.post.mjs +5 -1
  58. package/.output/server/chunks/routes/api/gitroot.get.mjs +5 -1
  59. package/.output/server/chunks/routes/api/health.get.mjs +36 -0
  60. package/.output/server/chunks/routes/api/index.get.mjs +5 -1
  61. package/.output/server/chunks/routes/api/input.post.mjs +5 -1
  62. package/.output/server/chunks/routes/api/interrupt.post.mjs +5 -1
  63. package/.output/server/chunks/routes/api/isgit.get.mjs +5 -1
  64. package/.output/server/chunks/routes/api/machine/awake.get.mjs +5 -1
  65. package/.output/server/chunks/routes/api/machine/awake.post.mjs +5 -1
  66. package/.output/server/chunks/routes/api/machine/order.get.mjs +5 -1
  67. package/.output/server/chunks/routes/api/machine/order.post.mjs +5 -1
  68. package/.output/server/chunks/routes/api/machine/rename.post.mjs +5 -1
  69. package/.output/server/chunks/routes/api/menu.post.mjs +5 -1
  70. package/.output/server/chunks/routes/api/model.post.mjs +5 -1
  71. package/.output/server/chunks/routes/api/models.get.mjs +5 -1
  72. package/.output/server/chunks/routes/api/nav.post.mjs +5 -1
  73. package/.output/server/chunks/routes/api/onboarding.get.mjs +5 -1
  74. package/.output/server/chunks/routes/api/onboarding.post.mjs +5 -1
  75. package/.output/server/chunks/routes/api/phone.get.mjs +5 -6
  76. package/.output/server/chunks/routes/api/phone.post.mjs +5 -6
  77. package/.output/server/chunks/routes/api/plugins/actions.get.mjs +5 -1
  78. package/.output/server/chunks/routes/api/plugins/invoke.post.mjs +5 -1
  79. package/.output/server/chunks/routes/api/plugins/profiles.get.mjs +5 -1
  80. package/.output/server/chunks/routes/api/plugins/projects.get.mjs +5 -1
  81. package/.output/server/chunks/routes/api/plugins/projects.post.mjs +5 -1
  82. package/.output/server/chunks/routes/api/plugins/thread-limits.get.mjs +6 -2
  83. package/.output/server/chunks/routes/api/plugins/thread-limits.post.mjs +6 -2
  84. package/.output/server/chunks/routes/api/project/report.get.mjs +5 -1
  85. package/.output/server/chunks/routes/api/prompt.post.mjs +6 -2
  86. package/.output/server/chunks/routes/api/push/quiet.post.mjs +6 -2
  87. package/.output/server/chunks/routes/api/push/subscribe.post.mjs +6 -2
  88. package/.output/server/chunks/routes/api/push/test.post.mjs +6 -2
  89. package/.output/server/chunks/routes/api/push/unsubscribe.post.mjs +6 -2
  90. package/.output/server/chunks/routes/api/quotas.get.mjs +6 -2
  91. package/.output/server/chunks/routes/api/rename.post.mjs +5 -1
  92. package/.output/server/chunks/routes/api/requeue.post.mjs +6 -2
  93. package/.output/server/chunks/routes/api/restart.get.mjs +5 -1
  94. package/.output/server/chunks/routes/api/restart.post.mjs +5 -1
  95. package/.output/server/chunks/routes/api/reveal.post.mjs +5 -1
  96. package/.output/server/chunks/routes/api/screen-key.post.mjs +6 -2
  97. package/.output/server/chunks/routes/api/screen.get.mjs +6 -2
  98. package/.output/server/chunks/routes/api/search.get.mjs +6 -2
  99. package/.output/server/chunks/routes/api/seen.post.mjs +6 -2
  100. package/.output/server/chunks/routes/api/sessions.get.mjs +6 -2
  101. package/.output/server/chunks/routes/api/sessions.post.mjs +6 -2
  102. package/.output/server/chunks/routes/api/space.post.mjs +6 -2
  103. package/.output/server/chunks/routes/api/state.get.mjs +6 -2
  104. package/.output/server/chunks/routes/api/unqueue.post.mjs +6 -2
  105. package/.output/server/chunks/routes/api/update/status.get.mjs +38 -0
  106. package/.output/server/chunks/routes/api/update.get.mjs +11 -50
  107. package/.output/server/chunks/routes/api/update.post.mjs +41 -0
  108. package/.output/server/chunks/routes/api/upload.post.mjs +5 -1
  109. package/.output/server/chunks/routes/api/worktrees/remove.post.mjs +5 -1
  110. package/.output/server/chunks/routes/api/worktrees.get.mjs +5 -1
  111. package/.output/server/chunks/routes/favicon.ico.get.mjs +5 -1
  112. package/.output/server/chunks/routes/icons/_name_.get.mjs +6 -2
  113. package/.output/server/chunks/routes/renderer.mjs +7 -3
  114. package/.output/server/chunks/routes/uploads/_...name_.get.mjs +6 -2
  115. package/.output/server/chunks/routes/ws/_..._.mjs +6 -2
  116. package/.output/server/chunks/routes/ws/events.mjs +6 -2
  117. package/.output/server/chunks/routes/ws/mirror.mjs +6 -2
  118. package/.output/server/chunks/routes/ws/term.mjs +8 -4
  119. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  120. package/.output/server/index.mjs +6 -2
  121. package/.output/server/package.json +1 -1
  122. package/README.md +110 -23
  123. package/bin/lib/commands.mjs +42 -8
  124. package/bin/lib/core.mjs +17 -2
  125. package/bin/lib/tailnet.d.mts +12 -3
  126. package/bin/lib/tailnet.mjs +103 -25
  127. package/bin/lib/updater.mjs +267 -0
  128. package/package.json +2 -1
  129. package/.output/public/_nuxt/BpQH8Ho1.js +0 -1
  130. package/.output/public/_nuxt/BrAeJIZR.js +0 -30
  131. package/.output/public/_nuxt/CYxHnMNH.js +0 -83
  132. package/.output/public/_nuxt/DLLQgP0d.js +0 -1
  133. package/.output/public/_nuxt/Dwh-l2rQ.js +0 -17
  134. package/.output/public/_nuxt/builds/meta/6466b0ab-7935-460c-abdf-2ea632dae6d0.json +0 -1
  135. package/.output/public/_nuxt/whFLy_Ne.js +0 -23
  136. 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,83 +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-08T11:43:41.732Z"}
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-08T11:43:41.732Z"}
8653
+ meta: {"type":"text/plain; charset=utf-8","etag":"\"cf5-BDqHg2xDFu+6dg84HBWtyBtrEoU\"","mtime":"2026-10-10T00:23:41.677Z"}
8650
8654
  },
8651
8655
  ["scripts:bump-homebrew.sh"]: {
8652
8656
  import: () => import('../raw/bump-homebrew.mjs').then(r => r.default || r),
8653
- meta: {"type":"application/x-sh","etag":"\"61b-suAgb/6azMZO/UmEjog8x0wZsQA\"","mtime":"2026-10-08T11:43:41.732Z"}
8657
+ meta: {"type":"application/x-sh","etag":"\"61b-suAgb/6azMZO/UmEjog8x0wZsQA\"","mtime":"2026-10-10T00:23:41.677Z"}
8654
8658
  },
8655
8659
  ["scripts:check-leaks.mjs"]: {
8656
8660
  import: () => import('../raw/check-leaks.mjs').then(r => r.default || r),
8657
- meta: {"type":"text/javascript; charset=utf-8","etag":"\"35b3-41HZueHfw7xuicx4q83qwdTEpbc\"","mtime":"2026-10-08T11:43:41.732Z"}
8661
+ meta: {"type":"text/javascript; charset=utf-8","etag":"\"35b3-41HZueHfw7xuicx4q83qwdTEpbc\"","mtime":"2026-10-10T00:23:41.677Z"}
8658
8662
  },
8659
8663
  ["scripts:dev.wherdr.open.plist"]: {
8660
8664
  import: () => import('../raw/dev.wherdr.open.mjs').then(r => r.default || r),
8661
- meta: {"type":"text/plain; charset=utf-8","etag":"\"1de-12VO2xq63mciP26rEzRh3cGfEkI\"","mtime":"2026-10-08T11:43:41.732Z"}
8665
+ meta: {"type":"text/plain; charset=utf-8","etag":"\"1de-12VO2xq63mciP26rEzRh3cGfEkI\"","mtime":"2026-10-10T00:23:41.677Z"}
8662
8666
  },
8663
8667
  ["scripts:docker-entrypoint.sh"]: {
8664
8668
  import: () => import('../raw/docker-entrypoint.mjs').then(r => r.default || r),
8665
- meta: {"type":"application/x-sh","etag":"\"3f3-8om3/mMriIIJYO+YcdXNJsv8tkI\"","mtime":"2026-10-08T11:43:41.732Z"}
8669
+ meta: {"type":"application/x-sh","etag":"\"3f3-8om3/mMriIIJYO+YcdXNJsv8tkI\"","mtime":"2026-10-10T00:23:41.677Z"}
8666
8670
  },
8667
8671
  ["scripts:herdr-plugin.sh"]: {
8668
8672
  import: () => import('../raw/herdr-plugin.mjs').then(r => r.default || r),
8669
- meta: {"type":"application/x-sh","etag":"\"89ea-WMEExeYOqeXl5Lacbu/zP6mb8fY\"","mtime":"2026-10-08T11:43:41.732Z"}
8673
+ meta: {"type":"application/x-sh","etag":"\"994f-jk8Je2eDp1XdX/Sk4mufv2vrJOc\"","mtime":"2026-10-10T00:23:41.677Z"}
8670
8674
  },
8671
8675
  ["scripts:install-claude-statusline.sh"]: {
8672
8676
  import: () => import('../raw/install-claude-statusline.mjs').then(r => r.default || r),
8673
- meta: {"type":"application/x-sh","etag":"\"db6-p7KGlvbZ+Qjt+JiT5yVtU4w3kfg\"","mtime":"2026-10-08T11:43:41.732Z"}
8677
+ meta: {"type":"application/x-sh","etag":"\"db6-p7KGlvbZ+Qjt+JiT5yVtU4w3kfg\"","mtime":"2026-10-10T00:23:41.677Z"}
8674
8678
  },
8675
8679
  ["scripts:install-host-open.sh"]: {
8676
8680
  import: () => import('../raw/install-host-open.mjs').then(r => r.default || r),
8677
- meta: {"type":"application/x-sh","etag":"\"103a-/zplHeQSS471/Vz3quwajHmzFQs\"","mtime":"2026-10-08T11:43:41.732Z"}
8681
+ meta: {"type":"application/x-sh","etag":"\"103a-/zplHeQSS471/Vz3quwajHmzFQs\"","mtime":"2026-10-10T00:23:41.677Z"}
8678
8682
  },
8679
8683
  ["scripts:pack-output.mjs"]: {
8680
8684
  import: () => import('../raw/pack-output.mjs').then(r => r.default || r),
8681
- meta: {"type":"text/javascript; charset=utf-8","etag":"\"54b-eU4eNNIxIecG1kwaLdtiTYODSSI\"","mtime":"2026-10-08T11:43:41.732Z"}
8685
+ meta: {"type":"text/javascript; charset=utf-8","etag":"\"54b-eU4eNNIxIecG1kwaLdtiTYODSSI\"","mtime":"2026-10-10T00:23:41.677Z"}
8682
8686
  },
8683
8687
  ["scripts:reveal.sh"]: {
8684
8688
  import: () => import('../raw/reveal.mjs').then(r => r.default || r),
8685
- meta: {"type":"application/x-sh","etag":"\"72e-h/LTEU7exXdiR3GKqhj71UOwAqQ\"","mtime":"2026-10-08T11:43:41.732Z"}
8689
+ meta: {"type":"application/x-sh","etag":"\"72e-h/LTEU7exXdiR3GKqhj71UOwAqQ\"","mtime":"2026-10-10T00:23:41.677Z"}
8686
8690
  },
8687
8691
  ["scripts:wherdr-open.sh"]: {
8688
8692
  import: () => import('../raw/wherdr-open.mjs').then(r => r.default || r),
8689
- meta: {"type":"application/x-sh","etag":"\"b5b-U5OyQk3hziU9KiUyB/1FT5TkOxk\"","mtime":"2026-10-08T11:43:41.732Z"}
8693
+ meta: {"type":"application/x-sh","etag":"\"b5b-U5OyQk3hziU9KiUyB/1FT5TkOxk\"","mtime":"2026-10-10T00:23:41.677Z"}
8690
8694
  },
8691
8695
  ["server:branding:favicon.ico"]: {
8692
8696
  import: () => import('../raw/favicon.mjs').then(r => r.default || r),
8693
- meta: {"type":"image/vnd.microsoft.icon","etag":"\"3aee-Oem7XTWv+9HEwv9u/3K5QcIjS3E\"","mtime":"2026-10-08T11:43:41.734Z"}
8697
+ meta: {"type":"image/vnd.microsoft.icon","etag":"\"3aee-Oem7XTWv+9HEwv9u/3K5QcIjS3E\"","mtime":"2026-10-10T00:23:41.681Z"}
8694
8698
  },
8695
8699
  ["server:branding:icons:apple-touch-icon.png"]: {
8696
8700
  import: () => import('../raw/apple-touch-icon.mjs').then(r => r.default || r),
8697
- meta: {"type":"image/png","etag":"\"1f8-AVRrBnAhalXrGkFPGdvl0fM7DH8\"","mtime":"2026-10-08T11:43:41.734Z"}
8701
+ meta: {"type":"image/png","etag":"\"1f8-AVRrBnAhalXrGkFPGdvl0fM7DH8\"","mtime":"2026-10-10T00:23:41.681Z"}
8698
8702
  },
8699
8703
  ["server:branding:icons:favicon-32.png"]: {
8700
8704
  import: () => import('../raw/favicon-32.mjs').then(r => r.default || r),
8701
- meta: {"type":"image/png","etag":"\"a3-hAEc+Ymq1aYS3kFLyZK/H18Pge8\"","mtime":"2026-10-08T11:43:41.734Z"}
8705
+ meta: {"type":"image/png","etag":"\"a3-hAEc+Ymq1aYS3kFLyZK/H18Pge8\"","mtime":"2026-10-10T00:23:41.681Z"}
8702
8706
  },
8703
8707
  ["server:branding:icons:icon-192.png"]: {
8704
8708
  import: () => import('../raw/icon-192.mjs').then(r => r.default || r),
8705
- meta: {"type":"image/png","etag":"\"222-kioUoPaSCUaRhKKWw0PRtFbAhxk\"","mtime":"2026-10-08T11:43:41.734Z"}
8709
+ meta: {"type":"image/png","etag":"\"222-kioUoPaSCUaRhKKWw0PRtFbAhxk\"","mtime":"2026-10-10T00:23:41.681Z"}
8706
8710
  },
8707
8711
  ["server:branding:icons:icon-512-maskable.png"]: {
8708
8712
  import: () => import('../raw/icon-512-maskable.mjs').then(r => r.default || r),
8709
- meta: {"type":"image/png","etag":"\"84b-mCXgbBkXk7sqOhmJ8hjp80SN3Qk\"","mtime":"2026-10-08T11:43:41.734Z"}
8713
+ meta: {"type":"image/png","etag":"\"84b-mCXgbBkXk7sqOhmJ8hjp80SN3Qk\"","mtime":"2026-10-10T00:23:41.681Z"}
8710
8714
  },
8711
8715
  ["server:branding:icons:icon-512.png"]: {
8712
8716
  import: () => import('../raw/icon-512.mjs').then(r => r.default || r),
8713
- meta: {"type":"image/png","etag":"\"8fc-ZwRMqAqQZeLLx4iNTZxs09C4Q1I\"","mtime":"2026-10-08T11:43:41.734Z"}
8717
+ meta: {"type":"image/png","etag":"\"8fc-ZwRMqAqQZeLLx4iNTZxs09C4Q1I\"","mtime":"2026-10-10T00:23:41.681Z"}
8714
8718
  },
8715
8719
  ["server:branding:icons:icon-maskable.svg"]: {
8716
8720
  import: () => import('../raw/icon-maskable.mjs').then(r => r.default || r),
8717
- meta: {"type":"image/svg+xml","etag":"\"4a1-7XtnuN5ZOwiwIjpgpj+/Rg9LssY\"","mtime":"2026-10-08T11:43:41.734Z"}
8721
+ meta: {"type":"image/svg+xml","etag":"\"4a1-7XtnuN5ZOwiwIjpgpj+/Rg9LssY\"","mtime":"2026-10-10T00:23:41.681Z"}
8718
8722
  },
8719
8723
  ["server:branding:icons:icon.svg"]: {
8720
8724
  import: () => import('../raw/icon.mjs').then(r => r.default || r),
8721
- meta: {"type":"image/svg+xml","etag":"\"49c-O1XxkWwKTf6VBoC0i4TEPdGPsCw\"","mtime":"2026-10-08T11:43:41.734Z"}
8725
+ meta: {"type":"image/svg+xml","etag":"\"49c-O1XxkWwKTf6VBoC0i4TEPdGPsCw\"","mtime":"2026-10-10T00:23:41.681Z"}
8722
8726
  }
8723
8727
  };
8724
8728
 
@@ -9621,7 +9625,7 @@ function _expandFromEnv(value) {
9621
9625
  const _inlineRuntimeConfig = {
9622
9626
  "app": {
9623
9627
  "baseURL": "/",
9624
- "buildId": "6466b0ab-7935-460c-abdf-2ea632dae6d0",
9628
+ "buildId": "7afbf062-5369-4002-b20d-7a582ff6e0f1",
9625
9629
  "buildAssetsDir": "/_nuxt/",
9626
9630
  "cdnURL": ""
9627
9631
  },
@@ -10197,6 +10201,7 @@ const HERDR_CHILD_ENV = Object.fromEntries(
10197
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);
10198
10202
  const APP_URL_FILE = path$1.join(DATA_DIR, "app-url.json");
10199
10203
  const ENV_APP_URL = /^https:\/\//i.test(env.APP_URL || "") ? env.APP_URL : "";
10204
+ const BOOT_APP_URL = env.APP_URL || "";
10200
10205
  function savedAppUrl() {
10201
10206
  var _a;
10202
10207
  try {
@@ -10209,8 +10214,13 @@ function savedAppUrl() {
10209
10214
  if (!ENV_APP_URL && savedAppUrl()) env.APP_URL = savedAppUrl();
10210
10215
  const appUrl = () => env.APP_URL || "mailto:herdr-web@localhost";
10211
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
+ }
10212
10221
  const PASSKEY_USER = (env.PASSKEY_USER || "wherdr").trim().slice(0, 64);
10213
10222
  const POLL_MS = Number(env.POLL_MS || 1e3);
10223
+ const TAILNET_REFRESH_MS = Number(env.TAILNET_REFRESH_MS || 3e4);
10214
10224
  const NOTIFY_SETTLE_MS = Number(env.NOTIFY_SETTLE_MS || 4e3);
10215
10225
  const UPLOAD_DIR = env.UPLOAD_DIR || path$1.join(HOME, ".cache/herdr-web/uploads");
10216
10226
  const UPLOAD_TTL_MS = 7 * 864e5;
@@ -10555,7 +10565,7 @@ async function agentPrompt(target, text) {
10555
10565
  await herdr("agent.prompt", { target, text });
10556
10566
  }
10557
10567
  }
10558
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
10568
+ const sleep$1 = (ms) => new Promise((r) => setTimeout(r, ms));
10559
10569
 
10560
10570
  var __defProp$5 = Object.defineProperty;
10561
10571
  var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -12835,6 +12845,19 @@ async function ompCommandTemplates(fs, home) {
12835
12845
  return out;
12836
12846
  }
12837
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
+
12838
12861
  const WINDOW_BYTES = 2 * 1024 * 1024;
12839
12862
  const MAX_READ_BYTES = 24 * 1024 * 1024;
12840
12863
  const PAGE_ITEMS = 120;
@@ -12853,6 +12876,10 @@ function humanText(t) {
12853
12876
  if (u !== s) return u;
12854
12877
  return isNoise(s) ? null : s;
12855
12878
  }
12879
+ const pastedOf = (raw) => {
12880
+ const b = pastedBlocks(raw).map((s) => clip(s));
12881
+ return b.length ? { pasted: b } : {};
12882
+ };
12856
12883
  const normMsg = (t) => stripImageTags(t).replace(/\s+/g, " ").trim().toLowerCase();
12857
12884
  function sameMsg(queued, said) {
12858
12885
  const q = normMsg(queued).slice(0, 60);
@@ -13030,10 +13057,11 @@ function parseClaude(lines, home = "") {
13030
13057
  const ts = d.timestamp || null;
13031
13058
  if (d.type === "attachment" && d.attachment && d.attachment.type === "queued_command" && d.attachment.commandMode === "prompt" && (d.attachment.origin || {}).kind === "human") {
13032
13059
  const parts = Array.isArray(d.attachment.prompt) ? d.attachment.prompt : [{ type: "text", text: String(d.attachment.prompt || "") }];
13033
- 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));
13034
13062
  const images = parts.filter((p) => p.type === "image").length;
13035
13063
  if (text || images) {
13036
- 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) });
13037
13065
  pendingCmd = null;
13038
13066
  said(text, d.attachment.timestamp || ts, images);
13039
13067
  }
@@ -13084,7 +13112,7 @@ function parseClaude(lines, home = "") {
13084
13112
  } else {
13085
13113
  const text2 = humanText(content);
13086
13114
  if (text2) {
13087
- items.push({ role: "user", text: clip(text2), ts });
13115
+ items.push({ role: "user", text: clip(text2), ts, ...pastedOf(content) });
13088
13116
  pendingCmd = null;
13089
13117
  said(text2, ts);
13090
13118
  noteSaid(d);
@@ -13094,6 +13122,7 @@ function parseClaude(lines, home = "") {
13094
13122
  }
13095
13123
  if (!Array.isArray(content)) continue;
13096
13124
  let text = "";
13125
+ let raw = "";
13097
13126
  let images = 0;
13098
13127
  let toolImages = content.filter((p) => p && p.type === "image").length;
13099
13128
  for (const part of content) {
@@ -13111,12 +13140,13 @@ function parseClaude(lines, home = "") {
13111
13140
  if (special(String(part.text || ""), ts)) continue;
13112
13141
  const t = humanText(part.text);
13113
13142
  if (t === null) continue;
13143
+ raw += "\n" + String(part.text || "");
13114
13144
  if (/^\[Request interrupted/.test(t)) items.push({ role: "system", text: "Interrupted", ts });
13115
13145
  else text += (text ? "\n" : "") + unwrapPasted(t.replace(/\[Image #\d+\]\s*/g, ""));
13116
13146
  } else if (part.type === "image") images++;
13117
13147
  }
13118
13148
  if (text.trim() || images) {
13119
- 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) });
13120
13150
  pendingCmd = null;
13121
13151
  said(text, ts, images);
13122
13152
  noteSaid(d);
@@ -13964,14 +13994,25 @@ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key,
13964
13994
  var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
13965
13995
  const fsp$3 = fs.promises;
13966
13996
  const LOCAL_LABEL_FILE = path$1.join(DATA_DIR, "machine-label.txt");
13967
- function readLocalLabel() {
13997
+ function renamedLabel() {
13968
13998
  try {
13969
- 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);
13970
14000
  } catch {
13971
- return HOST_LABEL;
14001
+ return "";
13972
14002
  }
13973
14003
  }
13974
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
+ }
13975
14016
  function localOs() {
13976
14017
  const p = os.platform();
13977
14018
  return p === "darwin" ? "Darwin" : p === "linux" ? "Linux" : p;
@@ -13979,7 +14020,7 @@ function localOs() {
13979
14020
  const localMachine = {
13980
14021
  key: LOCAL,
13981
14022
  profileId: null,
13982
- label: readLocalLabel(),
14023
+ label: renamedLabel() || HOST_LABEL,
13983
14024
  local: true,
13984
14025
  target: null,
13985
14026
  session: HERDR_SESSION || "default",
@@ -14015,7 +14056,7 @@ function localMachineId() {
14015
14056
  }
14016
14057
  return selfId;
14017
14058
  }
14018
- const selfNames = () => [HOST_LABEL, os.hostname(), ...SELF_HOSTS].filter(Boolean);
14059
+ const selfNames = () => [HOST_LABEL, os.hostname(), tailnetLabel, ...SELF_HOSTS].filter(Boolean);
14019
14060
  class SkipMachine extends Error {
14020
14061
  }
14021
14062
  const PROBE_SCRIPT = `echo "home=$HOME"
@@ -16171,7 +16212,7 @@ async function createAgent(body) {
16171
16212
  created = await hx("worktree.create", params, 3e4);
16172
16213
  } catch (e) {
16173
16214
  if (e.code !== "not_git_worktree") throw e;
16174
- await sleep(1e3);
16215
+ await sleep$1(1e3);
16175
16216
  created = await hx("worktree.create", params, 3e4);
16176
16217
  }
16177
16218
  } else if (!target) {
@@ -16196,7 +16237,7 @@ async function createAgent(body) {
16196
16237
  const pathSetup = 'export PATH="$HOME/.local/bin:$HOME/.kimi-code/bin:$HOME/.bun/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"';
16197
16238
  await herdr("pane.send_input", { pane_id: paneId, text: pathSetup });
16198
16239
  await herdr("pane.send_input", { pane_id: paneId, keys: ["enter"] });
16199
- await sleep(400);
16240
+ await sleep$1(400);
16200
16241
  let lastErr = null;
16201
16242
  for (let i = 0; i < 16; i++) {
16202
16243
  try {
@@ -16211,7 +16252,7 @@ async function createAgent(body) {
16211
16252
  break;
16212
16253
  }
16213
16254
  if (lastErr.code === "timeout" || lastErr.code === "unreachable" || /name/i.test(lastErr.code)) break;
16214
- await sleep(500);
16255
+ await sleep$1(500);
16215
16256
  }
16216
16257
  }
16217
16258
  if (lastErr) {
@@ -16297,7 +16338,7 @@ async function closePanel(paneId) {
16297
16338
  if (!panelOpen(r.read && r.read.text, p.agent)) return sent;
16298
16339
  await herdr("pane.send_input", { pane_id: paneId, keys: ["esc"] });
16299
16340
  sent = true;
16300
- await sleep(400);
16341
+ await sleep$1(400);
16301
16342
  }
16302
16343
  return sent;
16303
16344
  }
@@ -16336,10 +16377,184 @@ async function readUpload(name) {
16336
16377
  return { type: ext === "jpg" ? "image/jpeg" : `image/${ext}`, body: await fsp$2.readFile(path$1.join(UPLOAD_DIR, name)) };
16337
16378
  }
16338
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
+
16339
16492
  const clampInt = (v, lo, hi, dflt) => {
16340
16493
  const n = parseInt(String(v), 10);
16341
16494
  return Number.isFinite(n) ? Math.min(hi, Math.max(lo, n)) : dflt;
16342
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
+ }
16343
16558
  function openTerm(ws, url) {
16344
16559
  const pane = url.searchParams.get("pane") || "";
16345
16560
  if (!PANE_RE.test(pane)) {
@@ -16356,30 +16571,53 @@ function openTerm(ws, url) {
16356
16571
  const rows = clampInt(url.searchParams.get("rows"), 5, 200, 24);
16357
16572
  const args = ["terminal", "session", "control", splitId(pane).local, "--cols", String(cols), "--rows", String(rows)];
16358
16573
  if (url.searchParams.get("takeover") === "1") args.push("--takeover");
16359
- const child = machine.spawnHerdr(args);
16360
16574
  const sess = { pane, visible: true };
16361
16575
  termSessions.add(sess);
16362
- log(`term ${pane} opened ${cols}x${rows}${args.includes("--takeover") ? " (takeover)" : ""}${machine.local ? "" : ` on ${machine.label}`}`);
16363
- readline.createInterface({ input: child.stdout }).on("line", (line) => {
16364
- if (ws.isOpen()) ws.send(line);
16365
- });
16366
- readline.createInterface({ input: child.stderr }).on("line", (line) => {
16367
- log(`term ${pane} stderr: ${line}`);
16368
- if (ws.isOpen()) ws.send(JSON.stringify({ type: "herdr.stderr", message: line }));
16369
- });
16370
- child.on("error", (e) => {
16371
- if (ws.isOpen()) ws.send(JSON.stringify({ type: "terminal.closed", reason: `herdr: ${e.message}` }));
16372
- ws.close(4500, "herdr error");
16373
- });
16374
- child.on("exit", (code) => {
16375
- termSessions.delete(sess);
16376
- if (ws.isOpen()) ws.close(4e3, `herdr exit ${code}`);
16377
- });
16378
- child.stdin.on("error", () => {
16379
- });
16576
+ let child = null;
16577
+ let hold = null;
16578
+ let closed = false;
16579
+ let size = { cols, rows };
16580
+ let attached = false;
16581
+ const early = [];
16380
16582
  const toChild = (obj) => {
16381
- 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
+ }
16382
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
+ });
16383
16621
  return {
16384
16622
  onMessage(raw) {
16385
16623
  let m;
@@ -16393,9 +16631,12 @@ function openTerm(ws, url) {
16393
16631
  if (typeof m.text === "string" && m.text.length <= 65536) toChild({ type: "terminal.input", text: m.text });
16394
16632
  else if (typeof m.bytes === "string" && m.bytes.length <= 9e4) toChild({ type: "terminal.input", bytes: m.bytes });
16395
16633
  break;
16396
- case "terminal.resize":
16397
- 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 });
16398
16638
  break;
16639
+ }
16399
16640
  case "terminal.scroll":
16400
16641
  if (m.direction === "up" || m.direction === "down") {
16401
16642
  toChild({ type: "terminal.scroll", direction: m.direction, lines: clampInt(m.lines, 1, 500, 3) });
@@ -16419,13 +16660,18 @@ function openTerm(ws, url) {
16419
16660
  break;
16420
16661
  }
16421
16662
  },
16422
- onClose() {
16663
+ onClose(clean = false) {
16664
+ closed = true;
16423
16665
  termSessions.delete(sess);
16424
- toChild({ type: "terminal.release" });
16425
- child.stdin.end();
16426
- setTimeout(() => {
16427
- if (child.exitCode === null) child.kill("SIGTERM");
16428
- }, 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);
16429
16675
  }
16430
16676
  };
16431
16677
  }
@@ -16586,14 +16832,14 @@ async function waitMenu(paneId, ok, timeoutMs = 6e3) {
16586
16832
  const m = parseModelMenu(await screen(paneId));
16587
16833
  if (m && ok(m)) return m;
16588
16834
  if (Date.now() > until) return null;
16589
- await sleep(250);
16835
+ await sleep$1(250);
16590
16836
  }
16591
16837
  }
16592
16838
  async function closeMenu(paneId) {
16593
16839
  for (let i = 0; i < 3; i++) {
16594
16840
  if (!parseModelMenu(await screen(paneId))) return;
16595
16841
  await herdr("pane.send_input", { pane_id: paneId, keys: ["esc"] });
16596
- await sleep(400);
16842
+ await sleep$1(400);
16597
16843
  }
16598
16844
  }
16599
16845
  async function openMenu(p) {
@@ -16621,7 +16867,7 @@ async function readAllOptions(paneId, first) {
16621
16867
  for (let i = 0; i < 8 && m.scrollDown; i++) {
16622
16868
  const last = m.options[m.options.length - 1].n;
16623
16869
  await herdr("pane.send_input", { pane_id: paneId, keys: moveKeys(m.cursor, last + 1) });
16624
- await sleep(350);
16870
+ await sleep$1(350);
16625
16871
  const next = await waitMenu(paneId, (x) => x.kind === "model", 2e3);
16626
16872
  if (!next || next.cursor <= last) break;
16627
16873
  const before = all.size;
@@ -16703,7 +16949,7 @@ async function setModel(paneId, wanted) {
16703
16949
  const confirm = p.agent === "claude" ? switchConfirmKeys(text, confirmAs) : null;
16704
16950
  if (confirm) await herdr("pane.send_input", { pane_id: p.id, keys: confirm });
16705
16951
  clear = confirm || parseModelMenu(text) ? 0 : clear + 1;
16706
- await sleep(confirm ? 400 : 250);
16952
+ await sleep$1(confirm ? 400 : 250);
16707
16953
  }
16708
16954
  if (clear < 2) throw new HerdrError("stale", "The /model menu changed \u2014 try again.");
16709
16955
  const info = { id: null, label: confirmAs, effort, at: new Date(started).toISOString() };
@@ -16726,14 +16972,14 @@ async function waitOmpSelector(paneId, ok, timeoutMs = 6e3) {
16726
16972
  const s = await ompSelector(paneId).catch(() => null);
16727
16973
  if (s && ok(s)) return s;
16728
16974
  if (Date.now() > until) return null;
16729
- await sleep(250);
16975
+ await sleep$1(250);
16730
16976
  }
16731
16977
  }
16732
16978
  async function closeOmpSelector(paneId) {
16733
16979
  for (let i = 0; i < 3; i++) {
16734
16980
  if (!await ompSelector(paneId).catch(() => null)) return;
16735
16981
  await herdr("pane.send_input", { pane_id: paneId, keys: ["esc"] });
16736
- await sleep(400);
16982
+ await sleep$1(400);
16737
16983
  }
16738
16984
  }
16739
16985
  const OMP_SELECTOR_COLS = 160;
@@ -16773,7 +17019,7 @@ async function waitOmpModel(p, id, since) {
16773
17019
  const info = await transcripts.model(p).catch(() => null);
16774
17020
  if (info && info.id === id && info.at && Date.parse(info.at) >= since - 1500) return true;
16775
17021
  if (Date.now() > until) return false;
16776
- await sleep(300);
17022
+ await sleep$1(300);
16777
17023
  }
16778
17024
  }
16779
17025
  async function ompSetModel(paneId, wanted) {
@@ -16797,7 +17043,7 @@ async function ompSetModel(paneId, wanted) {
16797
17043
  }
16798
17044
  for (const ch of typed) {
16799
17045
  await herdr("pane.send_input", { pane_id: p.id, keys: [ch === " " ? "space" : ch] });
16800
- await sleep(80);
17046
+ await sleep$1(80);
16801
17047
  }
16802
17048
  s = await waitOmpSelector(p.id, (x) => x.search.length > 0, 3e3) || s;
16803
17049
  at = rank(s);
@@ -16808,7 +17054,7 @@ async function ompSetModel(paneId, wanted) {
16808
17054
  const delta = at - s.cursor;
16809
17055
  if (delta === 0) break;
16810
17056
  await herdr("pane.send_input", { pane_id: p.id, keys: [delta > 0 ? "down" : "up"] });
16811
- await sleep(120);
17057
+ await sleep$1(120);
16812
17058
  if (n === 79) throw new HerdrError("stale", "The model selector did not follow \u2014 nothing was changed.");
16813
17059
  }
16814
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;
@@ -16867,7 +17113,7 @@ async function waitSlider(paneId, ok, timeoutMs) {
16867
17113
  const s = parseClaudeEffortScreen(await screen(paneId).catch(() => ""));
16868
17114
  if (s && ok(s)) return s;
16869
17115
  if (Date.now() > until) return null;
16870
- await sleep(250);
17116
+ await sleep$1(250);
16871
17117
  }
16872
17118
  }
16873
17119
  async function sliderGone(paneId, timeoutMs = 3e3) {
@@ -16875,7 +17121,7 @@ async function sliderGone(paneId, timeoutMs = 3e3) {
16875
17121
  let clear = 0;
16876
17122
  while (clear < 2 && Date.now() < until) {
16877
17123
  clear = parseClaudeEffortScreen(await screen(paneId).catch(() => "")) ? 0 : clear + 1;
16878
- if (clear < 2) await sleep(250);
17124
+ if (clear < 2) await sleep$1(250);
16879
17125
  }
16880
17126
  return clear >= 2;
16881
17127
  }
@@ -16884,7 +17130,7 @@ async function closeEffortSlider(paneId) {
16884
17130
  if (!parseClaudeEffortScreen(await screen(paneId).catch(() => ""))) return;
16885
17131
  await herdr("pane.send_input", { pane_id: paneId, keys: ["esc"] }).catch(() => {
16886
17132
  });
16887
- await sleep(400);
17133
+ await sleep$1(400);
16888
17134
  }
16889
17135
  }
16890
17136
  async function openEffortSlider(p) {
@@ -16918,7 +17164,7 @@ async function waitOmpEffort(p, model, prev, shown, sinceAt, hasTranscript) {
16918
17164
  if (now && prev === "auto" && shown !== "auto" && Date.now() - pressed > OMP_REDRAW_MS) return { level: now, at: sinceAt, shown: now };
16919
17165
  }
16920
17166
  if (Date.now() - pressed > 3e3) return null;
16921
- await sleep(200);
17167
+ await sleep$1(200);
16922
17168
  }
16923
17169
  }
16924
17170
  async function ompCycleTo(p, before, level) {
@@ -17031,7 +17277,7 @@ async function setEffort(paneId, level) {
17031
17277
  let clear = 0;
17032
17278
  while (clear < 2 && Date.now() < until) {
17033
17279
  clear = parseModelMenu(await screen(p.id)) ? 0 : clear + 1;
17034
- await sleep(250);
17280
+ await sleep$1(250);
17035
17281
  }
17036
17282
  if (clear < 2) throw new HerdrError("stale", "The /model menu changed \u2014 try again.");
17037
17283
  } catch (e) {
@@ -17064,16 +17310,26 @@ function compareVersions(a, b) {
17064
17310
  for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] - y[i];
17065
17311
  return 0;
17066
17312
  }
17313
+ const MODES = ["docker", "docker-build", "npm", "npm-global", "brew", "plugin"];
17067
17314
  function installMode(env) {
17068
- return env === "docker" || env === "docker-build" || env === "npm" || env === "brew" ? env : "native";
17315
+ var _a;
17316
+ return (_a = MODES.find((m) => m === env)) != null ? _a : "native";
17069
17317
  }
17070
- function updateCommand(mode) {
17318
+ function updateCommand(mode, repo) {
17071
17319
  if (mode === "docker") return "docker compose pull && docker compose up -d";
17072
17320
  if (mode === "docker-build") return "git pull && docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build";
17073
17321
  if (mode === "npm") return "npx wherdr@latest";
17074
- if (mode === "brew") return "brew upgrade wherdr";
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`;
17075
17325
  return "git pull && npm ci && npm run build";
17076
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
+ }
17077
17333
 
17078
17334
  const CODEX_RELEASE_NOTES = "https://github.com/openai/codex/releases/latest";
17079
17335
  const CODEX_STANDALONE_COMMAND = `sh -c 'curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 sh'`;
@@ -18131,7 +18387,7 @@ function sendPrompt(p, text) {
18131
18387
  keys: async (keys) => {
18132
18388
  await herdr("pane.send_input", { pane_id: p.id, keys });
18133
18389
  },
18134
- sleep,
18390
+ sleep: sleep$1,
18135
18391
  sentSince: async (since) => {
18136
18392
  const r2 = await transcripts.chat(p, {});
18137
18393
  return (r2.items || []).filter((i) => i.role === "user" && i.ts && Date.parse(i.ts) >= since - 1e3).map((i) => i.text);
@@ -18200,13 +18456,13 @@ function takeBackFromClaude(p) {
18200
18456
  await herdr("pane.send_input", { pane_id: p.id, keys });
18201
18457
  },
18202
18458
  chat: async () => (await transcripts.chat(p, { fresh: true })).items || [],
18203
- sleep,
18459
+ sleep: sleep$1,
18204
18460
  pending: () => (queued.get(p.id) || []).map((q) => q.text)
18205
18461
  }).catch(() => null);
18206
18462
  }
18207
18463
  async function cancelTurn(p, text) {
18208
18464
  if (!unansweredTurn((await transcripts.chat(p, { fresh: true })).items || [], text)) return null;
18209
- 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);
18210
18466
  if (!r.stopped) return null;
18211
18467
  const item = await takeBackFromClaude(p);
18212
18468
  if (!item) return null;
@@ -18242,7 +18498,7 @@ function reconcileQueued(p) {
18242
18498
  }
18243
18499
  const unqueueBusy = /* @__PURE__ */ new Set();
18244
18500
  async function deliveryDone(paneId) {
18245
- 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);
18246
18502
  }
18247
18503
  async function cancelQueued(paneId, text, id) {
18248
18504
  const p = findPane(paneId);
@@ -18288,7 +18544,7 @@ async function cancelQueued(paneId, text, id) {
18288
18544
  return { queue: r2.queue || [], items: r2.items || [] };
18289
18545
  },
18290
18546
  prompt: (t) => agentPrompt(p.id, t),
18291
- sleep,
18547
+ sleep: sleep$1,
18292
18548
  original: (e) => {
18293
18549
  var _a2, _b2;
18294
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;
@@ -19170,1059 +19426,1568 @@ function stopNotices() {
19170
19426
  listeners.clear();
19171
19427
  }
19172
19428
 
19173
- const fsp = fs.promises;
19174
- const REFUSED = {
19175
- archive: "Archives cannot be read by the agent: extract the files first",
19176
- video: "Videos cannot be read by the agent",
19177
- audio: "Audio files cannot be read by the agent",
19178
- office: "Office documents cannot be read by the agent: export to PDF or text first",
19179
- executable: "Programs and binary files cannot be read by the agent",
19180
- binary: "Binary files cannot be read by the agent",
19181
- pdf: "This agent cannot read PDFs: only Claude Code can",
19182
- too_large: "File too large for the agent",
19183
- 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',
19184
19442
  };
19185
- async function saveAttachment(data, originalName, paneId) {
19186
- const pane = findPane(paneId);
19187
- const m = machineOfPane(paneId);
19188
- if (!pane || !m) throw new HerdrError("bad_pane", "Pane not found");
19189
- const check = checkAttachment(pane.agent, { name: originalName, type: "", size: data.length }, data.subarray(0, SNIFF_BYTES));
19190
- if (check.ok && check.kind === "image") throw new HerdrError("unsupported", "Images go through the photo upload");
19191
- if (!check.ok) throw new HerdrError(check.reason === "too_large" ? "too_large" : "unsupported", REFUSED[check.reason]);
19192
- const stored = storedAttachmentName(originalName, /* @__PURE__ */ new Date(), crypto.randomBytes(3).toString("hex"));
19193
- if (!isStoredAttachmentName(stored)) throw new HerdrError("bad_name", "Invalid file name");
19194
- let file;
19195
- if (m instanceof RemoteMachine) {
19196
- if (m.status !== "online") throw new HerdrError("unreachable", fmt("{machine} is unreachable", { machine: m.label }));
19197
- file = await m.putUpload(stored, data, "file");
19198
- } else {
19199
- file = path$1.join(ATTACH_DIR, stored);
19200
- if (path$1.dirname(file) !== path$1.resolve(ATTACH_DIR)) throw new HerdrError("bad_name", "Invalid file name");
19201
- await fsp.mkdir(ATTACH_DIR, { recursive: true, mode: 448 });
19202
- 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
+ }
19203
19509
  }
19204
- log(`file received ${stored} (${Math.round(data.length / 1024)} KB, ${check.kind})${m instanceof RemoteMachine ? ` -> ${m.label}` : ""}`);
19205
- return { path: file, name: stored, kind: check.kind, size: data.length, ref: attachmentRef(pane.agent, check.kind, file) };
19510
+ return out
19206
19511
  }
19207
- 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) {
19208
19538
  try {
19209
- for (const n of await fsp.readdir(ATTACH_DIR)) {
19210
- const f = path$1.join(ATTACH_DIR, n);
19211
- const st = await fsp.lstat(f);
19212
- if (st.isFile() && Date.now() - st.mtimeMs > UPLOAD_TTL_MS) await fsp.unlink(f);
19213
- }
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(/\.$/, "");
19214
19704
  } catch {
19705
+ return null;
19215
19706
  }
19216
19707
  }
19217
-
19218
- const _mZt23IdRC2vLuqDtWT6mb7m9BgHXq3U5QrnQ7Z5Hc4c = defineNitroPlugin((nitroApp) => {
19219
- initVapid();
19220
- startMachines();
19221
- startPolling();
19222
- startThreadLimits();
19223
- startNotices();
19224
- cleanUploads();
19225
- cleanAttachments();
19226
- const purge = setInterval(() => {
19227
- cleanUploads();
19228
- cleanAttachments();
19229
- }, 6 * 3600 * 1e3);
19230
- log(`herdr-web \u2014 socket ${HERDR_SOCK}${HERDR_SESSION ? ` (session ${HERDR_SESSION})` : ""}`);
19231
- nitroApp.hooks.hook("close", () => {
19232
- stopPolling();
19233
- stopNotices();
19234
- stopMachines();
19235
- 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
+ }
19236
19716
  });
19237
- });
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
+ }
19238
19775
 
19239
- const plugins = [
19240
- _9as7ch5sSe9R7BrbrDE2GGZpuA6oP9uMAcL7iRtRgrw,
19241
- _mZt23IdRC2vLuqDtWT6mb7m9BgHXq3U5QrnQ7Z5Hc4c
19242
- ];
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
+ }
19243
19782
 
19244
- const assets = {
19245
- "/manifest-en.webmanifest": {
19246
- "type": "application/manifest+json",
19247
- "etag": "\"260-q1JnCK0/ecdKwgeTve6N4I6/mWI\"",
19248
- "mtime": "2026-10-08T11:44:03.930Z",
19249
- "size": 608,
19250
- "path": "../public/manifest-en.webmanifest"
19251
- },
19252
- "/manifest.webmanifest": {
19253
- "type": "application/manifest+json",
19254
- "etag": "\"26e-ykoYuj4V8la457yChW3oQTur8RU\"",
19255
- "mtime": "2026-10-08T11:44:03.930Z",
19256
- "size": 622,
19257
- "path": "../public/manifest.webmanifest"
19258
- },
19259
- "/sw.js": {
19260
- "type": "text/javascript; charset=utf-8",
19261
- "etag": "\"1b1d-uKe7we3azu1ltb6U6uThnVqkUn4\"",
19262
- "mtime": "2026-10-08T11:44:04.183Z",
19263
- "size": 6941,
19264
- "path": "../public/sw.js"
19265
- },
19266
- "/_nuxt/AgentView.C4YOwvZG.css": {
19267
- "type": "text/css; charset=utf-8",
19268
- "etag": "\"15c8-yoJUdtWy2mP7U+kxJ717a6Bzxgg\"",
19269
- "mtime": "2026-10-08T11:44:03.918Z",
19270
- "size": 5576,
19271
- "path": "../public/_nuxt/AgentView.C4YOwvZG.css"
19272
- },
19273
- "/_nuxt/B6qBWHWB.js": {
19274
- "type": "text/javascript; charset=utf-8",
19275
- "etag": "\"13f-jMtJt8dAqnEh2SAOR1babBORdK4\"",
19276
- "mtime": "2026-10-08T11:44:03.918Z",
19277
- "size": 319,
19278
- "path": "../public/_nuxt/B6qBWHWB.js"
19279
- },
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": {
20395
+ "type": "text/javascript; charset=utf-8",
20396
+ "etag": "\"1b1d-wHdIux7mWagbphlmnadZ+jdW3jQ\"",
20397
+ "mtime": "2026-10-10T00:24:15.363Z",
20398
+ "size": 6941,
20399
+ "path": "../public/sw.js"
20400
+ },
20401
+ "/_nuxt/AgentView.C4YOwvZG.css": {
20402
+ "type": "text/css; charset=utf-8",
20403
+ "etag": "\"15c8-yoJUdtWy2mP7U+kxJ717a6Bzxgg\"",
20404
+ "mtime": "2026-10-10T00:24:14.946Z",
20405
+ "size": 5576,
20406
+ "path": "../public/_nuxt/AgentView.C4YOwvZG.css"
20407
+ },
20408
+ "/_nuxt/B7QP1TFs.js": {
20409
+ "type": "text/javascript; charset=utf-8",
20410
+ "etag": "\"1118a-Gc+7ipGEaJc4tPQJbl761DHnbVc\"",
20411
+ "mtime": "2026-10-10T00:24:14.946Z",
20412
+ "size": 70026,
20413
+ "path": "../public/_nuxt/B7QP1TFs.js"
20414
+ },
19280
20415
  "/_nuxt/BDNMzG2s.js": {
19281
20416
  "type": "text/javascript; charset=utf-8",
19282
20417
  "etag": "\"54-MasMfSk/A98C3Gn9uIOxtFxkWNw\"",
19283
- "mtime": "2026-10-08T11:44:03.918Z",
20418
+ "mtime": "2026-10-10T00:24:14.946Z",
19284
20419
  "size": 84,
19285
20420
  "path": "../public/_nuxt/BDNMzG2s.js"
19286
20421
  },
19287
20422
  "/_nuxt/Bd17z0YL.js": {
19288
20423
  "type": "text/javascript; charset=utf-8",
19289
20424
  "etag": "\"1615-CPDn06/EO3pj6KudXz5uMZ0FkhI\"",
19290
- "mtime": "2026-10-08T11:44:03.918Z",
20425
+ "mtime": "2026-10-10T00:24:14.946Z",
19291
20426
  "size": 5653,
19292
20427
  "path": "../public/_nuxt/Bd17z0YL.js"
19293
20428
  },
19294
- "/_nuxt/BpQH8Ho1.js": {
19295
- "type": "text/javascript; charset=utf-8",
19296
- "etag": "\"e3-flLwB9P4XX6B82smMHA/xJ0MHtI\"",
19297
- "mtime": "2026-10-08T11:44:03.918Z",
19298
- "size": 227,
19299
- "path": "../public/_nuxt/BpQH8Ho1.js"
19300
- },
19301
- "/_nuxt/BMIe8p_3.js": {
19302
- "type": "text/javascript; charset=utf-8",
19303
- "etag": "\"1f34c-xbnpB4ou/Yo+mnIWUPmKT1dw6kg\"",
19304
- "mtime": "2026-10-08T11:44:03.918Z",
19305
- "size": 127820,
19306
- "path": "../public/_nuxt/BMIe8p_3.js"
19307
- },
19308
- "/_nuxt/C12eXl5z.js": {
20429
+ "/_nuxt/BOpBIziZ.js": {
19309
20430
  "type": "text/javascript; charset=utf-8",
19310
- "etag": "\"3743-LHJuwpFjgipbDnhMaCyj/9CAzuY\"",
19311
- "mtime": "2026-10-08T11:44:03.918Z",
19312
- "size": 14147,
19313
- "path": "../public/_nuxt/C12eXl5z.js"
19314
- },
19315
- "/_nuxt/C2T9fQ4f.js": {
19316
- "type": "text/javascript; charset=utf-8",
19317
- "etag": "\"238-ULCQ+lAhpp4nPDbIDScqd3xGFLs\"",
19318
- "mtime": "2026-10-08T11:44:03.918Z",
19319
- "size": 568,
19320
- "path": "../public/_nuxt/C2T9fQ4f.js"
20431
+ "etag": "\"17047-w7HRRR9UP7E4j/0+gMWSMH60Me4\"",
20432
+ "mtime": "2026-10-10T00:24:14.946Z",
20433
+ "size": 94279,
20434
+ "path": "../public/_nuxt/BOpBIziZ.js"
19321
20435
  },
19322
20436
  "/_nuxt/CWwyK2XK.js": {
19323
20437
  "type": "text/javascript; charset=utf-8",
19324
20438
  "etag": "\"7e5e-4BG2J9cvS3sx6WQ360LFqNaThyA\"",
19325
- "mtime": "2026-10-08T11:44:03.918Z",
20439
+ "mtime": "2026-10-10T00:24:14.947Z",
19326
20440
  "size": 32350,
19327
20441
  "path": "../public/_nuxt/CWwyK2XK.js"
19328
20442
  },
19329
- "/_nuxt/BrAeJIZR.js": {
19330
- "type": "text/javascript; charset=utf-8",
19331
- "etag": "\"46cdd-poAuGC5qlfraU64uRa+Yc2AkKhw\"",
19332
- "mtime": "2026-10-08T11:44:03.918Z",
19333
- "size": 290013,
19334
- "path": "../public/_nuxt/BrAeJIZR.js"
19335
- },
19336
20443
  "/_nuxt/CXG-cOdh.js": {
19337
20444
  "type": "text/javascript; charset=utf-8",
19338
20445
  "etag": "\"d4e-hM6T4bjr/qfJVvSNRqFH3ePHBQY\"",
19339
- "mtime": "2026-10-08T11:44:03.918Z",
20446
+ "mtime": "2026-10-10T00:24:14.947Z",
19340
20447
  "size": 3406,
19341
20448
  "path": "../public/_nuxt/CXG-cOdh.js"
19342
20449
  },
19343
- "/_nuxt/CYxHnMNH.js": {
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": {
19344
20458
  "type": "text/javascript; charset=utf-8",
19345
- "etag": "\"16ad4-A0W9NTd4vhWgkMxHSgPh07/MudQ\"",
19346
- "mtime": "2026-10-08T11:44:03.918Z",
19347
- "size": 92884,
19348
- "path": "../public/_nuxt/CYxHnMNH.js"
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"
19349
20470
  },
19350
20471
  "/_nuxt/Ci8N4mWR.js": {
19351
20472
  "type": "text/javascript; charset=utf-8",
19352
20473
  "etag": "\"2426-fCrTYUyhtdHnTmu6ZJeEttvxVMo\"",
19353
- "mtime": "2026-10-08T11:44:03.918Z",
20474
+ "mtime": "2026-10-10T00:24:14.947Z",
19354
20475
  "size": 9254,
19355
20476
  "path": "../public/_nuxt/Ci8N4mWR.js"
19356
20477
  },
19357
- "/_nuxt/CctS2Xn6.js": {
20478
+ "/_nuxt/CkZ8t6uB.js": {
19358
20479
  "type": "text/javascript; charset=utf-8",
19359
- "etag": "\"16078-Cz/Wvlpwr6os4SjIEqnTr9/8LlU\"",
19360
- "mtime": "2026-10-08T11:44:03.918Z",
19361
- "size": 90232,
19362
- "path": "../public/_nuxt/CctS2Xn6.js"
20480
+ "etag": "\"3743-91mhngVNRfpvaYKVJ/QoYV6yh1A\"",
20481
+ "mtime": "2026-10-10T00:24:14.947Z",
20482
+ "size": 14147,
20483
+ "path": "../public/_nuxt/CkZ8t6uB.js"
19363
20484
  },
19364
- "/_nuxt/DLLQgP0d.js": {
20485
+ "/_nuxt/D7sxNXx1.js": {
19365
20486
  "type": "text/javascript; charset=utf-8",
19366
- "etag": "\"ed-o6vDEpQGmmIRAyMcrFDkG+VrA/Y\"",
19367
- "mtime": "2026-10-08T11:44:03.919Z",
19368
- "size": 237,
19369
- "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"
19370
20491
  },
19371
- "/_nuxt/CiYar6hg.js": {
20492
+ "/_nuxt/DViwOclv.js": {
19372
20493
  "type": "text/javascript; charset=utf-8",
19373
- "etag": "\"16f07-We6JfDwPvSXrF5YZ4079o5Xc8i8\"",
19374
- "mtime": "2026-10-08T11:44:03.918Z",
19375
- "size": 93959,
19376
- "path": "../public/_nuxt/CiYar6hg.js"
20494
+ "etag": "\"10a-+H4vdCMSTz83KYVa6X+gWZCDDow\"",
20495
+ "mtime": "2026-10-10T00:24:14.947Z",
20496
+ "size": 266,
20497
+ "path": "../public/_nuxt/DViwOclv.js"
19377
20498
  },
19378
- "/_nuxt/SiL1FJ5s.js": {
20499
+ "/_nuxt/DyBZDSCV.js": {
19379
20500
  "type": "text/javascript; charset=utf-8",
19380
- "etag": "\"176ad-/hlyeMkW67OV1A4G2nhIsYJX+vE\"",
19381
- "mtime": "2026-10-08T11:44:03.919Z",
19382
- "size": 95917,
19383
- "path": "../public/_nuxt/SiL1FJ5s.js"
20501
+ "etag": "\"1f34c-1/LEO6HIPrMJPOnrvbeL2tadMf4\"",
20502
+ "mtime": "2026-10-10T00:24:14.947Z",
20503
+ "size": 127820,
20504
+ "path": "../public/_nuxt/DyBZDSCV.js"
19384
20505
  },
19385
- "/_nuxt/Dwh-l2rQ.js": {
20506
+ "/_nuxt/DoD7Qb5P.js": {
19386
20507
  "type": "text/javascript; charset=utf-8",
19387
- "etag": "\"3919e-zK0jGmVhIk455555qPCvrLTDhXs\"",
19388
- "mtime": "2026-10-08T11:44:03.919Z",
19389
- "size": 233886,
19390
- "path": "../public/_nuxt/Dwh-l2rQ.js"
20508
+ "etag": "\"4c573-s0WTgDKba5SDqDXhmDa67SGcDHk\"",
20509
+ "mtime": "2026-10-10T00:24:14.947Z",
20510
+ "size": 312691,
20511
+ "path": "../public/_nuxt/DoD7Qb5P.js"
19391
20512
  },
19392
20513
  "/_nuxt/archivo-latin-ext-wght-normal.C4zznr8T.woff2": {
19393
20514
  "type": "font/woff2",
19394
20515
  "etag": "\"7f60-cwHN4xD2RhU0GVBfKzpzYZ7Al6U\"",
19395
- "mtime": "2026-10-08T11:44:03.919Z",
20516
+ "mtime": "2026-10-10T00:24:14.947Z",
19396
20517
  "size": 32608,
19397
20518
  "path": "../public/_nuxt/archivo-latin-ext-wght-normal.C4zznr8T.woff2"
19398
20519
  },
19399
20520
  "/_nuxt/archivo-latin-wght-normal.E0tuGl4L.woff2": {
19400
20521
  "type": "font/woff2",
19401
20522
  "etag": "\"8870-ZUSfFgU3jYpWYbszVY1rxpynehc\"",
19402
- "mtime": "2026-10-08T11:44:03.919Z",
20523
+ "mtime": "2026-10-10T00:24:14.947Z",
19403
20524
  "size": 34928,
19404
20525
  "path": "../public/_nuxt/archivo-latin-wght-normal.E0tuGl4L.woff2"
19405
20526
  },
19406
20527
  "/_nuxt/archivo-vietnamese-wght-normal.XAtsl5Q_.woff2": {
19407
20528
  "type": "font/woff2",
19408
20529
  "etag": "\"33b8-4zjQHgOy7y9EDRZs7TDAFDM4Ams\"",
19409
- "mtime": "2026-10-08T11:44:03.919Z",
20530
+ "mtime": "2026-10-10T00:24:14.947Z",
19410
20531
  "size": 13240,
19411
20532
  "path": "../public/_nuxt/archivo-vietnamese-wght-normal.XAtsl5Q_.woff2"
19412
20533
  },
19413
- "/_nuxt/WLQjK8uJ.js": {
20534
+ "/_nuxt/SiL1FJ5s.js": {
20535
+ "type": "text/javascript; charset=utf-8",
20536
+ "etag": "\"176ad-/hlyeMkW67OV1A4G2nhIsYJX+vE\"",
20537
+ "mtime": "2026-10-10T00:24:14.947Z",
20538
+ "size": 95917,
20539
+ "path": "../public/_nuxt/SiL1FJ5s.js"
20540
+ },
20541
+ "/_nuxt/D4Yz4rat.js": {
19414
20542
  "type": "text/javascript; charset=utf-8",
19415
- "etag": "\"9b3fe-Mhwqc5w8lJiuRM/0t8kIRoh6evE\"",
19416
- "mtime": "2026-10-08T11:44:03.919Z",
19417
- "size": 635902,
19418
- "path": "../public/_nuxt/WLQjK8uJ.js"
20543
+ "etag": "\"16234-4R0MEqvg4VFo319EzlQAELnsvis\"",
20544
+ "mtime": "2026-10-10T00:24:14.947Z",
20545
+ "size": 90676,
20546
+ "path": "../public/_nuxt/D4Yz4rat.js"
20547
+ },
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"
19419
20554
  },
19420
20555
  "/_nuxt/bLP7Jj-b.js": {
19421
20556
  "type": "text/javascript; charset=utf-8",
19422
20557
  "etag": "\"eab-MDKLGS0aoyNdipefSjbM7x6w6Y8\"",
19423
- "mtime": "2026-10-08T11:44:03.919Z",
20558
+ "mtime": "2026-10-10T00:24:14.948Z",
19424
20559
  "size": 3755,
19425
20560
  "path": "../public/_nuxt/bLP7Jj-b.js"
19426
20561
  },
19427
20562
  "/_nuxt/error-404.Bb87HomL.css": {
19428
20563
  "type": "text/css; charset=utf-8",
19429
20564
  "etag": "\"97d-4h9VfBznldxjqagfMldh1hDncR0\"",
19430
- "mtime": "2026-10-08T11:44:03.919Z",
20565
+ "mtime": "2026-10-10T00:24:14.948Z",
19431
20566
  "size": 2429,
19432
20567
  "path": "../public/_nuxt/error-404.Bb87HomL.css"
19433
20568
  },
19434
20569
  "/_nuxt/error-500.Bwd7zAaE.css": {
19435
20570
  "type": "text/css; charset=utf-8",
19436
20571
  "etag": "\"772-nqXXACYtFiPK+D42BNu4uEECARA\"",
19437
- "mtime": "2026-10-08T11:44:03.919Z",
20572
+ "mtime": "2026-10-10T00:24:14.948Z",
19438
20573
  "size": 1906,
19439
20574
  "path": "../public/_nuxt/error-500.Bwd7zAaE.css"
19440
20575
  },
19441
- "/_nuxt/inter-cyrillic-ext-wght-normal.BOeWTOD4.woff2": {
19442
- "type": "font/woff2",
19443
- "etag": "\"6568-cF1iUGbboMFZ8TfnP5HiMgl9II0\"",
19444
- "mtime": "2026-10-08T11:44:03.919Z",
19445
- "size": 25960,
19446
- "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"
19447
20582
  },
19448
20583
  "/_nuxt/inter-cyrillic-wght-normal.DqGufNeO.woff2": {
19449
20584
  "type": "font/woff2",
19450
20585
  "etag": "\"493c-n3Oy9D6jvzfMjpClqox+Zo7ERQQ\"",
19451
- "mtime": "2026-10-08T11:44:03.919Z",
20586
+ "mtime": "2026-10-10T00:24:14.948Z",
19452
20587
  "size": 18748,
19453
20588
  "path": "../public/_nuxt/inter-cyrillic-wght-normal.DqGufNeO.woff2"
19454
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
+ },
19455
20597
  "/_nuxt/inter-greek-ext-wght-normal.DlzME5K_.woff2": {
19456
20598
  "type": "font/woff2",
19457
20599
  "etag": "\"2be0-BP5iTzJeB8nLqYAgKpWNi5o1Zm8\"",
19458
- "mtime": "2026-10-08T11:44:03.919Z",
20600
+ "mtime": "2026-10-10T00:24:14.948Z",
19459
20601
  "size": 11232,
19460
20602
  "path": "../public/_nuxt/inter-greek-ext-wght-normal.DlzME5K_.woff2"
19461
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
+ },
19462
20611
  "/_nuxt/inter-greek-wght-normal.CkhJZR-_.woff2": {
19463
20612
  "type": "font/woff2",
19464
20613
  "etag": "\"4a34-xor/hj4YNqI52zFecXnUbzQ4Xs4\"",
19465
- "mtime": "2026-10-08T11:44:03.920Z",
20614
+ "mtime": "2026-10-10T00:24:14.948Z",
19466
20615
  "size": 18996,
19467
20616
  "path": "../public/_nuxt/inter-greek-wght-normal.CkhJZR-_.woff2"
19468
20617
  },
19469
20618
  "/_nuxt/inter-latin-ext-wght-normal.DO1Apj_S.woff2": {
19470
20619
  "type": "font/woff2",
19471
20620
  "etag": "\"14c4c-zz61D7IQFMB9QxHvTAOk/Vh4ibQ\"",
19472
- "mtime": "2026-10-08T11:44:03.920Z",
20621
+ "mtime": "2026-10-10T00:24:14.948Z",
19473
20622
  "size": 85068,
19474
20623
  "path": "../public/_nuxt/inter-latin-ext-wght-normal.DO1Apj_S.woff2"
19475
20624
  },
19476
20625
  "/_nuxt/inter-latin-wght-normal.Dx4kXJAl.woff2": {
19477
20626
  "type": "font/woff2",
19478
20627
  "etag": "\"bc80-8R1ym7Ck2DUNLqPQ/AYs9u8tUpg\"",
19479
- "mtime": "2026-10-08T11:44:03.920Z",
20628
+ "mtime": "2026-10-10T00:24:14.948Z",
19480
20629
  "size": 48256,
19481
20630
  "path": "../public/_nuxt/inter-latin-wght-normal.Dx4kXJAl.woff2"
19482
20631
  },
19483
20632
  "/_nuxt/inter-vietnamese-wght-normal.CBcvBZtf.woff2": {
19484
20633
  "type": "font/woff2",
19485
20634
  "etag": "\"280c-nBythjoDQ0+5wVAendJ6wU7Xz2M\"",
19486
- "mtime": "2026-10-08T11:44:03.920Z",
20635
+ "mtime": "2026-10-10T00:24:14.948Z",
19487
20636
  "size": 10252,
19488
20637
  "path": "../public/_nuxt/inter-vietnamese-wght-normal.CBcvBZtf.woff2"
19489
20638
  },
19490
20639
  "/_nuxt/jetbrains-mono-cyrillic-wght-normal.D73BlboJ.woff2": {
19491
20640
  "type": "font/woff2",
19492
20641
  "etag": "\"2f4c-WiAGfn140d4QND3ayQWaCHF8rbE\"",
19493
- "mtime": "2026-10-08T11:44:03.920Z",
20642
+ "mtime": "2026-10-10T00:24:14.948Z",
19494
20643
  "size": 12108,
19495
20644
  "path": "../public/_nuxt/jetbrains-mono-cyrillic-wght-normal.D73BlboJ.woff2"
19496
20645
  },
19497
20646
  "/_nuxt/jetbrains-mono-greek-wght-normal.Bw9x6K1M.woff2": {
19498
20647
  "type": "font/woff2",
19499
20648
  "etag": "\"232c-Dnz9DhH4c266e6TziU1pxRkV6FY\"",
19500
- "mtime": "2026-10-08T11:44:03.920Z",
20649
+ "mtime": "2026-10-10T00:24:14.948Z",
19501
20650
  "size": 9004,
19502
20651
  "path": "../public/_nuxt/jetbrains-mono-greek-wght-normal.Bw9x6K1M.woff2"
19503
20652
  },
19504
20653
  "/_nuxt/jetbrains-mono-latin-ext-wght-normal.DBQx-q_a.woff2": {
19505
20654
  "type": "font/woff2",
19506
20655
  "etag": "\"3b5c-HLF7Wvs2Z1IA1cPRs6jnor8OUQ4\"",
19507
- "mtime": "2026-10-08T11:44:03.927Z",
20656
+ "mtime": "2026-10-10T00:24:14.949Z",
19508
20657
  "size": 15196,
19509
20658
  "path": "../public/_nuxt/jetbrains-mono-latin-ext-wght-normal.DBQx-q_a.woff2"
19510
20659
  },
19511
20660
  "/_nuxt/jetbrains-mono-latin-wght-normal.B9CIFXIH.woff2": {
19512
20661
  "type": "font/woff2",
19513
20662
  "etag": "\"9dd4-5yd+cUUhzrXxdMyYebUeD0qml1M\"",
19514
- "mtime": "2026-10-08T11:44:03.927Z",
20663
+ "mtime": "2026-10-10T00:24:14.949Z",
19515
20664
  "size": 40404,
19516
20665
  "path": "../public/_nuxt/jetbrains-mono-latin-wght-normal.B9CIFXIH.woff2"
19517
20666
  },
19518
- "/_nuxt/jetbrains-mono-vietnamese-wght-normal.Bt-aOZkq.woff2": {
19519
- "type": "font/woff2",
19520
- "etag": "\"1d50-/Re0MyD6BV8h81wBPVijGZH5GBs\"",
19521
- "mtime": "2026-10-08T11:44:03.927Z",
19522
- "size": 7504,
19523
- "path": "../public/_nuxt/jetbrains-mono-vietnamese-wght-normal.Bt-aOZkq.woff2"
19524
- },
19525
- "/_nuxt/whFLy_Ne.js": {
19526
- "type": "text/javascript; charset=utf-8",
19527
- "etag": "\"10fef-Fe8e6U4rNb//7sdPdQau3Trl7fU\"",
19528
- "mtime": "2026-10-08T11:44:03.927Z",
19529
- "size": 69615,
19530
- "path": "../public/_nuxt/whFLy_Ne.js"
19531
- },
19532
- "/_nuxt/entry.cKR16VqG.css": {
19533
- "type": "text/css; charset=utf-8",
19534
- "etag": "\"5f50e-lGjGgrLDJlABD7dGL+/Cu4UfQi8\"",
19535
- "mtime": "2026-10-08T11:44:03.919Z",
19536
- "size": 390414,
19537
- "path": "../public/_nuxt/entry.cKR16VqG.css"
19538
- },
19539
20667
  "/_nuxt/wherdr-nerd-codicons.CtEsfSAf.woff2": {
19540
20668
  "type": "font/woff2",
19541
20669
  "etag": "\"d9b0-kmO/x6KCTFRlVipOvl+5XcArGUw\"",
19542
- "mtime": "2026-10-08T11:44:03.927Z",
20670
+ "mtime": "2026-10-10T00:24:14.949Z",
19543
20671
  "size": 55728,
19544
20672
  "path": "../public/_nuxt/wherdr-nerd-codicons.CtEsfSAf.woff2"
19545
20673
  },
20674
+ "/_nuxt/jetbrains-mono-vietnamese-wght-normal.Bt-aOZkq.woff2": {
20675
+ "type": "font/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"
20680
+ },
19546
20681
  "/_nuxt/wherdr-nerd-awesome.DQ3_iAfa.woff2": {
19547
20682
  "type": "font/woff2",
19548
20683
  "etag": "\"30178-02mpQKEDb74Ec8LtlhKceB1vHO8\"",
19549
- "mtime": "2026-10-08T11:44:03.927Z",
20684
+ "mtime": "2026-10-10T00:24:14.949Z",
19550
20685
  "size": 196984,
19551
20686
  "path": "../public/_nuxt/wherdr-nerd-awesome.DQ3_iAfa.woff2"
19552
20687
  },
19553
20688
  "/_nuxt/wherdr-nerd-extension.Dai_ky39.woff2": {
19554
20689
  "type": "font/woff2",
19555
20690
  "etag": "\"f76c-CjERGtjtOrkpSXy7JyZNl1CAu0U\"",
19556
- "mtime": "2026-10-08T11:44:03.927Z",
20691
+ "mtime": "2026-10-10T00:24:14.949Z",
19557
20692
  "size": 63340,
19558
20693
  "path": "../public/_nuxt/wherdr-nerd-extension.Dai_ky39.woff2"
19559
20694
  },
20695
+ "/_nuxt/wherdr-nerd-material-1.BdvqkCqW.woff2": {
20696
+ "type": "font/woff2",
20697
+ "etag": "\"1e130-1GKpbVoJoRf3AxaKTZS8FjxkOd4\"",
20698
+ "mtime": "2026-10-10T00:24:14.949Z",
20699
+ "size": 123184,
20700
+ "path": "../public/_nuxt/wherdr-nerd-material-1.BdvqkCqW.woff2"
20701
+ },
19560
20702
  "/_nuxt/wherdr-nerd-devicons.ZMSFX1sW.woff2": {
19561
20703
  "type": "font/woff2",
19562
20704
  "etag": "\"30f78-2RuGTMLnbTBdhlI9ExApyiUeD8c\"",
19563
- "mtime": "2026-10-08T11:44:03.927Z",
20705
+ "mtime": "2026-10-10T00:24:14.949Z",
19564
20706
  "size": 200568,
19565
20707
  "path": "../public/_nuxt/wherdr-nerd-devicons.ZMSFX1sW.woff2"
19566
20708
  },
19567
20709
  "/_nuxt/wherdr-nerd-material-2.C_flbBWO.woff2": {
19568
20710
  "type": "font/woff2",
19569
20711
  "etag": "\"1f95c-q6dAP964z2dgEvWzAYjTAzBtzBc\"",
19570
- "mtime": "2026-10-08T11:44:03.928Z",
20712
+ "mtime": "2026-10-10T00:24:14.949Z",
19571
20713
  "size": 129372,
19572
20714
  "path": "../public/_nuxt/wherdr-nerd-material-2.C_flbBWO.woff2"
19573
20715
  },
19574
20716
  "/_nuxt/wherdr-nerd-material-3.8xz7iX-Y.woff2": {
19575
20717
  "type": "font/woff2",
19576
20718
  "etag": "\"2248c-3GBJ1KjkT+xYPhza9V/DKaILa7Y\"",
19577
- "mtime": "2026-10-08T11:44:03.928Z",
20719
+ "mtime": "2026-10-10T00:24:14.949Z",
19578
20720
  "size": 140428,
19579
20721
  "path": "../public/_nuxt/wherdr-nerd-material-3.8xz7iX-Y.woff2"
19580
20722
  },
19581
- "/_nuxt/wherdr-nerd-material-1.BdvqkCqW.woff2": {
19582
- "type": "font/woff2",
19583
- "etag": "\"1e130-1GKpbVoJoRf3AxaKTZS8FjxkOd4\"",
19584
- "mtime": "2026-10-08T11:44:03.927Z",
19585
- "size": 123184,
19586
- "path": "../public/_nuxt/wherdr-nerd-material-1.BdvqkCqW.woff2"
19587
- },
19588
20723
  "/_nuxt/wherdr-nerd-material-4.CO_PudDR.woff2": {
19589
20724
  "type": "font/woff2",
19590
20725
  "etag": "\"1fa80-YcJvpFD/M3vTC4y6SMmp7s8a9gw\"",
19591
- "mtime": "2026-10-08T11:44:03.928Z",
20726
+ "mtime": "2026-10-10T00:24:14.949Z",
19592
20727
  "size": 129664,
19593
20728
  "path": "../public/_nuxt/wherdr-nerd-material-4.CO_PudDR.woff2"
19594
20729
  },
19595
- "/_nuxt/wherdr-nerd-powerline.B_kAMxXr.woff2": {
19596
- "type": "font/woff2",
19597
- "etag": "\"1bc0-iXm39wQwNbm2VjpVBOn72grfku0\"",
19598
- "mtime": "2026-10-08T11:44:03.928Z",
19599
- "size": 7104,
19600
- "path": "../public/_nuxt/wherdr-nerd-powerline.B_kAMxXr.woff2"
19601
- },
19602
20730
  "/_nuxt/wherdr-nerd-octicons.Hl4VEx8m.woff2": {
19603
20731
  "type": "font/woff2",
19604
20732
  "etag": "\"f890-FkAsW3iqENK+lU5UlSze0yqnE2s\"",
19605
- "mtime": "2026-10-08T11:44:03.928Z",
20733
+ "mtime": "2026-10-10T00:24:14.949Z",
19606
20734
  "size": 63632,
19607
20735
  "path": "../public/_nuxt/wherdr-nerd-octicons.Hl4VEx8m.woff2"
19608
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
+ },
19609
20744
  "/_nuxt/wherdr-symbols.C1LsPKmg.woff2": {
19610
20745
  "type": "font/woff2",
19611
20746
  "etag": "\"1304-Lwyr1XiDl3FK38nmS/6Pk9wfiWc\"",
19612
- "mtime": "2026-10-08T11:44:03.928Z",
20747
+ "mtime": "2026-10-10T00:24:14.950Z",
19613
20748
  "size": 4868,
19614
20749
  "path": "../public/_nuxt/wherdr-symbols.C1LsPKmg.woff2"
19615
20750
  },
19616
20751
  "/_nuxt/yzABg5xl.js": {
19617
20752
  "type": "text/javascript; charset=utf-8",
19618
20753
  "etag": "\"1c49-W7I7waHyF8BfftITNIl1xCbNFQw\"",
19619
- "mtime": "2026-10-08T11:44:03.928Z",
20754
+ "mtime": "2026-10-10T00:24:14.950Z",
19620
20755
  "size": 7241,
19621
20756
  "path": "../public/_nuxt/yzABg5xl.js"
19622
20757
  },
19623
20758
  "/_nuxt/builds/latest.json": {
19624
20759
  "type": "application/json",
19625
- "etag": "\"47-InxxMr1b0NpxElCQDYMExsdTFyM\"",
19626
- "mtime": "2026-10-08T11:44:03.903Z",
20760
+ "etag": "\"47-gUq0w2mhEU+I4n1/G1rUjuC+PWE\"",
20761
+ "mtime": "2026-10-10T00:24:14.921Z",
19627
20762
  "size": 71,
19628
20763
  "path": "../public/_nuxt/builds/latest.json"
19629
20764
  },
19630
- "/_nuxt/builds/meta/6466b0ab-7935-460c-abdf-2ea632dae6d0.json": {
20765
+ "/_nuxt/builds/meta/7afbf062-5369-4002-b20d-7a582ff6e0f1.json": {
19631
20766
  "type": "application/json",
19632
- "etag": "\"58-DWte5uFWPeAlfWNx7Bc31+0PHIM\"",
19633
- "mtime": "2026-10-08T11:44:03.899Z",
19634
- "size": 88,
19635
- "path": "../public/_nuxt/builds/meta/6466b0ab-7935-460c-abdf-2ea632dae6d0.json"
19636
- }
19637
- };
19638
-
19639
- const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
19640
- function normalizeWindowsPath(input = "") {
19641
- if (!input) {
19642
- return input;
19643
- }
19644
- return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
19645
- }
19646
- const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
19647
- const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
19648
- const _ROOT_FOLDER_RE = /^\/([A-Za-z]:)?$/;
19649
- function cwd() {
19650
- if (typeof process !== "undefined" && typeof process.cwd === "function") {
19651
- return process.cwd().replace(/\\/g, "/");
19652
- }
19653
- return "/";
19654
- }
19655
- const resolve = function(...arguments_) {
19656
- arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));
19657
- let resolvedPath = "";
19658
- let resolvedAbsolute = false;
19659
- for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {
19660
- const path = index >= 0 ? arguments_[index] : cwd();
19661
- if (!path || path.length === 0) {
19662
- continue;
19663
- }
19664
- resolvedPath = `${path}/${resolvedPath}`;
19665
- resolvedAbsolute = isAbsolute(path);
19666
- }
19667
- resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
19668
- if (resolvedAbsolute && !isAbsolute(resolvedPath)) {
19669
- return `/${resolvedPath}`;
19670
- }
19671
- return resolvedPath.length > 0 ? resolvedPath : ".";
19672
- };
19673
- function normalizeString(path, allowAboveRoot) {
19674
- let res = "";
19675
- let lastSegmentLength = 0;
19676
- let lastSlash = -1;
19677
- let dots = 0;
19678
- let char = null;
19679
- for (let index = 0; index <= path.length; ++index) {
19680
- if (index < path.length) {
19681
- char = path[index];
19682
- } else if (char === "/") {
19683
- break;
19684
- } else {
19685
- char = "/";
19686
- }
19687
- if (char === "/") {
19688
- if (lastSlash === index - 1 || dots === 1) ; else if (dots === 2) {
19689
- if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
19690
- if (res.length > 2) {
19691
- const lastSlashIndex = res.lastIndexOf("/");
19692
- if (lastSlashIndex === -1) {
19693
- res = "";
19694
- lastSegmentLength = 0;
19695
- } else {
19696
- res = res.slice(0, lastSlashIndex);
19697
- lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
19698
- }
19699
- lastSlash = index;
19700
- dots = 0;
19701
- continue;
19702
- } else if (res.length > 0) {
19703
- res = "";
19704
- lastSegmentLength = 0;
19705
- lastSlash = index;
19706
- dots = 0;
19707
- continue;
19708
- }
19709
- }
19710
- if (allowAboveRoot) {
19711
- res += res.length > 0 ? "/.." : "..";
19712
- lastSegmentLength = 2;
19713
- }
19714
- } else {
19715
- if (res.length > 0) {
19716
- res += `/${path.slice(lastSlash + 1, index)}`;
19717
- } else {
19718
- res = path.slice(lastSlash + 1, index);
19719
- }
19720
- lastSegmentLength = index - lastSlash - 1;
19721
- }
19722
- lastSlash = index;
19723
- dots = 0;
19724
- } else if (char === "." && dots !== -1) {
19725
- ++dots;
19726
- } else {
19727
- dots = -1;
19728
- }
19729
- }
19730
- return res;
19731
- }
19732
- const isAbsolute = function(p) {
19733
- return _IS_ABSOLUTE_RE.test(p);
19734
- };
19735
- const relative = function(from, to) {
19736
- const _from = resolve(from).replace(_ROOT_FOLDER_RE, "$1").split("/");
19737
- const _to = resolve(to).replace(_ROOT_FOLDER_RE, "$1").split("/");
19738
- if (_to[0][1] === ":" && _from[0][1] === ":" && _from[0] !== _to[0]) {
19739
- return _to.join("/");
19740
- }
19741
- const _fromCopy = [..._from];
19742
- for (const segment of _fromCopy) {
19743
- if (_to[0] !== segment) {
19744
- break;
19745
- }
19746
- _from.shift();
19747
- _to.shift();
19748
- }
19749
- return [..._from.map(() => ".."), ..._to].join("/");
19750
- };
19751
- const dirname = function(p) {
19752
- const segments = normalizeWindowsPath(p).replace(/\/$/, "").split("/").slice(0, -1);
19753
- if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) {
19754
- segments[0] += "/";
19755
- }
19756
- return segments.join("/") || (isAbsolute(p) ? "/" : ".");
19757
- };
19758
-
19759
- function readAsset (id) {
19760
- const serverDir = dirname(fileURLToPath(globalThis._importMeta_.url));
19761
- return promises.readFile(resolve(serverDir, assets[id].path))
19762
- }
19763
-
19764
- const publicAssetBases = {"/_nuxt/builds/meta/":{"maxAge":31536000},"/_nuxt/builds/":{"maxAge":1},"/_nuxt/":{"maxAge":31536000}};
19765
-
19766
- function isPublicAssetURL(id = '') {
19767
- if (assets[id]) {
19768
- return true
19769
- }
19770
- for (const base in publicAssetBases) {
19771
- if (id.startsWith(base)) { return true }
19772
- }
19773
- return false
19774
- }
19775
-
19776
- function getAsset (id) {
19777
- return assets[id]
19778
- }
19779
-
19780
- const METHODS = /* @__PURE__ */ new Set(["HEAD", "GET"]);
19781
- const EncodingMap = { gzip: ".gz", br: ".br" };
19782
- const _uN03ln = eventHandler((event) => {
19783
- if (event.method && !METHODS.has(event.method)) {
19784
- return;
19785
- }
19786
- let id = decodePath(
19787
- withLeadingSlash(withoutTrailingSlash(parseURL(event.path).pathname))
19788
- );
19789
- let asset;
19790
- const encodingHeader = String(
19791
- getRequestHeader(event, "accept-encoding") || ""
19792
- );
19793
- const encodings = [
19794
- ...encodingHeader.split(",").map((e) => EncodingMap[e.trim()]).filter(Boolean).sort(),
19795
- ""
19796
- ];
19797
- for (const encoding of encodings) {
19798
- for (const _id of [id + encoding, joinURL(id, "index.html" + encoding)]) {
19799
- const _asset = getAsset(_id);
19800
- if (_asset) {
19801
- asset = _asset;
19802
- id = _id;
19803
- break;
19804
- }
19805
- }
19806
- }
19807
- if (!asset) {
19808
- if (isPublicAssetURL(id)) {
19809
- removeResponseHeader(event, "Cache-Control");
19810
- throw createError$1({ statusCode: 404 });
19811
- }
19812
- return;
19813
- }
19814
- if (asset.encoding !== void 0) {
19815
- appendResponseHeader(event, "Vary", "Accept-Encoding");
19816
- }
19817
- const ifNotMatch = getRequestHeader(event, "if-none-match") === asset.etag;
19818
- if (ifNotMatch) {
19819
- setResponseStatus(event, 304, "Not Modified");
19820
- return "";
19821
- }
19822
- const ifModifiedSinceH = getRequestHeader(event, "if-modified-since");
19823
- const mtimeDate = new Date(asset.mtime);
19824
- if (ifModifiedSinceH && asset.mtime && new Date(ifModifiedSinceH) >= mtimeDate) {
19825
- setResponseStatus(event, 304, "Not Modified");
19826
- return "";
19827
- }
19828
- if (asset.type && !getResponseHeader(event, "Content-Type")) {
19829
- setResponseHeader(event, "Content-Type", asset.type);
19830
- }
19831
- if (asset.etag && !getResponseHeader(event, "ETag")) {
19832
- setResponseHeader(event, "ETag", asset.etag);
19833
- }
19834
- if (asset.mtime && !getResponseHeader(event, "Last-Modified")) {
19835
- setResponseHeader(event, "Last-Modified", mtimeDate.toUTCString());
19836
- }
19837
- if (asset.encoding && !getResponseHeader(event, "Content-Encoding")) {
19838
- setResponseHeader(event, "Content-Encoding", asset.encoding);
19839
- }
19840
- if (asset.size > 0 && !getResponseHeader(event, "Content-Length")) {
19841
- setResponseHeader(event, "Content-Length", asset.size);
19842
- }
19843
- return readAsset(id);
19844
- });
19845
-
19846
- const MAX_SESSION_DAYS = [1, 7, 30, 90, 365];
19847
- const DEFAULT_MAX_SESSION_DAYS = 7;
19848
- const DAY_MS = 24 * 3600 * 1e3;
19849
- function isMaxSessionDays(v) {
19850
- return MAX_SESSION_DAYS.includes(v);
19851
- }
19852
-
19853
- var __defProp = Object.defineProperty;
19854
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
19855
- var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
19856
- const SESSION_MS = 12 * 3600 * 1e3;
19857
- const RENEW_MS = 10 * 60 * 1e3;
19858
- const CHALLENGE_MS = 5 * 60 * 1e3;
19859
- const AUTH_COOKIE = "hw_session";
19860
- const CHALLENGE_COOKIE = "hw_challenge";
19861
- class AuthError extends Error {
19862
- constructor(code, message) {
19863
- super(message);
19864
- __publicField(this, "code");
19865
- 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"
19866
20771
  }
19867
- }
19868
- function createAuth({ dataDir, log: log2 = () => {
19869
- }, passkeyUser = "herdr" }) {
19870
- const FILE = path$1.join(dataDir, "auth.json");
19871
- let db = { secret: null, generation: 0, credentials: [] };
19872
- try {
19873
- db = { ...db, ...JSON.parse(fs.readFileSync(FILE, "utf8")) };
19874
- } catch {
20772
+ };
20773
+
20774
+ const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
20775
+ function normalizeWindowsPath(input = "") {
20776
+ if (!input) {
20777
+ return input;
19875
20778
  }
19876
- const save = () => {
19877
- fs.mkdirSync(dataDir, { recursive: true });
19878
- fs.writeFileSync(FILE, JSON.stringify(db, null, 2) + "\n", { mode: 384 });
19879
- };
19880
- if (!db.secret) {
19881
- db.secret = crypto.randomBytes(32).toString("hex");
19882
- 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, "/");
19883
20787
  }
19884
- const bootstrapToken = crypto.randomBytes(24).toString("base64url");
19885
- if (!db.credentials.length) log2(`First passkey: bootstrap token ${bootstrapToken}`);
19886
- const enabled = () => db.credentials.length > 0;
19887
- const sign = (v) => crypto.createHmac("sha256", db.secret).update(v).digest("base64url");
19888
- const safeEq = (a, b) => {
19889
- const x = Buffer.from(String(a));
19890
- const y = Buffer.from(String(b));
19891
- return x.length === y.length && crypto.timingSafeEqual(x, y);
19892
- };
19893
- const readCookie = (req) => {
19894
- const c = String(req.headers.cookie || "").split(/;\s*/).find((s) => s.startsWith(`${AUTH_COOKIE}=`));
19895
- if (!c) return null;
19896
- try {
19897
- return decodeURIComponent(c.slice(AUTH_COOKIE.length + 1));
19898
- } catch {
19899
- return null;
19900
- }
19901
- };
19902
- const challengeClient = (req) => {
19903
- const c = String(req.headers.cookie || "").split(/;\s*/).find((s) => s.startsWith(`${CHALLENGE_COOKIE}=`));
19904
- return (c == null ? void 0 : c.slice(CHALLENGE_COOKIE.length + 1)) || null;
19905
- };
19906
- const checkBootstrap = (body) => {
19907
- if (!enabled() && !safeEq(String((body == null ? void 0 : body.bootstrapToken) || ""), bootstrapToken)) {
19908
- 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;
19909
20798
  }
19910
- };
19911
- const maxSessionMs = () => {
19912
- var _a;
19913
- return ((_a = db.maxSessionDays) != null ? _a : DEFAULT_MAX_SESSION_DAYS) * DAY_MS;
19914
- };
19915
- function session(req, now = Date.now()) {
19916
- const v = readCookie(req);
19917
- if (!v) return null;
19918
- const parts = v.split(".");
19919
- if (parts.length !== 3 && parts.length !== 4) return null;
19920
- const mac = parts.pop();
19921
- if (!mac || parts[1] !== String(db.generation) || !safeEq(mac, sign(parts.join(".")))) return null;
19922
- const exp = Number(parts[0]);
19923
- const since = parts.length === 3 ? Number(parts[2]) : exp - SESSION_MS;
19924
- if (!(exp >= now) || !Number.isFinite(since)) return null;
19925
- if (now > since + maxSessionMs() + SESSION_MS) return null;
19926
- return { exp, since };
19927
- }
19928
- function isUnlocked(req) {
19929
- return !enabled() || session(req) !== null;
19930
- }
19931
- const secure = (req) => req.headers["x-forwarded-proto"] === "https" || String(req.headers.origin || "").startsWith("https://") ? "; Secure" : "";
19932
- function sessionCookie(req, now = Date.now(), since = now) {
19933
- const exp = now + SESSION_MS;
19934
- const payload = `${exp}.${db.generation}.${since}`;
19935
- return `${AUTH_COOKIE}=${payload}.${sign(payload)}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${SESSION_MS / 1e3}${secure(req)}`;
19936
- }
19937
- const clearCookie = (req) => `${AUTH_COOKIE}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0${secure(req)}`;
19938
- function renew(req, now = Date.now()) {
19939
- if (!enabled()) return null;
19940
- const s = session(req, now);
19941
- if (s === null || s.exp - now > SESSION_MS - RENEW_MS) return null;
19942
- return { cookie: sessionCookie(req, now, s.since), expiresAt: now + SESSION_MS };
19943
- }
19944
- function relying(req) {
19945
- const origin = String(req.headers.origin || "");
19946
- const { hostname } = new URL(origin);
19947
- return { origin, rpID: hostname };
19948
- }
19949
- const challenges = /* @__PURE__ */ new Map();
19950
- const challengeKey = (kind, client) => `${kind}:${client}`;
19951
- const issueChallenge = (req, kind, value) => {
19952
- const client = crypto.randomBytes(24).toString("base64url");
19953
- for (const [key, challenge] of challenges) if (challenge.exp < Date.now()) challenges.delete(key);
19954
- challenges.set(challengeKey(kind, client), { value, exp: Date.now() + CHALLENGE_MS });
19955
- return `${CHALLENGE_COOKIE}=${client}; Path=/api/auth/; HttpOnly; SameSite=Strict; Max-Age=${CHALLENGE_MS / 1e3}${secure(req)}`;
19956
- };
19957
- const takeChallenge = (req, kind) => {
19958
- const client = challengeClient(req);
19959
- const key = challengeKey(kind, client || "");
19960
- const c = challenges.get(key);
19961
- challenges.delete(key);
19962
- if (!c || c.exp < Date.now()) throw new AuthError("challenge", "Challenge expired, try again");
19963
- return c.value;
19964
- };
19965
- async function registerOptions(req, body = {}) {
19966
- if (enabled() && !isUnlocked(req)) throw new AuthError("locked", "Unlock first");
19967
- checkBootstrap(body);
19968
- const { rpID } = relying(req);
19969
- const opts = await generateRegistrationOptions({
19970
- rpName: "wherdr",
19971
- rpID,
19972
- userName: passkeyUser,
19973
- userDisplayName: passkeyUser,
19974
- attestationType: "none",
19975
- excludeCredentials: db.credentials.map((c) => ({ id: c.id, transports: c.transports })),
19976
- authenticatorSelection: { residentKey: "preferred", userVerification: "required" }
19977
- });
19978
- return { ...opts, __cookie: issueChallenge(req, "reg", opts.challenge) };
19979
- }
19980
- async function registerVerify(req, body) {
19981
- if (enabled() && !isUnlocked(req)) throw new AuthError("locked", "Unlock first");
19982
- checkBootstrap(body);
19983
- const { origin, rpID } = relying(req);
19984
- const v = await verifyRegistrationResponse({
19985
- response: body.response,
19986
- expectedChallenge: takeChallenge(req, "reg"),
19987
- expectedOrigin: origin,
19988
- expectedRPID: rpID,
19989
- requireUserVerification: true
19990
- });
19991
- if (!v.verified || !v.registrationInfo) throw new AuthError("rejected", "Key rejected");
19992
- const { credential } = v.registrationInfo;
19993
- db.credentials.push({
19994
- id: credential.id,
19995
- publicKey: Buffer.from(credential.publicKey).toString("base64url"),
19996
- counter: credential.counter,
19997
- transports: credential.transports || [],
19998
- name: String(body.name || "Device").slice(0, 40),
19999
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
20000
- });
20001
- save();
20002
- log2(`lock: key registered (${db.credentials.length})`);
20003
- return { ok: true, __cookie: sessionCookie(req) };
20004
- }
20005
- async function loginOptions(req) {
20006
- const { rpID } = relying(req);
20007
- const opts = await generateAuthenticationOptions({
20008
- rpID,
20009
- userVerification: "required",
20010
- allowCredentials: db.credentials.map((c) => ({ id: c.id, transports: c.transports }))
20011
- });
20012
- return { ...opts, __cookie: issueChallenge(req, "auth", opts.challenge) };
20013
- }
20014
- async function loginVerify(req, body) {
20015
- const { origin, rpID } = relying(req);
20016
- const cred = db.credentials.find((c) => c.id === (body.response && body.response.id));
20017
- if (!cred) throw new AuthError("unknown_key", "Key unknown on this server");
20018
- const v = await verifyAuthenticationResponse({
20019
- response: body.response,
20020
- expectedChallenge: takeChallenge(req, "auth"),
20021
- expectedOrigin: origin,
20022
- expectedRPID: rpID,
20023
- requireUserVerification: true,
20024
- credential: { id: cred.id, publicKey: Buffer.from(cred.publicKey, "base64url"), counter: cred.counter, transports: cred.transports }
20025
- });
20026
- if (!v.verified) throw new AuthError("rejected", "Unlock rejected");
20027
- cred.counter = v.authenticationInfo.newCounter;
20028
- cred.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
20029
- save();
20030
- return { ok: true, __cookie: sessionCookie(req) };
20799
+ resolvedPath = `${path}/${resolvedPath}`;
20800
+ resolvedAbsolute = isAbsolute(path);
20031
20801
  }
20032
- function disable(req) {
20033
- if (!isUnlocked(req)) throw new AuthError("locked", "Unlock first");
20034
- db.credentials = [];
20035
- db.generation += 1;
20036
- save();
20037
- log2("lock disabled");
20038
- return { ok: true, __cookie: clearCookie(req) };
20802
+ resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
20803
+ if (resolvedAbsolute && !isAbsolute(resolvedPath)) {
20804
+ return `/${resolvedPath}`;
20039
20805
  }
20040
- function lockAll(req) {
20041
- if (!enabled() || !isUnlocked(req)) throw new AuthError("locked", "Unlock first");
20042
- db.generation += 1;
20043
- save();
20044
- log2("lock: all devices locked");
20045
- 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
+ }
20046
20864
  }
20047
- function setMaxSession(req, days) {
20048
- if (!enabled() || !isUnlocked(req)) throw new AuthError("locked", "Unlock first");
20049
- if (!isMaxSessionDays(days)) throw new AuthError("invalid", "Invalid duration");
20050
- db.maxSessionDays = days;
20051
- save();
20052
- 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("/");
20053
20875
  }
20054
- function status(req, { resume = false, now = Date.now() } = {}) {
20055
- var _a, _b;
20056
- const devices = db.credentials.map((c) => ({ name: c.name, createdAt: c.createdAt, lastUsed: c.lastUsed || null }));
20057
- if (!enabled()) return { enabled: false, unlocked: true, devices };
20058
- const s = session(req, now);
20059
- const deadline = s ? s.since + maxSessionMs() : 0;
20060
- if (!s || resume && now >= deadline) {
20061
- 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;
20062
20880
  }
20063
- const renewed = renew(req, now);
20064
- return {
20065
- enabled: true,
20066
- unlocked: true,
20067
- expiresAt: (_a = renewed == null ? void 0 : renewed.expiresAt) != null ? _a : s.exp,
20068
- deadline,
20069
- now,
20070
- maxSessionDays: (_b = db.maxSessionDays) != null ? _b : DEFAULT_MAX_SESSION_DAYS,
20071
- devices,
20072
- ...renewed ? { __cookie: renewed.cookie } : {}
20073
- };
20881
+ _from.shift();
20882
+ _to.shift();
20074
20883
  }
20075
- return {
20076
- isUnlocked,
20077
- status,
20078
- renew,
20079
- registerOptions,
20080
- registerVerify,
20081
- loginOptions,
20082
- loginVerify,
20083
- disable,
20084
- lockAll,
20085
- setMaxSession,
20086
- lock: (req) => ({ ok: true, __cookie: clearCookie(req) }),
20087
- // For tests.
20088
- _sessionCookie: sessionCookie,
20089
- _db: () => db,
20090
- _bootstrapToken: () => bootstrapToken,
20091
- _takeChallenge: takeChallenge
20092
- };
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))
20093
20897
  }
20094
20898
 
20095
- const auth = createAuth({ dataDir: DATA_DIR, log, passkeyUser: PASSKEY_USER });
20096
- const reqOf = (event) => ({ headers: getRequestHeaders(event) });
20097
- function sameOrigin(headers) {
20098
- const o = headers.origin;
20099
- if (!o) return false;
20100
- try {
20101
- return new URL(o).host === headers.host;
20102
- } catch {
20103
- 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
20104
20904
  }
20905
+ for (const base in publicAssetBases) {
20906
+ if (id.startsWith(base)) { return true }
20907
+ }
20908
+ return false
20105
20909
  }
20106
- const BODY_MAX = 256 * 1024;
20107
- function sendError(event, status, body) {
20108
- setResponseStatus(event, status);
20109
- setResponseHeader(event, "cache-control", "no-store");
20110
- return body;
20910
+
20911
+ function getAsset (id) {
20912
+ return assets[id]
20111
20913
  }
20112
- function defineApi(fn, opts = {}) {
20113
- return defineEventHandler(async (event) => {
20114
- const url = getRequestURL(event);
20115
- try {
20116
- let body = {};
20117
- if (event.method === "POST") {
20118
- const headers = getRequestHeaders(event);
20119
- const ctype = String(headers["content-type"] || "");
20120
- const typeOk = opts.raw ? opts.raw.test(ctype) : ctype.startsWith("application/json");
20121
- if (!sameOrigin(headers) || !typeOk) return sendError(event, 403, { error: "Origin refused" });
20122
- const max = opts.raw ? opts.max || 20 * 1024 * 1024 : BODY_MAX;
20123
- const tooLarge = opts.raw ? opts.tooLarge || "Image too large (20 MB max)" : "Request too large";
20124
- if (Number(headers["content-length"] || 0) > max) throw new HerdrError("too_large", tooLarge);
20125
- const raw = await readRawBody(event, false);
20126
- if (raw && raw.length > max) throw new HerdrError("too_large", tooLarge);
20127
- if (opts.raw) body = { data: raw || Buffer.alloc(0), ctype };
20128
- else {
20129
- try {
20130
- body = raw && raw.length ? JSON.parse(raw.toString("utf8")) : {};
20131
- } catch {
20132
- throw new HerdrError("bad_json", "Invalid JSON");
20133
- }
20134
- }
20135
- }
20136
- const out = await fn(event, body);
20137
- setResponseHeader(event, "cache-control", "no-store");
20138
- if (out && typeof out === "object" && "__cookie" in out) {
20139
- const o = out;
20140
- setResponseHeader(event, "set-cookie", String(o.__cookie));
20141
- 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;
20142
20939
  }
20143
- return out;
20144
- } catch (e) {
20145
- const err = e;
20146
- const known = e instanceof HerdrError || url.pathname.startsWith("/api/auth/") && Boolean(err.code);
20147
- const code = known ? err.code : "internal";
20148
- if (!known) log("error", event.method, url.pathname, e);
20149
- const status = err.code === "unreachable" ? 503 : err.code === "locked" ? 401 : 400;
20150
- return sendError(event, status, { error: err.message, code });
20151
20940
  }
20152
- });
20153
- }
20154
- function sendImage(event, img) {
20155
- setResponseHeader(event, "content-type", img.type);
20156
- setResponseHeader(event, "cache-control", "private, max-age=86400");
20157
- setResponseHeader(event, "x-content-type-options", "nosniff");
20158
- setResponseHeader(event, "content-security-policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox");
20159
- return img.body;
20160
- }
20161
-
20162
- function hostname(value) {
20163
- try {
20164
- if (!value || /[\s/@?#]/.test(value)) return null;
20165
- const url = new URL(`http://${value}`);
20166
- if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) return null;
20167
- return url.hostname.replace(/^\[|\]$/g, "").toLowerCase().replace(/\.$/, "");
20168
- } catch {
20169
- return null;
20170
20941
  }
20171
- }
20172
- function allowedHosts(appUrl2 = process.env.APP_URL || "", extra = process.env.HERDR_WEB_ALLOWED_HOSTS || "", interfaces = os.networkInterfaces()) {
20173
- const names = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", os.hostname().toLowerCase()]);
20174
- try {
20175
- if (appUrl2) names.add(new URL(appUrl2).hostname.toLowerCase().replace(/^\[|\]$/g, ""));
20176
- } catch {
20942
+ if (!asset) {
20943
+ if (isPublicAssetURL(id)) {
20944
+ removeResponseHeader(event, "Cache-Control");
20945
+ throw createError$1({ statusCode: 404 });
20946
+ }
20947
+ return;
20177
20948
  }
20178
- for (const addresses of Object.values(interfaces)) {
20179
- for (const address of addresses || []) names.add(address.address.toLowerCase().replace(/%.+$/, ""));
20949
+ if (asset.encoding !== void 0) {
20950
+ appendResponseHeader(event, "Vary", "Accept-Encoding");
20180
20951
  }
20181
- for (const item of extra.split(",")) {
20182
- const name = hostname(item.trim());
20183
- 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 "";
20184
20956
  }
20185
- return names;
20186
- }
20187
- function hostAllowed(host, names = allowedHosts()) {
20188
- const name = hostname(host || "");
20189
- return Boolean(name && names.has(name));
20190
- }
20191
- function hostRefusalIsHtml(path, accept) {
20192
- return !path.startsWith("/api/") && /\btext\/html\b/i.test(Array.isArray(accept) ? accept.join(",") : accept || "");
20193
- }
20194
- const HOST_REFUSED_PAGE = `<!doctype html>
20195
- <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
20196
- <meta name="robots" content="noindex"><title>wherdr \xB7 address not enabled</title>
20197
- <style>
20198
- :root{color-scheme:dark}*{box-sizing:border-box}
20199
- 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))}
20200
- main{max-width:440px;border:1px solid #3a3f47;padding:24px}
20201
- .tag{font:11px/1 "JetBrains Mono",ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;color:#9aa0a8;margin:0 0 16px}
20202
- h1{font:600 20px/1.25 Archivo,system-ui,sans-serif;margin:0 0 8px}
20203
- p{margin:0 0 8px}hr{border:0;border-top:1px solid #3a3f47;margin:20px 0}
20204
- </style></head><body><main>
20205
- <p class="tag">wherdr \xB7 403</p>
20206
- <h1>This address isn't enabled yet.</h1>
20207
- <p>On the computer, open wherdr \u2192 Settings \u203A Phone (or press Check), then reload.</p>
20208
- <hr>
20209
- <h1 lang="fr">Cette adresse n\u2019est pas encore activ\xE9e.</h1>
20210
- <p lang="fr">Sur l\u2019ordinateur, ouvre wherdr \u2192 R\xE9glages \u203A T\xE9l\xE9phone (ou appuie sur V\xE9rifier), puis recharge.</p>
20211
- </main></body></html>
20212
- `;
20213
- function crossSiteRequest(headers) {
20214
- const site = headers["sec-fetch-site"];
20215
- return typeof site === "string" && site !== "same-origin" && site !== "none";
20216
- }
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
+ });
20217
20980
 
20218
- const needsUnlock = (p) => p.startsWith("/api/") && !p.startsWith("/api/auth/") || p.startsWith("/uploads/");
20219
- 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);
20220
20985
  if (!hostAllowed(event.node.req.headers.host)) {
20221
20986
  if (hostRefusalIsHtml(event.path.split("?")[0], event.node.req.headers.accept)) {
20222
20987
  setResponseStatus(event, 403);
20223
20988
  setResponseHeader(event, "content-type", "text/html; charset=utf-8");
20224
20989
  setResponseHeader(event, "cache-control", "no-store");
20225
- return HOST_REFUSED_PAGE;
20990
+ return hostRefusedPage();
20226
20991
  }
20227
20992
  return sendError(event, 403, { error: "Host not allowed", code: "host" });
20228
20993
  }
@@ -20270,6 +21035,7 @@ const _lazy_2uy6Z9 = () => import('../routes/api/effort.post.mjs');
20270
21035
  const _lazy_7FhJOq = () => import('../routes/api/efforts.get.mjs');
20271
21036
  const _lazy_Wg_MPT = () => import('../routes/api/file.post.mjs');
20272
21037
  const _lazy_kEWyNy = () => import('../routes/api/gitroot.get.mjs');
21038
+ const _lazy_1Vqvr9 = () => import('../routes/api/health.get.mjs');
20273
21039
  const _lazy_U4OEhz = () => import('../routes/api/input.post.mjs');
20274
21040
  const _lazy_PDyMr9 = () => import('../routes/api/interrupt.post.mjs');
20275
21041
  const _lazy_mtPGaX = () => import('../routes/api/isgit.get.mjs');
@@ -20316,6 +21082,8 @@ const _lazy_TqhtLZ = () => import('../routes/api/space.post.mjs');
20316
21082
  const _lazy_oeuOKk = () => import('../routes/api/state.get.mjs');
20317
21083
  const _lazy_ecbrXA = () => import('../routes/api/unqueue.post.mjs');
20318
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');
20319
21087
  const _lazy_5f22vB = () => import('../routes/api/upload.post.mjs');
20320
21088
  const _lazy_9iwHBL = () => import('../routes/api/worktrees.get.mjs');
20321
21089
  const _lazy_IA8rJi = () => import('../routes/api/worktrees/remove.post.mjs');
@@ -20360,6 +21128,7 @@ const handlers = [
20360
21128
  { route: '/api/efforts', handler: _lazy_7FhJOq, lazy: true, middleware: false, method: "get" },
20361
21129
  { route: '/api/file', handler: _lazy_Wg_MPT, lazy: true, middleware: false, method: "post" },
20362
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" },
20363
21132
  { route: '/api/input', handler: _lazy_U4OEhz, lazy: true, middleware: false, method: "post" },
20364
21133
  { route: '/api/interrupt', handler: _lazy_PDyMr9, lazy: true, middleware: false, method: "post" },
20365
21134
  { route: '/api/isgit', handler: _lazy_mtPGaX, lazy: true, middleware: false, method: "get" },
@@ -20406,6 +21175,8 @@ const handlers = [
20406
21175
  { route: '/api/state', handler: _lazy_oeuOKk, lazy: true, middleware: false, method: "get" },
20407
21176
  { route: '/api/unqueue', handler: _lazy_ecbrXA, lazy: true, middleware: false, method: "post" },
20408
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" },
20409
21180
  { route: '/api/upload', handler: _lazy_5f22vB, lazy: true, middleware: false, method: "post" },
20410
21181
  { route: '/api/worktrees', handler: _lazy_9iwHBL, lazy: true, middleware: false, method: "get" },
20411
21182
  { route: '/api/worktrees/remove', handler: _lazy_IA8rJi, lazy: true, middleware: false, method: "post" },
@@ -20877,5 +21648,5 @@ setupGracefulShutdown(listener, nitroApp);
20877
21648
  }
20878
21649
  const nodeServer = {};
20879
21650
 
20880
- 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, settingsTabs as b7, tabsOfRow as b8, hasTranscript as b9, encodePath as bA, joinRelativeURL as bB, defineRenderHandler as bC, createError$1 as bD, destr as bE, getRouteRules as bF, relative as bG, joinURL as bH, getResponseStatusText as bI, getResponseStatus as bJ, useNitroApp as bK, nodeServer as bL, SEARCH_DEADLINE_MS as ba, SEARCH_MAX_RESULTS as bb, spaceTitle as bc, markSeen as bd, listSessions as be, openSession as bf, PANE_DIRECTIONS as bg, cancelQueued as bh, installMode as bi, updateCommand as bj, compareVersions as bk, allMachines as bl, recentDirs as bm, joinId as bn, useStorage as bo, setResponseHeader as bp, getRouterParam as bq, readUpload as br, defineWebSocketHandler as bs, eventClients as bt, getStateJson as bu, hostAllowed as bv, sameOrigin as bw, mirrorSize as bx, openTerm as by, useRuntimeConfig 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 };
20881
21652
  //# sourceMappingURL=nitro.mjs.map