@adversity/coding-tool-x 2.2.0 → 2.3.0
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/CHANGELOG.md +20 -0
- package/README.md +4 -14
- package/dist/web/assets/index-dhun1bYQ.js +3555 -0
- package/dist/web/assets/index-hHb7DAda.css +41 -0
- package/dist/web/index.html +2 -2
- package/package.json +5 -4
- package/src/index.js +2 -2
- package/src/server/api/agents.js +188 -0
- package/src/server/api/commands.js +261 -0
- package/src/server/api/config-templates.js +20 -5
- package/src/server/api/permissions.js +347 -0
- package/src/server/api/rules.js +188 -0
- package/src/server/api/skills.js +66 -14
- package/src/server/api/workspaces.js +30 -55
- package/src/server/index.js +3 -0
- package/src/server/services/agents-service.js +179 -1
- package/src/server/services/commands-service.js +231 -47
- package/src/server/services/config-templates-service.js +457 -106
- package/src/server/services/format-converter.js +506 -0
- package/src/server/services/repo-scanner-base.js +678 -0
- package/src/server/services/rules-service.js +179 -1
- package/src/server/services/skill-service.js +114 -61
- package/src/server/services/workspace-service.js +110 -1
- package/dist/web/assets/index-D1AYlFLZ.js +0 -3220
- package/dist/web/assets/index-aL3cKxSK.css +0 -41
- package/docs/CHANGELOG.md +0 -582
- package/docs/DIRECTORY_MIGRATION.md +0 -112
- package/docs/PROJECT_STRUCTURE.md +0 -396
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Permissions API 路由
|
|
3
|
+
*
|
|
4
|
+
* 管理 Claude Code / Codex / Gemini CLI 命令执行权限
|
|
5
|
+
*
|
|
6
|
+
* 三个 CLI 工具的权限控制机制:
|
|
7
|
+
*
|
|
8
|
+
* 1. Claude Code:
|
|
9
|
+
* - 配置文件: ~/.claude/settings.json (用户级) 或 .claude/settings.json (项目级)
|
|
10
|
+
* - 权限格式: permissions.allow / permissions.deny 数组
|
|
11
|
+
* - 支持通配符: Bash(npm run *), Read(./src/**)
|
|
12
|
+
*
|
|
13
|
+
* 2. Codex CLI:
|
|
14
|
+
* - 启动参数: --ask-for-approval never/on-request/on-failure
|
|
15
|
+
* - 沙箱模式: --sandbox read-only/workspace-write/danger-full-access
|
|
16
|
+
* - YOLO 模式: --yolo 或 --dangerously-bypass-approvals-and-sandbox
|
|
17
|
+
*
|
|
18
|
+
* 3. Gemini CLI:
|
|
19
|
+
* - 启动参数: --approval-mode default/auto_edit/yolo
|
|
20
|
+
* - 允许工具: --allowed-tools "ShellTool(git status)"
|
|
21
|
+
* - 沙箱: --sandbox / -s
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const express = require('express');
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const os = require('os');
|
|
28
|
+
|
|
29
|
+
const router = express.Router();
|
|
30
|
+
|
|
31
|
+
// Claude Code 设置文件路径
|
|
32
|
+
function getClaudeSettingsPath(projectPath, isLocal = false) {
|
|
33
|
+
if (projectPath) {
|
|
34
|
+
return path.join(projectPath, '.claude', isLocal ? 'settings.local.json' : 'settings.json');
|
|
35
|
+
}
|
|
36
|
+
return path.join(os.homedir(), '.claude', 'settings.json');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 读取 Claude Code settings.json
|
|
40
|
+
function readClaudeSettings(projectPath, isLocal = false) {
|
|
41
|
+
const settingsPath = getClaudeSettingsPath(projectPath, isLocal);
|
|
42
|
+
try {
|
|
43
|
+
if (fs.existsSync(settingsPath)) {
|
|
44
|
+
const content = fs.readFileSync(settingsPath, 'utf-8');
|
|
45
|
+
return JSON.parse(content);
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error('[Permissions API] Error reading Claude settings:', err);
|
|
49
|
+
}
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 保存 Claude Code settings.json
|
|
54
|
+
function saveClaudeSettings(projectPath, settings, isLocal = false) {
|
|
55
|
+
const settingsPath = getClaudeSettingsPath(projectPath, isLocal);
|
|
56
|
+
const settingsDir = path.dirname(settingsPath);
|
|
57
|
+
|
|
58
|
+
if (!fs.existsSync(settingsDir)) {
|
|
59
|
+
fs.mkdirSync(settingsDir, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 全局 all-allow 状态(内存中)
|
|
66
|
+
let globalAllAllowEnabled = process.env.CLAUDE_ALL_ALLOW === 'true';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 获取项目的命令执行权限设置
|
|
70
|
+
* GET /api/permissions
|
|
71
|
+
* Query: projectPath - 项目路径, cliType - CLI 类型 (claude/codex/gemini)
|
|
72
|
+
*/
|
|
73
|
+
router.get('/', (req, res) => {
|
|
74
|
+
try {
|
|
75
|
+
const { projectPath, cliType = 'claude' } = req.query;
|
|
76
|
+
|
|
77
|
+
if (!projectPath) {
|
|
78
|
+
return res.status(400).json({
|
|
79
|
+
success: false,
|
|
80
|
+
message: '缺少 projectPath 参数'
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!fs.existsSync(projectPath)) {
|
|
85
|
+
return res.status(404).json({
|
|
86
|
+
success: false,
|
|
87
|
+
message: '项目路径不存在'
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 读取 Claude Code 配置(所有 CLI 工具共用此配置管理)
|
|
92
|
+
const settings = readClaudeSettings(projectPath);
|
|
93
|
+
const permissions = settings.permissions || {};
|
|
94
|
+
|
|
95
|
+
res.json({
|
|
96
|
+
success: true,
|
|
97
|
+
cliType,
|
|
98
|
+
settings: {
|
|
99
|
+
// Claude Code 格式
|
|
100
|
+
allow: permissions.allow || [],
|
|
101
|
+
deny: permissions.deny || [],
|
|
102
|
+
// 兼容旧格式
|
|
103
|
+
allowedCommands: permissions.allow || [],
|
|
104
|
+
denyCommands: permissions.deny || []
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error('[Permissions API] Get permissions error:', err);
|
|
109
|
+
res.status(500).json({
|
|
110
|
+
success: false,
|
|
111
|
+
message: err.message
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 保存项目的命令执行权限设置
|
|
118
|
+
* POST /api/permissions
|
|
119
|
+
* Body: { projectPath, settings: { allow, deny }, isLocal }
|
|
120
|
+
*/
|
|
121
|
+
router.post('/', (req, res) => {
|
|
122
|
+
try {
|
|
123
|
+
const { projectPath, settings: newPermissions, isLocal = false } = req.body;
|
|
124
|
+
|
|
125
|
+
if (!projectPath) {
|
|
126
|
+
return res.status(400).json({
|
|
127
|
+
success: false,
|
|
128
|
+
message: '缺少 projectPath 参数'
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!fs.existsSync(projectPath)) {
|
|
133
|
+
return res.status(404).json({
|
|
134
|
+
success: false,
|
|
135
|
+
message: '项目路径不存在'
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 读取现有设置
|
|
140
|
+
const settings = readClaudeSettings(projectPath, isLocal);
|
|
141
|
+
|
|
142
|
+
// 更新权限设置(使用 Claude Code 的标准格式)
|
|
143
|
+
settings.permissions = {
|
|
144
|
+
allow: newPermissions?.allow || newPermissions?.allowedCommands || [],
|
|
145
|
+
deny: newPermissions?.deny || newPermissions?.denyCommands || []
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// 保存设置
|
|
149
|
+
saveClaudeSettings(projectPath, settings, isLocal);
|
|
150
|
+
|
|
151
|
+
res.json({
|
|
152
|
+
success: true,
|
|
153
|
+
message: '权限设置已保存',
|
|
154
|
+
savedTo: isLocal ? '.claude/settings.local.json' : '.claude/settings.json'
|
|
155
|
+
});
|
|
156
|
+
} catch (err) {
|
|
157
|
+
console.error('[Permissions API] Save permissions error:', err);
|
|
158
|
+
res.status(500).json({
|
|
159
|
+
success: false,
|
|
160
|
+
message: err.message
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 获取全局 all-allow 模式状态
|
|
167
|
+
* GET /api/permissions/all-allow
|
|
168
|
+
*/
|
|
169
|
+
router.get('/all-allow', (req, res) => {
|
|
170
|
+
try {
|
|
171
|
+
res.json({
|
|
172
|
+
success: true,
|
|
173
|
+
enabled: globalAllAllowEnabled,
|
|
174
|
+
note: '此设置通过启动命令参数控制,如 claude --dangerously-skip-permissions'
|
|
175
|
+
});
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.error('[Permissions API] Get all-allow status error:', err);
|
|
178
|
+
res.status(500).json({
|
|
179
|
+
success: false,
|
|
180
|
+
message: err.message
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 设置全局 all-allow 模式(运行时)
|
|
187
|
+
* POST /api/permissions/all-allow
|
|
188
|
+
* Body: { enabled }
|
|
189
|
+
*/
|
|
190
|
+
router.post('/all-allow', (req, res) => {
|
|
191
|
+
try {
|
|
192
|
+
const { enabled } = req.body;
|
|
193
|
+
globalAllAllowEnabled = !!enabled;
|
|
194
|
+
|
|
195
|
+
res.json({
|
|
196
|
+
success: true,
|
|
197
|
+
enabled: globalAllAllowEnabled,
|
|
198
|
+
message: enabled ? 'All-Allow 模式已启用' : 'All-Allow 模式已禁用'
|
|
199
|
+
});
|
|
200
|
+
} catch (err) {
|
|
201
|
+
console.error('[Permissions API] Set all-allow status error:', err);
|
|
202
|
+
res.status(500).json({
|
|
203
|
+
success: false,
|
|
204
|
+
message: err.message
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 获取权限模版
|
|
211
|
+
* GET /api/permissions/templates
|
|
212
|
+
*/
|
|
213
|
+
router.get('/templates', (req, res) => {
|
|
214
|
+
try {
|
|
215
|
+
const templates = {
|
|
216
|
+
safe: {
|
|
217
|
+
name: '安全模式',
|
|
218
|
+
description: '仅允许只读命令,危险操作需要确认',
|
|
219
|
+
allow: [
|
|
220
|
+
'Bash(cat:*)',
|
|
221
|
+
'Bash(ls:*)',
|
|
222
|
+
'Bash(pwd)',
|
|
223
|
+
'Bash(echo:*)',
|
|
224
|
+
'Bash(head:*)',
|
|
225
|
+
'Bash(tail:*)',
|
|
226
|
+
'Bash(grep:*)',
|
|
227
|
+
'Read(*)'
|
|
228
|
+
],
|
|
229
|
+
deny: [
|
|
230
|
+
'Bash(rm:*)',
|
|
231
|
+
'Bash(sudo:*)',
|
|
232
|
+
'Bash(git push:*)',
|
|
233
|
+
'Bash(git reset --hard:*)',
|
|
234
|
+
'Bash(chmod:*)',
|
|
235
|
+
'Bash(chown:*)',
|
|
236
|
+
'Edit(*)'
|
|
237
|
+
]
|
|
238
|
+
},
|
|
239
|
+
balanced: {
|
|
240
|
+
name: '平衡模式',
|
|
241
|
+
description: '允许常用开发命令,危险操作需要确认',
|
|
242
|
+
allow: [
|
|
243
|
+
'Bash(cat:*)',
|
|
244
|
+
'Bash(ls:*)',
|
|
245
|
+
'Bash(pwd)',
|
|
246
|
+
'Bash(echo:*)',
|
|
247
|
+
'Bash(head:*)',
|
|
248
|
+
'Bash(tail:*)',
|
|
249
|
+
'Bash(grep:*)',
|
|
250
|
+
'Bash(find:*)',
|
|
251
|
+
'Bash(git status)',
|
|
252
|
+
'Bash(git diff:*)',
|
|
253
|
+
'Bash(git log:*)',
|
|
254
|
+
'Bash(npm run:*)',
|
|
255
|
+
'Bash(pnpm:*)',
|
|
256
|
+
'Bash(yarn:*)',
|
|
257
|
+
'Read(*)',
|
|
258
|
+
'Edit(*)'
|
|
259
|
+
],
|
|
260
|
+
deny: [
|
|
261
|
+
'Bash(rm -rf:*)',
|
|
262
|
+
'Bash(sudo:*)',
|
|
263
|
+
'Bash(git push --force:*)',
|
|
264
|
+
'Bash(git reset --hard:*)'
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
permissive: {
|
|
268
|
+
name: '宽松模式',
|
|
269
|
+
description: '允许大多数命令,仅阻止极度危险的操作',
|
|
270
|
+
allow: [
|
|
271
|
+
'Bash(*)',
|
|
272
|
+
'Read(*)',
|
|
273
|
+
'Edit(*)'
|
|
274
|
+
],
|
|
275
|
+
deny: [
|
|
276
|
+
'Bash(rm -rf /*)',
|
|
277
|
+
'Bash(sudo rm -rf:*)'
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
res.json({
|
|
283
|
+
success: true,
|
|
284
|
+
templates
|
|
285
|
+
});
|
|
286
|
+
} catch (err) {
|
|
287
|
+
console.error('[Permissions API] Get templates error:', err);
|
|
288
|
+
res.status(500).json({
|
|
289
|
+
success: false,
|
|
290
|
+
message: err.message
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* 获取各 CLI 工具的启动参数配置说明
|
|
297
|
+
* GET /api/permissions/cli-config
|
|
298
|
+
*/
|
|
299
|
+
router.get('/cli-config', (req, res) => {
|
|
300
|
+
try {
|
|
301
|
+
const cliConfigs = {
|
|
302
|
+
claude: {
|
|
303
|
+
name: 'Claude Code',
|
|
304
|
+
configFile: '.claude/settings.json',
|
|
305
|
+
userConfigFile: '~/.claude/settings.json',
|
|
306
|
+
permissionFormat: {
|
|
307
|
+
example: {
|
|
308
|
+
permissions: {
|
|
309
|
+
allow: ['Bash(npm run:*)', 'Read(./src/**)'],
|
|
310
|
+
deny: ['Bash(rm:*)', 'Read(.env)']
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
allAllowFlag: '--dangerously-skip-permissions',
|
|
315
|
+
docs: 'https://docs.anthropic.com/en/docs/claude-code'
|
|
316
|
+
},
|
|
317
|
+
codex: {
|
|
318
|
+
name: 'Codex CLI',
|
|
319
|
+
sandboxModes: ['read-only', 'workspace-write'],
|
|
320
|
+
approvalModes: ['suggest', 'auto-edit'],
|
|
321
|
+
allAllowFlag: '--dangerously-bypass-approvals-and-sandbox',
|
|
322
|
+
example: 'codex --approval-mode auto-edit --sandbox workspace-write "task"',
|
|
323
|
+
docs: 'https://github.com/openai/codex'
|
|
324
|
+
},
|
|
325
|
+
gemini: {
|
|
326
|
+
name: 'Gemini CLI',
|
|
327
|
+
allowedToolsFlag: '--allowedTools "ShellTool(git status)"',
|
|
328
|
+
sandboxFlag: '--sandbox / -s',
|
|
329
|
+
allAllowFlag: '--yolo',
|
|
330
|
+
docs: 'https://github.com/google-gemini/gemini-cli'
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
res.json({
|
|
335
|
+
success: true,
|
|
336
|
+
cliConfigs
|
|
337
|
+
});
|
|
338
|
+
} catch (err) {
|
|
339
|
+
console.error('[Permissions API] Get CLI config error:', err);
|
|
340
|
+
res.status(500).json({
|
|
341
|
+
success: false,
|
|
342
|
+
message: err.message
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
module.exports = router;
|
package/src/server/api/rules.js
CHANGED
|
@@ -268,4 +268,192 @@ router.delete('/:scope/*', (req, res) => {
|
|
|
268
268
|
}
|
|
269
269
|
});
|
|
270
270
|
|
|
271
|
+
// ==================== 仓库管理 API ====================
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 获取所有规则(包括远程仓库)
|
|
275
|
+
* GET /api/rules/all
|
|
276
|
+
* Query: projectPath, refresh=1 强制刷新缓存
|
|
277
|
+
*/
|
|
278
|
+
router.get('/all', async (req, res) => {
|
|
279
|
+
try {
|
|
280
|
+
const { projectPath, refresh } = req.query;
|
|
281
|
+
const forceRefresh = refresh === '1';
|
|
282
|
+
const result = await rulesService.listAllRules(projectPath || null, forceRefresh);
|
|
283
|
+
|
|
284
|
+
res.json({
|
|
285
|
+
success: true,
|
|
286
|
+
...result
|
|
287
|
+
});
|
|
288
|
+
} catch (err) {
|
|
289
|
+
console.error('[Rules API] List all rules error:', err);
|
|
290
|
+
res.status(500).json({
|
|
291
|
+
success: false,
|
|
292
|
+
message: err.message
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* 获取仓库列表
|
|
299
|
+
* GET /api/rules/repos
|
|
300
|
+
*/
|
|
301
|
+
router.get('/repos', (req, res) => {
|
|
302
|
+
try {
|
|
303
|
+
const repos = rulesService.getRepos();
|
|
304
|
+
res.json({
|
|
305
|
+
success: true,
|
|
306
|
+
repos
|
|
307
|
+
});
|
|
308
|
+
} catch (err) {
|
|
309
|
+
console.error('[Rules API] Get repos error:', err);
|
|
310
|
+
res.status(500).json({
|
|
311
|
+
success: false,
|
|
312
|
+
message: err.message
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 添加仓库
|
|
319
|
+
* POST /api/rules/repos
|
|
320
|
+
* Body: { owner, name, branch, directory, enabled }
|
|
321
|
+
*/
|
|
322
|
+
router.post('/repos', (req, res) => {
|
|
323
|
+
try {
|
|
324
|
+
const { owner, name, branch = 'main', directory = '', enabled = true } = req.body;
|
|
325
|
+
|
|
326
|
+
if (!owner || !name) {
|
|
327
|
+
return res.status(400).json({
|
|
328
|
+
success: false,
|
|
329
|
+
message: 'Missing owner or name'
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const repos = rulesService.addRepo({ owner, name, branch, directory, enabled });
|
|
334
|
+
|
|
335
|
+
res.json({
|
|
336
|
+
success: true,
|
|
337
|
+
repos
|
|
338
|
+
});
|
|
339
|
+
} catch (err) {
|
|
340
|
+
console.error('[Rules API] Add repo error:', err);
|
|
341
|
+
res.status(500).json({
|
|
342
|
+
success: false,
|
|
343
|
+
message: err.message
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 删除仓库
|
|
350
|
+
* DELETE /api/rules/repos/:owner/:name
|
|
351
|
+
* Query: directory - 可选,子目录路径
|
|
352
|
+
*/
|
|
353
|
+
router.delete('/repos/:owner/:name', (req, res) => {
|
|
354
|
+
try {
|
|
355
|
+
const { owner, name } = req.params;
|
|
356
|
+
const { directory = '' } = req.query;
|
|
357
|
+
const repos = rulesService.removeRepo(owner, name, directory);
|
|
358
|
+
|
|
359
|
+
res.json({
|
|
360
|
+
success: true,
|
|
361
|
+
repos
|
|
362
|
+
});
|
|
363
|
+
} catch (err) {
|
|
364
|
+
console.error('[Rules API] Remove repo error:', err);
|
|
365
|
+
res.status(500).json({
|
|
366
|
+
success: false,
|
|
367
|
+
message: err.message
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* 切换仓库启用状态
|
|
374
|
+
* PUT /api/rules/repos/:owner/:name/toggle
|
|
375
|
+
* Body: { enabled, directory }
|
|
376
|
+
*/
|
|
377
|
+
router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
378
|
+
try {
|
|
379
|
+
const { owner, name } = req.params;
|
|
380
|
+
const { enabled, directory = '' } = req.body;
|
|
381
|
+
|
|
382
|
+
const repos = rulesService.toggleRepo(owner, name, directory, enabled);
|
|
383
|
+
|
|
384
|
+
res.json({
|
|
385
|
+
success: true,
|
|
386
|
+
repos
|
|
387
|
+
});
|
|
388
|
+
} catch (err) {
|
|
389
|
+
console.error('[Rules API] Toggle repo error:', err);
|
|
390
|
+
res.status(500).json({
|
|
391
|
+
success: false,
|
|
392
|
+
message: err.message
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* 从远程仓库安装规则
|
|
399
|
+
* POST /api/rules/install
|
|
400
|
+
* Body: rule object from listAllRules
|
|
401
|
+
*/
|
|
402
|
+
router.post('/install', async (req, res) => {
|
|
403
|
+
try {
|
|
404
|
+
const rule = req.body;
|
|
405
|
+
|
|
406
|
+
if (!rule || !rule.repoOwner || !rule.repoName) {
|
|
407
|
+
return res.status(400).json({
|
|
408
|
+
success: false,
|
|
409
|
+
message: 'Missing rule info or repo info'
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const result = await rulesService.installFromRemote(rule);
|
|
414
|
+
|
|
415
|
+
res.json({
|
|
416
|
+
success: true,
|
|
417
|
+
...result
|
|
418
|
+
});
|
|
419
|
+
} catch (err) {
|
|
420
|
+
console.error('[Rules API] Install rule error:', err);
|
|
421
|
+
res.status(500).json({
|
|
422
|
+
success: false,
|
|
423
|
+
message: err.message
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* 卸载规则
|
|
430
|
+
* POST /api/rules/uninstall
|
|
431
|
+
* Body: { path } - 规则的相对路径
|
|
432
|
+
*/
|
|
433
|
+
router.post('/uninstall', (req, res) => {
|
|
434
|
+
try {
|
|
435
|
+
const { path } = req.body;
|
|
436
|
+
|
|
437
|
+
if (!path) {
|
|
438
|
+
return res.status(400).json({
|
|
439
|
+
success: false,
|
|
440
|
+
message: 'Missing path'
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const result = rulesService.uninstallRule(path);
|
|
445
|
+
|
|
446
|
+
res.json({
|
|
447
|
+
success: true,
|
|
448
|
+
...result
|
|
449
|
+
});
|
|
450
|
+
} catch (err) {
|
|
451
|
+
console.error('[Rules API] Uninstall rule error:', err);
|
|
452
|
+
res.status(500).json({
|
|
453
|
+
success: false,
|
|
454
|
+
message: err.message
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
|
|
271
459
|
module.exports = router;
|
package/src/server/api/skills.js
CHANGED
|
@@ -83,11 +83,13 @@ router.get('/installed', (req, res) => {
|
|
|
83
83
|
/**
|
|
84
84
|
* 安装技能
|
|
85
85
|
* POST /api/skills/install
|
|
86
|
-
* Body: { directory, repo: { owner, name, branch } }
|
|
86
|
+
* Body: { directory, fullDirectory, repo: { owner, name, branch } }
|
|
87
|
+
* - directory: 本地安装目录(相对路径)
|
|
88
|
+
* - fullDirectory: 仓库中的完整路径(当指定了仓库子目录时使用)
|
|
87
89
|
*/
|
|
88
90
|
router.post('/install', async (req, res) => {
|
|
89
91
|
try {
|
|
90
|
-
const { directory, repo } = req.body;
|
|
92
|
+
const { directory, fullDirectory, repo } = req.body;
|
|
91
93
|
|
|
92
94
|
if (!directory) {
|
|
93
95
|
return res.status(400).json({
|
|
@@ -103,11 +105,15 @@ router.post('/install', async (req, res) => {
|
|
|
103
105
|
});
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
const result = await skillService.installSkill(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
const result = await skillService.installSkill(
|
|
109
|
+
directory,
|
|
110
|
+
{
|
|
111
|
+
owner: repo.owner,
|
|
112
|
+
name: repo.name,
|
|
113
|
+
branch: repo.branch || 'main'
|
|
114
|
+
},
|
|
115
|
+
fullDirectory || null // 传递 fullDirectory 用于从仓库子目录下载
|
|
116
|
+
);
|
|
111
117
|
|
|
112
118
|
res.json({
|
|
113
119
|
success: true,
|
|
@@ -227,11 +233,12 @@ router.get('/repos', (req, res) => {
|
|
|
227
233
|
/**
|
|
228
234
|
* 添加仓库
|
|
229
235
|
* POST /api/skills/repos
|
|
230
|
-
* Body: { owner, name, branch, enabled }
|
|
236
|
+
* Body: { owner, name, branch, directory, enabled }
|
|
237
|
+
* - directory: 可选,指定扫描的子目录路径
|
|
231
238
|
*/
|
|
232
239
|
router.post('/repos', (req, res) => {
|
|
233
240
|
try {
|
|
234
|
-
const { owner, name, branch = 'main', enabled = true } = req.body;
|
|
241
|
+
const { owner, name, branch = 'main', directory = '', enabled = true } = req.body;
|
|
235
242
|
|
|
236
243
|
if (!owner || !name) {
|
|
237
244
|
return res.status(400).json({
|
|
@@ -240,7 +247,7 @@ router.post('/repos', (req, res) => {
|
|
|
240
247
|
});
|
|
241
248
|
}
|
|
242
249
|
|
|
243
|
-
const repos = skillService.addRepo({ owner, name, branch, enabled });
|
|
250
|
+
const repos = skillService.addRepo({ owner, name, branch, directory, enabled });
|
|
244
251
|
|
|
245
252
|
res.json({
|
|
246
253
|
success: true,
|
|
@@ -258,11 +265,13 @@ router.post('/repos', (req, res) => {
|
|
|
258
265
|
/**
|
|
259
266
|
* 删除仓库
|
|
260
267
|
* DELETE /api/skills/repos/:owner/:name
|
|
268
|
+
* Query: directory - 可选,子目录路径
|
|
261
269
|
*/
|
|
262
270
|
router.delete('/repos/:owner/:name', (req, res) => {
|
|
263
271
|
try {
|
|
264
272
|
const { owner, name } = req.params;
|
|
265
|
-
const
|
|
273
|
+
const { directory = '' } = req.query;
|
|
274
|
+
const repos = skillService.removeRepo(owner, name, directory);
|
|
266
275
|
|
|
267
276
|
res.json({
|
|
268
277
|
success: true,
|
|
@@ -280,14 +289,15 @@ router.delete('/repos/:owner/:name', (req, res) => {
|
|
|
280
289
|
/**
|
|
281
290
|
* 切换仓库启用状态
|
|
282
291
|
* PUT /api/skills/repos/:owner/:name/toggle
|
|
283
|
-
* Body: { enabled }
|
|
292
|
+
* Body: { enabled, directory }
|
|
293
|
+
* - directory: 可选,子目录路径
|
|
284
294
|
*/
|
|
285
295
|
router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
286
296
|
try {
|
|
287
297
|
const { owner, name } = req.params;
|
|
288
|
-
const { enabled } = req.body;
|
|
298
|
+
const { enabled, directory = '' } = req.body;
|
|
289
299
|
|
|
290
|
-
const repos = skillService.toggleRepo(owner, name, enabled);
|
|
300
|
+
const repos = skillService.toggleRepo(owner, name, directory, enabled);
|
|
291
301
|
|
|
292
302
|
res.json({
|
|
293
303
|
success: true,
|
|
@@ -302,4 +312,46 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
|
302
312
|
}
|
|
303
313
|
});
|
|
304
314
|
|
|
315
|
+
// ==================== 格式转换 API ====================
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 转换技能格式
|
|
319
|
+
* POST /api/skills/convert
|
|
320
|
+
* Body: { content, targetFormat }
|
|
321
|
+
* - content: 技能内容
|
|
322
|
+
* - targetFormat: 目标格式 ('claude' | 'codex')
|
|
323
|
+
*/
|
|
324
|
+
router.post('/convert', (req, res) => {
|
|
325
|
+
try {
|
|
326
|
+
const { content, targetFormat } = req.body;
|
|
327
|
+
|
|
328
|
+
if (!content) {
|
|
329
|
+
return res.status(400).json({
|
|
330
|
+
success: false,
|
|
331
|
+
message: '请提供技能内容'
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (!['claude', 'codex'].includes(targetFormat)) {
|
|
336
|
+
return res.status(400).json({
|
|
337
|
+
success: false,
|
|
338
|
+
message: '目标格式必须是 claude 或 codex'
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const result = skillService.convertSkillFormat(content, targetFormat);
|
|
343
|
+
|
|
344
|
+
res.json({
|
|
345
|
+
success: true,
|
|
346
|
+
...result
|
|
347
|
+
});
|
|
348
|
+
} catch (err) {
|
|
349
|
+
console.error('[Skills API] Convert skill error:', err);
|
|
350
|
+
res.status(500).json({
|
|
351
|
+
success: false,
|
|
352
|
+
message: err.message
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
305
357
|
module.exports = router;
|