@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.
- package/CHANGELOG.md +93 -0
- package/LICENSE +21 -0
- package/README.md +462 -0
- package/bin/mihomo-tui +34 -0
- package/dist/App.js +161 -0
- package/dist/App.js.map +1 -0
- package/dist/api/client.js +239 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/status.js +43 -0
- package/dist/api/status.js.map +1 -0
- package/dist/api/stream.js +139 -0
- package/dist/api/stream.js.map +1 -0
- package/dist/api/types.js +6 -0
- package/dist/api/types.js.map +1 -0
- package/dist/cli.js +221 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/conn.js +122 -0
- package/dist/commands/conn.js.map +1 -0
- package/dist/commands/logs.js +99 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/output.js +177 -0
- package/dist/commands/output.js.map +1 -0
- package/dist/commands/provider.js +140 -0
- package/dist/commands/provider.js.map +1 -0
- package/dist/commands/proxy.js +161 -0
- package/dist/commands/proxy.js.map +1 -0
- package/dist/commands/proxyEnv.js +193 -0
- package/dist/commands/proxyEnv.js.map +1 -0
- package/dist/commands/rules.js +87 -0
- package/dist/commands/rules.js.map +1 -0
- package/dist/commands/status.js +75 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/components/ConfirmDialog.js +31 -0
- package/dist/components/ConfirmDialog.js.map +1 -0
- package/dist/components/DelayBadge.js +27 -0
- package/dist/components/DelayBadge.js.map +1 -0
- package/dist/components/InputDialog.js +325 -0
- package/dist/components/InputDialog.js.map +1 -0
- package/dist/components/ListDialog.js +86 -0
- package/dist/components/ListDialog.js.map +1 -0
- package/dist/components/ProgressDialog.js +34 -0
- package/dist/components/ProgressDialog.js.map +1 -0
- package/dist/components/ScrollList.js +26 -0
- package/dist/components/ScrollList.js.map +1 -0
- package/dist/components/StatusBar.js +58 -0
- package/dist/components/StatusBar.js.map +1 -0
- package/dist/components/TextEditor.js +112 -0
- package/dist/components/TextEditor.js.map +1 -0
- package/dist/components/textEditorModel.js +154 -0
- package/dist/components/textEditorModel.js.map +1 -0
- package/dist/config/manager.js +296 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/preserveGroups.js +9 -0
- package/dist/config/preserveGroups.js.map +1 -0
- package/dist/config/ruleDocument.js +88 -0
- package/dist/config/ruleDocument.js.map +1 -0
- package/dist/config/ruleService.js +256 -0
- package/dist/config/ruleService.js.map +1 -0
- package/dist/config/ruleValidation.js +98 -0
- package/dist/config/ruleValidation.js.map +1 -0
- package/dist/config/service.js +63 -0
- package/dist/config/service.js.map +1 -0
- package/dist/config/settingsService.js +162 -0
- package/dist/config/settingsService.js.map +1 -0
- package/dist/config/skeleton.js +324 -0
- package/dist/config/skeleton.js.map +1 -0
- package/dist/config/subscriptionService.js +284 -0
- package/dist/config/subscriptionService.js.map +1 -0
- package/dist/config/subscriptions.js +200 -0
- package/dist/config/subscriptions.js.map +1 -0
- package/dist/config/types.js +5 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config.js +101 -0
- package/dist/config.js.map +1 -0
- package/dist/hooks/useProviders.js +138 -0
- package/dist/hooks/useProviders.js.map +1 -0
- package/dist/hooks/useProxies.js +298 -0
- package/dist/hooks/useProxies.js.map +1 -0
- package/dist/hooks/useStream.js +127 -0
- package/dist/hooks/useStream.js.map +1 -0
- package/dist/kernel/installer.js +317 -0
- package/dist/kernel/installer.js.map +1 -0
- package/dist/kernel/releases.js +111 -0
- package/dist/kernel/releases.js.map +1 -0
- package/dist/kernel/sources.js +98 -0
- package/dist/kernel/sources.js.map +1 -0
- package/dist/rules/editor.js +181 -0
- package/dist/rules/editor.js.map +1 -0
- package/dist/rules/matcher.js +83 -0
- package/dist/rules/matcher.js.map +1 -0
- package/dist/rules/model.js +25 -0
- package/dist/rules/model.js.map +1 -0
- package/dist/rules/runtime.js +46 -0
- package/dist/rules/runtime.js.map +1 -0
- package/dist/rules/types.js +10 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/ui/FooterLine.js +62 -0
- package/dist/ui/FooterLine.js.map +1 -0
- package/dist/ui/Panel.js +47 -0
- package/dist/ui/Panel.js.map +1 -0
- package/dist/ui/RowIndicator.js +25 -0
- package/dist/ui/RowIndicator.js.map +1 -0
- package/dist/ui/SectionHeader.js +14 -0
- package/dist/ui/SectionHeader.js.map +1 -0
- package/dist/ui/StatusDot.js +15 -0
- package/dist/ui/StatusDot.js.map +1 -0
- package/dist/ui/TopBar.js +81 -0
- package/dist/ui/TopBar.js.map +1 -0
- package/dist/ui/exitGuard.js +20 -0
- package/dist/ui/exitGuard.js.map +1 -0
- package/dist/ui/keyCapture.js +28 -0
- package/dist/ui/keyCapture.js.map +1 -0
- package/dist/ui/layout.js +32 -0
- package/dist/ui/layout.js.map +1 -0
- package/dist/ui/rowIndicator.js +42 -0
- package/dist/ui/rowIndicator.js.map +1 -0
- package/dist/ui/tableHeader.js +33 -0
- package/dist/ui/tableHeader.js.map +1 -0
- package/dist/ui/theme.js +47 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/tone.js +131 -0
- package/dist/ui/tone.js.map +1 -0
- package/dist/version.js +27 -0
- package/dist/version.js.map +1 -0
- package/dist/views/Conns.js +107 -0
- package/dist/views/Conns.js.map +1 -0
- package/dist/views/Logs.js +89 -0
- package/dist/views/Logs.js.map +1 -0
- package/dist/views/Providers.js +392 -0
- package/dist/views/Providers.js.map +1 -0
- package/dist/views/Proxies.js +233 -0
- package/dist/views/Proxies.js.map +1 -0
- package/dist/views/RuleEditor.js +356 -0
- package/dist/views/RuleEditor.js.map +1 -0
- package/dist/views/Rules.js +286 -0
- package/dist/views/Rules.js.map +1 -0
- package/dist/views/Settings.js +734 -0
- package/dist/views/Settings.js.map +1 -0
- package/dist/views/groupDisplay.js +53 -0
- package/dist/views/groupDisplay.js.map +1 -0
- package/dist/views/subscriptionFields.js +59 -0
- package/dist/views/subscriptionFields.js.map +1 -0
- package/dist/views/yamlFile.js +32 -0
- package/dist/views/yamlFile.js.map +1 -0
- package/package.json +66 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* commander 入口。无子命令时进入 TUI(检查点 5 接入),带子命令时走 CLI。
|
|
5
|
+
* 所有子命令共享同一份 ~/.config/mihomo-tui/config.json,并支持 --json。
|
|
6
|
+
*/
|
|
7
|
+
import { Command, CommanderError } from 'commander';
|
|
8
|
+
import { MihomoClient } from './api/client.js';
|
|
9
|
+
import { loadConfig } from './config.js';
|
|
10
|
+
import { runStatus } from './commands/status.js';
|
|
11
|
+
import { runProxyLs, runProxyTest, runProxyUnfix, runProxyUse } from './commands/proxy.js';
|
|
12
|
+
import { runProxyInit, runProxyOff, runProxyOn, runProxyStatus } from './commands/proxyEnv.js';
|
|
13
|
+
import { runProviderCheck, runProviderLs, runProviderUpdate } from './commands/provider.js';
|
|
14
|
+
import { runConnClose, runConnLs, runReload } from './commands/conn.js';
|
|
15
|
+
import { runLogs } from './commands/logs.js';
|
|
16
|
+
import { runRuleProviderLs, runRuleProviderUpdate, runRulesLs, runRulesTest } from './commands/rules.js';
|
|
17
|
+
import { EXIT, exitAfterFlush, reportError } from './commands/output.js';
|
|
18
|
+
import { VERSION } from './version.js';
|
|
19
|
+
const program = new Command();
|
|
20
|
+
program
|
|
21
|
+
.name('mihomo-tui')
|
|
22
|
+
.description('mihomo 内核的 CLI + TUI 管理工具(API 驱动,写配置仅限带备份与回滚的事务层)')
|
|
23
|
+
.version(VERSION, '-V, --version', '显示本工具版本')
|
|
24
|
+
.option('--api <url>', '覆盖控制口地址,默认取配置文件中的 api')
|
|
25
|
+
.option('--secret <token>', '覆盖控制口密钥')
|
|
26
|
+
.showHelpAfterError('(用 --help 查看用法)')
|
|
27
|
+
// commander 默认对参数错误用退出码 1,本项目约定参数错误为 2
|
|
28
|
+
.exitOverride((err) => {
|
|
29
|
+
if (err.code === 'commander.helpDisplayed' || err.code === 'commander.help') {
|
|
30
|
+
process.exit(EXIT.ok);
|
|
31
|
+
}
|
|
32
|
+
if (err.code === 'commander.version')
|
|
33
|
+
process.exit(EXIT.ok);
|
|
34
|
+
process.exit(err.exitCode === 0 ? EXIT.ok : EXIT.usage);
|
|
35
|
+
});
|
|
36
|
+
/** 合并配置文件与全局选项,命令行优先 */
|
|
37
|
+
function resolveConfig() {
|
|
38
|
+
const options = program.opts();
|
|
39
|
+
const config = loadConfig();
|
|
40
|
+
if (options.api)
|
|
41
|
+
config.api = options.api.replace(/\/+$/, '');
|
|
42
|
+
if (options.secret !== undefined)
|
|
43
|
+
config.secret = options.secret;
|
|
44
|
+
return config;
|
|
45
|
+
}
|
|
46
|
+
/** 统一异常出口:把三类失败翻译成人话与退出码 */
|
|
47
|
+
function wrap(fn) {
|
|
48
|
+
return async (...args) => {
|
|
49
|
+
try {
|
|
50
|
+
await fn(...args);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
reportError(err);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
program
|
|
58
|
+
.command('status')
|
|
59
|
+
.description('显示内核版本、模式、端口与 provider 概览')
|
|
60
|
+
.option('--json', '输出原始 JSON')
|
|
61
|
+
.action(wrap(async (options) => runStatus(resolveConfig(), options)));
|
|
62
|
+
const proxy = program.command('proxy').description('代理组与节点操作;on/off 切换本 shell 的系统代理环境变量');
|
|
63
|
+
proxy
|
|
64
|
+
.command('on')
|
|
65
|
+
.description('开启系统代理:输出 export 代码(eval 执行),把本 shell 流量指向本机 mihomo 混合端口')
|
|
66
|
+
.option('--json', '输出原始 JSON(含端口与 script)')
|
|
67
|
+
.option('--port <n>', '显式指定混合端口,默认按「内核 /configs → config.yaml」发现')
|
|
68
|
+
.option('--lan', 'no_proxy 追加私有网段(旧版 curl/wget 不识别 CIDR,默认关闭)')
|
|
69
|
+
.option('--all', '同时设置 all_proxy(部分工具按 socks 语义解释,默认不设)')
|
|
70
|
+
.option('--start', '内核离线时先拉起 systemd 用户服务')
|
|
71
|
+
.action(wrap(async (options) => runProxyOn(resolveConfig(), options)));
|
|
72
|
+
proxy
|
|
73
|
+
.command('off')
|
|
74
|
+
.description('关闭系统代理:输出 unset 代码(eval 执行);不访问内核,离线可用')
|
|
75
|
+
.option('--json', '输出原始 JSON')
|
|
76
|
+
.action(wrap(async (options) => runProxyOff(resolveConfig(), options)));
|
|
77
|
+
proxy
|
|
78
|
+
.command('status')
|
|
79
|
+
.description('查看内核 mixed-port 与本 shell 代理环境变量的一致性')
|
|
80
|
+
.option('--json', '输出原始 JSON')
|
|
81
|
+
.action(wrap(async (options) => runProxyStatus(resolveConfig(), options)));
|
|
82
|
+
proxy
|
|
83
|
+
.command('init')
|
|
84
|
+
.description('输出 shell 集成函数(proxy / proxy-on / proxy-off / proxy-tui)')
|
|
85
|
+
.option('--json', '输出原始 JSON')
|
|
86
|
+
.action(wrap(async (options) => runProxyInit(resolveConfig(), options)));
|
|
87
|
+
proxy
|
|
88
|
+
.command('ls')
|
|
89
|
+
.argument('[group]', '代理组名;省略则列出所有代理组')
|
|
90
|
+
.description('列出代理组及当前选中;带组名则列出该组节点与延迟')
|
|
91
|
+
.option('--json', '输出原始 JSON')
|
|
92
|
+
.action(wrap(async (group, options) => runProxyLs(resolveConfig(), group, options)));
|
|
93
|
+
proxy
|
|
94
|
+
.command('use')
|
|
95
|
+
.argument('<group>', '代理组名')
|
|
96
|
+
.argument('<name>', '节点名')
|
|
97
|
+
.description('切换代理组当前选中节点')
|
|
98
|
+
.option('--json', '输出原始 JSON')
|
|
99
|
+
.action(wrap(async (group, name, options) => runProxyUse(resolveConfig(), group, name, options)));
|
|
100
|
+
proxy
|
|
101
|
+
.command('unfix')
|
|
102
|
+
.argument('<group>', '代理组名')
|
|
103
|
+
.description('解除 url-test/fallback 组的钉选,恢复自动选路')
|
|
104
|
+
.option('--json', '输出原始 JSON')
|
|
105
|
+
.action(wrap(async (group, options) => runProxyUnfix(resolveConfig(), group, options)));
|
|
106
|
+
proxy
|
|
107
|
+
.command('test')
|
|
108
|
+
.argument('<group>', '代理组名')
|
|
109
|
+
.description('整组延迟测试')
|
|
110
|
+
.option('--json', '输出原始 JSON')
|
|
111
|
+
.option('-u, --url <url>', '测速地址,默认取组自身的 testUrl')
|
|
112
|
+
.option('-t, --timeout <ms>', '单节点超时(毫秒)')
|
|
113
|
+
.action(wrap(async (group, options) => runProxyTest(resolveConfig(), group, options)));
|
|
114
|
+
const provider = program.command('provider').description('订阅(proxy-providers)管理');
|
|
115
|
+
provider
|
|
116
|
+
.command('ls')
|
|
117
|
+
.description('列出所有 provider,含节点数、流量、到期与更新时间')
|
|
118
|
+
.option('--json', '输出原始 JSON')
|
|
119
|
+
.action(wrap(async (options) => runProviderLs(resolveConfig(), options)));
|
|
120
|
+
provider
|
|
121
|
+
.command('update')
|
|
122
|
+
.argument('[name]', 'provider 名称;省略则更新全部')
|
|
123
|
+
.description('更新订阅(内核重新拉取,不重启进程)')
|
|
124
|
+
.option('--json', '输出原始 JSON')
|
|
125
|
+
.action(wrap(async (name, options) => runProviderUpdate(resolveConfig(), name, options)));
|
|
126
|
+
provider
|
|
127
|
+
.command('check')
|
|
128
|
+
.argument('<name>', 'provider 名称')
|
|
129
|
+
.description('触发该 provider 的健康检查')
|
|
130
|
+
.option('--json', '输出原始 JSON')
|
|
131
|
+
.action(wrap(async (name, options) => runProviderCheck(resolveConfig(), name, options)));
|
|
132
|
+
const rules = program.command('rules').description('查看当前生效规则并测试命中');
|
|
133
|
+
rules
|
|
134
|
+
.command('ls')
|
|
135
|
+
.description('列出当前生效规则(按匹配优先级)')
|
|
136
|
+
.option('--type <type>', '只看指定类型,如 DOMAIN-SUFFIX')
|
|
137
|
+
.option('--json', '输出原始 JSON')
|
|
138
|
+
.action(wrap(async (options) => runRulesLs(resolveConfig(), options)));
|
|
139
|
+
rules
|
|
140
|
+
.command('test')
|
|
141
|
+
.argument('<host>', '要测试的域名或 IP')
|
|
142
|
+
.description('保守本地匹配域名/IP;依赖内核数据时返回无法判定')
|
|
143
|
+
.option('--json', '输出原始 JSON')
|
|
144
|
+
.action(wrap(async (host, options) => runRulesTest(resolveConfig(), host, options)));
|
|
145
|
+
const ruleProvider = program
|
|
146
|
+
.command('rule-provider')
|
|
147
|
+
.description('规则集(rule-providers)管理');
|
|
148
|
+
ruleProvider
|
|
149
|
+
.command('ls')
|
|
150
|
+
.description('列出所有规则集')
|
|
151
|
+
.option('--json', '输出原始 JSON')
|
|
152
|
+
.action(wrap(async (options) => runRuleProviderLs(resolveConfig(), options)));
|
|
153
|
+
ruleProvider
|
|
154
|
+
.command('update')
|
|
155
|
+
.argument('<name>', '规则集名称')
|
|
156
|
+
.description('更新指定规则集(内核重新拉取)')
|
|
157
|
+
.option('--json', '输出 JSON')
|
|
158
|
+
.action(wrap(async (name, options) => runRuleProviderUpdate(resolveConfig(), name, options)));
|
|
159
|
+
const conn = program.command('conn').description('连接管理');
|
|
160
|
+
conn
|
|
161
|
+
.command('ls')
|
|
162
|
+
.description('列出当前活跃连接')
|
|
163
|
+
.option('--json', '输出原始 JSON')
|
|
164
|
+
.option('-s, --sort <field>', '排序方式:traffic / time / host', 'traffic')
|
|
165
|
+
.option('-n, --limit <count>', '最多显示多少条')
|
|
166
|
+
.action(wrap(async (options) => runConnLs(resolveConfig(), options)));
|
|
167
|
+
conn
|
|
168
|
+
.command('close')
|
|
169
|
+
.argument('[id]', '连接 ID,支持 ls 显示的 8 位前缀')
|
|
170
|
+
.description('关闭指定连接,或用 --all 关闭全部')
|
|
171
|
+
.option('--all', '关闭全部连接')
|
|
172
|
+
.option('--json', '输出原始 JSON')
|
|
173
|
+
.action(wrap(async (id, options) => runConnClose(resolveConfig(), id, options)));
|
|
174
|
+
program
|
|
175
|
+
.command('logs')
|
|
176
|
+
.description('实时日志')
|
|
177
|
+
.option('-l, --level <level>', '日志级别:silent/error/warning/info/debug', 'info')
|
|
178
|
+
.option('-f, --follow', '持续跟随,Ctrl+C 退出')
|
|
179
|
+
.option('-n, --lines <count>', '非 follow 模式下收集多少条后退出', '20')
|
|
180
|
+
.option('--idle <seconds>', '非 follow 模式下多久无新日志则退出', '5')
|
|
181
|
+
.option('-g, --grep <pattern>', '按正则过滤(不区分大小写)')
|
|
182
|
+
.option('--json', '每行输出原始 JSON')
|
|
183
|
+
.action(wrap(async (options) => runLogs(resolveConfig(), options)));
|
|
184
|
+
program
|
|
185
|
+
.command('reload')
|
|
186
|
+
.description('让内核热重载配置文件(本程序不写配置,只触发重读)')
|
|
187
|
+
.option('-p, --path <path>', '配置文件路径,省略则用内核启动时的路径')
|
|
188
|
+
.option('--json', '输出原始 JSON')
|
|
189
|
+
.action(wrap(async (options) => runReload(resolveConfig(), options)));
|
|
190
|
+
program.action(async () => {
|
|
191
|
+
// 无子命令 → 进入 TUI。
|
|
192
|
+
// 先握一次 REST:内核不可达时给退出码 3,而不是渲染一个空界面让人猜。
|
|
193
|
+
const config = resolveConfig();
|
|
194
|
+
const client = new MihomoClient(config);
|
|
195
|
+
let version;
|
|
196
|
+
let mode;
|
|
197
|
+
try {
|
|
198
|
+
const [v, c] = await Promise.all([client.version(), client.configs()]);
|
|
199
|
+
version = v.version;
|
|
200
|
+
mode = c.mode;
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
reportError(err);
|
|
204
|
+
}
|
|
205
|
+
if (!process.stdout.isTTY) {
|
|
206
|
+
process.stderr.write('TUI 需要交互式终端。管道场景请使用子命令,如 mihomo-tui proxy ls --json\n');
|
|
207
|
+
process.exit(EXIT.usage);
|
|
208
|
+
}
|
|
209
|
+
const { render } = await import('ink');
|
|
210
|
+
const { App } = await import('./App.js');
|
|
211
|
+
const instance = render(_jsx(App, { config: config, version: version, mode: mode }), {
|
|
212
|
+
// 使用备用屏幕:TUI 运行在独立屏幕,退出后恢复到进入前的终端状态(类似 vim/htop)
|
|
213
|
+
alternateScreen: true,
|
|
214
|
+
exitOnCtrlC: false,
|
|
215
|
+
});
|
|
216
|
+
await instance.waitUntilExit();
|
|
217
|
+
// mihomo 不回 WebSocket close 帧,句柄不释放 → 必须显式退出(SPEC 3.6)
|
|
218
|
+
await exitAfterFlush(EXIT.ok);
|
|
219
|
+
});
|
|
220
|
+
program.parseAsync(process.argv).catch(reportError);
|
|
221
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** proxy_tui conn ls / close,以及 reload */
|
|
2
|
+
import { MihomoClient } from '../api/client.js';
|
|
3
|
+
import { EXIT, formatBytes, printJson, renderTable, terminalWidth } from './output.js';
|
|
4
|
+
/** 连接持续时长 */
|
|
5
|
+
function formatDuration(start) {
|
|
6
|
+
const began = Date.parse(start);
|
|
7
|
+
if (Number.isNaN(began))
|
|
8
|
+
return '---';
|
|
9
|
+
const seconds = Math.max(0, Math.floor((Date.now() - began) / 1000));
|
|
10
|
+
if (seconds < 60)
|
|
11
|
+
return `${seconds}s`;
|
|
12
|
+
if (seconds < 3600)
|
|
13
|
+
return `${Math.floor(seconds / 60)}m${seconds % 60}s`;
|
|
14
|
+
return `${Math.floor(seconds / 3600)}h${Math.floor((seconds % 3600) / 60)}m`;
|
|
15
|
+
}
|
|
16
|
+
/** 展示用主机名:优先 host,回退嗅探结果与目标 IP */
|
|
17
|
+
function describeHost(conn) {
|
|
18
|
+
const { host, sniffHost, destinationIP, destinationPort } = conn.metadata;
|
|
19
|
+
const name = host || sniffHost || destinationIP || '?';
|
|
20
|
+
return destinationPort ? `${name}:${destinationPort}` : name;
|
|
21
|
+
}
|
|
22
|
+
export async function runConnLs(config, options) {
|
|
23
|
+
const client = new MihomoClient(config);
|
|
24
|
+
const data = await client.connections();
|
|
25
|
+
// 无连接时内核返回 null 而非 [](实测,见 SPEC 3.5)
|
|
26
|
+
const conns = data.connections ?? [];
|
|
27
|
+
const sort = options.sort ?? 'traffic';
|
|
28
|
+
if (!['traffic', 'time', 'host'].includes(sort)) {
|
|
29
|
+
process.stderr.write('错误:--sort 只支持 traffic / time / host\n');
|
|
30
|
+
process.exit(EXIT.usage);
|
|
31
|
+
}
|
|
32
|
+
const sorted = conns.slice().sort((a, b) => {
|
|
33
|
+
if (sort === 'time')
|
|
34
|
+
return Date.parse(a.start) - Date.parse(b.start);
|
|
35
|
+
if (sort === 'host')
|
|
36
|
+
return describeHost(a).localeCompare(describeHost(b));
|
|
37
|
+
return b.download + b.upload - (a.download + a.upload);
|
|
38
|
+
});
|
|
39
|
+
const limit = options.limit ? Number(options.limit) : undefined;
|
|
40
|
+
if (limit !== undefined && (!Number.isFinite(limit) || limit <= 0)) {
|
|
41
|
+
process.stderr.write('错误:--limit 需为正整数\n');
|
|
42
|
+
process.exit(EXIT.usage);
|
|
43
|
+
}
|
|
44
|
+
const shown = limit === undefined ? sorted : sorted.slice(0, limit);
|
|
45
|
+
if (options.json) {
|
|
46
|
+
printJson({
|
|
47
|
+
uploadTotal: data.uploadTotal,
|
|
48
|
+
downloadTotal: data.downloadTotal,
|
|
49
|
+
memory: data.memory,
|
|
50
|
+
count: conns.length,
|
|
51
|
+
connections: shown,
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (shown.length === 0) {
|
|
56
|
+
process.stdout.write(`当前无活跃连接。累计 ↑${formatBytes(data.uploadTotal)} ↓${formatBytes(data.downloadTotal)}\n`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// 窄终端下省略代理链列,避免横向截断导致乱码
|
|
60
|
+
const narrow = terminalWidth() < 100;
|
|
61
|
+
const header = narrow
|
|
62
|
+
? ['ID', 'HOST', 'UP', 'DOWN', 'TIME']
|
|
63
|
+
: ['ID', 'HOST', 'RULE', 'CHAIN', 'UP', 'DOWN', 'TIME'];
|
|
64
|
+
const rows = shown.map((conn) => {
|
|
65
|
+
const id = conn.id.slice(0, 8);
|
|
66
|
+
const base = [id, describeHost(conn)];
|
|
67
|
+
const tail = [formatBytes(conn.upload), formatBytes(conn.download), formatDuration(conn.start)];
|
|
68
|
+
if (narrow)
|
|
69
|
+
return [...base, ...tail];
|
|
70
|
+
const rule = conn.rulePayload ? `${conn.rule}(${conn.rulePayload})` : conn.rule;
|
|
71
|
+
// chains 是从出口到入口排列,反转后读起来才是流量实际经过的顺序
|
|
72
|
+
return [...base, rule, conn.chains.slice().reverse().join(' → '), ...tail];
|
|
73
|
+
});
|
|
74
|
+
process.stdout.write(`${renderTable(header, rows)}\n`);
|
|
75
|
+
process.stdout.write(`\n共 ${conns.length} 条连接${shown.length < conns.length ? `(显示前 ${shown.length} 条)` : ''},累计 ↑${formatBytes(data.uploadTotal)} ↓${formatBytes(data.downloadTotal)},内核内存 ${formatBytes(data.memory)}\n`);
|
|
76
|
+
}
|
|
77
|
+
export async function runConnClose(config, id, options) {
|
|
78
|
+
const client = new MihomoClient(config);
|
|
79
|
+
if (options.all) {
|
|
80
|
+
const before = await client.connections();
|
|
81
|
+
const count = before.connections?.length ?? 0;
|
|
82
|
+
await client.closeAllConnections();
|
|
83
|
+
if (options.json)
|
|
84
|
+
printJson({ closed: 'all', count });
|
|
85
|
+
else
|
|
86
|
+
process.stdout.write(`已关闭全部连接(${count} 条)\n`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!id) {
|
|
90
|
+
process.stderr.write('用法:conn close <id> 或 conn close --all\n');
|
|
91
|
+
process.exit(EXIT.usage);
|
|
92
|
+
}
|
|
93
|
+
// 支持用 ls 显示的 8 位短 ID
|
|
94
|
+
const data = await client.connections();
|
|
95
|
+
const conns = data.connections ?? [];
|
|
96
|
+
const matched = conns.filter((conn) => conn.id === id || conn.id.startsWith(id));
|
|
97
|
+
if (matched.length === 0) {
|
|
98
|
+
process.stderr.write(`错误:未找到连接:${id}\n`);
|
|
99
|
+
process.exit(EXIT.error);
|
|
100
|
+
}
|
|
101
|
+
if (matched.length > 1) {
|
|
102
|
+
process.stderr.write(`错误:ID 前缀 ${id} 匹配到 ${matched.length} 条连接,请给出更长的 ID\n`);
|
|
103
|
+
process.exit(EXIT.usage);
|
|
104
|
+
}
|
|
105
|
+
const target = matched[0];
|
|
106
|
+
await client.closeConnection(target.id);
|
|
107
|
+
if (options.json)
|
|
108
|
+
printJson({ closed: target.id, host: describeHost(target) });
|
|
109
|
+
else
|
|
110
|
+
process.stdout.write(`已关闭连接 ${target.id.slice(0, 8)}(${describeHost(target)})\n`);
|
|
111
|
+
}
|
|
112
|
+
export async function runReload(config, options) {
|
|
113
|
+
const client = new MihomoClient(config);
|
|
114
|
+
// 本程序不写配置文件,只让内核自己重新读盘(PUT /configs?force=false,实测 204 且 PID 不变)
|
|
115
|
+
await client.reload(options.path ?? '');
|
|
116
|
+
const version = await client.version();
|
|
117
|
+
if (options.json)
|
|
118
|
+
printJson({ reloaded: true, version: version.version });
|
|
119
|
+
else
|
|
120
|
+
process.stdout.write(`配置已热重载,内核 ${version.version} 未重启\n`);
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=conn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conn.js","sourceRoot":"","sources":["../../src/commands/conn.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/** proxy_tui logs —— WebSocket 实时日志 */
|
|
2
|
+
import { MihomoClient } from '../api/client.js';
|
|
3
|
+
import { RingBuffer, Stream } from '../api/stream.js';
|
|
4
|
+
import { EXIT, exitAfterFlush } from './output.js';
|
|
5
|
+
const LEVELS = ['silent', 'error', 'warning', 'info', 'debug'];
|
|
6
|
+
export async function runLogs(config, options) {
|
|
7
|
+
const level = (options.level ?? 'info');
|
|
8
|
+
if (!LEVELS.includes(level)) {
|
|
9
|
+
process.stderr.write(`错误:--level 只支持 ${LEVELS.join(' / ')}\n`);
|
|
10
|
+
process.exit(EXIT.usage);
|
|
11
|
+
}
|
|
12
|
+
let pattern;
|
|
13
|
+
if (options.grep) {
|
|
14
|
+
try {
|
|
15
|
+
pattern = new RegExp(options.grep, 'i');
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
process.stderr.write(`错误:--grep 正则无效:${err instanceof Error ? err.message : err}\n`);
|
|
19
|
+
process.exit(EXIT.usage);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const wanted = options.lines ? Number(options.lines) : 20;
|
|
23
|
+
if (!Number.isFinite(wanted) || wanted <= 0) {
|
|
24
|
+
process.stderr.write('错误:--lines 需为正整数\n');
|
|
25
|
+
process.exit(EXIT.usage);
|
|
26
|
+
}
|
|
27
|
+
const idleSeconds = options.idle ? Number(options.idle) : 5;
|
|
28
|
+
if (!Number.isFinite(idleSeconds) || idleSeconds <= 0) {
|
|
29
|
+
process.stderr.write('错误:--idle 需为正数(秒)\n');
|
|
30
|
+
process.exit(EXIT.usage);
|
|
31
|
+
}
|
|
32
|
+
const client = new MihomoClient(config);
|
|
33
|
+
// 先握一次 REST,内核不可达时直接给退出码 3,而不是让 WebSocket 静默重连
|
|
34
|
+
await client.version();
|
|
35
|
+
// 环形缓冲上限 1000 行,防止 -f 长跑内存膨胀(SPEC 5.3)
|
|
36
|
+
const buffer = new RingBuffer(1000);
|
|
37
|
+
let count = 0;
|
|
38
|
+
await new Promise((resolve) => {
|
|
39
|
+
let idleTimer;
|
|
40
|
+
let stream;
|
|
41
|
+
const finish = () => {
|
|
42
|
+
if (idleTimer)
|
|
43
|
+
clearTimeout(idleTimer);
|
|
44
|
+
idleTimer = undefined;
|
|
45
|
+
stream?.close();
|
|
46
|
+
resolve();
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* 空闲超时:内核闲着不产日志时,非 follow 模式必须能自己退出,
|
|
50
|
+
* 否则 `logs -n 5` 会永久挂住(实测过)。
|
|
51
|
+
*/
|
|
52
|
+
const resetIdle = () => {
|
|
53
|
+
if (options.follow)
|
|
54
|
+
return;
|
|
55
|
+
if (idleTimer)
|
|
56
|
+
clearTimeout(idleTimer);
|
|
57
|
+
idleTimer = setTimeout(() => {
|
|
58
|
+
if (count === 0) {
|
|
59
|
+
process.stderr.write(`[${idleSeconds}s 内无日志输出,已退出]\n`);
|
|
60
|
+
}
|
|
61
|
+
finish();
|
|
62
|
+
}, idleSeconds * 1000);
|
|
63
|
+
};
|
|
64
|
+
stream = new Stream(client.logsUrl(level), {
|
|
65
|
+
onMessage: (frame) => {
|
|
66
|
+
if (typeof frame?.payload !== 'string')
|
|
67
|
+
return;
|
|
68
|
+
if (pattern && !pattern.test(frame.payload)) {
|
|
69
|
+
// 被过滤掉的帧也算「有动静」,避免刷屏时被空闲超时误杀
|
|
70
|
+
resetIdle();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
buffer.push(frame);
|
|
74
|
+
count += 1;
|
|
75
|
+
process.stdout.write(options.json
|
|
76
|
+
? `${JSON.stringify(frame)}\n`
|
|
77
|
+
: `${frame.type.padEnd(7)} ${frame.payload}\n`);
|
|
78
|
+
if (!options.follow && count >= wanted) {
|
|
79
|
+
finish();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
resetIdle();
|
|
83
|
+
},
|
|
84
|
+
onState: (state, detail) => {
|
|
85
|
+
// 状态信息走 stderr,stdout 保持纯日志以便管道消费
|
|
86
|
+
if (state === 'open')
|
|
87
|
+
resetIdle();
|
|
88
|
+
if (state === 'reconnecting') {
|
|
89
|
+
process.stderr.write(`[连接断开,正在重连${detail ? `:${detail}` : ''}]\n`);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
process.once('SIGINT', finish);
|
|
94
|
+
process.once('SIGTERM', finish);
|
|
95
|
+
});
|
|
96
|
+
// mihomo 不回 WebSocket close 帧,句柄不会释放,必须显式退出(见 exitAfterFlush 注释)
|
|
97
|
+
await exitAfterFlush(EXIT.ok);
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=logs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logs.js","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/** CLI 输出与错误处理的公共部分:统一表格排版、脱敏、退出码。 */
|
|
2
|
+
import { ApiBusinessError, HttpStatusError, KernelUnreachableError, } from '../api/client.js';
|
|
3
|
+
import { CONFIG_PATH } from '../config.js';
|
|
4
|
+
import { InvalidTargetError } from '../rules/matcher.js';
|
|
5
|
+
/** 退出码约定:0 成功,1 通用错误,2 参数错误,3 内核不可达 */
|
|
6
|
+
export const EXIT = { ok: 0, error: 1, usage: 2, unreachable: 3 };
|
|
7
|
+
export function printJson(value) {
|
|
8
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 订阅 URL 含 token,任何展示都必须脱敏。
|
|
12
|
+
* 实现随 v0.2.0 迁至 config/subscriptions.ts(清单模块是 token 的属主),
|
|
13
|
+
* 这里保留导出以维持既有调用方与测试的导入路径。
|
|
14
|
+
*/
|
|
15
|
+
export { redactUrl } from '../config/subscriptions.js';
|
|
16
|
+
/**
|
|
17
|
+
* 终端宽度。
|
|
18
|
+
* 非 TTY(输出被管道或重定向)时不做窄屏降级 —— 那种场景是给 grep/jq 消费的,
|
|
19
|
+
* 信息完整比排版好看更重要,故返回 Infinity。
|
|
20
|
+
*/
|
|
21
|
+
export function terminalWidth() {
|
|
22
|
+
if (!process.stdout.isTTY)
|
|
23
|
+
return Number.POSITIVE_INFINITY;
|
|
24
|
+
return process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
25
|
+
}
|
|
26
|
+
/** 中日韩字符与 emoji 占两列,等宽终端下必须按显示宽度对齐而非按码点数 */
|
|
27
|
+
export function displayWidth(text) {
|
|
28
|
+
let width = 0;
|
|
29
|
+
for (const char of text) {
|
|
30
|
+
const code = char.codePointAt(0) ?? 0;
|
|
31
|
+
if (code === 0xfe0f || (code >= 0x1f1e6 && code <= 0x1f1ff)) {
|
|
32
|
+
// 变体选择符不占宽。区域指示符(国旗的一半)按 2 计:终端对成对 RI
|
|
33
|
+
// 的渲染宽度有分歧(多数按组合字符 2 列,pyte 等按两个宽字符 4 列)。
|
|
34
|
+
// 预算取最坏情况——宁可行尾多留白,也不能让整行超宽折行撑爆面板。
|
|
35
|
+
width += code === 0xfe0f ? 0 : 2;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
width += isWide(code) ? 2 : 1;
|
|
39
|
+
}
|
|
40
|
+
return width;
|
|
41
|
+
}
|
|
42
|
+
function isWide(code) {
|
|
43
|
+
return ((code >= 0x1100 && code <= 0x115f) ||
|
|
44
|
+
(code >= 0x2e80 && code <= 0xa4cf) ||
|
|
45
|
+
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
46
|
+
(code >= 0xf900 && code <= 0xfaff) ||
|
|
47
|
+
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
48
|
+
(code >= 0xff00 && code <= 0xff60) ||
|
|
49
|
+
(code >= 0xffe0 && code <= 0xffe6) ||
|
|
50
|
+
(code >= 0x1f300 && code <= 0x1f64f) ||
|
|
51
|
+
(code >= 0x1f900 && code <= 0x1f9ff) ||
|
|
52
|
+
(code >= 0x20000 && code <= 0x3fffd));
|
|
53
|
+
}
|
|
54
|
+
export function padDisplay(text, width) {
|
|
55
|
+
const pad = width - displayWidth(text);
|
|
56
|
+
return pad > 0 ? text + ' '.repeat(pad) : text;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 按**显示宽度**截断,超出时以 … 结尾。
|
|
60
|
+
* 不能用 slice —— 那是按码点数切,中文和 emoji 会把列宽撑破导致错位。
|
|
61
|
+
*/
|
|
62
|
+
export function truncateDisplay(text, max) {
|
|
63
|
+
if (max <= 0)
|
|
64
|
+
return '';
|
|
65
|
+
if (displayWidth(text) <= max)
|
|
66
|
+
return text;
|
|
67
|
+
let out = '';
|
|
68
|
+
for (const char of text) {
|
|
69
|
+
if (displayWidth(out + char) > max - 1)
|
|
70
|
+
break;
|
|
71
|
+
out += char;
|
|
72
|
+
}
|
|
73
|
+
return `${out}…`;
|
|
74
|
+
}
|
|
75
|
+
/** 先按显示宽度截断再补齐,保证该列恒定占 width 列 */
|
|
76
|
+
export function fitDisplay(text, width) {
|
|
77
|
+
return padDisplay(truncateDisplay(text, width), width);
|
|
78
|
+
}
|
|
79
|
+
/** 按显示宽度输出表格,最后一列不补空格,便于 grep */
|
|
80
|
+
export function renderTable(header, rows) {
|
|
81
|
+
const widths = header.map((cell, index) => Math.max(displayWidth(cell), ...rows.map((row) => displayWidth(row[index] ?? ''))));
|
|
82
|
+
const lines = [header, ...rows].map((row) => row
|
|
83
|
+
.map((cell, index) => (index === row.length - 1 ? cell : padDisplay(cell, widths[index] ?? 0)))
|
|
84
|
+
.join(' ')
|
|
85
|
+
.trimEnd());
|
|
86
|
+
return lines.join('\n');
|
|
87
|
+
}
|
|
88
|
+
export function formatBytes(bytes) {
|
|
89
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
90
|
+
return '---';
|
|
91
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
92
|
+
let value = bytes;
|
|
93
|
+
let unit = 0;
|
|
94
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
95
|
+
value /= 1024;
|
|
96
|
+
unit += 1;
|
|
97
|
+
}
|
|
98
|
+
return `${value >= 100 || unit === 0 ? Math.round(value) : value.toFixed(2)} ${units[unit]}`;
|
|
99
|
+
}
|
|
100
|
+
/** 相对时间,用于 provider 的 UPDATED 列 */
|
|
101
|
+
export function formatRelativeTime(iso) {
|
|
102
|
+
if (!iso)
|
|
103
|
+
return '---';
|
|
104
|
+
const time = Date.parse(iso);
|
|
105
|
+
if (Number.isNaN(time))
|
|
106
|
+
return '---';
|
|
107
|
+
const seconds = Math.floor((Date.now() - time) / 1000);
|
|
108
|
+
if (seconds < 0)
|
|
109
|
+
return '刚刚';
|
|
110
|
+
if (seconds < 60)
|
|
111
|
+
return `${seconds}s 前`;
|
|
112
|
+
if (seconds < 3600)
|
|
113
|
+
return `${Math.floor(seconds / 60)}m 前`;
|
|
114
|
+
if (seconds < 86400)
|
|
115
|
+
return `${Math.floor(seconds / 3600)}h 前`;
|
|
116
|
+
return `${Math.floor(seconds / 86400)}d 前`;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 冲净 stdout/stderr 后退出。
|
|
120
|
+
*
|
|
121
|
+
* 为什么需要:mihomo 不回应 WebSocket 的 close 帧(实测 v1.19.24),
|
|
122
|
+
* undici 的关闭握手永不完成,TCP 句柄一直挂在事件循环上,进程不会自然退出。
|
|
123
|
+
* 凡是用过 Stream 的命令都必须走这里显式退出。
|
|
124
|
+
*
|
|
125
|
+
* 手段说明:Node 官方没有 flush API,「写空串等回调」依赖流按 FIFO
|
|
126
|
+
* 处理写队列的实现行为(回调触发即代表其前的数据已处理完);该行为
|
|
127
|
+
* 无文档背书,故回调若因流异常永不触发,超时护栏保证进程仍能退出。
|
|
128
|
+
*/
|
|
129
|
+
const FLUSH_TIMEOUT_MS = 1000;
|
|
130
|
+
export async function exitAfterFlush(code) {
|
|
131
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
132
|
+
if (stream.writableLength > 0) {
|
|
133
|
+
await new Promise((resolve) => {
|
|
134
|
+
const timer = setTimeout(resolve, FLUSH_TIMEOUT_MS);
|
|
135
|
+
stream.write('', () => {
|
|
136
|
+
clearTimeout(timer);
|
|
137
|
+
resolve();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
process.exit(code);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 把异常翻译成一行人话 + 退出码。
|
|
146
|
+
* 业务错误(如延迟测试失败)由调用方自行处理,不该走到这里。
|
|
147
|
+
*/
|
|
148
|
+
export function reportError(err) {
|
|
149
|
+
if (err instanceof InvalidTargetError) {
|
|
150
|
+
process.stderr.write(`错误:${err.message}\n`);
|
|
151
|
+
process.exit(EXIT.usage);
|
|
152
|
+
}
|
|
153
|
+
if (err instanceof KernelUnreachableError) {
|
|
154
|
+
process.stderr.write(`错误:${err.message}\n`);
|
|
155
|
+
process.stderr.write('内核可能未运行,请排查:systemctl --user status mihomo\n');
|
|
156
|
+
process.exit(EXIT.unreachable);
|
|
157
|
+
}
|
|
158
|
+
if (err instanceof HttpStatusError) {
|
|
159
|
+
if (err.status === 401) {
|
|
160
|
+
process.stderr.write(`错误:控制口鉴权失败(HTTP 401),请检查 secret 配置:${CONFIG_PATH}\n`);
|
|
161
|
+
process.exit(EXIT.error);
|
|
162
|
+
}
|
|
163
|
+
if (err.status === 404) {
|
|
164
|
+
process.stderr.write(`错误:目标不存在(HTTP 404):${decodeURIComponent(err.path)}\n`);
|
|
165
|
+
process.exit(EXIT.error);
|
|
166
|
+
}
|
|
167
|
+
process.stderr.write(`错误:${err.message}\n`);
|
|
168
|
+
process.exit(EXIT.error);
|
|
169
|
+
}
|
|
170
|
+
if (err instanceof ApiBusinessError) {
|
|
171
|
+
process.stderr.write(`错误:${err.message}\n`);
|
|
172
|
+
process.exit(EXIT.error);
|
|
173
|
+
}
|
|
174
|
+
process.stderr.write(`错误:${err instanceof Error ? err.message : String(err)}\n`);
|
|
175
|
+
process.exit(EXIT.error);
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=output.js.map
|