- 在InitCommand中添加decodeURIComponent()处理中文路径 - 解决MCP传入URL编码路径导致验证失败的问题 - 支持类似 /home/azu/%E6%A1%8C%E9%9D%A2/PromptX 的编码路径 Co-authored-by: Cen-Yaozu <azu@example.com>
260 lines
9.0 KiB
JavaScript
260 lines
9.0 KiB
JavaScript
const BasePouchCommand = require('../BasePouchCommand')
|
||
const { getGlobalResourceManager } = require('../../resource')
|
||
const { COMMANDS } = require('../../../../constants')
|
||
const { getDirectoryService } = require('../../../utils/DirectoryService')
|
||
const RegistryData = require('../../resource/RegistryData')
|
||
const ProjectDiscovery = require('../../resource/discovery/ProjectDiscovery')
|
||
const CurrentProjectManager = require('../../../utils/CurrentProjectManager')
|
||
const logger = require('../../../utils/logger')
|
||
const path = require('path')
|
||
const fs = require('fs-extra')
|
||
|
||
/**
|
||
* 初始化锦囊命令
|
||
* 负责准备工作环境和传达系统协议
|
||
*/
|
||
class InitCommand extends BasePouchCommand {
|
||
constructor () {
|
||
super()
|
||
// 使用全局单例 ResourceManager
|
||
this.resourceManager = getGlobalResourceManager()
|
||
this.projectDiscovery = new ProjectDiscovery()
|
||
this.directoryService = getDirectoryService()
|
||
this.currentProjectManager = new CurrentProjectManager()
|
||
}
|
||
|
||
getPurpose () {
|
||
return '初始化PromptX工作环境,创建必要的配置目录和文件,生成项目级资源注册表'
|
||
}
|
||
|
||
async getContent (args) {
|
||
// 获取工作目录参数,支持两种格式:
|
||
// 1. 来自MCP的对象格式:{ workingDirectory: "path" }
|
||
// 2. 来自CLI的字符串格式:["path"]
|
||
let workingDirectory
|
||
|
||
if (args && typeof args[0] === 'object' && args[0].workingDirectory) {
|
||
// MCP格式
|
||
workingDirectory = args[0].workingDirectory
|
||
} else if (args && typeof args[0] === 'string') {
|
||
// CLI格式
|
||
workingDirectory = args[0]
|
||
}
|
||
// 注意:如果args[0]是空对象{},workingDirectory保持undefined,走后续的自动检测逻辑
|
||
|
||
let projectPath
|
||
|
||
if (workingDirectory) {
|
||
// AI提供了工作目录,先解码中文路径,然后使用
|
||
const decodedWorkingDirectory = decodeURIComponent(workingDirectory)
|
||
projectPath = path.resolve(decodedWorkingDirectory)
|
||
|
||
// 验证AI提供的路径是否有效
|
||
if (!await this.currentProjectManager.validateProjectPath(projectPath)) {
|
||
return `❌ 提供的工作目录无效: ${projectPath}
|
||
|
||
请确保:
|
||
1. 路径存在且为目录
|
||
2. 不是用户主目录
|
||
3. 具有适当的访问权限
|
||
|
||
💡 请提供一个有效的项目目录路径。`
|
||
}
|
||
|
||
// 保存AI提供的项目路径
|
||
await this.currentProjectManager.setCurrentProject(projectPath)
|
||
|
||
} else {
|
||
// AI没有提供工作目录,检查是否已有保存的项目
|
||
const savedProject = await this.currentProjectManager.getCurrentProject()
|
||
|
||
if (savedProject) {
|
||
// 使用之前保存的项目路径
|
||
projectPath = savedProject
|
||
} else {
|
||
// 没有保存的项目,要求AI提供
|
||
return `🎯 PromptX需要知道当前项目的工作目录。
|
||
|
||
请在调用此工具时提供 workingDirectory 参数,例如:
|
||
- workingDirectory: "/Users/sean/WorkSpaces/DeepracticeProjects/PromptX"
|
||
|
||
💡 你当前工作在哪个项目目录?请提供完整的绝对路径。`
|
||
}
|
||
}
|
||
|
||
// 构建统一的查找上下文,使用确定的项目路径
|
||
const context = {
|
||
startDir: projectPath,
|
||
platform: process.platform,
|
||
avoidUserHome: true,
|
||
// init命令特有:AI提供的路径优先级最高,然后是当前目录
|
||
strategies: [
|
||
'aiProvidedProjectPath', // 最高优先级:AI提供的项目路径
|
||
'currentWorkingDirectoryIfHasMarkers',
|
||
'currentWorkingDirectory'
|
||
]
|
||
}
|
||
|
||
// 1. 获取版本信息
|
||
const version = await this.getVersionInfo()
|
||
|
||
// 2. 基础环境准备 - 创建 .promptx 目录
|
||
await this.ensurePromptXDirectory(context)
|
||
|
||
// 3. 生成项目级资源注册表
|
||
const registryStats = await this.generateProjectRegistry(context)
|
||
|
||
// 4. 刷新全局 ResourceManager(确保新资源立即可用)
|
||
await this.refreshGlobalResourceManager()
|
||
|
||
return `🎯 PromptX 初始化完成!
|
||
|
||
## 📦 版本信息
|
||
✅ **PromptX v${version}** - AI专业能力增强框架
|
||
|
||
## 🏗️ 环境准备
|
||
✅ 创建了 \`.promptx\` 配置目录
|
||
✅ 工作环境就绪
|
||
|
||
## 📋 项目资源注册表
|
||
${registryStats.message}
|
||
|
||
## 🚀 下一步建议
|
||
- 使用 \`welcome\` 发现可用的专业角色
|
||
- 使用 \`action\` 激活特定角色获得专业能力
|
||
- 使用 \`learn\` 深入学习专业知识
|
||
- 使用 \`remember/recall\` 管理专业记忆
|
||
|
||
💡 **提示**: ${registryStats.totalResources > 0 ? '项目资源已优化为注册表模式,性能大幅提升!' : '现在可以开始创建项目级资源了!'}`
|
||
}
|
||
|
||
/**
|
||
* 生成项目级资源注册表
|
||
* @param {Object} context - 查找上下文
|
||
* @returns {Promise<Object>} 注册表生成统计信息
|
||
*/
|
||
async generateProjectRegistry(context) {
|
||
try {
|
||
// 1. 使用统一的目录服务获取项目根目录
|
||
const projectRoot = await this.directoryService.getProjectRoot(context)
|
||
const resourceDir = await this.directoryService.getResourceDirectory(context)
|
||
|
||
// 2. 确保资源目录存在(具体子目录由ResourceManager扫描时按需创建)
|
||
await fs.ensureDir(resourceDir)
|
||
logger.debug(`[InitCommand] 确保资源目录存在: ${resourceDir}`)
|
||
|
||
// 3. 使用 ProjectDiscovery 的正确方法生成注册表
|
||
logger.step('正在扫描项目资源...')
|
||
const registryData = await this.projectDiscovery.generateRegistry(projectRoot)
|
||
|
||
// 4. 生成统计信息
|
||
const stats = registryData.getStats()
|
||
const registryPath = await this.directoryService.getRegistryPath(context)
|
||
|
||
if (registryData.size === 0) {
|
||
return {
|
||
message: `✅ 项目资源目录已创建,注册表已初始化
|
||
📂 目录: ${path.relative(process.cwd(), resourceDir)}
|
||
💾 注册表: ${path.relative(process.cwd(), registryPath)}
|
||
💡 现在可以在 domain 目录下创建角色资源了`,
|
||
totalResources: 0
|
||
}
|
||
}
|
||
|
||
return {
|
||
message: `✅ 项目资源注册表已重新生成
|
||
📊 总计: ${registryData.size} 个资源
|
||
📋 分类: role(${stats.byProtocol.role || 0}), thought(${stats.byProtocol.thought || 0}), execution(${stats.byProtocol.execution || 0}), knowledge(${stats.byProtocol.knowledge || 0})
|
||
💾 位置: ${path.relative(process.cwd(), registryPath)}`,
|
||
totalResources: registryData.size
|
||
}
|
||
|
||
} catch (error) {
|
||
logger.error('生成项目注册表时出错:', error)
|
||
return {
|
||
message: `❌ 生成项目注册表失败: ${error.message}`,
|
||
totalResources: 0
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 确保 .promptx 基础目录存在
|
||
* 使用统一的目录服务创建基础环境
|
||
*/
|
||
async ensurePromptXDirectory (context) {
|
||
const promptxDir = await this.directoryService.getPromptXDirectory(context)
|
||
await fs.ensureDir(promptxDir)
|
||
logger.debug(`[InitCommand] 确保.promptx目录存在: ${promptxDir}`)
|
||
}
|
||
|
||
/**
|
||
* 刷新全局 ResourceManager
|
||
* 确保新创建的资源立即可用,无需重启 MCP Server
|
||
*/
|
||
async refreshGlobalResourceManager() {
|
||
try {
|
||
logger.debug('[InitCommand] 刷新全局 ResourceManager...')
|
||
|
||
// 重新初始化 ResourceManager,清除缓存并重新发现资源
|
||
await this.resourceManager.initializeWithNewArchitecture()
|
||
|
||
logger.debug('[InitCommand] 全局 ResourceManager 刷新完成')
|
||
} catch (error) {
|
||
logger.warn(`[InitCommand] 刷新 ResourceManager 失败: ${error.message}`)
|
||
// 不抛出错误,避免影响 init 命令的主要功能
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取版本信息
|
||
*/
|
||
async getVersionInfo () {
|
||
try {
|
||
const packageJsonPath = path.resolve(__dirname, '../../../../../package.json')
|
||
if (await fs.pathExists(packageJsonPath)) {
|
||
const packageJson = await fs.readJSON(packageJsonPath)
|
||
const baseVersion = packageJson.version || '未知版本'
|
||
const nodeVersion = process.version
|
||
const packageName = packageJson.name || 'dpml-prompt'
|
||
|
||
return `${baseVersion} (${packageName}@${baseVersion}, Node.js ${nodeVersion})`
|
||
}
|
||
} catch (error) {
|
||
logger.warn('无法读取版本信息:', error.message)
|
||
}
|
||
return '未知版本'
|
||
}
|
||
|
||
async getPATEOAS (args) {
|
||
const version = await this.getVersionInfo()
|
||
return {
|
||
currentState: 'initialized',
|
||
availableTransitions: ['welcome', 'action', 'learn', 'recall', 'remember'],
|
||
nextActions: [
|
||
{
|
||
name: '发现专业角色',
|
||
description: '查看所有可用的AI专业角色',
|
||
method: 'MCP PromptX welcome 工具',
|
||
priority: 'recommended'
|
||
},
|
||
{
|
||
name: '激活专业角色',
|
||
description: '直接激活特定专业角色(如果已知角色ID)',
|
||
method: 'MCP PromptX action 工具',
|
||
priority: 'optional'
|
||
}
|
||
],
|
||
metadata: {
|
||
timestamp: new Date().toISOString(),
|
||
version: version,
|
||
description: 'PromptX专业能力增强系统已就绪'
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
module.exports = InitCommand
|