herdr-remote 0.2.32 → 0.2.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +3 -1
  2. package/README.zh-CN.md +2 -1
  3. package/dist/connector/agent-watch.js +16 -3
  4. package/dist/connector/herdr-prefix-keys.js +28 -0
  5. package/dist/connector/host-connector.js +6 -20
  6. package/dist/connector/host-hello.js +32 -0
  7. package/dist/herdr-api.js +7 -1
  8. package/dist/herdr-command.js +7 -2
  9. package/dist/i18n/en.js +5 -18
  10. package/dist/i18n/zh.js +5 -18
  11. package/dist/pty-session.js +27 -2
  12. package/dist/socket-discovery.js +16 -6
  13. package/dist/terminal-font/formats.js +26 -6
  14. package/dist/terminal-font/terminals.js +19 -3
  15. package/dist/tui/App.js +3 -25
  16. package/dist/tui/api.js +3 -3
  17. package/dist/tui/screens/About.js +18 -27
  18. package/dist/update-command.js +16 -19
  19. package/dist/updater.js +91 -48
  20. package/herdr-plugin.toml +2 -2
  21. package/node_modules/herdr-remote-relay/LICENSE +21 -0
  22. package/node_modules/herdr-remote-relay/README.md +76 -0
  23. package/node_modules/herdr-remote-relay/README.zh-CN.md +76 -0
  24. package/node_modules/herdr-remote-relay/bin/herdr-remote-relay.js +116 -0
  25. package/node_modules/herdr-remote-relay/config.example.json +18 -0
  26. package/node_modules/herdr-remote-relay/deploy/cloudflared/config.yml.example +9 -0
  27. package/node_modules/herdr-remote-relay/deploy/docker/compose.yaml +23 -0
  28. package/node_modules/herdr-remote-relay/deploy/nginx/herdr-remote-relay.conf.example +49 -0
  29. package/node_modules/herdr-remote-relay/deploy/systemd/herdr-remote-relay.service +41 -0
  30. package/node_modules/herdr-remote-relay/deploy/systemd/relay.env.example +25 -0
  31. package/node_modules/herdr-remote-relay/dist/auth-store.d.ts +114 -0
  32. package/node_modules/herdr-remote-relay/dist/auth-store.js +289 -0
  33. package/node_modules/herdr-remote-relay/dist/metrics.d.ts +76 -0
  34. package/node_modules/herdr-remote-relay/dist/metrics.js +249 -0
  35. package/node_modules/herdr-remote-relay/dist/protocol/frames.d.ts +27 -0
  36. package/node_modules/herdr-remote-relay/dist/protocol/frames.js +110 -0
  37. package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.d.ts +8 -0
  38. package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.js +34 -0
  39. package/node_modules/herdr-remote-relay/dist/protocol/http.d.ts +165 -0
  40. package/node_modules/herdr-remote-relay/dist/protocol/http.js +3 -0
  41. package/node_modules/herdr-remote-relay/dist/protocol/index.d.ts +7 -0
  42. package/node_modules/herdr-remote-relay/dist/protocol/index.js +9 -0
  43. package/node_modules/herdr-remote-relay/dist/protocol/messages.d.ts +362 -0
  44. package/node_modules/herdr-remote-relay/dist/protocol/messages.js +20 -0
  45. package/node_modules/herdr-remote-relay/dist/protocol/paste.d.ts +14 -0
  46. package/node_modules/herdr-remote-relay/dist/protocol/paste.js +50 -0
  47. package/node_modules/herdr-remote-relay/dist/protocol/release.d.ts +8 -0
  48. package/node_modules/herdr-remote-relay/dist/protocol/release.js +17 -0
  49. package/node_modules/herdr-remote-relay/dist/protocol/terminal.d.ts +79 -0
  50. package/node_modules/herdr-remote-relay/dist/protocol/terminal.js +150 -0
  51. package/node_modules/herdr-remote-relay/dist/relay-config.d.ts +74 -0
  52. package/node_modules/herdr-remote-relay/dist/relay-config.js +316 -0
  53. package/node_modules/herdr-remote-relay/dist/relay-server.d.ts +64 -0
  54. package/node_modules/herdr-remote-relay/dist/relay-server.js +194 -0
  55. package/node_modules/herdr-remote-relay/dist/scroll-input.d.ts +17 -0
  56. package/node_modules/herdr-remote-relay/dist/scroll-input.js +124 -0
  57. package/node_modules/herdr-remote-relay/dist/server/client-channel.d.ts +3 -0
  58. package/node_modules/herdr-remote-relay/dist/server/client-channel.js +396 -0
  59. package/node_modules/herdr-remote-relay/dist/server/font-proxy.d.ts +19 -0
  60. package/node_modules/herdr-remote-relay/dist/server/font-proxy.js +155 -0
  61. package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.d.ts +27 -0
  62. package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.js +83 -0
  63. package/node_modules/herdr-remote-relay/dist/server/host-channel.d.ts +3 -0
  64. package/node_modules/herdr-remote-relay/dist/server/host-channel.js +271 -0
  65. package/node_modules/herdr-remote-relay/dist/server/http-api.d.ts +3 -0
  66. package/node_modules/herdr-remote-relay/dist/server/http-api.js +214 -0
  67. package/node_modules/herdr-remote-relay/dist/server/maintenance.d.ts +5 -0
  68. package/node_modules/herdr-remote-relay/dist/server/maintenance.js +64 -0
  69. package/node_modules/herdr-remote-relay/dist/server/requests.d.ts +34 -0
  70. package/node_modules/herdr-remote-relay/dist/server/requests.js +142 -0
  71. package/node_modules/herdr-remote-relay/dist/server/sessions.d.ts +36 -0
  72. package/node_modules/herdr-remote-relay/dist/server/sessions.js +230 -0
  73. package/node_modules/herdr-remote-relay/dist/server/sockets.d.ts +12 -0
  74. package/node_modules/herdr-remote-relay/dist/server/sockets.js +64 -0
  75. package/node_modules/herdr-remote-relay/dist/server/status.d.ts +11 -0
  76. package/node_modules/herdr-remote-relay/dist/server/status.js +99 -0
  77. package/node_modules/herdr-remote-relay/dist/server/transport.d.ts +16 -0
  78. package/node_modules/herdr-remote-relay/dist/server/transport.js +110 -0
  79. package/node_modules/herdr-remote-relay/dist/server/types.d.ts +131 -0
  80. package/node_modules/herdr-remote-relay/dist/server/types.js +3 -0
  81. package/node_modules/herdr-remote-relay/dist/server/version.d.ts +2 -0
  82. package/node_modules/herdr-remote-relay/dist/server/version.js +5 -0
  83. package/node_modules/herdr-remote-relay/dist/state.d.ts +6 -0
  84. package/node_modules/herdr-remote-relay/dist/state.js +47 -0
  85. package/node_modules/herdr-remote-relay/package.json +76 -0
  86. package/node_modules/herdr-remote-relay/web/dist/assets/index-BeveQ9Di.js +61 -0
  87. package/node_modules/herdr-remote-relay/web/dist/assets/index-XYYRsgSb.css +32 -0
  88. package/node_modules/herdr-remote-relay/web/dist/assets/vendor-react-CYSfZuHu.js +17 -0
  89. package/node_modules/herdr-remote-relay/web/dist/assets/vendor-xterm-BhCxil_4.js +9 -0
  90. package/node_modules/herdr-remote-relay/web/dist/fonts/SymbolsNerdFontMono-Regular.woff2 +0 -0
  91. package/node_modules/herdr-remote-relay/web/dist/fonts/cascadia-code/OFL.txt +93 -0
  92. package/node_modules/herdr-remote-relay/web/dist/fonts/cascadia-code/cascadia-code-latin-400-italic.woff2 +0 -0
  93. package/node_modules/herdr-remote-relay/web/dist/fonts/cascadia-code/cascadia-code-latin-400-normal.woff2 +0 -0
  94. package/node_modules/herdr-remote-relay/web/dist/fonts/cascadia-code/cascadia-code-latin-700-italic.woff2 +0 -0
  95. package/node_modules/herdr-remote-relay/web/dist/fonts/cascadia-code/cascadia-code-latin-700-normal.woff2 +0 -0
  96. package/node_modules/herdr-remote-relay/web/dist/fonts/cascadia-code/cascadia-code-latin-ext-400-italic.woff2 +0 -0
  97. package/node_modules/herdr-remote-relay/web/dist/fonts/cascadia-code/cascadia-code-latin-ext-400-normal.woff2 +0 -0
  98. package/node_modules/herdr-remote-relay/web/dist/fonts/cascadia-code/cascadia-code-latin-ext-700-italic.woff2 +0 -0
  99. package/node_modules/herdr-remote-relay/web/dist/fonts/cascadia-code/cascadia-code-latin-ext-700-normal.woff2 +0 -0
  100. package/node_modules/herdr-remote-relay/web/dist/fonts/fira-code/OFL.txt +93 -0
  101. package/node_modules/herdr-remote-relay/web/dist/fonts/fira-code/fira-code-latin-400-normal.woff2 +0 -0
  102. package/node_modules/herdr-remote-relay/web/dist/fonts/fira-code/fira-code-latin-700-normal.woff2 +0 -0
  103. package/node_modules/herdr-remote-relay/web/dist/fonts/fira-code/fira-code-latin-ext-400-normal.woff2 +0 -0
  104. package/node_modules/herdr-remote-relay/web/dist/fonts/fira-code/fira-code-latin-ext-700-normal.woff2 +0 -0
  105. package/node_modules/herdr-remote-relay/web/dist/fonts/ibm-plex-mono/OFL.txt +93 -0
  106. package/node_modules/herdr-remote-relay/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-italic.woff2 +0 -0
  107. package/node_modules/herdr-remote-relay/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  108. package/node_modules/herdr-remote-relay/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-700-italic.woff2 +0 -0
  109. package/node_modules/herdr-remote-relay/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-700-normal.woff2 +0 -0
  110. package/node_modules/herdr-remote-relay/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-italic.woff2 +0 -0
  111. package/node_modules/herdr-remote-relay/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  112. package/node_modules/herdr-remote-relay/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-700-italic.woff2 +0 -0
  113. package/node_modules/herdr-remote-relay/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-700-normal.woff2 +0 -0
  114. package/node_modules/herdr-remote-relay/web/dist/fonts/jetbrains-mono/OFL.txt +93 -0
  115. package/node_modules/herdr-remote-relay/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-400-italic.woff2 +0 -0
  116. package/node_modules/herdr-remote-relay/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2 +0 -0
  117. package/node_modules/herdr-remote-relay/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-700-italic.woff2 +0 -0
  118. package/node_modules/herdr-remote-relay/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-700-normal.woff2 +0 -0
  119. package/node_modules/herdr-remote-relay/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-italic.woff2 +0 -0
  120. package/node_modules/herdr-remote-relay/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
  121. package/node_modules/herdr-remote-relay/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-700-italic.woff2 +0 -0
  122. package/node_modules/herdr-remote-relay/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-700-normal.woff2 +0 -0
  123. package/node_modules/herdr-remote-relay/web/dist/fonts/source-code-pro/OFL.txt +93 -0
  124. package/node_modules/herdr-remote-relay/web/dist/fonts/source-code-pro/source-code-pro-latin-400-italic.woff2 +0 -0
  125. package/node_modules/herdr-remote-relay/web/dist/fonts/source-code-pro/source-code-pro-latin-400-normal.woff2 +0 -0
  126. package/node_modules/herdr-remote-relay/web/dist/fonts/source-code-pro/source-code-pro-latin-700-italic.woff2 +0 -0
  127. package/node_modules/herdr-remote-relay/web/dist/fonts/source-code-pro/source-code-pro-latin-700-normal.woff2 +0 -0
  128. package/node_modules/herdr-remote-relay/web/dist/fonts/source-code-pro/source-code-pro-latin-ext-400-italic.woff2 +0 -0
  129. package/node_modules/herdr-remote-relay/web/dist/fonts/source-code-pro/source-code-pro-latin-ext-400-normal.woff2 +0 -0
  130. package/node_modules/herdr-remote-relay/web/dist/fonts/source-code-pro/source-code-pro-latin-ext-700-italic.woff2 +0 -0
  131. package/node_modules/herdr-remote-relay/web/dist/fonts/source-code-pro/source-code-pro-latin-ext-700-normal.woff2 +0 -0
  132. package/node_modules/herdr-remote-relay/web/dist/index.html +36 -0
  133. package/package.json +6 -3
  134. package/dist/relay-updater.js +0 -81
  135. package/dist/tui/screens/useRelayUpdate.js +0 -122
package/README.md CHANGED
@@ -7,11 +7,13 @@ Web terminal client for [Herdr](https://herdr.dev) workspaces. Mobile-friendly U
7
7
  ## Installation & Usage
8
8
 
9
9
  ```bash
10
- npm install -g herdr-remote
10
+ npm install -g https://github.com/dibin666/herdr-remote/releases/latest/download/herdr-remote.tgz
11
11
  herdr-remote
12
12
  ```
13
13
 
14
14
  Running `herdr-remote` without arguments launches the setup wizard and configuration TUI.
15
+ Releases go to [GitHub Releases](https://github.com/dibin666/herdr-remote/releases) first and to
16
+ npm as a fallback (`npm install -g herdr-remote`); `herdr-remote update` follows the same order.
15
17
 
16
18
  Native Windows 10/11 is supported with Node.js 22+; install Herdr from PowerShell with
17
19
  `powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex"`.
package/README.zh-CN.md CHANGED
@@ -7,11 +7,12 @@
7
7
  ## 安装与运行
8
8
 
9
9
  ```bash
10
- npm install -g herdr-remote
10
+ npm install -g https://github.com/dibin666/herdr-remote/releases/latest/download/herdr-remote.tgz
11
11
  herdr-remote
12
12
  ```
13
13
 
14
14
  直接运行 `herdr-remote` 即可启动向导并进入配置 TUI。
15
+ 新版本先发布到 [GitHub Releases](https://github.com/dibin666/herdr-remote/releases),npm 作为备用渠道(`npm install -g herdr-remote`);`herdr-remote update` 按同样的顺序更新。
15
16
 
16
17
  已支持 Windows 10/11 原生运行(需 Node.js 22+),请在 PowerShell 中执行
17
18
  `powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex"` 安装 Herdr。
@@ -19,7 +19,10 @@ const AGENT_STATUS_SUBSCRIPTIONS = [
19
19
  *
20
20
  * A long-lived subscription provides fast refreshes on pane and workspace
21
21
  * focus changes; the five-second poll still repairs missed events and a
22
- * restarted Herdr server. Both paths only read the side-channel snapshot.
22
+ * restarted Herdr server. Both paths only read the side-channel snapshot, and
23
+ * events only invalidate it: Herdr's snapshots and events share no sequence,
24
+ * so each (re)started subscription and each event mid-read asks for a fresh
25
+ * read rather than patching the last one.
23
26
  */
24
27
  export class AgentWatch {
25
28
  socketPath;
@@ -30,6 +33,7 @@ export class AgentWatch {
30
33
  debounceTimer = null;
31
34
  subscription = null;
32
35
  pending = false;
36
+ stale = false;
33
37
  lastSummary = null;
34
38
  constructor(options, send) {
35
39
  this.socketPath = options.socketPath;
@@ -43,7 +47,7 @@ export class AgentWatch {
43
47
  this.pollTimer = setInterval(() => this.read(), AGENT_STATUS_POLL_MS);
44
48
  this.pollTimer.unref?.();
45
49
  try {
46
- this.subscription = this.subscribe(this.socketPath, AGENT_STATUS_SUBSCRIPTIONS, () => this.scheduleRead());
50
+ this.subscription = this.subscribe(this.socketPath, AGENT_STATUS_SUBSCRIPTIONS, () => this.scheduleRead(), { onStarted: () => this.scheduleRead() });
47
51
  }
48
52
  catch {
49
53
  // A missing or restarting Herdr server leaves the regular poll in place.
@@ -63,6 +67,7 @@ export class AgentWatch {
63
67
  this.subscription.close();
64
68
  this.subscription = null;
65
69
  }
70
+ this.stale = false;
66
71
  this.lastSummary = null;
67
72
  }
68
73
  scheduleRead() {
@@ -84,9 +89,14 @@ export class AgentWatch {
84
89
  * Herdr's server not running is an ordinary state, not news.
85
90
  */
86
91
  async read() {
87
- if (this.pending || !this.pollTimer)
92
+ if (!this.pollTimer)
93
+ return;
94
+ if (this.pending) {
95
+ this.stale = true;
88
96
  return;
97
+ }
89
98
  this.pending = true;
99
+ this.stale = false;
90
100
  let snapshot;
91
101
  try {
92
102
  const result = await this.request(this.socketPath, 'session.snapshot', {});
@@ -97,6 +107,9 @@ export class AgentWatch {
97
107
  }
98
108
  finally {
99
109
  this.pending = false;
110
+ // Something changed while this answer was in flight; it may predate it.
111
+ if (this.stale)
112
+ this.scheduleRead();
100
113
  }
101
114
  // Stopped while the answer was in flight; the browser it was for is gone.
102
115
  if (!this.pollTimer)
@@ -0,0 +1,28 @@
1
+ // The keys that put this workstation's Herdr into prefix mode. A browser has
2
+ // to tell a Herdr command from typed text, and only Herdr's own config file
3
+ // says which key starts one, so the host reads it and reports it in its hello.
4
+ import fs from 'node:fs';
5
+ import { sanitizeHerdrPrefixKeys } from 'herdr-remote-relay/protocol';
6
+ import { herdrConfigPath } from '../socket-discovery.js';
7
+ import { parseTomlSubset } from '../terminal-font/formats.js';
8
+ /** What Herdr uses when `keys.prefix` is absent or names nothing it can read. */
9
+ const DEFAULT_PREFIX_KEYS = ['ctrl+b'];
10
+ /**
11
+ * `keys.prefix` from the Herdr config the connector's Herdr reads: one key or
12
+ * a list, each a combo like `ctrl+space`. Always at least one key, because
13
+ * Herdr itself falls back to `ctrl+b` when the file, the key or every entry
14
+ * is unusable.
15
+ */
16
+ export function readHerdrPrefixKeys(env = process.env, platform = process.platform) {
17
+ let text;
18
+ try {
19
+ text = fs.readFileSync(herdrConfigPath(env, platform), 'utf8');
20
+ }
21
+ catch {
22
+ // No readable config file means Herdr runs on its defaults.
23
+ return [...DEFAULT_PREFIX_KEYS];
24
+ }
25
+ const configured = parseTomlSubset(text)['keys.prefix'];
26
+ const entries = typeof configured === 'string' ? [configured] : configured;
27
+ return sanitizeHerdrPrefixKeys(entries) ?? [...DEFAULT_PREFIX_KEYS];
28
+ }
@@ -3,7 +3,7 @@
3
3
  // message to the part of the connector that owns it.
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
- import { CAPABILITY, PROTOCOL_VERSION, unpackStreamFrame, } from 'herdr-remote-relay/protocol';
6
+ import { unpackStreamFrame } from 'herdr-remote-relay/protocol';
7
7
  import { WebSocket } from 'ws';
8
8
  import { loadConfig } from '../config.js';
9
9
  import { EXIT_AUTH_FAILED, EXIT_REPLACED } from '../exit-codes.js';
@@ -16,6 +16,7 @@ import { randomToken } from 'herdr-remote-relay/state';
16
16
  import { openAdminTabFor } from './admin-tab.js';
17
17
  import { AgentWatch } from './agent-watch.js';
18
18
  import { FontServer } from './font-server.js';
19
+ import { hostHello } from './host-hello.js';
19
20
  import { acquireHostLock, releaseHostLock } from './lock.js';
20
21
  import { Sessions } from './sessions.js';
21
22
  import { UpdateReport } from './update-report.js';
@@ -178,29 +179,14 @@ export class HostConnector {
178
179
  }
179
180
  ws.isAlive = true;
180
181
  this.authFailure = false;
181
- sendJson(ws, {
182
- type: 'host_hello',
183
- protocol: PROTOCOL_VERSION,
182
+ sendJson(ws, hostHello({
184
183
  hostId: this.hostId,
185
184
  token: this.hostToken,
186
- password: this.relayPassword || null,
187
- hostname: os.hostname(),
188
- platform: process.platform,
189
- ...(process.platform === 'win32' &&
190
- (process.env.MSYSTEM || /(?:^|[\\/])(?:bash|sh)(?:\.exe)?$/i.test(process.env.SHELL || ''))
191
- ? { shellProfile: 'git-bash' }
192
- : {}),
193
- arch: process.arch,
185
+ password: this.relayPassword,
194
186
  version: this.updates.runningVersion,
195
- terminalPalette: this.terminalPalette || null,
187
+ terminalPalette: this.terminalPalette,
196
188
  terminalFont: this.font.publicFont(),
197
- capabilities: [
198
- CAPABILITY.hostHandoff,
199
- CAPABILITY.idleHeartbeat,
200
- CAPABILITY.binaryFrameV2,
201
- ...(process.platform === 'win32' ? [CAPABILITY.adminTabs] : []),
202
- ],
203
- });
189
+ }));
204
190
  // The relay sends host_ready with the current browser count. No business
205
191
  // heartbeat is started until that message says somebody is watching.
206
192
  });
@@ -0,0 +1,32 @@
1
+ // The first message a workstation sends the relay: who it is, what it runs,
2
+ // and the facts about its terminal and Herdr that a browser cannot learn itself.
3
+ import os from 'node:os';
4
+ import { CAPABILITY, PROTOCOL_VERSION } from 'herdr-remote-relay/protocol';
5
+ import { readHerdrPrefixKeys } from './herdr-prefix-keys.js';
6
+ /** Built afresh for each connection, so a reconnect reports the config as it is now. */
7
+ export function hostHello(identity) {
8
+ return {
9
+ type: 'host_hello',
10
+ protocol: PROTOCOL_VERSION,
11
+ hostId: identity.hostId,
12
+ token: identity.token,
13
+ password: identity.password || null,
14
+ hostname: os.hostname(),
15
+ platform: process.platform,
16
+ ...(process.platform === 'win32' &&
17
+ (process.env.MSYSTEM || /(?:^|[\\/])(?:bash|sh)(?:\.exe)?$/i.test(process.env.SHELL || ''))
18
+ ? { shellProfile: 'git-bash' }
19
+ : {}),
20
+ arch: process.arch,
21
+ version: identity.version,
22
+ terminalPalette: identity.terminalPalette || null,
23
+ terminalFont: identity.terminalFont,
24
+ herdrPrefixKeys: readHerdrPrefixKeys(),
25
+ capabilities: [
26
+ CAPABILITY.hostHandoff,
27
+ CAPABILITY.idleHeartbeat,
28
+ CAPABILITY.binaryFrameV2,
29
+ ...(process.platform === 'win32' ? [CAPABILITY.adminTabs] : []),
30
+ ],
31
+ };
32
+ }
package/dist/herdr-api.js CHANGED
@@ -122,9 +122,14 @@ function requestHerdr(socketPath, method, params = {}, options = {}) {
122
122
  * They share a socket, but are separate NDJSON messages. A subscription never
123
123
  * starts or writes to a terminal, so an unavailable socket is only a reason to
124
124
  * retry this side channel.
125
+ *
126
+ * Events are not replayed across connections, and from Herdr 0.9.2 a reader
127
+ * that falls behind gets an `events_lost` error carrying the request id and a
128
+ * closed socket. Both end in a reconnect, and `onStarted` fires on every
129
+ * acknowledgement so the caller can re-read what it may have missed.
125
130
  */
126
131
  function subscribeHerdr(socketPath, subscriptions, onEvent, options = {}) {
127
- const { connect = net.connect, retryBaseMs = SUBSCRIBE_RETRY_BASE_MS, retryMaxMs = SUBSCRIBE_RETRY_MAX_MS, } = options;
132
+ const { connect = net.connect, retryBaseMs = SUBSCRIBE_RETRY_BASE_MS, retryMaxMs = SUBSCRIBE_RETRY_MAX_MS, onStarted, } = options;
128
133
  let socket = null;
129
134
  let reconnectTimer = null;
130
135
  let handshakeTimer = null;
@@ -203,6 +208,7 @@ function subscribeHerdr(socketPath, subscriptions, onEvent, options = {}) {
203
208
  }
204
209
  acknowledged = true;
205
210
  retryCount = 0;
211
+ onStarted?.();
206
212
  }
207
213
  else if (acknowledged &&
208
214
  typeof message?.event === 'string' &&
@@ -29,8 +29,13 @@ const COMMAND_NAME = 'herdr';
29
29
  * machine in the background stops resizing somebody else's focused pane, and a
30
30
  * large paste no longer disconnects the client — and pasting is how an image
31
31
  * reaches an agent from a phone.
32
+ *
33
+ * 0.9.3 because a browser types through escape sequences: 0.9.2 stopped a tap
34
+ * on a pane from sending a stray Escape to a working agent and stopped mouse
35
+ * reports split across reads from leaking into the shell, and it also broke
36
+ * Escape followed by a key — Alt+B, Alt+F, Alt+Backspace — which 0.9.3 mends.
32
37
  */
33
- const MIN_HERDR_VERSION = '0.9.1';
38
+ const MIN_HERDR_VERSION = '0.9.3';
34
39
  /** Where a user-level install lands, in the order we trust it. */
35
40
  const FALLBACK_DIRECTORIES = [
36
41
  '~/.local/bin',
@@ -224,7 +229,7 @@ function herdrVersion({ command, timeout = 10_000, ...lookup } = {}) {
224
229
  /** The one line that tells a user why an older Herdr is a problem here. */
225
230
  function herdrOutdatedMessage(version) {
226
231
  return (`Herdr ${version} is older than ${MIN_HERDR_VERSION}, which herdr-remote is written against. ` +
227
- 'Run "herdr update" — window titles, background machine activation and large pastes all ' +
232
+ 'Run "herdr update" — window titles, large pastes, taps on a pane and Alt shortcuts all ' +
228
233
  'misbehave in browser windows before that release.');
229
234
  }
230
235
  /** The one message a user needs to fix a missing install themselves. */
package/dist/i18n/en.js CHANGED
@@ -163,7 +163,7 @@ const en = {
163
163
  'herdr.autoStartHint': 'Off: Herdr starts only when you run it, or when you confirm it from a browser. On: herdr-remote also starts it every time it starts, at boot included. Takes effect the next time herdr-remote starts.',
164
164
  'herdr.cliMissing': 'Command herdr not found. Set HERDR_BIN_PATH to its full path.',
165
165
  'herdr.version': 'Herdr version',
166
- 'herdr.versionOutdated': 'Herdr {version} is older than {minimum}. Run "herdr update": window titles, background machine activation and large pastes misbehave in browser windows before that release.',
166
+ 'herdr.versionOutdated': 'Herdr {version} is older than {minimum}. Run "herdr update": window titles, large pastes, taps on a pane and Alt shortcuts misbehave in browser windows before that release.',
167
167
  'about.title': 'Language & about',
168
168
  'about.language': 'Interface language',
169
169
  'about.languageAuto': 'Follow system ({detected})',
@@ -229,16 +229,16 @@ const en = {
229
229
  'update.title': 'Updates',
230
230
  'update.banner': 'herdr-remote {latest} is out (running {current}). Press 7 and Enter on the update row to install.',
231
231
  'update.check': 'Check for updates',
232
- 'update.checking': 'Checking npm…',
232
+ 'update.checking': 'Checking GitHub Releases and npm…',
233
233
  'update.upToDate': 'Up to date ({version})',
234
234
  'update.available': 'Update {version} available (press Enter to install)',
235
235
  'update.updating': 'Installing {version}…',
236
236
  'update.updatingRetry': 'Installing {version}… (npm has not caught up yet, attempt {attempt})',
237
237
  'update.done': 'Updated to {version}',
238
238
  'update.restartHint': 'Update installed. Restart herdr-remote to apply.',
239
- 'update.errorNetwork': 'Could not reach any npm registry',
240
- 'update.errorNetworkDetail': 'No registry answered: {message}',
241
- 'update.errorFailed': 'Update failed. Run: npm install -g herdr-remote@{version} --prefer-online',
239
+ 'update.errorNetwork': 'Could not reach GitHub Releases or any npm registry',
240
+ 'update.errorNetworkDetail': 'Nothing answered: {message}',
241
+ 'update.errorFailed': 'Update failed. Run: npm install -g {url}',
242
242
  'update.errorFailedDetail': 'npm: {message}',
243
243
  'update.errorNotYetPublished': 'npm has not caught up with {version} yet (common in the first minutes after a release). Press Enter to try again shortly.',
244
244
  'update.errorNotApplied': 'npm reported success, but {installed} is still installed',
@@ -253,18 +253,5 @@ const en = {
253
253
  'update.brokerRestartFailed': 'The admin terminal broker did not start again. Sign out of Windows and back in, or run herdr-remote admin-broker restart.',
254
254
  'update.brokerBusy': 'An admin terminal is open, so the broker keeps running the previous release. Close admin terminals, then run herdr-remote admin-broker restart.',
255
255
  'update.errorBusy': "A process still holds the herdr-remote folder; an admin terminal broker set up by an older release does. In an administrator PowerShell run: Get-ScheduledTask -TaskName 'HerdrRemoteAdminBroker-*' | Stop-ScheduledTask. Then run herdr-remote update again here, and afterwards herdr-remote admin-broker install as administrator.",
256
- 'relayUpdate.check': 'Relay {version}: check for updates',
257
- 'relayUpdate.checking': 'Relay: checking npm…',
258
- 'relayUpdate.upToDate': 'Relay up to date ({version})',
259
- 'relayUpdate.available': 'Relay {latest} available, installed {installed} (press Enter to install)',
260
- 'relayUpdate.needsCli': 'Relay {latest} needs a newer herdr-remote: update herdr-remote first',
261
- 'relayUpdate.updating': 'Installing relay {version}…',
262
- 'relayUpdate.updatingRetry': 'Installing relay {version}… (npm has not caught up yet, attempt {attempt})',
263
- 'relayUpdate.restart': 'Relay {version} installed, {running} still running (press Enter to restart)',
264
- 'relayUpdate.restarting': 'Restarting herdr-remote…',
265
- 'relayUpdate.restarted': 'herdr-remote restarted; the relay now runs {version}.',
266
- 'relayUpdate.done': 'Relay updated to {version}',
267
- 'relayUpdate.errorNotApplied': 'npm finished, but relay {installed} is still installed',
268
- 'relayUpdate.banner': 'Relay {latest} is out (installed {current}). Press 7 and Enter on the relay row to install.',
269
256
  };
270
257
  export default en;
package/dist/i18n/zh.js CHANGED
@@ -162,7 +162,7 @@ const zh = {
162
162
  'herdr.autoStartHint': '关:Herdr 只在你手动运行,或在浏览器里确认后才启动。开:herdr-remote 每次启动(包括开机)时一并启动 Herdr。下次 herdr-remote 启动时生效。',
163
163
  'herdr.cliMissing': '未找到 herdr 命令,可将 HERDR_BIN_PATH 设为其完整路径。',
164
164
  'herdr.version': 'Herdr 版本',
165
- 'herdr.versionOutdated': 'Herdr {version} 低于 {minimum},请运行 “herdr update”:在该版本之前,浏览器窗口的标题、后台机器激活与大段粘贴都会出问题。',
165
+ 'herdr.versionOutdated': 'Herdr {version} 低于 {minimum},请运行 “herdr update”:在该版本之前,浏览器窗口的标题、大段粘贴、点按窗格与 Alt 快捷键都会出问题。',
166
166
  'about.title': '语言与关于',
167
167
  'about.language': '界面语言',
168
168
  'about.languageAuto': '跟随系统({detected})',
@@ -228,16 +228,16 @@ const zh = {
228
228
  'update.title': '更新',
229
229
  'update.banner': 'herdr-remote {latest} 已发布(当前 {current}),按 7 进入「语言与关于」,在更新一行按 Enter 安装。',
230
230
  'update.check': '检查更新',
231
- 'update.checking': '查询 npm 中…',
231
+ 'update.checking': '查询 GitHub Releases 与 npm 中…',
232
232
  'update.upToDate': '已是最新({version})',
233
233
  'update.available': '新版本 {version} 可用(按 Enter 安装)',
234
234
  'update.updating': '安装 {version} 中…',
235
235
  'update.updatingRetry': '安装 {version} 中…(npm 尚未同步,第 {attempt} 次尝试)',
236
236
  'update.done': '已更新至 {version}',
237
237
  'update.restartHint': '更新已安装,重启 herdr-remote 生效。',
238
- 'update.errorNetwork': '所有 npm registry 均无法连接',
239
- 'update.errorNetworkDetail': '无 registry 应答:{message}',
240
- 'update.errorFailed': '更新失败,可手动执行:npm install -g herdr-remote@{version} --prefer-online',
238
+ 'update.errorNetwork': 'GitHub Releases 与所有 npm registry 均无法连接',
239
+ 'update.errorNetworkDetail': '均无应答:{message}',
240
+ 'update.errorFailed': '更新失败,可手动执行:npm install -g {url}',
241
241
  'update.errorFailedDetail': 'npm:{message}',
242
242
  'update.errorNotYetPublished': 'npm 还没同步到 {version}(刚发布的几分钟内常见),稍后按 Enter 重试。',
243
243
  'update.errorNotApplied': 'npm 报告成功,但安装的仍是 {installed}',
@@ -252,18 +252,5 @@ const zh = {
252
252
  'update.brokerRestartFailed': '管理员终端 broker 没有重新启动。重新登录 Windows,或运行 herdr-remote admin-broker restart。',
253
253
  'update.brokerBusy': '有管理员终端开着,broker 继续运行旧版本。关闭所有管理员终端后运行 herdr-remote admin-broker restart。',
254
254
  'update.errorBusy': "仍有进程占用 herdr-remote 的安装目录,旧版本设置的管理员终端 broker 会这样。先在管理员 PowerShell 中执行:Get-ScheduledTask -TaskName 'HerdrRemoteAdminBroker-*' | Stop-ScheduledTask,再回到这里重新运行 herdr-remote update,最后以管理员身份运行 herdr-remote admin-broker install。",
255
- 'relayUpdate.check': 'Relay {version}:检查更新',
256
- 'relayUpdate.checking': 'Relay:查询 npm 中…',
257
- 'relayUpdate.upToDate': 'Relay 已是最新({version})',
258
- 'relayUpdate.available': 'Relay 新版本 {latest} 可用,当前 {installed}(按 Enter 安装)',
259
- 'relayUpdate.needsCli': 'Relay {latest} 需要更新 herdr-remote,请先更新上一行',
260
- 'relayUpdate.updating': '安装 relay {version} 中…',
261
- 'relayUpdate.updatingRetry': '安装 relay {version} 中…(npm 尚未同步,第 {attempt} 次尝试)',
262
- 'relayUpdate.restart': 'Relay {version} 已安装,运行中的仍是 {running}(按 Enter 重启生效)',
263
- 'relayUpdate.restarting': '正在重启 herdr-remote…',
264
- 'relayUpdate.restarted': 'herdr-remote 已重启,relay 现在运行 {version}。',
265
- 'relayUpdate.done': 'Relay 已更新至 {version}',
266
- 'relayUpdate.errorNotApplied': 'npm 已完成,但安装的仍是 relay {installed}',
267
- 'relayUpdate.banner': 'Relay {latest} 已发布(当前 {current}),按 7 进入「语言与关于」,在 Relay 一行按 Enter 安装。',
268
255
  };
269
256
  export default zh;
@@ -1,5 +1,28 @@
1
1
  import os from 'node:os';
2
2
  import { nodePty } from './node-pty-loader.js';
3
+ /**
4
+ * Variables that name the terminal this process was started from. The Herdr
5
+ * client's outer terminal is the browser's xterm.js, and from Herdr 0.9.2 the
6
+ * client reads these to pick image transports: one that believes it runs in
7
+ * Ghostty, kitty or WezTerm hands images over as local file paths no browser
8
+ * can open. Herdr strips the same list from its own panes.
9
+ */
10
+ const OUTER_TERMINAL_ENV = [
11
+ 'TERM_PROGRAM',
12
+ 'TERM_PROGRAM_VERSION',
13
+ 'ITERM_SESSION_ID',
14
+ 'LC_TERMINAL',
15
+ 'LC_TERMINAL_VERSION',
16
+ 'WEZTERM_PANE',
17
+ 'KITTY_WINDOW_ID',
18
+ 'WT_SESSION',
19
+ 'TMUX',
20
+ 'TMUX_PANE',
21
+ 'STY',
22
+ 'ZELLIJ',
23
+ 'ZELLIJ_SESSION_NAME',
24
+ 'ZELLIJ_PANE_ID',
25
+ ];
3
26
  class PtySession {
4
27
  static DEFAULT_COLS = 100;
5
28
  static DEFAULT_ROWS = 30;
@@ -29,12 +52,14 @@ class PtySession {
29
52
  this.terminal = null;
30
53
  this.startedAt = null;
31
54
  }
32
- static childEnv(socketPath) {
33
- const env = { ...process.env };
55
+ static childEnv(socketPath, source = process.env) {
56
+ const env = { ...source };
34
57
  for (const key of Object.keys(env)) {
35
58
  if (key.startsWith('HERDR_'))
36
59
  delete env[key];
37
60
  }
61
+ for (const key of OUTER_TERMINAL_ENV)
62
+ delete env[key];
38
63
  if (socketPath)
39
64
  env.HERDR_SOCKET_PATH = socketPath;
40
65
  env.TERM = 'xterm-256color';
@@ -1,18 +1,28 @@
1
1
  import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
- function defaultSocketPath(env = process.env, platform = process.platform) {
5
- if (env.HERDR_SOCKET_PATH)
6
- return env.HERDR_SOCKET_PATH;
4
+ /**
5
+ * Herdr's own config directory, resolved as Herdr does (`XDG_CONFIG_HOME`
6
+ * first on every platform, then the platform's home for it), so the socket
7
+ * and `config.toml` are found wherever Herdr put them.
8
+ */
9
+ function herdrConfigDir(env = process.env, platform = process.platform) {
7
10
  if (platform === 'win32') {
8
11
  const configHome = env.XDG_CONFIG_HOME ||
9
12
  env.APPDATA ||
10
13
  (env.USERPROFILE && path.join(env.USERPROFILE, 'AppData', 'Roaming')) ||
11
14
  path.join(os.homedir(), 'AppData', 'Roaming');
12
- return path.join(configHome, 'herdr', 'herdr.sock');
15
+ return path.join(configHome, 'herdr');
13
16
  }
14
17
  const configHome = env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
15
- return path.join(configHome, 'herdr', 'herdr.sock');
18
+ return path.join(configHome, 'herdr');
19
+ }
20
+ function defaultSocketPath(env = process.env, platform = process.platform) {
21
+ return env.HERDR_SOCKET_PATH || path.join(herdrConfigDir(env, platform), 'herdr.sock');
22
+ }
23
+ /** The `config.toml` Herdr reads; `HERDR_CONFIG_PATH` names it outright. */
24
+ function herdrConfigPath(env = process.env, platform = process.platform) {
25
+ return env.HERDR_CONFIG_PATH || path.join(herdrConfigDir(env, platform), 'config.toml');
16
26
  }
17
27
  /**
18
28
  * On Windows, Herdr uses this path itself as the GenericNamespaced pipe name;
@@ -57,4 +67,4 @@ function inspectSocket(socketPath, options = {}) {
57
67
  };
58
68
  }
59
69
  }
60
- export { defaultSocketPath, herdrEndpoint, resolveSocketPath, inspectSocket };
70
+ export { defaultSocketPath, herdrConfigPath, herdrEndpoint, resolveSocketPath, inspectSocket };
@@ -193,10 +193,28 @@ export function parseJsonc(text) {
193
193
  return null;
194
194
  }
195
195
  }
196
+ /** `line` up to a `#` that starts a comment, not one inside a string. */
197
+ function withoutTomlComment(line) {
198
+ let quote = '';
199
+ for (let i = 0; i < line.length; i += 1) {
200
+ const char = line[i];
201
+ if (quote) {
202
+ if (char === '\\' && quote === '"')
203
+ i += 1;
204
+ else if (char === quote)
205
+ quote = '';
206
+ }
207
+ else if (char === '"' || char === "'")
208
+ quote = char;
209
+ else if (char === '#')
210
+ return line.slice(0, i);
211
+ }
212
+ return line;
213
+ }
196
214
  /**
197
- * Just enough TOML for Alacritty's font keys: tables, dotted keys, strings,
198
- * numbers, inline tables and (possibly multi-line) string arrays. Returns a
199
- * flat `dotted.key → value` map.
215
+ * Just enough TOML for Alacritty's font keys and Herdr's `keys.prefix`:
216
+ * tables, dotted keys, strings, numbers, inline tables and (possibly
217
+ * multi-line) string arrays. Returns a flat `dotted.key → value` map.
200
218
  */
201
219
  export function parseTomlSubset(text) {
202
220
  const values = {};
@@ -253,11 +271,13 @@ export function parseTomlSubset(text) {
253
271
  if (equals === -1)
254
272
  continue;
255
273
  let rawValue = line.slice(equals + 1);
256
- // An array may span lines; gather it before assigning.
257
- if (rawValue.trim().startsWith('[') && !rawValue.includes(']')) {
274
+ if (rawValue.trim().startsWith('[')) {
275
+ // A comment inside an array may hold quotes or a `]` of its own.
276
+ rawValue = withoutTomlComment(rawValue);
277
+ // An array may span lines; gather it before assigning.
258
278
  while (index + 1 < lines.length && !rawValue.includes(']')) {
259
279
  index += 1;
260
- rawValue += ` ${lines[index].trim()}`;
280
+ rawValue += ` ${withoutTomlComment(lines[index]).trim()}`;
261
281
  }
262
282
  }
263
283
  assign(table, line.slice(0, equals), rawValue);
@@ -19,6 +19,15 @@ const TERMINAL_PROCESSES = [
19
19
  [/^xterm$/, 'xterm'],
20
20
  [/^(urxvt|urxvtd|rxvt)$/, 'urxvt'],
21
21
  ];
22
+ /** macOS app bundle ids, which every process an app starts inherits. */
23
+ const BUNDLE_TERMINALS = {
24
+ 'com.apple.Terminal': 'apple-terminal',
25
+ 'com.googlecode.iterm2': 'iterm2',
26
+ 'com.microsoft.VSCode': 'vscode',
27
+ 'com.mitchellh.ghostty': 'ghostty',
28
+ 'com.github.wez.wezterm': 'wezterm',
29
+ 'net.kovidgoyal.kitty': 'kitty',
30
+ };
22
31
  /**
23
32
  * The emulator a process runs under, from its own ancestry first.
24
33
  *
@@ -37,10 +46,15 @@ export function identifyTerminal({ env = process.env, ancestry = [], } = {}) {
37
46
  return id;
38
47
  }
39
48
  }
49
+ // From Herdr 0.9.2 a pane says TERM_PROGRAM=herdr and loses the terminal's
50
+ // session markers, so a start from inside Herdr falls through to markers
51
+ // Herdr leaves alone: macOS's app bundle id, ITERM_PROFILE, KITTY_PID,
52
+ // WT_PROFILE_ID.
40
53
  const program = env.TERM_PROGRAM || '';
54
+ const bundle = env.__CFBundleIdentifier || '';
41
55
  if (program === 'vscode')
42
56
  return 'vscode';
43
- if (program === 'iTerm.app' || env.ITERM_SESSION_ID)
57
+ if (program === 'iTerm.app' || env.ITERM_SESSION_ID || env.ITERM_PROFILE)
44
58
  return 'iterm2';
45
59
  if (program === 'Apple_Terminal')
46
60
  return 'apple-terminal';
@@ -48,7 +62,7 @@ export function identifyTerminal({ env = process.env, ancestry = [], } = {}) {
48
62
  return 'wezterm';
49
63
  if (program === 'ghostty' || env.GHOSTTY_RESOURCES_DIR)
50
64
  return 'ghostty';
51
- if (env.KITTY_WINDOW_ID || env.TERM === 'xterm-kitty')
65
+ if (env.KITTY_WINDOW_ID || env.KITTY_PID || env.TERM === 'xterm-kitty')
52
66
  return 'kitty';
53
67
  if (env.ALACRITTY_SOCKET || env.ALACRITTY_WINDOW_ID || env.ALACRITTY_LOG)
54
68
  return 'alacritty';
@@ -64,8 +78,10 @@ export function identifyTerminal({ env = process.env, ancestry = [], } = {}) {
64
78
  return 'gnome-terminal';
65
79
  if (/^rxvt-unicode/.test(env.TERM || ''))
66
80
  return 'urxvt';
81
+ if (Object.hasOwn(BUNDLE_TERMINALS, bundle))
82
+ return BUNDLE_TERMINALS[bundle];
67
83
  // WT_SESSION is inherited by nested terminals, so specific terminal markers win first.
68
- if (env.WT_SESSION)
84
+ if (env.WT_SESSION || env.WT_PROFILE_ID)
69
85
  return 'windows-terminal';
70
86
  // Last: a terminal started from xterm inherits this too.
71
87
  if (env.XTERM_VERSION)
package/dist/tui/App.js CHANGED
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Box, Text, useApp, useInput } from 'ink';
4
4
  import { useMouse, useMouseTarget } from './mouse/index.js';
5
5
  import { theme } from './theme.js';
6
- import { checkForRelayUpdate, checkForUpdate, createDraft, createTranslator, detectLocale, ensureRuntime, fullStatus, loadConfig, requiresRestart, runsLocalRelay, updateChecksEnabled, } from './api.js';
6
+ import { checkForUpdate, createDraft, createTranslator, detectLocale, ensureRuntime, fullStatus, loadConfig, requiresRestart, updateChecksEnabled, } from './api.js';
7
7
  import { Overview } from './screens/Overview.js';
8
8
  import { PairScreen } from './screens/Pair.js';
9
9
  import { Services } from './screens/Services.js';
@@ -30,7 +30,7 @@ function Tab({ label, active, onSelect, }) {
30
30
  function Footer({ hints }) {
31
31
  return (_jsx(Box, { marginTop: 1, paddingX: 1, children: _jsx(Text, { color: theme.muted, children: hints.join(' · ') }) }));
32
32
  }
33
- export function App({ initialLanguage, needsWizard, updateChecker = updateChecksEnabled() ? checkForUpdate : null, relayUpdateChecker = updateChecksEnabled() ? checkForRelayUpdate : null, }) {
33
+ export function App({ initialLanguage, needsWizard, updateChecker = updateChecksEnabled() ? checkForUpdate : null, }) {
34
34
  const { exit } = useApp();
35
35
  const mouse = useMouse();
36
36
  const [config, setConfig] = useState(() => loadConfig());
@@ -43,8 +43,6 @@ export function App({ initialLanguage, needsWizard, updateChecker = updateChecks
43
43
  const [editingId, setEditingId] = useState(null);
44
44
  const [wizardDone, setWizardDone] = useState(!needsWizard);
45
45
  const [updateCheck, setUpdateCheck] = useState(null);
46
- const [relayUpdateCheck, setRelayUpdateCheck] = useState(null);
47
- const localRelay = runsLocalRelay(config);
48
46
  // Every time the TUI opens: that is when someone is here to act on it.
49
47
  // Failure says nothing; the About tab can still be asked by hand.
50
48
  useEffect(() => {
@@ -61,21 +59,6 @@ export function App({ initialLanguage, needsWizard, updateChecker = updateChecks
61
59
  cancelled = true;
62
60
  };
63
61
  }, [updateChecker]);
64
- // A relay somewhere else is somebody else's to update.
65
- useEffect(() => {
66
- if (!relayUpdateChecker || !localRelay)
67
- return undefined;
68
- let cancelled = false;
69
- relayUpdateChecker()
70
- .then((result) => {
71
- if (!cancelled && result?.ok)
72
- setRelayUpdateCheck(result);
73
- })
74
- .catch(() => { });
75
- return () => {
76
- cancelled = true;
77
- };
78
- }, [relayUpdateChecker, localRelay]);
79
62
  const locale = useMemo(() => detectLocale({ preference: initialLanguage ?? draft.ui.language }), [initialLanguage, draft.ui.language]);
80
63
  const t = useMemo(() => createTranslator(locale), [locale]);
81
64
  const dirty = useMemo(() => JSON.stringify(config) !== JSON.stringify(draft), [config, draft]);
@@ -153,8 +136,6 @@ export function App({ initialLanguage, needsWizard, updateChecker = updateChecks
153
136
  message,
154
137
  updateCheck,
155
138
  setUpdateCheck,
156
- relayUpdateCheck,
157
- setRelayUpdateCheck,
158
139
  };
159
140
  useInput((input, key) => {
160
141
  if (key.ctrl && input === 'c') {
@@ -214,8 +195,5 @@ export function App({ initialLanguage, needsWizard, updateChecker = updateChecks
214
195
  : t('hint.mouseUnsupported'),
215
196
  t('hint.quit'),
216
197
  ].filter(Boolean);
217
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: t('app.name') }), _jsx(Text, { color: theme.muted, children: ` ${t('app.tagline')}` })] }), updateCheck?.updateAvailable ? (_jsx(Text, { color: theme.warn, children: t('update.banner', { latest: updateCheck.latest ?? '', current: updateCheck.current }) })) : null, localRelay && relayUpdateCheck?.updateAvailable ? (_jsx(Text, { color: theme.warn, children: t('relayUpdate.banner', {
218
- latest: relayUpdateCheck.latest ?? '',
219
- current: relayUpdateCheck.current,
220
- }) })) : null] }), _jsx(Box, { marginBottom: 1, children: TABS.map((entry, entryIndex) => (_jsx(Tab, { label: `${entryIndex + 1} ${t(entry.labelKey)}`, active: entry.id === tab, onSelect: () => setTab(entry.id) }, entry.id))) }), tab === 'overview' ? _jsx(Overview, { ctx: context }) : null, tab === 'pair' ? _jsx(PairScreen, { ctx: context }) : null, tab === 'services' ? _jsx(Services, { ctx: context }) : null, tab === 'relay' ? _jsx(RelayScreen, { ctx: context }) : null, tab === 'keepalive' ? _jsx(Keepalive, { ctx: context }) : null, tab === 'herdr' ? _jsx(HerdrScreen, { ctx: context }) : null, tab === 'about' ? _jsx(About, { ctx: context }) : null, restartPending ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.warn, children: t('hint.restartRequired') }) })) : null, _jsx(Footer, { hints: hints })] }));
198
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: t('app.name') }), _jsx(Text, { color: theme.muted, children: ` ${t('app.tagline')}` })] }), updateCheck?.updateAvailable ? (_jsx(Text, { color: theme.warn, children: t('update.banner', { latest: updateCheck.latest ?? '', current: updateCheck.current }) })) : null] }), _jsx(Box, { marginBottom: 1, children: TABS.map((entry, entryIndex) => (_jsx(Tab, { label: `${entryIndex + 1} ${t(entry.labelKey)}`, active: entry.id === tab, onSelect: () => setTab(entry.id) }, entry.id))) }), tab === 'overview' ? _jsx(Overview, { ctx: context }) : null, tab === 'pair' ? _jsx(PairScreen, { ctx: context }) : null, tab === 'services' ? _jsx(Services, { ctx: context }) : null, tab === 'relay' ? _jsx(RelayScreen, { ctx: context }) : null, tab === 'keepalive' ? _jsx(Keepalive, { ctx: context }) : null, tab === 'herdr' ? _jsx(HerdrScreen, { ctx: context }) : null, tab === 'about' ? _jsx(About, { ctx: context }) : null, restartPending ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.warn, children: t('hint.restartRequired') }) })) : null, _jsx(Footer, { hints: hints })] }));
221
199
  }
package/dist/tui/api.js CHANGED
@@ -16,9 +16,9 @@ import { ensureRuntime, regenerateHostIdentity, setRelayPassword } from '../runt
16
16
  import { pair, readLogTail } from '../service.js';
17
17
  import { fullStatus, restartAll, startAll, stopAll, whileServicesStopped } from '../lifecycle.js';
18
18
  import * as keepalive from '../keepalive/index.js';
19
- import { canSelfUpdate, checkForUpdate, compareVersions, currentVersion, updateChecksEnabled, installKind, performUpdate, } from '../updater.js';
20
- import { checkForRelayUpdate, installedRelayVersion, performRelayUpdate, } from '../relay-updater.js';
21
- export { detectLocale, listReachableAddresses, MIN_HERDR_VERSION, OFFICIAL_RELAY_URL, isOfficialRelay, selectedMode, canSelfUpdate, checkForUpdate, compareVersions, currentVersion, updateChecksEnabled, installKind, performUpdate, checkForRelayUpdate, installedRelayVersion, performRelayUpdate, runsLocalRelay, bindAddress, configExists, configPath, createDraft, createTranslator, ensureRuntime, extractPairingCode, fieldsForMode, fullStatus, getField, getFieldPlaceholder, herdrVersion, keepalive, loadConfig, pair, readLogTail, regenerateHostIdentity, requiresRestart, restartAll, saveDraft, setField, setRelayPassword, startAll, stateDir, stopAll, whileServicesStopped, };
19
+ import { canSelfUpdate, checkForUpdate, currentVersion, updateChecksEnabled, installKind, installedRelayVersion, performUpdate, } from '../updater.js';
20
+ export { cliTarballUrl } from 'herdr-remote-relay/protocol';
21
+ export { detectLocale, listReachableAddresses, MIN_HERDR_VERSION, OFFICIAL_RELAY_URL, isOfficialRelay, selectedMode, canSelfUpdate, checkForUpdate, currentVersion, updateChecksEnabled, installKind, performUpdate, installedRelayVersion, runsLocalRelay, bindAddress, configExists, configPath, createDraft, createTranslator, ensureRuntime, extractPairingCode, fieldsForMode, fullStatus, getField, getFieldPlaceholder, herdrVersion, keepalive, loadConfig, pair, readLogTail, regenerateHostIdentity, requiresRestart, restartAll, saveDraft, setField, setRelayPassword, startAll, stateDir, stopAll, whileServicesStopped, };
22
22
  export async function adminBrokerStatus({ waitForStart = false } = {}) {
23
23
  if (process.platform !== 'win32')
24
24
  return false;