herdr-remote 0.2.39 → 0.3.32

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 (205) hide show
  1. package/README.md +4 -2
  2. package/README.zh-CN.md +2 -2
  3. package/dist/chunks/3FQAXVGC.js +3 -0
  4. package/dist/chunks/42KARAL4.js +31 -0
  5. package/dist/chunks/44IGVPMA.js +5 -0
  6. package/dist/chunks/5LZFJ6UZ.js +3 -0
  7. package/dist/chunks/7SMZ43LI.js +9 -0
  8. package/dist/chunks/AX4EPJCP.js +4 -0
  9. package/dist/chunks/B76DFI4H.js +16 -0
  10. package/dist/chunks/BYGDPZZD.js +3 -0
  11. package/dist/chunks/C2UUZY6J.js +2 -0
  12. package/dist/chunks/CVJ4HBMP.js +3 -0
  13. package/dist/chunks/DGUZCGQN.js +3 -0
  14. package/dist/chunks/FONUO5O5.js +4 -0
  15. package/dist/chunks/H24ZP5D5.js +16 -0
  16. package/dist/chunks/I6NRCYLI.js +3 -0
  17. package/dist/chunks/JJGEV4EZ.js +3 -0
  18. package/dist/chunks/K5RFPHSF.js +5 -0
  19. package/dist/chunks/KGOQKOHH.js +5 -0
  20. package/dist/chunks/KRVF37OW.js +3 -0
  21. package/dist/chunks/KUDCC7ZO.js +6 -0
  22. package/dist/chunks/LPR662JH.js +77 -0
  23. package/dist/chunks/LY2ZGCXN.js +4 -0
  24. package/dist/chunks/M52LJEES.js +6 -0
  25. package/dist/chunks/MCUVFSSK.js +3 -0
  26. package/dist/chunks/MEFDV375.js +10 -0
  27. package/dist/chunks/MKAEXKXG.js +10 -0
  28. package/dist/chunks/O2LNVFUT.js +3 -0
  29. package/dist/chunks/PBMBFWLQ.js +3 -0
  30. package/dist/chunks/PBRQ3HEG.js +3 -0
  31. package/dist/chunks/QCSILMFB.js +3 -0
  32. package/dist/chunks/QRGNEKME.js +5 -0
  33. package/dist/chunks/RMHH3IAX.js +63 -0
  34. package/dist/chunks/TNLCNVA2.js +3 -0
  35. package/dist/chunks/U6QAYG3E.js +3 -0
  36. package/dist/chunks/VD54DF7R.js +45 -0
  37. package/dist/chunks/WY7WHEJ4.js +3 -0
  38. package/dist/cli.js +21 -294
  39. package/dist/connector/admin-broker.js +3 -303
  40. package/dist/connector/main.js +20 -18
  41. package/dist/harfbuzz-subset.wasm +0 -0
  42. package/dist/postinstall.js +17 -0
  43. package/herdr-plugin.toml +1 -1
  44. package/node_modules/herdr-remote-relay/bin/herdr-remote-relay.js +33 -92
  45. package/node_modules/herdr-remote-relay/package.json +3 -46
  46. package/package.json +8 -27
  47. package/dist/agent-status.js +0 -130
  48. package/dist/binary-plist.js +0 -157
  49. package/dist/config.js +0 -278
  50. package/dist/connector/admin-broker-setup.js +0 -40
  51. package/dist/connector/admin-shell.js +0 -67
  52. package/dist/connector/admin-tab.js +0 -90
  53. package/dist/connector/adminBrokerEndpoint.js +0 -68
  54. package/dist/connector/agent-watch.js +0 -123
  55. package/dist/connector/elevatedPty.js +0 -141
  56. package/dist/connector/font-server.js +0 -165
  57. package/dist/connector/herdr-prefix-keys.js +0 -28
  58. package/dist/connector/host-connector.js +0 -433
  59. package/dist/connector/host-hello.js +0 -32
  60. package/dist/connector/lock.js +0 -73
  61. package/dist/connector/sessions.js +0 -381
  62. package/dist/connector/update-report.js +0 -70
  63. package/dist/connector/wire.js +0 -42
  64. package/dist/exit-codes.js +0 -13
  65. package/dist/font-subset.js +0 -116
  66. package/dist/herdr-api.js +0 -254
  67. package/dist/herdr-command.js +0 -245
  68. package/dist/herdr-plugin.js +0 -120
  69. package/dist/herdr-server.js +0 -190
  70. package/dist/i18n/en.js +0 -257
  71. package/dist/i18n/index.js +0 -48
  72. package/dist/i18n/zh.js +0 -256
  73. package/dist/keepalive/detached.js +0 -97
  74. package/dist/keepalive/environment.js +0 -44
  75. package/dist/keepalive/index.js +0 -129
  76. package/dist/keepalive/launchd.js +0 -117
  77. package/dist/keepalive/systemd.js +0 -112
  78. package/dist/keepalive/types.js +0 -1
  79. package/dist/keepalive/windows-service-setup.js +0 -138
  80. package/dist/keepalive/windows-service.js +0 -272
  81. package/dist/keepalive/windows-task.js +0 -191
  82. package/dist/lib/process.js +0 -132
  83. package/dist/lifecycle.js +0 -79
  84. package/dist/net-interfaces.js +0 -82
  85. package/dist/node-pty-loader.js +0 -80
  86. package/dist/pasted-files.js +0 -115
  87. package/dist/paths.js +0 -74
  88. package/dist/pty-session.js +0 -133
  89. package/dist/relay-client.js +0 -106
  90. package/dist/relay-urls.js +0 -99
  91. package/dist/runtime.js +0 -107
  92. package/dist/service.js +0 -267
  93. package/dist/settings-model.js +0 -298
  94. package/dist/socket-discovery.js +0 -70
  95. package/dist/supervisor.js +0 -323
  96. package/dist/terminal-font/deps.js +0 -83
  97. package/dist/terminal-font/files.js +0 -357
  98. package/dist/terminal-font/formats.js +0 -355
  99. package/dist/terminal-font/index.js +0 -187
  100. package/dist/terminal-font/readers.js +0 -373
  101. package/dist/terminal-font/terminals.js +0 -147
  102. package/dist/terminal-palette.js +0 -399
  103. package/dist/tui/App.js +0 -199
  104. package/dist/tui/api.js +0 -91
  105. package/dist/tui/components/TextField.js +0 -50
  106. package/dist/tui/components/common.js +0 -80
  107. package/dist/tui/index.js +0 -36
  108. package/dist/tui/mouse/index.js +0 -164
  109. package/dist/tui/mouse/protocol.js +0 -96
  110. package/dist/tui/mouse/source.js +0 -79
  111. package/dist/tui/screens/About.js +0 -172
  112. package/dist/tui/screens/Herdr.js +0 -75
  113. package/dist/tui/screens/Keepalive.js +0 -141
  114. package/dist/tui/screens/Overview.js +0 -36
  115. package/dist/tui/screens/Pair.js +0 -74
  116. package/dist/tui/screens/Relay.js +0 -243
  117. package/dist/tui/screens/Services.js +0 -63
  118. package/dist/tui/screens/Wizard.js +0 -241
  119. package/dist/tui/terminal.js +0 -39
  120. package/dist/tui/theme.js +0 -33
  121. package/dist/update-command.js +0 -118
  122. package/dist/updater/in-place.js +0 -83
  123. package/dist/updater/npm.js +0 -62
  124. package/dist/updater.js +0 -401
  125. package/node_modules/herdr-remote-relay/README.md +0 -76
  126. package/node_modules/herdr-remote-relay/README.zh-CN.md +0 -76
  127. package/node_modules/herdr-remote-relay/config.example.json +0 -18
  128. package/node_modules/herdr-remote-relay/deploy/cloudflared/config.yml.example +0 -9
  129. package/node_modules/herdr-remote-relay/deploy/docker/compose.yaml +0 -23
  130. package/node_modules/herdr-remote-relay/deploy/nginx/herdr-remote-relay.conf.example +0 -49
  131. package/node_modules/herdr-remote-relay/deploy/systemd/herdr-remote-relay.service +0 -41
  132. package/node_modules/herdr-remote-relay/deploy/systemd/relay.env.example +0 -25
  133. package/node_modules/herdr-remote-relay/dist/auth-store.d.ts +0 -114
  134. package/node_modules/herdr-remote-relay/dist/auth-store.js +0 -289
  135. package/node_modules/herdr-remote-relay/dist/metrics.d.ts +0 -76
  136. package/node_modules/herdr-remote-relay/dist/metrics.js +0 -249
  137. package/node_modules/herdr-remote-relay/dist/protocol/frames.d.ts +0 -27
  138. package/node_modules/herdr-remote-relay/dist/protocol/frames.js +0 -110
  139. package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.d.ts +0 -8
  140. package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.js +0 -34
  141. package/node_modules/herdr-remote-relay/dist/protocol/http.d.ts +0 -165
  142. package/node_modules/herdr-remote-relay/dist/protocol/http.js +0 -3
  143. package/node_modules/herdr-remote-relay/dist/protocol/index.d.ts +0 -7
  144. package/node_modules/herdr-remote-relay/dist/protocol/index.js +0 -9
  145. package/node_modules/herdr-remote-relay/dist/protocol/messages.d.ts +0 -362
  146. package/node_modules/herdr-remote-relay/dist/protocol/messages.js +0 -20
  147. package/node_modules/herdr-remote-relay/dist/protocol/paste.d.ts +0 -14
  148. package/node_modules/herdr-remote-relay/dist/protocol/paste.js +0 -50
  149. package/node_modules/herdr-remote-relay/dist/protocol/release.d.ts +0 -8
  150. package/node_modules/herdr-remote-relay/dist/protocol/release.js +0 -17
  151. package/node_modules/herdr-remote-relay/dist/protocol/terminal.d.ts +0 -79
  152. package/node_modules/herdr-remote-relay/dist/protocol/terminal.js +0 -150
  153. package/node_modules/herdr-remote-relay/dist/relay-config.d.ts +0 -74
  154. package/node_modules/herdr-remote-relay/dist/relay-config.js +0 -316
  155. package/node_modules/herdr-remote-relay/dist/relay-server.d.ts +0 -67
  156. package/node_modules/herdr-remote-relay/dist/relay-server.js +0 -196
  157. package/node_modules/herdr-remote-relay/dist/scroll-input.d.ts +0 -17
  158. package/node_modules/herdr-remote-relay/dist/scroll-input.js +0 -124
  159. package/node_modules/herdr-remote-relay/dist/server/client-channel.d.ts +0 -3
  160. package/node_modules/herdr-remote-relay/dist/server/client-channel.js +0 -396
  161. package/node_modules/herdr-remote-relay/dist/server/font-proxy.d.ts +0 -19
  162. package/node_modules/herdr-remote-relay/dist/server/font-proxy.js +0 -155
  163. package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.d.ts +0 -27
  164. package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.js +0 -83
  165. package/node_modules/herdr-remote-relay/dist/server/host-channel.d.ts +0 -3
  166. package/node_modules/herdr-remote-relay/dist/server/host-channel.js +0 -271
  167. package/node_modules/herdr-remote-relay/dist/server/http-api.d.ts +0 -4
  168. package/node_modules/herdr-remote-relay/dist/server/http-api.js +0 -219
  169. package/node_modules/herdr-remote-relay/dist/server/maintenance.d.ts +0 -5
  170. package/node_modules/herdr-remote-relay/dist/server/maintenance.js +0 -64
  171. package/node_modules/herdr-remote-relay/dist/server/requests.d.ts +0 -34
  172. package/node_modules/herdr-remote-relay/dist/server/requests.js +0 -142
  173. package/node_modules/herdr-remote-relay/dist/server/sessions.d.ts +0 -36
  174. package/node_modules/herdr-remote-relay/dist/server/sessions.js +0 -230
  175. package/node_modules/herdr-remote-relay/dist/server/sockets.d.ts +0 -12
  176. package/node_modules/herdr-remote-relay/dist/server/sockets.js +0 -64
  177. package/node_modules/herdr-remote-relay/dist/server/status.d.ts +0 -11
  178. package/node_modules/herdr-remote-relay/dist/server/status.js +0 -99
  179. package/node_modules/herdr-remote-relay/dist/server/transport.d.ts +0 -16
  180. package/node_modules/herdr-remote-relay/dist/server/transport.js +0 -110
  181. package/node_modules/herdr-remote-relay/dist/server/types.d.ts +0 -133
  182. package/node_modules/herdr-remote-relay/dist/server/types.js +0 -3
  183. package/node_modules/herdr-remote-relay/dist/server/version.d.ts +0 -2
  184. package/node_modules/herdr-remote-relay/dist/server/version.js +0 -5
  185. package/node_modules/herdr-remote-relay/dist/state.d.ts +0 -6
  186. package/node_modules/herdr-remote-relay/dist/state.js +0 -47
  187. package/node_modules/ws/LICENSE +0 -20
  188. package/node_modules/ws/README.md +0 -548
  189. package/node_modules/ws/browser.js +0 -8
  190. package/node_modules/ws/index.js +0 -22
  191. package/node_modules/ws/lib/buffer-util.js +0 -131
  192. package/node_modules/ws/lib/constants.js +0 -19
  193. package/node_modules/ws/lib/event-target.js +0 -292
  194. package/node_modules/ws/lib/extension.js +0 -203
  195. package/node_modules/ws/lib/limiter.js +0 -55
  196. package/node_modules/ws/lib/permessage-deflate.js +0 -530
  197. package/node_modules/ws/lib/receiver.js +0 -743
  198. package/node_modules/ws/lib/sender.js +0 -607
  199. package/node_modules/ws/lib/stream.js +0 -161
  200. package/node_modules/ws/lib/subprotocol.js +0 -62
  201. package/node_modules/ws/lib/validation.js +0 -152
  202. package/node_modules/ws/lib/websocket-server.js +0 -562
  203. package/node_modules/ws/lib/websocket.js +0 -1407
  204. package/node_modules/ws/package.json +0 -74
  205. package/node_modules/ws/wrapper.mjs +0 -21
package/dist/herdr-api.js DELETED
@@ -1,254 +0,0 @@
1
- /**
2
- * One question for Herdr's local socket API.
3
- *
4
- * Everything else in this package talks to Herdr by spawning a client into a
5
- * PTY and shipping the bytes. That is the terminal. This is the side channel:
6
- * the one place that asks Herdr *about* itself rather than drawing it, so a
7
- * phone can answer "does anything need me?" without navigating a TUI through a
8
- * 40-column viewport.
9
- *
10
- * The wire format is newline-delimited JSON over the Unix socket or, on
11
- * Windows, the named pipe it names:
12
- *
13
- * request {"id":"…","method":"session.snapshot","params":{}}
14
- * response {"id":"…","result":{…}} or {"id":"…","error":{"code","message"}}
15
- *
16
- * One request per connection, which is why this is a function and not a client.
17
- * Herdr closes the socket once it has answered: against 0.9.1 a second request
18
- * written to the same connection meets `EPIPE`. A long-lived client that
19
- * reconnects on close therefore turns into a connect-answer-close loop about
20
- * once a second, busy regardless of how rarely it actually asks anything. The
21
- * `herdr` CLI connects per command for the same reason, and so does this.
22
- *
23
- * Subscriptions are the exception Herdr does keep open — `events.subscribe`
24
- * holds the connection. Focus and agent-detection events let the caller refresh
25
- * the focused pane immediately, with a slow poll retained as a fallback.
26
- *
27
- * Nothing here may affect a terminal session. Herdr's server is an ordinary
28
- * process that can be restarted under a running herdr-remote, and when it is,
29
- * the PTYs keep streaming while this quietly fails and is asked again later.
30
- */
31
- import net from 'node:net';
32
- import { herdrEndpoint } from './socket-discovery.js';
33
- /** How long one question waits, connection included. */
34
- const REQUEST_TIMEOUT_MS = 10_000;
35
- /** An answer this long is not an answer. Guards a socket that never newlines. */
36
- const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
37
- /** A subscription that disappears should recover without keeping a dead socket busy. */
38
- const SUBSCRIBE_RETRY_BASE_MS = 1_000;
39
- const SUBSCRIBE_RETRY_MAX_MS = 10_000;
40
- let requestCounter = 0;
41
- /**
42
- * Ask Herdr one thing.
43
- *
44
- * Resolves with the `result` object, rejects for a refused connection, a
45
- * timeout, an unparseable answer, or an `error` response — in which case
46
- * `error.code` is Herdr's own code.
47
- */
48
- function requestHerdr(socketPath, method, params = {}, options = {}) {
49
- const { timeout = REQUEST_TIMEOUT_MS, connect = net.connect } = options;
50
- requestCounter += 1;
51
- const id = `herdr-remote:${requestCounter}`;
52
- return new Promise((resolve, reject) => {
53
- let socket;
54
- let buffer = '';
55
- let settled = false;
56
- const finish = (error, result) => {
57
- if (settled)
58
- return;
59
- settled = true;
60
- clearTimeout(timer);
61
- if (socket) {
62
- socket.removeAllListeners();
63
- socket.destroy();
64
- }
65
- if (error)
66
- reject(error);
67
- else
68
- resolve(result);
69
- };
70
- const timer = setTimeout(() => finish(new Error(`Herdr API did not answer ${method} in time`)), timeout);
71
- if (typeof timer.unref === 'function')
72
- timer.unref();
73
- try {
74
- socket = connect(herdrEndpoint(socketPath));
75
- }
76
- catch (error) {
77
- finish(error);
78
- return;
79
- }
80
- const connection = socket;
81
- connection.setEncoding('utf8');
82
- connection.on('connect', () => {
83
- connection.write(`${JSON.stringify({ id, method, params })}\n`);
84
- });
85
- connection.on('data', (chunk) => {
86
- buffer += chunk;
87
- if (buffer.length > MAX_RESPONSE_BYTES) {
88
- finish(new Error('Herdr API sent an oversized response'));
89
- return;
90
- }
91
- const newline = buffer.indexOf('\n');
92
- if (newline === -1)
93
- return;
94
- let message;
95
- try {
96
- message = JSON.parse(buffer.slice(0, newline));
97
- }
98
- catch {
99
- finish(new Error('Herdr API sent a line that is not JSON'));
100
- return;
101
- }
102
- if (message.error) {
103
- const error = Object.assign(new Error(message.error.message || 'Herdr API request failed'), {
104
- code: message.error.code || 'herdr_api_error',
105
- });
106
- finish(error);
107
- return;
108
- }
109
- finish(null, message.result);
110
- });
111
- connection.on('error', (error) => finish(error));
112
- // Herdr hanging up before it answered is the ordinary shape of "the server
113
- // is not running", not something that needs its own diagnosis.
114
- connection.on('close', () => finish(new Error(`Herdr API closed before answering ${method}`)));
115
- });
116
- }
117
- /**
118
- * Subscribe to Herdr's event stream and reconnect if its local server restarts.
119
- *
120
- * The initial response is an acknowledgement (`result.type` is
121
- * `subscription_started`); later lines are envelopes with `event` and `data`.
122
- * They share a socket, but are separate NDJSON messages. A subscription never
123
- * starts or writes to a terminal, so an unavailable socket is only a reason to
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.
130
- */
131
- function subscribeHerdr(socketPath, subscriptions, onEvent, options = {}) {
132
- const { connect = net.connect, retryBaseMs = SUBSCRIBE_RETRY_BASE_MS, retryMaxMs = SUBSCRIBE_RETRY_MAX_MS, onStarted, } = options;
133
- let socket = null;
134
- let reconnectTimer = null;
135
- let handshakeTimer = null;
136
- let retryCount = 0;
137
- let closed = false;
138
- const scheduleReconnect = () => {
139
- if (closed || reconnectTimer)
140
- return;
141
- const delay = Math.min(retryBaseMs * 2 ** retryCount, retryMaxMs);
142
- retryCount += 1;
143
- reconnectTimer = setTimeout(() => {
144
- reconnectTimer = null;
145
- open();
146
- }, delay);
147
- if (typeof reconnectTimer.unref === 'function')
148
- reconnectTimer.unref();
149
- };
150
- const open = () => {
151
- if (closed)
152
- return;
153
- let current;
154
- try {
155
- current = connect(herdrEndpoint(socketPath));
156
- }
157
- catch {
158
- scheduleReconnect();
159
- return;
160
- }
161
- socket = current;
162
- let buffer = '';
163
- let requestId = null;
164
- let acknowledged = false;
165
- const clearHandshake = () => {
166
- if (handshakeTimer)
167
- clearTimeout(handshakeTimer);
168
- handshakeTimer = null;
169
- };
170
- current.setEncoding('utf8');
171
- current.on('connect', () => {
172
- requestCounter += 1;
173
- requestId = `herdr-remote:subscription:${requestCounter}`;
174
- current.write(`${JSON.stringify({
175
- id: requestId,
176
- method: 'events.subscribe',
177
- params: { subscriptions },
178
- })}\n`);
179
- handshakeTimer = setTimeout(() => current.destroy(), REQUEST_TIMEOUT_MS);
180
- if (typeof handshakeTimer.unref === 'function')
181
- handshakeTimer.unref();
182
- });
183
- current.on('data', (chunk) => {
184
- buffer += chunk;
185
- if (Buffer.byteLength(buffer, 'utf8') > MAX_RESPONSE_BYTES) {
186
- current.destroy();
187
- return;
188
- }
189
- let newline = buffer.indexOf('\n');
190
- for (; newline !== -1; newline = buffer.indexOf('\n')) {
191
- const line = buffer.slice(0, newline);
192
- buffer = buffer.slice(newline + 1);
193
- if (!line)
194
- continue;
195
- let message;
196
- try {
197
- message = JSON.parse(line);
198
- }
199
- catch {
200
- current.destroy();
201
- return;
202
- }
203
- if (message?.id === requestId) {
204
- clearHandshake();
205
- if (message.error) {
206
- current.destroy();
207
- return;
208
- }
209
- acknowledged = true;
210
- retryCount = 0;
211
- onStarted?.();
212
- }
213
- else if (acknowledged &&
214
- typeof message?.event === 'string' &&
215
- message.data &&
216
- typeof message.data === 'object') {
217
- try {
218
- onEvent(message);
219
- }
220
- catch (error) {
221
- // A handler's bug must not end the subscription, but must not vanish either.
222
- process.stderr.write(`herdr-remote: Herdr event handler failed: ${error instanceof Error ? error.message : error}\n`);
223
- }
224
- }
225
- }
226
- });
227
- current.on('error', () => { });
228
- current.on('close', () => {
229
- clearHandshake();
230
- if (socket === current)
231
- socket = null;
232
- scheduleReconnect();
233
- });
234
- };
235
- open();
236
- return {
237
- close() {
238
- if (closed)
239
- return;
240
- closed = true;
241
- if (reconnectTimer)
242
- clearTimeout(reconnectTimer);
243
- if (handshakeTimer)
244
- clearTimeout(handshakeTimer);
245
- reconnectTimer = null;
246
- handshakeTimer = null;
247
- const active = socket;
248
- socket = null;
249
- if (active)
250
- active.destroy();
251
- },
252
- };
253
- }
254
- export { requestHerdr, subscribeHerdr };
@@ -1,245 +0,0 @@
1
- // How a spawned Herdr is located.
2
- //
3
- // `HERDR_BIN_PATH` is what the service layer hands to the host connector, and
4
- // it is also the escape hatch for an install that is not on `PATH`. Neither is
5
- // enough on its own once a service manager is in the picture: `systemd --user`
6
- // and `launchd` start their units with a minimal `PATH` that contains none of
7
- // the directories a user-level install writes to. A bare `herdr` then reaches
8
- // `execvp(3)` inside the forked PTY child, which prints
9
- // "execvp(3) failed.: No such file or directory" and exits — the failure the
10
- // browser sees as an endless reconnect loop.
11
- //
12
- // So the name is resolved to an absolute path here, ahead of any spawn:
13
- // an honoured override first, then `PATH`, then the handful of directories
14
- // installers actually use.
15
- import fs from 'node:fs';
16
- import os from 'node:os';
17
- import path from 'node:path';
18
- import { spawnSync } from 'node:child_process';
19
- // The same MAJOR.MINOR.PATCH comparison the self-updater uses. A second copy
20
- // would be a second place for 0.9.10 to sort below 0.9.9.
21
- import { compareVersions } from './updater.js';
22
- const COMMAND_NAME = 'herdr';
23
- /**
24
- * The oldest Herdr this release is written against.
25
- *
26
- * 0.9.0 gave every client its own view, which is what one PTY per browser
27
- * window rests on. 0.9.1 is what makes that model behave: window titles follow
28
- * each client's own view instead of another client's selection, activating a
29
- * machine in the background stops resizing somebody else's focused pane, and a
30
- * large paste no longer disconnects the client — and pasting is how an image
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.
37
- */
38
- const MIN_HERDR_VERSION = '0.9.3';
39
- /** Where a user-level install lands, in the order we trust it. */
40
- const FALLBACK_DIRECTORIES = [
41
- '~/.local/bin',
42
- '~/.cargo/bin',
43
- '~/bin',
44
- '/opt/homebrew/bin',
45
- '/usr/local/bin',
46
- '/home/linuxbrew/.linuxbrew/bin',
47
- '/usr/bin',
48
- ];
49
- function expandHome(directory, home) {
50
- if (directory === '~')
51
- return home;
52
- if (directory.startsWith('~/'))
53
- return path.join(home, directory.slice(2));
54
- return directory;
55
- }
56
- /** Windows only publishes Herdr as an executable; shell scripts need a shell. */
57
- function candidateNames(base = COMMAND_NAME, platform = process.platform) {
58
- if (platform !== 'win32')
59
- return [base];
60
- return [/\.exe$/i.test(base) ? base : `${base}.exe`];
61
- }
62
- function isExecutableFile(candidate) {
63
- try {
64
- if (!fs.statSync(candidate).isFile())
65
- return false;
66
- fs.accessSync(candidate, fs.constants.X_OK);
67
- return true;
68
- }
69
- catch {
70
- return false;
71
- }
72
- }
73
- function findInDirectory(directory, names = candidateNames()) {
74
- if (!directory)
75
- return null;
76
- for (const name of names) {
77
- const candidate = path.join(directory, name);
78
- if (isExecutableFile(candidate))
79
- return candidate;
80
- }
81
- return null;
82
- }
83
- function findOnSearchPath(searchPath, names = candidateNames()) {
84
- for (const entry of String(searchPath || '').split(path.delimiter)) {
85
- const found = findInDirectory(entry.trim(), names);
86
- if (found)
87
- return found;
88
- }
89
- return null;
90
- }
91
- function fallbackDirectories(home = os.homedir(), platform = process.platform, env = process.env) {
92
- if (platform === 'win32') {
93
- return [
94
- path.join(env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'Programs', 'Herdr', 'bin'),
95
- path.join(home, '.cargo', 'bin'),
96
- path.join(home, 'scoop', 'shims'),
97
- ];
98
- }
99
- return FALLBACK_DIRECTORIES.map((directory) => expandHome(directory, home));
100
- }
101
- function looksLikePath(value) {
102
- return value.includes('/') || value.includes(path.sep);
103
- }
104
- /**
105
- * Honour `HERDR_BIN_PATH`.
106
- *
107
- * It is documented as the executable, but a directory is an easy thing to
108
- * write there, and a bare name — someone copying `herdr` into a unit file —
109
- * still has to go through the search. A value that resolves to nothing is
110
- * treated as absent rather than fatal: a stale override left behind by a
111
- * moved install is precisely the case we are trying to survive.
112
- */
113
- function resolveOverride(value, searchPath, platform) {
114
- const trimmed = String(value || '').trim();
115
- if (!trimmed)
116
- return null;
117
- if (looksLikePath(trimmed)) {
118
- const absolute = path.resolve(trimmed);
119
- if (isExecutableFile(absolute))
120
- return absolute;
121
- if (platform === 'win32' && path.extname(absolute) === '') {
122
- const executable = `${absolute}.exe`;
123
- if (isExecutableFile(executable))
124
- return executable;
125
- }
126
- return findInDirectory(absolute, candidateNames(COMMAND_NAME, platform));
127
- }
128
- return findOnSearchPath(searchPath, candidateNames(trimmed, platform));
129
- }
130
- /**
131
- * Everything known about where Herdr is, so callers can report it and not only
132
- * spawn it.
133
- *
134
- * `source` is `env` for an honoured `HERDR_BIN_PATH`, `path` for a hit on
135
- * `PATH`, `fallback` for one of the well-known install directories, and
136
- * `unresolved` when nothing matched. In that last case `command` is still the
137
- * bare name: `PATH` may be right in a way we cannot see from here, and letting
138
- * the spawn proceed keeps the old behaviour for anyone relying on it.
139
- */
140
- function findHerdrCommand({ env = process.env, home = os.homedir(), platform = process.platform, directories = fallbackDirectories(home, platform, env), } = {}) {
141
- const names = candidateNames(COMMAND_NAME, platform);
142
- const override = resolveOverride(env.HERDR_BIN_PATH, env.PATH, platform);
143
- if (override)
144
- return { command: override, source: 'env', found: true };
145
- const onPath = findOnSearchPath(env.PATH, names);
146
- if (onPath)
147
- return { command: onPath, source: 'path', found: true };
148
- for (const directory of directories) {
149
- const found = findInDirectory(directory, names);
150
- if (found)
151
- return { command: found, source: 'fallback', found: true };
152
- }
153
- return { command: COMMAND_NAME, source: 'unresolved', found: false };
154
- }
155
- function resolveHerdrCommand(options) {
156
- return findHerdrCommand(options).command;
157
- }
158
- /**
159
- * Re-check a command resolved earlier. A connector outlives the install that
160
- * was missing when it started, and an absolute path outlives the binary it
161
- * pointed at, so neither answer stays true for the life of the service.
162
- */
163
- function verifyHerdrCommand(command, options = {}) {
164
- if (typeof command === 'string' && looksLikePath(command) && isExecutableFile(command)) {
165
- return { command, source: 'verified', found: true };
166
- }
167
- return findHerdrCommand(options);
168
- }
169
- /** `herdr 0.9.1`, `herdr 0.10.0-preview.2` — the three numbers are all we compare. */
170
- function parseHerdrVersion(output) {
171
- const match = /(\d+)\.(\d+)\.(\d+)/.exec(String(output || ''));
172
- return match ? `${match[1]}.${match[2]}.${match[3]}` : null;
173
- }
174
- /**
175
- * Whether an install is new enough.
176
- *
177
- * An unreadable version counts as new enough. Not knowing is not evidence of an
178
- * old Herdr, and a warning nobody can act on is worse than no warning.
179
- */
180
- function meetsMinimum(version, minimum = MIN_HERDR_VERSION) {
181
- if (!version)
182
- return true;
183
- return compareVersions(version, minimum) >= 0;
184
- }
185
- /**
186
- * Ask an install what it is.
187
- *
188
- * Deliberately the binary and not the socket: `--version` answers before the
189
- * server is up, which is when the TUI and the plugin registration ask.
190
- *
191
- * `ok` is whether the command ran at all — the presence check registration has
192
- * always used. `version` is null whenever the output could not be read, and
193
- * `supported` is true in that case for the reason `meetsMinimum` explains.
194
- */
195
- function herdrVersion({ command, timeout = 10_000, ...lookup } = {}) {
196
- const resolved = command || resolveHerdrCommand(lookup);
197
- const unknown = {
198
- command: resolved,
199
- version: null,
200
- raw: null,
201
- supported: true,
202
- ok: false,
203
- };
204
- let result;
205
- try {
206
- result = spawnSync(resolved, ['--version'], {
207
- encoding: 'utf8',
208
- timeout,
209
- windowsHide: true,
210
- });
211
- }
212
- catch {
213
- return unknown;
214
- }
215
- // Status 1 has always counted as present: a binary that answers at all is
216
- // installed, whatever it thinks of the flag.
217
- if (result.error || (result.status !== 0 && result.status !== 1))
218
- return unknown;
219
- const raw = `${result.stdout || ''}${result.stderr || ''}`.trim();
220
- const version = parseHerdrVersion(raw);
221
- return {
222
- command: resolved,
223
- version,
224
- raw: raw || null,
225
- supported: meetsMinimum(version),
226
- ok: true,
227
- };
228
- }
229
- /** The one line that tells a user why an older Herdr is a problem here. */
230
- function herdrOutdatedMessage(version) {
231
- return (`Herdr ${version} is older than ${MIN_HERDR_VERSION}, which herdr-remote is written against. ` +
232
- 'Run "herdr update" — window titles, large pastes, taps on a pane and Alt shortcuts all ' +
233
- 'misbehave in browser windows before that release.');
234
- }
235
- /** The one message a user needs to fix a missing install themselves. */
236
- function herdrNotFoundMessage({ env = process.env, home = os.homedir(), platform = process.platform, directories = fallbackDirectories(home, platform, env), } = {}) {
237
- const override = String(env.HERDR_BIN_PATH || '').trim();
238
- const parts = [`Herdr executable "${COMMAND_NAME}" was not found.`];
239
- if (override)
240
- parts.push(`HERDR_BIN_PATH=${override} does not point at an executable.`);
241
- parts.push(directories.length ? `Searched PATH and ${directories.join(', ')}.` : 'Searched PATH.');
242
- parts.push('Install Herdr or set HERDR_BIN_PATH to its full path, then restart herdr-remote.');
243
- return parts.join(' ');
244
- }
245
- export { FALLBACK_DIRECTORIES, MIN_HERDR_VERSION, fallbackDirectories, findHerdrCommand, herdrNotFoundMessage, herdrOutdatedMessage, herdrVersion, meetsMinimum, parseHerdrVersion, resolveHerdrCommand, verifyHerdrCommand, };
@@ -1,120 +0,0 @@
1
- // Registration with the Herdr CLI.
2
- //
3
- // Installing from npm puts this package somewhere under a global node_modules
4
- // tree; Herdr learns about it with `herdr plugin link <path>`. Doing that from
5
- // here means the user never has to find the install directory themselves.
6
- import fs from 'node:fs';
7
- import path from 'node:path';
8
- import { spawnSync } from 'node:child_process';
9
- import { PACKAGE_ROOT } from './paths.js';
10
- import { herdrVersion, resolveHerdrCommand } from './herdr-command.js';
11
- const PLUGIN_ID = 'herdr.remote.web';
12
- const MANIFEST_NAME = 'herdr-plugin.toml';
13
- function manifestPath() {
14
- return path.join(PACKAGE_ROOT, MANIFEST_NAME);
15
- }
16
- // Registration also runs from `herdr plugin` actions and from the service
17
- // manager, neither of which is guaranteed the PATH the user installed with, so
18
- // the command is resolved rather than named.
19
- function runHerdr(args, { timeout = 15_000 } = {}) {
20
- const result = spawnSync(resolveHerdrCommand(), args, {
21
- encoding: 'utf8',
22
- timeout,
23
- windowsHide: true,
24
- });
25
- if (result.error?.code === 'ENOENT') {
26
- throw Object.assign(new Error('herdr command not found'), { code: 'HERDR_NOT_FOUND' });
27
- }
28
- return result;
29
- }
30
- /**
31
- * Parse `herdr plugin list`. Lines look like:
32
- * - herdr.remote.web (Herdr Remote Web) enabled [local:/path/to/package]
33
- *
34
- * Herdr appends diagnostics inside the brackets when something is wrong, e.g.
35
- * `[local:/path; 1 warning(s)]`, so everything from the first `;` is dropped
36
- * before the path is read.
37
- */
38
- function parsePluginList(output) {
39
- const plugins = [];
40
- for (const line of String(output || '').split('\n')) {
41
- const match = /^-\s+(\S+)\s+\((.*?)\)\s+(\S+)(?:\s+\[(.*)\])?/.exec(line.trim());
42
- if (!match)
43
- continue;
44
- const [, id, name, state, rawSource] = match;
45
- const source = rawSource ? rawSource.split(';')[0].trim() : null;
46
- plugins.push({
47
- id,
48
- name,
49
- enabled: state === 'enabled',
50
- source,
51
- warnings: rawSource?.includes(';')
52
- ? rawSource.slice(rawSource.indexOf(';') + 1).trim()
53
- : null,
54
- localPath: source?.startsWith('local:') ? source.slice('local:'.length) : null,
55
- });
56
- }
57
- return plugins;
58
- }
59
- function registrationStatus() {
60
- if (!fs.existsSync(manifestPath())) {
61
- return { available: false, registered: false, reason: 'manifest missing' };
62
- }
63
- // Carried on every answer: the screen that shows registration is also the one
64
- // that has to say the installed Herdr is too old for this plugin.
65
- const { version, supported } = herdrVersion();
66
- const installed = { version, versionSupported: supported };
67
- let result;
68
- try {
69
- result = runHerdr(['plugin', 'list']);
70
- }
71
- catch (error) {
72
- if (error.code === 'HERDR_NOT_FOUND')
73
- return { available: false, registered: false, reason: 'herdr not found' };
74
- throw error;
75
- }
76
- if (result.status !== 0) {
77
- return {
78
- available: true,
79
- registered: false,
80
- reason: String(result.stderr || '').trim(),
81
- ...installed,
82
- };
83
- }
84
- const plugins = parsePluginList(result.stdout);
85
- const entry = plugins.find((plugin) => plugin.id === PLUGIN_ID);
86
- if (!entry)
87
- return { available: true, registered: false, packageRoot: PACKAGE_ROOT, ...installed };
88
- return {
89
- available: true,
90
- registered: true,
91
- enabled: entry.enabled,
92
- linkedPath: entry.localPath,
93
- // A stale link pointing at an old checkout is the main failure mode after
94
- // switching from a source install to npm.
95
- stale: entry.localPath ? path.resolve(entry.localPath) !== path.resolve(PACKAGE_ROOT) : false,
96
- packageRoot: PACKAGE_ROOT,
97
- ...installed,
98
- };
99
- }
100
- function register() {
101
- const current = registrationStatus();
102
- if (current.registered && current.stale) {
103
- // Herdr refuses to link a second plugin with the same id, so drop the old
104
- // link before pointing it at this install.
105
- runHerdr(['plugin', 'unlink', PLUGIN_ID]);
106
- }
107
- const result = runHerdr(['plugin', 'link', PACKAGE_ROOT]);
108
- if (result.status !== 0) {
109
- throw new Error(String(result.stderr || result.stdout || '').trim() || 'herdr plugin link failed');
110
- }
111
- return { ok: true, path: PACKAGE_ROOT, output: String(result.stdout || '').trim() };
112
- }
113
- function unregister() {
114
- const result = runHerdr(['plugin', 'unlink', PLUGIN_ID]);
115
- if (result.status !== 0) {
116
- throw new Error(String(result.stderr || result.stdout || '').trim() || 'herdr plugin unlink failed');
117
- }
118
- return { ok: true, output: String(result.stdout || '').trim() };
119
- }
120
- export { parsePluginList, registrationStatus, register, unregister };