clash-kit 1.0.1 → 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/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 +3 -1
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,6 +1,6 @@
|
|
|
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": {
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"bin",
|
|
12
12
|
"lib",
|
|
13
13
|
"index.js",
|
|
14
|
+
"country.mmdb",
|
|
15
|
+
"default.yaml",
|
|
14
16
|
"package.json",
|
|
15
17
|
"README.md"
|
|
16
18
|
],
|