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