clash-kit 1.1.1 → 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/lib/sysproxy.js CHANGED
@@ -1,145 +1,133 @@
1
- import ora from 'ora'
2
- import { triggerManualProxy } from '@mihomo-party/sysproxy'
3
- import { execSync } from 'child_process'
4
- import * as api from './api.js'
5
-
6
- const defaultBypass = (() => {
7
- switch (process.platform) {
8
- case 'linux':
9
- return ['localhost', '127.0.0.1', '192.168.0.0/16', '10.0.0.0/8', '172.16.0.0/12', '::1']
10
- case 'darwin':
11
- return [
12
- '127.0.0.1',
13
- '192.168.0.0/16',
14
- '10.0.0.0/8',
15
- '172.16.0.0/12',
16
- 'localhost',
17
- '*.local',
18
- '*.crashlytics.com',
19
- '<local>',
20
- ]
21
- case 'win32':
22
- return [
23
- 'localhost',
24
- '127.*',
25
- '192.168.*',
26
- '10.*',
27
- '172.16.*',
28
- '172.17.*',
29
- '172.18.*',
30
- '172.19.*',
31
- '172.20.*',
32
- '172.21.*',
33
- '172.22.*',
34
- '172.23.*',
35
- '172.24.*',
36
- '172.25.*',
37
- '172.26.*',
38
- '172.27.*',
39
- '172.28.*',
40
- '172.29.*',
41
- '172.30.*',
42
- '172.31.*',
43
- '<local>',
44
- ]
45
- default:
46
- return ['localhost', '127.0.0.1', '192.168.0.0/16', '10.0.0.0/8', '172.16.0.0/12', '::1']
47
- }
48
- })()
49
-
50
- export async function enableSystemProxy() {
51
- const spinner = ora('正在等待 Clash API 就绪以设置系统代理...').start()
52
- try {
53
- const config = await api.getConfig()
54
- const port = config['mixed-port'] || config['port']
55
-
56
- if (!port) {
57
- throw new Error('未找到 HTTP 代理端口配置 (port 或 mixed-port)')
58
- }
59
-
60
- // 默认代理地址为 127.0.0.1
61
- const host = '127.0.0.1'
62
- const bypass = defaultBypass.join(',')
63
-
64
- // 开启系统代理
65
- triggerManualProxy(true, host, port, bypass)
66
- spinner.succeed(`系统代理已开启: ${host}:${port}`)
67
- return true
68
- } catch (err) {
69
- if (spinner && spinner.isSpinning) spinner.fail(`开启系统代理失败: ${err.message}`)
70
- else console.error(err.message)
71
- return false
72
- }
73
- }
74
-
75
- export async function disableSystemProxy() {
76
- try {
77
- // 关闭系统代理
78
- triggerManualProxy(false, '', 0, '')
79
-
80
- console.log('系统代理已关闭')
81
- return true
82
- } catch (err) {
83
- console.error(`关闭系统代理失败: ${err.message}`)
84
- return false
85
- }
86
- }
87
-
88
- export async function getSystemProxyStatus() {
89
- // 使用 macOS networksetup 命令获取系统代理状态
90
- if (process.platform === 'darwin') {
91
- try {
92
- const output = execSync('networksetup -getwebproxy Wi-Fi', { encoding: 'utf-8' })
93
- const enabled = output.includes('Enabled: Yes')
94
- if (enabled) {
95
- const serverMatch = output.match(/Server: (.+)/)
96
- const portMatch = output.match(/Port: (\d+)/)
97
- const server = serverMatch ? serverMatch[1].trim() : ''
98
- const port = portMatch ? portMatch[1].trim() : ''
99
- return { enabled: true, server, port }
100
- }
101
- return { enabled: false }
102
- } catch (e) {
103
- return { enabled: false, error: e.message }
104
- }
105
- } else if (process.platform === 'win32') {
106
- try {
107
- const regPath = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'
108
- // 查询 ProxyEnable
109
- const enableOutput = execSync(`reg query "${regPath}" /v ProxyEnable`, { encoding: 'utf-8' })
110
- // 检查输出中是否包含 0x1,表示代理已开启
111
- const isEnabled = /ProxyEnable\s+REG_DWORD\s+0x1/.test(enableOutput)
112
-
113
- if (isEnabled) {
114
- let server = ''
115
- let port = ''
116
- try {
117
- // 查询 ProxyServer
118
- const serverOutput = execSync(`reg query "${regPath}" /v ProxyServer`, { encoding: 'utf-8' })
119
- const match = serverOutput.match(/ProxyServer\s+REG_SZ\s+(.*)/)
120
-
121
- if (match && match[1]) {
122
- const fullAddress = match[1].trim()
123
- // 简单的处理 host:port 格式
124
- const lastColonIndex = fullAddress.lastIndexOf(':')
125
- if (lastColonIndex !== -1) {
126
- server = fullAddress.substring(0, lastColonIndex)
127
- port = fullAddress.substring(lastColonIndex + 1)
128
- } else {
129
- server = fullAddress
130
- }
131
- }
132
- } catch (e) {
133
- // 忽略获取详细信息的错误
134
- }
135
- return { enabled: true, server, port }
136
- }
137
- return { enabled: false }
138
- } catch (e) {
139
- // 如果注册表键不存在或查询出错
140
- return { enabled: false, error: e.message }
141
- }
142
- } else {
143
- return { enabled: false, error: '不支持的平台' }
144
- }
145
- }
1
+ import { triggerManualProxy } from '@mihomo-party/sysproxy'
2
+ import { execSync } from 'child_process'
3
+ import * as api from './api.js'
4
+
5
+ const defaultBypass = (() => {
6
+ switch (process.platform) {
7
+ case 'linux':
8
+ return ['localhost', '127.0.0.1', '192.168.0.0/16', '10.0.0.0/8', '172.16.0.0/12', '::1']
9
+ case 'darwin':
10
+ return [
11
+ '127.0.0.1',
12
+ '192.168.0.0/16',
13
+ '10.0.0.0/8',
14
+ '172.16.0.0/12',
15
+ 'localhost',
16
+ '*.local',
17
+ '*.crashlytics.com',
18
+ '<local>',
19
+ ]
20
+ case 'win32':
21
+ return [
22
+ 'localhost',
23
+ '127.*',
24
+ '192.168.*',
25
+ '10.*',
26
+ '172.16.*',
27
+ '172.17.*',
28
+ '172.18.*',
29
+ '172.19.*',
30
+ '172.20.*',
31
+ '172.21.*',
32
+ '172.22.*',
33
+ '172.23.*',
34
+ '172.24.*',
35
+ '172.25.*',
36
+ '172.26.*',
37
+ '172.27.*',
38
+ '172.28.*',
39
+ '172.29.*',
40
+ '172.30.*',
41
+ '172.31.*',
42
+ '<local>',
43
+ ]
44
+ default:
45
+ return ['localhost', '127.0.0.1', '192.168.0.0/16', '10.0.0.0/8', '172.16.0.0/12', '::1']
46
+ }
47
+ })()
48
+
49
+ export async function enableSystemProxy() {
50
+ try {
51
+ const config = await api.getConfig()
52
+ const port = config['mixed-port'] || config['port']
53
+ if (!port) throw new Error('未找到 HTTP 代理端口配置 (port 或 mixed-port)')
54
+ // 默认代理地址为 127.0.0.1
55
+ const host = '127.0.0.1'
56
+ const bypass = defaultBypass.join(',')
57
+
58
+ // 开启系统代理
59
+ triggerManualProxy(true, host, port, bypass)
60
+ return { success: true, host, port }
61
+ } catch (err) {
62
+ return { success: false, error: err.message }
63
+ }
64
+ }
65
+
66
+ export async function disableSystemProxy() {
67
+ try {
68
+ // 关闭系统代理
69
+ triggerManualProxy(false, '', 0, '')
70
+ return { success: true }
71
+ } catch (err) {
72
+ return { success: false, error: err.message }
73
+ }
74
+ }
75
+
76
+ export async function getSystemProxyStatus() {
77
+ // 使用 macOS networksetup 命令获取系统代理状态
78
+ if (process.platform === 'darwin') {
79
+ try {
80
+ const output = execSync('networksetup -getwebproxy Wi-Fi', { encoding: 'utf-8' })
81
+ const enabled = output.includes('Enabled: Yes')
82
+ if (enabled) {
83
+ const serverMatch = output.match(/Server: (.+)/)
84
+ const portMatch = output.match(/Port: (\d+)/)
85
+ const server = serverMatch ? serverMatch[1].trim() : ''
86
+ const port = portMatch ? portMatch[1].trim() : ''
87
+ return { enabled: true, server, port }
88
+ }
89
+ return { enabled: false }
90
+ } catch (e) {
91
+ return { enabled: false, error: e.message }
92
+ }
93
+ } else if (process.platform === 'win32') {
94
+ try {
95
+ const regPath = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'
96
+ // 查询 ProxyEnable
97
+ const enableOutput = execSync(`reg query "${regPath}" /v ProxyEnable`, { encoding: 'utf-8' })
98
+ // 检查输出中是否包含 0x1,表示代理已开启
99
+ const isEnabled = /ProxyEnable\s+REG_DWORD\s+0x1/.test(enableOutput)
100
+
101
+ if (isEnabled) {
102
+ let server = ''
103
+ let port = ''
104
+ try {
105
+ // 查询 ProxyServer
106
+ const serverOutput = execSync(`reg query "${regPath}" /v ProxyServer`, { encoding: 'utf-8' })
107
+ const match = serverOutput.match(/ProxyServer\s+REG_SZ\s+(.*)/)
108
+
109
+ if (match && match[1]) {
110
+ const fullAddress = match[1].trim()
111
+ // 简单的处理 host:port 格式
112
+ const lastColonIndex = fullAddress.lastIndexOf(':')
113
+ if (lastColonIndex !== -1) {
114
+ server = fullAddress.substring(0, lastColonIndex)
115
+ port = fullAddress.substring(lastColonIndex + 1)
116
+ } else {
117
+ server = fullAddress
118
+ }
119
+ }
120
+ } catch (e) {
121
+ // 忽略获取详细信息的错误
122
+ }
123
+ return { enabled: true, server, port }
124
+ }
125
+ return { enabled: false }
126
+ } catch (e) {
127
+ // 如果注册表键不存在或查询出错
128
+ return { enabled: false, error: e.message }
129
+ }
130
+ } else {
131
+ return { enabled: false, error: '不支持的平台' }
132
+ }
133
+ }
package/lib/tun.js CHANGED
@@ -1,126 +1,126 @@
1
- import { execSync } from 'child_process'
2
- import fs from 'fs'
3
- import path from 'path'
4
- import YAML from 'yaml'
5
- import { fileURLToPath } from 'url'
6
- import { reloadConfig } from './api.js'
7
-
8
- const __filename = fileURLToPath(import.meta.url)
9
- const __dirname = path.dirname(__filename)
10
- const CONFIG_PATH = path.join(__dirname, '../config.yaml')
11
- const BIN_PATH = path.join(__dirname, '../clash-kit')
12
-
13
- export function checkTunPermissions() {
14
- if (process.platform === 'win32') return true // Windows 需要管理员权限终端,难以通过文件属性判断
15
-
16
- try {
17
- const stats = fs.statSync(BIN_PATH)
18
- // SUID 位是 0o4000
19
- // 检查所有者是否为 root (uid 0) 并且拥有 SUID 位
20
- const isRootOwned = stats.uid === 0
21
- const hasSuid = (stats.mode & 0o4000) === 0o4000
22
- return isRootOwned && hasSuid
23
- } catch (e) {
24
- return false
25
- }
26
- }
27
-
28
- export function setupPermissions() {
29
- if (process.platform === 'win32') {
30
- throw new Error('Windows 请使用管理员身份运行终端即可')
31
- }
32
-
33
- const group = process.platform === 'darwin' ? 'admin' : 'root'
34
- const cmdChown = `chown root:${group} "${BIN_PATH}"`
35
- const cmdChmod = `chmod +sx "${BIN_PATH}"`
36
-
37
- try {
38
- console.log('正在提升内核权限 (需要 sudo 密码)...')
39
- execSync(`sudo ${cmdChown}`, { stdio: 'inherit' })
40
- execSync(`sudo ${cmdChmod}`, { stdio: 'inherit' })
41
- return true
42
- } catch (e) {
43
- throw new Error('权限设置失败')
44
- }
45
- }
46
-
47
- export async function isTunEnabled() {
48
- try {
49
- if (!fs.existsSync(CONFIG_PATH)) return false
50
- const file = fs.readFileSync(CONFIG_PATH, 'utf8')
51
- const config = YAML.parse(file)
52
- return config?.tun?.enable === true
53
- } catch (error) {
54
- return false
55
- }
56
- }
57
-
58
- export async function enableTun() {
59
- await updateTunConfig(true)
60
- }
61
-
62
- export async function disableTun() {
63
- await updateTunConfig(false)
64
- }
65
-
66
- async function updateTunConfig(enable) {
67
- try {
68
- if (!fs.existsSync(CONFIG_PATH)) {
69
- throw new Error('配置文件不存在')
70
- }
71
-
72
- const file = fs.readFileSync(CONFIG_PATH, 'utf8')
73
- const config = YAML.parse(file) || {}
74
-
75
- // 基础 TUN 配置
76
- if (!config.tun) {
77
- config.tun = {}
78
- }
79
-
80
- // 强制更新关键配置
81
- config.tun.enable = enable
82
- if (enable) {
83
- config.tun.stack = 'mixed' // 推荐使用 mixed 模式 (system/gvisor)
84
- config.tun['auto-route'] = true
85
- config.tun['auto-detect-interface'] = true
86
- config.tun['dns-hijack'] = ['any:53']
87
- if (process.platform === 'darwin') {
88
- config.tun.device = 'utun1500' // macOS 推荐指定 device
89
- }
90
- }
91
-
92
- // 开启 TUN 时必须配合 DNS 增强模式
93
- if (enable) {
94
- if (!config.dns) config.dns = {}
95
-
96
- config.dns.enable = true
97
- config.dns['enhanced-mode'] = 'fake-ip'
98
- config.dns.listen = config.dns.listen || '0.0.0.0:1053'
99
- config.dns.ipv6 = false // 避免 IPv6 导致的一些漏网之鱼,视情况开启
100
-
101
- // 确保有可用的 Nameservers (参考 clash-party 默认值)
102
- const defaultNameservers = [
103
- 'https://223.5.5.5/dns-query', // AliDNS
104
- 'https://doh.pub/dns-query', // DNSPod
105
- '8.8.8.8',
106
- ]
107
-
108
- if (!config.dns.nameserver || config.dns.nameserver.length === 0) {
109
- config.dns.nameserver = defaultNameservers
110
- }
111
-
112
- // 添加 Fake-IP 过滤,防止回环
113
- if (!config.dns['fake-ip-filter']) {
114
- config.dns['fake-ip-filter'] = ['*', '+.lan', '+.local']
115
- }
116
- }
117
-
118
- const newYaml = YAML.stringify(config)
119
- fs.writeFileSync(CONFIG_PATH, newYaml, 'utf8')
120
-
121
- // 重载配置使生效
122
- await reloadConfig(CONFIG_PATH)
123
- } catch (error) {
124
- throw new Error(`修改 TUN 配置失败: ${error.message}`)
125
- }
126
- }
1
+ import { execSync } from 'child_process'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+ import YAML from 'yaml'
5
+ import { fileURLToPath } from 'url'
6
+ import { reloadConfig } from './api.js'
7
+
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = path.dirname(__filename)
10
+ const CONFIG_PATH = path.join(__dirname, '../config.yaml')
11
+ const BIN_PATH = path.join(__dirname, '../clash-kit')
12
+
13
+ export function checkTunPermissions() {
14
+ if (process.platform === 'win32') return true // Windows 需要管理员权限终端,难以通过文件属性判断
15
+
16
+ try {
17
+ const stats = fs.statSync(BIN_PATH)
18
+ // SUID 位是 0o4000
19
+ // 检查所有者是否为 root (uid 0) 并且拥有 SUID 位
20
+ const isRootOwned = stats.uid === 0
21
+ const hasSuid = (stats.mode & 0o4000) === 0o4000
22
+ return isRootOwned && hasSuid
23
+ } catch (e) {
24
+ return false
25
+ }
26
+ }
27
+
28
+ export function setupPermissions() {
29
+ if (process.platform === 'win32') {
30
+ throw new Error('Windows 请使用管理员身份运行终端即可')
31
+ }
32
+
33
+ const group = process.platform === 'darwin' ? 'admin' : 'root'
34
+ const cmdChown = `chown root:${group} "${BIN_PATH}"`
35
+ const cmdChmod = `chmod +sx "${BIN_PATH}"`
36
+
37
+ try {
38
+ console.log('正在提升内核权限 (需要 sudo 密码)...')
39
+ execSync(`sudo ${cmdChown}`, { stdio: 'inherit' })
40
+ execSync(`sudo ${cmdChmod}`, { stdio: 'inherit' })
41
+ return true
42
+ } catch (e) {
43
+ throw new Error('权限设置失败')
44
+ }
45
+ }
46
+
47
+ export async function isTunEnabled() {
48
+ try {
49
+ if (!fs.existsSync(CONFIG_PATH)) return false
50
+ const file = fs.readFileSync(CONFIG_PATH, 'utf8')
51
+ const config = YAML.parse(file)
52
+ return config?.tun?.enable === true
53
+ } catch (error) {
54
+ return false
55
+ }
56
+ }
57
+
58
+ export async function enableTun() {
59
+ await updateTunConfig(true)
60
+ }
61
+
62
+ export async function disableTun() {
63
+ await updateTunConfig(false)
64
+ }
65
+
66
+ async function updateTunConfig(enable) {
67
+ try {
68
+ if (!fs.existsSync(CONFIG_PATH)) {
69
+ throw new Error('配置文件不存在')
70
+ }
71
+
72
+ const file = fs.readFileSync(CONFIG_PATH, 'utf8')
73
+ const config = YAML.parse(file) || {}
74
+
75
+ // 基础 TUN 配置
76
+ if (!config.tun) {
77
+ config.tun = {}
78
+ }
79
+
80
+ // 强制更新关键配置
81
+ config.tun.enable = enable
82
+ if (enable) {
83
+ config.tun.stack = 'mixed' // 推荐使用 mixed 模式 (system/gvisor)
84
+ config.tun['auto-route'] = true
85
+ config.tun['auto-detect-interface'] = true
86
+ config.tun['dns-hijack'] = ['any:53']
87
+ if (process.platform === 'darwin') {
88
+ config.tun.device = 'utun1500' // macOS 推荐指定 device
89
+ }
90
+ }
91
+
92
+ // 开启 TUN 时必须配合 DNS 增强模式
93
+ if (enable) {
94
+ if (!config.dns) config.dns = {}
95
+
96
+ config.dns.enable = true
97
+ config.dns['enhanced-mode'] = 'fake-ip'
98
+ config.dns.listen = config.dns.listen || '0.0.0.0:1053'
99
+ config.dns.ipv6 = false // 避免 IPv6 导致的一些漏网之鱼,视情况开启
100
+
101
+ // 确保有可用的 Nameservers (参考 clash-party 默认值)
102
+ const defaultNameservers = [
103
+ 'https://223.5.5.5/dns-query', // AliDNS
104
+ 'https://doh.pub/dns-query', // DNSPod
105
+ '8.8.8.8',
106
+ ]
107
+
108
+ if (!config.dns.nameserver || config.dns.nameserver.length === 0) {
109
+ config.dns.nameserver = defaultNameservers
110
+ }
111
+
112
+ // 添加 Fake-IP 过滤,防止回环
113
+ if (!config.dns['fake-ip-filter']) {
114
+ config.dns['fake-ip-filter'] = ['*', '+.lan', '+.local']
115
+ }
116
+ }
117
+
118
+ const newYaml = YAML.stringify(config)
119
+ fs.writeFileSync(CONFIG_PATH, newYaml, 'utf8')
120
+
121
+ // 重载配置使生效
122
+ await reloadConfig(CONFIG_PATH)
123
+ } catch (error) {
124
+ throw new Error(`修改 TUN 配置失败: ${error.message}`)
125
+ }
126
+ }
package/package.json CHANGED
@@ -1,49 +1,49 @@
1
- {
2
- "name": "clash-kit",
3
- "version": "1.1.1",
4
- "type": "module",
5
- "description": "A command-line interface for managing Clash configurations, subscriptions, and proxies.",
6
- "bin": {
7
- "ck": "./bin/index.js",
8
- "clash": "./bin/index.js"
9
- },
10
- "files": [
11
- "bin",
12
- "lib",
13
- ".gitignore",
14
- "default.yaml",
15
- "country.mmdb",
16
- "index.js",
17
- "LICENSE.md",
18
- "package.json",
19
- "README.md"
20
- ],
21
- "main": "index.js",
22
- "scripts": {
23
- "start": "node bin/index.js start",
24
- "test": "echo \"Error: no test specified\" && exit 1",
25
- "prepublishOnly": "node scripts/release.js"
26
- },
27
- "keywords": [
28
- "clash",
29
- "proxy",
30
- "cli",
31
- "subscription",
32
- "manager"
33
- ],
34
- "author": "荣顶",
35
- "license": "ISC",
36
- "packageManager": "pnpm@10.23.0",
37
- "dependencies": {
38
- "@inquirer/prompts": "^8.1.0",
39
- "@mihomo-party/sysproxy": "^2.0.8",
40
- "adm-zip": "^0.5.16",
41
- "axios": "^1.13.2",
42
- "chalk": "^5.6.2",
43
- "cli-progress": "^3.12.0",
44
- "commander": "^14.0.2",
45
- "inquirer": "^13.1.0",
46
- "ora": "^9.0.0",
47
- "yaml": "^2.8.2"
48
- }
49
- }
1
+ {
2
+ "name": "clash-kit",
3
+ "version": "1.1.5",
4
+ "type": "module",
5
+ "description": "A command-line interface for managing Clash configurations, subscriptions, and proxies.",
6
+ "bin": {
7
+ "ck": "./bin/index.js",
8
+ "clash": "./bin/index.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "lib",
13
+ ".gitignore",
14
+ "default.yaml",
15
+ "country.mmdb",
16
+ "LICENSE.md",
17
+ "package.json",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "start": "node bin/index.js start",
22
+ "test": "echo \"Error: no test specified\" && exit 1",
23
+ "prepublishOnly": "node scripts/release.js"
24
+ },
25
+ "keywords": [
26
+ "clash",
27
+ "proxy",
28
+ "cli",
29
+ "subscription",
30
+ "manager"
31
+ ],
32
+ "author": "荣顶",
33
+ "license": "ISC",
34
+ "packageManager": "pnpm@10.23.0",
35
+ "dependencies": {
36
+ "@inquirer/prompts": "^8.1.0",
37
+ "@mihomo-party/sysproxy": "^2.0.8",
38
+ "adm-zip": "^0.5.16",
39
+ "axios": "^1.13.2",
40
+ "boxen": "^8.0.1",
41
+ "chalk": "^5.6.2",
42
+ "cli-progress": "^3.12.0",
43
+ "commander": "^14.0.2",
44
+ "inquirer": "^13.1.0",
45
+ "ora": "^9.0.0",
46
+ "update-notifier": "^7.3.1",
47
+ "yaml": "^2.8.2"
48
+ }
49
+ }