@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/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
@@ -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