@adversity/coding-tool-x 2.4.1 → 2.5.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.
@@ -9,6 +9,7 @@ const path = require('path');
9
9
  const fs = require('fs');
10
10
 
11
11
  const { ptyManager } = require('../services/pty-manager');
12
+ const { getWebTerminalShellConfig } = require('../services/terminal-config');
12
13
  const {
13
14
  loadTerminalCommands,
14
15
  saveTerminalCommands,
@@ -171,13 +172,16 @@ router.post('/create', (req, res) => {
171
172
  // 获取启动命令
172
173
  const startCommand = getCommandForChannel(channel, sessionId, workDir);
173
174
 
175
+ const shellConfig = getWebTerminalShellConfig();
176
+
174
177
  // 创建终端
175
178
  const terminal = ptyManager.create({
176
179
  cwd: workDir,
177
180
  channel,
178
181
  sessionId,
179
182
  projectName,
180
- startCommand
183
+ startCommand,
184
+ ...shellConfig
181
185
  });
182
186
 
183
187
  res.json({
@@ -109,9 +109,9 @@ router.get('/check-git/*', (req, res) => {
109
109
  * GET /api/workspaces/available-projects
110
110
  * 获取所有渠道(Claude/Codex/Gemini)的项目并集
111
111
  */
112
- router.get('/available-projects', (req, res) => {
112
+ router.get('/available-projects', async (req, res) => {
113
113
  try {
114
- const projects = workspaceService.getAllAvailableProjects();
114
+ const projects = await workspaceService.getAllAvailableProjects();
115
115
  res.json({
116
116
  success: true,
117
117
  data: projects
@@ -109,6 +109,7 @@ async function startServer(port) {
109
109
  app.use('/api/aliases', require('./api/aliases')());
110
110
  app.use('/api/favorites', require('./api/favorites'));
111
111
  app.use('/api/ui-config', require('./api/ui-config'));
112
+ app.use('/api/security', require('./api/security'));
112
113
  app.use('/api/channels', require('./api/channels'));
113
114
  app.use('/api/proxy', require('./api/proxy'));
114
115
  app.use('/api/codex/proxy', require('./api/codex-proxy'));
@@ -264,7 +265,7 @@ function autoRestoreProxies() {
264
265
  }
265
266
 
266
267
  // 启动时执行健康检查
267
- function performStartupHealthCheck() {
268
+ async function performStartupHealthCheck() {
268
269
  const { healthCheckAllProjects } = require('./services/health-check');
269
270
  const { getProjects } = require('./services/sessions');
270
271
 
@@ -273,7 +274,7 @@ function performStartupHealthCheck() {
273
274
 
274
275
  // 获取所有项目
275
276
  const config = loadConfig();
276
- const projects = getProjects(config);
277
+ const projects = await getProjects(config);
277
278
 
278
279
  if (projects.length === 0) {
279
280
  console.log(chalk.gray(' 未发现项目,跳过健康检查'));
@@ -8,8 +8,158 @@ const path = require('path');
8
8
  const permissionTemplatesService = require('./permission-templates-service');
9
9
  const configTemplatesService = require('./config-templates-service');
10
10
  const channelsService = require('./channels');
11
+ const { AgentsService } = require('./agents-service');
12
+ const { CommandsService } = require('./commands-service');
13
+ const { RulesService } = require('./rules-service');
14
+ const { SkillService } = require('./skill-service');
11
15
 
12
16
  const CONFIG_VERSION = '1.0.0';
17
+ const SKILL_FILE_ENCODING = 'base64';
18
+ const SKILL_IGNORE_DIRS = new Set(['.git']);
19
+ const SKILL_IGNORE_FILES = new Set(['.DS_Store']);
20
+
21
+ function ensureDir(dirPath) {
22
+ if (!fs.existsSync(dirPath)) {
23
+ fs.mkdirSync(dirPath, { recursive: true });
24
+ }
25
+ }
26
+
27
+ function resolveSafePath(baseDir, relativePath) {
28
+ if (!relativePath || typeof relativePath !== 'string') return null;
29
+ const normalized = path.normalize(relativePath);
30
+ if (path.isAbsolute(normalized)) return null;
31
+ const resolved = path.resolve(baseDir, normalized);
32
+ const relative = path.relative(baseDir, resolved);
33
+ if (relative.startsWith('..') || path.isAbsolute(relative)) return null;
34
+ return resolved;
35
+ }
36
+
37
+ function buildAgentContent(agent) {
38
+ const lines = ['---'];
39
+ if (agent.name) lines.push(`name: ${agent.name}`);
40
+ if (agent.description) lines.push(`description: "${agent.description}"`);
41
+ if (agent.tools) lines.push(`tools: ${agent.tools}`);
42
+ if (agent.model) lines.push(`model: ${agent.model}`);
43
+ if (agent.permissionMode) lines.push(`permissionMode: ${agent.permissionMode}`);
44
+ if (agent.skills) lines.push(`skills: ${agent.skills}`);
45
+ lines.push('---');
46
+ const body = agent.systemPrompt || '';
47
+ return `${lines.join('\n')}\n\n${body}`;
48
+ }
49
+
50
+ function buildCommandContent(command) {
51
+ const frontmatter = {};
52
+ if (command.description) frontmatter.description = command.description;
53
+ if (command.allowedTools) frontmatter['allowed-tools'] = command.allowedTools;
54
+ if (command.argumentHint) frontmatter['argument-hint'] = command.argumentHint;
55
+
56
+ const lines = [];
57
+ if (Object.keys(frontmatter).length > 0) {
58
+ lines.push('---');
59
+ if (frontmatter.description) {
60
+ lines.push(`description: "${frontmatter.description}"`);
61
+ }
62
+ if (frontmatter['allowed-tools']) {
63
+ lines.push(`allowed-tools: ${frontmatter['allowed-tools']}`);
64
+ }
65
+ if (frontmatter['argument-hint']) {
66
+ lines.push(`argument-hint: ${frontmatter['argument-hint']}`);
67
+ }
68
+ lines.push('---');
69
+ lines.push('');
70
+ }
71
+
72
+ const body = command.body || '';
73
+ return `${lines.join('\n')}${body}`;
74
+ }
75
+
76
+ function buildRuleContent(rule) {
77
+ const lines = [];
78
+ if (rule.paths) {
79
+ lines.push('---');
80
+ lines.push(`paths: ${rule.paths}`);
81
+ lines.push('---');
82
+ lines.push('');
83
+ }
84
+ const body = rule.body || '';
85
+ return `${lines.join('\n')}${body}`;
86
+ }
87
+
88
+ function collectSkillFiles(baseDir) {
89
+ const files = [];
90
+ const stack = [baseDir];
91
+
92
+ while (stack.length > 0) {
93
+ const currentDir = stack.pop();
94
+ let entries = [];
95
+ try {
96
+ entries = fs.readdirSync(currentDir, { withFileTypes: true });
97
+ } catch (err) {
98
+ continue;
99
+ }
100
+
101
+ for (const entry of entries) {
102
+ if (entry.isDirectory()) {
103
+ if (SKILL_IGNORE_DIRS.has(entry.name)) {
104
+ continue;
105
+ }
106
+ stack.push(path.join(currentDir, entry.name));
107
+ } else if (entry.isFile()) {
108
+ if (SKILL_IGNORE_FILES.has(entry.name)) {
109
+ continue;
110
+ }
111
+ const fullPath = path.join(currentDir, entry.name);
112
+ const relativePath = path.relative(baseDir, fullPath);
113
+ try {
114
+ const content = fs.readFileSync(fullPath);
115
+ files.push({
116
+ path: relativePath,
117
+ encoding: SKILL_FILE_ENCODING,
118
+ content: content.toString(SKILL_FILE_ENCODING)
119
+ });
120
+ } catch (err) {
121
+ continue;
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ files.sort((a, b) => a.path.localeCompare(b.path));
128
+ return files;
129
+ }
130
+
131
+ function exportSkillsSnapshot() {
132
+ const skillService = new SkillService();
133
+ const installedSkills = skillService.getInstalledSkills();
134
+ const baseDir = skillService.installDir;
135
+
136
+ return installedSkills.map((skill) => {
137
+ const directory = skill.directory;
138
+ const skillDir = resolveSafePath(baseDir, directory);
139
+ if (!skillDir || !fs.existsSync(skillDir)) {
140
+ return null;
141
+ }
142
+ return {
143
+ directory,
144
+ name: skill.name || directory,
145
+ description: skill.description || '',
146
+ files: collectSkillFiles(skillDir)
147
+ };
148
+ }).filter(Boolean);
149
+ }
150
+
151
+ function writeTextFile(baseDir, relativePath, content, overwrite) {
152
+ if (!content && content !== '') {
153
+ return 'failed';
154
+ }
155
+ const targetPath = resolveSafePath(baseDir, relativePath);
156
+ if (!targetPath) return 'failed';
157
+ if (fs.existsSync(targetPath) && !overwrite) return 'skipped';
158
+
159
+ ensureDir(path.dirname(targetPath));
160
+ fs.writeFileSync(targetPath, content, 'utf8');
161
+ return 'success';
162
+ }
13
163
 
14
164
  /**
15
165
  * 导出所有配置为JSON
@@ -38,20 +188,49 @@ function exportAllConfigs() {
38
188
  const favorites = favoritesService.loadFavorites();
39
189
 
40
190
  // 获取 Agents 配置
41
- const agentsService = require('./agents-service');
42
- const agents = agentsService.getAllAgents();
191
+ const agentsService = new AgentsService();
192
+ const { agents: rawAgents } = agentsService.listAgents();
193
+ const agents = rawAgents.map(agent => ({
194
+ fileName: agent.fileName,
195
+ name: agent.name,
196
+ description: agent.description,
197
+ tools: agent.tools,
198
+ model: agent.model,
199
+ permissionMode: agent.permissionMode,
200
+ skills: agent.skills,
201
+ path: agent.path,
202
+ systemPrompt: agent.systemPrompt,
203
+ fullContent: agent.fullContent
204
+ }));
43
205
 
44
206
  // 获取 Skills 配置
45
- const skillService = require('./skill-service');
46
- const skills = skillService.getAllSkills();
207
+ const skills = exportSkillsSnapshot();
47
208
 
48
209
  // 获取 Commands 配置
49
- const commandsService = require('./commands-service');
50
- const commands = commandsService.getAllCommands();
210
+ const commandsService = new CommandsService();
211
+ const { commands: rawCommands } = commandsService.listCommands();
212
+ const commands = rawCommands.map(command => ({
213
+ name: command.name,
214
+ namespace: command.namespace,
215
+ description: command.description,
216
+ allowedTools: command.allowedTools,
217
+ argumentHint: command.argumentHint,
218
+ path: command.path,
219
+ body: command.body,
220
+ fullContent: command.fullContent
221
+ }));
51
222
 
52
223
  // 获取 Rules 配置
53
- const rulesService = require('./rules-service');
54
- const rules = rulesService.getAllRules();
224
+ const rulesService = new RulesService();
225
+ const { rules: rawRules } = rulesService.listRules();
226
+ const rules = rawRules.map(rule => ({
227
+ fileName: rule.fileName,
228
+ directory: rule.directory,
229
+ paths: rule.paths,
230
+ path: rule.path,
231
+ body: rule.body,
232
+ fullContent: rule.fullContent
233
+ }));
55
234
 
56
235
  // 获取 MCP 配置
57
236
  const mcpService = require('./mcp-service');
@@ -251,15 +430,24 @@ function importConfigs(importData, options = {}) {
251
430
  }
252
431
 
253
432
  // 导入 Agents
254
- if (agents && agents.length > 0 && overwrite) {
433
+ if (agents && agents.length > 0) {
255
434
  try {
256
- const agentsService = require('./agents-service');
435
+ const agentsService = new AgentsService();
436
+ const baseDir = agentsService.userAgentsDir;
437
+
257
438
  for (const agent of agents) {
258
- try {
259
- agentsService.saveAgent(agent);
439
+ const relativePath = agent.path || agent.fileName || agent.name;
440
+ const filePath = relativePath && relativePath.endsWith('.md')
441
+ ? relativePath
442
+ : (relativePath ? `${relativePath}.md` : null);
443
+ const content = agent.fullContent || agent.content || buildAgentContent(agent);
444
+
445
+ const status = filePath ? writeTextFile(baseDir, filePath, content, overwrite) : 'failed';
446
+ if (status === 'success') {
260
447
  results.agents.success++;
261
- } catch (err) {
262
- console.error(`[ConfigImport] 导入 Agent 失败: ${agent.name}`, err);
448
+ } else if (status === 'skipped') {
449
+ results.agents.skipped++;
450
+ } else {
263
451
  results.agents.failed++;
264
452
  }
265
453
  }
@@ -269,16 +457,55 @@ function importConfigs(importData, options = {}) {
269
457
  }
270
458
 
271
459
  // 导入 Skills
272
- if (skills && skills.length > 0 && overwrite) {
460
+ if (skills && skills.length > 0) {
273
461
  try {
274
- const skillService = require('./skill-service');
462
+ const skillService = new SkillService();
463
+ const baseDir = skillService.installDir;
464
+ ensureDir(baseDir);
465
+
275
466
  for (const skill of skills) {
276
- try {
277
- skillService.saveSkill(skill);
278
- results.skills.success++;
279
- } catch (err) {
280
- console.error(`[ConfigImport] 导入 Skill 失败: ${skill.name}`, err);
467
+ const directory = skill.directory;
468
+ const skillDir = resolveSafePath(baseDir, directory);
469
+ if (!skillDir) {
470
+ results.skills.failed++;
471
+ continue;
472
+ }
473
+
474
+ if (fs.existsSync(skillDir)) {
475
+ if (!overwrite) {
476
+ results.skills.skipped++;
477
+ continue;
478
+ }
479
+ fs.rmSync(skillDir, { recursive: true, force: true });
480
+ }
481
+
482
+ ensureDir(skillDir);
483
+ const files = Array.isArray(skill.files) ? skill.files : [];
484
+ let failed = false;
485
+
486
+ for (const file of files) {
487
+ const filePath = resolveSafePath(skillDir, file.path);
488
+ if (!filePath) {
489
+ failed = true;
490
+ break;
491
+ }
492
+ ensureDir(path.dirname(filePath));
493
+ try {
494
+ if (file.encoding === SKILL_FILE_ENCODING) {
495
+ fs.writeFileSync(filePath, Buffer.from(file.content || '', SKILL_FILE_ENCODING));
496
+ } else {
497
+ fs.writeFileSync(filePath, file.content || '', file.encoding || 'utf8');
498
+ }
499
+ } catch (err) {
500
+ failed = true;
501
+ break;
502
+ }
503
+ }
504
+
505
+ if (failed || files.length === 0) {
281
506
  results.skills.failed++;
507
+ } else {
508
+ results.skills.success++;
282
509
  }
283
510
  }
284
511
  } catch (err) {
@@ -287,15 +514,25 @@ function importConfigs(importData, options = {}) {
287
514
  }
288
515
 
289
516
  // 导入 Commands
290
- if (commands && commands.length > 0 && overwrite) {
517
+ if (commands && commands.length > 0) {
291
518
  try {
292
- const commandsService = require('./commands-service');
519
+ const commandsService = new CommandsService();
520
+ const baseDir = commandsService.userCommandsDir;
521
+
293
522
  for (const command of commands) {
294
- try {
295
- commandsService.saveCommand(command);
523
+ const relativePath = command.path || (
524
+ command.namespace
525
+ ? path.join(command.namespace, `${command.name}.md`)
526
+ : `${command.name}.md`
527
+ );
528
+ const content = command.fullContent || command.content || buildCommandContent(command);
529
+
530
+ const status = relativePath ? writeTextFile(baseDir, relativePath, content, overwrite) : 'failed';
531
+ if (status === 'success') {
296
532
  results.commands.success++;
297
- } catch (err) {
298
- console.error(`[ConfigImport] 导入 Command 失败: ${command.name}`, err);
533
+ } else if (status === 'skipped') {
534
+ results.commands.skipped++;
535
+ } else {
299
536
  results.commands.failed++;
300
537
  }
301
538
  }
@@ -305,15 +542,25 @@ function importConfigs(importData, options = {}) {
305
542
  }
306
543
 
307
544
  // 导入 Rules
308
- if (rules && rules.length > 0 && overwrite) {
545
+ if (rules && rules.length > 0) {
309
546
  try {
310
- const rulesService = require('./rules-service');
547
+ const rulesService = new RulesService();
548
+ const baseDir = rulesService.userRulesDir;
549
+
311
550
  for (const rule of rules) {
312
- try {
313
- rulesService.saveRule(rule);
551
+ const relativePath = rule.path || (
552
+ rule.directory
553
+ ? path.join(rule.directory, `${rule.fileName || rule.name}.md`)
554
+ : `${rule.fileName || rule.name}.md`
555
+ );
556
+ const content = rule.fullContent || rule.content || buildRuleContent(rule);
557
+
558
+ const status = relativePath ? writeTextFile(baseDir, relativePath, content, overwrite) : 'failed';
559
+ if (status === 'success') {
314
560
  results.rules.success++;
315
- } catch (err) {
316
- console.error(`[ConfigImport] 导入 Rule 失败: ${rule.name}`, err);
561
+ } else if (status === 'skipped') {
562
+ results.rules.skipped++;
563
+ } else {
317
564
  results.rules.failed++;
318
565
  }
319
566
  }