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
@@ -1,130 +0,0 @@
1
- /**
2
- * What the agents on this workstation are doing, small enough to put in a
3
- * status bar.
4
- *
5
- * The question a phone actually has is "does anything need me?", and the
6
- * terminal is a poor way to ask it: finding a blocked agent means navigating
7
- * Herdr's sidebar through a viewport the width of a hand. Herdr already knows
8
- * the answer — it classifies every pane as idle, working, blocked, done or
9
- * unknown — so this turns a session snapshot into the two-line version.
10
- *
11
- * Deliberately a pure function over a snapshot. The socket, the debounce and
12
- * the retry live in the host connector; what counts as "needs me" lives here,
13
- * where it can be tested without either.
14
- */
15
- /** Herdr's own vocabulary, in the order a person cares about them. */
16
- const AGENT_STATUSES = ['blocked', 'done', 'working', 'idle', 'unknown'];
17
- /**
18
- * How many agents travel with the summary.
19
- *
20
- * The counts are always exact; this caps only the named list, which exists so a
21
- * future screen can say *which* agent is blocked. A workstation with more than
22
- * this many live agents is not going to be read off a phone anyway.
23
- */
24
- const MAX_LISTED_AGENTS = 16;
25
- /** Titles come from a remote program; keep them short and printable. */
26
- const MAX_TITLE_LENGTH = 48;
27
- function cleanText(value, limit = MAX_TITLE_LENGTH) {
28
- if (typeof value !== 'string')
29
- return null;
30
- const collapsed = value
31
- .replace(/[\u0000-\u001F\u007F-\u009F]/g, ' ')
32
- .replace(/\s+/g, ' ')
33
- .trim();
34
- if (!collapsed)
35
- return null;
36
- return collapsed.length > limit ? collapsed.slice(0, limit) : collapsed;
37
- }
38
- function normalizeStatus(value) {
39
- return AGENT_STATUSES.includes(value)
40
- ? value
41
- : 'unknown';
42
- }
43
- function emptyCounts() {
44
- return Object.fromEntries(AGENT_STATUSES.map((status) => [status, 0]));
45
- }
46
- /**
47
- * Turn `session.snapshot` into the summary the browser receives.
48
- *
49
- * Sorted by how much each status wants attention, so the capped list keeps the
50
- * agents worth naming rather than the first ones Herdr happened to report.
51
- * `focusedPaneId` and `focusedAgent` also track the live pane selection,
52
- * including shells and panes Herdr has not added to its agent summary.
53
- */
54
- function summarizeAgents(snapshot) {
55
- const entries = Array.isArray(snapshot?.agents) ? snapshot.agents : [];
56
- const panes = Array.isArray(snapshot?.panes) ? snapshot.panes : [];
57
- const reportedFocusId = typeof snapshot?.focused_pane_id === 'string' && snapshot.focused_pane_id
58
- ? snapshot.focused_pane_id
59
- : null;
60
- // `focused_pane_id` is the primary signal in Herdr snapshots. The focused
61
- // flags are a fallback for snapshots where that top-level field is absent.
62
- const focusedPane = (reportedFocusId
63
- ? panes.find((pane) => pane?.pane_id === reportedFocusId)
64
- : panes.find((pane) => pane?.focused === true)) || null;
65
- const focusedAgentEntry = (reportedFocusId
66
- ? entries.find((entry) => entry?.pane_id === reportedFocusId)
67
- : entries.find((entry) => entry?.focused === true)) || null;
68
- const focusedPaneId = reportedFocusId ||
69
- (typeof focusedPane?.pane_id === 'string' ? focusedPane.pane_id : null) ||
70
- (typeof focusedAgentEntry?.pane_id === 'string' ? focusedAgentEntry.pane_id : null);
71
- const focusedAgent = typeof focusedPane?.agent === 'string'
72
- ? focusedPane.agent
73
- : typeof focusedAgentEntry?.agent === 'string'
74
- ? focusedAgentEntry.agent
75
- : null;
76
- const counts = emptyCounts();
77
- const ranked = [];
78
- for (const entry of entries) {
79
- if (!entry || typeof entry.pane_id !== 'string')
80
- continue;
81
- const status = normalizeStatus(entry.agent_status);
82
- counts[status] += 1;
83
- ranked.push({
84
- paneId: entry.pane_id,
85
- workspaceId: typeof entry.workspace_id === 'string' ? entry.workspace_id : null,
86
- agent: cleanText(entry.display_agent || entry.agent, 24),
87
- title: cleanText(entry.terminal_title_stripped || entry.terminal_title),
88
- status,
89
- focused: entry.focused === true,
90
- });
91
- }
92
- ranked.sort((left, right) => AGENT_STATUSES.indexOf(left.status) - AGENT_STATUSES.indexOf(right.status));
93
- return {
94
- focusedPaneId,
95
- focusedAgent,
96
- counts,
97
- // Total rather than a sum the browser has to compute, and the one number a
98
- // collapsed chip can show on its own.
99
- total: ranked.length,
100
- agents: ranked.slice(0, MAX_LISTED_AGENTS),
101
- };
102
- }
103
- /** Whether two summaries say the same thing, so an unchanged one is not sent. */
104
- function sameSummary(left, right) {
105
- if (!left || !right)
106
- return left === right;
107
- if (left.focusedPaneId !== right.focusedPaneId || left.focusedAgent !== right.focusedAgent)
108
- return false;
109
- if (left.total !== right.total)
110
- return false;
111
- for (const status of AGENT_STATUSES) {
112
- if ((left.counts?.[status] || 0) !== (right.counts?.[status] || 0))
113
- return false;
114
- }
115
- if (left.agents.length !== right.agents.length)
116
- return false;
117
- return left.agents.every((agent, index) => {
118
- const other = right.agents[index];
119
- return (other &&
120
- agent.paneId === other.paneId &&
121
- agent.status === other.status &&
122
- agent.agent === other.agent &&
123
- agent.title === other.title);
124
- });
125
- }
126
- /** The summary of a workstation with nothing to report, or nothing to ask. */
127
- function emptySummary() {
128
- return { focusedPaneId: null, focusedAgent: null, counts: emptyCounts(), total: 0, agents: [] };
129
- }
130
- export { AGENT_STATUSES, emptySummary, sameSummary, summarizeAgents };
@@ -1,157 +0,0 @@
1
- /**
2
- * Reads Apple binary property lists (`bplist00`), the format macOS writes its
3
- * preference files in, and the NSKeyedArchiver graphs stored inside them.
4
- *
5
- * Terminal.app keeps each profile's font as an archived NSFont — a binary
6
- * plist nested in a binary plist — and there is no command-line tool that
7
- * turns that into something readable without asking Terminal itself (an
8
- * AppleScript that prompts for Automation access). A reader this small is
9
- * less intrusive than any of those.
10
- */
11
- /** A reference to another object in a keyed archive (`CF$UID`). */
12
- class Uid {
13
- value;
14
- constructor(value) {
15
- this.value = value;
16
- }
17
- }
18
- function readSizedInt(buffer, offset, size) {
19
- let value = 0;
20
- for (let i = 0; i < size; i += 1)
21
- value = value * 256 + buffer[offset + i];
22
- return value;
23
- }
24
- /**
25
- * Parses a whole `bplist00` buffer into plain values: objects, arrays,
26
- * strings, numbers, booleans, Buffers (data), Dates and `Uid`s. Throws on
27
- * anything malformed; callers treat that as "no answer".
28
- */
29
- function parseBinaryPlist(buffer) {
30
- if (!Buffer.isBuffer(buffer) ||
31
- buffer.length < 40 ||
32
- buffer.toString('latin1', 0, 8) !== 'bplist00') {
33
- throw new Error('not a binary plist');
34
- }
35
- const trailer = buffer.length - 32;
36
- const offsetSize = buffer[trailer + 6];
37
- const refSize = buffer[trailer + 7];
38
- const count = readSizedInt(buffer, trailer + 8, 8);
39
- const top = readSizedInt(buffer, trailer + 16, 8);
40
- const tableOffset = readSizedInt(buffer, trailer + 24, 8);
41
- if (!offsetSize || !refSize || count > 1_000_000 || tableOffset + count * offsetSize > trailer) {
42
- throw new Error('corrupt binary plist trailer');
43
- }
44
- const offsets = [];
45
- for (let i = 0; i < count; i += 1)
46
- offsets.push(readSizedInt(buffer, tableOffset + i * offsetSize, offsetSize));
47
- const parsing = new Set();
48
- const parse = (index) => {
49
- if (index >= count || parsing.has(index))
50
- throw new Error('corrupt binary plist reference');
51
- parsing.add(index);
52
- try {
53
- return parseObject(offsets[index]);
54
- }
55
- finally {
56
- parsing.delete(index);
57
- }
58
- };
59
- // A length stored in the marker's low nibble, or as the int object after it.
60
- const lengthAt = (offset) => {
61
- const low = buffer[offset] & 0x0f;
62
- if (low !== 0x0f)
63
- return { length: low, start: offset + 1 };
64
- const marker = buffer[offset + 1];
65
- if ((marker & 0xf0) !== 0x10)
66
- throw new Error('corrupt binary plist length');
67
- const size = 1 << (marker & 0x0f);
68
- return { length: readSizedInt(buffer, offset + 2, size), start: offset + 2 + size };
69
- };
70
- const parseObject = (offset) => {
71
- const marker = buffer[offset];
72
- const type = marker >> 4;
73
- const low = marker & 0x0f;
74
- switch (type) {
75
- case 0x0:
76
- if (marker === 0x08)
77
- return false;
78
- if (marker === 0x09)
79
- return true;
80
- return null;
81
- case 0x1: {
82
- const size = 1 << low;
83
- if (size === 8)
84
- return Number(buffer.readBigInt64BE(offset + 1));
85
- return readSizedInt(buffer, offset + 1, size);
86
- }
87
- case 0x2:
88
- return low === 2 ? buffer.readFloatBE(offset + 1) : buffer.readDoubleBE(offset + 1);
89
- case 0x3:
90
- return new Date(Date.UTC(2001, 0, 1) + buffer.readDoubleBE(offset + 1) * 1000);
91
- case 0x4: {
92
- const { length, start } = lengthAt(offset);
93
- return buffer.subarray(start, start + length);
94
- }
95
- case 0x5: {
96
- const { length, start } = lengthAt(offset);
97
- return buffer.toString('latin1', start, start + length);
98
- }
99
- case 0x6: {
100
- const { length, start } = lengthAt(offset);
101
- return Buffer.from(buffer.subarray(start, start + length * 2))
102
- .swap16()
103
- .toString('utf16le');
104
- }
105
- case 0x8:
106
- return new Uid(readSizedInt(buffer, offset + 1, low + 1));
107
- case 0xa:
108
- case 0xc: {
109
- const { length, start } = lengthAt(offset);
110
- const items = [];
111
- for (let i = 0; i < length; i += 1)
112
- items.push(parse(readSizedInt(buffer, start + i * refSize, refSize)));
113
- return items;
114
- }
115
- case 0xd: {
116
- const { length, start } = lengthAt(offset);
117
- const result = {};
118
- for (let i = 0; i < length; i += 1) {
119
- const key = parse(readSizedInt(buffer, start + i * refSize, refSize));
120
- result[String(key)] = parse(readSizedInt(buffer, start + (length + i) * refSize, refSize));
121
- }
122
- return result;
123
- }
124
- default:
125
- throw new Error(`unsupported binary plist object 0x${marker.toString(16)}`);
126
- }
127
- };
128
- return parse(top);
129
- }
130
- /**
131
- * The root object of an NSKeyedArchiver plist, with every `Uid` replaced by
132
- * the object it points at (to a bounded depth; archives may be cyclic).
133
- */
134
- function unarchiveKeyed(archive, depth = 6) {
135
- const record = (value) => value && typeof value === 'object' && !Array.isArray(value)
136
- ? value
137
- : undefined;
138
- const objects = record(archive)?.$objects;
139
- const rootRef = record(record(archive)?.$top)?.root;
140
- if (!Array.isArray(objects) || !(rootRef instanceof Uid))
141
- throw new Error('not a keyed archive');
142
- const resolve = (value, level) => {
143
- if (value instanceof Uid)
144
- return level > depth ? null : resolve(objects[value.value], level + 1);
145
- if (Array.isArray(value))
146
- return value.map((item) => resolve(item, level));
147
- if (value && typeof value === 'object' && !Buffer.isBuffer(value) && !(value instanceof Date)) {
148
- const result = {};
149
- for (const [key, item] of Object.entries(value))
150
- result[key] = resolve(item, level);
151
- return result;
152
- }
153
- return value === '$null' ? null : value;
154
- };
155
- return resolve(rootRef, 0);
156
- }
157
- export { parseBinaryPlist, unarchiveKeyed };
package/dist/config.js DELETED
@@ -1,278 +0,0 @@
1
- // The configuration file: its shape, its defaults, and how a file on disk,
2
- // older schemas and the environment become one validated Config.
3
- import fs from 'node:fs';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- import { DEFAULTS as RELAY_DEFAULTS } from 'herdr-remote-relay/config';
7
- import { configPath, legacyConfigPath } from './paths.js';
8
- import { isLoopbackHost, isUnspecifiedAddress, isUnspecifiedHost, normalizeUrl, } from './relay-urls.js';
9
- // Access modes
10
- // local — a relay runs on this machine, reachable only from this machine.
11
- // lan — a relay runs on this machine, bound to every interface so other
12
- // devices on the LAN (or on a Tailscale/WireGuard overlay) reach it.
13
- // remote — no local relay; the host connector dials an operator-run relay,
14
- // which is the only way in from outside the local network.
15
- const ACCESS_MODES = ['local', 'lan', 'remote'];
16
- // The relay this project runs for people who do not want to host one. Offered
17
- // as a one-keystroke choice during setup; it is never selected automatically,
18
- // because routing a terminal through someone else's server has to be a
19
- // deliberate decision. It is a plain public relay: no join password, and each
20
- // workstation stays reachable only through its own host token.
21
- const OFFICIAL_RELAY_URL = 'wss://herdr-remote.564616.xyz';
22
- const LANGUAGES = ['auto', 'zh', 'en'];
23
- const KEEPALIVE_MANAGERS = [
24
- 'auto',
25
- 'systemd',
26
- 'launchd',
27
- 'supervisor',
28
- 'windows-task',
29
- 'windows-service',
30
- 'none',
31
- ];
32
- function isOneOf(list, value) {
33
- return list.includes(value);
34
- }
35
- const DEFAULTS = {
36
- ui: {
37
- language: 'auto',
38
- },
39
- relay: {
40
- mode: 'local',
41
- port: 8787,
42
- // Address advertised in pairing URLs when mode is "lan". Empty means "pick
43
- // the first non-internal IPv4 automatically".
44
- lanHost: '',
45
- // Manual override for the URL browsers open. Empty means "derive it".
46
- publicUrl: '',
47
- // Operator-run relay, e.g. wss://herdr.example.com (mode "remote" only).
48
- remoteUrl: '',
49
- // The CLI starts a relay of its own, so its limits are the relay
50
- // package's: a smaller ceiling here would reject an upload the hosted
51
- // relay accepts.
52
- maxPayloadBytes: RELAY_DEFAULTS.relay.maxPayloadBytes,
53
- maxClientsPerHost: RELAY_DEFAULTS.relay.maxClientsPerHost,
54
- maxHosts: RELAY_DEFAULTS.relay.maxHosts,
55
- maxPendingHandshakes: RELAY_DEFAULTS.relay.maxPendingHandshakes,
56
- maxBufferedBytesPerClient: RELAY_DEFAULTS.relay.maxBufferedBytesPerClient,
57
- hostReconnectGraceMs: RELAY_DEFAULTS.relay.hostReconnectGraceMs,
58
- allowedOrigins: [],
59
- },
60
- herdr: {
61
- socketPath: null,
62
- args: [],
63
- cwd: os.homedir(),
64
- // Start Herdr's server when herdr-remote starts (at boot, under keep-alive).
65
- // Off: Herdr starts when the user runs it, or confirms it from the WebUI.
66
- autoStart: false,
67
- },
68
- auth: {
69
- pairingTtlMs: RELAY_DEFAULTS.auth.pairingTtlMs,
70
- deviceTtlMs: RELAY_DEFAULTS.auth.deviceTtlMs,
71
- maxDevices: RELAY_DEFAULTS.auth.maxDevices,
72
- },
73
- cleanup: { ...RELAY_DEFAULTS.cleanup },
74
- keepalive: {
75
- manager: 'auto',
76
- },
77
- };
78
- function clone(value) {
79
- return JSON.parse(JSON.stringify(value));
80
- }
81
- function readJson(filePath) {
82
- try {
83
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
84
- }
85
- catch (error) {
86
- const { code, message } = error;
87
- if (code !== 'ENOENT') {
88
- process.stderr.write(`herdr-remote: ignoring invalid JSON at ${filePath}: ${message}\n`);
89
- }
90
- return {};
91
- }
92
- }
93
- /**
94
- * Copy a config written by an older, plugin-scoped install into the canonical
95
- * location. Runs at most once: it never overwrites an existing config.
96
- */
97
- function migrateLegacyConfig() {
98
- const target = configPath();
99
- if (fs.existsSync(target))
100
- return { migrated: false, reason: 'config already exists' };
101
- const legacy = legacyConfigPath();
102
- if (!legacy || !fs.existsSync(legacy))
103
- return { migrated: false, reason: 'no legacy config' };
104
- try {
105
- fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
106
- fs.copyFileSync(legacy, target);
107
- return { migrated: true, from: legacy, to: target };
108
- }
109
- catch (error) {
110
- return { migrated: false, reason: error.message };
111
- }
112
- }
113
- function mergeConfig(fileConfig) {
114
- const config = clone(DEFAULTS);
115
- const file = (fileConfig ?? {});
116
- for (const section of Object.keys(config)) {
117
- const values = file[section];
118
- if (values && typeof values === 'object')
119
- Object.assign(config[section], values);
120
- }
121
- return config;
122
- }
123
- /**
124
- * Accept configs written before access modes existed. The 0.1 schema used
125
- * `relay.local` (boolean), `relay.host` (bind address) and `relay.url`.
126
- */
127
- function normalizeLegacyFields(config, fileConfig) {
128
- const legacy = fileConfig?.relay || {};
129
- // The absence of an explicit `mode` — not an invalid one — is what marks a
130
- // file as pre-0.2, since merging defaults always leaves a valid mode behind.
131
- const hasExplicitMode = isOneOf(ACCESS_MODES, legacy.mode);
132
- if (!hasExplicitMode) {
133
- if (legacy.local === false && typeof legacy.url === 'string' && legacy.url) {
134
- config.relay.mode = 'remote';
135
- if (!config.relay.remoteUrl)
136
- config.relay.remoteUrl = legacy.url;
137
- }
138
- else if (typeof legacy.host === 'string' && legacy.host && !isLoopbackHost(legacy.host)) {
139
- config.relay.mode = 'lan';
140
- }
141
- else {
142
- config.relay.mode = 'local';
143
- }
144
- }
145
- if (config.relay.mode === 'lan' &&
146
- !config.relay.lanHost &&
147
- typeof legacy.host === 'string' &&
148
- !isLoopbackHost(legacy.host) &&
149
- legacy.host !== '0.0.0.0') {
150
- config.relay.lanHost = legacy.host;
151
- }
152
- // Superseded keys copied in by the merge would otherwise reappear in the
153
- // in-memory config and confuse anything reading it.
154
- delete config.relay.local;
155
- delete config.relay.host;
156
- delete config.relay.url;
157
- // `publicUrl` used to be mandatory and defaulted to the loopback URL; treat
158
- // that default as "no override" so derivation can take over.
159
- if (typeof config.relay.publicUrl === 'string' &&
160
- /^https?:\/\/(127\.0\.0\.1|localhost)(:\d+)?\/?$/.test(config.relay.publicUrl)) {
161
- config.relay.publicUrl = '';
162
- }
163
- }
164
- function parseInteger(value, fallback, min, max) {
165
- const numeric = Number(value);
166
- if (!Number.isInteger(numeric) || numeric < min || numeric > max)
167
- return fallback;
168
- return numeric;
169
- }
170
- function validate(config) {
171
- if (!isOneOf(ACCESS_MODES, config.relay.mode))
172
- config.relay.mode = DEFAULTS.relay.mode;
173
- if (!isOneOf(LANGUAGES, config.ui.language))
174
- config.ui.language = DEFAULTS.ui.language;
175
- if (!isOneOf(KEEPALIVE_MANAGERS, config.keepalive.manager))
176
- config.keepalive.manager = DEFAULTS.keepalive.manager;
177
- config.relay.port = parseInteger(config.relay.port, DEFAULTS.relay.port, 1, 65535);
178
- config.relay.maxPayloadBytes = parseInteger(config.relay.maxPayloadBytes, DEFAULTS.relay.maxPayloadBytes, 4096, 16 * 1024 * 1024);
179
- config.relay.maxClientsPerHost = parseInteger(config.relay.maxClientsPerHost, DEFAULTS.relay.maxClientsPerHost, 1, 256);
180
- config.relay.maxHosts = parseInteger(config.relay.maxHosts, DEFAULTS.relay.maxHosts, 1, 100000);
181
- config.relay.maxPendingHandshakes = parseInteger(config.relay.maxPendingHandshakes, DEFAULTS.relay.maxPendingHandshakes, 16, 100000);
182
- config.relay.maxBufferedBytesPerClient = parseInteger(config.relay.maxBufferedBytesPerClient, DEFAULTS.relay.maxBufferedBytesPerClient, 64 * 1024, 256 * 1024 * 1024);
183
- config.relay.hostReconnectGraceMs = parseInteger(config.relay.hostReconnectGraceMs, DEFAULTS.relay.hostReconnectGraceMs, 1000, 24 * 60 * 60 * 1000);
184
- config.auth.pairingTtlMs = parseInteger(config.auth.pairingTtlMs, DEFAULTS.auth.pairingTtlMs, 30 * 1000, 24 * 60 * 60 * 1000);
185
- config.auth.deviceTtlMs = parseInteger(config.auth.deviceTtlMs, DEFAULTS.auth.deviceTtlMs, 60 * 1000, 365 * 24 * 60 * 60 * 1000);
186
- config.auth.maxDevices = parseInteger(config.auth.maxDevices, DEFAULTS.auth.maxDevices, 1, 10000);
187
- config.cleanup.intervalMs = parseInteger(config.cleanup.intervalMs, DEFAULTS.cleanup.intervalMs, 1000, 24 * 60 * 60 * 1000);
188
- config.cleanup.heartbeatIntervalMs = parseInteger(config.cleanup.heartbeatIntervalMs, DEFAULTS.cleanup.heartbeatIntervalMs, 1000, 10 * 60 * 1000);
189
- config.cleanup.staleAfterMs = parseInteger(config.cleanup.staleAfterMs, DEFAULTS.cleanup.staleAfterMs, config.cleanup.heartbeatIntervalMs * 2, 24 * 60 * 60 * 1000);
190
- config.relay.publicUrl = normalizeUrl(config.relay.publicUrl);
191
- config.relay.remoteUrl = normalizeUrl(config.relay.remoteUrl);
192
- config.relay.lanHost =
193
- typeof config.relay.lanHost === 'string' ? config.relay.lanHost.trim() : '';
194
- // A LAN relay listens on every interface, but a loopback or wildcard value
195
- // cannot be opened by another device. Clear stale values from older TUI
196
- // versions so the advertised URL falls back to a real interface address.
197
- if (config.relay.mode === 'lan' &&
198
- (isLoopbackHost(config.relay.lanHost) || isUnspecifiedAddress(config.relay.lanHost))) {
199
- config.relay.lanHost = '';
200
- }
201
- // 0.0.0.0 is a server-side bind wildcard, never a destination a browser can
202
- // open. Treat it like an empty override so LAN mode derives the real address.
203
- if (isUnspecifiedHost(config.relay.publicUrl))
204
- config.relay.publicUrl = '';
205
- if (!Array.isArray(config.relay.allowedOrigins))
206
- config.relay.allowedOrigins = [];
207
- if (!Array.isArray(config.herdr.args) ||
208
- !config.herdr.args.every((arg) => typeof arg === 'string')) {
209
- config.herdr.args = [];
210
- }
211
- const args = config.herdr.args;
212
- // Herdr 0.9.0 removed --no-session; retaining it in saved configs causes
213
- // session creation to fail. Filter it out if present.
214
- if (args.includes('--no-session')) {
215
- config.herdr.args = args.filter((arg) => arg !== '--no-session');
216
- process.stderr.write('herdr-remote: ignoring removed --no-session argument (removed in Herdr 0.9.0)\n');
217
- }
218
- if (typeof config.herdr.socketPath !== 'string' || config.herdr.socketPath.length === 0) {
219
- config.herdr.socketPath = null;
220
- }
221
- if (typeof config.herdr.cwd !== 'string' || config.herdr.cwd.length === 0) {
222
- config.herdr.cwd = os.homedir();
223
- }
224
- config.herdr.autoStart = config.herdr.autoStart === true;
225
- // A "remote" config without a relay URL cannot reach anything; fall back to
226
- // local rather than silently starting a host connector that dials nowhere.
227
- if (config.relay.mode === 'remote' && !config.relay.remoteUrl) {
228
- config.relay.mode = 'local';
229
- }
230
- // Every field `Config` promises has been normalised above.
231
- return config;
232
- }
233
- function applyEnvironment(config) {
234
- const env = process.env;
235
- if (env.HERDR_REMOTE_LANG && isOneOf(LANGUAGES, env.HERDR_REMOTE_LANG))
236
- config.ui.language = env.HERDR_REMOTE_LANG;
237
- if (env.HERDR_REMOTE_MODE && isOneOf(ACCESS_MODES, env.HERDR_REMOTE_MODE))
238
- config.relay.mode = env.HERDR_REMOTE_MODE;
239
- if (env.RELAY_PORT)
240
- config.relay.port = env.RELAY_PORT;
241
- if (env.RELAY_PUBLIC_URL)
242
- config.relay.publicUrl = env.RELAY_PUBLIC_URL;
243
- if (env.RELAY_REMOTE_URL)
244
- config.relay.remoteUrl = env.RELAY_REMOTE_URL;
245
- if (env.RELAY_MAX_HOSTS)
246
- config.relay.maxHosts = env.RELAY_MAX_HOSTS;
247
- if (env.RELAY_MAX_PENDING_HANDSHAKES)
248
- config.relay.maxPendingHandshakes = env.RELAY_MAX_PENDING_HANDSHAKES;
249
- if (env.RELAY_MAX_BUFFERED_BYTES_PER_CLIENT)
250
- config.relay.maxBufferedBytesPerClient = env.RELAY_MAX_BUFFERED_BYTES_PER_CLIENT;
251
- if (env.RELAY_HOST_RECONNECT_GRACE_MS)
252
- config.relay.hostReconnectGraceMs = env.RELAY_HOST_RECONNECT_GRACE_MS;
253
- if (env.HERDR_SOCKET_PATH)
254
- config.herdr.socketPath = env.HERDR_SOCKET_PATH;
255
- if (env.HERDR_CWD)
256
- config.herdr.cwd = env.HERDR_CWD;
257
- if (env.HERDR_ARGS_JSON) {
258
- try {
259
- const args = JSON.parse(env.HERDR_ARGS_JSON);
260
- if (Array.isArray(args))
261
- config.herdr.args = args;
262
- }
263
- catch (error) {
264
- process.stderr.write(`herdr-remote: invalid HERDR_ARGS_JSON: ${error.message}\n`);
265
- }
266
- }
267
- }
268
- function loadConfig() {
269
- const fileConfig = readJson(configPath());
270
- const draft = mergeConfig(fileConfig);
271
- normalizeLegacyFields(draft, fileConfig);
272
- applyEnvironment(draft);
273
- return validate(draft);
274
- }
275
- function configExists() {
276
- return fs.existsSync(configPath());
277
- }
278
- export { DEFAULTS, ACCESS_MODES, OFFICIAL_RELAY_URL, LANGUAGES, KEEPALIVE_MANAGERS, configExists, migrateLegacyConfig, loadConfig, validate, };
@@ -1,40 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import { cliEntryPoint } from '../keepalive/environment.js';
3
- import { encodePowerShellCommand, escapePowerShellSingleQuoted, } from '../keepalive/windows-service.js';
4
- function waitForChild(child) {
5
- return new Promise((resolve, reject) => {
6
- child.once('error', reject);
7
- child.once('close', (code) => resolve(code ?? 1));
8
- });
9
- }
10
- export function renderAdminBrokerElevationScript({ action, nodePath, entryPoint, }) {
11
- const quote = escapePowerShellSingleQuoted;
12
- const args = `"${entryPoint}" admin-broker ${action} --elevated`;
13
- return `$ErrorActionPreference = 'Stop'
14
- try {
15
- $process = Start-Process -FilePath '${quote(nodePath)}' -ArgumentList '${quote(args)}' -Verb RunAs -Wait -PassThru -WindowStyle Hidden
16
- } catch {
17
- if (($_.Exception.HResult -band 65535) -eq 1223) { exit 1223 }
18
- exit 1
19
- }
20
- exit $process.ExitCode`;
21
- }
22
- function elevatedErrorCode(message, code) {
23
- return Object.assign(new Error(message), { code });
24
- }
25
- /** Register the per-user task from the Windows TUI, requesting elevation once. */
26
- export async function configureAdminBroker(action, t) {
27
- if (process.platform !== 'win32')
28
- throw new Error(t('adminBroker.windowsOnly'));
29
- const script = renderAdminBrokerElevationScript({
30
- action,
31
- nodePath: process.execPath,
32
- entryPoint: cliEntryPoint(),
33
- });
34
- const child = spawn('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', encodePowerShellCommand(script)], { windowsHide: true, stdio: 'ignore' });
35
- const exitCode = await waitForChild(child);
36
- if (exitCode === 1223)
37
- throw elevatedErrorCode(t('adminBroker.elevationCancelled'), 'ELEVATION_CANCELLED');
38
- if (exitCode !== 0)
39
- throw new Error(t('adminBroker.setupFailed'));
40
- }
@@ -1,67 +0,0 @@
1
- // `herdr-remote admin-shell`: the administrator terminal inside a Herdr tab.
2
- //
3
- // This process runs in the tab's pane with the pane's standard rights and
4
- // relays its console to an elevated PowerShell that the admin broker runs. On
5
- // Windows, Node's raw mode reads keys as VT input and writes VT output through
6
- // unchanged, so the relay is byte for byte.
7
- import { requestHerdr } from '../herdr-api.js';
8
- import { ADMIN_TAB_ENV } from './admin-tab.js';
9
- import { ElevatedPty } from './elevatedPty.js';
10
- const BROKER_UNAVAILABLE_CODES = ['ENOENT', 'ECONNREFUSED', 'ETIMEDOUT'];
11
- /** Close the pane this runs in, which closes the tab `openAdminTab` made for it. */
12
- async function closeOwnPane() {
13
- const socketPath = process.env.HERDR_SOCKET_PATH;
14
- const paneId = process.env.HERDR_PANE_ID;
15
- if (process.env[ADMIN_TAB_ENV] !== '1' || !socketPath || !paneId)
16
- return;
17
- try {
18
- await requestHerdr(socketPath, 'pane.close', { pane_id: paneId });
19
- }
20
- catch {
21
- // The shell's prompt comes back instead; the user can close the tab.
22
- }
23
- }
24
- export async function runAdminShell(t, { stdin = process.stdin, stdout = process.stdout, platform = process.platform, } = {}) {
25
- if (platform !== 'win32')
26
- throw new Error(t('adminBroker.windowsOnly'));
27
- if (!stdin.isTTY || !stdout.isTTY)
28
- throw new Error(t('adminShell.needsTerminal'));
29
- const pty = new ElevatedPty({
30
- command: 'powershell.exe',
31
- args: ['-NoLogo', '-NoProfile'],
32
- cwd: process.cwd(),
33
- });
34
- const relayInput = (data) => pty.write(data);
35
- const relayResize = () => pty.resize(stdout.columns, stdout.rows);
36
- stdin.setRawMode(true);
37
- stdin.on('data', relayInput);
38
- // The CLI's Windows color probe pauses stdin before any command runs, and a
39
- // paused stream ignores a new 'data' listener: no key would reach PowerShell.
40
- stdin.resume();
41
- stdout.on('resize', relayResize);
42
- try {
43
- const exitCode = await new Promise((resolve, reject) => {
44
- pty.start({
45
- cols: stdout.columns,
46
- rows: stdout.rows,
47
- onData: (data) => stdout.write(data),
48
- onExit: (event) => resolve(event.exitCode),
49
- onError: reject,
50
- });
51
- });
52
- await closeOwnPane();
53
- return exitCode;
54
- }
55
- catch (error) {
56
- const { code } = error;
57
- if (code && BROKER_UNAVAILABLE_CODES.includes(code))
58
- throw new Error(t('adminShell.brokerUnavailable'));
59
- throw error;
60
- }
61
- finally {
62
- stdin.off('data', relayInput);
63
- stdout.off('resize', relayResize);
64
- stdin.setRawMode(false);
65
- stdin.pause();
66
- }
67
- }