clash-kit 1.0.3 → 1.0.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/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # Clash Kit
2
2
 
3
- <img width="800" alt="clash-kit" src="https://github.com/user-attachments/assets/dd7dfd29-f59a-418b-8623-6ab08ece9ddb" />
4
-
5
3
  一个基于 Node.js 的 Clash 命令行管理工具,旨在简化 Clash 的配置管理、订阅切换和节点测速等操作。
6
4
 
5
+ ## 截图
6
+
7
+ <img width="1920" alt="image" src="https://github.com/user-attachments/assets/1183f778-62b0-4ac7-ab55-b821b66161f0" />
8
+
7
9
  ## 特性
8
10
 
9
11
  - 🔄 **订阅管理**:支持添加、切换多个订阅源。
@@ -42,10 +44,13 @@ ck init
42
44
 
43
45
  ```bash
44
46
  # 启动 Clash 代理服务
45
- ck start
47
+ ck start # 或者 ck on
46
48
 
47
49
  # 启动并自动开启系统代理
48
50
  ck start -s
51
+
52
+ # 关闭服务并关闭系统代理
53
+ ck stop # 或者 ck off
49
54
  ```
50
55
 
51
56
  ### 4. 添加订阅
@@ -54,18 +59,24 @@ ck start -s
54
59
  # 交互式管理订阅(添加、切换、删除等)【推荐使用这种方式来管理订阅】
55
60
  ck sub
56
61
 
62
+ # 列出所有订阅
63
+ ck sub -l
64
+
57
65
  # 手动添加订阅
58
66
  ck sub -a "https://example.com/subscribe?token=xxx" -n "abcName"
59
67
  ```
60
68
 
61
- ### 4. 节点测速与切换
69
+ ### 4. 节点切换(自动测速)
62
70
 
63
- ```bash
64
- # 测速
65
- ck test
71
+ 进入交互式界面,自动对当前节点组进行并发测速,并展示带有即时延迟数据的节点列表供选择。
66
72
 
67
- # 切换节点
68
- ck proxy
73
+ ```bash
74
+ # 切换节点 (支持别名: node, proxy, switch)
75
+ ck use
76
+ # 或者
77
+ ck switch
78
+ # 或者
79
+ ck node
69
80
  ```
70
81
 
71
82
  ### 5. 更多功能
@@ -74,30 +85,34 @@ ck proxy
74
85
  # 查看状态
75
86
  ck status
76
87
 
88
+ # 节点并发测速 (仅测速不切换,支持别名: test, ls, t)
89
+ ck list
90
+
91
+ # 设置系统代理
92
+ ck sys on
93
+ ck sys off
94
+
77
95
  # 开启 TUN 模式 (需要 sudo 权限)
78
- sudo ck tun on
96
+ ck tun on # 开启
97
+ ck tun off # 关闭
79
98
  ```
80
99
 
81
100
  ## 命令详解
82
101
 
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" />
102
+ | 命令 (别名) | 说明 | 示例 |
103
+ | ----------------------------- | -------------------------- | --------------------------------------- |
104
+ | `ck init` | 初始化内核及权限 | `ck init` |
105
+ | `ck start` (`on`) | 启动 Clash 服务 | `ck on` `ck on -s` (启动并设置系统代理) |
106
+ | `ck stop` (`off`) | 停止服务并关闭代理 | `ck off` |
107
+ | `ck status` (`info`, `view`) | 查看运行状态及当前节点延迟 | `ck status` |
108
+ | `ck sysproxy` (`sys`) | 设置系统代理 | `ck sys on` / `ck sys off` |
109
+ | `ck tun` | 设置 TUN 模式 (需要 sudo) | `ck tun on` |
110
+ | `ck sub` | 管理订阅(交互式)【推荐】 | `ck sub` |
111
+ | `ck sub -l` | 列出所有订阅 | `ck sub -l` |
112
+ | `ck sub -a <url>` | 添加订阅 | `ck sub -a "http..." -n "pro"` |
113
+ | `ck sub -u <name>` | 切换订阅 | `ck sub -u "pro"` |
114
+ | `ck use` (`node`, `switch`) | 切换节点 (自动测速) | `ck use` / `ck node` |
115
+ | `ck list` (`ls`, `test`, `t`) | 节点测速列表 (不切换) | `ck list` / `ck test` |
101
116
 
102
117
  ## License
103
118
 
package/bin/index.js CHANGED
@@ -23,10 +23,15 @@ program
23
23
  .action(init)
24
24
 
25
25
  // 启动 clash 服务
26
- program.command('start').description('启动 Clash 服务').option('-s, --sysproxy', '启动后自动开启系统代理').action(start)
26
+ program
27
+ .command('start')
28
+ .alias('on')
29
+ .description('启动 Clash 服务')
30
+ .option('-s, --sysproxy', '启动后自动开启系统代理')
31
+ .action(start)
27
32
 
28
33
  // 停止 clash 服务
29
- program.command('stop').description('停止 Clash 服务').action(stop)
34
+ program.command('stop').alias('off').description('停止 Clash 服务').action(stop)
30
35
 
31
36
  // 设置系统代理
32
37
  program
@@ -40,7 +45,7 @@ program
40
45
  program.command('tun').description('设置 TUN 模式 (可能需要提权)').argument('[action]', 'on 或 off').action(setTun)
41
46
 
42
47
  // 查看 clash 状态
43
- program.command('status').alias('st').description('查看 Clash 运行状态').action(status)
48
+ program.command('status').alias('st').alias('view').alias('info').description('查看 Clash 运行状态').action(status)
44
49
 
45
50
  // 管理订阅
46
51
  program
@@ -53,9 +58,19 @@ program
53
58
  .action(manageSub)
54
59
 
55
60
  // 切换节点
56
- program.command('proxy').alias('p').description('切换节点').action(proxy)
61
+ program
62
+ .command('use')
63
+ .aliases(['node', 'proxy', 'switch'])
64
+ .description('切换节点 (别名: node, proxy, switch)')
65
+ .action(proxy)
57
66
 
58
- // 节点测速
59
- program.command('test').alias('t').description('节点测速').action(test)
67
+ // 列出所有节点,并测速
68
+ program
69
+ .command('list')
70
+ .alias('ls')
71
+ .alias('test')
72
+ .alias('t')
73
+ .description('节点测速 (别名: list, ls, test, t) ')
74
+ .action(test)
60
75
 
61
76
  program.parse(process.argv)
package/index.js CHANGED
@@ -2,27 +2,75 @@ import { spawn, execSync } from 'child_process'
2
2
  import path from 'path'
3
3
  import fs from 'fs'
4
4
  import chalk from 'chalk'
5
+ import axios from 'axios'
6
+ import ora from 'ora'
7
+ import YAML from 'yaml'
5
8
  import { fileURLToPath } from 'url'
6
9
  import { getApiBase, getProxyPort } from './lib/api.js'
7
10
  import * as sysproxy from './lib/sysproxy.js'
8
11
  import * as tun from './lib/tun.js'
12
+ import { isPortOpen, extractPort, getPortOccupier } from './lib/port.js'
9
13
 
10
14
  const __filename = fileURLToPath(import.meta.url)
11
15
  const __dirname = path.dirname(__filename)
12
16
 
13
- // ---------------- 1. 配置项 ----------------
17
+ // ---------------- 配置项 ----------------
14
18
  const CLASH_BIN_PATH = path.join(__dirname, 'clash-kit') // 解压后的二进制文件路径
15
19
  const CLASH_CONFIG_PATH = path.join(__dirname, 'config.yaml') // 配置文件路径
16
20
 
17
- // ---------------- 2. 启动 Clash.Meta 进程 ----------------
18
- function startClash() {
21
+ async function checkPorts() {
22
+ try {
23
+ if (fs.existsSync(CLASH_CONFIG_PATH)) {
24
+ const configContent = fs.readFileSync(CLASH_CONFIG_PATH, 'utf8')
25
+ const config = YAML.parse(configContent)
26
+
27
+ const checks = [
28
+ { key: 'mixed-port', name: 'Mixed Port' },
29
+ { key: 'port', name: 'HTTP Port' },
30
+ { key: 'socks-port', name: 'SOCKS Port' },
31
+ { key: 'external-controller', name: 'External Controller' },
32
+ ]
33
+
34
+ for (const check of checks) {
35
+ const val = config[check.key]
36
+ const port = extractPort(val)
37
+ if (port) {
38
+ const isOpen = await isPortOpen(port)
39
+ if (!isOpen) {
40
+ const occupier = getPortOccupier(port)
41
+ const occupierInfo = occupier ? ` (被 ${occupier} 占用)` : ''
42
+
43
+ console.error(chalk.red(`\n启动失败: 端口 ${port} (${check.name}) 已被占用${occupierInfo}`))
44
+ console.error(chalk.yellow(`请检查是否有其他代理软件正在运行,或修改 config.yaml 中的 ${check.key} \n`))
45
+
46
+ if (!occupierInfo) {
47
+ console.error(`占用进程未知,可能是权限不足或系统进程`)
48
+ console.error(chalk.yellow(`提示: 可尝试使用 'sudo lsof -i :${port}' 手动查看端口占用情况`))
49
+ }
50
+ process.exit(1)
51
+ }
52
+ }
53
+ }
54
+ }
55
+ } catch (e) {
56
+ console.error(chalk.yellow('警告: 端口检查预检失败,将尝试直接启动内核:', e.message))
57
+ }
58
+ }
59
+
60
+ // ---------------- 启动 Clash.Meta 进程 ----------------
61
+ async function startClash() {
19
62
  // 尝试停止已存在的进程
20
63
  try {
21
64
  execSync('pkill -f clash-kit')
65
+ // 稍微等待端口释放,避免 restart 时偶发端口占用报错
66
+ await new Promise(resolve => setTimeout(resolve, 500))
22
67
  } catch (e) {
23
68
  // 忽略错误,说明没有运行中的进程
24
69
  }
25
70
 
71
+ // 检查端口占用 (核心策略:报错/启动失败)
72
+ await checkPorts()
73
+
26
74
  const logPath = path.join(__dirname, 'clash.log')
27
75
  const logFd = fs.openSync(logPath, 'a')
28
76
 
@@ -50,7 +98,7 @@ function startClash() {
50
98
  return clashProcess
51
99
  }
52
100
 
53
- // ---------------- 3. 清理函数 ----------------
101
+ // 清理函数
54
102
  async function cleanup() {
55
103
  try {
56
104
  // 关闭系统代理
@@ -75,7 +123,7 @@ async function cleanup() {
75
123
  }
76
124
  }
77
125
 
78
- // ---------------- 4. 注册进程退出处理 ----------------
126
+ // 注册进程退出处理
79
127
  function setupExitHandlers() {
80
128
  // 处理正常退出 (Ctrl+C)
81
129
  process.on('SIGINT', async () => {
@@ -99,8 +147,21 @@ function setupExitHandlers() {
99
147
  })
100
148
  }
101
149
 
102
- // ---------------- 5. 执行流程 ----------------
103
- export function main() {
150
+ // 检查服务健康状态
151
+ async function checkServiceHealth(apiBase, maxRetries = 20) {
152
+ for (let i = 0; i < maxRetries; i++) {
153
+ try {
154
+ await axios.get(apiBase, { timeout: 1000 })
155
+ return true
156
+ } catch (e) {
157
+ if (e.response) return true // 端口已通 (即使是 401 也可以)
158
+ await new Promise(r => setTimeout(r, 200)) // 200ms * 20 = 4s
159
+ }
160
+ }
161
+ return false
162
+ }
163
+
164
+ export async function main() {
104
165
  // 检查 clash-kit 二进制文件是否存在
105
166
  if (!fs.existsSync(CLASH_BIN_PATH)) {
106
167
  return console.error(chalk.red('\n找不到 Clash.Meta 内核文件,请先运行 clash init 命令初始化内核!\n'))
@@ -113,10 +174,30 @@ export function main() {
113
174
  // 设置退出处理
114
175
  setupExitHandlers()
115
176
 
116
- const clashProcess = startClash()
177
+ const clashProcess = await startClash()
178
+
179
+ const spinner = ora('正在等待服务启动...').start()
180
+ const started = await checkServiceHealth(getApiBase())
181
+
182
+ if (!started) {
183
+ spinner.fail(chalk.red('启动失败'))
184
+ const logPath = path.join(__dirname, 'clash.log')
185
+ if (fs.existsSync(logPath)) {
186
+ console.log(chalk.yellow('\n------- clash.log (Last 20 lines) -------'))
187
+ const lines = fs.readFileSync(logPath, 'utf8').trim().split('\n')
188
+ console.log(lines.slice(-20).join('\n'))
189
+ console.log(chalk.yellow('-----------------------------------------\n'))
190
+ }
191
+ try {
192
+ process.kill(clashProcess.pid)
193
+ } catch (e) {}
194
+ process.exit(1)
195
+ }
196
+
197
+ spinner.succeed(chalk.green('启动成功'))
198
+
117
199
  const { http, socks } = getProxyPort()
118
200
 
119
- console.log(chalk.green('\n代理服务已在后台启动✅'))
120
201
  if (clashProcess.pid) {
121
202
  console.log(`进程名称:${chalk.yellow('clash-kit')} PID: ${chalk.yellow(clashProcess.pid)}`)
122
203
  }
@@ -1,11 +1,12 @@
1
1
  import { select } from '@inquirer/prompts'
2
2
  import ora from 'ora'
3
+ import chalk from 'chalk'
3
4
  import * as api from '../api.js'
4
5
 
5
6
  export async function proxy() {
6
7
  let spinner = ora('正在获取最新代理列表...').start()
7
8
  try {
8
- const proxies = await api.getProxies()
9
+ let proxies = await api.getProxies()
9
10
  spinner.stop()
10
11
 
11
12
  // 通常我们只关心 Proxy 组或者 Selector 类型的组
@@ -24,10 +25,42 @@ export async function proxy() {
24
25
 
25
26
  const group = proxies[groupName]
26
27
 
28
+ // 自动对组内所有节点进行测速
29
+ spinner = ora(`测速后选择合适的节点,正在对 [${groupName}] 进行测速...`).start()
30
+ await Promise.all(group.all.map(n => api.getProxyDelay(n).catch(() => {})))
31
+
32
+ // 测速完成后,刷新数据以获取最新状态
33
+ proxies = await api.getProxies()
34
+ spinner.stop()
35
+
36
+ const updatedGroup = proxies[groupName]
37
+
27
38
  // 选择节点
28
39
  const proxyName = await select({
29
- message: `[${groupName}] 当前: ${group.now}, 请选择节点:`,
30
- choices: group.all.map(n => ({ name: n, value: n })),
40
+ message: `[${groupName}] 当前: ${updatedGroup.now}, 请选择节点:`,
41
+ pageSize: 15,
42
+ choices: updatedGroup.all.map(n => {
43
+ const node = proxies[n]
44
+ const lastHistory = node?.history && node.history.length ? node.history[node.history.length - 1] : null
45
+ let delayInfo = ''
46
+
47
+ if (lastHistory && lastHistory.delay > 0) {
48
+ const delay = lastHistory.delay
49
+ if (delay < 800) {
50
+ delayInfo = chalk.green(` ${delay}ms`)
51
+ } else if (delay < 1500) {
52
+ delayInfo = chalk.yellow(` ${delay}ms`)
53
+ } else {
54
+ delayInfo = chalk.red(` ${delay}ms`)
55
+ }
56
+ } else if (lastHistory && lastHistory.delay === 0) {
57
+ delayInfo = chalk.red(' [超时]')
58
+ } else {
59
+ delayInfo = chalk.gray(' [未测速]')
60
+ }
61
+
62
+ return { name: `${n}${delayInfo}`, value: n }
63
+ }),
31
64
  })
32
65
 
33
66
  spinner = ora(`正在切换到 ${proxyName}...`).start()
@@ -2,7 +2,7 @@ import * as sysproxy from '../sysproxy.js'
2
2
  import { main as startClashService } from '../../index.js'
3
3
 
4
4
  export async function start(options) {
5
- startClashService()
5
+ await startClashService()
6
6
 
7
7
  if (options.sysproxy) {
8
8
  console.log('正在等待 Clash API 就绪以设置系统代理...')
package/lib/port.js ADDED
@@ -0,0 +1,108 @@
1
+ import net from 'net'
2
+ import { execSync } from 'child_process'
3
+ import os from 'os'
4
+
5
+ /**
6
+ * 检查端口是否被占用 (true = 空闲, false = 被占用)
7
+ * @param {number} port
8
+ * @returns {Promise<boolean>}
9
+ */
10
+ export function isPortOpen(port) {
11
+ return new Promise((resolve) => {
12
+ const server = net.createServer()
13
+ server.once('error', (err) => {
14
+ resolve(false) // 端口被占用
15
+ })
16
+ server.once('listening', () => {
17
+ server.close()
18
+ resolve(true) // 端口可用
19
+ })
20
+ server.listen(port, '127.0.0.1')
21
+ })
22
+ }
23
+
24
+ /**
25
+ * 获取占用端口的进程信息
26
+ * @param {number} port
27
+ * @returns {string|null} 进程名称(PID),例如 "mihomo (PID: 1234)"
28
+ */
29
+ export function getPortOccupier(port) {
30
+ try {
31
+ const platform = os.platform()
32
+ if (platform === 'win32') {
33
+ // Windows 实现
34
+ try {
35
+ const output = execSync(`netstat -ano | findstr :${port}`).toString()
36
+ const lines = output.trim().split('\n')
37
+ if(lines.length > 0) {
38
+ const parts = lines[0].trim().split(/\s+/)
39
+ const pid = parts[parts.length - 1]
40
+ return `PID: ${pid}`
41
+ }
42
+ } catch (e) { return null }
43
+ } else {
44
+ // macOS / Linux
45
+ // lsof output format: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
46
+ const output = execSync(`lsof -i :${port} -sTCP:LISTEN -P -n`).toString().trim()
47
+ const lines = output.split('\n')
48
+ if (lines.length > 1) {
49
+ const parts = lines[1].trim().split(/\s+/)
50
+ return `${parts[0]} (PID: ${parts[1]})`
51
+ }
52
+ }
53
+ } catch (e) {
54
+ // lsof 找不到时会返回非 0 退出码,抛出错误
55
+ return null
56
+ }
57
+ return null
58
+ }
59
+
60
+ /**
61
+ * 寻找可用端口
62
+ * @param {number} startPort
63
+ * @returns {Promise<number>}
64
+ */
65
+ export function findAvailablePort(startPort) {
66
+ return new Promise((resolve, reject) => {
67
+ const server = net.createServer()
68
+ server.on('error', (err) => {
69
+ if (startPort <= 65535) {
70
+ // 核心逻辑:如果报错,递归尝试 +1 的端口
71
+ resolve(findAvailablePort(startPort + 1))
72
+ } else {
73
+ reject(err)
74
+ }
75
+ })
76
+ server.on('listening', () => {
77
+ server.close(() => {
78
+ resolve(startPort)
79
+ })
80
+ })
81
+ server.listen(startPort, '127.0.0.1')
82
+ })
83
+ }
84
+
85
+ /**
86
+ * 从配置值中提取端口号
87
+ * @param {string|number} val - 例如 9090, ":9090", "127.0.0.1:9090"
88
+ * @returns {number|null}
89
+ */
90
+ export function extractPort(val) {
91
+ if (!val) return null
92
+ if (typeof val === 'number') return val
93
+ if (typeof val === 'string') {
94
+ // 移除空白
95
+ val = val.trim()
96
+ // 如果是纯数字字符串
97
+ if (/^\d+$/.test(val)) return parseInt(val, 10)
98
+
99
+ // 如果包含冒号,取最后一部分
100
+ if (val.includes(':')) {
101
+ const parts = val.split(':')
102
+ const lastPart = parts[parts.length - 1]
103
+ const port = parseInt(lastPart, 10)
104
+ return isNaN(port) ? null : port
105
+ }
106
+ }
107
+ return null
108
+ }
@@ -4,6 +4,7 @@ import axios from 'axios'
4
4
  import { fileURLToPath } from 'url'
5
5
  import ora from 'ora'
6
6
  import { reloadConfig, isClashRunning } from './api.js'
7
+ import YAML from 'yaml'
7
8
 
8
9
  const __filename = fileURLToPath(import.meta.url)
9
10
  const __dirname = path.dirname(__filename)
@@ -20,9 +21,46 @@ if (!fs.existsSync(PROFILES_DIR)) {
20
21
  export async function downloadSubscription(url, name) {
21
22
  const spinner = ora(`正在下载订阅 ${name}...`).start()
22
23
  try {
23
- const res = await axios.get(url, { responseType: 'text' })
24
+ const res = await axios.get(url, {
25
+ responseType: 'text',
26
+ headers: {
27
+ 'User-Agent': 'Clash/1.0.0', // 伪装成 Clash 客户端,通常能直接获取 YAML 格式
28
+ },
29
+ })
30
+
31
+ let content = res.data
32
+
33
+ // 尝试解析 YAML,如果不是对象或者看起来不像配置,尝试 Base64 解码
34
+ let isConfig = false
35
+ try {
36
+ const parsed = YAML.parse(content)
37
+ if (parsed && typeof parsed === 'object' && (parsed.proxies || parsed.Proxy || parsed.port)) {
38
+ isConfig = true
39
+ }
40
+ } catch (e) {
41
+ console.warn('订阅服务商返回的不是有效 YAML,尝试 Base64 解码...')
42
+ }
43
+
44
+ if (!isConfig) {
45
+ try {
46
+ // 尝试 Base64 解码
47
+ const decoded = Buffer.from(content, 'base64').toString('utf-8')
48
+ // 再次检查解码后是否为有效 YAML 配置
49
+ const parsedDecoded = YAML.parse(decoded)
50
+ if (
51
+ parsedDecoded &&
52
+ typeof parsedDecoded === 'object' &&
53
+ (parsedDecoded.proxies || parsedDecoded.Proxy || parsedDecoded.port)
54
+ ) {
55
+ content = decoded
56
+ }
57
+ } catch (e) {
58
+ console.warn('Base64 解码失败,保留原始内容')
59
+ }
60
+ }
61
+
24
62
  const filePath = path.join(PROFILES_DIR, `${name}.yaml`)
25
- fs.writeFileSync(filePath, res.data)
63
+ fs.writeFileSync(filePath, content)
26
64
  spinner.succeed(`订阅 ${name} 下载成功`)
27
65
  return filePath
28
66
  } catch (err) {
@@ -51,9 +89,27 @@ export async function useProfile(name) {
51
89
 
52
90
  const spinner = ora(`正在切换到配置 ${name}...`).start()
53
91
 
54
- // 直接复制配置文件,保持原样
55
- fs.copyFileSync(source, CONFIG_PATH)
56
- fs.writeFileSync(CURRENT_PROFILE_PATH, name)
92
+ // 读取订阅配置
93
+ const subscriptionConfig = YAML.parse(fs.readFileSync(source, 'utf8'))
94
+
95
+ // 读取现有配置(如果存在)
96
+ let existingConfig = {}
97
+ if (fs.existsSync(CONFIG_PATH)) {
98
+ existingConfig = YAML.parse(fs.readFileSync(CONFIG_PATH, 'utf8'))
99
+ }
100
+
101
+ // 合并配置:保留用户自定义字段,更新订阅字段
102
+ const mergedConfig = {
103
+ ...subscriptionConfig,
104
+ port: existingConfig['port'],
105
+ 'bind-address': existingConfig['bind-address'],
106
+ 'socks-port': existingConfig['socks-port'],
107
+ 'allow-lan': existingConfig['allow-lan'],
108
+ // 其他需要保留的自定义字段...
109
+ }
110
+
111
+ // 写入合并后的配置
112
+ fs.writeFileSync(CONFIG_PATH, YAML.stringify(mergedConfig))
57
113
 
58
114
  // 尝试热重载
59
115
  if (await isClashRunning()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clash-kit",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "description": "A command-line interface for managing Clash configurations, subscriptions, and proxies.",
6
6
  "bin": {