clash-kit 1.0.0 → 1.0.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.
- package/README.md +33 -25
- package/bin/index.js +10 -5
- package/country.mmdb +0 -0
- package/default.yaml +46 -0
- package/lib/api.js +10 -0
- package/lib/commands/init.js +73 -4
- package/lib/commands/sub.js +2 -5
- package/lib/subscription.js +11 -10
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
# Clash
|
|
1
|
+
# Clash Kit
|
|
2
|
+
|
|
3
|
+
<img width="800" alt="clash-kit" src="https://github.com/user-attachments/assets/dd7dfd29-f59a-418b-8623-6ab08ece9ddb" />
|
|
2
4
|
|
|
3
5
|
一个基于 Node.js 的 Clash 命令行管理工具,旨在简化 Clash 的配置管理、订阅切换和节点测速等操作。
|
|
4
6
|
|
|
@@ -12,7 +14,7 @@
|
|
|
12
14
|
- 🛠 **自动初始化**:自动处理二进制文件权限问题。
|
|
13
15
|
|
|
14
16
|
- 💻 **系统代理**:一键开启/关闭 macOS 系统 HTTP 代理。
|
|
15
|
-
- 🛡 **TUN
|
|
17
|
+
- 🛡 **TUN 模式**:高级网络模式,接管系统所有流量(真 · 全局代理)。
|
|
16
18
|
|
|
17
19
|
## 使用
|
|
18
20
|
|
|
@@ -30,6 +32,8 @@ pnpm add -g clash-kit
|
|
|
30
32
|
|
|
31
33
|
```bash
|
|
32
34
|
clash init
|
|
35
|
+
# 推荐用简化命令(文档后续均以简化命令为例)
|
|
36
|
+
ck init
|
|
33
37
|
```
|
|
34
38
|
|
|
35
39
|
### 3. 启动服务
|
|
@@ -38,58 +42,62 @@ clash init
|
|
|
38
42
|
|
|
39
43
|
```bash
|
|
40
44
|
# 启动 Clash 代理服务
|
|
41
|
-
|
|
45
|
+
ck start
|
|
42
46
|
|
|
43
47
|
# 启动并自动开启系统代理
|
|
44
|
-
|
|
48
|
+
ck start -s
|
|
45
49
|
```
|
|
46
50
|
|
|
47
51
|
### 4. 添加订阅
|
|
48
52
|
|
|
49
53
|
```bash
|
|
50
|
-
#
|
|
51
|
-
|
|
54
|
+
# 交互式管理订阅(添加、切换、删除等)【推荐使用这种方式来管理订阅】
|
|
55
|
+
ck sub
|
|
52
56
|
|
|
53
57
|
# 手动添加订阅
|
|
54
|
-
|
|
58
|
+
ck sub -a "https://example.com/subscribe?token=xxx" -n "abcName"
|
|
55
59
|
```
|
|
56
60
|
|
|
57
61
|
### 4. 节点测速与切换
|
|
58
62
|
|
|
59
63
|
```bash
|
|
60
64
|
# 测速
|
|
61
|
-
|
|
65
|
+
ck test
|
|
62
66
|
|
|
63
67
|
# 切换节点
|
|
64
|
-
|
|
68
|
+
ck proxy
|
|
65
69
|
```
|
|
66
70
|
|
|
67
71
|
### 5. 更多功能
|
|
68
72
|
|
|
69
73
|
```bash
|
|
70
74
|
# 查看状态
|
|
71
|
-
|
|
75
|
+
ck status
|
|
72
76
|
|
|
73
77
|
# 开启 TUN 模式 (需要 sudo 权限)
|
|
74
|
-
sudo
|
|
78
|
+
sudo ck tun on
|
|
75
79
|
```
|
|
76
80
|
|
|
77
81
|
## 命令详解
|
|
78
82
|
|
|
79
|
-
| 命令
|
|
80
|
-
| --------------------- | -------------------------- |
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
83
|
+
| 命令 (别名) | 说明 | 示例 |
|
|
84
|
+
| --------------------- | -------------------------- | ------------------------------- |
|
|
85
|
+
| `ck init` (`i`) | 初始化内核及权限 | `ck i` |
|
|
86
|
+
| `ck start` (`on`) | 启动 Clash 服务 | `ck on -s` (启动并设置系统代理) |
|
|
87
|
+
| `ck stop` (`off`) | 停止服务并关闭代理 | `ck off` |
|
|
88
|
+
| `ck status` (`st`) | 查看运行状态及当前节点延迟 | `ck st` |
|
|
89
|
+
| `ck sub` (`s`) | 管理订阅(交互式) | `ck s` |
|
|
90
|
+
| `ck sub -a <url>` | 添加订阅 | `ck s -a "http..." -n "pro"` |
|
|
91
|
+
| `ck sub -u <name>` | 切换订阅 | `ck s -u "pro"` |
|
|
92
|
+
| `ck sub -l` | 列出所有订阅 | `ck s -l` |
|
|
93
|
+
| `ck proxy` (`p`) | 切换节点(交互式) | `ck p` |
|
|
94
|
+
| `ck test` (`t`) | 节点并发测速 | `ck t` |
|
|
95
|
+
| `ck sysproxy` (`sys`) | 设置系统代理 (on/off) | `ck sys on` |
|
|
96
|
+
| `ck tun` | 设置 TUN 模式 (on/off) | `sudo ck tun on` |
|
|
97
|
+
|
|
98
|
+
## 截图
|
|
99
|
+
|
|
100
|
+
<img width="2742" height="1994" alt="image" src="https://github.com/user-attachments/assets/1183f778-62b0-4ac7-ab55-b821b66161f0" />
|
|
93
101
|
|
|
94
102
|
## 目录结构
|
|
95
103
|
|
package/bin/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { test } from '../lib/commands/test.js'
|
|
|
13
13
|
|
|
14
14
|
const program = new Command()
|
|
15
15
|
|
|
16
|
-
program.name('clash').description('Clash CLI 管理工具').version('1.0.0')
|
|
16
|
+
program.name('clash').alias('ck').description('Clash CLI 管理工具 (Alias: ck)').version('1.0.0')
|
|
17
17
|
|
|
18
18
|
// 初始化 clash 内核
|
|
19
19
|
program
|
|
@@ -29,13 +29,18 @@ program.command('start').description('启动 Clash 服务').option('-s, --syspro
|
|
|
29
29
|
program.command('stop').description('停止 Clash 服务').action(stop)
|
|
30
30
|
|
|
31
31
|
// 设置系统代理
|
|
32
|
-
program
|
|
32
|
+
program
|
|
33
|
+
.command('sysproxy')
|
|
34
|
+
.alias('sys')
|
|
35
|
+
.description('设置系统代理')
|
|
36
|
+
.argument('[action]', 'on 或 off')
|
|
37
|
+
.action(setSysProxy)
|
|
33
38
|
|
|
34
39
|
// 设置 TUN 模式(真正的全局代理,所有流量都会被代理)
|
|
35
40
|
program.command('tun').description('设置 TUN 模式 (可能需要提权)').argument('[action]', 'on 或 off').action(setTun)
|
|
36
41
|
|
|
37
42
|
// 查看 clash 状态
|
|
38
|
-
program.command('status').description('查看 Clash 运行状态').action(status)
|
|
43
|
+
program.command('status').alias('st').description('查看 Clash 运行状态').action(status)
|
|
39
44
|
|
|
40
45
|
// 管理订阅
|
|
41
46
|
program
|
|
@@ -48,9 +53,9 @@ program
|
|
|
48
53
|
.action(manageSub)
|
|
49
54
|
|
|
50
55
|
// 切换节点
|
|
51
|
-
program.command('proxy').description('切换节点').action(proxy)
|
|
56
|
+
program.command('proxy').alias('p').description('切换节点').action(proxy)
|
|
52
57
|
|
|
53
58
|
// 节点测速
|
|
54
|
-
program.command('test').description('节点测速').action(test)
|
|
59
|
+
program.command('test').alias('t').description('节点测速').action(test)
|
|
55
60
|
|
|
56
61
|
program.parse(process.argv)
|
package/country.mmdb
ADDED
|
Binary file
|
package/default.yaml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# HTTP 代理端口
|
|
2
|
+
port: 7890
|
|
3
|
+
# SOCKS5 代理端口
|
|
4
|
+
socks-port: 7891
|
|
5
|
+
# 允许局域网连接
|
|
6
|
+
allow-lan: true
|
|
7
|
+
|
|
8
|
+
# 规则模式:Rule(规则) / Global(全局)/ Direct(直连)
|
|
9
|
+
mode: Rule
|
|
10
|
+
# 日志级别:info / warning / error / debug / silent
|
|
11
|
+
log-level: info
|
|
12
|
+
# 外部控制接口(用于 Dashboard)
|
|
13
|
+
external-controller: 127.0.0.1:9090
|
|
14
|
+
|
|
15
|
+
# 代理节点定义 (示例)
|
|
16
|
+
proxies:
|
|
17
|
+
# - name: "Shadowsocks 示例"
|
|
18
|
+
# type: ss
|
|
19
|
+
# server: server
|
|
20
|
+
# port: 443
|
|
21
|
+
# cipher: chacha20-ietf-poly1305
|
|
22
|
+
# password: "password"
|
|
23
|
+
|
|
24
|
+
# - name: "Vmess 示例"
|
|
25
|
+
# type: vmess
|
|
26
|
+
# server: server
|
|
27
|
+
# port: 443
|
|
28
|
+
# uuid: uuid
|
|
29
|
+
# alterId: 64
|
|
30
|
+
# cipher: auto
|
|
31
|
+
|
|
32
|
+
# 代理组定义
|
|
33
|
+
proxy-groups:
|
|
34
|
+
- name: PROXY
|
|
35
|
+
type: select
|
|
36
|
+
proxies:
|
|
37
|
+
- DIRECT
|
|
38
|
+
# - "Shadowsocks 示例"
|
|
39
|
+
|
|
40
|
+
# 规则定义
|
|
41
|
+
rules:
|
|
42
|
+
- DOMAIN-SUFFIX,google.com,PROXY
|
|
43
|
+
- DOMAIN-KEYWORD,google,PROXY
|
|
44
|
+
- DOMAIN,google.com,PROXY
|
|
45
|
+
- GEOIP,CN,DIRECT
|
|
46
|
+
- MATCH,PROXY
|
package/lib/api.js
CHANGED
|
@@ -104,3 +104,13 @@ export async function reloadConfig(configPath) {
|
|
|
104
104
|
throw new Error(`重载配置失败: ${err.message}`)
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
+
|
|
108
|
+
export async function isClashRunning() {
|
|
109
|
+
try {
|
|
110
|
+
// 使用较短的超时时间快速检查
|
|
111
|
+
await axios.get(`${getApiBase()}/version`, { timeout: 200 })
|
|
112
|
+
return true
|
|
113
|
+
} catch (err) {
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
}
|
package/lib/commands/init.js
CHANGED
|
@@ -2,12 +2,57 @@ import path from 'path'
|
|
|
2
2
|
import fs from 'fs'
|
|
3
3
|
import { fileURLToPath } from 'url'
|
|
4
4
|
import { downloadClash } from '../kernel.js'
|
|
5
|
+
import axios from 'axios'
|
|
6
|
+
import ora from 'ora'
|
|
7
|
+
import chalk from 'chalk'
|
|
5
8
|
|
|
6
9
|
const __filename = fileURLToPath(import.meta.url)
|
|
7
10
|
const __dirname = path.dirname(__filename)
|
|
8
11
|
|
|
9
|
-
const DEFAULT_CONFIG = `mixed-port: 7890
|
|
10
|
-
|
|
12
|
+
const DEFAULT_CONFIG = `mixed-port: 7890\n`
|
|
13
|
+
|
|
14
|
+
const RESOURCES = [
|
|
15
|
+
{
|
|
16
|
+
filename: 'country.mmdb',
|
|
17
|
+
url: 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country-lite.mmdb',
|
|
18
|
+
},
|
|
19
|
+
// {
|
|
20
|
+
// filename: 'geoip.metadb',
|
|
21
|
+
// url: 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb',
|
|
22
|
+
// },
|
|
23
|
+
// {
|
|
24
|
+
// filename: 'geosite.dat',
|
|
25
|
+
// url: 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat',
|
|
26
|
+
// },
|
|
27
|
+
// {
|
|
28
|
+
// filename: 'geoip.dat',
|
|
29
|
+
// url: 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat',
|
|
30
|
+
// },
|
|
31
|
+
// {
|
|
32
|
+
// filename: 'ASN.mmdb',
|
|
33
|
+
// url: 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb',
|
|
34
|
+
// },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
async function downloadResource(resource, targetDir) {
|
|
38
|
+
const filePath = path.join(targetDir, resource.filename)
|
|
39
|
+
const spinner = ora(`正在下载 ${resource.filename}...`).start()
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const response = await axios({
|
|
43
|
+
url: resource.url,
|
|
44
|
+
method: 'GET',
|
|
45
|
+
responseType: 'arraybuffer',
|
|
46
|
+
timeout: 30 * 1000, // 30s timeout
|
|
47
|
+
})
|
|
48
|
+
fs.writeFileSync(filePath, response.data)
|
|
49
|
+
spinner.succeed(`${resource.filename} 下载完成`)
|
|
50
|
+
} catch (e) {
|
|
51
|
+
spinner.fail(`${resource.filename} 下载失败: ${e.message}`)
|
|
52
|
+
// 不要抛出错误,让其他资源继续下载
|
|
53
|
+
// throw e
|
|
54
|
+
}
|
|
55
|
+
}
|
|
11
56
|
|
|
12
57
|
export async function init(options) {
|
|
13
58
|
const rootDir = path.join(__dirname, '../..')
|
|
@@ -18,9 +63,33 @@ export async function init(options) {
|
|
|
18
63
|
try {
|
|
19
64
|
// 创建默认配置文件(如果不存在)
|
|
20
65
|
if (!fs.existsSync(configPath)) {
|
|
21
|
-
|
|
22
|
-
|
|
66
|
+
const defaultConfigPath = path.join(rootDir, 'default.yaml')
|
|
67
|
+
if (fs.existsSync(defaultConfigPath)) {
|
|
68
|
+
fs.copyFileSync(defaultConfigPath, configPath)
|
|
69
|
+
console.log(`已从 default.yaml 创建配置文件: ${configPath}`)
|
|
70
|
+
} else {
|
|
71
|
+
console.warn(chalk.yellow('警告: 未找到 default.yaml,将创建最小配置文件'))
|
|
72
|
+
fs.writeFileSync(configPath, DEFAULT_CONFIG, 'utf8')
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 检查并下载资源文件
|
|
77
|
+
console.log(chalk.blue('\n正在检查资源文件...'))
|
|
78
|
+
for (const resource of RESOURCES) {
|
|
79
|
+
const filePath = path.join(rootDir, resource.filename)
|
|
80
|
+
if (options.force && fs.existsSync(filePath)) {
|
|
81
|
+
try {
|
|
82
|
+
fs.unlinkSync(filePath)
|
|
83
|
+
} catch (e) {}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!fs.existsSync(filePath)) {
|
|
87
|
+
await downloadResource(resource, rootDir)
|
|
88
|
+
} else {
|
|
89
|
+
console.log(chalk.gray(`资源 ${resource.filename} 已存在`))
|
|
90
|
+
}
|
|
23
91
|
}
|
|
92
|
+
console.log(chalk.green('资源检查完成\n'))
|
|
24
93
|
|
|
25
94
|
if (fs.existsSync(binPath) && !options.force) {
|
|
26
95
|
console.log(`Clash 内核已存在: ${binPath}`)
|
package/lib/commands/sub.js
CHANGED
|
@@ -23,10 +23,7 @@ async function handleAddSubscription(url, name) {
|
|
|
23
23
|
|
|
24
24
|
export async function manageSub(options) {
|
|
25
25
|
if (options.add) {
|
|
26
|
-
if (!options.name)
|
|
27
|
-
console.error('错误: 添加订阅时必须指定名称 (-n)')
|
|
28
|
-
return
|
|
29
|
-
}
|
|
26
|
+
if (!options.name) return console.error('错误: 添加订阅时必须指定名称 (-n)')
|
|
30
27
|
try {
|
|
31
28
|
await handleAddSubscription(options.add, options.name)
|
|
32
29
|
} catch (err) {
|
|
@@ -34,7 +31,7 @@ export async function manageSub(options) {
|
|
|
34
31
|
}
|
|
35
32
|
} else if (options.list) {
|
|
36
33
|
const profiles = sub.listProfiles()
|
|
37
|
-
console.log('
|
|
34
|
+
console.log(`${profiles.length ? '已添加的订阅:' : '暂无已添加的订阅'}`)
|
|
38
35
|
profiles.forEach(p => console.log(`- ${p}`))
|
|
39
36
|
} else if (options.use) {
|
|
40
37
|
try {
|
package/lib/subscription.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'path'
|
|
|
3
3
|
import axios from 'axios'
|
|
4
4
|
import { fileURLToPath } from 'url'
|
|
5
5
|
import ora from 'ora'
|
|
6
|
-
import { reloadConfig } from './api.js'
|
|
6
|
+
import { reloadConfig, isClashRunning } from './api.js'
|
|
7
7
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url)
|
|
9
9
|
const __dirname = path.dirname(__filename)
|
|
@@ -47,9 +47,7 @@ export function getCurrentProfile() {
|
|
|
47
47
|
|
|
48
48
|
export async function useProfile(name) {
|
|
49
49
|
const source = path.join(PROFILES_DIR, `${name}.yaml`)
|
|
50
|
-
if (!fs.existsSync(source)) {
|
|
51
|
-
throw new Error(`配置文件 ${name} 不存在`)
|
|
52
|
-
}
|
|
50
|
+
if (!fs.existsSync(source)) throw new Error(`配置文件 ${name} 不存在`)
|
|
53
51
|
|
|
54
52
|
const spinner = ora(`正在切换到配置 ${name}...`).start()
|
|
55
53
|
|
|
@@ -58,11 +56,14 @@ export async function useProfile(name) {
|
|
|
58
56
|
fs.writeFileSync(CURRENT_PROFILE_PATH, name)
|
|
59
57
|
|
|
60
58
|
// 尝试热重载
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
if (await isClashRunning()) {
|
|
60
|
+
try {
|
|
61
|
+
await reloadConfig(CONFIG_PATH)
|
|
62
|
+
spinner.succeed('Clash 配置已切换并热重载生效')
|
|
63
|
+
} catch (err) {
|
|
64
|
+
spinner.warn(`配置已切换,但热重载失败: ${err.message}`)
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
spinner.succeed('配置已切换(Clash 未运行,将在下次启动时生效)')
|
|
67
68
|
}
|
|
68
69
|
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clash-kit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A command-line interface for managing Clash configurations, subscriptions, and proxies.",
|
|
6
6
|
"bin": {
|
|
7
|
+
"ck": "./bin/index.js",
|
|
7
8
|
"clash": "./bin/index.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"bin",
|
|
11
12
|
"lib",
|
|
12
13
|
"index.js",
|
|
14
|
+
"country.mmdb",
|
|
15
|
+
"default.yaml",
|
|
13
16
|
"package.json",
|
|
14
17
|
"README.md"
|
|
15
18
|
],
|