clash-kit 1.1.0 → 1.1.2

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.
@@ -1,73 +1,73 @@
1
- import { select } from '@inquirer/prompts'
2
- import ora from 'ora'
3
- import chalk from 'chalk'
4
- import * as api from '../api.js'
5
-
6
- export async function proxy() {
7
- let spinner = ora('正在获取最新代理列表...').start()
8
- try {
9
- let proxies = await api.getProxies()
10
- spinner.stop()
11
-
12
- // 通常我们只关心 Proxy 组或者 Selector 类型的组
13
- const groups = Object.values(proxies).filter(p => p.type === 'Selector')
14
-
15
- if (groups.length === 0) {
16
- console.log('没有找到可选的节点组')
17
- return
18
- }
19
-
20
- // 选择组
21
- const groupName = await select({
22
- message: '请选择节点组:',
23
- choices: groups.map(g => ({ name: g.name, value: g.name })),
24
- })
25
-
26
- const group = proxies[groupName]
27
-
28
- // 自动对组内所有节点进行测速
29
- spinner = ora(`测速后选择合适的节点,正在对 [${groupName}] 进行测速...`).start()
30
- await Promise.all(group.all.map(n => api.getProxyDelay(n).catch(() => {})))
31
-
32
- // 测速完成后,刷新数据以获取最新状态
33
- proxies = await api.getProxies()
34
- spinner.stop()
35
-
36
- const updatedGroup = proxies[groupName]
37
-
38
- // 选择节点
39
- const proxyName = await select({
40
- message: `[${groupName}] 当前: ${updatedGroup.now}, 请选择节点:`,
41
- pageSize: 15,
42
- choices: updatedGroup.all.map(n => {
43
- const node = proxies[n]
44
- const lastHistory = node?.history && node.history.length ? node.history[node.history.length - 1] : null
45
- let delayInfo = ''
46
-
47
- if (lastHistory && lastHistory.delay > 0) {
48
- const delay = lastHistory.delay
49
- if (delay < 800) {
50
- delayInfo = chalk.green(` ${delay}ms`)
51
- } else if (delay < 1500) {
52
- delayInfo = chalk.yellow(` ${delay}ms`)
53
- } else {
54
- delayInfo = chalk.red(` ${delay}ms`)
55
- }
56
- } else if (lastHistory && lastHistory.delay === 0) {
57
- delayInfo = chalk.red(' [超时]')
58
- } else {
59
- delayInfo = chalk.gray(' [未测速]')
60
- }
61
-
62
- return { name: `${n}${delayInfo}`, value: n }
63
- }),
64
- })
65
-
66
- spinner = ora(`正在切换到 ${proxyName}...`).start()
67
- await api.switchProxy(groupName, proxyName)
68
- spinner.succeed(`已切换 ${groupName} -> ${proxyName}`)
69
- } catch (err) {
70
- if (spinner && spinner.isSpinning) spinner.fail(err.message)
71
- else console.error(err.message)
72
- }
73
- }
1
+ import { select } from '@inquirer/prompts'
2
+ import ora from 'ora'
3
+ import chalk from 'chalk'
4
+ import * as api from '../api.js'
5
+
6
+ export async function proxy() {
7
+ let spinner = ora('正在获取最新代理列表...').start()
8
+ try {
9
+ let proxies = await api.getProxies()
10
+ spinner.stop()
11
+
12
+ // 通常我们只关心 Proxy 组或者 Selector 类型的组
13
+ const groups = Object.values(proxies).filter(p => p.type === 'Selector')
14
+
15
+ if (groups.length === 0) {
16
+ console.log('没有找到可选的节点组')
17
+ return
18
+ }
19
+
20
+ // 选择组
21
+ const groupName = await select({
22
+ message: '请选择节点组:',
23
+ choices: groups.map(g => ({ name: g.name, value: g.name })),
24
+ })
25
+
26
+ const group = proxies[groupName]
27
+
28
+ // 自动对组内所有节点进行测速
29
+ spinner = ora(`测速后选择合适的节点,正在对 [${groupName}] 进行测速...`).start()
30
+ await Promise.all(group.all.map(n => api.getProxyDelay(n).catch(() => {})))
31
+
32
+ // 测速完成后,刷新数据以获取最新状态
33
+ proxies = await api.getProxies()
34
+ spinner.stop()
35
+
36
+ const updatedGroup = proxies[groupName]
37
+
38
+ // 选择节点
39
+ const proxyName = await select({
40
+ message: `[${groupName}] 当前: ${updatedGroup.now}, 请选择节点:`,
41
+ pageSize: 15,
42
+ choices: updatedGroup.all.map(n => {
43
+ const node = proxies[n]
44
+ const lastHistory = node?.history && node.history.length ? node.history[node.history.length - 1] : null
45
+ let delayInfo = ''
46
+
47
+ if (lastHistory && lastHistory.delay > 0) {
48
+ const delay = lastHistory.delay
49
+ if (delay < 800) {
50
+ delayInfo = chalk.green(` ${delay}ms`)
51
+ } else if (delay < 1500) {
52
+ delayInfo = chalk.yellow(` ${delay}ms`)
53
+ } else {
54
+ delayInfo = chalk.red(` ${delay}ms`)
55
+ }
56
+ } else if (lastHistory && lastHistory.delay === 0) {
57
+ delayInfo = chalk.red(' [超时]')
58
+ } else {
59
+ delayInfo = chalk.gray(' [未测速]')
60
+ }
61
+
62
+ return { name: `${n}${delayInfo}`, value: n }
63
+ }),
64
+ })
65
+
66
+ spinner = ora(`正在切换到 ${proxyName}...`).start()
67
+ await api.switchProxy(groupName, proxyName)
68
+ spinner.succeed(`已切换 ${groupName} -> ${proxyName}`)
69
+ } catch (err) {
70
+ if (spinner && spinner.isSpinning) spinner.fail(err.message)
71
+ else console.error(err.message)
72
+ }
73
+ }
@@ -0,0 +1,10 @@
1
+ import { stop } from './stop.js'
2
+ import { start } from './start.js'
3
+
4
+ export async function restart(options) {
5
+ console.log('正在重启 Clash 服务...')
6
+ await stop()
7
+ // 稍微等待一下,确保资源释放
8
+ await new Promise(resolve => setTimeout(resolve, 1000))
9
+ await start(options)
10
+ }
@@ -1,30 +1,30 @@
1
- import * as sysproxy from '../sysproxy.js'
2
- import { main as startClashService } from '../../index.js'
3
- import { setTun } from './tun.js'
4
-
5
- export async function start(options) {
6
- await startClashService()
7
-
8
- const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
9
-
10
- if (options.sysproxy) {
11
- console.log('正在等待 Clash API 就绪以设置系统代理...')
12
-
13
- // 尝试 5 次,每次间隔 1 秒
14
- for (let i = 0; i < 5; i++) {
15
- await sleep(1000)
16
- try {
17
- const success = await sysproxy.enableSystemProxy()
18
- if (success) break
19
- } catch (e) {
20
- if (i === 4) console.error('设置系统代理超时,请稍后手动设置: clash sysproxy on')
21
- }
22
- }
23
- }
24
-
25
- if (options.tun) {
26
- // 稍微延迟一下,确保服务可能有响应
27
- await sleep(1000)
28
- await setTun('on')
29
- }
30
- }
1
+ import * as sysproxy from '../sysproxy.js'
2
+ import { main as startClashService } from '../../index.js'
3
+ import { setTun } from './tun.js'
4
+
5
+ export async function start(options) {
6
+ await startClashService()
7
+
8
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
9
+
10
+ if (options.sysproxy) {
11
+ console.log('正在等待 Clash API 就绪以设置系统代理...')
12
+
13
+ // 尝试 5 次,每次间隔 1 秒
14
+ for (let i = 0; i < 5; i++) {
15
+ await sleep(1000)
16
+ try {
17
+ const success = await sysproxy.enableSystemProxy()
18
+ if (success) break
19
+ } catch (e) {
20
+ if (i === 4) console.error('设置系统代理超时,请稍后手动设置: clash sysproxy on')
21
+ }
22
+ }
23
+ }
24
+
25
+ if (options.tun) {
26
+ // 稍微延迟一下,确保服务可能有响应
27
+ await sleep(1000)
28
+ await setTun('on')
29
+ }
30
+ }
@@ -1,85 +1,105 @@
1
- import ora from 'ora'
2
- import * as api from '../api.js'
3
- import * as sub from '../subscription.js'
4
- import * as tun from '../tun.js'
5
- import * as sysproxy from '../sysproxy.js'
6
-
7
- export async function status() {
8
- const spinner = ora('正在获取 Clash 状态...').start()
9
- try {
10
- const config = await api.getConfig()
11
- spinner.stop()
12
- const apiBase = api.getApiBase()
13
- const currentProfile = sub.getCurrentProfile()
14
-
15
- // 获取 TUN 和系统代理状态
16
- const tunEnabled = await tun.isTunEnabled()
17
- const sysProxyStatus = await sysproxy.getSystemProxyStatus()
18
-
19
- console.log('\n=== Clash 状态 ===')
20
- console.log(`状态: \x1b[32m运行中\x1b[0m`)
21
- console.log(`当前配置: ${currentProfile || '未知 (默认或手动修改)'}`)
22
- console.log(`API 地址: ${apiBase}`)
23
- console.log(`运行模式: ${config.mode}`)
24
- console.log(`HTTP 端口: ${config['port'] || '未设置'}`)
25
- console.log(`Socks5 端口: ${config['socks-port'] || '未设置'}`)
26
- if (config['mixed-port']) {
27
- console.log(`Mixed 端口: ${config['mixed-port']}`)
28
- }
29
-
30
- console.log('\n=== 代理模式 ===')
31
- // TUN 状态
32
- if (tunEnabled) {
33
- console.log(`TUN 模式: \x1b[32m已开启\x1b[0m`)
34
- } else {
35
- console.log(`TUN 模式: \x1b[90m未开启\x1b[0m`)
36
- }
37
- // 系统代理状态
38
- if (sysProxyStatus.enabled) {
39
- console.log(`系统代理: \x1b[32m已开启\x1b[0m (${sysProxyStatus.server}:${sysProxyStatus.port})`)
40
- } else {
41
- console.log(`系统代理: \x1b[90m未开启\x1b[0m`)
42
- }
43
-
44
- const proxies = await api.getProxies()
45
-
46
- // 过滤出 Selector 类型的代理组
47
- const selectors = Object.entries(proxies)
48
- .filter(([name, proxy]) => proxy.type === 'Selector')
49
- .map(([name, proxy]) => ({ name, now: proxy.now }))
50
-
51
- console.log('\n=== 节点信息 ===')
52
-
53
- if (selectors.length === 0) {
54
- console.log('未找到节点选择组。')
55
- } else {
56
- for (const { name, now } of selectors) {
57
- process.stdout.write(`组 [${name}] 当前节点: ${now} ... 测速中`)
58
- const testUrl = now === 'DIRECT' ? 'http://connect.rom.miui.com/generate_204' : undefined
59
- const delay = await api.getProxyDelay(now, testUrl)
60
-
61
- let delayStr = ''
62
- if (delay > 0) {
63
- if (delay < 200) delayStr = `\x1b[32m${delay}ms\x1b[0m`
64
- else if (delay < 500) delayStr = `\x1b[33m${delay}ms\x1b[0m`
65
- else delayStr = `\x1b[31m${delay}ms\x1b[0m`
66
- } else {
67
- delayStr = `\x1b[31m超时/失败\x1b[0m`
68
- }
69
-
70
- process.stdout.clearLine(0)
71
- process.stdout.cursorTo(0)
72
- console.log(`组 [${name}] 当前节点: ${now} - 延迟: ${delayStr}`)
73
- }
74
- }
75
- } catch (err) {
76
- if (spinner.isSpinning) spinner.stop()
77
- if (err.message && (err.message.includes('ECONNREFUSED') || err.message.includes('无法连接'))) {
78
- console.log('\n=== Clash 状态 ===')
79
- console.log(`状态: \x1b[31m未运行\x1b[0m`)
80
- console.log('提示: 请使用 `clash start` 启动服务')
81
- } else {
82
- console.error(`获取状态失败: ${err.message}`)
83
- }
84
- }
85
- }
1
+ import ora from 'ora'
2
+ import path from 'path'
3
+ import * as api from '../api.js'
4
+ import * as sub from '../subscription.js'
5
+ import * as tun from '../tun.js'
6
+ import * as sysproxy from '../sysproxy.js'
7
+ import { getClashProcessInfo } from '../kernel.js'
8
+ import chalk from 'chalk'
9
+ import boxen from 'boxen'
10
+
11
+ export async function status() {
12
+ const spinner = ora('正在获取 Clash 状态...').start()
13
+ try {
14
+ const [config, currentProfile, tunEnabled, sysProxyStatus, proxies, processInfo] = await Promise.all([
15
+ api.getConfig(),
16
+ sub.getCurrentProfile(),
17
+ tun.isTunEnabled(),
18
+ sysproxy.getSystemProxyStatus(),
19
+ api.getProxies(),
20
+ getClashProcessInfo(),
21
+ ])
22
+
23
+ spinner.stop()
24
+ const apiBase = api.getApiBase()
25
+ const configPath = sub.CONFIG_PATH
26
+ const logPath = path.resolve(path.dirname(configPath), 'clash.log')
27
+
28
+ const content = []
29
+ let statusLine = `状态:${chalk.green('运行中')}`
30
+ if (processInfo?.pid) {
31
+ statusLine += ` (PID: ${chalk.yellow(processInfo.pid)})`
32
+ }
33
+ content.push(statusLine)
34
+ content.push(`当前配置: ${currentProfile || '未知'}`)
35
+ content.push(`运行模式: ${config.mode}`)
36
+ content.push(`API 地址: ${chalk.cyan(apiBase)}`)
37
+ content.push(`HTTP 代理: ${chalk.cyan(`http://127.0.0.1:${config['port'] || '未设置'}`)}`)
38
+ content.push(`SOCKS5 代理: ${chalk.cyan(`socks5://127.0.0.1:${config['socks-port'] || '未设置'}`)}`)
39
+ content.push(
40
+ `混合代理: ${config['mixed-port'] ? chalk.cyan(`127.0.0.1:${config['mixed-port']}`) : chalk.gray('未设置')}`,
41
+ )
42
+ content.push('')
43
+ const sysProxyText = sysProxyStatus.enabled
44
+ ? chalk.green(`已开启 (${sysProxyStatus.server}:${sysProxyStatus.port})`)
45
+ : chalk.gray('未开启')
46
+ content.push(`系统代理: ${sysProxyText}`)
47
+ content.push(`TUN 模式(拦截所有流量): ${tunEnabled ? chalk.green('已开启') : chalk.gray('未开启')}`)
48
+ content.push('')
49
+ content.push(`日志文件: ${chalk.blueBright.underline(logPath)}`)
50
+ content.push(`配置文件: ${chalk.blueBright.underline(configPath)}`)
51
+ content.push('')
52
+
53
+ content.push('')
54
+ content.push(
55
+ `${chalk.gray('系统代理: ck sys <on|off> , TUN 模式: ck tun <on|off>')} , 更多命令: ${chalk.blue('ck help')}`,
56
+ )
57
+
58
+ console.log(
59
+ boxen(content.join('\n'), {
60
+ title: chalk.bold.bgGreen('Clash Kit'),
61
+ titleAlignment: 'center',
62
+ padding: 1,
63
+ borderStyle: 'bold',
64
+ borderColor: 'green',
65
+ margin: { top: 1, bottom: 0, left: 0, right: 0 },
66
+ }),
67
+ )
68
+
69
+ // 默认测速 Proxy 组的所有节点
70
+ const group = proxies['Proxy'] || Object.values(proxies).find(p => p.type === 'Selector')
71
+ if (group?.now) {
72
+ const testUrl = group.now === 'DIRECT' ? 'http://connect.rom.miui.com/generate_204' : undefined
73
+ const delay = await api.getProxyDelay(group.now, testUrl)
74
+ let delayStr = ''
75
+ if (delay > 0) {
76
+ if (delay < 200) delayStr = chalk.green(`${delay}ms`)
77
+ else if (delay < 500) delayStr = chalk.yellow(`${delay}ms`)
78
+ else delayStr = chalk.red(`${delay}ms`)
79
+ } else {
80
+ delayStr = chalk.red('超时/失败')
81
+ }
82
+ console.log(`🚀 节点延迟 [${group.name}: ${group.now}]: ${delayStr}`)
83
+ }
84
+ } catch (err) {
85
+ if (spinner.isSpinning) spinner.stop()
86
+ if (err.message && (err.message.includes('ECONNREFUSED') || err.message.includes('无法连接'))) {
87
+ const configPath = sub.CONFIG_PATH
88
+ const content = []
89
+ content.push(`状态:${chalk.yellow('未运行')}`)
90
+ content.push('提示:请使用 `ck start` 启动服务')
91
+ content.push(`当前配置文件: ${configPath || '未知'}`)
92
+ console.log(
93
+ boxen(content.join('\n'), {
94
+ title: chalk.bold.bgYellow('Clash Kit'),
95
+ titleAlignment: 'center',
96
+ padding: 1,
97
+ borderStyle: 'single',
98
+ margin: { top: 1, bottom: 1, left: 0, right: 0 },
99
+ }),
100
+ )
101
+ } else {
102
+ console.error(`获取状态失败: ${err.message}`)
103
+ }
104
+ }
105
+ }
@@ -1,30 +1,67 @@
1
- import ora from 'ora'
2
- import * as sysproxy from '../sysproxy.js'
3
- import * as tun from '../tun.js'
4
- import { killClashProcess } from '../kernel.js'
5
-
6
- export async function stop() {
7
- const spinner = ora('正在停止 Clash 服务...').start()
8
- try {
9
- // 停止前先关闭系统代理
10
- await sysproxy.disableSystemProxy()
11
-
12
- // 检查并关闭 TUN 模式
13
- const tunEnabled = await tun.isTunEnabled()
14
- if (tunEnabled) {
15
- spinner.text = '正在关闭 TUN 模式...'
16
- await tun.disableTun()
17
- console.log('TUN 模式已关闭')
18
- }
19
-
20
- // 使用公共函数停止进程
21
- spinner.text = '正在停止 Clash 服务...'
22
- if (killClashProcess()) {
23
- spinner.succeed('Clash 服务已停止')
24
- } else {
25
- spinner.warn('未找到运行中的 Clash 服务,或已停止')
26
- }
27
- } catch (err) {
28
- spinner.warn(`停止服务时出错: ${err.message}`)
29
- }
30
- }
1
+ import ora from 'ora'
2
+ import * as sysproxy from '../sysproxy.js'
3
+ import * as tun from '../tun.js'
4
+ import { setTun } from './tun.js'
5
+ import { killClashProcess } from '../kernel.js'
6
+ import boxen from 'boxen'
7
+ import chalk from 'chalk'
8
+
9
+ export async function stop() {
10
+ const spinner = ora('正在停止服务...').start()
11
+ try {
12
+ let wasTunEnabled = false
13
+
14
+ // 1. 关闭系统代理
15
+ spinner.text = '正在关闭系统代理...'
16
+ await sysproxy.disableSystemProxy()
17
+
18
+ // 2. 检查并关闭 TUN 模式
19
+ const tunEnabled = await tun.isTunEnabled()
20
+ if (tunEnabled) {
21
+ wasTunEnabled = true
22
+ spinner.text = '正在关闭 TUN 模式...'
23
+ const result = await setTun('off', { silent: true })
24
+ if (!result.success) {
25
+ throw new Error(`关闭 TUN 模式失败: ${result.error}`)
26
+ }
27
+ }
28
+
29
+ // 3. 停止 Clash 核心进程
30
+ spinner.text = '正在停止 Clash 核心进程...'
31
+ const stopped = killClashProcess()
32
+ spinner.stop() // All processing is done, stop spinner
33
+
34
+ // 4. 显示最终结果
35
+ if (stopped) {
36
+ const content = []
37
+ content.push(`Clash 服务: ${chalk.yellow('已停止')}`)
38
+ content.push(`系统代理: ${chalk.gray('已关闭')}`)
39
+ if (wasTunEnabled) content.push(`TUN 模式: ${chalk.gray('已关闭 (DNS 已恢复)')}`)
40
+
41
+ console.log(
42
+ boxen(content.join('\n'), {
43
+ title: 'Clash Kit',
44
+ padding: 1,
45
+ margin: 1,
46
+ borderStyle: 'round',
47
+ borderColor: 'green',
48
+ titleAlignment: 'center',
49
+ }),
50
+ )
51
+ } else {
52
+ console.log(
53
+ boxen(chalk.yellow('未找到运行中的 Clash 服务'), {
54
+ title: 'Clash Kit',
55
+ padding: 1,
56
+ margin: 1,
57
+ borderStyle: 'round',
58
+ borderColor: 'yellow',
59
+ titleAlignment: 'center',
60
+ }),
61
+ )
62
+ }
63
+ } catch (err) {
64
+ if (spinner.isSpinning) spinner.stop()
65
+ console.error(`\n停止服务时出错: ${err.message}`)
66
+ }
67
+ }