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 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
+ }
@@ -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
- 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}`)
@@ -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 {
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clash-kit",
3
- "version": "1.0.1",
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
  ],