clash-kit 1.0.1 → 1.0.3

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/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ .DS_Store
2
+ node_modules
3
+ .env
4
+ dist
5
+
6
+ .vscode
7
+ .idea
8
+
9
+ *.log
10
+
11
+ clash-kit
12
+ config.yaml
13
+ profiles
14
+ ruleset
15
+
16
+ # clash 相关
17
+ cache.db
18
+ geoip.metadb
19
+ m.yaml
20
+ .current_profile
package/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020-present, 荣顶 and wechat-bot contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -99,20 +99,9 @@ sudo ck tun on
99
99
 
100
100
  <img width="2742" height="1994" alt="image" src="https://github.com/user-attachments/assets/1183f778-62b0-4ac7-ab55-b821b66161f0" />
101
101
 
102
- ## 目录结构
103
-
104
- - `bin/`: CLI 入口文件
105
- - `lib/`: 核心逻辑库
106
- - `profiles/`: 存放下载的订阅配置文件
107
- - `clash-meta`: Clash 核心二进制文件
108
- - `config.yaml`: 当前生效的配置文件
109
-
110
- ## 注意事项
111
-
112
- - 本工具依赖 `clash-meta` 二进制文件,请确保其与本工具在同一目录下(安装包已包含)。
113
- - 测速功能依赖 Clash API,请确保 `clash start` 正在运行。
114
- - 默认 API 地址为 `http://127.0.0.1:9090`。
115
-
116
102
  ## License
117
103
 
118
- ISC
104
+ [MIT](./LICENSE).
105
+
106
+ 开源不易,点赞鼓励!
107
+ 点一个 Star⭐️ 支持我们~ 🌸Let's enjoy it!
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/index.js CHANGED
@@ -11,14 +11,14 @@ const __filename = fileURLToPath(import.meta.url)
11
11
  const __dirname = path.dirname(__filename)
12
12
 
13
13
  // ---------------- 1. 配置项 ----------------
14
- const CLASH_BIN_PATH = path.join(__dirname, 'clash-meta') // 解压后的二进制文件路径
14
+ const CLASH_BIN_PATH = path.join(__dirname, 'clash-kit') // 解压后的二进制文件路径
15
15
  const CLASH_CONFIG_PATH = path.join(__dirname, 'config.yaml') // 配置文件路径
16
16
 
17
17
  // ---------------- 2. 启动 Clash.Meta 进程 ----------------
18
18
  function startClash() {
19
19
  // 尝试停止已存在的进程
20
20
  try {
21
- execSync('pkill -f clash-meta')
21
+ execSync('pkill -f clash-kit')
22
22
  } catch (e) {
23
23
  // 忽略错误,说明没有运行中的进程
24
24
  }
@@ -65,7 +65,7 @@ async function cleanup() {
65
65
 
66
66
  // 停止 Clash 进程
67
67
  try {
68
- execSync('pkill -f clash-meta')
68
+ execSync('pkill -f clash-kit')
69
69
  console.log('Clash 服务已停止')
70
70
  } catch (e) {
71
71
  // 进程可能已经停止
@@ -101,7 +101,7 @@ function setupExitHandlers() {
101
101
 
102
102
  // ---------------- 5. 执行流程 ----------------
103
103
  export function main() {
104
- // 检查 clash-meta 二进制文件是否存在
104
+ // 检查 clash-kit 二进制文件是否存在
105
105
  if (!fs.existsSync(CLASH_BIN_PATH)) {
106
106
  return console.error(chalk.red('\n找不到 Clash.Meta 内核文件,请先运行 clash init 命令初始化内核!\n'))
107
107
  }
@@ -118,7 +118,7 @@ export function main() {
118
118
 
119
119
  console.log(chalk.green('\n代理服务已在后台启动✅'))
120
120
  if (clashProcess.pid) {
121
- console.log(`PID: ${chalk.yellow(clashProcess.pid)}`)
121
+ console.log(`进程名称:${chalk.yellow('clash-kit')} PID: ${chalk.yellow(clashProcess.pid)}`)
122
122
  }
123
123
 
124
124
  console.log(``)
@@ -126,7 +126,13 @@ export function main() {
126
126
  console.log(`SOCKS5 Proxy: ${chalk.cyan(`127.0.0.1:${socks}`)}`)
127
127
  console.log(`API: ${chalk.cyan.underline(getApiBase())}`)
128
128
  console.log(``)
129
- console.log(chalk.gray('提示: 如需停止代理可使用 clash stop 命令'))
129
+
130
+ console.log(chalk.gray('运行日志文件: ' + path.join(__dirname, 'clash.log')))
131
+ console.log(chalk.blue('提示:如需查看运行状态可使用 clash status 命令'))
132
+ console.log(chalk.blue('如需停止代理可使用 clash stop 命令'))
133
+ console.log(chalk.gray('----------------------------------------'))
134
+ console.log(chalk.gray('如需切换系统代理模式可使用 clash sysproxy on/off 命令'))
135
+ console.log(chalk.gray('如需切换 TUN 模式可使用 clash tun on/off 命令'))
130
136
  }
131
137
 
132
138
  // 运行脚本
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
+ }
@@ -2,25 +2,94 @@ 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, '../..')
14
- const binName = process.platform === 'win32' ? 'clash-meta.exe' : 'clash-meta'
59
+ const binName = process.platform === 'win32' ? 'clash-kit.exe' : 'clash-kit'
15
60
  const binPath = path.join(rootDir, binName)
16
61
  const configPath = path.join(rootDir, 'config.yaml')
17
62
 
18
63
  try {
19
64
  // 创建默认配置文件(如果不存在)
20
65
  if (!fs.existsSync(configPath)) {
21
- fs.writeFileSync(configPath, DEFAULT_CONFIG, 'utf8')
22
- console.log(`已创建默认配置文件: ${configPath}`)
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}`)
@@ -17,9 +17,9 @@ export async function stop() {
17
17
  console.log('TUN 模式已关闭')
18
18
  }
19
19
 
20
- // 使用 pkill 匹配进程名包含 clash-meta 的进程
20
+ // 使用 pkill 匹配进程名包含 clash-kit 的进程
21
21
  spinner.text = '正在停止 Clash 服务...'
22
- execSync('pkill -f clash-meta')
22
+ execSync('pkill -f clash-kit')
23
23
  spinner.succeed('Clash 服务已停止')
24
24
  } catch (err) {
25
25
  // pkill 如果没找到进程会抛出错误,这里捕获并提示
@@ -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/kernel.js CHANGED
@@ -52,7 +52,7 @@ export async function downloadClash(targetDir) {
52
52
  const downloadUrl = `https://github.com/MetaCubeX/mihomo/releases/download/${version}/${name}-${version}.${urlExt}`
53
53
  const tempFileName = `mihomo-temp.${urlExt}`
54
54
  const tempPath = path.join(targetDir, tempFileName)
55
- const targetBinName = `clash-meta${isWin ? '.exe' : ''}`
55
+ const targetBinName = `clash-kit${isWin ? '.exe' : ''}`
56
56
  const targetBinPath = path.join(targetDir, targetBinName)
57
57
 
58
58
  // 3. 下载文件
@@ -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
- try {
62
- await reloadConfig(CONFIG_PATH)
63
- spinner.succeed('Clash 配置已切换并热重载生效')
64
- } catch (err) {
65
- // 忽略错误,可能是 Clash 未运行
66
- spinner.warn('配置已切换,但 Clash 未运行或无法连接,将在下次启动时生效')
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/lib/tun.js CHANGED
@@ -8,7 +8,7 @@ import { reloadConfig } from './api.js'
8
8
  const __filename = fileURLToPath(import.meta.url)
9
9
  const __dirname = path.dirname(__filename)
10
10
  const CONFIG_PATH = path.join(__dirname, '../config.yaml')
11
- const BIN_PATH = path.join(__dirname, '../clash-meta')
11
+ const BIN_PATH = path.join(__dirname, '../clash-kit')
12
12
 
13
13
  export function checkTunPermissions() {
14
14
  if (process.platform === 'win32') return true // Windows 需要管理员权限终端,难以通过文件属性判断
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clash-kit",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "description": "A command-line interface for managing Clash configurations, subscriptions, and proxies.",
6
6
  "bin": {
@@ -10,7 +10,11 @@
10
10
  "files": [
11
11
  "bin",
12
12
  "lib",
13
+ ".gitignore",
14
+ "default.yaml",
15
+ "country.mmdb",
13
16
  "index.js",
17
+ "LICENSE.md",
14
18
  "package.json",
15
19
  "README.md"
16
20
  ],