clash-kit 1.1.2 → 1.1.5
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/README.md +50 -24
- package/bin/index.js +17 -0
- package/lib/api.js +1 -1
- package/lib/commands/init.js +6 -0
- package/lib/commands/proxy.js +34 -26
- package/lib/commands/start.js +17 -9
- package/lib/commands/status.js +28 -7
- package/lib/commands/stop.js +8 -8
- package/lib/commands/sub.js +57 -14
- package/lib/commands/test.js +16 -12
- package/lib/commands/tun.js +3 -3
- package/{index.js → lib/service.js} +15 -19
- package/lib/subscription.js +24 -0
- package/lib/sysnet.js +4 -27
- package/lib/tun.js +1 -25
- package/package.json +2 -3
- package/bin/dns-helper +0 -0
package/README.md
CHANGED
|
@@ -7,15 +7,13 @@
|
|
|
7
7
|
|
|
8
8
|
目前已兼容 Windows,MacOs,Linux。
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<img width="1920" alt="image" src="https://github.com/user-attachments/assets/1183f778-62b0-4ac7-ab55-b821b66161f0" />
|
|
10
|
+
<img width="604" height="315" alt="image" src="https://github.com/user-attachments/assets/7c97ef51-5a95-4612-aa43-ce66423d7560" />
|
|
13
11
|
|
|
14
12
|
## 特性
|
|
15
13
|
|
|
16
|
-
- 🔄
|
|
14
|
+
- 🔄 **订阅管理**:支持添加、切换、修改、删除多个订阅源。
|
|
17
15
|
- 🌐 **节点切换**:交互式选择并切换当前使用的代理节点。
|
|
18
|
-
- 🔥
|
|
16
|
+
- 🔥 **热重载**:切换订阅/开关 TUN 后立即生效,无需重启服务。
|
|
19
17
|
- ⚡ **节点测速**:支持多线程并发测速,彩色高亮显示延迟结果。
|
|
20
18
|
- 📊 **状态监控**:实时查看服务运行状态、当前节点及延迟。
|
|
21
19
|
- 🛠 **自动初始化**:自动处理二进制文件权限问题。
|
|
@@ -23,39 +21,63 @@
|
|
|
23
21
|
- 💻 **系统代理**:一键开启/关闭 macOS 系统 HTTP 代理。
|
|
24
22
|
- 🛡 **TUN 模式**:高级网络模式,接管系统所有流量(真 · 全局代理)。
|
|
25
23
|
|
|
26
|
-
##
|
|
24
|
+
## 安装
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
- 支持通过 npm 或其它任意包管理器全局安装:
|
|
29
27
|
|
|
30
28
|
```bash
|
|
31
29
|
npm install -g clash-kit
|
|
32
30
|
# 或者
|
|
33
31
|
pnpm add -g clash-kit
|
|
32
|
+
# 或者
|
|
33
|
+
yarn global add clash-kit
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- 也支持通过 Homebrew 安装:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 1. 先通过 brew tap 添加仓库
|
|
40
|
+
brew tap wangrongding/clash-kit https://github.com/wangrongding/clash-kit
|
|
41
|
+
# 2. 安装 clash-kit
|
|
42
|
+
brew install clash-kit
|
|
34
43
|
```
|
|
35
44
|
|
|
36
|
-
|
|
45
|
+
## 使用
|
|
46
|
+
|
|
47
|
+
### 1. 初始化
|
|
37
48
|
|
|
38
49
|
首次安装后,需要先初始化 Clash 内核与权限:
|
|
39
50
|
|
|
40
51
|
```bash
|
|
41
|
-
clash init
|
|
42
52
|
# 推荐用简化命令(文档后续均以简化命令为例)
|
|
43
|
-
ck init
|
|
53
|
+
ck init # 或者 clash init
|
|
44
54
|
```
|
|
45
55
|
|
|
46
|
-
### 3.
|
|
56
|
+
### 3. 管理订阅
|
|
47
57
|
|
|
48
58
|
```bash
|
|
49
|
-
#
|
|
59
|
+
# 交互式管理订阅(添加、切换、修改、删除)【推荐】
|
|
50
60
|
ck sub
|
|
51
61
|
|
|
52
62
|
# 列出所有订阅
|
|
53
63
|
ck sub -l
|
|
54
64
|
|
|
55
|
-
#
|
|
56
|
-
ck sub -a "https://example.com/subscribe?token=xxx"
|
|
65
|
+
# 手动添加订阅(-n 名称,-a 链接)
|
|
66
|
+
ck sub -n "test123" -a "https://example.com/subscribe?token=xxx"
|
|
67
|
+
|
|
68
|
+
# 手动切换订阅
|
|
69
|
+
ck sub -u "test123"
|
|
57
70
|
```
|
|
58
71
|
|
|
72
|
+
交互式模式 (`ck sub`) 提供以下操作:
|
|
73
|
+
|
|
74
|
+
| 操作 | 说明 |
|
|
75
|
+
| -------- | ------------------------------------------ |
|
|
76
|
+
| 切换订阅 | 从现有订阅中选择并立即生效(热重载) |
|
|
77
|
+
| 添加订阅 | 依次输入名称和链接,完成下载 |
|
|
78
|
+
| 修改订阅 | 重命名 和/或 更换订阅链接(均可留空跳过) |
|
|
79
|
+
| 删除订阅 | 选择后需二次确认,若删除当前订阅会自动解绑 |
|
|
80
|
+
|
|
59
81
|
### 4. 启动服务
|
|
60
82
|
|
|
61
83
|
启动 Clash 核心服务(建议在一个单独的终端窗口运行):
|
|
@@ -68,35 +90,35 @@ ck on # 或者 ck start
|
|
|
68
90
|
ck on -s
|
|
69
91
|
# 启动并自动开启 TUN 模式(全局代理, 需要 sudo 权限)
|
|
70
92
|
ck on -t
|
|
93
|
+
```
|
|
71
94
|
|
|
72
|
-
|
|
95
|
+
### 5. 关闭服务 或 重新启动服务
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# 关闭服务
|
|
73
99
|
ck off # 或者 ck stop
|
|
74
100
|
|
|
75
101
|
# 重新启动服务
|
|
76
102
|
ck rs # 或者 ck restart
|
|
77
103
|
```
|
|
78
104
|
|
|
79
|
-
###
|
|
105
|
+
### 6. 节点切换(自动测速)
|
|
80
106
|
|
|
81
107
|
进入交互式界面,自动对当前节点组进行并发测速,并展示带有即时延迟数据的节点列表供选择。
|
|
82
108
|
|
|
83
109
|
```bash
|
|
84
110
|
# 切换节点 (支持别名: node, proxy, switch)
|
|
85
111
|
ck use
|
|
86
|
-
# 或者
|
|
87
|
-
ck switch
|
|
88
|
-
# 或者
|
|
89
|
-
ck node
|
|
90
112
|
```
|
|
91
113
|
|
|
92
|
-
###
|
|
114
|
+
### 7. 更多功能
|
|
93
115
|
|
|
94
116
|
```bash
|
|
95
117
|
# 查看状态
|
|
96
118
|
ck info # 或者 ck status, ck view
|
|
97
119
|
|
|
98
120
|
# 节点并发测速 (仅测速不切换,支持别名: test, ls, t)
|
|
99
|
-
ck
|
|
121
|
+
ck ls # 或者 ck list,ck test,ck t
|
|
100
122
|
|
|
101
123
|
# 设置系统代理
|
|
102
124
|
ck sys on
|
|
@@ -117,14 +139,18 @@ ck tun off # 关闭
|
|
|
117
139
|
| `ck rs` (`restart`) | 重启 Clash 服务 | `ck rs` `ck rs -s` (重启并设置系统代理) `ck rs -t` (重启并开启 TUN 模式) |
|
|
118
140
|
| `ck info` (`status`, `view`) | 查看运行状态及当前节点延迟 | `ck info` / `ck status` / `ck view` |
|
|
119
141
|
| `ck sysproxy` (`sys`) | 设置系统代理 | `ck sys on` / `ck sys off` |
|
|
120
|
-
| `ck tun` | 设置 TUN 模式 (需要 sudo) | `ck tun on`
|
|
142
|
+
| `ck tun` | 设置 TUN 模式 (需要 sudo) | `ck tun on` / `ck tun off` |
|
|
121
143
|
| `ck sub` | 管理订阅(交互式)【推荐】 | `ck sub` |
|
|
122
144
|
| `ck sub -l` | 列出所有订阅 | `ck sub -l` |
|
|
123
|
-
| `ck sub -a <url>`
|
|
145
|
+
| `ck sub -n <name> -a <url>` | 添加订阅 | `ck sub -n "pro" -a "http..."` |
|
|
124
146
|
| `ck sub -u <name>` | 切换订阅 | `ck sub -u "pro"` |
|
|
125
147
|
| `ck use` (`node`, `switch`) | 切换节点 (自动测速) | `ck use` / `ck node` |
|
|
126
148
|
| `ck list` (`ls`, `test`, `t`) | 节点测速列表 (不切换) | `ck list` / `ck test` |
|
|
127
149
|
|
|
150
|
+
## 截图
|
|
151
|
+
|
|
152
|
+
<img width="1920" alt="image" src="https://github.com/user-attachments/assets/1183f778-62b0-4ac7-ab55-b821b66161f0" />
|
|
153
|
+
|
|
128
154
|
## License
|
|
129
155
|
|
|
130
156
|
[MIT](./LICENSE).
|
package/bin/index.js
CHANGED
|
@@ -12,10 +12,26 @@ import { status } from '../lib/commands/status.js'
|
|
|
12
12
|
import { manageSub } from '../lib/commands/sub.js'
|
|
13
13
|
import { proxy } from '../lib/commands/proxy.js'
|
|
14
14
|
import { test } from '../lib/commands/test.js'
|
|
15
|
+
import updateNotifier from 'update-notifier'
|
|
15
16
|
|
|
16
17
|
const require = createRequire(import.meta.url)
|
|
17
18
|
const pkg = require('../package.json')
|
|
18
19
|
|
|
20
|
+
updateNotifier({
|
|
21
|
+
pkg: pkg,
|
|
22
|
+
updateCheckInterval: 1000 * 60 * 60 * 24, // 检查更新间隔,1 day
|
|
23
|
+
shouldNotifyInNpmScript: true,
|
|
24
|
+
}).notify({
|
|
25
|
+
isGlobal: true,
|
|
26
|
+
boxenOptions: {
|
|
27
|
+
title: '有新版本的 Clash Kit 可用',
|
|
28
|
+
padding: 1,
|
|
29
|
+
margin: 1,
|
|
30
|
+
align: 'center',
|
|
31
|
+
borderColor: 'yellowBright',
|
|
32
|
+
borderStyle: 'round',
|
|
33
|
+
},
|
|
34
|
+
})
|
|
19
35
|
const program = new Command()
|
|
20
36
|
|
|
21
37
|
program.name('clash').alias('ck').description('Clash CLI 管理工具 (Alias: ck)').version(pkg.version, '-v, --version')
|
|
@@ -71,6 +87,7 @@ program
|
|
|
71
87
|
.option('-n, --name <name>', '订阅名称')
|
|
72
88
|
.option('-l, --list', '列出所有订阅')
|
|
73
89
|
.option('-u, --use <name>', '切换使用的订阅')
|
|
90
|
+
.option('-d, --delete <name>', '删除订阅')
|
|
74
91
|
.action(manageSub)
|
|
75
92
|
|
|
76
93
|
// 切换节点
|
package/lib/api.js
CHANGED
|
@@ -76,7 +76,7 @@ export async function getProxyDelay(proxyName, testUrl = 'http://www.gstatic.com
|
|
|
76
76
|
try {
|
|
77
77
|
const res = await axios.get(`${getApiBase()}/proxies/${encodeURIComponent(proxyName)}/delay`, {
|
|
78
78
|
params: {
|
|
79
|
-
timeout:
|
|
79
|
+
timeout: 3000,
|
|
80
80
|
url: testUrl,
|
|
81
81
|
},
|
|
82
82
|
headers,
|
package/lib/commands/init.js
CHANGED
|
@@ -120,6 +120,12 @@ export async function init(options) {
|
|
|
120
120
|
console.log('正在初始化 Clash 内核...')
|
|
121
121
|
await downloadClash(rootDir)
|
|
122
122
|
console.log('Clash 内核初始化成功!')
|
|
123
|
+
|
|
124
|
+
console.log(chalk.bold.green('\n✅ 初始化完成!接下来:'))
|
|
125
|
+
console.log(chalk.cyan(' 1. ck sub ') + chalk.gray('添加订阅'))
|
|
126
|
+
console.log(chalk.cyan(' 2. ck on ') + chalk.gray('启动 Clash 服务'))
|
|
127
|
+
console.log(chalk.cyan(' 3. ck sys on ') + chalk.gray('开启系统代理'))
|
|
128
|
+
console.log(chalk.gray('\n 更多帮助: ck help\n'))
|
|
123
129
|
} catch (err) {
|
|
124
130
|
console.error(`初始化失败: ${err.message}`)
|
|
125
131
|
process.exit(1)
|
package/lib/commands/proxy.js
CHANGED
|
@@ -8,8 +8,6 @@ export async function proxy() {
|
|
|
8
8
|
try {
|
|
9
9
|
let proxies = await api.getProxies()
|
|
10
10
|
spinner.stop()
|
|
11
|
-
|
|
12
|
-
// 通常我们只关心 Proxy 组或者 Selector 类型的组
|
|
13
11
|
const groups = Object.values(proxies).filter(p => p.type === 'Selector')
|
|
14
12
|
|
|
15
13
|
if (groups.length === 0) {
|
|
@@ -35,39 +33,49 @@ export async function proxy() {
|
|
|
35
33
|
|
|
36
34
|
const updatedGroup = proxies[groupName]
|
|
37
35
|
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const lastHistory = node?.history && node.history.length ? node.history[node.history.length - 1] : null
|
|
45
|
-
let delayInfo = ''
|
|
36
|
+
// 构建节点条目并按延迟排序,超时/未测速排最后
|
|
37
|
+
const nodeEntries = updatedGroup.all.map(n => {
|
|
38
|
+
const node = proxies[n]
|
|
39
|
+
const lastHistory = node?.history && node.history.length ? node.history[node.history.length - 1] : null
|
|
40
|
+
let delay = Infinity
|
|
41
|
+
let delayInfo = ''
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
} else {
|
|
54
|
-
delayInfo = chalk.red(` ${delay}ms`)
|
|
55
|
-
}
|
|
56
|
-
} else if (lastHistory && lastHistory.delay === 0) {
|
|
57
|
-
delayInfo = chalk.red(' [超时]')
|
|
43
|
+
if (lastHistory && lastHistory.delay > 0) {
|
|
44
|
+
delay = lastHistory.delay
|
|
45
|
+
if (delay < 200) {
|
|
46
|
+
delayInfo = chalk.green(` ${delay}ms`)
|
|
47
|
+
} else if (delay < 800) {
|
|
48
|
+
delayInfo = chalk.yellow(` ${delay}ms`)
|
|
58
49
|
} else {
|
|
59
|
-
delayInfo = chalk.
|
|
50
|
+
delayInfo = chalk.red(` ${delay}ms`)
|
|
60
51
|
}
|
|
52
|
+
} else if (lastHistory && lastHistory.delay === 0) {
|
|
53
|
+
delayInfo = chalk.red(' [超时]')
|
|
54
|
+
} else {
|
|
55
|
+
delayInfo = chalk.gray(' [未测速]')
|
|
56
|
+
}
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
return { name: `${n}${delayInfo}`, value: n, delay }
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
nodeEntries.sort((a, b) => a.delay - b.delay)
|
|
62
|
+
|
|
63
|
+
// 选择节点
|
|
64
|
+
const proxyName = await select({
|
|
65
|
+
message: `[${groupName}] 当前: ${updatedGroup.now}, 请选择节点:`,
|
|
66
|
+
pageSize: 15,
|
|
67
|
+
choices: nodeEntries.map(({ name, value }) => ({ name, value })),
|
|
64
68
|
})
|
|
65
69
|
|
|
66
70
|
spinner = ora(`正在切换到 ${proxyName}...`).start()
|
|
67
71
|
await api.switchProxy(groupName, proxyName)
|
|
68
72
|
spinner.succeed(`已切换 ${groupName} -> ${proxyName}`)
|
|
69
73
|
} catch (err) {
|
|
70
|
-
if (spinner && spinner.isSpinning) spinner.
|
|
71
|
-
|
|
74
|
+
if (spinner && spinner.isSpinning) spinner.stop()
|
|
75
|
+
if (err.message && (err.message.includes('ECONNREFUSED') || err.message.includes('无法连接'))) {
|
|
76
|
+
console.error(chalk.red('\nClash 服务未运行,请先执行: ck start\n'))
|
|
77
|
+
} else {
|
|
78
|
+
console.error(chalk.red(err.message))
|
|
79
|
+
}
|
|
72
80
|
}
|
|
73
81
|
}
|
package/lib/commands/start.js
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
import * as sysproxy from '../sysproxy.js'
|
|
2
|
-
import { main as startClashService } from '
|
|
2
|
+
import { main as startClashService } from '../service.js'
|
|
3
3
|
import { setTun } from './tun.js'
|
|
4
|
+
import ora from 'ora'
|
|
5
|
+
import boxen from 'boxen'
|
|
6
|
+
import chalk from 'chalk'
|
|
4
7
|
|
|
5
8
|
export async function start(options) {
|
|
9
|
+
const spinner = ora('正在启动 Clash 服务...').start()
|
|
6
10
|
await startClashService()
|
|
11
|
+
spinner.stop()
|
|
7
12
|
|
|
8
13
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
9
14
|
|
|
10
15
|
if (options.sysproxy) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// 尝试 5 次,每次间隔 1 秒
|
|
16
|
+
const sysSpinner = ora('正在等待 Clash API 就绪以设置系统代理...').start()
|
|
17
|
+
let sysOk = false
|
|
14
18
|
for (let i = 0; i < 5; i++) {
|
|
15
19
|
await sleep(1000)
|
|
16
20
|
try {
|
|
17
|
-
const
|
|
18
|
-
if (success)
|
|
21
|
+
const result = await sysproxy.enableSystemProxy()
|
|
22
|
+
if (result.success) {
|
|
23
|
+
sysSpinner.succeed(`系统代理已开启: ${result.host}:${result.port}`)
|
|
24
|
+
sysOk = true
|
|
25
|
+
break
|
|
26
|
+
}
|
|
19
27
|
} catch (e) {
|
|
20
|
-
|
|
28
|
+
// 继续重试
|
|
21
29
|
}
|
|
22
30
|
}
|
|
31
|
+
if (!sysOk) sysSpinner.fail('设置系统代理超时,请稍后手动执行: ck sys on')
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
if (options.tun) {
|
|
26
|
-
|
|
27
|
-
await sleep(1000)
|
|
35
|
+
await sleep(500)
|
|
28
36
|
await setTun('on')
|
|
29
37
|
}
|
|
30
38
|
}
|
package/lib/commands/status.js
CHANGED
|
@@ -25,13 +25,28 @@ export async function status() {
|
|
|
25
25
|
const configPath = sub.CONFIG_PATH
|
|
26
26
|
const logPath = path.resolve(path.dirname(configPath), 'clash.log')
|
|
27
27
|
|
|
28
|
+
// 订阅节点数
|
|
29
|
+
let profileNodeCount = ''
|
|
30
|
+
if (currentProfile) {
|
|
31
|
+
try {
|
|
32
|
+
const profilePath = path.join(path.dirname(configPath), 'profiles', `${currentProfile}.yaml`)
|
|
33
|
+
const YAML = (await import('yaml')).default
|
|
34
|
+
const fs = (await import('fs')).default
|
|
35
|
+
if (fs.existsSync(profilePath)) {
|
|
36
|
+
const parsed = YAML.parse(fs.readFileSync(profilePath, 'utf8'))
|
|
37
|
+
const count = parsed?.proxies?.length || 0
|
|
38
|
+
if (count > 0) profileNodeCount = chalk.gray(` (${count} 个节点)`)
|
|
39
|
+
}
|
|
40
|
+
} catch {}
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
const content = []
|
|
29
44
|
let statusLine = `状态:${chalk.green('运行中')}`
|
|
30
45
|
if (processInfo?.pid) {
|
|
31
46
|
statusLine += ` (PID: ${chalk.yellow(processInfo.pid)})`
|
|
32
47
|
}
|
|
33
48
|
content.push(statusLine)
|
|
34
|
-
content.push(`当前配置: ${currentProfile || '未知'}`)
|
|
49
|
+
content.push(`当前配置: ${currentProfile || '未知'}${profileNodeCount}`)
|
|
35
50
|
content.push(`运行模式: ${config.mode}`)
|
|
36
51
|
content.push(`API 地址: ${chalk.cyan(apiBase)}`)
|
|
37
52
|
content.push(`HTTP 代理: ${chalk.cyan(`http://127.0.0.1:${config['port'] || '未设置'}`)}`)
|
|
@@ -40,14 +55,20 @@ export async function status() {
|
|
|
40
55
|
`混合代理: ${config['mixed-port'] ? chalk.cyan(`127.0.0.1:${config['mixed-port']}`) : chalk.gray('未设置')}`,
|
|
41
56
|
)
|
|
42
57
|
content.push('')
|
|
43
|
-
const sysProxyText = sysProxyStatus
|
|
58
|
+
const sysProxyText = sysProxyStatus?.enabled
|
|
44
59
|
? chalk.green(`已开启 (${sysProxyStatus.server}:${sysProxyStatus.port})`)
|
|
45
60
|
: chalk.gray('未开启')
|
|
46
61
|
content.push(`系统代理: ${sysProxyText}`)
|
|
47
62
|
content.push(`TUN 模式(拦截所有流量): ${tunEnabled ? chalk.green('已开启') : chalk.gray('未开启')}`)
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
if (tunEnabled && config.dns) {
|
|
64
|
+
const dnsServers = config.dns.nameserver?.slice(0, 2).join(', ') || ''
|
|
65
|
+
const dnsMode = config.dns['enhanced-mode'] || ''
|
|
66
|
+
content.push(
|
|
67
|
+
`DNS 模式: ${chalk.cyan(dnsMode || '默认')}${dnsServers ? ' 服务器: ' + chalk.cyan(dnsServers) : ''}`,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
50
70
|
content.push(`配置文件: ${chalk.blueBright.underline(configPath)}`)
|
|
71
|
+
content.push(`日志文件: ${chalk.blueBright.underline(logPath)}`)
|
|
51
72
|
content.push('')
|
|
52
73
|
|
|
53
74
|
content.push('')
|
|
@@ -57,7 +78,7 @@ export async function status() {
|
|
|
57
78
|
|
|
58
79
|
console.log(
|
|
59
80
|
boxen(content.join('\n'), {
|
|
60
|
-
title: chalk.bold.bgGreen('Clash Kit'),
|
|
81
|
+
title: chalk.bold.whiteBright.bgGreen(' Clash Kit '),
|
|
61
82
|
titleAlignment: 'center',
|
|
62
83
|
padding: 1,
|
|
63
84
|
borderStyle: 'bold',
|
|
@@ -79,7 +100,7 @@ export async function status() {
|
|
|
79
100
|
} else {
|
|
80
101
|
delayStr = chalk.red('超时/失败')
|
|
81
102
|
}
|
|
82
|
-
console.log(`🚀 节点延迟 [${group.name}: ${group.now}]: ${delayStr}`)
|
|
103
|
+
console.log(`🚀 节点延迟 [${group.name}: ${group.now}]: ${delayStr}\n`)
|
|
83
104
|
}
|
|
84
105
|
} catch (err) {
|
|
85
106
|
if (spinner.isSpinning) spinner.stop()
|
|
@@ -91,7 +112,7 @@ export async function status() {
|
|
|
91
112
|
content.push(`当前配置文件: ${configPath || '未知'}`)
|
|
92
113
|
console.log(
|
|
93
114
|
boxen(content.join('\n'), {
|
|
94
|
-
title: chalk.bold.bgYellow('Clash Kit'),
|
|
115
|
+
title: chalk.bold.bgYellow(' Clash Kit '),
|
|
95
116
|
titleAlignment: 'center',
|
|
96
117
|
padding: 1,
|
|
97
118
|
borderStyle: 'single',
|
package/lib/commands/stop.js
CHANGED
|
@@ -11,18 +11,18 @@ export async function stop() {
|
|
|
11
11
|
try {
|
|
12
12
|
let wasTunEnabled = false
|
|
13
13
|
|
|
14
|
-
// 1.
|
|
14
|
+
// 1. 关闭系统代理(失败不中断)
|
|
15
15
|
spinner.text = '正在关闭系统代理...'
|
|
16
|
-
await sysproxy.disableSystemProxy()
|
|
16
|
+
await sysproxy.disableSystemProxy().catch(() => {})
|
|
17
17
|
|
|
18
|
-
// 2. 检查并关闭 TUN
|
|
19
|
-
const tunEnabled = await tun.isTunEnabled()
|
|
18
|
+
// 2. 检查并关闭 TUN 模式(失败不中断,继续杀进程)
|
|
19
|
+
const tunEnabled = await tun.isTunEnabled().catch(() => false)
|
|
20
20
|
if (tunEnabled) {
|
|
21
21
|
wasTunEnabled = true
|
|
22
22
|
spinner.text = '正在关闭 TUN 模式...'
|
|
23
|
-
const result = await setTun('off', { silent: true })
|
|
23
|
+
const result = await setTun('off', { silent: true }).catch(() => ({ success: false }))
|
|
24
24
|
if (!result.success) {
|
|
25
|
-
|
|
25
|
+
spinner.text = 'TUN 关闭失败,继续停止进程...'
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -40,7 +40,7 @@ export async function stop() {
|
|
|
40
40
|
|
|
41
41
|
console.log(
|
|
42
42
|
boxen(content.join('\n'), {
|
|
43
|
-
title: 'Clash Kit',
|
|
43
|
+
title: ' Clash Kit ',
|
|
44
44
|
padding: 1,
|
|
45
45
|
margin: 1,
|
|
46
46
|
borderStyle: 'round',
|
|
@@ -51,7 +51,7 @@ export async function stop() {
|
|
|
51
51
|
} else {
|
|
52
52
|
console.log(
|
|
53
53
|
boxen(chalk.yellow('未找到运行中的 Clash 服务'), {
|
|
54
|
-
title: 'Clash Kit',
|
|
54
|
+
title: ' Clash Kit ',
|
|
55
55
|
padding: 1,
|
|
56
56
|
margin: 1,
|
|
57
57
|
borderStyle: 'round',
|
package/lib/commands/sub.js
CHANGED
|
@@ -4,7 +4,8 @@ import fs from 'fs'
|
|
|
4
4
|
import { fileURLToPath } from 'url'
|
|
5
5
|
import chalk from 'chalk'
|
|
6
6
|
import * as sub from '../subscription.js'
|
|
7
|
-
import {
|
|
7
|
+
import { confirm } from '@inquirer/prompts'
|
|
8
|
+
import { CLASH_BIN_PATH } from '../service.js'
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url)
|
|
10
11
|
const __dirname = path.dirname(__filename)
|
|
@@ -45,37 +46,79 @@ export async function manageSub(options) {
|
|
|
45
46
|
} catch (err) {
|
|
46
47
|
console.error(err.message)
|
|
47
48
|
}
|
|
49
|
+
} else if (options.delete) {
|
|
50
|
+
console.log('🌸🌸🌸 / options: ', options)
|
|
51
|
+
try {
|
|
52
|
+
sub.deleteProfile(options.delete)
|
|
53
|
+
console.log(chalk.green(`订阅 "${options.delete}" 已删除`))
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error(chalk.red(err.message))
|
|
56
|
+
}
|
|
48
57
|
} else {
|
|
49
58
|
// 交互式模式
|
|
50
59
|
const profiles = sub.listProfiles()
|
|
51
60
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
const choices = [
|
|
62
|
+
{ name: '切换订阅', value: 'switch' },
|
|
63
|
+
{ name: '添加订阅', value: 'add' },
|
|
64
|
+
{ name: '修改订阅', value: 'edit' },
|
|
65
|
+
{ name: '删除订阅', value: 'delete' },
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
const action = await select({ message: '请选择操作:', choices })
|
|
59
69
|
|
|
60
70
|
if (action === 'switch') {
|
|
61
|
-
if (profiles.length === 0)
|
|
62
|
-
console.log('暂无订阅,请先添加')
|
|
63
|
-
return
|
|
64
|
-
}
|
|
71
|
+
if (profiles.length === 0) return console.log('暂无订阅,请先添加')
|
|
65
72
|
const profile = await select({
|
|
66
73
|
message: '选择要使用的订阅:',
|
|
67
74
|
choices: profiles.map(p => ({ name: p, value: p })),
|
|
68
75
|
})
|
|
69
76
|
await sub.useProfile(profile)
|
|
70
77
|
} else if (action === 'add') {
|
|
71
|
-
const url = await input({ message: '请输入订阅链接:' })
|
|
72
78
|
const name = await input({ message: '请输入订阅名称:' })
|
|
73
|
-
|
|
79
|
+
const url = await input({ message: '请输入订阅链接:' })
|
|
74
80
|
try {
|
|
75
81
|
await handleAddSubscription(url, name)
|
|
76
82
|
} catch (err) {
|
|
77
83
|
console.error(err.message)
|
|
78
84
|
}
|
|
85
|
+
} else if (action === 'edit') {
|
|
86
|
+
if (profiles.length === 0) return console.log('暂无订阅,请先添加')
|
|
87
|
+
const profile = await select({
|
|
88
|
+
message: '选择要修改的订阅:',
|
|
89
|
+
choices: profiles.map(p => ({ name: p, value: p })),
|
|
90
|
+
})
|
|
91
|
+
const newName = await input({ message: `新名称 (留空保持 "${profile}" 不变):` })
|
|
92
|
+
const newUrl = await input({ message: '新订阅链接 (留空则不重新下载):' })
|
|
93
|
+
const finalName = newName.trim() || profile
|
|
94
|
+
try {
|
|
95
|
+
if (newName.trim() && newName.trim() !== profile) {
|
|
96
|
+
sub.renameProfile(profile, finalName)
|
|
97
|
+
console.log(chalk.green(`已重命名: ${profile} → ${finalName}`))
|
|
98
|
+
}
|
|
99
|
+
if (newUrl.trim()) {
|
|
100
|
+
await sub.downloadSubscription(newUrl.trim(), finalName)
|
|
101
|
+
}
|
|
102
|
+
if (!newName.trim() && !newUrl.trim()) {
|
|
103
|
+
console.log(chalk.gray('未做任何修改'))
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error(chalk.red(err.message))
|
|
107
|
+
}
|
|
108
|
+
} else if (action === 'delete') {
|
|
109
|
+
if (profiles.length === 0) return console.log('暂无订阅,请先添加')
|
|
110
|
+
const profile = await select({
|
|
111
|
+
message: '选择要删除的订阅:',
|
|
112
|
+
choices: profiles.map(p => ({ name: p, value: p })),
|
|
113
|
+
})
|
|
114
|
+
const ok = await confirm({ message: `确定删除订阅 "${profile}"?`, default: false })
|
|
115
|
+
if (!ok) return console.log(chalk.gray('已取消'))
|
|
116
|
+
try {
|
|
117
|
+
sub.deleteProfile(profile)
|
|
118
|
+
console.log(chalk.green(`订阅 "${profile}" 已删除`))
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error(chalk.red(err.message))
|
|
121
|
+
}
|
|
79
122
|
}
|
|
80
123
|
}
|
|
81
124
|
}
|
package/lib/commands/test.js
CHANGED
|
@@ -3,7 +3,17 @@ import * as api from '../api.js'
|
|
|
3
3
|
|
|
4
4
|
export async function test() {
|
|
5
5
|
try {
|
|
6
|
-
|
|
6
|
+
let proxies
|
|
7
|
+
try {
|
|
8
|
+
proxies = await api.getProxies()
|
|
9
|
+
} catch (err) {
|
|
10
|
+
if (err.message && (err.message.includes('ECONNREFUSED') || err.message.includes('无法连接'))) {
|
|
11
|
+
console.error(chalk.red('\nClash 服务未运行,请先执行: ck start\n'))
|
|
12
|
+
} else {
|
|
13
|
+
console.error(chalk.red(err.message))
|
|
14
|
+
}
|
|
15
|
+
return
|
|
16
|
+
}
|
|
7
17
|
// 默认测速 Proxy 组的所有节点
|
|
8
18
|
const group = proxies['Proxy'] || Object.values(proxies).find(p => p.type === 'Selector')
|
|
9
19
|
|
|
@@ -15,17 +25,13 @@ export async function test() {
|
|
|
15
25
|
console.log(`\n[${group.name}]${group.all.length}个节点, 当前选中: ${group.now}\n`)
|
|
16
26
|
|
|
17
27
|
const results = []
|
|
18
|
-
const concurrency = 10 // 并发数量
|
|
19
|
-
const queue = [...group.all]
|
|
20
28
|
const total = group.all.length
|
|
21
29
|
let completed = 0
|
|
22
30
|
const current = group.now // 当前选中节点
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (!name) break
|
|
28
|
-
|
|
32
|
+
// 全并发测速
|
|
33
|
+
await Promise.all(
|
|
34
|
+
group.all.map(async name => {
|
|
29
35
|
try {
|
|
30
36
|
const testUrl = name === 'DIRECT' ? 'http://connect.rom.miui.com/generate_204' : undefined
|
|
31
37
|
const delay = await api.getProxyDelay(name, testUrl)
|
|
@@ -46,10 +52,8 @@ export async function test() {
|
|
|
46
52
|
completed++
|
|
47
53
|
results.push({ name, delay: 99999, isCurrent: name === current })
|
|
48
54
|
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
await Promise.all(Array.from({ length: Math.min(concurrency, total) }, () => worker()))
|
|
55
|
+
}),
|
|
56
|
+
)
|
|
53
57
|
|
|
54
58
|
console.log(chalk.bold.blue('\n=== 测速结果 (Top 5) ==='))
|
|
55
59
|
results.sort((a, b) => a.delay - b.delay)
|
package/lib/commands/tun.js
CHANGED
|
@@ -4,7 +4,7 @@ import ora from 'ora'
|
|
|
4
4
|
import boxen from 'boxen'
|
|
5
5
|
import * as tun from '../tun.js'
|
|
6
6
|
import * as sysnet from '../sysnet.js'
|
|
7
|
-
import { main as startClashService } from '
|
|
7
|
+
import { main as startClashService } from '../service.js'
|
|
8
8
|
|
|
9
9
|
async function turnOn() {
|
|
10
10
|
const spinner = ora('正在配置 TUN 模式...').start()
|
|
@@ -73,9 +73,9 @@ async function turnOff(options = {}) {
|
|
|
73
73
|
await tun.disableTun()
|
|
74
74
|
|
|
75
75
|
if (spinner) spinner.text = '正在恢复系统 DNS...'
|
|
76
|
-
const dnsResult = sysnet.setDNS([])
|
|
76
|
+
const dnsResult = sysnet.setDNS([])
|
|
77
77
|
if (!dnsResult.success) {
|
|
78
|
-
|
|
78
|
+
console.warn(chalk.yellow(`\n DNS 恢复失败 (可忽略,DNS 将在重启后自动恢复): ${dnsResult.error}`))
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
if (spinner) spinner.stop()
|
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
import { spawn
|
|
1
|
+
import { spawn } from 'child_process'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import fs from 'fs'
|
|
4
4
|
import chalk from 'chalk'
|
|
5
5
|
import axios from 'axios'
|
|
6
6
|
import ora from 'ora'
|
|
7
|
-
import boxen from 'boxen'
|
|
8
7
|
import YAML from 'yaml'
|
|
9
8
|
import { fileURLToPath } from 'url'
|
|
10
|
-
import { getApiBase
|
|
11
|
-
import { status } from './
|
|
12
|
-
import * as sysproxy from './
|
|
13
|
-
import * as tun from './
|
|
14
|
-
import { isPortOpen, extractPort, getPortOccupier } from './
|
|
15
|
-
import { killClashProcess } from './
|
|
9
|
+
import { getApiBase } from './api.js'
|
|
10
|
+
import { status } from './commands/status.js'
|
|
11
|
+
import * as sysproxy from './sysproxy.js'
|
|
12
|
+
import * as tun from './tun.js'
|
|
13
|
+
import { isPortOpen, extractPort, getPortOccupier } from './port.js'
|
|
14
|
+
import { killClashProcess } from './kernel.js'
|
|
16
15
|
|
|
17
16
|
const __filename = fileURLToPath(import.meta.url)
|
|
18
17
|
const __dirname = path.dirname(__filename)
|
|
19
18
|
|
|
20
19
|
// ---------------- 配置项 ----------------
|
|
21
|
-
export const CLASH_BIN_PATH = path.join(__dirname, process.platform === 'win32' ? 'clash-kit.exe' : 'clash-kit') // 解压后的二进制文件路径
|
|
22
|
-
export const CLASH_CONFIG_PATH = path.join(__dirname, 'config.yaml') // 配置文件路径
|
|
20
|
+
export const CLASH_BIN_PATH = path.join(__dirname, '..', process.platform === 'win32' ? 'clash-kit.exe' : 'clash-kit') // 解压后的二进制文件路径
|
|
21
|
+
export const CLASH_CONFIG_PATH = path.join(__dirname, '..', 'config.yaml') // 配置文件路径
|
|
22
|
+
|
|
23
|
+
const ROOT_DIR = path.join(__dirname, '..')
|
|
23
24
|
|
|
24
25
|
async function checkPorts() {
|
|
25
26
|
try {
|
|
@@ -70,11 +71,11 @@ async function startClash() {
|
|
|
70
71
|
// 检查端口占用 (核心策略:报错/启动失败)
|
|
71
72
|
await checkPorts()
|
|
72
73
|
|
|
73
|
-
const logPath = path.join(
|
|
74
|
+
const logPath = path.join(ROOT_DIR, 'clash.log')
|
|
74
75
|
const logFd = fs.openSync(logPath, 'a')
|
|
75
76
|
|
|
76
|
-
const clashProcess = spawn(CLASH_BIN_PATH, ['-f', CLASH_CONFIG_PATH, '-d',
|
|
77
|
-
cwd:
|
|
77
|
+
const clashProcess = spawn(CLASH_BIN_PATH, ['-f', CLASH_CONFIG_PATH, '-d', ROOT_DIR], {
|
|
78
|
+
cwd: ROOT_DIR,
|
|
78
79
|
detached: true,
|
|
79
80
|
stdio: ['ignore', logFd, logFd], // 重定向 stdout 和 stderr 到日志文件
|
|
80
81
|
})
|
|
@@ -177,7 +178,7 @@ export async function main() {
|
|
|
177
178
|
|
|
178
179
|
if (!started) {
|
|
179
180
|
spinner.fail(chalk.red('启动失败'))
|
|
180
|
-
const logPath = path.join(
|
|
181
|
+
const logPath = path.join(ROOT_DIR, 'clash.log')
|
|
181
182
|
if (fs.existsSync(logPath)) {
|
|
182
183
|
console.log(chalk.yellow('\n------- clash.log (Last 20 lines) -------'))
|
|
183
184
|
const lines = fs.readFileSync(logPath, 'utf8').trim().split('\n')
|
|
@@ -197,8 +198,3 @@ export async function main() {
|
|
|
197
198
|
// 调用 status 命令来打印完整的状态信息
|
|
198
199
|
await status()
|
|
199
200
|
}
|
|
200
|
-
|
|
201
|
-
// 运行脚本
|
|
202
|
-
if (process.argv[1] === __filename) {
|
|
203
|
-
main()
|
|
204
|
-
}
|
package/lib/subscription.js
CHANGED
|
@@ -126,3 +126,27 @@ export async function useProfile(name) {
|
|
|
126
126
|
spinner.succeed('配置已切换(Clash 未运行,将在下次启动时生效)')
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
export function deleteProfile(name) {
|
|
131
|
+
const filePath = path.join(PROFILES_DIR, `${name}.yaml`)
|
|
132
|
+
if (!fs.existsSync(filePath)) throw new Error(`订阅 ${name} 不存在`)
|
|
133
|
+
fs.unlinkSync(filePath)
|
|
134
|
+
// 若删除的是当前使用的订阅,清除记录
|
|
135
|
+
if (fs.existsSync(CURRENT_PROFILE_PATH)) {
|
|
136
|
+
const current = fs.readFileSync(CURRENT_PROFILE_PATH, 'utf8').trim()
|
|
137
|
+
if (current === name) fs.unlinkSync(CURRENT_PROFILE_PATH)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function renameProfile(oldName, newName) {
|
|
142
|
+
const oldPath = path.join(PROFILES_DIR, `${oldName}.yaml`)
|
|
143
|
+
const newPath = path.join(PROFILES_DIR, `${newName}.yaml`)
|
|
144
|
+
if (!fs.existsSync(oldPath)) throw new Error(`订阅 ${oldName} 不存在`)
|
|
145
|
+
if (fs.existsSync(newPath)) throw new Error(`订阅名称 ${newName} 已存在`)
|
|
146
|
+
fs.renameSync(oldPath, newPath)
|
|
147
|
+
// 同步更新当前使用记录
|
|
148
|
+
if (fs.existsSync(CURRENT_PROFILE_PATH)) {
|
|
149
|
+
const current = fs.readFileSync(CURRENT_PROFILE_PATH, 'utf8').trim()
|
|
150
|
+
if (current === oldName) fs.writeFileSync(CURRENT_PROFILE_PATH, newName)
|
|
151
|
+
}
|
|
152
|
+
}
|
package/lib/sysnet.js
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import { execSync } from 'child_process'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import { fileURLToPath } from 'url'
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
7
|
-
const __dirname = path.dirname(__filename)
|
|
8
|
-
const HELPER_BIN = path.join(__dirname, '../bin/dns-helper')
|
|
9
2
|
|
|
10
3
|
/**
|
|
11
4
|
* 获取 macOS 当前主要的网络服务名称 (Wi-Fi, Ethernet 等)
|
|
@@ -38,34 +31,18 @@ function getMainNetworkService() {
|
|
|
38
31
|
*/
|
|
39
32
|
export function setDNS(servers) {
|
|
40
33
|
if (process.platform !== 'darwin') {
|
|
41
|
-
return { success: true
|
|
34
|
+
return { success: true }
|
|
42
35
|
}
|
|
43
36
|
|
|
44
37
|
const service = getMainNetworkService()
|
|
45
38
|
if (!service) {
|
|
46
|
-
return { success: false, error: '
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// 检查是否存在 helper
|
|
50
|
-
let useHelper = false
|
|
51
|
-
try {
|
|
52
|
-
if (fs.existsSync(HELPER_BIN)) {
|
|
53
|
-
const stats = fs.statSync(HELPER_BIN)
|
|
54
|
-
if (stats.uid === 0 && (stats.mode & 0o4000)) {
|
|
55
|
-
useHelper = true
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
} catch (e) {
|
|
59
|
-
/* ignore */
|
|
39
|
+
return { success: false, error: '无法获取网络服务' }
|
|
60
40
|
}
|
|
61
41
|
|
|
62
|
-
const serversArg = servers.length > 0 ? servers.join(' ') : 'Empty'
|
|
63
|
-
const command = useHelper
|
|
64
|
-
? `"${HELPER_BIN}" "${service}" ${serversArg}`
|
|
65
|
-
: `sudo networksetup -setdnsservers "${service}" ${serversArg}`
|
|
42
|
+
const serversArg = servers.length > 0 ? servers.join(' ') : '"Empty"'
|
|
66
43
|
|
|
67
44
|
try {
|
|
68
|
-
execSync(
|
|
45
|
+
execSync(`sudo networksetup -setdnsservers "${service}" ${serversArg}`)
|
|
69
46
|
return { success: true }
|
|
70
47
|
} catch (e) {
|
|
71
48
|
return { success: false, error: e.message }
|
package/lib/tun.js
CHANGED
|
@@ -9,8 +9,6 @@ const __filename = fileURLToPath(import.meta.url)
|
|
|
9
9
|
const __dirname = path.dirname(__filename)
|
|
10
10
|
const CONFIG_PATH = path.join(__dirname, '../config.yaml')
|
|
11
11
|
const BIN_PATH = path.join(__dirname, '../clash-kit')
|
|
12
|
-
const HELPER_SRC = path.join(__dirname, '../scripts/dns-helper.c')
|
|
13
|
-
const HELPER_BIN = path.join(__dirname, '../bin/dns-helper')
|
|
14
12
|
|
|
15
13
|
export function checkTunPermissions() {
|
|
16
14
|
if (process.platform === 'win32') return true // Windows 需要管理员权限终端,难以通过文件属性判断
|
|
@@ -21,15 +19,7 @@ export function checkTunPermissions() {
|
|
|
21
19
|
// 检查所有者是否为 root (uid 0) 并且拥有 SUID 位
|
|
22
20
|
const isRootOwned = stats.uid === 0
|
|
23
21
|
const hasSuid = (stats.mode & 0o4000) === 0o4000
|
|
24
|
-
|
|
25
|
-
// 同时也检查 DNS 辅助工具的权限
|
|
26
|
-
let helperOk = false
|
|
27
|
-
if (fs.existsSync(HELPER_BIN)) {
|
|
28
|
-
const hStats = fs.statSync(HELPER_BIN)
|
|
29
|
-
helperOk = hStats.uid === 0 && (hStats.mode & 0o4000) === 0o4000
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return isRootOwned && hasSuid && helperOk
|
|
22
|
+
return isRootOwned && hasSuid
|
|
33
23
|
} catch (e) {
|
|
34
24
|
return false
|
|
35
25
|
}
|
|
@@ -48,20 +38,6 @@ export function setupPermissions() {
|
|
|
48
38
|
console.log('正在提升内核权限 (需要 sudo 密码)...')
|
|
49
39
|
execSync(`sudo ${cmdChown}`, { stdio: 'inherit' })
|
|
50
40
|
execSync(`sudo ${cmdChmod}`, { stdio: 'inherit' })
|
|
51
|
-
|
|
52
|
-
// 编译并设置 DNS 辅助工具
|
|
53
|
-
if (fs.existsSync(HELPER_SRC)) {
|
|
54
|
-
console.log('正在编译 DNS 辅助工具...')
|
|
55
|
-
try {
|
|
56
|
-
execSync(`cc "${HELPER_SRC}" -o "${HELPER_BIN}"`, { stdio: 'inherit' })
|
|
57
|
-
execSync(`sudo chown root:${group} "${HELPER_BIN}"`, { stdio: 'inherit' })
|
|
58
|
-
execSync(`sudo chmod +sx "${HELPER_BIN}"`, { stdio: 'inherit' })
|
|
59
|
-
console.log('DNS 辅助工具设置成功')
|
|
60
|
-
} catch (compileErr) {
|
|
61
|
-
console.warn('DNS 辅助工具编译或设置失败,系统 DNS 切换可能仍需密码:', compileErr.message)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
41
|
return true
|
|
66
42
|
} catch (e) {
|
|
67
43
|
throw new Error('权限设置失败')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clash-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A command-line interface for managing Clash configurations, subscriptions, and proxies.",
|
|
6
6
|
"bin": {
|
|
@@ -13,12 +13,10 @@
|
|
|
13
13
|
".gitignore",
|
|
14
14
|
"default.yaml",
|
|
15
15
|
"country.mmdb",
|
|
16
|
-
"index.js",
|
|
17
16
|
"LICENSE.md",
|
|
18
17
|
"package.json",
|
|
19
18
|
"README.md"
|
|
20
19
|
],
|
|
21
|
-
"main": "index.js",
|
|
22
20
|
"scripts": {
|
|
23
21
|
"start": "node bin/index.js start",
|
|
24
22
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -45,6 +43,7 @@
|
|
|
45
43
|
"commander": "^14.0.2",
|
|
46
44
|
"inquirer": "^13.1.0",
|
|
47
45
|
"ora": "^9.0.0",
|
|
46
|
+
"update-notifier": "^7.3.1",
|
|
48
47
|
"yaml": "^2.8.2"
|
|
49
48
|
}
|
|
50
49
|
}
|
package/bin/dns-helper
DELETED
|
Binary file
|