coding-tool-x 3.5.7 → 3.5.9
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/dist/web/assets/{Analytics-C6DEmD3D.js → Analytics-C5W3axXs.js} +2 -2
- package/dist/web/assets/Analytics-vQS5IWvs.css +1 -0
- package/dist/web/assets/{ConfigTemplates-Cf_iTpC4.js → ConfigTemplates-DzyVFDx9.js} +1 -1
- package/dist/web/assets/{Home-BtBmYLJ1.js → Home-C9TQNB6f.js} +1 -1
- package/dist/web/assets/Home-qzk118Of.css +1 -0
- package/dist/web/assets/{PluginManager-DEk8vSw5.js → PluginManager-9B_brLWT.js} +1 -1
- package/dist/web/assets/ProjectList-Bjt6mrsV.js +1 -0
- package/dist/web/assets/ProjectList-GCC2QOmq.css +1 -0
- package/dist/web/assets/SessionList-BsHPgmUR.css +1 -0
- package/dist/web/assets/SessionList-DcBH13uA.js +1 -0
- package/dist/web/assets/{SkillManager-DcZOiiSf.js → SkillManager-vST8DRRg.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-BHqI8aGV.js → WorkspaceManager-ov1KgRXR.js} +1 -1
- package/dist/web/assets/icons-CEq2hYB-.js +1 -0
- package/dist/web/assets/index-Dih_bOsv.css +1 -0
- package/dist/web/assets/index-Duc7QP4e.js +2 -0
- package/dist/web/assets/{naive-ui-BaTCPPL5.js → naive-ui-Cg4_ZeoT.js} +1 -1
- package/dist/web/assets/{vendors-Fza9uSYn.js → vendors-Bsp-dq2d.js} +1 -1
- package/dist/web/assets/vue-vendor-BxIT0uQq.js +45 -0
- package/dist/web/index.html +7 -7
- package/package.json +1 -1
- package/src/commands/export-config.js +6 -6
- package/src/config/default.js +2 -6
- package/src/config/loader.js +2 -2
- package/src/config/paths.js +160 -33
- package/src/server/api/agents.js +52 -2
- package/src/server/api/codex-sessions.js +4 -2
- package/src/server/api/commands.js +38 -2
- package/src/server/api/opencode-sessions.js +4 -2
- package/src/server/api/plugins.js +104 -1
- package/src/server/api/sessions.js +9 -7
- package/src/server/services/agents-service.js +269 -62
- package/src/server/services/commands-service.js +281 -81
- package/src/server/services/config-export-service.js +7 -7
- package/src/server/services/config-registry-service.js +4 -5
- package/src/server/services/config-sync-manager.js +61 -41
- package/src/server/services/config-sync-service.js +3 -3
- package/src/server/services/gemini-channels.js +5 -5
- package/src/server/services/gemini-config.js +3 -4
- package/src/server/services/gemini-sessions.js +23 -20
- package/src/server/services/gemini-settings-manager.js +2 -3
- package/src/server/services/mcp-service.js +9 -14
- package/src/server/services/native-oauth-adapters.js +3 -3
- package/src/server/services/notification-hooks.js +3 -3
- package/src/server/services/opencode-sessions.js +16 -6
- package/src/server/services/opencode-settings-manager.js +3 -3
- package/src/server/services/plugins-service.js +499 -23
- package/src/server/services/prompts-service.js +5 -9
- package/src/server/services/session-launch-command.js +1 -24
- package/src/server/services/sessions.js +91 -40
- package/src/server/services/skill-service.js +155 -18
- package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +0 -1
- package/dist/web/assets/ProjectList-BMVhA_Kh.js +0 -1
- package/dist/web/assets/ProjectList-DL4JK6ci.css +0 -1
- package/dist/web/assets/SessionList-B5ioAXxg.js +0 -1
- package/dist/web/assets/SessionList-B8dXVXfi.css +0 -1
- package/dist/web/assets/icons-CQuif85v.js +0 -1
- package/dist/web/assets/index-CtByKdkA.js +0 -2
- package/dist/web/assets/index-VGAxnLqi.css +0 -1
- package/dist/web/assets/vue-vendor-aWwwFAao.js +0 -45
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const { RepoScannerBase } = require('./repo-scanner-base');
|
|
11
|
-
const { NATIVE_PATHS } = require('../../config/paths');
|
|
11
|
+
const { NATIVE_PATHS, PATHS } = require('../../config/paths');
|
|
12
12
|
const {
|
|
13
13
|
parseCommandContent,
|
|
14
14
|
parseFrontmatter
|
|
@@ -17,18 +17,21 @@ const {
|
|
|
17
17
|
// 默认仓库源
|
|
18
18
|
const DEFAULT_REPOS = [];
|
|
19
19
|
const SUPPORTED_PLATFORMS = ['claude', 'opencode'];
|
|
20
|
-
const
|
|
21
|
-
const
|
|
20
|
+
const CLAUDE_COMMANDS_DIR = NATIVE_PATHS.claude.commands;
|
|
21
|
+
const OPENCODE_COMMANDS_DIR = NATIVE_PATHS.opencode.commands;
|
|
22
|
+
const OPENCODE_LEGACY_COMMANDS_DIR = NATIVE_PATHS.opencode.commandsLegacy;
|
|
22
23
|
|
|
23
24
|
const PLATFORM_CONFIG = {
|
|
24
25
|
claude: {
|
|
25
26
|
userCommandsDir: CLAUDE_COMMANDS_DIR,
|
|
27
|
+
storageDir: PATHS.localCommands.claude,
|
|
26
28
|
projectCommandsDir: (projectPath) => path.join(projectPath, '.claude', 'commands'),
|
|
27
29
|
repoType: 'commands'
|
|
28
30
|
},
|
|
29
31
|
opencode: {
|
|
30
|
-
userCommandsDir:
|
|
31
|
-
|
|
32
|
+
userCommandsDir: OPENCODE_COMMANDS_DIR,
|
|
33
|
+
storageDir: PATHS.localCommands.opencode,
|
|
34
|
+
legacyUserCommandsDir: OPENCODE_LEGACY_COMMANDS_DIR,
|
|
32
35
|
projectCommandsDir: (projectPath) => {
|
|
33
36
|
const modern = path.join(projectPath, '.opencode', 'commands');
|
|
34
37
|
const legacy = path.join(projectPath, '.opencode', 'command');
|
|
@@ -54,6 +57,25 @@ function ensureDir(dirPath) {
|
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
|
|
60
|
+
function normalizeCommandRelativePath(relativePath, errorLabel = '命令路径') {
|
|
61
|
+
const raw = String(relativePath || '').replace(/\\/g, '/').trim();
|
|
62
|
+
if (!raw || raw.includes('\0')) {
|
|
63
|
+
throw new Error(`${errorLabel}不合法`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const normalized = path.posix.normalize(raw).replace(/^(\.\/)+/, '');
|
|
67
|
+
if (!normalized ||
|
|
68
|
+
normalized === '.' ||
|
|
69
|
+
normalized === '..' ||
|
|
70
|
+
normalized.startsWith('../') ||
|
|
71
|
+
normalized.includes('/../') ||
|
|
72
|
+
path.posix.isAbsolute(normalized)) {
|
|
73
|
+
throw new Error(`${errorLabel}不合法`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return normalized;
|
|
77
|
+
}
|
|
78
|
+
|
|
57
79
|
/**
|
|
58
80
|
* 生成 frontmatter 字符串(用于命令创建/更新)
|
|
59
81
|
*/
|
|
@@ -89,7 +111,7 @@ function generateCommandFrontmatter(data) {
|
|
|
89
111
|
/**
|
|
90
112
|
* 递归扫描目录获取命令文件
|
|
91
113
|
*/
|
|
92
|
-
function scanCommandsDir(dir, basePath, scope) {
|
|
114
|
+
function scanCommandsDir(dir, basePath, scope, options = {}) {
|
|
93
115
|
const commands = [];
|
|
94
116
|
|
|
95
117
|
if (!fs.existsSync(dir)) {
|
|
@@ -104,7 +126,7 @@ function scanCommandsDir(dir, basePath, scope) {
|
|
|
104
126
|
|
|
105
127
|
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
106
128
|
// 递归扫描子目录
|
|
107
|
-
const subCommands = scanCommandsDir(fullPath, basePath, scope);
|
|
129
|
+
const subCommands = scanCommandsDir(fullPath, basePath, scope, options);
|
|
108
130
|
commands.push(...subCommands);
|
|
109
131
|
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
110
132
|
// 解析命令文件
|
|
@@ -113,11 +135,10 @@ function scanCommandsDir(dir, basePath, scope) {
|
|
|
113
135
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
114
136
|
|
|
115
137
|
// 计算相对路径和命令名
|
|
116
|
-
const relativePath = path.relative(basePath, fullPath);
|
|
138
|
+
const relativePath = normalizeCommandRelativePath(path.relative(basePath, fullPath));
|
|
117
139
|
const commandName = entry.name.replace(/\.md$/, '');
|
|
118
140
|
const namespace = path.dirname(relativePath);
|
|
119
|
-
|
|
120
|
-
commands.push({
|
|
141
|
+
const command = {
|
|
121
142
|
name: commandName,
|
|
122
143
|
namespace: namespace === '.' ? null : namespace,
|
|
123
144
|
scope,
|
|
@@ -132,7 +153,15 @@ function scanCommandsDir(dir, basePath, scope) {
|
|
|
132
153
|
body,
|
|
133
154
|
fullContent: content,
|
|
134
155
|
updatedAt: fs.statSync(fullPath).mtime.getTime()
|
|
135
|
-
}
|
|
156
|
+
};
|
|
157
|
+
const decoratedCommand = typeof options.decorate === 'function'
|
|
158
|
+
? {
|
|
159
|
+
...command,
|
|
160
|
+
...options.decorate(command)
|
|
161
|
+
}
|
|
162
|
+
: command;
|
|
163
|
+
|
|
164
|
+
commands.push(decoratedCommand);
|
|
136
165
|
} catch (err) {
|
|
137
166
|
console.warn(`[CommandsService] Failed to parse ${fullPath}:`, err.message);
|
|
138
167
|
}
|
|
@@ -240,6 +269,7 @@ class CommandsService {
|
|
|
240
269
|
const config = PLATFORM_CONFIG[this.platform];
|
|
241
270
|
|
|
242
271
|
this.userCommandsDir = config.userCommandsDir;
|
|
272
|
+
this.storageDir = config.storageDir;
|
|
243
273
|
if (this.platform === 'opencode') {
|
|
244
274
|
const legacyUserDir = config.legacyUserCommandsDir;
|
|
245
275
|
if (legacyUserDir && fs.existsSync(legacyUserDir) && !fs.existsSync(this.userCommandsDir)) {
|
|
@@ -250,6 +280,7 @@ class CommandsService {
|
|
|
250
280
|
this.projectCommandsDir = config.projectCommandsDir;
|
|
251
281
|
this.repoScanner = new CommandsRepoScanner(this.platform, this.userCommandsDir);
|
|
252
282
|
ensureDir(this.userCommandsDir);
|
|
283
|
+
ensureDir(this.storageDir);
|
|
253
284
|
}
|
|
254
285
|
|
|
255
286
|
getProjectCommandsDir(projectPath) {
|
|
@@ -261,28 +292,165 @@ class CommandsService {
|
|
|
261
292
|
* 获取所有命令列表
|
|
262
293
|
* @param {string} projectPath - 项目路径(可选,用于获取项目级命令)
|
|
263
294
|
*/
|
|
264
|
-
|
|
295
|
+
getManagedCommandPath(relativePath) {
|
|
296
|
+
return path.join(this.storageDir, normalizeCommandRelativePath(relativePath));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
isInstalledCommand(relativePath) {
|
|
300
|
+
return fs.existsSync(path.join(this.userCommandsDir, normalizeCommandRelativePath(relativePath)));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
buildCommandContent({ description, allowedTools, argumentHint, agent, model, subtask, body }) {
|
|
304
|
+
const frontmatterData = {};
|
|
305
|
+
if (description) frontmatterData.description = description;
|
|
306
|
+
if (this.platform !== 'opencode') {
|
|
307
|
+
if (allowedTools) frontmatterData['allowed-tools'] = allowedTools;
|
|
308
|
+
if (argumentHint) frontmatterData['argument-hint'] = argumentHint;
|
|
309
|
+
}
|
|
310
|
+
if (agent) frontmatterData.agent = agent;
|
|
311
|
+
if (model) frontmatterData.model = model;
|
|
312
|
+
if (typeof subtask === 'boolean') frontmatterData.subtask = subtask;
|
|
313
|
+
|
|
314
|
+
let content = '';
|
|
315
|
+
if (Object.keys(frontmatterData).length > 0) {
|
|
316
|
+
content = generateCommandFrontmatter(frontmatterData) + '\n\n';
|
|
317
|
+
}
|
|
318
|
+
content += body || '';
|
|
319
|
+
return content;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
copyFileRecursive(sourcePath, targetPath) {
|
|
323
|
+
ensureDir(path.dirname(targetPath));
|
|
324
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
ensureManagedCommandCopy(relativePath, sourcePath, options = {}) {
|
|
328
|
+
const overwrite = options.overwrite === true;
|
|
329
|
+
const normalizedPath = normalizeCommandRelativePath(relativePath);
|
|
330
|
+
if (!sourcePath || !fs.existsSync(sourcePath)) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const managedPath = this.getManagedCommandPath(normalizedPath);
|
|
335
|
+
if (fs.existsSync(managedPath) && !overwrite) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
this.copyFileRecursive(sourcePath, managedPath);
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
removeEmptyCommandDirs(baseDir, relativePath) {
|
|
344
|
+
const normalizedPath = normalizeCommandRelativePath(relativePath);
|
|
345
|
+
let currentDir = path.dirname(path.join(baseDir, normalizedPath));
|
|
346
|
+
const resolvedBaseDir = path.resolve(baseDir);
|
|
347
|
+
|
|
348
|
+
while (currentDir && currentDir !== resolvedBaseDir && currentDir.startsWith(`${resolvedBaseDir}${path.sep}`)) {
|
|
349
|
+
try {
|
|
350
|
+
if (fs.existsSync(currentDir) && fs.readdirSync(currentDir).length === 0) {
|
|
351
|
+
fs.rmdirSync(currentDir);
|
|
352
|
+
currentDir = path.dirname(currentDir);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
} catch {
|
|
356
|
+
// ignore cleanup failures
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
mergeInstalledUserCommands(commands, options = {}) {
|
|
363
|
+
const syncManagedLocalCommands = options.syncManagedLocalCommands === true;
|
|
364
|
+
const installedCommands = scanCommandsDir(this.userCommandsDir, this.userCommandsDir, 'user', {
|
|
365
|
+
decorate: () => ({
|
|
366
|
+
installed: true,
|
|
367
|
+
isManagedLocal: false,
|
|
368
|
+
source: 'native-installed'
|
|
369
|
+
})
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
for (const installedCommand of installedCommands) {
|
|
373
|
+
const existing = commands.find(command =>
|
|
374
|
+
command.scope === 'user' &&
|
|
375
|
+
command.path.toLowerCase() === installedCommand.path.toLowerCase()
|
|
376
|
+
);
|
|
377
|
+
const managedPath = this.getManagedCommandPath(installedCommand.path);
|
|
378
|
+
|
|
379
|
+
if (existing) {
|
|
380
|
+
if (syncManagedLocalCommands && fs.existsSync(managedPath)) {
|
|
381
|
+
this.ensureManagedCommandCopy(installedCommand.path, installedCommand.fullPath, { overwrite: true });
|
|
382
|
+
}
|
|
383
|
+
existing.installed = true;
|
|
384
|
+
if (!existing.description && installedCommand.description) existing.description = installedCommand.description;
|
|
385
|
+
if (!existing.body && installedCommand.body) existing.body = installedCommand.body;
|
|
386
|
+
if (!existing.fullContent && installedCommand.fullContent) existing.fullContent = installedCommand.fullContent;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
this.ensureManagedCommandCopy(installedCommand.path, installedCommand.fullPath, {
|
|
391
|
+
overwrite: syncManagedLocalCommands
|
|
392
|
+
});
|
|
393
|
+
commands.push(installedCommand);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
mergeLocalUserCommands(commands) {
|
|
398
|
+
if (!fs.existsSync(this.storageDir)) return;
|
|
399
|
+
|
|
400
|
+
const localCommands = scanCommandsDir(this.storageDir, this.storageDir, 'user', {
|
|
401
|
+
decorate: (command) => ({
|
|
402
|
+
installed: this.isInstalledCommand(command.path),
|
|
403
|
+
isManagedLocal: true,
|
|
404
|
+
source: 'local'
|
|
405
|
+
})
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
for (const localCommand of localCommands) {
|
|
409
|
+
const existing = commands.find(command =>
|
|
410
|
+
command.scope === 'user' &&
|
|
411
|
+
command.path.toLowerCase() === localCommand.path.toLowerCase()
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
if (existing) {
|
|
415
|
+
existing.isManagedLocal = true;
|
|
416
|
+
existing.installed = this.isInstalledCommand(existing.path);
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
commands.push(localCommand);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
listCommands(projectPath = null, options = {}) {
|
|
265
425
|
const commands = [];
|
|
426
|
+
const syncManagedLocalCommands = options.syncManagedLocalCommands === true;
|
|
266
427
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
commands.push(...userCommands);
|
|
428
|
+
this.mergeInstalledUserCommands(commands, { syncManagedLocalCommands });
|
|
429
|
+
this.mergeLocalUserCommands(commands);
|
|
270
430
|
|
|
271
431
|
// 获取项目级命令(如果提供了项目路径)
|
|
272
432
|
if (projectPath) {
|
|
273
433
|
const projectCommandsDir = this.getProjectCommandsDir(projectPath);
|
|
274
|
-
const projectCommands = scanCommandsDir(projectCommandsDir, projectCommandsDir, 'project'
|
|
434
|
+
const projectCommands = scanCommandsDir(projectCommandsDir, projectCommandsDir, 'project', {
|
|
435
|
+
decorate: () => ({
|
|
436
|
+
installed: true,
|
|
437
|
+
isManagedLocal: false,
|
|
438
|
+
source: 'native-installed'
|
|
439
|
+
})
|
|
440
|
+
});
|
|
275
441
|
commands.push(...projectCommands);
|
|
276
442
|
}
|
|
277
443
|
|
|
278
444
|
// 按名称排序
|
|
279
445
|
commands.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
|
|
446
|
+
const userCount = commands.filter(command => command.scope === 'user').length;
|
|
447
|
+
const projectCount = commands.length - userCount;
|
|
280
448
|
|
|
281
449
|
return {
|
|
282
450
|
commands,
|
|
283
451
|
total: commands.length,
|
|
284
|
-
userCount
|
|
285
|
-
projectCount
|
|
452
|
+
userCount,
|
|
453
|
+
projectCount
|
|
286
454
|
};
|
|
287
455
|
}
|
|
288
456
|
|
|
@@ -292,7 +460,9 @@ class CommandsService {
|
|
|
292
460
|
*/
|
|
293
461
|
async listAllCommands(projectPath = null, forceRefresh = false) {
|
|
294
462
|
// 获取本地命令
|
|
295
|
-
const { commands: localCommands, userCount, projectCount } = this.listCommands(projectPath
|
|
463
|
+
const { commands: localCommands, userCount, projectCount } = this.listCommands(projectPath, {
|
|
464
|
+
syncManagedLocalCommands: forceRefresh
|
|
465
|
+
});
|
|
296
466
|
|
|
297
467
|
// 获取远程命令
|
|
298
468
|
let remoteCommands = [];
|
|
@@ -340,25 +510,30 @@ class CommandsService {
|
|
|
340
510
|
? this.userCommandsDir
|
|
341
511
|
: this.getProjectCommandsDir(projectPath);
|
|
342
512
|
|
|
343
|
-
const relativePath = namespace
|
|
513
|
+
const relativePath = normalizeCommandRelativePath(namespace
|
|
344
514
|
? path.join(namespace, `${name}.md`)
|
|
345
|
-
: `${name}.md
|
|
346
|
-
|
|
515
|
+
: `${name}.md`);
|
|
347
516
|
const fullPath = path.join(baseDir, relativePath);
|
|
517
|
+
const managedPath = scope === 'user' ? this.getManagedCommandPath(relativePath) : '';
|
|
518
|
+
const activePath = fs.existsSync(fullPath)
|
|
519
|
+
? fullPath
|
|
520
|
+
: (managedPath && fs.existsSync(managedPath) ? managedPath : '');
|
|
348
521
|
|
|
349
|
-
if (!
|
|
522
|
+
if (!activePath) {
|
|
350
523
|
return null;
|
|
351
524
|
}
|
|
352
525
|
|
|
353
|
-
const content = fs.readFileSync(
|
|
526
|
+
const content = fs.readFileSync(activePath, 'utf-8');
|
|
354
527
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
528
|
+
const installed = fs.existsSync(fullPath);
|
|
529
|
+
const isManagedLocal = scope === 'user' && !!managedPath && fs.existsSync(managedPath);
|
|
355
530
|
|
|
356
531
|
return {
|
|
357
532
|
name,
|
|
358
533
|
namespace,
|
|
359
534
|
scope,
|
|
360
535
|
path: relativePath,
|
|
361
|
-
fullPath,
|
|
536
|
+
fullPath: activePath,
|
|
362
537
|
description: frontmatter.description || '',
|
|
363
538
|
allowedTools: frontmatter['allowed-tools'] || '',
|
|
364
539
|
argumentHint: frontmatter['argument-hint'] || '',
|
|
@@ -367,7 +542,10 @@ class CommandsService {
|
|
|
367
542
|
subtask: frontmatter.subtask || '',
|
|
368
543
|
body,
|
|
369
544
|
fullContent: content,
|
|
370
|
-
|
|
545
|
+
installed,
|
|
546
|
+
isManagedLocal,
|
|
547
|
+
source: installed ? 'native-installed' : 'local',
|
|
548
|
+
updatedAt: fs.statSync(activePath).mtime.getTime()
|
|
371
549
|
};
|
|
372
550
|
}
|
|
373
551
|
|
|
@@ -392,30 +570,28 @@ class CommandsService {
|
|
|
392
570
|
ensureDir(targetDir);
|
|
393
571
|
|
|
394
572
|
const filePath = path.join(targetDir, `${name}.md`);
|
|
573
|
+
const relativePath = normalizeCommandRelativePath(namespace ? path.join(namespace, `${name}.md`) : `${name}.md`);
|
|
574
|
+
const managedPath = scope === 'user' ? this.getManagedCommandPath(relativePath) : '';
|
|
395
575
|
|
|
396
576
|
// 检查是否已存在
|
|
397
|
-
if (fs.existsSync(filePath)) {
|
|
577
|
+
if (fs.existsSync(filePath) || (managedPath && fs.existsSync(managedPath))) {
|
|
398
578
|
throw new Error(`命令 "${name}" 已存在`);
|
|
399
579
|
}
|
|
400
580
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (typeof subtask === 'boolean') frontmatterData.subtask = subtask;
|
|
411
|
-
|
|
412
|
-
let content = '';
|
|
413
|
-
if (Object.keys(frontmatterData).length > 0) {
|
|
414
|
-
content = generateCommandFrontmatter(frontmatterData) + '\n\n';
|
|
415
|
-
}
|
|
416
|
-
content += body || '';
|
|
581
|
+
const content = this.buildCommandContent({
|
|
582
|
+
description,
|
|
583
|
+
allowedTools,
|
|
584
|
+
argumentHint,
|
|
585
|
+
agent,
|
|
586
|
+
model,
|
|
587
|
+
subtask,
|
|
588
|
+
body
|
|
589
|
+
});
|
|
417
590
|
|
|
418
591
|
fs.writeFileSync(filePath, content, 'utf-8');
|
|
592
|
+
if (managedPath) {
|
|
593
|
+
this.copyFileRecursive(filePath, managedPath);
|
|
594
|
+
}
|
|
419
595
|
|
|
420
596
|
return this.getCommand(name, scope, projectPath, namespace);
|
|
421
597
|
}
|
|
@@ -428,34 +604,34 @@ class CommandsService {
|
|
|
428
604
|
? this.userCommandsDir
|
|
429
605
|
: this.getProjectCommandsDir(projectPath);
|
|
430
606
|
|
|
431
|
-
const relativePath = namespace
|
|
607
|
+
const relativePath = normalizeCommandRelativePath(namespace
|
|
432
608
|
? path.join(namespace, `${name}.md`)
|
|
433
|
-
: `${name}.md
|
|
434
|
-
|
|
609
|
+
: `${name}.md`);
|
|
435
610
|
const filePath = path.join(baseDir, relativePath);
|
|
611
|
+
const managedPath = scope === 'user' ? this.getManagedCommandPath(relativePath) : '';
|
|
612
|
+
const hasManagedCopy = managedPath && fs.existsSync(managedPath);
|
|
436
613
|
|
|
437
|
-
if (!fs.existsSync(filePath)) {
|
|
614
|
+
if (!fs.existsSync(filePath) && !hasManagedCopy) {
|
|
438
615
|
throw new Error(`命令 "${name}" 不存在`);
|
|
439
616
|
}
|
|
440
617
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (typeof subtask === 'boolean') frontmatterData.subtask = subtask;
|
|
618
|
+
const content = this.buildCommandContent({
|
|
619
|
+
description,
|
|
620
|
+
allowedTools,
|
|
621
|
+
argumentHint,
|
|
622
|
+
agent,
|
|
623
|
+
model,
|
|
624
|
+
subtask,
|
|
625
|
+
body
|
|
626
|
+
});
|
|
451
627
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
628
|
+
if (fs.existsSync(filePath)) {
|
|
629
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
630
|
+
}
|
|
631
|
+
if (managedPath) {
|
|
632
|
+
ensureDir(path.dirname(managedPath));
|
|
633
|
+
fs.writeFileSync(managedPath, content, 'utf-8');
|
|
455
634
|
}
|
|
456
|
-
content += body || '';
|
|
457
|
-
|
|
458
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
459
635
|
|
|
460
636
|
return this.getCommand(name, scope, projectPath, namespace);
|
|
461
637
|
}
|
|
@@ -468,29 +644,27 @@ class CommandsService {
|
|
|
468
644
|
? this.userCommandsDir
|
|
469
645
|
: this.getProjectCommandsDir(projectPath);
|
|
470
646
|
|
|
471
|
-
const relativePath = namespace
|
|
647
|
+
const relativePath = normalizeCommandRelativePath(namespace
|
|
472
648
|
? path.join(namespace, `${name}.md`)
|
|
473
|
-
: `${name}.md
|
|
474
|
-
|
|
649
|
+
: `${name}.md`);
|
|
475
650
|
const filePath = path.join(baseDir, relativePath);
|
|
651
|
+
const managedPath = scope === 'user' ? this.getManagedCommandPath(relativePath) : '';
|
|
652
|
+
let removed = false;
|
|
476
653
|
|
|
477
|
-
if (
|
|
478
|
-
|
|
654
|
+
if (fs.existsSync(filePath)) {
|
|
655
|
+
fs.unlinkSync(filePath);
|
|
656
|
+
this.removeEmptyCommandDirs(baseDir, relativePath);
|
|
657
|
+
removed = true;
|
|
479
658
|
}
|
|
480
659
|
|
|
481
|
-
fs.
|
|
660
|
+
if (managedPath && fs.existsSync(managedPath)) {
|
|
661
|
+
fs.unlinkSync(managedPath);
|
|
662
|
+
this.removeEmptyCommandDirs(this.storageDir, relativePath);
|
|
663
|
+
removed = true;
|
|
664
|
+
}
|
|
482
665
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const namespaceDir = path.join(baseDir, namespace);
|
|
486
|
-
try {
|
|
487
|
-
const remaining = fs.readdirSync(namespaceDir);
|
|
488
|
-
if (remaining.length === 0) {
|
|
489
|
-
fs.rmdirSync(namespaceDir);
|
|
490
|
-
}
|
|
491
|
-
} catch (err) {
|
|
492
|
-
// 忽略删除目录错误
|
|
493
|
-
}
|
|
666
|
+
if (!removed) {
|
|
667
|
+
return { success: false, message: '命令不存在' };
|
|
494
668
|
}
|
|
495
669
|
|
|
496
670
|
return { success: true, message: '命令已删除' };
|
|
@@ -557,11 +731,37 @@ class CommandsService {
|
|
|
557
731
|
return this.repoScanner.installCommand(command);
|
|
558
732
|
}
|
|
559
733
|
|
|
734
|
+
installLocalCommand(relativePath) {
|
|
735
|
+
const normalizedPath = normalizeCommandRelativePath(relativePath);
|
|
736
|
+
const managedPath = this.getManagedCommandPath(normalizedPath);
|
|
737
|
+
const targetPath = path.join(this.userCommandsDir, normalizedPath);
|
|
738
|
+
|
|
739
|
+
if (!fs.existsSync(managedPath)) {
|
|
740
|
+
throw new Error(`本地命令 "${normalizedPath}" 不存在`);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (fs.existsSync(targetPath)) {
|
|
744
|
+
return { success: true, message: 'Already installed' };
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
this.copyFileRecursive(managedPath, targetPath);
|
|
748
|
+
return { success: true, message: 'Installed successfully' };
|
|
749
|
+
}
|
|
750
|
+
|
|
560
751
|
/**
|
|
561
752
|
* 卸载命令
|
|
562
753
|
*/
|
|
563
754
|
uninstallCommand(relativePath) {
|
|
564
|
-
|
|
755
|
+
const normalizedPath = normalizeCommandRelativePath(relativePath);
|
|
756
|
+
const targetPath = path.join(this.userCommandsDir, normalizedPath);
|
|
757
|
+
|
|
758
|
+
if (fs.existsSync(targetPath)) {
|
|
759
|
+
fs.unlinkSync(targetPath);
|
|
760
|
+
this.removeEmptyCommandDirs(this.userCommandsDir, normalizedPath);
|
|
761
|
+
return { success: true, message: 'Uninstalled successfully' };
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return { success: true, message: 'Not installed' };
|
|
565
765
|
}
|
|
566
766
|
|
|
567
767
|
// ==================== 格式转换 ====================
|
|
@@ -14,7 +14,7 @@ const opencodeChannelsService = require('./opencode-channels');
|
|
|
14
14
|
const { AgentsService } = require('./agents-service');
|
|
15
15
|
const { CommandsService } = require('./commands-service');
|
|
16
16
|
const { SkillService } = require('./skill-service');
|
|
17
|
-
const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
17
|
+
const { PATHS, NATIVE_PATHS, getNativePathDir } = require('../../config/paths');
|
|
18
18
|
|
|
19
19
|
const CONFIG_VERSION = '1.4.0';
|
|
20
20
|
const SKILL_FILE_ENCODING = 'base64';
|
|
@@ -25,8 +25,8 @@ const LEGACY_CC_TOOL_DIR = PATHS.base;
|
|
|
25
25
|
const CLAUDE_SETTINGS_PATH = NATIVE_PATHS.claude.settings;
|
|
26
26
|
const LEGACY_PLUGINS_DIR = path.join(LEGACY_CC_TOOL_DIR, 'plugins', 'installed');
|
|
27
27
|
const LEGACY_PLUGINS_REGISTRY = path.join(LEGACY_CC_TOOL_DIR, 'plugins', 'registry.json');
|
|
28
|
-
const CLAUDE_PLUGINS_DIR =
|
|
29
|
-
const NATIVE_PLUGINS_REGISTRY =
|
|
28
|
+
const CLAUDE_PLUGINS_DIR = NATIVE_PATHS.claude.plugins;
|
|
29
|
+
const NATIVE_PLUGINS_REGISTRY = NATIVE_PATHS.claude.installedPlugins;
|
|
30
30
|
const PLUGIN_IGNORE_DIRS = new Set(['.git', 'node_modules', '.DS_Store']);
|
|
31
31
|
const PLUGIN_IGNORE_FILES = new Set(['.DS_Store']);
|
|
32
32
|
const PLUGIN_SENSITIVE_PATTERNS = [
|
|
@@ -44,7 +44,7 @@ const CC_PROMPTS_PATH = PATHS.prompts;
|
|
|
44
44
|
const CC_SECURITY_PATH = PATHS.security;
|
|
45
45
|
const LEGACY_UI_CONFIG_PATH = PATHS.uiConfig;
|
|
46
46
|
const LEGACY_NOTIFY_HOOK_PATH = PATHS.notifyHook;
|
|
47
|
-
const GEMINI_SETTINGS_PATH =
|
|
47
|
+
const GEMINI_SETTINGS_PATH = NATIVE_PATHS.gemini.settings;
|
|
48
48
|
const AGENT_PLATFORMS = ['claude', 'codex', 'opencode'];
|
|
49
49
|
const COMMAND_PLATFORMS = ['claude', 'opencode'];
|
|
50
50
|
const SKILL_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
|
|
@@ -156,7 +156,7 @@ function writeJsonFileAbsolute(filePath, data, overwrite, options = {}) {
|
|
|
156
156
|
return 'failed';
|
|
157
157
|
}
|
|
158
158
|
if (fs.existsSync(filePath) && !overwrite) return 'skipped';
|
|
159
|
-
ensureDir(
|
|
159
|
+
ensureDir(getNativePathDir(filePath));
|
|
160
160
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
|
|
161
161
|
if (options.mode) {
|
|
162
162
|
try {
|
|
@@ -173,7 +173,7 @@ function writeTextFileAbsolute(filePath, content, overwrite, options = {}) {
|
|
|
173
173
|
return 'failed';
|
|
174
174
|
}
|
|
175
175
|
if (fs.existsSync(filePath) && !overwrite) return 'skipped';
|
|
176
|
-
ensureDir(
|
|
176
|
+
ensureDir(getNativePathDir(filePath));
|
|
177
177
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
178
178
|
if (options.mode) {
|
|
179
179
|
try {
|
|
@@ -507,7 +507,7 @@ function syncImportedChannelsToNativeConfigs(importChannelsByType, nativeConfigs
|
|
|
507
507
|
]
|
|
508
508
|
);
|
|
509
509
|
if (persistedClaudeChannel?.id) {
|
|
510
|
-
ensureDir(
|
|
510
|
+
ensureDir(NATIVE_PATHS.claude.dir);
|
|
511
511
|
channelsService.applyChannelToSettings(persistedClaudeChannel.id);
|
|
512
512
|
}
|
|
513
513
|
}
|
|
@@ -17,12 +17,11 @@ const REGISTRY_FILE = PATHS.configRegistry;
|
|
|
17
17
|
const CONFIGS_DIR = PATHS.configs;
|
|
18
18
|
|
|
19
19
|
// Claude Code native directories
|
|
20
|
-
const CLAUDE_HOME_DIR = path.dirname(NATIVE_PATHS.claude.settings);
|
|
21
20
|
const CLAUDE_DIRS = {
|
|
22
|
-
skills:
|
|
23
|
-
commands:
|
|
24
|
-
agents:
|
|
25
|
-
plugins:
|
|
21
|
+
skills: NATIVE_PATHS.claude.skills,
|
|
22
|
+
commands: NATIVE_PATHS.claude.commands,
|
|
23
|
+
agents: NATIVE_PATHS.claude.agents,
|
|
24
|
+
plugins: NATIVE_PATHS.claude.plugins
|
|
26
25
|
};
|
|
27
26
|
|
|
28
27
|
// Valid config types
|