@adversity/coding-tool-x 3.0.6 → 3.1.1

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.
Files changed (133) hide show
  1. package/CHANGELOG.md +38 -18
  2. package/README.md +8 -8
  3. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  4. package/dist/web/assets/ConfigTemplates-ZrK_s7ma.js +1 -0
  5. package/dist/web/assets/Home-B8YfhZ3c.js +1 -0
  6. package/dist/web/assets/Home-Di2qsylF.css +1 -0
  7. package/dist/web/assets/PluginManager-BD7QUZbU.js +1 -0
  8. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  9. package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
  10. package/dist/web/assets/ProjectList-DRb1DuHV.js +1 -0
  11. package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
  12. package/dist/web/assets/SessionList-lZ0LKzfT.js +1 -0
  13. package/dist/web/assets/SkillManager-C1xG5B4Q.js +1 -0
  14. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  15. package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
  16. package/dist/web/assets/Terminal-DksBo_lM.js +1 -0
  17. package/dist/web/assets/WorkspaceManager-Burx7XOo.js +1 -0
  18. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  19. package/dist/web/assets/icons-kcfLIMBB.js +1 -0
  20. package/dist/web/assets/index-Ufv5rCa5.css +1 -0
  21. package/dist/web/assets/index-lAkrRC3h.js +2 -0
  22. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  23. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  24. package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
  25. package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
  26. package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
  27. package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
  28. package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
  29. package/dist/web/index.html +8 -6
  30. package/package.json +4 -2
  31. package/src/commands/channels.js +48 -1
  32. package/src/commands/cli-type.js +4 -2
  33. package/src/commands/daemon.js +92 -13
  34. package/src/commands/doctor.js +10 -9
  35. package/src/commands/list.js +1 -1
  36. package/src/commands/logs.js +6 -4
  37. package/src/commands/port-config.js +24 -4
  38. package/src/commands/proxy-control.js +12 -6
  39. package/src/commands/search.js +1 -1
  40. package/src/commands/security.js +3 -2
  41. package/src/commands/stats.js +226 -52
  42. package/src/commands/switch.js +1 -1
  43. package/src/commands/toggle-proxy.js +31 -6
  44. package/src/commands/ui.js +8 -1
  45. package/src/commands/update.js +97 -0
  46. package/src/commands/workspace.js +1 -1
  47. package/src/config/default.js +39 -2
  48. package/src/config/loader.js +74 -8
  49. package/src/config/paths.js +105 -33
  50. package/src/index.js +67 -4
  51. package/src/plugins/constants.js +3 -2
  52. package/src/plugins/plugin-api.js +1 -1
  53. package/src/reset-config.js +4 -2
  54. package/src/server/api/agents.js +57 -14
  55. package/src/server/api/channels.js +112 -33
  56. package/src/server/api/codex-channels.js +111 -18
  57. package/src/server/api/codex-proxy.js +14 -8
  58. package/src/server/api/commands.js +71 -18
  59. package/src/server/api/config-export.js +0 -6
  60. package/src/server/api/config-registry.js +11 -3
  61. package/src/server/api/config.js +376 -5
  62. package/src/server/api/convert.js +133 -0
  63. package/src/server/api/dashboard.js +22 -6
  64. package/src/server/api/gemini-channels.js +107 -18
  65. package/src/server/api/gemini-proxy.js +14 -8
  66. package/src/server/api/gemini-sessions.js +1 -1
  67. package/src/server/api/health-check.js +4 -3
  68. package/src/server/api/mcp.js +3 -3
  69. package/src/server/api/opencode-channels.js +419 -0
  70. package/src/server/api/opencode-projects.js +99 -0
  71. package/src/server/api/opencode-proxy.js +198 -0
  72. package/src/server/api/opencode-sessions.js +403 -0
  73. package/src/server/api/opencode-statistics.js +57 -0
  74. package/src/server/api/plugins.js +66 -19
  75. package/src/server/api/prompts.js +2 -2
  76. package/src/server/api/proxy.js +7 -4
  77. package/src/server/api/sessions.js +3 -0
  78. package/src/server/api/skills.js +69 -18
  79. package/src/server/api/workspaces.js +78 -6
  80. package/src/server/codex-proxy-server.js +32 -19
  81. package/src/server/dev-server.js +1 -1
  82. package/src/server/gemini-proxy-server.js +17 -3
  83. package/src/server/index.js +164 -48
  84. package/src/server/opencode-proxy-server.js +4375 -0
  85. package/src/server/proxy-server.js +30 -19
  86. package/src/server/services/agents-service.js +61 -24
  87. package/src/server/services/channel-scheduler.js +9 -5
  88. package/src/server/services/channels.js +70 -12
  89. package/src/server/services/codex-channels.js +61 -23
  90. package/src/server/services/codex-settings-manager.js +271 -49
  91. package/src/server/services/codex-statistics-service.js +2 -2
  92. package/src/server/services/commands-service.js +84 -25
  93. package/src/server/services/config-export-service.js +7 -45
  94. package/src/server/services/config-registry-service.js +63 -17
  95. package/src/server/services/config-sync-manager.js +160 -7
  96. package/src/server/services/config-templates-service.js +204 -51
  97. package/src/server/services/env-checker.js +26 -12
  98. package/src/server/services/env-manager.js +126 -18
  99. package/src/server/services/favorites.js +5 -3
  100. package/src/server/services/gemini-channels.js +37 -15
  101. package/src/server/services/gemini-statistics-service.js +2 -2
  102. package/src/server/services/mcp-service.js +350 -9
  103. package/src/server/services/model-detector.js +707 -221
  104. package/src/server/services/network-access.js +80 -0
  105. package/src/server/services/opencode-channels.js +206 -0
  106. package/src/server/services/opencode-gateway-converter.js +639 -0
  107. package/src/server/services/opencode-sessions.js +663 -0
  108. package/src/server/services/opencode-settings-manager.js +342 -0
  109. package/src/server/services/opencode-statistics-service.js +255 -0
  110. package/src/server/services/plugins-service.js +479 -22
  111. package/src/server/services/prompts-service.js +53 -11
  112. package/src/server/services/proxy-runtime.js +1 -1
  113. package/src/server/services/repo-scanner-base.js +1 -1
  114. package/src/server/services/security-config.js +1 -1
  115. package/src/server/services/session-cache.js +1 -1
  116. package/src/server/services/skill-service.js +300 -46
  117. package/src/server/services/speed-test.js +464 -186
  118. package/src/server/services/statistics-service.js +2 -2
  119. package/src/server/services/terminal-commands.js +10 -3
  120. package/src/server/services/terminal-config.js +1 -1
  121. package/src/server/services/ui-config.js +1 -1
  122. package/src/server/services/workspace-service.js +57 -100
  123. package/src/server/websocket-server.js +132 -3
  124. package/src/ui/menu.js +49 -40
  125. package/src/utils/port-helper.js +22 -8
  126. package/src/utils/session.js +5 -4
  127. package/dist/web/assets/icons-BxudHPiX.js +0 -1
  128. package/dist/web/assets/index-D2VfwJBa.js +0 -14
  129. package/dist/web/assets/index-oXBzu0bd.css +0 -41
  130. package/dist/web/assets/naive-ui-DT-Uur8K.js +0 -1
  131. package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
  132. package/src/server/api/permissions.js +0 -385
  133. package/src/server/services/permission-templates-service.js +0 -308
@@ -1,385 +0,0 @@
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
- const permissionTemplatesService = require('../services/permission-templates-service');
29
-
30
- const router = express.Router();
31
-
32
- // Claude Code 设置文件路径
33
- function getClaudeSettingsPath(projectPath) {
34
- if (projectPath) {
35
- return path.join(projectPath, '.claude', 'settings.json');
36
- }
37
- return path.join(os.homedir(), '.claude', 'settings.json');
38
- }
39
-
40
- // 读取 Claude Code settings.json
41
- function readClaudeSettings(projectPath) {
42
- const settingsPath = getClaudeSettingsPath(projectPath);
43
- try {
44
- if (fs.existsSync(settingsPath)) {
45
- const content = fs.readFileSync(settingsPath, 'utf-8');
46
- return JSON.parse(content);
47
- }
48
- } catch (err) {
49
- console.error('[Permissions API] Error reading Claude settings:', err);
50
- }
51
- return {};
52
- }
53
-
54
- // 保存 Claude Code settings.json
55
- function saveClaudeSettings(projectPath, settings) {
56
- const settingsPath = getClaudeSettingsPath(projectPath);
57
- const settingsDir = path.dirname(settingsPath);
58
-
59
- try {
60
- // 确保目录存在
61
- if (!fs.existsSync(settingsDir)) {
62
- fs.mkdirSync(settingsDir, { recursive: true });
63
- }
64
-
65
- // 写入文件
66
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
67
-
68
- // 验证文件已创建
69
- if (!fs.existsSync(settingsPath)) {
70
- throw new Error('文件写入后验证失败,文件未被创建');
71
- }
72
-
73
- return { success: true, path: settingsPath };
74
- } catch (err) {
75
- console.error('[Permissions API] Error saving Claude settings:', err);
76
- throw new Error(`保存配置文件失败: ${err.message}`);
77
- }
78
- }
79
-
80
- // 全局 all-allow 状态(内存中)
81
- let globalAllAllowEnabled = process.env.CLAUDE_ALL_ALLOW === 'true';
82
-
83
- /**
84
- * 获取项目的命令执行权限设置
85
- * GET /api/permissions
86
- * Query: projectPath - 项目路径, cliType - CLI 类型 (claude/codex/gemini)
87
- */
88
- router.get('/', (req, res) => {
89
- try {
90
- const { projectPath, cliType = 'claude' } = req.query;
91
-
92
- if (!projectPath) {
93
- return res.status(400).json({
94
- success: false,
95
- message: '缺少 projectPath 参数'
96
- });
97
- }
98
-
99
- if (!fs.existsSync(projectPath)) {
100
- return res.status(404).json({
101
- success: false,
102
- message: '项目路径不存在'
103
- });
104
- }
105
-
106
- // 读取 Claude Code 配置(所有 CLI 工具共用此配置管理)
107
- const settings = readClaudeSettings(projectPath);
108
- const permissions = settings.permissions || {};
109
-
110
- res.json({
111
- success: true,
112
- cliType,
113
- settings: {
114
- // Claude Code 格式
115
- allow: permissions.allow || [],
116
- deny: permissions.deny || [],
117
- // 兼容旧格式
118
- allowedCommands: permissions.allow || [],
119
- denyCommands: permissions.deny || []
120
- }
121
- });
122
- } catch (err) {
123
- console.error('[Permissions API] Get permissions error:', err);
124
- res.status(500).json({
125
- success: false,
126
- message: err.message
127
- });
128
- }
129
- });
130
-
131
- /**
132
- * 保存项目的命令执行权限设置
133
- * POST /api/permissions
134
- * Body: { projectPath, settings: { allow, deny } }
135
- */
136
- router.post('/', (req, res) => {
137
- try {
138
- const { projectPath, settings: newPermissions } = req.body;
139
-
140
- if (!projectPath) {
141
- return res.status(400).json({
142
- success: false,
143
- message: '缺少 projectPath 参数'
144
- });
145
- }
146
-
147
- if (!fs.existsSync(projectPath)) {
148
- return res.status(404).json({
149
- success: false,
150
- message: '项目路径不存在'
151
- });
152
- }
153
-
154
- // 读取现有设置
155
- const settings = readClaudeSettings(projectPath);
156
-
157
- // 更新权限设置(使用 Claude Code 的标准格式)
158
- settings.permissions = {
159
- allow: newPermissions?.allow || newPermissions?.allowedCommands || [],
160
- deny: newPermissions?.deny || newPermissions?.denyCommands || []
161
- };
162
-
163
- // 保存设置
164
- const saveResult = saveClaudeSettings(projectPath, settings);
165
-
166
- res.json({
167
- success: true,
168
- message: '权限设置已保存',
169
- savedTo: '.claude/settings.json',
170
- fullPath: saveResult.path
171
- });
172
- } catch (err) {
173
- console.error('[Permissions API] Save permissions error:', err);
174
- res.status(500).json({
175
- success: false,
176
- message: err.message
177
- });
178
- }
179
- });
180
-
181
- /**
182
- * 获取全局 all-allow 模式状态
183
- * GET /api/permissions/all-allow
184
- */
185
- router.get('/all-allow', (req, res) => {
186
- try {
187
- res.json({
188
- success: true,
189
- enabled: globalAllAllowEnabled,
190
- note: '此设置通过启动命令参数控制,如 claude --dangerously-skip-permissions'
191
- });
192
- } catch (err) {
193
- console.error('[Permissions API] Get all-allow status error:', err);
194
- res.status(500).json({
195
- success: false,
196
- message: err.message
197
- });
198
- }
199
- });
200
-
201
- /**
202
- * 设置全局 all-allow 模式(运行时)
203
- * POST /api/permissions/all-allow
204
- * Body: { enabled }
205
- */
206
- router.post('/all-allow', (req, res) => {
207
- try {
208
- const { enabled } = req.body;
209
- globalAllAllowEnabled = !!enabled;
210
-
211
- res.json({
212
- success: true,
213
- enabled: globalAllAllowEnabled,
214
- message: enabled ? 'All-Allow 模式已启用' : 'All-Allow 模式已禁用'
215
- });
216
- } catch (err) {
217
- console.error('[Permissions API] Set all-allow status error:', err);
218
- res.status(500).json({
219
- success: false,
220
- message: err.message
221
- });
222
- }
223
- });
224
-
225
- /**
226
- * 获取所有权限模版(内置 + 自定义)
227
- * GET /api/permissions/templates
228
- */
229
- router.get('/templates', (req, res) => {
230
- try {
231
- const templates = permissionTemplatesService.getAllTemplates();
232
- res.json({
233
- success: true,
234
- data: templates
235
- });
236
- } catch (err) {
237
- console.error('[Permissions API] Get templates error:', err);
238
- res.status(500).json({
239
- success: false,
240
- message: err.message
241
- });
242
- }
243
- });
244
-
245
- /**
246
- * 获取单个权限模版
247
- * GET /api/permissions/templates/:id
248
- */
249
- router.get('/templates/:id', (req, res) => {
250
- try {
251
- const template = permissionTemplatesService.getTemplateById(req.params.id);
252
- if (!template) {
253
- return res.status(404).json({
254
- success: false,
255
- message: '模版不存在'
256
- });
257
- }
258
- res.json({
259
- success: true,
260
- data: template
261
- });
262
- } catch (err) {
263
- console.error('[Permissions API] Get template error:', err);
264
- res.status(500).json({
265
- success: false,
266
- message: err.message
267
- });
268
- }
269
- });
270
-
271
- /**
272
- * 创建自定义权限模版
273
- * POST /api/permissions/templates
274
- */
275
- router.post('/templates', (req, res) => {
276
- try {
277
- const template = permissionTemplatesService.createTemplate(req.body);
278
- res.json({
279
- success: true,
280
- data: template,
281
- message: '模版创建成功'
282
- });
283
- } catch (err) {
284
- console.error('[Permissions API] Create template error:', err);
285
- res.status(400).json({
286
- success: false,
287
- message: err.message
288
- });
289
- }
290
- });
291
-
292
- /**
293
- * 更新自定义权限模版
294
- * PUT /api/permissions/templates/:id
295
- */
296
- router.put('/templates/:id', (req, res) => {
297
- try {
298
- const template = permissionTemplatesService.updateTemplate(req.params.id, req.body);
299
- res.json({
300
- success: true,
301
- data: template,
302
- message: '模版更新成功'
303
- });
304
- } catch (err) {
305
- console.error('[Permissions API] Update template error:', err);
306
- res.status(400).json({
307
- success: false,
308
- message: err.message
309
- });
310
- }
311
- });
312
-
313
- /**
314
- * 删除自定义权限模版
315
- * DELETE /api/permissions/templates/:id
316
- */
317
- router.delete('/templates/:id', (req, res) => {
318
- try {
319
- permissionTemplatesService.deleteTemplate(req.params.id);
320
- res.json({
321
- success: true,
322
- message: '模版删除成功'
323
- });
324
- } catch (err) {
325
- console.error('[Permissions API] Delete template error:', err);
326
- res.status(400).json({
327
- success: false,
328
- message: err.message
329
- });
330
- }
331
- });
332
-
333
- /**
334
- * 获取各 CLI 工具的启动参数配置说明
335
- * GET /api/permissions/cli-config
336
- */
337
- router.get('/cli-config', (req, res) => {
338
- try {
339
- const cliConfigs = {
340
- claude: {
341
- name: 'Claude Code',
342
- configFile: '.claude/settings.json',
343
- userConfigFile: '~/.claude/settings.json',
344
- permissionFormat: {
345
- example: {
346
- permissions: {
347
- allow: ['Bash(npm run:*)', 'Read(./src/**)'],
348
- deny: ['Bash(rm:*)', 'Read(.env)']
349
- }
350
- }
351
- },
352
- allAllowFlag: '--dangerously-skip-permissions',
353
- docs: 'https://docs.anthropic.com/en/docs/claude-code'
354
- },
355
- codex: {
356
- name: 'Codex CLI',
357
- sandboxModes: ['read-only', 'workspace-write'],
358
- approvalModes: ['suggest', 'auto-edit'],
359
- allAllowFlag: '--dangerously-bypass-approvals-and-sandbox',
360
- example: 'codex --approval-mode auto-edit --sandbox workspace-write "task"',
361
- docs: 'https://github.com/openai/codex'
362
- },
363
- gemini: {
364
- name: 'Gemini CLI',
365
- allowedToolsFlag: '--allowedTools "ShellTool(git status)"',
366
- sandboxFlag: '--sandbox / -s',
367
- allAllowFlag: '--yolo',
368
- docs: 'https://github.com/google-gemini/gemini-cli'
369
- }
370
- };
371
-
372
- res.json({
373
- success: true,
374
- cliConfigs
375
- });
376
- } catch (err) {
377
- console.error('[Permissions API] Get CLI config error:', err);
378
- res.status(500).json({
379
- success: false,
380
- message: err.message
381
- });
382
- }
383
- });
384
-
385
- module.exports = router;
@@ -1,308 +0,0 @@
1
- /**
2
- * Permission Templates Service
3
- *
4
- * 管理权限配置模版的 CRUD 操作
5
- * 支持内置模版和自定义模版
6
- */
7
-
8
- const fs = require('fs');
9
- const path = require('path');
10
- const { PATHS } = require('../../config/paths');
11
-
12
- // 权限模版存储文件
13
- const TEMPLATES_FILE = path.join(PATHS.config, 'permission-templates.json');
14
-
15
- // 内置权限模版
16
- const BUILTIN_TEMPLATES = [
17
- {
18
- id: 'safe',
19
- name: '安全模式',
20
- description: '仅允许只读命令,危险操作需要确认',
21
- permissions: {
22
- allow: [
23
- 'Bash(cat:*)',
24
- 'Bash(ls:*)',
25
- 'Bash(pwd)',
26
- 'Bash(echo:*)',
27
- 'Bash(head:*)',
28
- 'Bash(tail:*)',
29
- 'Bash(grep:*)',
30
- 'Read(*)'
31
- ],
32
- deny: [
33
- 'Bash(rm:*)',
34
- 'Bash(sudo:*)',
35
- 'Bash(git push:*)',
36
- 'Bash(git reset --hard:*)',
37
- 'Bash(chmod:*)',
38
- 'Bash(chown:*)',
39
- 'Edit(*)'
40
- ]
41
- },
42
- isBuiltin: true
43
- },
44
- {
45
- id: 'balanced',
46
- name: '平衡模式',
47
- description: '允许常用开发命令,危险操作需要确认',
48
- permissions: {
49
- allow: [
50
- 'Bash(cat:*)',
51
- 'Bash(ls:*)',
52
- 'Bash(pwd)',
53
- 'Bash(echo:*)',
54
- 'Bash(head:*)',
55
- 'Bash(tail:*)',
56
- 'Bash(grep:*)',
57
- 'Bash(find:*)',
58
- 'Bash(git status)',
59
- 'Bash(git diff:*)',
60
- 'Bash(git log:*)',
61
- 'Bash(npm run:*)',
62
- 'Bash(pnpm:*)',
63
- 'Bash(yarn:*)',
64
- 'Read(*)',
65
- 'Edit(*)'
66
- ],
67
- deny: [
68
- 'Bash(rm -rf:*)',
69
- 'Bash(sudo:*)',
70
- 'Bash(git push --force:*)',
71
- 'Bash(git reset --hard:*)'
72
- ]
73
- },
74
- isBuiltin: true
75
- },
76
- {
77
- id: 'permissive',
78
- name: '宽松模式',
79
- description: '允许大多数命令,仅阻止极度危险的操作',
80
- permissions: {
81
- allow: [
82
- 'Bash(*)',
83
- 'Read(*)',
84
- 'Edit(*)'
85
- ],
86
- deny: [
87
- 'Bash(rm -rf /*)',
88
- 'Bash(sudo rm -rf:*)'
89
- ]
90
- },
91
- isBuiltin: true
92
- }
93
- ];
94
-
95
- /**
96
- * 确保配置目录存在
97
- */
98
- function ensureDir(dirPath) {
99
- if (!fs.existsSync(dirPath)) {
100
- fs.mkdirSync(dirPath, { recursive: true });
101
- }
102
- }
103
-
104
- /**
105
- * 加载所有模版(内置 + 自定义)
106
- */
107
- /**
108
- * 加载所有模版(内置 + 自定义)
109
- * 内置模版可被用户覆盖修改
110
- */
111
- /**
112
- * 加载所有模版(内置 + 自定义)
113
- * 内置模版可被用户覆盖修改或隐藏
114
- */
115
- function loadTemplates() {
116
- try {
117
- if (fs.existsSync(TEMPLATES_FILE)) {
118
- const content = fs.readFileSync(TEMPLATES_FILE, 'utf8');
119
- const data = JSON.parse(content);
120
-
121
- // 分离自定义模版和隐藏标记
122
- const customTemplates = (data.custom || []).filter(t => !t._hidden);
123
- const hiddenIds = (data.custom || []).filter(t => t._hidden).map(t => t.id);
124
-
125
- const customIds = customTemplates.map(t => t.id);
126
-
127
- // 过滤掉已被自定义覆盖或隐藏的内置模版
128
- const effectiveBuiltin = BUILTIN_TEMPLATES.filter(t =>
129
- !customIds.includes(t.id) && !hiddenIds.includes(t.id)
130
- );
131
-
132
- return {
133
- builtin: effectiveBuiltin,
134
- custom: customTemplates,
135
- all: [...customTemplates, ...effectiveBuiltin] // 自定义优先
136
- };
137
- }
138
- } catch (error) {
139
- console.error('[PermissionTemplates] 加载模版失败:', error.message);
140
- }
141
-
142
- return {
143
- builtin: BUILTIN_TEMPLATES,
144
- custom: [],
145
- all: BUILTIN_TEMPLATES
146
- };
147
- }
148
-
149
- /**
150
- * 保存自定义模版
151
- */
152
- function saveCustomTemplates(customTemplates) {
153
- ensureDir(PATHS.config);
154
- fs.writeFileSync(TEMPLATES_FILE, JSON.stringify({ custom: customTemplates }, null, 2), 'utf8');
155
- }
156
-
157
- /**
158
- * 获取所有模版
159
- */
160
- /**
161
- * 获取所有模版(合并后的最终列表)
162
- */
163
- function getAllTemplates() {
164
- const { all } = loadTemplates();
165
- return all;
166
- }
167
-
168
- /**
169
- * 根据 ID 获取模版
170
- */
171
- function getTemplateById(id) {
172
- const templates = getAllTemplates();
173
- return templates.find(t => t.id === id) || null;
174
- }
175
-
176
- /**
177
- * 创建自定义模版
178
- */
179
- function createTemplate(template) {
180
- const { custom } = loadTemplates();
181
-
182
- // 验证必填字段
183
- if (!template.name || !template.name.trim()) {
184
- throw new Error('模版名称不能为空');
185
- }
186
-
187
- // 生成唯一 ID
188
- const id = `custom-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
189
-
190
- const newTemplate = {
191
- id,
192
- name: template.name.trim(),
193
- description: template.description || '',
194
- permissions: {
195
- allow: template.permissions?.allow || [],
196
- deny: template.permissions?.deny || []
197
- },
198
- isBuiltin: false,
199
- createdAt: new Date().toISOString()
200
- };
201
-
202
- custom.push(newTemplate);
203
- saveCustomTemplates(custom);
204
-
205
- return newTemplate;
206
- }
207
-
208
- /**
209
- * 更新自定义模版
210
- */
211
- /**
212
- * 更新权限模版(包括内置模版)
213
- * 编辑内置模版时,会在自定义列表中创建覆盖版本
214
- */
215
- function updateTemplate(id, updates) {
216
- const { custom, all } = loadTemplates();
217
-
218
- // 查找模版(先查自定义,再查所有)
219
- const existingTemplate = all.find(t => t.id === id);
220
- if (!existingTemplate) {
221
- throw new Error('模版不存在');
222
- }
223
-
224
- // 验证名称
225
- if (updates.name !== undefined && !updates.name.trim()) {
226
- throw new Error('模版名称不能为空');
227
- }
228
-
229
- const updated = {
230
- ...existingTemplate,
231
- name: updates.name?.trim() ?? existingTemplate.name,
232
- description: updates.description ?? existingTemplate.description,
233
- permissions: updates.permissions ?? existingTemplate.permissions,
234
- isBuiltin: false, // 编辑后标记为自定义(覆盖内置)
235
- updatedAt: new Date().toISOString()
236
- };
237
-
238
- // 如果是编辑内置模版,则添加到自定义列表中(覆盖)
239
- const customIndex = custom.findIndex(t => t.id === id);
240
- if (customIndex !== -1) {
241
- custom[customIndex] = updated;
242
- } else {
243
- custom.push(updated);
244
- }
245
-
246
- saveCustomTemplates(custom);
247
- return updated;
248
- }
249
-
250
- /**
251
- * 删除自定义模版
252
- */
253
- /**
254
- * 删除权限模版(包括内置模版)
255
- * 删除内置模版时,会在自定义列表中添加隐藏标记
256
- */
257
- /**
258
- * 删除权限模版(包括内置模版)
259
- * 删除内置模版时,会在自定义列表中添加隐藏标记
260
- */
261
- function deleteTemplate(id) {
262
- try {
263
- if (!fs.existsSync(TEMPLATES_FILE)) {
264
- ensureDir(PATHS.config);
265
- fs.writeFileSync(TEMPLATES_FILE, JSON.stringify({ custom: [] }, null, 2), 'utf8');
266
- }
267
-
268
- const content = fs.readFileSync(TEMPLATES_FILE, 'utf8');
269
- const data = JSON.parse(content);
270
- const customList = data.custom || [];
271
-
272
- // 检查是否为内置模版
273
- const isBuiltin = BUILTIN_TEMPLATES.some(t => t.id === id);
274
-
275
- if (isBuiltin) {
276
- // 内置模版:添加隐藏标记
277
- const hideMarker = {
278
- id,
279
- _hidden: true,
280
- deletedAt: new Date().toISOString()
281
- };
282
- customList.push(hideMarker);
283
- fs.writeFileSync(TEMPLATES_FILE, JSON.stringify({ custom: customList }, null, 2), 'utf8');
284
- } else {
285
- // 自定义模版:直接删除
286
- const index = customList.findIndex(t => t.id === id && !t._hidden);
287
- if (index === -1) {
288
- throw new Error('模版不存在');
289
- }
290
- customList.splice(index, 1);
291
- fs.writeFileSync(TEMPLATES_FILE, JSON.stringify({ custom: customList }, null, 2), 'utf8');
292
- }
293
-
294
- return true;
295
- } catch (error) {
296
- if (error.message === '模版不存在') throw error;
297
- throw new Error('删除模版失败: ' + error.message);
298
- }
299
- }
300
-
301
- module.exports = {
302
- getAllTemplates,
303
- getTemplateById,
304
- createTemplate,
305
- updateTemplate,
306
- deleteTemplate,
307
- BUILTIN_TEMPLATES
308
- };