@morndream/mihomo-tui 0.5.0-rc.0

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 (145) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/LICENSE +21 -0
  3. package/README.md +462 -0
  4. package/bin/mihomo-tui +34 -0
  5. package/dist/App.js +161 -0
  6. package/dist/App.js.map +1 -0
  7. package/dist/api/client.js +239 -0
  8. package/dist/api/client.js.map +1 -0
  9. package/dist/api/status.js +43 -0
  10. package/dist/api/status.js.map +1 -0
  11. package/dist/api/stream.js +139 -0
  12. package/dist/api/stream.js.map +1 -0
  13. package/dist/api/types.js +6 -0
  14. package/dist/api/types.js.map +1 -0
  15. package/dist/cli.js +221 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/commands/conn.js +122 -0
  18. package/dist/commands/conn.js.map +1 -0
  19. package/dist/commands/logs.js +99 -0
  20. package/dist/commands/logs.js.map +1 -0
  21. package/dist/commands/output.js +177 -0
  22. package/dist/commands/output.js.map +1 -0
  23. package/dist/commands/provider.js +140 -0
  24. package/dist/commands/provider.js.map +1 -0
  25. package/dist/commands/proxy.js +161 -0
  26. package/dist/commands/proxy.js.map +1 -0
  27. package/dist/commands/proxyEnv.js +193 -0
  28. package/dist/commands/proxyEnv.js.map +1 -0
  29. package/dist/commands/rules.js +87 -0
  30. package/dist/commands/rules.js.map +1 -0
  31. package/dist/commands/status.js +75 -0
  32. package/dist/commands/status.js.map +1 -0
  33. package/dist/components/ConfirmDialog.js +31 -0
  34. package/dist/components/ConfirmDialog.js.map +1 -0
  35. package/dist/components/DelayBadge.js +27 -0
  36. package/dist/components/DelayBadge.js.map +1 -0
  37. package/dist/components/InputDialog.js +325 -0
  38. package/dist/components/InputDialog.js.map +1 -0
  39. package/dist/components/ListDialog.js +86 -0
  40. package/dist/components/ListDialog.js.map +1 -0
  41. package/dist/components/ProgressDialog.js +34 -0
  42. package/dist/components/ProgressDialog.js.map +1 -0
  43. package/dist/components/ScrollList.js +26 -0
  44. package/dist/components/ScrollList.js.map +1 -0
  45. package/dist/components/StatusBar.js +58 -0
  46. package/dist/components/StatusBar.js.map +1 -0
  47. package/dist/components/TextEditor.js +112 -0
  48. package/dist/components/TextEditor.js.map +1 -0
  49. package/dist/components/textEditorModel.js +154 -0
  50. package/dist/components/textEditorModel.js.map +1 -0
  51. package/dist/config/manager.js +296 -0
  52. package/dist/config/manager.js.map +1 -0
  53. package/dist/config/preserveGroups.js +9 -0
  54. package/dist/config/preserveGroups.js.map +1 -0
  55. package/dist/config/ruleDocument.js +88 -0
  56. package/dist/config/ruleDocument.js.map +1 -0
  57. package/dist/config/ruleService.js +256 -0
  58. package/dist/config/ruleService.js.map +1 -0
  59. package/dist/config/ruleValidation.js +98 -0
  60. package/dist/config/ruleValidation.js.map +1 -0
  61. package/dist/config/service.js +63 -0
  62. package/dist/config/service.js.map +1 -0
  63. package/dist/config/settingsService.js +162 -0
  64. package/dist/config/settingsService.js.map +1 -0
  65. package/dist/config/skeleton.js +324 -0
  66. package/dist/config/skeleton.js.map +1 -0
  67. package/dist/config/subscriptionService.js +284 -0
  68. package/dist/config/subscriptionService.js.map +1 -0
  69. package/dist/config/subscriptions.js +200 -0
  70. package/dist/config/subscriptions.js.map +1 -0
  71. package/dist/config/types.js +5 -0
  72. package/dist/config/types.js.map +1 -0
  73. package/dist/config.js +101 -0
  74. package/dist/config.js.map +1 -0
  75. package/dist/hooks/useProviders.js +138 -0
  76. package/dist/hooks/useProviders.js.map +1 -0
  77. package/dist/hooks/useProxies.js +298 -0
  78. package/dist/hooks/useProxies.js.map +1 -0
  79. package/dist/hooks/useStream.js +127 -0
  80. package/dist/hooks/useStream.js.map +1 -0
  81. package/dist/kernel/installer.js +317 -0
  82. package/dist/kernel/installer.js.map +1 -0
  83. package/dist/kernel/releases.js +111 -0
  84. package/dist/kernel/releases.js.map +1 -0
  85. package/dist/kernel/sources.js +98 -0
  86. package/dist/kernel/sources.js.map +1 -0
  87. package/dist/rules/editor.js +181 -0
  88. package/dist/rules/editor.js.map +1 -0
  89. package/dist/rules/matcher.js +83 -0
  90. package/dist/rules/matcher.js.map +1 -0
  91. package/dist/rules/model.js +25 -0
  92. package/dist/rules/model.js.map +1 -0
  93. package/dist/rules/runtime.js +46 -0
  94. package/dist/rules/runtime.js.map +1 -0
  95. package/dist/rules/types.js +10 -0
  96. package/dist/rules/types.js.map +1 -0
  97. package/dist/ui/FooterLine.js +62 -0
  98. package/dist/ui/FooterLine.js.map +1 -0
  99. package/dist/ui/Panel.js +47 -0
  100. package/dist/ui/Panel.js.map +1 -0
  101. package/dist/ui/RowIndicator.js +25 -0
  102. package/dist/ui/RowIndicator.js.map +1 -0
  103. package/dist/ui/SectionHeader.js +14 -0
  104. package/dist/ui/SectionHeader.js.map +1 -0
  105. package/dist/ui/StatusDot.js +15 -0
  106. package/dist/ui/StatusDot.js.map +1 -0
  107. package/dist/ui/TopBar.js +81 -0
  108. package/dist/ui/TopBar.js.map +1 -0
  109. package/dist/ui/exitGuard.js +20 -0
  110. package/dist/ui/exitGuard.js.map +1 -0
  111. package/dist/ui/keyCapture.js +28 -0
  112. package/dist/ui/keyCapture.js.map +1 -0
  113. package/dist/ui/layout.js +32 -0
  114. package/dist/ui/layout.js.map +1 -0
  115. package/dist/ui/rowIndicator.js +42 -0
  116. package/dist/ui/rowIndicator.js.map +1 -0
  117. package/dist/ui/tableHeader.js +33 -0
  118. package/dist/ui/tableHeader.js.map +1 -0
  119. package/dist/ui/theme.js +47 -0
  120. package/dist/ui/theme.js.map +1 -0
  121. package/dist/ui/tone.js +131 -0
  122. package/dist/ui/tone.js.map +1 -0
  123. package/dist/version.js +27 -0
  124. package/dist/version.js.map +1 -0
  125. package/dist/views/Conns.js +107 -0
  126. package/dist/views/Conns.js.map +1 -0
  127. package/dist/views/Logs.js +89 -0
  128. package/dist/views/Logs.js.map +1 -0
  129. package/dist/views/Providers.js +392 -0
  130. package/dist/views/Providers.js.map +1 -0
  131. package/dist/views/Proxies.js +233 -0
  132. package/dist/views/Proxies.js.map +1 -0
  133. package/dist/views/RuleEditor.js +356 -0
  134. package/dist/views/RuleEditor.js.map +1 -0
  135. package/dist/views/Rules.js +286 -0
  136. package/dist/views/Rules.js.map +1 -0
  137. package/dist/views/Settings.js +734 -0
  138. package/dist/views/Settings.js.map +1 -0
  139. package/dist/views/groupDisplay.js +53 -0
  140. package/dist/views/groupDisplay.js.map +1 -0
  141. package/dist/views/subscriptionFields.js +59 -0
  142. package/dist/views/subscriptionFields.js.map +1 -0
  143. package/dist/views/yamlFile.js +32 -0
  144. package/dist/views/yamlFile.js.map +1 -0
  145. package/package.json +66 -0
package/dist/App.js ADDED
@@ -0,0 +1,161 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Ink 根组件:六个标签页 + 全局快捷键 + 底部状态栏。
4
+ *
5
+ * 退出必须显式 process.exit —— mihomo 不回应 WebSocket close 帧,
6
+ * 句柄不释放,事件循环永不为空(SPEC 3.6)。
7
+ */
8
+ import { useEffect, useState } from 'react';
9
+ import { Box, Text, useApp, useInput, useStdout } from 'ink';
10
+ import { MihomoClient } from './api/client.js';
11
+ import { useProxies } from './hooks/useProxies.js';
12
+ import { useProviders } from './hooks/useProviders.js';
13
+ import { useConnectionsStream, useLogStream, useStatusStream } from './hooks/useStream.js';
14
+ import { StatusBar, stateTone } from './components/StatusBar.js';
15
+ import { TopBar } from './ui/TopBar.js';
16
+ import { Panel } from './ui/Panel.js';
17
+ import { FooterLine } from './ui/FooterLine.js';
18
+ import { colors } from './ui/theme.js';
19
+ import { keysCaptured } from './ui/keyCapture.js';
20
+ import { requestGuardedExit } from './ui/exitGuard.js';
21
+ import { ConfirmDialog } from './components/ConfirmDialog.js';
22
+ import { ProxiesView } from './views/Proxies.js';
23
+ import { ProvidersView } from './views/Providers.js';
24
+ import { RulesView } from './views/Rules.js';
25
+ import { LogsView } from './views/Logs.js';
26
+ import { ConnsView } from './views/Conns.js';
27
+ import { SettingsView } from './views/Settings.js';
28
+ const TABS = ['节点', '订阅', '规则', '日志', '连接', '设置'];
29
+ const MODE_NAMES = { rule: '规则', global: '全局', direct: '直连' };
30
+ export function App({ config, version, mode }) {
31
+ const { exit } = useApp();
32
+ const { stdout } = useStdout();
33
+ const [tab, setTab] = useState(0);
34
+ const [message, setMessage] = useState('');
35
+ const [logLevel, setLogLevel] = useState('info');
36
+ const [currentMode, setCurrentMode] = useState(mode || 'rule');
37
+ const [tick, setTick] = useState(0);
38
+ // 退出确认层:主界面 ESC 先弹确认(分级退出,spec 2026-09-13)
39
+ const [confirmExit, setConfirmExit] = useState(false);
40
+ const [size, setSize] = useState({
41
+ columns: stdout?.columns ?? 80,
42
+ rows: stdout?.rows ?? 24,
43
+ });
44
+ // 终端尺寸变化时重算布局(窄屏降级为单栏)
45
+ useEffect(() => {
46
+ if (!stdout)
47
+ return;
48
+ const onResize = () => setSize({ columns: stdout.columns, rows: stdout.rows });
49
+ stdout.on('resize', onResize);
50
+ return () => {
51
+ stdout.off('resize', onResize);
52
+ };
53
+ }, [stdout]);
54
+ // 提示信息 4 秒后自动消失
55
+ useEffect(() => {
56
+ if (!message)
57
+ return;
58
+ const timer = setTimeout(() => setMessage(''), 4000);
59
+ return () => clearTimeout(timer);
60
+ }, [message]);
61
+ const proxies = useProxies(config);
62
+ const providers = useProviders(config);
63
+ const status = useStatusStream(config);
64
+ // 日志与连接流只在对应标签页可见时渲染,避免在其他页被高频数据带着重渲染
65
+ const logs = useLogStream(config, logLevel, tab === 3);
66
+ const connections = useConnectionsStream(config, tab === 4);
67
+ // spinner 心跳只在真的有东西转的时候才跑 —— 否则整个 App 每 120ms 重渲染一次,
68
+ // 纯属白烧 CPU 与制造垃圾
69
+ const spinning = Boolean(proxies.testingGroup) || providers.providers.some((p) => p.updating);
70
+ useEffect(() => {
71
+ if (!spinning)
72
+ return;
73
+ const timer = setInterval(() => setTick((t) => t + 1), 120);
74
+ return () => clearInterval(timer);
75
+ }, [spinning]);
76
+ useInput((input, key) => {
77
+ // Rule transactions register a separate guard; ordinary sessions retain hard exit.
78
+ if (key.ctrl && input === 'c') {
79
+ if (!requestGuardedExit(exit))
80
+ exit();
81
+ return;
82
+ }
83
+ // 内层优先:对话框/文本编辑态/二次确认态捕获按键时全局键一律让路,
84
+ // 否则对话框里的 ESC 会被这里再消费一次把整只 TUI 退掉(广播式 useInput)
85
+ if (keysCaptured()) {
86
+ return;
87
+ }
88
+ // 日志页在编辑过滤词时会吞掉普通按键,这里只处理明确的全局键
89
+ if (key.tab) {
90
+ setTab((t) => (t + 1) % TABS.length);
91
+ return;
92
+ }
93
+ // [ 向左切一个标签、] 向右切(循环式,与 Tab 同语义;TUI 惯例键位)
94
+ if (input === '[') {
95
+ setTab((t) => (t + TABS.length - 1) % TABS.length);
96
+ return;
97
+ }
98
+ if (input === ']') {
99
+ setTab((t) => (t + 1) % TABS.length);
100
+ return;
101
+ }
102
+ // 数字键 1-6 直接切换到对应标签(1=节点, 2=订阅, 3=规则, 4=日志, 5=连接, 6=设置)
103
+ if (input >= '1' && input <= '6') {
104
+ const index = parseInt(input, 10) - 1;
105
+ if (index < TABS.length) {
106
+ setTab(index);
107
+ }
108
+ return;
109
+ }
110
+ if (key.escape) {
111
+ // 分级退出:主界面 ESC 不再直接退,先弹退出确认(Enter 才真退)
112
+ setConfirmExit(true);
113
+ return;
114
+ }
115
+ // m 键切换模式: rule -> global -> direct -> rule
116
+ // 规则页 m 留给本地标记,不能同时切换内核运行模式。
117
+ if (input === 'm' && tab !== 2) {
118
+ const modes = ['rule', 'global', 'direct'];
119
+ const currentIndex = modes.indexOf(currentMode);
120
+ const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % modes.length;
121
+ const nextMode = modes[nextIndex];
122
+ const client = new MihomoClient(config);
123
+ client
124
+ .setMode(nextMode)
125
+ .then(() => {
126
+ setCurrentMode(nextMode);
127
+ const modeNames = {
128
+ rule: '规则',
129
+ global: '全局',
130
+ direct: '直连'
131
+ };
132
+ setMessage(`已切换到 ${modeNames[nextMode]} 模式`);
133
+ })
134
+ .catch((err) => {
135
+ setMessage(`切换模式失败: ${err.message}`);
136
+ });
137
+ }
138
+ });
139
+ const apiPort = (() => {
140
+ try {
141
+ return new URL(new MihomoClient(config).api).port || '19090';
142
+ }
143
+ catch {
144
+ return '19090';
145
+ }
146
+ })();
147
+ // 布局预算:顶栏卡片 3(标题线+内容+底边)+ 键提示/消息共用 1 + 状态栏 2
148
+ const bodyHeight = Math.max(6, size.rows - 6);
149
+ const disconnected = status.state === 'closed' || status.state === 'reconnecting';
150
+ // 连接中视同已连接(沿用既有行为;真正的断开/重连才降级)
151
+ const shownState = disconnected ? status.state : 'open';
152
+ return (_jsxs(Box, { flexDirection: "column", width: size.columns, minHeight: size.rows, children: [_jsx(TopBar, { tabs: TABS, active: tab, modeLabel: MODE_NAMES[currentMode] ?? currentMode, apiPort: apiPort, statusTone: stateTone(shownState), width: size.columns }), _jsx(Box, { paddingX: 1, height: 1, children: message ? (_jsx(Text, { color: colors.success, children: ` ${message}` })) : (_jsx(FooterLine, { hints: [
153
+ { key: 'Tab/[]', label: '切换' },
154
+ { key: 'M', label: '切模式' },
155
+ { key: 'ESC', label: '退出' },
156
+ ], width: size.columns - 2 })) }), proxies.error && tab === 0 ? (_jsx(Panel, { danger: true, title: "\u5185\u6838\u4E0D\u53EF\u8FBE", width: size.columns, children: _jsx(Text, { color: colors.danger, children: `${proxies.error} 请检查 systemctl --user status mihomo` }) })) : null, confirmExit ? (
157
+ // 退出确认居中独占 body(cc-switch 确认卡片语言:边框标题「退出」、
158
+ // 键帽上置、文案居中、大留白)——模态期间页面内容隐藏,按键由 ConfirmDialog 独占
159
+ _jsx(Box, { flexDirection: "column", flexGrow: 1, justifyContent: "center", alignItems: "center", children: _jsx(ConfirmDialog, { borderTitle: "\u9000\u51FA", message: ['确认退出?', '内核服务不受影响,仍在后台运行'], enterConfirms: true, width: Math.min(44, size.columns - 4), onConfirm: exit, onCancel: () => setConfirmExit(false) }) })) : (_jsxs(Box, { flexDirection: "column", flexGrow: 1, height: bodyHeight, children: [tab === 0 ? (_jsx(ProxiesView, { proxies: proxies, height: bodyHeight, width: size.columns, tick: tick, active: tab === 0, onMessage: setMessage })) : null, tab === 1 ? (_jsx(ProvidersView, { providers: providers, config: config, onChanged: () => proxies.refresh(), height: bodyHeight, width: size.columns, tick: tick, active: tab === 1, onMessage: setMessage })) : null, tab === 2 ? (_jsx(RulesView, { config: config, height: bodyHeight, width: size.columns, active: tab === 2, onMessage: setMessage })) : null, tab === 3 ? (_jsx(LogsView, { logs: logs, level: logLevel, onLevelChange: setLogLevel, height: bodyHeight, width: size.columns, active: tab === 3, onMessage: setMessage })) : null, tab === 4 ? (_jsx(ConnsView, { config: config, data: connections.data, height: bodyHeight, width: size.columns, active: tab === 4, onMessage: setMessage })) : null, tab === 5 ? (_jsx(SettingsView, { config: config, version: version, height: bodyHeight, width: size.columns, active: tab === 5, onMessage: setMessage })) : null] })), _jsx(StatusBar, { version: version, mode: currentMode, up: status.up, down: status.down, upTotal: status.upTotal, downTotal: status.downTotal, memory: status.memory, state: shownState, currentNode: proxies.currentNode })] }));
160
+ }
161
+ //# sourceMappingURL=App.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"App.js","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,239 @@
1
+ /** 内核连不上(服务未启动 / 端口不对),对应退出码 3 */
2
+ export class KernelUnreachableError extends Error {
3
+ api;
4
+ cause;
5
+ constructor(api, cause) {
6
+ super(`无法连接 mihomo 控制口 ${api}`);
7
+ this.api = api;
8
+ this.cause = cause;
9
+ this.name = 'KernelUnreachableError';
10
+ }
11
+ }
12
+ /** HTTP 状态码错误(401 密钥错误、404 组名不存在等) */
13
+ export class HttpStatusError extends Error {
14
+ status;
15
+ path;
16
+ body;
17
+ constructor(status, path, body) {
18
+ super(`HTTP ${status} ${path}${body ? `: ${body}` : ''}`);
19
+ this.status = status;
20
+ this.path = path;
21
+ this.body = body;
22
+ this.name = 'HttpStatusError';
23
+ }
24
+ }
25
+ /**
26
+ * 内核返回 {"message": "..."} 的业务错误。
27
+ * 典型来源是延迟测试失败 —— 属于「节点不可用」这一正常结果,调用方不得当成崩溃。
28
+ */
29
+ export class ApiBusinessError extends Error {
30
+ path;
31
+ constructor(path, message) {
32
+ super(message);
33
+ this.path = path;
34
+ this.name = 'ApiBusinessError';
35
+ }
36
+ }
37
+ const DEFAULT_TIMEOUT = 8000;
38
+ export class MihomoClient {
39
+ api;
40
+ secret;
41
+ testUrl;
42
+ testTimeout;
43
+ constructor(config) {
44
+ this.api = config.api.replace(/\/+$/, '');
45
+ this.secret = config.secret;
46
+ this.testUrl = config.testUrl;
47
+ this.testTimeout = config.testTimeout;
48
+ }
49
+ /** 拼 URL:path 由调用方给出且各段已编码,query 交给 URLSearchParams */
50
+ buildUrl(path, query) {
51
+ const url = new URL(`${this.api}${path}`);
52
+ for (const [key, value] of Object.entries(query ?? {})) {
53
+ if (value !== undefined)
54
+ url.searchParams.set(key, String(value));
55
+ }
56
+ return url.toString();
57
+ }
58
+ async request(path, options = {}) {
59
+ const { method = 'GET', query, body, timeout = DEFAULT_TIMEOUT } = options;
60
+ const headers = {};
61
+ if (this.secret)
62
+ headers.Authorization = `Bearer ${this.secret}`;
63
+ if (body !== undefined)
64
+ headers['Content-Type'] = 'application/json';
65
+ let response;
66
+ try {
67
+ response = await fetch(this.buildUrl(path, query), {
68
+ method,
69
+ headers,
70
+ body: body === undefined ? undefined : JSON.stringify(body),
71
+ signal: AbortSignal.timeout(timeout),
72
+ });
73
+ }
74
+ catch (err) {
75
+ // fetch 只在网络层失败或超时时抛异常,HTTP 错误状态不抛
76
+ throw new KernelUnreachableError(this.api, err);
77
+ }
78
+ const text = await response.text();
79
+ const parsed = text ? safeJsonParse(text) : undefined;
80
+ if (!response.ok) {
81
+ const message = isErrorBody(parsed) ? parsed.message : '';
82
+ // 401 与 404 是调用方用错了(密钥/名称),归入 HTTP 错误。
83
+ // 其余带 message 的状态码是内核对本次操作的明确表态,属业务错误:
84
+ // 503 {"message":"An error occurred in the delay test"} —— 节点不可用(实测)
85
+ // 400 {"message":"Selector update error: proxy not exist"} —— 节点名不在组内(实测)
86
+ if (message && ![401, 403, 404, 405].includes(response.status)) {
87
+ throw new ApiBusinessError(path, message);
88
+ }
89
+ throw new HttpStatusError(response.status, path, message || text.slice(0, 200));
90
+ }
91
+ return parsed;
92
+ }
93
+ // ---- 只读 ----
94
+ version() {
95
+ return this.request('/version');
96
+ }
97
+ configs() {
98
+ return this.request('/configs');
99
+ }
100
+ async proxies() {
101
+ const data = await this.request('/proxies');
102
+ return data.proxies ?? {};
103
+ }
104
+ proxy(name) {
105
+ return this.request(`/proxies/${encodeURIComponent(name)}`);
106
+ }
107
+ async providers() {
108
+ const data = await this.request('/providers/proxies');
109
+ return data.providers ?? {};
110
+ }
111
+ connections() {
112
+ return this.request('/connections');
113
+ }
114
+ /** 当前生效的规则列表(按匹配优先级排序) */
115
+ async rules() {
116
+ const data = await this.request('/rules');
117
+ return data.rules ?? [];
118
+ }
119
+ /** 规则集 provider 列表 */
120
+ async ruleProviders() {
121
+ const data = await this.request('/providers/rules');
122
+ return data.providers ?? {};
123
+ }
124
+ /** 触发规则集 provider 重新拉取 */
125
+ updateRuleProvider(name) {
126
+ return this.request(`/providers/rules/${encodeURIComponent(name)}`, {
127
+ method: 'PUT',
128
+ timeout: 60_000,
129
+ });
130
+ }
131
+ // ---- 运行时写操作(均不触碰配置文件)----
132
+ /** index 必须来自最新 GET /rules。 */
133
+ setRuleDisabled(index, disabled) {
134
+ if (!Number.isInteger(index) || index < 0)
135
+ throw new Error('规则索引必须为非负整数');
136
+ return this.request('/rules/disable', {
137
+ method: 'PATCH',
138
+ body: { [index]: disabled },
139
+ });
140
+ }
141
+ /**
142
+ * 切换代理组当前选中节点。
143
+ * 实测:对 Selector 组会改 now;对 URLTest/Fallback 组则写 fixed 字段
144
+ * 把该组钉死在指定节点(now 仍由测速决定,直到用 unfixProxy 解除)。
145
+ */
146
+ selectProxy(group, name) {
147
+ return this.request(`/proxies/${encodeURIComponent(group)}`, {
148
+ method: 'PUT',
149
+ body: { name },
150
+ });
151
+ }
152
+ /** 解除 URLTest/Fallback 组的 fixed 钉选,恢复自动测速选路 */
153
+ unfixProxy(group) {
154
+ return this.request(`/proxies/${encodeURIComponent(group)}`, { method: 'DELETE' });
155
+ }
156
+ /** 单节点延迟测试。节点不可用时内核返回 503 + message,抛 ApiBusinessError */
157
+ testProxyDelay(name, url = this.testUrl, timeout = this.testTimeout) {
158
+ return this.request(`/proxies/${encodeURIComponent(name)}/delay`, {
159
+ query: { url, timeout },
160
+ timeout: timeout + 2000,
161
+ });
162
+ }
163
+ /** 整组延迟测试,返回 {节点名: 延迟},失效节点不出现在结果里 */
164
+ testGroupDelay(group, url = this.testUrl, timeout = this.testTimeout) {
165
+ return this.request(`/group/${encodeURIComponent(group)}/delay`, {
166
+ query: { url, timeout },
167
+ timeout: timeout + 2000,
168
+ });
169
+ }
170
+ /** 更新指定订阅(provider) */
171
+ updateProvider(name) {
172
+ return this.request(`/providers/proxies/${encodeURIComponent(name)}`, {
173
+ method: 'PUT',
174
+ timeout: 60_000,
175
+ });
176
+ }
177
+ /** 触发 provider 健康检查 */
178
+ healthCheckProvider(name) {
179
+ return this.request(`/providers/proxies/${encodeURIComponent(name)}/healthcheck`, {
180
+ timeout: 60_000,
181
+ });
182
+ }
183
+ closeConnection(id) {
184
+ return this.request(`/connections/${encodeURIComponent(id)}`, { method: 'DELETE' });
185
+ }
186
+ closeAllConnections() {
187
+ return this.request('/connections', { method: 'DELETE' });
188
+ }
189
+ /**
190
+ * 切换运行模式:rule(规则)、global(全局)、direct(直连)
191
+ */
192
+ setMode(mode) {
193
+ return this.request('/configs', {
194
+ method: 'PATCH',
195
+ body: { mode },
196
+ });
197
+ }
198
+ /**
199
+ * 热重载配置。内核自行从磁盘重新读取,本程序不写入任何内容。
200
+ * path 为空时内核用启动时的配置路径。
201
+ */
202
+ reload(path = '') {
203
+ return this.request('/configs', {
204
+ method: 'PUT',
205
+ query: { force: 'false' },
206
+ body: { path, payload: '' },
207
+ timeout: 30_000,
208
+ });
209
+ }
210
+ /** WebSocket 地址;secret 只能走 query 参数(WebSocket 无法自定义请求头) */
211
+ wsUrl(path, query = {}) {
212
+ const url = new URL(`${this.api}${path}`);
213
+ url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
214
+ for (const [key, value] of Object.entries(query)) {
215
+ if (value !== undefined)
216
+ url.searchParams.set(key, String(value));
217
+ }
218
+ if (this.secret)
219
+ url.searchParams.set('token', this.secret);
220
+ return url.toString();
221
+ }
222
+ logsUrl(level = 'info') {
223
+ return this.wsUrl('/logs', { level });
224
+ }
225
+ }
226
+ function safeJsonParse(text) {
227
+ try {
228
+ return JSON.parse(text);
229
+ }
230
+ catch {
231
+ return undefined;
232
+ }
233
+ }
234
+ function isErrorBody(value) {
235
+ return (typeof value === 'object' &&
236
+ value !== null &&
237
+ typeof value.message === 'string');
238
+ }
239
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 取节点最近一次延迟。
3
+ * 优先用 extra[testUrl] 下的记录(同一节点在不同测速地址下有独立历史),
4
+ * 无对应记录时回退到顶层 history。
5
+ */
6
+ export function latestDelay(proxy, testUrl) {
7
+ const scoped = testUrl ? proxy.extra?.[testUrl]?.history : undefined;
8
+ const history = scoped && scoped.length > 0 ? scoped : proxy.history;
9
+ return history?.at(-1)?.delay;
10
+ }
11
+ export function classify(proxy, thresholds, testUrl) {
12
+ const delay = latestDelay(proxy, testUrl);
13
+ // 从未测过:history 为空,与「测过但失败」是两件事
14
+ if (delay === undefined)
15
+ return { status: 'untested', delay: undefined };
16
+ // 内核用 delay=0 表示该次测试失败
17
+ if (delay <= 0)
18
+ return { status: 'dead', delay: undefined };
19
+ if (delay < thresholds.good)
20
+ return { status: 'good', delay };
21
+ if (delay < thresholds.fair)
22
+ return { status: 'fair', delay };
23
+ return { status: 'slow', delay };
24
+ }
25
+ /** 纯文本渲染(CLI 用)。TUI 里由 DelayBadge 负责着色 */
26
+ export function formatDelay(result) {
27
+ switch (result.status) {
28
+ case 'untested':
29
+ return '---';
30
+ case 'dead':
31
+ return '错误';
32
+ default:
33
+ return `${result.delay}ms`;
34
+ }
35
+ }
36
+ export const STATUS_LABEL = {
37
+ good: '正常',
38
+ fair: '一般',
39
+ slow: '缓慢',
40
+ untested: '未测试',
41
+ dead: '错误',
42
+ };
43
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/api/status.ts"],"names":[],"mappings":"AAeA;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAgB,EAAE,OAAgB;IAC5D,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;IACpE,MAAM,OAAO,GAAG,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAA;IACpE,OAAO,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAA;AAC/B,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,KAAgB,EAChB,UAA2B,EAC3B,OAAgB;IAEhB,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACzC,+BAA+B;IAC/B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;IACxE,uBAAuB;IACvB,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;IAC3D,IAAI,KAAK,GAAG,UAAU,CAAC,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAC7D,IAAI,KAAK,GAAG,UAAU,CAAC,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AAClC,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,WAAW,CAAC,MAAiB;IAC3C,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,UAAU;YACb,OAAO,KAAK,CAAA;QACd,KAAK,MAAM;YACT,OAAO,IAAI,CAAA;QACb;YACE,OAAO,GAAG,MAAM,CAAC,KAAK,IAAI,CAAA;IAC9B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAA+B;IACtD,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE,KAAK;IACf,IAAI,EAAE,IAAI;CACX,CAAA"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * WebSocket 封装。Node 24 内置全局 WebSocket,不引入 ws 依赖。
3
+ * 三个端点:/traffic、/memory、/logs、/connections(均实测返回 101)。
4
+ *
5
+ * 要点:
6
+ * - secret 通过 query 参数 token 传递(WebSocket 无法自定义请求头);
7
+ * - 断线指数退避重连 1s → 2s → 4s,上限 30s;
8
+ * - 重连期间保留已有数据,由调用方决定如何展示。
9
+ */
10
+ const INITIAL_DELAY = 1000;
11
+ const MAX_DELAY = 30_000;
12
+ /**
13
+ * 一条自动重连的 WebSocket 订阅。
14
+ * 返回的 close() 是幂等的,调用后不再重连。
15
+ */
16
+ export class Stream {
17
+ url;
18
+ handlers;
19
+ options;
20
+ socket;
21
+ timer;
22
+ delay;
23
+ closed = false;
24
+ state = 'connecting';
25
+ constructor(url, handlers, options = {}) {
26
+ this.url = url;
27
+ this.handlers = handlers;
28
+ this.options = options;
29
+ this.delay = options.initialDelay ?? INITIAL_DELAY;
30
+ this.connect();
31
+ }
32
+ setState(state, detail) {
33
+ if (this.state === state)
34
+ return;
35
+ this.state = state;
36
+ this.handlers.onState?.(state, detail);
37
+ }
38
+ connect() {
39
+ if (this.closed)
40
+ return;
41
+ this.setState(this.delay === (this.options.initialDelay ?? INITIAL_DELAY) ? 'connecting' : 'reconnecting');
42
+ let socket;
43
+ try {
44
+ socket = new WebSocket(this.url);
45
+ }
46
+ catch (err) {
47
+ // 非法 URL 之类的同步失败,也走退避重试,避免调用方拿到异常直接崩
48
+ this.scheduleReconnect(err instanceof Error ? err.message : String(err));
49
+ return;
50
+ }
51
+ this.socket = socket;
52
+ socket.addEventListener('open', () => {
53
+ // 连上后重置退避,下次断线仍从 1s 起
54
+ this.delay = this.options.initialDelay ?? INITIAL_DELAY;
55
+ this.setState('open');
56
+ });
57
+ socket.addEventListener('message', (event) => {
58
+ const text = typeof event.data === 'string'
59
+ ? event.data
60
+ : Buffer.from(event.data).toString('utf8');
61
+ // 内核偶发发送空帧或非 JSON 心跳,静默丢弃,不能让流断掉
62
+ let frame;
63
+ try {
64
+ frame = JSON.parse(text);
65
+ }
66
+ catch {
67
+ return;
68
+ }
69
+ this.handlers.onMessage(frame);
70
+ });
71
+ socket.addEventListener('error', () => {
72
+ // error 之后必然跟一个 close,重连逻辑统一放 close 里,避免重复排程
73
+ });
74
+ socket.addEventListener('close', (event) => {
75
+ this.socket = undefined;
76
+ if (this.closed)
77
+ return;
78
+ this.scheduleReconnect(event.reason || `code ${event.code}`);
79
+ });
80
+ }
81
+ scheduleReconnect(detail) {
82
+ if (this.closed)
83
+ return;
84
+ this.setState('reconnecting', detail);
85
+ const wait = this.delay;
86
+ this.delay = Math.min(this.delay * 2, this.options.maxDelay ?? MAX_DELAY);
87
+ this.timer = setTimeout(() => {
88
+ this.timer = undefined;
89
+ this.connect();
90
+ }, wait);
91
+ // 重连定时器不应吊住进程退出
92
+ this.timer.unref?.();
93
+ }
94
+ close() {
95
+ if (this.closed)
96
+ return;
97
+ this.closed = true;
98
+ if (this.timer)
99
+ clearTimeout(this.timer);
100
+ this.timer = undefined;
101
+ // 1000 = 正常关闭。
102
+ // 注意:mihomo v1.19.24 实测**从不回应 close 帧**,仍继续推送数据帧与 ping,
103
+ // 因此 undici 的 close() 握手永不完成、底层 TCP 句柄不会释放,事件循环也就不会空。
104
+ // 本类只保证「关闭后不再回调、不再重连」;进程退出必须由调用方显式执行
105
+ // (CLI 用 exitAfterFlush,TUI 在 unmount 后同样需要显式退出)。
106
+ this.socket?.close(1000);
107
+ this.socket = undefined;
108
+ this.setState('closed');
109
+ }
110
+ }
111
+ /** 定长环形缓冲,用于日志与连接历史,防止长时间运行内存膨胀 */
112
+ export class RingBuffer {
113
+ capacity;
114
+ items = [];
115
+ constructor(capacity) {
116
+ this.capacity = capacity;
117
+ }
118
+ push(item) {
119
+ this.items.push(item);
120
+ if (this.items.length > this.capacity) {
121
+ this.items.splice(0, this.items.length - this.capacity);
122
+ }
123
+ }
124
+ get size() {
125
+ return this.items.length;
126
+ }
127
+ /** 返回副本,调用方可安全持有 */
128
+ toArray() {
129
+ return [...this.items];
130
+ }
131
+ /** 取最后 n 条 */
132
+ tail(n) {
133
+ return this.items.slice(Math.max(0, this.items.length - n));
134
+ }
135
+ clear() {
136
+ this.items = [];
137
+ }
138
+ }
139
+ //# sourceMappingURL=stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/api/stream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * REST API 响应类型。字段以本机 mihomo v1.19.24 实测响应为准,
3
+ * 未实测确认的字段一律标为可选,避免类型比现实更乐观。
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}