herdr-remote 0.2.39 → 0.3.31

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 (203) hide show
  1. package/dist/chunks/353DMT35.js +3 -0
  2. package/dist/chunks/42KARAL4.js +31 -0
  3. package/dist/chunks/7AUQHB3O.js +10 -0
  4. package/dist/chunks/7SMZ43LI.js +9 -0
  5. package/dist/chunks/AM3OEMFK.js +16 -0
  6. package/dist/chunks/ANNJESOL.js +10 -0
  7. package/dist/chunks/AWB4A65P.js +63 -0
  8. package/dist/chunks/C2UUZY6J.js +2 -0
  9. package/dist/chunks/DGUZCGQN.js +3 -0
  10. package/dist/chunks/H24ZP5D5.js +16 -0
  11. package/dist/chunks/I6NRCYLI.js +3 -0
  12. package/dist/chunks/JJGEV4EZ.js +3 -0
  13. package/dist/chunks/KRSURB5L.js +5 -0
  14. package/dist/chunks/KRVF37OW.js +3 -0
  15. package/dist/chunks/KUDCC7ZO.js +6 -0
  16. package/dist/chunks/M3NRPJK2.js +77 -0
  17. package/dist/chunks/M4AFZHKH.js +3 -0
  18. package/dist/chunks/M52LJEES.js +6 -0
  19. package/dist/chunks/O2LNVFUT.js +3 -0
  20. package/dist/chunks/OJPBI7KB.js +3 -0
  21. package/dist/chunks/OMN6C7J6.js +3 -0
  22. package/dist/chunks/PBMBFWLQ.js +3 -0
  23. package/dist/chunks/PBRQ3HEG.js +3 -0
  24. package/dist/chunks/PFFVGFPL.js +45 -0
  25. package/dist/chunks/QRGNEKME.js +5 -0
  26. package/dist/chunks/TNLCNVA2.js +3 -0
  27. package/dist/chunks/U27JJKJ6.js +3 -0
  28. package/dist/chunks/U6QAYG3E.js +3 -0
  29. package/dist/chunks/UEOQE77N.js +4 -0
  30. package/dist/chunks/WSACSE45.js +4 -0
  31. package/dist/chunks/WY7WHEJ4.js +3 -0
  32. package/dist/chunks/XJKWLK43.js +4 -0
  33. package/dist/chunks/XN6D5S55.js +3 -0
  34. package/dist/chunks/YD3MIP7B.js +5 -0
  35. package/dist/chunks/YXDQ62BB.js +4 -0
  36. package/dist/cli.js +21 -294
  37. package/dist/connector/admin-broker.js +3 -303
  38. package/dist/connector/main.js +20 -18
  39. package/dist/harfbuzz-subset.wasm +0 -0
  40. package/dist/postinstall.js +17 -0
  41. package/herdr-plugin.toml +1 -1
  42. package/node_modules/herdr-remote-relay/bin/herdr-remote-relay.js +33 -92
  43. package/node_modules/herdr-remote-relay/package.json +3 -46
  44. package/package.json +8 -27
  45. package/dist/agent-status.js +0 -130
  46. package/dist/binary-plist.js +0 -157
  47. package/dist/config.js +0 -278
  48. package/dist/connector/admin-broker-setup.js +0 -40
  49. package/dist/connector/admin-shell.js +0 -67
  50. package/dist/connector/admin-tab.js +0 -90
  51. package/dist/connector/adminBrokerEndpoint.js +0 -68
  52. package/dist/connector/agent-watch.js +0 -123
  53. package/dist/connector/elevatedPty.js +0 -141
  54. package/dist/connector/font-server.js +0 -165
  55. package/dist/connector/herdr-prefix-keys.js +0 -28
  56. package/dist/connector/host-connector.js +0 -433
  57. package/dist/connector/host-hello.js +0 -32
  58. package/dist/connector/lock.js +0 -73
  59. package/dist/connector/sessions.js +0 -381
  60. package/dist/connector/update-report.js +0 -70
  61. package/dist/connector/wire.js +0 -42
  62. package/dist/exit-codes.js +0 -13
  63. package/dist/font-subset.js +0 -116
  64. package/dist/herdr-api.js +0 -254
  65. package/dist/herdr-command.js +0 -245
  66. package/dist/herdr-plugin.js +0 -120
  67. package/dist/herdr-server.js +0 -190
  68. package/dist/i18n/en.js +0 -257
  69. package/dist/i18n/index.js +0 -48
  70. package/dist/i18n/zh.js +0 -256
  71. package/dist/keepalive/detached.js +0 -97
  72. package/dist/keepalive/environment.js +0 -44
  73. package/dist/keepalive/index.js +0 -129
  74. package/dist/keepalive/launchd.js +0 -117
  75. package/dist/keepalive/systemd.js +0 -112
  76. package/dist/keepalive/types.js +0 -1
  77. package/dist/keepalive/windows-service-setup.js +0 -138
  78. package/dist/keepalive/windows-service.js +0 -272
  79. package/dist/keepalive/windows-task.js +0 -191
  80. package/dist/lib/process.js +0 -132
  81. package/dist/lifecycle.js +0 -79
  82. package/dist/net-interfaces.js +0 -82
  83. package/dist/node-pty-loader.js +0 -80
  84. package/dist/pasted-files.js +0 -115
  85. package/dist/paths.js +0 -74
  86. package/dist/pty-session.js +0 -133
  87. package/dist/relay-client.js +0 -106
  88. package/dist/relay-urls.js +0 -99
  89. package/dist/runtime.js +0 -107
  90. package/dist/service.js +0 -267
  91. package/dist/settings-model.js +0 -298
  92. package/dist/socket-discovery.js +0 -70
  93. package/dist/supervisor.js +0 -323
  94. package/dist/terminal-font/deps.js +0 -83
  95. package/dist/terminal-font/files.js +0 -357
  96. package/dist/terminal-font/formats.js +0 -355
  97. package/dist/terminal-font/index.js +0 -187
  98. package/dist/terminal-font/readers.js +0 -373
  99. package/dist/terminal-font/terminals.js +0 -147
  100. package/dist/terminal-palette.js +0 -399
  101. package/dist/tui/App.js +0 -199
  102. package/dist/tui/api.js +0 -91
  103. package/dist/tui/components/TextField.js +0 -50
  104. package/dist/tui/components/common.js +0 -80
  105. package/dist/tui/index.js +0 -36
  106. package/dist/tui/mouse/index.js +0 -164
  107. package/dist/tui/mouse/protocol.js +0 -96
  108. package/dist/tui/mouse/source.js +0 -79
  109. package/dist/tui/screens/About.js +0 -172
  110. package/dist/tui/screens/Herdr.js +0 -75
  111. package/dist/tui/screens/Keepalive.js +0 -141
  112. package/dist/tui/screens/Overview.js +0 -36
  113. package/dist/tui/screens/Pair.js +0 -74
  114. package/dist/tui/screens/Relay.js +0 -243
  115. package/dist/tui/screens/Services.js +0 -63
  116. package/dist/tui/screens/Wizard.js +0 -241
  117. package/dist/tui/terminal.js +0 -39
  118. package/dist/tui/theme.js +0 -33
  119. package/dist/update-command.js +0 -118
  120. package/dist/updater/in-place.js +0 -83
  121. package/dist/updater/npm.js +0 -62
  122. package/dist/updater.js +0 -401
  123. package/node_modules/herdr-remote-relay/README.md +0 -76
  124. package/node_modules/herdr-remote-relay/README.zh-CN.md +0 -76
  125. package/node_modules/herdr-remote-relay/config.example.json +0 -18
  126. package/node_modules/herdr-remote-relay/deploy/cloudflared/config.yml.example +0 -9
  127. package/node_modules/herdr-remote-relay/deploy/docker/compose.yaml +0 -23
  128. package/node_modules/herdr-remote-relay/deploy/nginx/herdr-remote-relay.conf.example +0 -49
  129. package/node_modules/herdr-remote-relay/deploy/systemd/herdr-remote-relay.service +0 -41
  130. package/node_modules/herdr-remote-relay/deploy/systemd/relay.env.example +0 -25
  131. package/node_modules/herdr-remote-relay/dist/auth-store.d.ts +0 -114
  132. package/node_modules/herdr-remote-relay/dist/auth-store.js +0 -289
  133. package/node_modules/herdr-remote-relay/dist/metrics.d.ts +0 -76
  134. package/node_modules/herdr-remote-relay/dist/metrics.js +0 -249
  135. package/node_modules/herdr-remote-relay/dist/protocol/frames.d.ts +0 -27
  136. package/node_modules/herdr-remote-relay/dist/protocol/frames.js +0 -110
  137. package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.d.ts +0 -8
  138. package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.js +0 -34
  139. package/node_modules/herdr-remote-relay/dist/protocol/http.d.ts +0 -165
  140. package/node_modules/herdr-remote-relay/dist/protocol/http.js +0 -3
  141. package/node_modules/herdr-remote-relay/dist/protocol/index.d.ts +0 -7
  142. package/node_modules/herdr-remote-relay/dist/protocol/index.js +0 -9
  143. package/node_modules/herdr-remote-relay/dist/protocol/messages.d.ts +0 -362
  144. package/node_modules/herdr-remote-relay/dist/protocol/messages.js +0 -20
  145. package/node_modules/herdr-remote-relay/dist/protocol/paste.d.ts +0 -14
  146. package/node_modules/herdr-remote-relay/dist/protocol/paste.js +0 -50
  147. package/node_modules/herdr-remote-relay/dist/protocol/release.d.ts +0 -8
  148. package/node_modules/herdr-remote-relay/dist/protocol/release.js +0 -17
  149. package/node_modules/herdr-remote-relay/dist/protocol/terminal.d.ts +0 -79
  150. package/node_modules/herdr-remote-relay/dist/protocol/terminal.js +0 -150
  151. package/node_modules/herdr-remote-relay/dist/relay-config.d.ts +0 -74
  152. package/node_modules/herdr-remote-relay/dist/relay-config.js +0 -316
  153. package/node_modules/herdr-remote-relay/dist/relay-server.d.ts +0 -67
  154. package/node_modules/herdr-remote-relay/dist/relay-server.js +0 -196
  155. package/node_modules/herdr-remote-relay/dist/scroll-input.d.ts +0 -17
  156. package/node_modules/herdr-remote-relay/dist/scroll-input.js +0 -124
  157. package/node_modules/herdr-remote-relay/dist/server/client-channel.d.ts +0 -3
  158. package/node_modules/herdr-remote-relay/dist/server/client-channel.js +0 -396
  159. package/node_modules/herdr-remote-relay/dist/server/font-proxy.d.ts +0 -19
  160. package/node_modules/herdr-remote-relay/dist/server/font-proxy.js +0 -155
  161. package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.d.ts +0 -27
  162. package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.js +0 -83
  163. package/node_modules/herdr-remote-relay/dist/server/host-channel.d.ts +0 -3
  164. package/node_modules/herdr-remote-relay/dist/server/host-channel.js +0 -271
  165. package/node_modules/herdr-remote-relay/dist/server/http-api.d.ts +0 -4
  166. package/node_modules/herdr-remote-relay/dist/server/http-api.js +0 -219
  167. package/node_modules/herdr-remote-relay/dist/server/maintenance.d.ts +0 -5
  168. package/node_modules/herdr-remote-relay/dist/server/maintenance.js +0 -64
  169. package/node_modules/herdr-remote-relay/dist/server/requests.d.ts +0 -34
  170. package/node_modules/herdr-remote-relay/dist/server/requests.js +0 -142
  171. package/node_modules/herdr-remote-relay/dist/server/sessions.d.ts +0 -36
  172. package/node_modules/herdr-remote-relay/dist/server/sessions.js +0 -230
  173. package/node_modules/herdr-remote-relay/dist/server/sockets.d.ts +0 -12
  174. package/node_modules/herdr-remote-relay/dist/server/sockets.js +0 -64
  175. package/node_modules/herdr-remote-relay/dist/server/status.d.ts +0 -11
  176. package/node_modules/herdr-remote-relay/dist/server/status.js +0 -99
  177. package/node_modules/herdr-remote-relay/dist/server/transport.d.ts +0 -16
  178. package/node_modules/herdr-remote-relay/dist/server/transport.js +0 -110
  179. package/node_modules/herdr-remote-relay/dist/server/types.d.ts +0 -133
  180. package/node_modules/herdr-remote-relay/dist/server/types.js +0 -3
  181. package/node_modules/herdr-remote-relay/dist/server/version.d.ts +0 -2
  182. package/node_modules/herdr-remote-relay/dist/server/version.js +0 -5
  183. package/node_modules/herdr-remote-relay/dist/state.d.ts +0 -6
  184. package/node_modules/herdr-remote-relay/dist/state.js +0 -47
  185. package/node_modules/ws/LICENSE +0 -20
  186. package/node_modules/ws/README.md +0 -548
  187. package/node_modules/ws/browser.js +0 -8
  188. package/node_modules/ws/index.js +0 -22
  189. package/node_modules/ws/lib/buffer-util.js +0 -131
  190. package/node_modules/ws/lib/constants.js +0 -19
  191. package/node_modules/ws/lib/event-target.js +0 -292
  192. package/node_modules/ws/lib/extension.js +0 -203
  193. package/node_modules/ws/lib/limiter.js +0 -55
  194. package/node_modules/ws/lib/permessage-deflate.js +0 -530
  195. package/node_modules/ws/lib/receiver.js +0 -743
  196. package/node_modules/ws/lib/sender.js +0 -607
  197. package/node_modules/ws/lib/stream.js +0 -161
  198. package/node_modules/ws/lib/subprotocol.js +0 -62
  199. package/node_modules/ws/lib/validation.js +0 -152
  200. package/node_modules/ws/lib/websocket-server.js +0 -562
  201. package/node_modules/ws/lib/websocket.js +0 -1407
  202. package/node_modules/ws/package.json +0 -74
  203. package/node_modules/ws/wrapper.mjs +0 -21
@@ -1,50 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
3
- import { Box, Text, useInput } from 'ink';
4
- import { theme } from '../theme.js';
5
- // Anything below 0x20, plus DEL: escape sequences and control keys that must
6
- // never end up as literal text in a field.
7
- const CONTROL_CHARACTERS = /[\x00-\x1f\x7f]/;
8
- /**
9
- * A single-line editor.
10
- *
11
- * Small enough to own rather than depend on: the TUI needs exactly one editing
12
- * affordance, and keeping it here means the escape/confirm contract is the same
13
- * everywhere and stays consistent with `useInput({ isActive })` gating, so a
14
- * keystroke never lands in both the editor and the surrounding menu.
15
- */
16
- export function TextField({ value, placeholder, active, mask = false, onSubmit, onCancel, }) {
17
- const [buffer, setBuffer] = useState(value);
18
- useEffect(() => {
19
- if (active)
20
- setBuffer(value);
21
- }, [active, value]);
22
- useInput((input, key) => {
23
- if (key.return) {
24
- onSubmit(buffer);
25
- return;
26
- }
27
- if (key.escape) {
28
- setBuffer(value);
29
- onCancel();
30
- return;
31
- }
32
- if (key.backspace || key.delete) {
33
- setBuffer((current) => current.slice(0, -1));
34
- return;
35
- }
36
- if (key.ctrl && input === 'u') {
37
- setBuffer('');
38
- return;
39
- }
40
- if (input && !key.ctrl && !key.meta && !CONTROL_CHARACTERS.test(input)) {
41
- setBuffer((current) => current + input);
42
- }
43
- }, { isActive: active });
44
- const display = active ? buffer : value;
45
- const shown = mask && display ? '•'.repeat(Math.min(display.length, 32)) : display;
46
- if (!active) {
47
- return shown ? _jsx(Text, { children: shown }) : _jsx(Text, { color: theme.muted, children: placeholder ?? '' });
48
- }
49
- return (_jsxs(Box, { children: [_jsx(Text, { color: theme.accent, children: shown }), _jsx(Text, { color: theme.accent, inverse: true, children: ' ' })] }));
50
- }
@@ -1,80 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef } from 'react';
3
- import { Box, Text } from 'ink';
4
- import { useMouseTarget } from '../mouse/index.js';
5
- import { SELECTED_MARKER, STATUS_COLOR, STATUS_GLYPH, UNSELECTED_MARKER, theme, } from '../theme.js';
6
- export function StatusDot({ level }) {
7
- return _jsx(Text, { color: STATUS_COLOR[level], children: STATUS_GLYPH[level] });
8
- }
9
- // Wide enough for the longest label in either language plus a separating
10
- // space. Too narrow and Ink wraps the label onto a second line or butts it
11
- // straight against its value.
12
- const LABEL_COLUMN_WIDTH = 20;
13
- /** A `label value` row with the labels aligned into a column. */
14
- export function Row({ label, labelWidth = LABEL_COLUMN_WIDTH, children, }) {
15
- return (_jsxs(Box, { children: [_jsx(Box, { width: labelWidth, flexShrink: 0, children: _jsx(Text, { color: theme.muted, wrap: "truncate-end", children: label }) }), _jsx(Box, { flexGrow: 1, children: children })] }));
16
- }
17
- /**
18
- * One row of a list that responds to both the keyboard and the mouse.
19
- *
20
- * Each row is its own component because the mouse hooks bind to a ref: mapping
21
- * over items in the parent and calling hooks inline would make the hook order
22
- * depend on the list length.
23
- *
24
- * The row stretches to the full width of its container so the whole line is a
25
- * click target, not just the few columns the label happens to occupy.
26
- */
27
- export function Selectable({ selected, disabled = false, onSelect, onHover, children, }) {
28
- const ref = useRef(null);
29
- const hovered = useMouseTarget(ref, { disabled, onClick: onSelect });
30
- useFollowPointer(hovered, onHover);
31
- const active = selected || hovered;
32
- const color = disabled ? theme.muted : active ? theme.accent : undefined;
33
- return (_jsxs(Box, { ref: ref, children: [_jsxs(Text, { color: active ? theme.accent : undefined, children: [active ? SELECTED_MARKER : UNSELECTED_MARKER, ' '] }), _jsx(Text, { color: color, bold: active, dimColor: disabled, children: children })] }));
34
- }
35
- /**
36
- * Move the keyboard cursor to whatever the pointer is over.
37
- *
38
- * Without this the two run independently and a list shows two markers at once —
39
- * one where the keyboard is, one under the mouse — leaving it ambiguous which
40
- * one Enter would activate.
41
- */
42
- function useFollowPointer(hovered, onHover) {
43
- const handler = useRef(onHover);
44
- handler.current = onHover;
45
- useEffect(() => {
46
- if (hovered)
47
- handler.current?.();
48
- }, [hovered]);
49
- }
50
- /**
51
- * Keyboard/mouse driven list. `onChange` moves the highlight, `onSelect`
52
- * activates an entry.
53
- */
54
- export function Menu({ items, selectedId, onChange, onSelect, }) {
55
- return (_jsx(Box, { flexDirection: "column", children: items.map((item) => (_jsx(Selectable, { selected: item.id === selectedId, disabled: item.disabled, onSelect: () => onSelect(item.id), onHover: () => onChange(item.id), children: item.label }, item.id))) }));
56
- }
57
- /**
58
- * A `label value` row that is clickable across its whole width.
59
- *
60
- * Used for settings: clicking anywhere on the line — label or value — starts
61
- * editing it, which is what a pointer user expects.
62
- */
63
- export function FieldRow({ label, selected, disabled = false, labelWidth = 22, onSelect, onHover, children, }) {
64
- const ref = useRef(null);
65
- const hovered = useMouseTarget(ref, { disabled, onClick: onSelect });
66
- useFollowPointer(hovered, onHover);
67
- const active = selected || hovered;
68
- return (_jsxs(Box, { ref: ref, children: [_jsxs(Box, { width: labelWidth, flexShrink: 0, children: [_jsxs(Text, { color: active ? theme.accent : undefined, children: [active ? SELECTED_MARKER : UNSELECTED_MARKER, ' '] }), _jsx(Text, { color: active ? theme.accent : theme.muted, bold: active, wrap: "truncate-end", children: label })] }), _jsx(Box, { flexGrow: 1, children: children })] }));
69
- }
70
- /** A framed panel used for every screen body, so the layout stays predictable. */
71
- export function Panel({ title, children }) {
72
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.border, paddingX: 1, flexGrow: 1, children: [title ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: title }) })) : null, children] }));
73
- }
74
- /** Feedback from the last action. Colour is the only signal, no icons. */
75
- export function Message({ text, level, }) {
76
- if (!text)
77
- return null;
78
- const color = level === 'error' ? theme.bad : level === 'success' ? theme.ok : theme.muted;
79
- return (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: color, children: text }) }));
80
- }
package/dist/tui/index.js DELETED
@@ -1,36 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { render } from 'ink';
3
- import { App } from './App.js';
4
- import { MouseProvider, createMouseSource } from './mouse/index.js';
5
- import { enterFullScreen, restoreTerminal } from './terminal.js';
6
- import { configExists } from './api.js';
7
- export { App } from './App.js';
8
- export { Wizard } from './screens/Wizard.js';
9
- export async function startTui({ language = null } = {}) {
10
- const needsWizard = !configExists();
11
- const mouse = createMouseSource(process.stdin, process.stdout);
12
- // Before render: Ink draws from wherever the cursor is, and the mouse hit
13
- // test assumes the app starts at row 1.
14
- enterFullScreen();
15
- const instance = render(_jsx(MouseProvider, { source: mouse, children: _jsx(App, { initialLanguage: language, needsWizard: needsWizard }) }), { stdin: mouse.stdin, exitOnCtrlC: false });
16
- const cleanup = () => {
17
- mouse.dispose();
18
- restoreTerminal();
19
- };
20
- const onSignal = () => {
21
- instance.unmount();
22
- cleanup();
23
- process.exit(0);
24
- };
25
- process.once('SIGINT', onSignal);
26
- process.once('SIGTERM', onSignal);
27
- process.once('exit', cleanup);
28
- try {
29
- await instance.waitUntilExit();
30
- }
31
- finally {
32
- cleanup();
33
- process.off('SIGINT', onSignal);
34
- process.off('SIGTERM', onSignal);
35
- }
36
- }
@@ -1,164 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
3
- export { createMouseSource } from './source.js';
4
- export { splitMouseInput, createMouseSplitter } from './protocol.js';
5
- /**
6
- * Absolute position of an element, in 1-based terminal coordinates.
7
- *
8
- * Yoga reports each node's offset relative to its parent, so the walk up the
9
- * tree accumulates them. The +1 converts to terminal coordinates, which is only
10
- * correct because the app owns the alternate screen and therefore starts at
11
- * row 1 — rendering below a shell prompt would shift every row.
12
- */
13
- export function measureElement(node) {
14
- if (!node?.yogaNode)
15
- return null;
16
- const layout = node.yogaNode.getComputedLayout();
17
- let left = 0;
18
- let top = 0;
19
- let current = node;
20
- while (current?.yogaNode) {
21
- const own = current.yogaNode.getComputedLayout();
22
- left += own.left;
23
- top += own.top;
24
- current = current.parentNode ?? undefined;
25
- }
26
- return { left: left + 1, top: top + 1, width: layout.width, height: layout.height };
27
- }
28
- /**
29
- * Half-open containment: a row-N item owns row N and nothing else.
30
- *
31
- * The inclusive test used by @ink-tools/ink-mouse (`y <= top + height`) makes
32
- * every one-row item also claim the row below it, so two neighbours fire for a
33
- * single click and the selection lands on the wrong one.
34
- */
35
- export function rectContains(rect, x, y) {
36
- return (x >= rect.left && x < rect.left + rect.width && y >= rect.top && y < rect.top + rect.height);
37
- }
38
- // Two contexts on purpose: the registry must stay referentially stable, or
39
- // every hover would re-run the registration effect of every target on screen.
40
- const MouseRegistryContext = createContext(null);
41
- const MouseStateContext = createContext(null);
42
- export function MouseProvider({ source, children, }) {
43
- const targets = useRef(new Set());
44
- const pressed = useRef(null);
45
- const [hovered, setHovered] = useState(null);
46
- const [enabled, setEnabled] = useState(false);
47
- const register = useCallback((target) => {
48
- targets.current.add(target);
49
- return () => {
50
- targets.current.delete(target);
51
- if (pressed.current === target)
52
- pressed.current = null;
53
- setHovered((current) => (current === target ? null : current));
54
- };
55
- }, []);
56
- /** The innermost registered element under the pointer. */
57
- const hitTest = useCallback((x, y) => {
58
- let best = null;
59
- let bestArea = Number.POSITIVE_INFINITY;
60
- for (const target of targets.current) {
61
- const rect = measureElement(target.ref.current);
62
- if (!rect || rect.width <= 0 || rect.height <= 0)
63
- continue;
64
- if (!rectContains(rect, x, y))
65
- continue;
66
- const area = rect.width * rect.height;
67
- if (area < bestArea) {
68
- best = target;
69
- bestArea = area;
70
- }
71
- }
72
- return best;
73
- }, []);
74
- useEffect(() => {
75
- if (!source)
76
- return undefined;
77
- return source.subscribe((event) => {
78
- if (event.type === 'wheel')
79
- return;
80
- const target = hitTest(event.x, event.y);
81
- if (event.type === 'move') {
82
- // Only re-render when the pointer actually crosses into a different
83
- // element; any-motion tracking fires on every cell otherwise.
84
- setHovered((current) => (current === target ? current : target));
85
- return;
86
- }
87
- if (event.type === 'press') {
88
- if (event.button !== 'left')
89
- return;
90
- pressed.current = target;
91
- setHovered(target);
92
- return;
93
- }
94
- // Release: a click counts only when press and release share a target,
95
- // so dragging off a row cancels it the way a button should.
96
- if (pressed.current && pressed.current === target)
97
- target?.onClick?.();
98
- pressed.current = null;
99
- });
100
- }, [source, hitTest]);
101
- useEffect(() => {
102
- const previous = hovered;
103
- previous?.onHover?.(true);
104
- return () => previous?.onHover?.(false);
105
- }, [hovered]);
106
- const enable = useCallback(() => {
107
- if (!source)
108
- return;
109
- source.enable();
110
- setEnabled(source.isEnabled());
111
- }, [source]);
112
- const disable = useCallback(() => {
113
- if (!source)
114
- return;
115
- source.disable();
116
- setEnabled(source.isEnabled());
117
- }, [source]);
118
- const registry = useMemo(() => ({ register }), [register]);
119
- const state = useMemo(() => ({
120
- supported: Boolean(source?.supported),
121
- enabled,
122
- enable,
123
- disable,
124
- }), [source, enabled, enable, disable]);
125
- return (_jsx(MouseRegistryContext.Provider, { value: registry, children: _jsx(MouseStateContext.Provider, { value: state, children: children }) }));
126
- }
127
- const KEYBOARD_ONLY = {
128
- supported: false,
129
- enabled: false,
130
- enable: () => { },
131
- disable: () => { },
132
- };
133
- export function useMouse() {
134
- // Tests mount screens without a provider; degrade to keyboard only rather
135
- // than crash the interface.
136
- return useContext(MouseStateContext) ?? KEYBOARD_ONLY;
137
- }
138
- /**
139
- * Make an element clickable and hoverable.
140
- *
141
- * Returns whether the pointer is currently over it, so callers can highlight
142
- * without tracking mouse state themselves.
143
- */
144
- export function useMouseTarget(ref, { onClick, disabled = false } = {}) {
145
- const context = useContext(MouseRegistryContext);
146
- const [hovered, setHovered] = useState(false);
147
- const handler = useRef(onClick);
148
- handler.current = onClick;
149
- const target = useMemo(() => ({
150
- ref,
151
- onClick: () => handler.current?.(),
152
- onHover: setHovered,
153
- }), [ref]);
154
- useEffect(() => {
155
- if (!context || disabled)
156
- return undefined;
157
- return context.register(target);
158
- }, [context, target, disabled]);
159
- useEffect(() => {
160
- if (disabled)
161
- setHovered(false);
162
- }, [disabled]);
163
- return hovered && !disabled;
164
- }
@@ -1,96 +0,0 @@
1
- // SGR mouse reporting: parsing and the escape sequences that turn it on.
2
- //
3
- // Written here rather than taken from a library because the available ones get
4
- // the hit test wrong: `@ink-tools/ink-mouse` compares a click against inclusive
5
- // bounds (`y >= top && y <= bottom`) while its rectangles are half-open, so a
6
- // one-row item at row N also swallows clicks on row N+1 and two neighbouring
7
- // rows both fire. In a vertical menu that reads as "the mouse selects the wrong
8
- // entry".
9
- const ESC = String.fromCharCode(27);
10
- /** Any-motion tracking (1003) so hover works without a button held, plus SGR
11
- * coordinates (1006) so columns past 223 still report correctly. */
12
- export const ENABLE_MOUSE = `${ESC}[?1000h${ESC}[?1002h${ESC}[?1003h${ESC}[?1006h`;
13
- export const DISABLE_MOUSE = `${ESC}[?1006l${ESC}[?1003l${ESC}[?1002l${ESC}[?1000l`;
14
- // ESC [ < button ; column ; row (M|m)
15
- const SGR_PATTERN = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])/;
16
- // The longest incomplete prefix worth holding on to; beyond this the input is
17
- // not a mouse report and must be passed through rather than swallowed.
18
- const MAX_PENDING = 32;
19
- function decodeButton(code, isRelease) {
20
- if (code & 64)
21
- return (code & 3) === 1 ? 'wheel-down' : 'wheel-up';
22
- if (isRelease)
23
- return 'none';
24
- switch (code & 3) {
25
- case 0:
26
- return 'left';
27
- case 1:
28
- return 'middle';
29
- case 2:
30
- return 'right';
31
- default:
32
- return 'none';
33
- }
34
- }
35
- function couldBeMousePrefix(value) {
36
- // Partial forms of "\x1b[<…" that may complete on the next chunk.
37
- return (`${ESC}[<`.startsWith(value.slice(0, 3)) &&
38
- value.length <= MAX_PENDING &&
39
- /^\x1b(\[(<[\d;]*)?)?$/.test(value));
40
- }
41
- /**
42
- * Separate mouse reports from ordinary key input.
43
- *
44
- * Ink's key parser knows nothing about mouse reports, so an unfiltered click
45
- * would reach `useInput` as a stray Escape and cancel whatever the user was
46
- * editing. Everything that is not a mouse report is passed through untouched.
47
- */
48
- export function splitMouseInput(buffer) {
49
- const events = [];
50
- let passthrough = '';
51
- let index = 0;
52
- while (index < buffer.length) {
53
- const escapeAt = buffer.indexOf(ESC, index);
54
- if (escapeAt === -1) {
55
- passthrough += buffer.slice(index);
56
- return { events, passthrough, pending: '' };
57
- }
58
- passthrough += buffer.slice(index, escapeAt);
59
- const rest = buffer.slice(escapeAt);
60
- const match = SGR_PATTERN.exec(rest);
61
- if (match) {
62
- const code = Number(match[1]);
63
- const isRelease = match[4] === 'm';
64
- const isMotion = (code & 32) !== 0;
65
- const isWheel = (code & 64) !== 0;
66
- events.push({
67
- type: isWheel ? 'wheel' : isMotion ? 'move' : isRelease ? 'release' : 'press',
68
- button: decodeButton(code, isRelease),
69
- x: Number(match[2]),
70
- y: Number(match[3]),
71
- shift: (code & 4) !== 0,
72
- alt: (code & 8) !== 0,
73
- ctrl: (code & 16) !== 0,
74
- });
75
- index = escapeAt + match[0].length;
76
- continue;
77
- }
78
- if (couldBeMousePrefix(rest)) {
79
- // Hold the fragment back; the rest of the report is in the next chunk.
80
- return { events, passthrough, pending: rest };
81
- }
82
- // A real escape sequence (arrow key, etc.) — hand it to the key parser.
83
- passthrough += ESC;
84
- index = escapeAt + 1;
85
- }
86
- return { events, passthrough, pending: '' };
87
- }
88
- /** Stateful wrapper that carries a partial sequence across chunk boundaries. */
89
- export function createMouseSplitter() {
90
- let pending = '';
91
- return (chunk) => {
92
- const result = splitMouseInput(pending + chunk);
93
- pending = result.pending;
94
- return { events: result.events, passthrough: result.passthrough };
95
- };
96
- }
@@ -1,79 +0,0 @@
1
- import { PassThrough } from 'node:stream';
2
- import { createMouseSplitter, DISABLE_MOUSE, ENABLE_MOUSE } from './protocol.js';
3
- /**
4
- * Read the real stdin, peel off mouse reports, and forward the rest to Ink.
5
- *
6
- * Ink and the mouse layer cannot both own stdin: whoever reads it first
7
- * consumes the bytes. So this reads it once, routes mouse reports to
8
- * subscribers, and republishes the remaining keystrokes on a stream that looks
9
- * enough like a TTY for Ink to drive.
10
- */
11
- export function createMouseSource(input = process.stdin, output = process.stdout) {
12
- const supported = Boolean(input.isTTY && output.isTTY);
13
- const listeners = new Set();
14
- const split = createMouseSplitter();
15
- const forwarded = new PassThrough();
16
- const onData = (chunk) => {
17
- const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
18
- const { events, passthrough } = split(text);
19
- for (const event of events) {
20
- for (const listener of listeners)
21
- listener(event);
22
- }
23
- if (passthrough.length > 0)
24
- forwarded.write(passthrough);
25
- };
26
- if (supported) {
27
- input.on('data', onData);
28
- // A 'data' listener only starts a stream nobody paused. The Windows color
29
- // probe reads stdin before the TUI and pauses it afterwards, and a paused
30
- // stdin delivers no keys and keeps nothing alive: the TUI exited at once.
31
- input.resume();
32
- }
33
- // Ink expects a TTY: it checks isTTY and drives raw mode itself. Delegate
34
- // those to the real stdin while it reads from our filtered copy.
35
- const stdin = forwarded;
36
- stdin.isTTY = input.isTTY;
37
- stdin.setRawMode = ((mode) => {
38
- input.setRawMode?.(mode);
39
- return stdin;
40
- });
41
- stdin.ref = (() => {
42
- input.ref?.();
43
- return stdin;
44
- });
45
- stdin.unref = (() => {
46
- input.unref?.();
47
- return stdin;
48
- });
49
- let enabled = false;
50
- const enable = () => {
51
- if (!supported || enabled)
52
- return;
53
- output.write(ENABLE_MOUSE);
54
- enabled = true;
55
- };
56
- const disable = () => {
57
- if (!enabled)
58
- return;
59
- output.write(DISABLE_MOUSE);
60
- enabled = false;
61
- };
62
- return {
63
- stdin,
64
- supported,
65
- isEnabled: () => enabled,
66
- enable,
67
- disable,
68
- subscribe(listener) {
69
- listeners.add(listener);
70
- return () => listeners.delete(listener);
71
- },
72
- dispose() {
73
- disable();
74
- listeners.clear();
75
- if (supported)
76
- input.off('data', onData);
77
- },
78
- };
79
- }
@@ -1,172 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
3
- import { Box, Text, useInput } from 'ink';
4
- import { theme } from '../theme.js';
5
- import { Message, Panel, Row, Selectable } from '../components/common.js';
6
- import { canSelfUpdate, cliTarballUrl, checkForUpdate, configPath, currentVersion, detectLocale, installKind, installedRelayVersion, performUpdate, runsLocalRelay, saveDraft, setField, stateDir, whileServicesStopped, } from '../api.js';
7
- /** `https://registry.npmmirror.com` → `registry.npmmirror.com`, for one line of text. */
8
- const sourceHost = (source) => source.replace(/^https?:\/\//, '');
9
- export function About({ ctx }) {
10
- const { t, draft } = ctx;
11
- const [selected, setSelected] = useState('auto');
12
- const [update, setUpdate] = useState({ phase: 'idle' });
13
- /** Why the install will come from somewhere other than where some sources said. */
14
- const [sourceNote, setSourceNote] = useState(null);
15
- const checkRef = useState(() => ({ current: { source: '', sources: [] } }))[0];
16
- const detected = detectLocale({ preference: 'auto' });
17
- const languageOptions = [
18
- { id: 'auto', label: t('about.languageAuto', { detected }) },
19
- { id: 'zh', label: t('about.languageZh') },
20
- { id: 'en', label: t('about.languageEn') },
21
- ];
22
- // The update row is a selectable option alongside the languages, so one set
23
- // of arrow keys drives the whole screen.
24
- const updatable = canSelfUpdate();
25
- const updateLabel = (() => {
26
- switch (update.phase) {
27
- case 'checking':
28
- return t('update.checking');
29
- case 'current':
30
- return t('update.upToDate', { version: update.latest });
31
- case 'available':
32
- return t('update.available', { version: update.latest });
33
- case 'updating':
34
- return update.attempt > 1
35
- ? t('update.updatingRetry', { version: update.latest, attempt: update.attempt })
36
- : t('update.updating', { version: update.latest });
37
- case 'done':
38
- return t('update.done', { version: update.latest });
39
- case 'error':
40
- return t(update.messageKey, update.params);
41
- default:
42
- return t('update.check');
43
- }
44
- })();
45
- const options = [...languageOptions, { id: 'update', label: updateLabel }];
46
- /** Show a check's answer, whether this screen asked or the TUI did on opening. */
47
- const applyCheck = (result) => {
48
- checkRef.current = { source: result.source ?? '', sources: result.sources ?? [] };
49
- // A registry that has not synced the release yet used to be the whole
50
- // answer. It is still asked, and said to be behind.
51
- const behind = result.behind ?? [];
52
- setSourceNote(result.updateAvailable && behind.length > 0 && result.source
53
- ? {
54
- registries: behind
55
- .map((entry) => `${sourceHost(entry.source)} ${entry.version ?? ''}`.trim())
56
- .join(', '),
57
- source: sourceHost(result.source),
58
- }
59
- : null);
60
- setUpdate(result.updateAvailable
61
- ? { phase: 'available', latest: result.latest }
62
- : { phase: 'current', latest: result.latest });
63
- };
64
- // The TUI already asked when it opened; start from that answer.
65
- // biome-ignore lint/correctness/useExhaustiveDependencies: only a new answer from the opening check matters, not each render's applyCheck or phase
66
- useEffect(() => {
67
- if (ctx.updateCheck?.ok && update.phase === 'idle')
68
- applyCheck(ctx.updateCheck);
69
- }, [ctx.updateCheck]);
70
- const runCheck = async () => {
71
- setUpdate({ phase: 'checking' });
72
- setSourceNote(null);
73
- const result = await checkForUpdate();
74
- if (!result.ok) {
75
- setUpdate({ phase: 'error', messageKey: result.errorKey ?? 'update.errorNetwork' });
76
- // Which sources were tried, and what each of them said. Without this
77
- // the row reads "could not reach npm registry" on a machine where
78
- // `npm install` works perfectly, and there is nothing to act on.
79
- const { message } = result;
80
- if (message)
81
- ctx.notify((t) => t('update.errorNetworkDetail', { message }), 'error');
82
- return;
83
- }
84
- ctx.setUpdateCheck(result);
85
- applyCheck(result);
86
- };
87
- const runUpdate = async (latest) => {
88
- setUpdate({ phase: 'updating', latest, attempt: 1 });
89
- const result = await whileServicesStopped(ctx.config, () => performUpdate({
90
- source: checkRef.current.source,
91
- sources: checkRef.current.sources,
92
- version: latest,
93
- onAttempt: ({ attempt }) => setUpdate({ phase: 'updating', latest, attempt }),
94
- }));
95
- if (!result.ok) {
96
- const params = {
97
- version: latest,
98
- installed: result.installed ?? '',
99
- url: cliTarballUrl(latest),
100
- };
101
- setUpdate({ phase: 'error', messageKey: result.errorKey ?? 'update.errorFailed', params });
102
- // npm's own words: "update failed" alone is what left this unfixable.
103
- ctx.notify((t) => result.summary
104
- ? t('update.errorFailedDetail', { message: result.summary })
105
- : t(result.errorKey ?? 'update.errorFailed', params), 'error');
106
- return;
107
- }
108
- setUpdate({ phase: 'done', latest });
109
- setSourceNote(null);
110
- // Installed: the line under the title has nothing left to announce.
111
- ctx.setUpdateCheck(null);
112
- ctx.notify((t) => t('update.restartHint'), 'success');
113
- };
114
- /**
115
- * One row, two meanings: check first, then install what the check found.
116
- * Refusing to self-update is reported rather than hidden, so a checkout does
117
- * not look like a silently broken button.
118
- */
119
- const activateUpdate = () => {
120
- if (update.phase === 'checking' || update.phase === 'updating')
121
- return;
122
- if (update.phase === 'available') {
123
- if (!updatable) {
124
- setUpdate({ phase: 'error', messageKey: `update.cannot.${installKind()}` });
125
- return;
126
- }
127
- void runUpdate(update.latest);
128
- return;
129
- }
130
- void runCheck();
131
- };
132
- const choose = (language) => {
133
- const result = setField(draft, 'language', language);
134
- const { errorKey } = result;
135
- if (errorKey) {
136
- ctx.notify((t) => t(errorKey), 'error');
137
- return;
138
- }
139
- ctx.updateDraft(result.draft);
140
- try {
141
- saveDraft(result.draft);
142
- ctx.reloadConfig();
143
- ctx.notify((t) => t('common.saved', { path: configPath() }), 'success');
144
- }
145
- catch (error) {
146
- ctx.notify((t) => t('error.saveFailed', { message: error.message }), 'error');
147
- }
148
- };
149
- const activate = (id) => {
150
- if (id === 'update') {
151
- activateUpdate();
152
- return;
153
- }
154
- choose(id);
155
- };
156
- useInput((_input, key) => {
157
- const index = options.findIndex((option) => option.id === selected);
158
- if (key.upArrow)
159
- setSelected(options[(index - 1 + options.length) % options.length].id);
160
- else if (key.downArrow)
161
- setSelected(options[(index + 1) % options.length].id);
162
- else if (key.return)
163
- activate(selected);
164
- }, { isActive: ctx.editingId === null });
165
- return (_jsxs(Panel, { title: t('about.title'), children: [_jsx(Text, { color: theme.muted, children: t('about.language') }), _jsx(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: languageOptions.map((option) => (_jsx(Selectable, { selected: selected === option.id, onSelect: () => {
166
- setSelected(option.id);
167
- choose(option.id);
168
- }, onHover: () => setSelected(option.id), children: `${option.label}${draft.ui.language === option.id ? ' ✓' : ''}` }, option.id))) }), _jsx(Text, { color: theme.muted, children: t('update.title') }), _jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [_jsx(Selectable, { selected: selected === 'update', onSelect: () => {
169
- setSelected('update');
170
- activateUpdate();
171
- }, onHover: () => setSelected('update'), children: updateLabel }), sourceNote ? (_jsx(Text, { color: theme.muted, children: ` ${t('update.mirrorBehind', sourceNote)}` })) : null] }), _jsx(Row, { label: t('about.version'), children: _jsx(Text, { color: theme.muted, children: currentVersion() }) }), runsLocalRelay(ctx.config) ? (_jsx(Row, { label: t('about.relayPackage'), children: _jsx(Text, { color: theme.muted, children: installedRelayVersion() ?? '?' }) })) : null, _jsx(Row, { label: t('about.configPath'), children: _jsx(Text, { color: theme.muted, children: configPath() }) }), _jsx(Row, { label: t('about.statePath'), children: _jsx(Text, { color: theme.muted, children: stateDir() }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: t('about.docs') }) }), _jsx(Message, { text: ctx.message?.text ?? null, level: ctx.message?.level ?? 'info' })] }));
172
- }