herdr-remote 0.2.2 → 0.2.4

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/dist/tui.mjs CHANGED
@@ -360,7 +360,7 @@ var require_en = __commonJS({
360
360
  "use strict";
361
361
  module.exports = {
362
362
  "app.name": "Herdr Remote",
363
- "app.tagline": "Remote browser access to your Herdr workspaces",
363
+ "app.tagline": "Remote browser access to Herdr workspaces",
364
364
  "common.yes": "Yes",
365
365
  "common.no": "No",
366
366
  "common.on": "on",
@@ -393,12 +393,12 @@ var require_en = __commonJS({
393
393
  "nav.about": "Language & about",
394
394
  "mode.local": "This machine only",
395
395
  "mode.lan": "Local network / Tailscale",
396
- "mode.official": "Official relay (no server needed)",
396
+ "mode.official": "Official relay",
397
397
  "mode.remote": "Self-hosted relay",
398
- "mode.local.description": "The relay listens on 127.0.0.1. Only a browser on this machine can reach it.",
399
- "mode.lan.description": "The relay listens on 0.0.0.0 (every interface), so phones on your LAN or tailnet can reach it.",
400
- "mode.official.description": "Reach this machine from anywhere through the relay run by the Herdr Remote project. Nothing to deploy. Your terminal traffic passes through a server you do not control, and only devices you pair can open it.",
401
- "mode.remote.description": "No local relay. The workstation dials a relay you run, which is the only way in from outside your network.",
398
+ "mode.local.description": "Relay listens on 127.0.0.1. Local browser only.",
399
+ "mode.lan.description": "Relay listens on 0.0.0.0. Accessible over LAN or Tailscale.",
400
+ "mode.official.description": "Connect via official relay. No server needed.",
401
+ "mode.remote.description": "Connect via self-hosted relay for external access.",
402
402
  "overview.title": "Status",
403
403
  "overview.mode": "Access mode",
404
404
  "overview.relay": "Relay",
@@ -413,20 +413,20 @@ var require_en = __commonJS({
413
413
  "overview.relayRemote": "remote, {url}",
414
414
  "overview.socketMissing": "not found \u2014 is Herdr running?",
415
415
  "overview.unreachable": "unreachable: {message}",
416
- "overview.notStarted": "Services are not running. Open the Services tab to start them.",
416
+ "overview.notStarted": "Services not running. Open Services tab to start.",
417
417
  "pair.title": "Pair a device",
418
- "pair.generate": "Generate a one-time pairing code",
419
- "pair.regenerate": "Generate another code",
420
- "pair.working": "Starting services and requesting a code\u2026",
418
+ "pair.generate": "Generate pairing code",
419
+ "pair.regenerate": "Regenerate code",
420
+ "pair.working": "Generating pairing code\u2026",
421
421
  "pair.code": "Pairing code",
422
- "pair.url": "Open on your phone",
422
+ "pair.url": "Access URL",
423
423
  "pair.expires": "Expires in {minutes} (at {time})",
424
- "pair.expired": "This code has expired. Generate a new one.",
425
- "pair.instructions": "Open the URL, enter the code, and the browser is paired for good.",
426
- "pair.qrHint": "Scan the code with your phone camera to open the URL.",
427
- "pair.qrUnavailable": "The terminal is too narrow for a QR code; use the URL above.",
428
- "pair.failed": "Could not create a pairing code: {message}",
429
- "pair.hostOffline": "The relay has no workstation connected yet. Start the services first.",
424
+ "pair.expired": "Code expired. Generate a new one.",
425
+ "pair.instructions": "Open URL and enter code to pair.",
426
+ "pair.qrHint": "Scan QR code to open URL.",
427
+ "pair.qrUnavailable": "Terminal too narrow for QR code; use URL above.",
428
+ "pair.failed": "Could not create pairing code: {message}",
429
+ "pair.hostOffline": "Relay has no workstation connected. Start services first.",
430
430
  "services.title": "Services",
431
431
  "services.start": "Start services",
432
432
  "services.stop": "Stop services",
@@ -436,71 +436,74 @@ var require_en = __commonJS({
436
436
  "services.restarted": "Services restarted.",
437
437
  "services.startFailed": "Could not start services: {message}",
438
438
  "services.stopFailed": "Could not stop services: {message}",
439
- "services.unsavedBlocked": "There are unsaved changes. Save them first, or the services restart on the configuration still on disk.",
440
- "services.managedNotice": "These services are managed by {manager}; the keep-alive unit was used to apply the change.",
441
- "services.logs": "Recent log output",
442
- "services.logEmpty": "No log output yet.",
439
+ "services.unsavedBlocked": "Unsaved changes. Save before restarting.",
440
+ "services.managedNotice": "Managed by {manager}; applied via keep-alive service.",
441
+ "services.logs": "Recent logs",
442
+ "services.logEmpty": "No logs.",
443
443
  "services.logRelay": "Relay",
444
444
  "services.logHost": "Host connector",
445
445
  "relay.title": "Relay settings",
446
446
  "relay.settings": "Settings",
447
+ "relay.locked": "fixed",
448
+ "relay.officialFixed": "The official relay address is fixed and cannot be edited.",
449
+ "relay.officialHint": "Official relay: address is fixed and no password is needed.",
447
450
  "relay.password": "Relay password",
448
- "relay.passwordHint": "Must match RELAY_PASSWORD on the relay. Leave empty for a public relay.",
451
+ "relay.passwordHint": "Must match RELAY_PASSWORD on relay. Leave empty if public.",
449
452
  "relay.passwordSet": "set",
450
- "relay.passwordEmpty": "not set (public relay)",
451
- "relay.passwordSaved": "Relay password saved. Restart the services to apply it.",
452
- "relay.showPassword": "Show the password",
453
- "relay.hidePassword": "Hide the password",
454
- "relay.identity": "Workstation id",
455
- "relay.regenerate": "Generate a new workstation identity",
456
- "relay.regenerated": "New workstation identity generated. Restart the services to enrol again.",
457
- "relay.envSnippet": "Command for your relay server",
458
- "relay.envHint": "Run this on the relay host.",
459
- "relay.test": "Test the relay connection",
453
+ "relay.passwordEmpty": "not set (public)",
454
+ "relay.passwordSaved": "Relay password saved. Restart services to apply.",
455
+ "relay.showPassword": "Show password",
456
+ "relay.hidePassword": "Hide password",
457
+ "relay.identity": "Workstation ID",
458
+ "relay.regenerate": "Generate new ID",
459
+ "relay.regenerated": "New workstation identity generated. Restart services to re-register.",
460
+ "relay.envSnippet": "Relay startup command",
461
+ "relay.envHint": "Run this command on the relay host.",
462
+ "relay.test": "Test relay connection",
460
463
  "relay.testOk": "Relay reachable: version {version}, {hosts} workstation(s) connected.",
461
464
  "relay.testFailed": "Relay unreachable: {message}",
462
- "relay.selectAddress": "Choose the address to advertise",
465
+ "relay.selectAddress": "Choose advertised address",
463
466
  "relay.listenAddress": "Listen address",
464
- "relay.listenAddressHint": "This is the server bind address. The advertised address below is what browsers open.",
467
+ "relay.listenAddressHint": "Server bind address. Browsers use advertised address below.",
465
468
  "relay.addressTailscale": "Tailscale",
466
469
  "relay.addressLan": "LAN",
467
470
  "relay.addressVirtual": "virtual",
468
471
  "relay.addressLoopback": "loopback",
469
- "relay.noAddresses": "No non-loopback addresses found. Connect to a network or start Tailscale.",
470
- "relay.docsHint": "Running your own relay: see docs/self-hosted-relay.md",
472
+ "relay.noAddresses": "No non-loopback addresses found.",
473
+ "relay.docsHint": "Self-hosting guide: docs/self-hosted-relay.md",
471
474
  "keepalive.title": "Keep-alive service",
472
475
  "keepalive.manager": "Manager",
473
476
  "keepalive.state": "State",
474
477
  "keepalive.install": "Install and start",
475
478
  "keepalive.uninstall": "Stop and remove",
476
- "keepalive.restart": "Restart the service",
479
+ "keepalive.restart": "Restart service",
477
480
  "keepalive.installed": "Keep-alive installed with {manager}.",
478
481
  "keepalive.uninstalled": "Keep-alive removed.",
479
482
  "keepalive.restarted": "Keep-alive service restarted.",
480
483
  "keepalive.failed": "Keep-alive operation failed: {message}",
481
484
  "keepalive.unitPath": "Unit file",
482
- "keepalive.logsHint": "Follow logs with: {command}",
483
- "keepalive.linger": "Start at boot without logging in",
484
- "keepalive.lingerEnabled": "Lingering is enabled: the service starts at boot.",
485
- "keepalive.lingerDisabled": "Lingering is off, so the service only runs while you are logged in.",
485
+ "keepalive.logsHint": "View logs: {command}",
486
+ "keepalive.linger": "Start at boot",
487
+ "keepalive.lingerEnabled": "Start at boot enabled.",
488
+ "keepalive.lingerDisabled": "Start at boot disabled (runs while logged in).",
486
489
  "keepalive.enableLinger": "Enable start at boot",
487
- "keepalive.lingerDone": "Lingering enabled for {username}.",
488
- "keepalive.fallbackNote": "No system service manager is available; a supervised background process is used instead. It does not survive a reboot.",
490
+ "keepalive.lingerDone": "Start at boot enabled for {username}.",
491
+ "keepalive.fallbackNote": "No system service manager found; background process will not survive reboot.",
489
492
  "herdr.title": "Herdr integration",
490
493
  "herdr.socketPath": "Socket path",
491
494
  "herdr.args": "Extra arguments",
492
495
  "herdr.plugin": "Plugin registration",
493
496
  "herdr.pluginRegistered": "Registered with Herdr.",
494
497
  "herdr.pluginMissing": "Not registered with Herdr.",
495
- "herdr.register": "Register this package as a Herdr plugin",
496
- "herdr.unregister": "Unregister the Herdr plugin",
498
+ "herdr.register": "Register as Herdr plugin",
499
+ "herdr.unregister": "Unregister plugin",
497
500
  "herdr.registerDone": "Registered: {path}",
498
501
  "herdr.unregisterDone": "Plugin unregistered.",
499
502
  "herdr.registerFailed": "Registration failed: {message}",
500
- "herdr.cliMissing": "The herdr command was not found on PATH.",
503
+ "herdr.cliMissing": "Command herdr not found in PATH.",
501
504
  "about.title": "Language & about",
502
505
  "about.language": "Interface language",
503
- "about.languageAuto": "Follow the system ({detected})",
506
+ "about.languageAuto": "Follow system ({detected})",
504
507
  "about.languageZh": "\u4E2D\u6587",
505
508
  "about.languageEn": "English",
506
509
  "about.version": "Version",
@@ -510,20 +513,20 @@ var require_en = __commonJS({
510
513
  "about.docs": "Self-hosting guide: docs/self-hosted-relay.md",
511
514
  "wizard.title": "First-time setup",
512
515
  "wizard.step": "Step {current} of {total}",
513
- "wizard.languageTitle": "Choose your language",
514
- "wizard.accessTitle": "How do you want to reach this machine?",
515
- "wizard.accessHint": "You can change this later on the Relay screen.",
516
- "wizard.addressTitle": "Which address should phones use?",
517
- "wizard.relayTitle": "Your relay server",
516
+ "wizard.languageTitle": "Choose language",
517
+ "wizard.accessTitle": "Access mode",
518
+ "wizard.accessHint": "Can be changed later in Relay settings.",
519
+ "wizard.addressTitle": "Browser access address",
520
+ "wizard.relayTitle": "Relay server",
518
521
  "wizard.relayUrlLabel": "Relay URL (wss://\u2026)",
519
- "wizard.relayHint": "The relay must already be running. See docs/self-hosted-relay.md to set one up.",
522
+ "wizard.relayHint": "Relay must be running. See docs/self-hosted-relay.md.",
520
523
  "wizard.passwordTitle": "Relay password",
521
- "wizard.passwordHint": "The RELAY_PASSWORD your relay was started with. Leave it empty if the relay has none.",
522
- "wizard.finishTitle": "Ready",
524
+ "wizard.passwordHint": "RELAY_PASSWORD of the relay. Leave empty if unset.",
525
+ "wizard.finishTitle": "Finish",
523
526
  "wizard.finishHint": "Configuration will be saved to {path}.",
524
- "wizard.startNow": "Start the services now",
525
- "wizard.installKeepalive": "Keep the services running in the background",
526
- "wizard.finish": "Save and continue",
527
+ "wizard.startNow": "Start services now",
528
+ "wizard.installKeepalive": "Run in background",
529
+ "wizard.finish": "Save and finish",
527
530
  "field.mode": "Access mode",
528
531
  "field.port": "Relay port",
529
532
  "field.lanHost": "Browser address",
@@ -536,15 +539,15 @@ var require_en = __commonJS({
536
539
  "placeholder.autoDiscovered": "auto-discovered",
537
540
  "placeholder.none": "none",
538
541
  "error.invalidMode": "Unknown access mode.",
539
- "error.invalidPort": "The port must be a number between 1 and 65535.",
540
- "error.invalidLanHost": "The browser address must be a reachable LAN or Tailscale address, not loopback or 0.0.0.0.",
541
- "error.invalidRelayUrl": "The relay URL must start with wss://, ws://, https:// or http://.",
542
- "error.invalidPublicUrl": "The browser URL must start with https:// or http:// and cannot use 0.0.0.0 or ::.",
542
+ "error.invalidPort": "Port must be a number between 1 and 65535.",
543
+ "error.invalidLanHost": "Browser address must be a reachable LAN or Tailscale address, not loopback or 0.0.0.0.",
544
+ "error.invalidRelayUrl": "Relay URL must start with wss://, ws://, https:// or http://.",
545
+ "error.invalidPublicUrl": "Browser URL must start with https:// or http:// and cannot use 0.0.0.0 or ::.",
543
546
  "error.invalidLanguage": "Unknown language.",
544
547
  "error.invalidKeepalive": "Unknown keep-alive manager.",
545
548
  "error.unknownField": "Unknown setting.",
546
- "error.remoteUrlRequired": "A relay URL is required for the self-hosted relay mode.",
547
- "error.saveFailed": "Could not save the configuration: {message}",
549
+ "error.remoteUrlRequired": "Relay URL is required for self-hosted relay mode.",
550
+ "error.saveFailed": "Could not save configuration: {message}",
548
551
  "hint.navigate": "\u2191\u2193 move",
549
552
  "hint.select": "\u21B5 select",
550
553
  "hint.edit": "\u21B5 edit",
@@ -554,22 +557,23 @@ var require_en = __commonJS({
554
557
  "hint.mouseOn": "m mouse off",
555
558
  "hint.mouseOff": "m mouse on",
556
559
  "hint.mouseUnsupported": "mouse unsupported",
557
- "hint.restartRequired": "Restart the services to apply these changes.",
558
- "hint.unsavedChanges": "Unsaved changes \u2014 press s to save.",
560
+ "hint.restartRequired": "Restart services to apply changes.",
561
+ "hint.unsavedChanges": "Unsaved changes (press s to save).",
559
562
  "hint.save": "s save",
560
563
  "hint.editing": "\u21B5 confirm \xB7 esc cancel",
561
564
  "update.title": "Updates",
562
565
  "update.check": "Check for updates",
563
566
  "update.checking": "Checking npm\u2026",
564
567
  "update.upToDate": "Up to date ({version})",
565
- "update.available": "Update available: {version} \u2014 press Enter to install",
568
+ "update.available": "Update {version} available (press Enter to install)",
566
569
  "update.updating": "Installing {version}\u2026",
567
570
  "update.done": "Updated to {version}",
568
- "update.restartHint": "Update installed. Restart herdr-remote to use it.",
569
- "update.errorNetwork": "Could not reach the npm registry",
570
- "update.errorFailed": "The update failed. Try: npm install -g herdr-remote@latest",
571
- "update.cannot.source": "Running from a source checkout \u2014 update with git, not npm",
572
- "update.cannot.linked": "Running from a linked working copy \u2014 npm link manages this one"
571
+ "update.restartHint": "Update installed. Restart herdr-remote to apply.",
572
+ "update.errorNetwork": "Could not reach any npm registry",
573
+ "update.errorNetworkDetail": "No registry answered: {message}",
574
+ "update.errorFailed": "Update failed. Run: npm install -g herdr-remote@latest",
575
+ "update.cannot.source": "Source checkout \u2014 update with git",
576
+ "update.cannot.linked": "Linked package \u2014 managed by npm link"
573
577
  };
574
578
  }
575
579
  });
@@ -580,7 +584,7 @@ var require_zh = __commonJS({
580
584
  "use strict";
581
585
  module.exports = {
582
586
  "app.name": "Herdr Remote",
583
- "app.tagline": "\u5728\u6D4F\u89C8\u5668\u91CC\u8FDC\u7A0B\u4F7F\u7528\u4F60\u7684 Herdr \u5DE5\u4F5C\u533A",
587
+ "app.tagline": "\u5728\u6D4F\u89C8\u5668\u4E2D\u4F7F\u7528 Herdr \u5DE5\u4F5C\u533A",
584
588
  "common.yes": "\u662F",
585
589
  "common.no": "\u5426",
586
590
  "common.on": "\u5F00",
@@ -598,11 +602,11 @@ var require_zh = __commonJS({
598
602
  "common.disabled": "\u5DF2\u505C\u7528",
599
603
  "common.loading": "\u5904\u7406\u4E2D\u2026",
600
604
  "common.save": "\u4FDD\u5B58\u8BBE\u7F6E",
601
- "common.saved": "\u914D\u7F6E\u5DF2\u4FDD\u5B58\u5230 {path}",
605
+ "common.saved": "\u914D\u7F6E\u5DF2\u4FDD\u5B58\u81F3 {path}",
602
606
  "common.back": "\u8FD4\u56DE",
603
607
  "common.cancel": "\u53D6\u6D88",
604
608
  "common.confirm": "\u786E\u8BA4",
605
- "common.pid": "\u8FDB\u7A0B {pid}",
609
+ "common.pid": "PID {pid}",
606
610
  "common.notApplicable": "\u4E0D\u9002\u7528",
607
611
  "nav.overview": "\u6982\u89C8",
608
612
  "nav.pair": "\u914D\u5BF9\u8BBE\u5907",
@@ -613,12 +617,12 @@ var require_zh = __commonJS({
613
617
  "nav.about": "\u8BED\u8A00\u4E0E\u5173\u4E8E",
614
618
  "mode.local": "\u4EC5\u672C\u673A",
615
619
  "mode.lan": "\u5C40\u57DF\u7F51 / Tailscale",
616
- "mode.official": "\u5B98\u65B9 relay\uFF08\u65E0\u9700\u81EA\u5EFA\u670D\u52A1\u5668\uFF09",
617
- "mode.remote": "\u81EA\u5EFA relay",
618
- "mode.local.description": "relay \u76D1\u542C 127.0.0.1\uFF0C\u53EA\u6709\u672C\u673A\u6D4F\u89C8\u5668\u80FD\u8BBF\u95EE\u3002",
619
- "mode.lan.description": "relay \u76D1\u542C 0.0.0.0\uFF08\u6240\u6709\u7F51\u5361\uFF09\uFF0C\u5C40\u57DF\u7F51\u6216 tailnet \u91CC\u7684\u624B\u673A\u90FD\u80FD\u8BBF\u95EE\u3002",
620
- "mode.official.description": "\u901A\u8FC7 Herdr Remote \u5B98\u65B9 relay \u4ECE\u4EFB\u610F\u7F51\u7EDC\u8BBF\u95EE\u672C\u673A\uFF0C\u65E0\u9700\u90E8\u7F72\u4EFB\u4F55\u670D\u52A1\u5668\u3002\u7EC8\u7AEF\u6D41\u91CF\u4F1A\u7ECF\u8FC7\u4E00\u53F0\u4F60\u65E0\u6CD5\u63A7\u5236\u7684\u670D\u52A1\u5668\uFF0C\u4F46\u53EA\u6709\u4F60\u914D\u5BF9\u8FC7\u7684\u8BBE\u5907\u624D\u80FD\u6253\u5F00\u7EC8\u7AEF\u3002",
621
- "mode.remote.description": "\u4E0D\u542F\u52A8\u672C\u5730 relay\u3002\u672C\u673A\u8FDE\u63A5\u4F60\u81EA\u5EFA\u7684 relay\uFF0C\u8FD9\u662F\u4ECE\u5916\u7F51\u8BBF\u95EE\u7684\u552F\u4E00\u65B9\u5F0F\u3002",
620
+ "mode.official": "\u5B98\u65B9 Relay",
621
+ "mode.remote": "\u81EA\u5EFA Relay",
622
+ "mode.local.description": "\u76D1\u542C 127.0.0.1\uFF0C\u4EC5\u672C\u673A\u53EF\u8BBF\u95EE\u3002",
623
+ "mode.lan.description": "\u76D1\u542C 0.0.0.0\uFF0C\u5C40\u57DF\u7F51\u4E0E Tailscale \u8BBE\u5907\u53EF\u8BBF\u95EE\u3002",
624
+ "mode.official.description": "\u4F7F\u7528\u5B98\u65B9 Relay \u8DE8\u7F51\u8BBF\u95EE\uFF0C\u65E0\u9700\u81EA\u5EFA\u670D\u52A1\u5668\u3002",
625
+ "mode.remote.description": "\u8FDE\u63A5\u81EA\u5EFA Relay \u8DE8\u7F51\u8BBF\u95EE\u3002",
622
626
  "overview.title": "\u72B6\u6001",
623
627
  "overview.mode": "\u8BBF\u95EE\u65B9\u5F0F",
624
628
  "overview.relay": "Relay",
@@ -628,25 +632,25 @@ var require_zh = __commonJS({
628
632
  "overview.keepalive": "\u4FDD\u6D3B\u670D\u52A1",
629
633
  "overview.devices": "\u6D4F\u89C8\u5668",
630
634
  "overview.hosts": "\u5DE5\u4F5C\u7AD9",
631
- "overview.uptime": "Relay \u8FD0\u884C\u65F6\u957F",
635
+ "overview.uptime": "Relay \u8FD0\u884C\u65F6\u95F4",
632
636
  "overview.relayLocal": "\u672C\u5730\uFF0C{bind}:{port}",
633
637
  "overview.relayRemote": "\u8FDC\u7A0B\uFF0C{url}",
634
- "overview.socketMissing": "\u672A\u627E\u5230 \u2014 Herdr \u5728\u8FD0\u884C\u5417\uFF1F",
638
+ "overview.socketMissing": "\u672A\u627E\u5230\uFF0C\u8BF7\u786E\u8BA4 Herdr \u662F\u5426\u8FD0\u884C",
635
639
  "overview.unreachable": "\u65E0\u6CD5\u8BBF\u95EE\uFF1A{message}",
636
- "overview.notStarted": "\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u8BF7\u5230\u300C\u670D\u52A1\u300D\u9875\u9762\u542F\u52A8\u3002",
640
+ "overview.notStarted": "\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u53EF\u524D\u5F80\u300C\u670D\u52A1\u300D\u542F\u52A8\u3002",
637
641
  "pair.title": "\u914D\u5BF9\u8BBE\u5907",
638
- "pair.generate": "\u751F\u6210\u4E00\u6B21\u6027\u914D\u5BF9\u7801",
639
- "pair.regenerate": "\u91CD\u65B0\u751F\u6210\u914D\u5BF9\u7801",
640
- "pair.working": "\u6B63\u5728\u542F\u52A8\u670D\u52A1\u5E76\u7533\u8BF7\u914D\u5BF9\u7801\u2026",
642
+ "pair.generate": "\u751F\u6210\u914D\u5BF9\u7801",
643
+ "pair.regenerate": "\u91CD\u65B0\u751F\u6210",
644
+ "pair.working": "\u751F\u6210\u914D\u5BF9\u7801\u4E2D\u2026",
641
645
  "pair.code": "\u914D\u5BF9\u7801",
642
- "pair.url": "\u5728\u624B\u673A\u4E0A\u6253\u5F00",
646
+ "pair.url": "\u8BBF\u95EE\u5730\u5740",
643
647
  "pair.expires": "\u5269\u4F59 {minutes}\uFF08\u81F3 {time}\uFF09",
644
- "pair.expired": "\u914D\u5BF9\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u751F\u6210\u3002",
645
- "pair.instructions": "\u6253\u5F00\u94FE\u63A5\u3001\u8F93\u5165\u914D\u5BF9\u7801\uFF0C\u4E4B\u540E\u8FD9\u53F0\u6D4F\u89C8\u5668\u5C31\u957F\u671F\u53EF\u7528\u4E86\u3002",
646
- "pair.qrHint": "\u7528\u624B\u673A\u76F8\u673A\u626B\u7801\u5373\u53EF\u6253\u5F00\u94FE\u63A5\u3002",
647
- "pair.qrUnavailable": "\u7EC8\u7AEF\u5BBD\u5EA6\u4E0D\u8DB3\u4EE5\u663E\u793A\u4E8C\u7EF4\u7801\uFF0C\u8BF7\u4F7F\u7528\u4E0A\u9762\u7684\u94FE\u63A5\u3002",
648
+ "pair.expired": "\u914D\u5BF9\u7801\u5DF2\u8FC7\u671F\uFF0C\u9700\u91CD\u65B0\u751F\u6210\u3002",
649
+ "pair.instructions": "\u6253\u5F00\u94FE\u63A5\u5E76\u8F93\u5165\u914D\u5BF9\u7801\u3002",
650
+ "pair.qrHint": "\u626B\u63CF\u4E8C\u7EF4\u7801\u6253\u5F00\u94FE\u63A5\u3002",
651
+ "pair.qrUnavailable": "\u7EC8\u7AEF\u5BBD\u5EA6\u4E0D\u8DB3\uFF0C\u65E0\u6CD5\u663E\u793A\u4E8C\u7EF4\u7801\u3002",
648
652
  "pair.failed": "\u751F\u6210\u914D\u5BF9\u7801\u5931\u8D25\uFF1A{message}",
649
- "pair.hostOffline": "relay \u4E0A\u8FD8\u6CA1\u6709\u5DE5\u4F5C\u7AD9\u8FDE\u63A5\uFF0C\u8BF7\u5148\u542F\u52A8\u670D\u52A1\u3002",
653
+ "pair.hostOffline": "Relay \u672A\u8FDE\u63A5\u5DE5\u4F5C\u7AD9\uFF0C\u9700\u5148\u542F\u52A8\u670D\u52A1\u3002",
650
654
  "services.title": "\u670D\u52A1",
651
655
  "services.start": "\u542F\u52A8\u670D\u52A1",
652
656
  "services.stop": "\u505C\u6B62\u670D\u52A1",
@@ -656,38 +660,41 @@ var require_zh = __commonJS({
656
660
  "services.restarted": "\u670D\u52A1\u5DF2\u91CD\u542F\u3002",
657
661
  "services.startFailed": "\u542F\u52A8\u670D\u52A1\u5931\u8D25\uFF1A{message}",
658
662
  "services.stopFailed": "\u505C\u6B62\u670D\u52A1\u5931\u8D25\uFF1A{message}",
659
- "services.unsavedBlocked": "\u6709\u672A\u4FDD\u5B58\u7684\u4FEE\u6539\u3002\u8BF7\u5148\u4FDD\u5B58\uFF0C\u5426\u5219\u670D\u52A1\u4F1A\u7528\u78C1\u76D8\u4E0A\u7684\u65E7\u914D\u7F6E\u91CD\u542F\u3002",
660
- "services.managedNotice": "\u670D\u52A1\u7531 {manager} \u6258\u7BA1\uFF0C\u5DF2\u901A\u8FC7\u4FDD\u6D3B\u670D\u52A1\u6267\u884C\u6B64\u64CD\u4F5C\u3002",
663
+ "services.unsavedBlocked": "\u914D\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u5148\u4FDD\u5B58\u518D\u91CD\u542F\u3002",
664
+ "services.managedNotice": "\u7531 {manager} \u6258\u7BA1\uFF0C\u5DF2\u901A\u8FC7\u4FDD\u6D3B\u670D\u52A1\u6267\u884C\u3002",
661
665
  "services.logs": "\u6700\u8FD1\u65E5\u5FD7",
662
- "services.logEmpty": "\u6682\u65E0\u65E5\u5FD7\u8F93\u51FA\u3002",
666
+ "services.logEmpty": "\u6682\u65E0\u65E5\u5FD7\u3002",
663
667
  "services.logRelay": "Relay",
664
668
  "services.logHost": "\u4E3B\u673A\u8FDE\u63A5\u5668",
665
669
  "relay.title": "Relay \u8BBE\u7F6E",
666
670
  "relay.settings": "\u8BBE\u7F6E",
671
+ "relay.locked": "\u56FA\u5B9A",
672
+ "relay.officialFixed": "\u5B98\u65B9 relay \u5730\u5740\u56FA\u5B9A\uFF0C\u4E0D\u53EF\u4FEE\u6539\u3002",
673
+ "relay.officialHint": "\u5B98\u65B9 relay\uFF1A\u5730\u5740\u56FA\u5B9A\uFF0C\u65E0\u9700\u5BC6\u7801\u3002",
667
674
  "relay.password": "Relay \u5BC6\u7801",
668
- "relay.passwordHint": "\u9700\u4E0E relay \u4E0A\u7684 RELAY_PASSWORD \u4E00\u81F4\u3002\u7559\u7A7A\u8868\u793A\u516C\u7528 relay\u3002",
675
+ "relay.passwordHint": "\u987B\u4E0E Relay \u7AEF\u7684 RELAY_PASSWORD \u4E00\u81F4\u3002\u7559\u7A7A\u4E3A\u516C\u7528 Relay\u3002",
669
676
  "relay.passwordSet": "\u5DF2\u8BBE\u7F6E",
670
- "relay.passwordEmpty": "\u672A\u8BBE\u7F6E\uFF08\u516C\u7528 relay\uFF09",
671
- "relay.passwordSaved": "Relay \u5BC6\u7801\u5DF2\u4FDD\u5B58\uFF0C\u91CD\u542F\u670D\u52A1\u540E\u751F\u6548\u3002",
677
+ "relay.passwordEmpty": "\u672A\u8BBE\u7F6E\uFF08\u516C\u7528\uFF09",
678
+ "relay.passwordSaved": "Relay \u5BC6\u7801\u5DF2\u4FDD\u5B58\uFF0C\u91CD\u542F\u670D\u52A1\u751F\u6548\u3002",
672
679
  "relay.showPassword": "\u663E\u793A\u5BC6\u7801",
673
680
  "relay.hidePassword": "\u9690\u85CF\u5BC6\u7801",
674
681
  "relay.identity": "\u5DE5\u4F5C\u7AD9\u6807\u8BC6",
675
- "relay.regenerate": "\u91CD\u65B0\u751F\u6210\u5DE5\u4F5C\u7AD9\u6807\u8BC6",
676
- "relay.regenerated": "\u5DF2\u751F\u6210\u65B0\u7684\u5DE5\u4F5C\u7AD9\u6807\u8BC6\uFF0C\u91CD\u542F\u670D\u52A1\u540E\u4F1A\u91CD\u65B0\u6CE8\u518C\u3002",
677
- "relay.envSnippet": "relay \u670D\u52A1\u5668\u542F\u52A8\u547D\u4EE4",
678
- "relay.envHint": "\u5728 relay \u4E3B\u673A\u4E0A\u6267\u884C\u8FD9\u6761\u547D\u4EE4\u3002",
679
- "relay.test": "\u6D4B\u8BD5 relay \u8FDE\u63A5",
680
- "relay.testOk": "relay \u53EF\u8BBF\u95EE\uFF1A\u7248\u672C {version}\uFF0C\u5DF2\u8FDE\u63A5 {hosts} \u53F0\u5DE5\u4F5C\u7AD9\u3002",
681
- "relay.testFailed": "relay \u65E0\u6CD5\u8BBF\u95EE\uFF1A{message}",
682
- "relay.selectAddress": "\u9009\u62E9\u5BF9\u5916\u516C\u5E03\u7684\u5730\u5740",
682
+ "relay.regenerate": "\u91CD\u65B0\u751F\u6210\u6807\u8BC6",
683
+ "relay.regenerated": "\u5DF2\u751F\u6210\u65B0\u6807\u8BC6\uFF0C\u91CD\u542F\u540E\u91CD\u65B0\u6CE8\u518C\u3002",
684
+ "relay.envSnippet": "Relay \u542F\u52A8\u547D\u4EE4",
685
+ "relay.envHint": "\u5728 Relay \u4E3B\u673A\u6267\u884C\u6B64\u547D\u4EE4\u3002",
686
+ "relay.test": "\u6D4B\u8BD5 Relay \u8FDE\u63A5",
687
+ "relay.testOk": "Relay \u53EF\u8BBF\u95EE\uFF1A\u7248\u672C {version}\uFF0C\u5DF2\u8FDE\u63A5 {hosts} \u53F0\u5DE5\u4F5C\u7AD9\u3002",
688
+ "relay.testFailed": "Relay \u65E0\u6CD5\u8BBF\u95EE\uFF1A{message}",
689
+ "relay.selectAddress": "\u9009\u62E9\u516C\u5E03\u5730\u5740",
683
690
  "relay.listenAddress": "\u76D1\u542C\u5730\u5740",
684
- "relay.listenAddressHint": "\u8FD9\u662F\u670D\u52A1\u5668\u7ED1\u5B9A\u5730\u5740\uFF1B\u4E0B\u9762\u7684\u5BF9\u5916\u516C\u5E03\u5730\u5740\u624D\u662F\u6D4F\u89C8\u5668\u8981\u6253\u5F00\u7684\u5730\u5740\u3002",
691
+ "relay.listenAddressHint": "\u670D\u52A1\u5668\u7ED1\u5B9A\u5730\u5740\uFF1B\u6D4F\u89C8\u5668\u8BBF\u95EE\u4E0B\u65B9\u516C\u5E03\u5730\u5740\u3002",
685
692
  "relay.addressTailscale": "Tailscale",
686
693
  "relay.addressLan": "\u5C40\u57DF\u7F51",
687
694
  "relay.addressVirtual": "\u865A\u62DF\u7F51\u5361",
688
695
  "relay.addressLoopback": "\u56DE\u73AF",
689
- "relay.noAddresses": "\u6CA1\u6709\u627E\u5230\u975E\u56DE\u73AF\u5730\u5740\uFF0C\u8BF7\u5148\u8FDE\u63A5\u7F51\u7EDC\u6216\u542F\u52A8 Tailscale\u3002",
690
- "relay.docsHint": "\u81EA\u5EFA relay \u8BF7\u53C2\u9605 docs/self-hosted-relay.zh-CN.md",
696
+ "relay.noAddresses": "\u672A\u627E\u5230\u975E\u56DE\u73AF\u5730\u5740\u3002",
697
+ "relay.docsHint": "\u81EA\u5EFA\u6307\u5357\uFF1Adocs/self-hosted-relay.zh-CN.md",
691
698
  "keepalive.title": "\u540E\u53F0\u4FDD\u6D3B\u670D\u52A1",
692
699
  "keepalive.manager": "\u7BA1\u7406\u65B9\u5F0F",
693
700
  "keepalive.state": "\u72B6\u6001",
@@ -700,24 +707,24 @@ var require_zh = __commonJS({
700
707
  "keepalive.failed": "\u4FDD\u6D3B\u64CD\u4F5C\u5931\u8D25\uFF1A{message}",
701
708
  "keepalive.unitPath": "\u5355\u5143\u6587\u4EF6",
702
709
  "keepalive.logsHint": "\u67E5\u770B\u65E5\u5FD7\uFF1A{command}",
703
- "keepalive.linger": "\u672A\u767B\u5F55\u4E5F\u5F00\u673A\u81EA\u542F",
704
- "keepalive.lingerEnabled": "\u5DF2\u5F00\u542F linger\uFF1A\u5F00\u673A\u5373\u542F\u52A8\u3002",
705
- "keepalive.lingerDisabled": "\u672A\u5F00\u542F linger\uFF0C\u670D\u52A1\u53EA\u5728\u4F60\u767B\u5F55\u671F\u95F4\u8FD0\u884C\u3002",
710
+ "keepalive.linger": "\u5F00\u673A\u81EA\u542F",
711
+ "keepalive.lingerEnabled": "\u5DF2\u5F00\u542F\u5F00\u673A\u81EA\u542F\u3002",
712
+ "keepalive.lingerDisabled": "\u672A\u5F00\u542F\u5F00\u673A\u81EA\u542F\uFF08\u4EC5\u767B\u5F55\u671F\u95F4\u8FD0\u884C\uFF09\u3002",
706
713
  "keepalive.enableLinger": "\u5F00\u542F\u5F00\u673A\u81EA\u542F",
707
- "keepalive.lingerDone": "\u5DF2\u4E3A {username} \u5F00\u542F linger\u3002",
708
- "keepalive.fallbackNote": "\u7CFB\u7EDF\u6CA1\u6709\u53EF\u7528\u7684\u670D\u52A1\u7BA1\u7406\u5668\uFF0C\u6539\u7528\u5185\u7F6E\u7684\u540E\u53F0\u5B88\u62A4\u8FDB\u7A0B\uFF0C\u91CD\u542F\u7535\u8111\u540E\u4E0D\u4F1A\u81EA\u52A8\u6062\u590D\u3002",
714
+ "keepalive.lingerDone": "\u5DF2\u4E3A {username} \u5F00\u542F\u5F00\u673A\u81EA\u542F\u3002",
715
+ "keepalive.fallbackNote": "\u65E0\u7CFB\u7EDF\u670D\u52A1\u7BA1\u7406\u5668\uFF0C\u4F7F\u7528\u540E\u53F0\u5B88\u62A4\u8FDB\u7A0B\uFF0C\u91CD\u542F\u540E\u4E0D\u81EA\u52A8\u6062\u590D\u3002",
709
716
  "herdr.title": "Herdr \u96C6\u6210",
710
717
  "herdr.socketPath": "\u5957\u63A5\u5B57\u8DEF\u5F84",
711
718
  "herdr.args": "\u9644\u52A0\u53C2\u6570",
712
719
  "herdr.plugin": "\u63D2\u4EF6\u6CE8\u518C",
713
720
  "herdr.pluginRegistered": "\u5DF2\u6CE8\u518C\u5230 Herdr\u3002",
714
721
  "herdr.pluginMissing": "\u5C1A\u672A\u6CE8\u518C\u5230 Herdr\u3002",
715
- "herdr.register": "\u628A\u672C\u5305\u6CE8\u518C\u4E3A Herdr \u63D2\u4EF6",
716
- "herdr.unregister": "\u53D6\u6D88\u6CE8\u518C Herdr \u63D2\u4EF6",
722
+ "herdr.register": "\u6CE8\u518C\u4E3A Herdr \u63D2\u4EF6",
723
+ "herdr.unregister": "\u53D6\u6D88\u6CE8\u518C",
717
724
  "herdr.registerDone": "\u5DF2\u6CE8\u518C\uFF1A{path}",
718
- "herdr.unregisterDone": "\u63D2\u4EF6\u5DF2\u53D6\u6D88\u6CE8\u518C\u3002",
725
+ "herdr.unregisterDone": "\u5DF2\u53D6\u6D88\u6CE8\u518C\u3002",
719
726
  "herdr.registerFailed": "\u6CE8\u518C\u5931\u8D25\uFF1A{message}",
720
- "herdr.cliMissing": "PATH \u4E2D\u627E\u4E0D\u5230 herdr \u547D\u4EE4\u3002",
727
+ "herdr.cliMissing": "PATH \u4E2D\u672A\u627E\u5230 herdr \u547D\u4EE4\u3002",
721
728
  "about.title": "\u8BED\u8A00\u4E0E\u5173\u4E8E",
722
729
  "about.language": "\u754C\u9762\u8BED\u8A00",
723
730
  "about.languageAuto": "\u8DDF\u968F\u7CFB\u7EDF\uFF08{detected}\uFF09",
@@ -727,23 +734,23 @@ var require_zh = __commonJS({
727
734
  "about.configPath": "\u914D\u7F6E\u6587\u4EF6",
728
735
  "about.statePath": "\u72B6\u6001\u76EE\u5F55",
729
736
  "about.relayPackage": "Relay \u5305",
730
- "about.docs": "\u81EA\u5EFA relay \u6307\u5357\uFF1Adocs/self-hosted-relay.zh-CN.md",
737
+ "about.docs": "\u81EA\u5EFA\u6307\u5357\uFF1Adocs/self-hosted-relay.zh-CN.md",
731
738
  "wizard.title": "\u9996\u6B21\u8BBE\u7F6E",
732
739
  "wizard.step": "\u7B2C {current} / {total} \u6B65",
733
740
  "wizard.languageTitle": "\u9009\u62E9\u8BED\u8A00",
734
- "wizard.accessTitle": "\u4F60\u6253\u7B97\u600E\u4E48\u8BBF\u95EE\u8FD9\u53F0\u673A\u5668\uFF1F",
735
- "wizard.accessHint": "\u4E4B\u540E\u53EF\u4EE5\u5728 Relay \u9875\u9762\u968F\u65F6\u4FEE\u6539\u3002",
736
- "wizard.addressTitle": "\u624B\u673A\u5E94\u8BE5\u4F7F\u7528\u54EA\u4E2A\u5730\u5740\uFF1F",
737
- "wizard.relayTitle": "\u4F60\u7684 relay \u670D\u52A1\u5668",
741
+ "wizard.accessTitle": "\u9009\u62E9\u8BBF\u95EE\u65B9\u5F0F",
742
+ "wizard.accessHint": "\u540E\u7EED\u53EF\u5728 Relay \u9875\u9762\u4FEE\u6539\u3002",
743
+ "wizard.addressTitle": "\u9009\u62E9\u8BBF\u95EE\u5730\u5740",
744
+ "wizard.relayTitle": "Relay \u670D\u52A1\u5668",
738
745
  "wizard.relayUrlLabel": "Relay \u5730\u5740\uFF08wss://\u2026\uFF09",
739
- "wizard.relayHint": "relay \u9700\u8981\u5DF2\u7ECF\u5728\u8FD0\u884C\u3002\u642D\u5EFA\u65B9\u6CD5\u89C1 docs/self-hosted-relay.zh-CN.md\u3002",
746
+ "wizard.relayHint": "Relay \u9700\u5148\u8FD0\u884C\u3002\u642D\u5EFA\u6307\u5357\u89C1 docs/self-hosted-relay.zh-CN.md\u3002",
740
747
  "wizard.passwordTitle": "Relay \u5BC6\u7801",
741
- "wizard.passwordHint": "relay \u542F\u52A8\u65F6\u8BBE\u7F6E\u7684 RELAY_PASSWORD\u3002relay \u6CA1\u8BBE\u5BC6\u7801\u5C31\u7559\u7A7A\u3002",
748
+ "wizard.passwordHint": "Relay \u7684 RELAY_PASSWORD\u3002\u672A\u8BBE\u7559\u7A7A\u3002",
742
749
  "wizard.finishTitle": "\u5B8C\u6210",
743
- "wizard.finishHint": "\u914D\u7F6E\u5C06\u4FDD\u5B58\u5230 {path}\u3002",
750
+ "wizard.finishHint": "\u914D\u7F6E\u5C06\u4FDD\u5B58\u81F3 {path}\u3002",
744
751
  "wizard.startNow": "\u7ACB\u5373\u542F\u52A8\u670D\u52A1",
745
- "wizard.installKeepalive": "\u8BA9\u670D\u52A1\u5728\u540E\u53F0\u4FDD\u6301\u8FD0\u884C",
746
- "wizard.finish": "\u4FDD\u5B58\u5E76\u7EE7\u7EED",
752
+ "wizard.installKeepalive": "\u540E\u53F0\u4FDD\u6D3B",
753
+ "wizard.finish": "\u4FDD\u5B58\u5E76\u5B8C\u6210",
747
754
  "field.mode": "\u8BBF\u95EE\u65B9\u5F0F",
748
755
  "field.port": "Relay \u7AEF\u53E3",
749
756
  "field.lanHost": "\u6D4F\u89C8\u5668\u5730\u5740",
@@ -756,14 +763,14 @@ var require_zh = __commonJS({
756
763
  "placeholder.autoDiscovered": "\u81EA\u52A8\u63A2\u6D4B",
757
764
  "placeholder.none": "\u65E0",
758
765
  "error.invalidMode": "\u672A\u77E5\u7684\u8BBF\u95EE\u65B9\u5F0F\u3002",
759
- "error.invalidPort": "\u7AEF\u53E3\u5FC5\u987B\u662F 1 \u5230 65535 \u4E4B\u95F4\u7684\u6570\u5B57\u3002",
760
- "error.invalidLanHost": "\u6D4F\u89C8\u5668\u5730\u5740\u5FC5\u987B\u662F\u53EF\u8BBF\u95EE\u7684\u5C40\u57DF\u7F51\u6216 Tailscale \u5730\u5740\uFF0C\u4E0D\u80FD\u586B\u5199\u56DE\u73AF\u5730\u5740\u6216 0.0.0.0\u3002",
761
- "error.invalidRelayUrl": "Relay \u5730\u5740\u9700\u4EE5 wss://\u3001ws://\u3001https:// \u6216 http:// \u5F00\u5934\u3002",
762
- "error.invalidPublicUrl": "\u6D4F\u89C8\u5668\u5730\u5740\u9700\u4EE5 https:// \u6216 http:// \u5F00\u5934\uFF0C\u4E0D\u80FD\u4F7F\u7528 0.0.0.0 \u6216 ::\u3002",
766
+ "error.invalidPort": "\u7AEF\u53E3\u987B\u4E3A 1 \u5230 65535 \u4E4B\u95F4\u7684\u6570\u5B57\u3002",
767
+ "error.invalidLanHost": "\u6D4F\u89C8\u5668\u5730\u5740\u987B\u4E3A\u53EF\u8BBF\u95EE\u5C40\u57DF\u7F51\u6216 Tailscale \u5730\u5740\uFF0C\u4E0D\u80FD\u4E3A\u56DE\u73AF\u6216 0.0.0.0\u3002",
768
+ "error.invalidRelayUrl": "Relay \u5730\u5740\u987B\u4EE5 wss://\u3001ws://\u3001https:// \u6216 http:// \u5F00\u5934\u3002",
769
+ "error.invalidPublicUrl": "\u6D4F\u89C8\u5668\u5730\u5740\u987B\u4EE5 https:// \u6216 http:// \u5F00\u5934\uFF0C\u4E0D\u80FD\u4E3A 0.0.0.0 \u6216 ::\u3002",
763
770
  "error.invalidLanguage": "\u672A\u77E5\u7684\u8BED\u8A00\u3002",
764
771
  "error.invalidKeepalive": "\u672A\u77E5\u7684\u4FDD\u6D3B\u7BA1\u7406\u65B9\u5F0F\u3002",
765
772
  "error.unknownField": "\u672A\u77E5\u7684\u8BBE\u7F6E\u9879\u3002",
766
- "error.remoteUrlRequired": "\u81EA\u5EFA relay \u6A21\u5F0F\u5FC5\u987B\u586B\u5199 Relay \u5730\u5740\u3002",
773
+ "error.remoteUrlRequired": "\u81EA\u5EFA Relay \u6A21\u5F0F\u987B\u586B\u5199 Relay \u5730\u5740\u3002",
767
774
  "error.saveFailed": "\u4FDD\u5B58\u914D\u7F6E\u5931\u8D25\uFF1A{message}",
768
775
  "hint.navigate": "\u2191\u2193 \u79FB\u52A8",
769
776
  "hint.select": "\u21B5 \u9009\u62E9",
@@ -774,22 +781,23 @@ var require_zh = __commonJS({
774
781
  "hint.mouseOn": "m \u5173\u95ED\u9F20\u6807",
775
782
  "hint.mouseOff": "m \u5F00\u542F\u9F20\u6807",
776
783
  "hint.mouseUnsupported": "\u4E0D\u652F\u6301\u9F20\u6807",
777
- "hint.restartRequired": "\u9700\u8981\u91CD\u542F\u670D\u52A1\u624D\u80FD\u751F\u6548\u3002",
778
- "hint.unsavedChanges": "\u6709\u672A\u4FDD\u5B58\u7684\u4FEE\u6539 \u2014 \u6309 s \u4FDD\u5B58\u3002",
784
+ "hint.restartRequired": "\u91CD\u542F\u670D\u52A1\u751F\u6548\u3002",
785
+ "hint.unsavedChanges": "\u6709\u672A\u4FDD\u5B58\u4FEE\u6539\uFF08\u6309 s \u4FDD\u5B58\uFF09\u3002",
779
786
  "hint.save": "s \u4FDD\u5B58",
780
787
  "hint.editing": "\u21B5 \u786E\u8BA4 \xB7 esc \u53D6\u6D88",
781
788
  "update.title": "\u66F4\u65B0",
782
789
  "update.check": "\u68C0\u67E5\u66F4\u65B0",
783
- "update.checking": "\u6B63\u5728\u67E5\u8BE2 npm\u2026",
790
+ "update.checking": "\u67E5\u8BE2 npm \u4E2D\u2026",
784
791
  "update.upToDate": "\u5DF2\u662F\u6700\u65B0\uFF08{version}\uFF09",
785
- "update.available": "\u6709\u65B0\u7248\u672C {version} \u2014\u2014 \u6309 Enter \u5B89\u88C5",
786
- "update.updating": "\u6B63\u5728\u5B89\u88C5 {version}\u2026",
787
- "update.done": "\u5DF2\u66F4\u65B0\u5230 {version}",
788
- "update.restartHint": "\u66F4\u65B0\u5DF2\u5B89\u88C5\uFF0C\u91CD\u542F herdr-remote \u540E\u751F\u6548\u3002",
789
- "update.errorNetwork": "\u65E0\u6CD5\u8FDE\u63A5 npm registry",
792
+ "update.available": "\u65B0\u7248\u672C {version} \u53EF\u7528\uFF08\u6309 Enter \u5B89\u88C5\uFF09",
793
+ "update.updating": "\u5B89\u88C5 {version} \u4E2D\u2026",
794
+ "update.done": "\u5DF2\u66F4\u65B0\u81F3 {version}",
795
+ "update.restartHint": "\u66F4\u65B0\u5DF2\u5B89\u88C5\uFF0C\u91CD\u542F herdr-remote \u751F\u6548\u3002",
796
+ "update.errorNetwork": "\u6240\u6709 npm registry \u5747\u65E0\u6CD5\u8FDE\u63A5",
797
+ "update.errorNetworkDetail": "\u65E0 registry \u5E94\u7B54\uFF1A{message}",
790
798
  "update.errorFailed": "\u66F4\u65B0\u5931\u8D25\uFF0C\u53EF\u624B\u52A8\u6267\u884C\uFF1Anpm install -g herdr-remote@latest",
791
- "update.cannot.source": "\u5F53\u524D\u4ECE\u6E90\u7801\u76EE\u5F55\u8FD0\u884C \u2014\u2014 \u8BF7\u7528 git \u66F4\u65B0\uFF0C\u800C\u4E0D\u662F npm",
792
- "update.cannot.linked": "\u5F53\u524D\u662F npm link \u7684\u5DE5\u4F5C\u526F\u672C \u2014\u2014 \u7531 npm link \u7BA1\u7406"
799
+ "update.cannot.source": "\u6E90\u7801\u76EE\u5F55\u8FD0\u884C\uFF0C\u8BF7\u4F7F\u7528 git \u66F4\u65B0",
800
+ "update.cannot.linked": "npm link \u5DE5\u4F5C\u526F\u672C\uFF0C\u7531 npm link \u7BA1\u7406"
793
801
  };
794
802
  }
795
803
  });
@@ -954,6 +962,7 @@ var require_settings_model = __commonJS({
954
962
  ACCESS_MODES: ACCESS_MODES2,
955
963
  KEEPALIVE_MANAGERS,
956
964
  LANGUAGES,
965
+ OFFICIAL_RELAY_URL: OFFICIAL_RELAY_URL2,
957
966
  configDir,
958
967
  configPath: configPath2,
959
968
  isLoopbackHost,
@@ -976,6 +985,14 @@ var require_settings_model = __commonJS({
976
985
  { id: "keepaliveManager", kind: "choice", choices: KEEPALIVE_MANAGERS, labelKey: "field.keepalive" }
977
986
  ];
978
987
  var EMPTY = "";
988
+ var SELECTABLE_MODES2 = ["local", "lan", "official", "remote"];
989
+ function selectedMode2(draft) {
990
+ if (draft.relay.mode === "remote" && draft.relay.remoteUrl === OFFICIAL_RELAY_URL2) return "official";
991
+ return draft.relay.mode;
992
+ }
993
+ function isOfficialRelay2(draft) {
994
+ return selectedMode2(draft) === "official";
995
+ }
979
996
  function clone(value) {
980
997
  return JSON.parse(JSON.stringify(value));
981
998
  }
@@ -1038,7 +1055,16 @@ var require_settings_model = __commonJS({
1038
1055
  const value = typeof rawValue === "string" ? rawValue.trim() : rawValue;
1039
1056
  switch (id) {
1040
1057
  case "mode": {
1041
- if (!ACCESS_MODES2.includes(value)) return { draft, errorKey: "error.invalidMode" };
1058
+ if (!SELECTABLE_MODES2.includes(value)) return { draft, errorKey: "error.invalidMode" };
1059
+ if (value === "official") {
1060
+ next.relay.mode = "remote";
1061
+ next.relay.remoteUrl = OFFICIAL_RELAY_URL2;
1062
+ next.relay.publicUrl = EMPTY;
1063
+ break;
1064
+ }
1065
+ if (value === "remote" && next.relay.remoteUrl === OFFICIAL_RELAY_URL2) {
1066
+ next.relay.remoteUrl = EMPTY;
1067
+ }
1042
1068
  next.relay.mode = value;
1043
1069
  if (value === "lan" && (isLoopbackHost(next.relay.lanHost) || isUnspecifiedAddress(next.relay.lanHost))) {
1044
1070
  next.relay.lanHost = EMPTY;
@@ -1139,8 +1165,11 @@ var require_settings_model = __commonJS({
1139
1165
  }
1140
1166
  module.exports = {
1141
1167
  FIELDS: FIELDS2,
1168
+ SELECTABLE_MODES: SELECTABLE_MODES2,
1142
1169
  createDraft: createDraft2,
1143
1170
  fieldsForMode: fieldsForMode2,
1171
+ isOfficialRelay: isOfficialRelay2,
1172
+ selectedMode: selectedMode2,
1144
1173
  getField: getField2,
1145
1174
  getFieldPlaceholder: getFieldPlaceholder2,
1146
1175
  setField: setField2,
@@ -2301,11 +2330,13 @@ var require_updater = __commonJS({
2301
2330
  "src/updater.js"(exports, module) {
2302
2331
  "use strict";
2303
2332
  var fs = __require("node:fs");
2333
+ var os = __require("node:os");
2304
2334
  var path = __require("node:path");
2305
2335
  var { spawn } = __require("node:child_process");
2306
2336
  var { PACKAGE_ROOT } = require_config();
2307
2337
  var PACKAGE_NAME = "herdr-remote";
2308
- var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
2338
+ var DEFAULT_REGISTRY = "https://registry.npmjs.org";
2339
+ var MIRROR_REGISTRY = "https://registry.npmmirror.com";
2309
2340
  function currentVersion() {
2310
2341
  try {
2311
2342
  return __require(path.join(PACKAGE_ROOT, "package.json")).version;
@@ -2325,6 +2356,43 @@ var require_updater = __commonJS({
2325
2356
  function canSelfUpdate2() {
2326
2357
  return installKind2() === "npm";
2327
2358
  }
2359
+ function normalizeRegistry(value) {
2360
+ if (typeof value !== "string") return null;
2361
+ const trimmed = value.trim().replace(/\/+$/, "");
2362
+ if (!/^https?:\/\//i.test(trimmed)) return null;
2363
+ return trimmed;
2364
+ }
2365
+ function registryFromNpmrc(filePath) {
2366
+ let contents;
2367
+ try {
2368
+ contents = fs.readFileSync(filePath, "utf8");
2369
+ } catch {
2370
+ return null;
2371
+ }
2372
+ let found = null;
2373
+ for (const rawLine of contents.split(/\r?\n/)) {
2374
+ const line = rawLine.trim();
2375
+ if (!line || line.startsWith("#") || line.startsWith(";")) continue;
2376
+ const match = /^registry\s*=\s*(.+)$/i.exec(line);
2377
+ if (match) found = normalizeRegistry(match[1].replace(/^["']|["']$/g, ""));
2378
+ }
2379
+ return found;
2380
+ }
2381
+ function registryCandidates({ env = process.env, home = os.homedir(), cwd = process.cwd() } = {}) {
2382
+ const candidates = [];
2383
+ const add = (value) => {
2384
+ const normalized = normalizeRegistry(value);
2385
+ if (normalized && !candidates.includes(normalized)) candidates.push(normalized);
2386
+ };
2387
+ add(env.npm_config_registry);
2388
+ add(env.NPM_CONFIG_REGISTRY);
2389
+ add(env.HERDR_REMOTE_REGISTRY);
2390
+ add(registryFromNpmrc(path.join(cwd, ".npmrc")));
2391
+ add(registryFromNpmrc(path.join(home, ".npmrc")));
2392
+ add(DEFAULT_REGISTRY);
2393
+ add(MIRROR_REGISTRY);
2394
+ return candidates;
2395
+ }
2328
2396
  function compareVersions(a, b) {
2329
2397
  const parse = (value) => String(value).split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
2330
2398
  const left = parse(a);
@@ -2336,39 +2404,64 @@ var require_updater = __commonJS({
2336
2404
  }
2337
2405
  return 0;
2338
2406
  }
2339
- async function checkForUpdate2({ timeoutMs = 8e3, fetchImpl = globalThis.fetch } = {}) {
2340
- const current = currentVersion();
2407
+ async function askRegistry(registry, { timeoutMs, fetchImpl }) {
2341
2408
  const controller = new AbortController();
2342
2409
  const timer = setTimeout(() => controller.abort(), timeoutMs);
2343
2410
  try {
2344
- const response = await fetchImpl(REGISTRY_URL, {
2411
+ const response = await fetchImpl(`${registry}/${PACKAGE_NAME}/latest`, {
2345
2412
  signal: controller.signal,
2346
2413
  headers: { Accept: "application/vnd.npm.install-v1+json" }
2347
2414
  });
2348
- if (!response.ok) {
2349
- return { ok: false, current, errorKey: "update.errorNetwork" };
2350
- }
2415
+ if (!response.ok) return { ok: false, message: `HTTP ${response.status ?? "?"}` };
2351
2416
  const body = await response.json();
2352
2417
  const latest = typeof body?.version === "string" ? body.version : null;
2353
- if (!latest) return { ok: false, current, errorKey: "update.errorNetwork" };
2354
- return {
2355
- ok: true,
2356
- current,
2357
- latest,
2358
- updateAvailable: compareVersions(latest, current) > 0
2359
- };
2360
- } catch {
2361
- return { ok: false, current, errorKey: "update.errorNetwork" };
2418
+ if (!latest) return { ok: false, message: "registry answered without a version" };
2419
+ return { ok: true, latest };
2420
+ } catch (error) {
2421
+ return { ok: false, message: error?.name === "AbortError" ? "timed out" : String(error?.message || error) };
2362
2422
  } finally {
2363
2423
  clearTimeout(timer);
2364
2424
  }
2365
2425
  }
2426
+ async function checkForUpdate2({
2427
+ timeoutMs = 6e3,
2428
+ fetchImpl = globalThis.fetch,
2429
+ registries = registryCandidates()
2430
+ } = {}) {
2431
+ const current = currentVersion();
2432
+ const failures = [];
2433
+ for (const registry of registries) {
2434
+ const attempt = await askRegistry(registry, { timeoutMs, fetchImpl });
2435
+ if (attempt.ok) {
2436
+ return {
2437
+ ok: true,
2438
+ current,
2439
+ latest: attempt.latest,
2440
+ registry,
2441
+ updateAvailable: compareVersions(attempt.latest, current) > 0
2442
+ };
2443
+ }
2444
+ failures.push(`${registry}: ${attempt.message}`);
2445
+ }
2446
+ return {
2447
+ ok: false,
2448
+ current,
2449
+ errorKey: "update.errorNetwork",
2450
+ message: failures.join("; "),
2451
+ triedRegistries: registries
2452
+ };
2453
+ }
2366
2454
  function performUpdate2({
2367
2455
  spawnImpl = spawn,
2368
2456
  timeoutMs = 18e4,
2369
2457
  // Seam for tests: the suite runs from a checkout, where the guard below is
2370
2458
  // correctly the only reachable outcome.
2371
- installKindImpl = installKind2
2459
+ installKindImpl = installKind2,
2460
+ // The registry that just answered the version check. Installing from
2461
+ // somewhere the machine has been shown to reach beats installing from a
2462
+ // default it may have no route to. Empty means "whatever npm is configured
2463
+ // with", which is the right answer when no check has run.
2464
+ registry = ""
2372
2465
  } = {}) {
2373
2466
  return new Promise((resolve) => {
2374
2467
  const kind = installKindImpl();
@@ -2376,7 +2469,10 @@ var require_updater = __commonJS({
2376
2469
  resolve({ ok: false, errorKey: `update.cannot.${kind}` });
2377
2470
  return;
2378
2471
  }
2379
- const child = spawnImpl("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], {
2472
+ const args = ["install", "-g", `${PACKAGE_NAME}@latest`];
2473
+ const normalizedRegistry = normalizeRegistry(registry);
2474
+ if (normalizedRegistry) args.push("--registry", normalizedRegistry);
2475
+ const child = spawnImpl("npm", args, {
2380
2476
  encoding: "utf8",
2381
2477
  timeout: timeoutMs
2382
2478
  });
@@ -2396,12 +2492,15 @@ var require_updater = __commonJS({
2396
2492
  }
2397
2493
  module.exports = {
2398
2494
  PACKAGE_NAME,
2495
+ DEFAULT_REGISTRY,
2496
+ MIRROR_REGISTRY,
2399
2497
  canSelfUpdate: canSelfUpdate2,
2400
2498
  checkForUpdate: checkForUpdate2,
2401
2499
  compareVersions,
2402
2500
  currentVersion,
2403
2501
  installKind: installKind2,
2404
- performUpdate: performUpdate2
2502
+ performUpdate: performUpdate2,
2503
+ registryCandidates
2405
2504
  };
2406
2505
  }
2407
2506
  });
@@ -3105,6 +3204,7 @@ function RelayScreen({ ctx }) {
3105
3204
  const [revealTokens, setRevealTokens] = useState5(false);
3106
3205
  const [showEnv, setShowEnv] = useState5(false);
3107
3206
  const [password, setPassword] = useState5(ctx.runtime.relayPassword || "");
3207
+ const official = (0, import_settings_model.isOfficialRelay)(draft);
3108
3208
  const fields = useMemo2(
3109
3209
  () => (0, import_settings_model.fieldsForMode)(draft.relay.mode).filter((field) => ["mode", "port", "lanHost", "remoteUrl", "publicUrl"].includes(field.id)),
3110
3210
  [draft.relay.mode]
@@ -3112,36 +3212,45 @@ function RelayScreen({ ctx }) {
3112
3212
  const entries = [
3113
3213
  ...fields.map((field) => ({
3114
3214
  id: field.id,
3115
- kind: "field",
3215
+ // The official relay's address is ours and its browser URL follows from
3216
+ // it, so neither is offered for editing: an editable box holding a value
3217
+ // that must not change is an invitation to break the setup.
3218
+ kind: official && (field.id === "remoteUrl" || field.id === "publicUrl") ? "readonly" : "field",
3116
3219
  fieldKind: field.kind,
3117
3220
  label: t(field.labelKey)
3118
3221
  })),
3119
3222
  // The password only matters when talking to a relay somebody else started;
3120
- // a local relay is configured with our own token automatically.
3121
- ...draft.relay.mode === "remote" ? [{ id: "password", kind: "password", label: t("relay.password") }] : [],
3223
+ // a local relay is configured with our own token automatically, and the
3224
+ // official one authenticates workstations by their own host token.
3225
+ ...draft.relay.mode === "remote" && !official ? [{ id: "password", kind: "password", label: t("relay.password") }] : [],
3122
3226
  // Saving needs a row of its own. Every field here only edits a draft, and
3123
3227
  // the services restart from what is on disk — with `s` as the sole way to
3124
3228
  // commit, a change could look applied, survive a restart, and never take
3125
3229
  // effect. It is also the only way to save with the mouse.
3126
3230
  { id: "save", kind: "action", label: t("common.save") },
3127
3231
  { id: "test", kind: "action", label: t("relay.test") },
3128
- ...draft.relay.mode === "remote" ? [
3232
+ ...draft.relay.mode === "remote" && !official ? [
3129
3233
  { id: "reveal", kind: "action", label: revealTokens ? t("relay.hidePassword") : t("relay.showPassword") },
3130
3234
  { id: "env", kind: "action", label: t("relay.envSnippet") }
3131
3235
  ] : [],
3132
3236
  { id: "regenerate", kind: "action", label: t("relay.regenerate") }
3133
3237
  ];
3134
3238
  const addresses = useMemo2(() => listReachableAddresses({ includeLoopback: false }), []);
3135
- const applyField = (id, value) => {
3136
- const result = (0, import_settings_model.setField)(draft, id, value);
3137
- if (result.errorKey) {
3138
- ctx.notify(t(result.errorKey), "error");
3139
- return false;
3239
+ const applyFields = (updates) => {
3240
+ let next = draft;
3241
+ for (const [id, value] of updates) {
3242
+ const result = (0, import_settings_model.setField)(next, id, value);
3243
+ if (result.errorKey) {
3244
+ ctx.notify(t(result.errorKey), "error");
3245
+ return false;
3246
+ }
3247
+ next = result.draft;
3140
3248
  }
3141
- ctx.updateDraft(result.draft);
3249
+ ctx.updateDraft(next);
3142
3250
  ctx.notify("", "info");
3143
3251
  return true;
3144
3252
  };
3253
+ const applyField = (id, value) => applyFields([[id, value]]);
3145
3254
  const save = () => {
3146
3255
  try {
3147
3256
  const result = (0, import_settings_model.saveDraft)(draft);
@@ -3155,6 +3264,10 @@ function RelayScreen({ ctx }) {
3155
3264
  const activate = (id) => {
3156
3265
  const entry = entries.find((candidate) => candidate.id === id);
3157
3266
  if (!entry) return;
3267
+ if (entry.kind === "readonly") {
3268
+ ctx.notify(t("relay.officialFixed"), "info");
3269
+ return;
3270
+ }
3158
3271
  if (entry.kind === "field" || entry.kind === "password") {
3159
3272
  ctx.setEditing(id);
3160
3273
  return;
@@ -3209,14 +3322,13 @@ function RelayScreen({ ctx }) {
3209
3322
  { id: "official", label: t("mode.official"), description: t("mode.official.description") },
3210
3323
  { id: "remote", label: t("mode.remote"), description: t("mode.remote.description") }
3211
3324
  ],
3212
- current: draft.relay.mode === "remote" && draft.relay.remoteUrl === import_config.OFFICIAL_RELAY_URL ? "official" : draft.relay.mode,
3325
+ current: (0, import_settings_model.selectedMode)(draft),
3213
3326
  onPick: (choice) => {
3327
+ if (!applyField("mode", choice)) return;
3214
3328
  if (choice === "official") {
3215
- if (applyField("mode", "remote")) applyField("remoteUrl", import_config.OFFICIAL_RELAY_URL);
3216
- ctx.setEditing(null);
3217
- return;
3329
+ setPassword("");
3330
+ (0, import_service.setRelayPassword)("");
3218
3331
  }
3219
- applyField("mode", choice);
3220
3332
  ctx.setEditing(null);
3221
3333
  },
3222
3334
  onCancel: () => ctx.setEditing(null)
@@ -3280,10 +3392,14 @@ function RelayScreen({ ctx }) {
3280
3392
  },
3281
3393
  onCancel: () => ctx.setEditing(null)
3282
3394
  }
3395
+ ) : entry.kind === "readonly" ? (
3396
+ // Fixed by us, so it is printed rather than offered for editing.
3397
+ /* @__PURE__ */ jsx7(Text6, { color: theme.muted, children: `${(0, import_settings_model.getField)(draft, entry.id) || placeholderText(ctx, entry.id)} (${t("relay.locked")})` })
3283
3398
  ) : entry.fieldKind === "choice" ? (
3284
3399
  // Choice fields are picked from a list, never typed, so they
3285
- // show the translated label rather than the stored id.
3286
- /* @__PURE__ */ jsx7(Text6, { children: t(`mode.${(0, import_settings_model.getField)(draft, entry.id)}`) })
3400
+ // show the translated label rather than the stored id — and the
3401
+ // official relay is named as itself, not as "self-hosted".
3402
+ /* @__PURE__ */ jsx7(Text6, { children: t(`mode.${(0, import_settings_model.selectedMode)(draft)}`) })
3287
3403
  ) : /* @__PURE__ */ jsx7(
3288
3404
  TextField,
3289
3405
  {
@@ -3300,13 +3416,13 @@ function RelayScreen({ ctx }) {
3300
3416
  entry.id
3301
3417
  )),
3302
3418
  ctx.dirty ? /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text6, { color: theme.warn, children: t("hint.unsavedChanges") }) }) : null,
3303
- draft.relay.mode === "remote" ? /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text6, { color: theme.muted, children: t("relay.passwordHint") }) }) : null,
3419
+ draft.relay.mode === "remote" ? /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text6, { color: theme.muted, children: official ? t("relay.officialHint") : t("relay.passwordHint") }) }) : null,
3304
3420
  /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Row, { label: t("relay.identity"), children: /* @__PURE__ */ jsx7(Text6, { color: theme.muted, children: ctx.runtime.hostId }) }) }),
3305
3421
  showEnv ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: theme.border, paddingX: 1, children: [
3306
3422
  /* @__PURE__ */ jsx7(Text6, { color: theme.muted, children: t("relay.envHint") }),
3307
3423
  /* @__PURE__ */ jsx7(Text6, { children: relayStartCommand(ctx.config, password) })
3308
3424
  ] }) : null,
3309
- /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text6, { color: theme.muted, children: t("relay.docsHint") }) }),
3425
+ official ? null : /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text6, { color: theme.muted, children: t("relay.docsHint") }) }),
3310
3426
  /* @__PURE__ */ jsx7(Message, { text: ctx.message?.text ?? null, level: ctx.message?.level ?? "info" })
3311
3427
  ] });
3312
3428
  }
@@ -3598,6 +3714,7 @@ function About({ ctx }) {
3598
3714
  const { t, draft } = ctx;
3599
3715
  const [selected, setSelected] = useState8("auto");
3600
3716
  const [update, setUpdate] = useState8({ phase: "idle" });
3717
+ const registryRef = useState8(() => ({ current: "" }))[0];
3601
3718
  const detected = detectLocale({ preference: "auto" });
3602
3719
  const languageOptions = [
3603
3720
  { id: "auto", label: t("about.languageAuto", { detected }) },
@@ -3629,13 +3746,15 @@ function About({ ctx }) {
3629
3746
  const result = await (0, import_updater.checkForUpdate)();
3630
3747
  if (!result.ok) {
3631
3748
  setUpdate({ phase: "error", messageKey: result.errorKey ?? "update.errorNetwork" });
3749
+ if (result.message) ctx.notify(t("update.errorNetworkDetail", { message: result.message }), "error");
3632
3750
  return;
3633
3751
  }
3752
+ registryRef.current = result.registry ?? "";
3634
3753
  setUpdate(result.updateAvailable ? { phase: "available", latest: result.latest } : { phase: "current", latest: result.latest });
3635
3754
  };
3636
3755
  const runUpdate = async (latest) => {
3637
3756
  setUpdate({ phase: "updating", latest });
3638
- const result = await (0, import_updater.performUpdate)();
3757
+ const result = await (0, import_updater.performUpdate)({ registry: registryRef.current });
3639
3758
  if (!result.ok) {
3640
3759
  setUpdate({ phase: "error", messageKey: result.errorKey ?? "update.errorFailed" });
3641
3760
  ctx.notify(t("update.errorFailed"), "error");
@@ -3712,7 +3831,7 @@ function About({ ctx }) {
3712
3831
  children: updateLabel
3713
3832
  }
3714
3833
  ) }),
3715
- /* @__PURE__ */ jsx10(Row, { label: t("about.version"), children: /* @__PURE__ */ jsx10(Text9, { color: theme.muted, children: "0.2.2" }) }),
3834
+ /* @__PURE__ */ jsx10(Row, { label: t("about.version"), children: /* @__PURE__ */ jsx10(Text9, { color: theme.muted, children: "0.2.4" }) }),
3716
3835
  /* @__PURE__ */ jsx10(Row, { label: t("about.configPath"), children: /* @__PURE__ */ jsx10(Text9, { color: theme.muted, children: (0, import_config.configPath)() }) }),
3717
3836
  /* @__PURE__ */ jsx10(Row, { label: t("about.statePath"), children: /* @__PURE__ */ jsx10(Text9, { color: theme.muted, children: (0, import_config.stateDir)() }) }),
3718
3837
  /* @__PURE__ */ jsx10(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text9, { color: theme.muted, children: t("about.docs") }) }),
@@ -3724,10 +3843,10 @@ function About({ ctx }) {
3724
3843
  import { useMemo as useMemo3, useState as useState9 } from "react";
3725
3844
  import { Box as Box10, Text as Text10, useInput as useInput8 } from "ink";
3726
3845
  import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3727
- function stepsFor(mode) {
3846
+ function stepsFor(mode, remoteUrl) {
3728
3847
  const steps = ["language", "access"];
3729
3848
  if (mode === "lan") steps.push("address");
3730
- if (mode === "remote") steps.push("relayUrl", "password");
3849
+ if (mode === "remote" && remoteUrl !== import_config.OFFICIAL_RELAY_URL) steps.push("relayUrl", "password");
3731
3850
  steps.push("finish");
3732
3851
  return steps;
3733
3852
  }
@@ -3742,18 +3861,26 @@ function Wizard({ ctx, onDone }) {
3742
3861
  () => listReachableAddresses({ includeLoopback: false }),
3743
3862
  []
3744
3863
  );
3745
- const steps = useMemo3(() => stepsFor(draft.relay.mode), [draft.relay.mode]);
3864
+ const steps = useMemo3(
3865
+ () => stepsFor(draft.relay.mode, draft.relay.remoteUrl),
3866
+ [draft.relay.mode, draft.relay.remoteUrl]
3867
+ );
3746
3868
  const stepIndex = Math.max(0, steps.indexOf(step));
3747
- const apply = (id, value) => {
3748
- const result = (0, import_settings_model.setField)(draft, id, value);
3749
- if (result.errorKey) {
3750
- ctx.notify(t(result.errorKey), "error");
3751
- return false;
3869
+ const applyAll = (updates) => {
3870
+ let next = draft;
3871
+ for (const [id, value] of updates) {
3872
+ const result = (0, import_settings_model.setField)(next, id, value);
3873
+ if (result.errorKey) {
3874
+ ctx.notify(t(result.errorKey), "error");
3875
+ return false;
3876
+ }
3877
+ next = result.draft;
3752
3878
  }
3753
- ctx.updateDraft(result.draft);
3879
+ ctx.updateDraft(next);
3754
3880
  ctx.notify("", "info");
3755
3881
  return true;
3756
3882
  };
3883
+ const apply = (id, value) => applyAll([[id, value]]);
3757
3884
  const advance = (from, order = steps) => {
3758
3885
  const index = order.indexOf(from);
3759
3886
  setStep(order[Math.min(order.length - 1, index + 1)]);
@@ -3835,16 +3962,17 @@ function Wizard({ ctx, onDone }) {
3835
3962
  description: t("mode.remote.description")
3836
3963
  }
3837
3964
  ],
3838
- current: draft.relay.mode === "remote" && draft.relay.remoteUrl === import_config.OFFICIAL_RELAY_URL ? "official" : draft.relay.mode,
3965
+ current: (0, import_settings_model.selectedMode)(draft),
3839
3966
  onPick: (choice) => {
3840
3967
  if (choice === "official") {
3841
- if (!apply("mode", "remote")) return;
3842
- if (!apply("remoteUrl", import_config.OFFICIAL_RELAY_URL)) return;
3968
+ if (!apply("mode", "official")) return;
3843
3969
  (0, import_service.setRelayPassword)("");
3844
- advance("access", ["language", "access", "finish"]);
3970
+ advance("access", stepsFor("remote", import_config.OFFICIAL_RELAY_URL));
3845
3971
  return;
3846
3972
  }
3847
- if (apply("mode", choice)) advance("access", stepsFor(choice));
3973
+ if (apply("mode", choice)) {
3974
+ advance("access", stepsFor(choice, choice === "remote" ? "" : draft.relay.remoteUrl));
3975
+ }
3848
3976
  },
3849
3977
  onCancel: back
3850
3978
  }