feat: 更新命令名称为dpml-prompt,保持PromptX品牌名称
This commit is contained in:
@ -1,28 +1,28 @@
|
||||
const ResourceProtocol = require('./ResourceProtocol');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const ResourceProtocol = require('./ResourceProtocol')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
|
||||
/**
|
||||
* 执行模式协议处理器
|
||||
* 处理 execution:// 协议的资源解析
|
||||
*/
|
||||
class ExecutionProtocol extends ResourceProtocol {
|
||||
constructor() {
|
||||
super('execution');
|
||||
this.registry = {};
|
||||
constructor () {
|
||||
super('execution')
|
||||
this.registry = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表
|
||||
*/
|
||||
setRegistry(registry) {
|
||||
this.registry = registry || {};
|
||||
setRegistry (registry) {
|
||||
this.registry = registry || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议信息
|
||||
*/
|
||||
getProtocolInfo() {
|
||||
getProtocolInfo () {
|
||||
return {
|
||||
name: 'execution',
|
||||
description: '执行模式资源协议',
|
||||
@ -32,47 +32,47 @@ class ExecutionProtocol extends ResourceProtocol {
|
||||
'execution://prompt-developer',
|
||||
'execution://memory-trigger'
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析资源路径
|
||||
*/
|
||||
async resolvePath(resourcePath, queryParams) {
|
||||
const executionId = resourcePath.trim();
|
||||
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
const executionId = resourcePath.trim()
|
||||
|
||||
if (!this.registry[executionId]) {
|
||||
throw new Error(`执行模式 "${executionId}" 未在注册表中找到`);
|
||||
throw new Error(`执行模式 "${executionId}" 未在注册表中找到`)
|
||||
}
|
||||
|
||||
let resolvedPath = this.registry[executionId];
|
||||
|
||||
let resolvedPath = this.registry[executionId]
|
||||
|
||||
// 处理 @package:// 前缀
|
||||
if (resolvedPath.startsWith('@package://')) {
|
||||
resolvedPath = resolvedPath.replace('@package://', '');
|
||||
resolvedPath = resolvedPath.replace('@package://', '')
|
||||
}
|
||||
|
||||
return resolvedPath;
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载资源内容
|
||||
*/
|
||||
async loadContent(resolvedPath, queryParams) {
|
||||
async loadContent (resolvedPath, queryParams) {
|
||||
try {
|
||||
const content = await fs.readFile(resolvedPath, 'utf-8');
|
||||
return content;
|
||||
const content = await fs.readFile(resolvedPath, 'utf-8')
|
||||
return content
|
||||
} catch (error) {
|
||||
throw new Error(`无法加载执行模式文件 ${resolvedPath}: ${error.message}`);
|
||||
throw new Error(`无法加载执行模式文件 ${resolvedPath}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证资源路径
|
||||
*/
|
||||
validatePath(resourcePath) {
|
||||
return /^[a-zA-Z0-9_-]+$/.test(resourcePath);
|
||||
validatePath (resourcePath) {
|
||||
return /^[a-zA-Z0-9_-]+$/.test(resourcePath)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ExecutionProtocol;
|
||||
module.exports = ExecutionProtocol
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const fsPromises = require('fs').promises;
|
||||
const ResourceProtocol = require('./ResourceProtocol');
|
||||
const { QueryParams } = require('../types');
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const fsPromises = require('fs').promises
|
||||
const ResourceProtocol = require('./ResourceProtocol')
|
||||
const { QueryParams } = require('../types')
|
||||
|
||||
/**
|
||||
* 包协议实现
|
||||
@ -10,25 +10,25 @@ const { QueryParams } = require('../types');
|
||||
* 支持:本地开发、npm install、npm -g、npx、monorepo等场景
|
||||
*/
|
||||
class PackageProtocol extends ResourceProtocol {
|
||||
constructor(options = {}) {
|
||||
super('package', options);
|
||||
|
||||
constructor (options = {}) {
|
||||
super('package', options)
|
||||
|
||||
// 包安装模式检测缓存
|
||||
this.installModeCache = new Map();
|
||||
this.installModeCache = new Map()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表(保持与其他协议的一致性)
|
||||
*/
|
||||
setRegistry(registry) {
|
||||
setRegistry (registry) {
|
||||
// Package协议不使用注册表,但为了一致性提供此方法
|
||||
this.registry = registry || {};
|
||||
this.registry = registry || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议信息
|
||||
*/
|
||||
getProtocolInfo() {
|
||||
getProtocolInfo () {
|
||||
return {
|
||||
name: this.name,
|
||||
description: '包协议 - 智能访问NPM包资源,支持多种安装模式',
|
||||
@ -40,96 +40,96 @@ class PackageProtocol extends ResourceProtocol {
|
||||
'@package://templates/basic/template.md'
|
||||
],
|
||||
installModes: [
|
||||
'development', // 开发模式
|
||||
'local', // 本地npm install
|
||||
'global', // 全局npm install -g
|
||||
'npx', // npx执行
|
||||
'monorepo', // monorepo workspace
|
||||
'link' // npm link
|
||||
'development', // 开发模式
|
||||
'local', // 本地npm install
|
||||
'global', // 全局npm install -g
|
||||
'npx', // npx执行
|
||||
'monorepo', // monorepo workspace
|
||||
'link' // npm link
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测当前包安装模式
|
||||
*/
|
||||
detectInstallMode() {
|
||||
const cacheKey = 'currentInstallMode';
|
||||
detectInstallMode () {
|
||||
const cacheKey = 'currentInstallMode'
|
||||
if (this.installModeCache.has(cacheKey)) {
|
||||
return this.installModeCache.get(cacheKey);
|
||||
return this.installModeCache.get(cacheKey)
|
||||
}
|
||||
|
||||
const mode = this._performInstallModeDetection();
|
||||
this.installModeCache.set(cacheKey, mode);
|
||||
return mode;
|
||||
const mode = this._performInstallModeDetection()
|
||||
this.installModeCache.set(cacheKey, mode)
|
||||
return mode
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行安装模式检测
|
||||
*/
|
||||
_performInstallModeDetection() {
|
||||
const cwd = process.cwd();
|
||||
const execPath = process.argv[0];
|
||||
const scriptPath = process.argv[1];
|
||||
|
||||
_performInstallModeDetection () {
|
||||
const cwd = process.cwd()
|
||||
const execPath = process.argv[0]
|
||||
const scriptPath = process.argv[1]
|
||||
|
||||
// 检测npx执行
|
||||
if (this._isNpxExecution()) {
|
||||
return 'npx';
|
||||
return 'npx'
|
||||
}
|
||||
|
||||
|
||||
// 检测全局安装
|
||||
if (this._isGlobalInstall()) {
|
||||
return 'global';
|
||||
return 'global'
|
||||
}
|
||||
|
||||
|
||||
// 检测开发模式
|
||||
if (this._isDevelopmentMode()) {
|
||||
return 'development';
|
||||
return 'development'
|
||||
}
|
||||
|
||||
|
||||
// 检测monorepo
|
||||
if (this._isMonorepoWorkspace()) {
|
||||
return 'monorepo';
|
||||
return 'monorepo'
|
||||
}
|
||||
|
||||
|
||||
// 检测npm link
|
||||
if (this._isNpmLink()) {
|
||||
return 'link';
|
||||
return 'link'
|
||||
}
|
||||
|
||||
|
||||
// 默认为本地安装
|
||||
return 'local';
|
||||
return 'local'
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否是npx执行
|
||||
*/
|
||||
_isNpxExecution() {
|
||||
_isNpxExecution () {
|
||||
// 检查环境变量
|
||||
if (process.env.npm_execpath && process.env.npm_execpath.includes('npx')) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// 检查npm_config_cache路径
|
||||
if (process.env.npm_config_cache && process.env.npm_config_cache.includes('_npx')) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// 检查执行路径
|
||||
const scriptPath = process.argv[1];
|
||||
const scriptPath = process.argv[1]
|
||||
if (scriptPath && scriptPath.includes('_npx')) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否是全局安装
|
||||
*/
|
||||
_isGlobalInstall() {
|
||||
const currentPath = __dirname;
|
||||
|
||||
_isGlobalInstall () {
|
||||
const currentPath = __dirname
|
||||
|
||||
// 常见全局安装路径
|
||||
const globalPaths = [
|
||||
'/usr/lib/node_modules',
|
||||
@ -138,206 +138,206 @@ class PackageProtocol extends ResourceProtocol {
|
||||
path.join(process.env.HOME || '', '.npm-global'),
|
||||
path.join(process.env.APPDATA || '', 'npm', 'node_modules'),
|
||||
path.join(process.env.PREFIX || '', 'lib', 'node_modules')
|
||||
];
|
||||
|
||||
return globalPaths.some(globalPath =>
|
||||
]
|
||||
|
||||
return globalPaths.some(globalPath =>
|
||||
currentPath.startsWith(globalPath)
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否是开发模式
|
||||
*/
|
||||
_isDevelopmentMode() {
|
||||
_isDevelopmentMode () {
|
||||
// 检查NODE_ENV
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// 检查是否在node_modules外
|
||||
const currentPath = __dirname;
|
||||
const currentPath = __dirname
|
||||
if (!currentPath.includes('node_modules')) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// 检查package.json中的main字段是否指向源文件
|
||||
try {
|
||||
const packageJsonPath = this.findPackageJson();
|
||||
const packageJsonPath = this.findPackageJson()
|
||||
if (packageJsonPath) {
|
||||
const packageJson = require(packageJsonPath);
|
||||
const mainFile = packageJson.main || 'index.js';
|
||||
return mainFile.startsWith('src/') || mainFile.startsWith('lib/');
|
||||
const packageJson = require(packageJsonPath)
|
||||
const mainFile = packageJson.main || 'index.js'
|
||||
return mainFile.startsWith('src/') || mainFile.startsWith('lib/')
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略错误,继续其他检测
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否是monorepo workspace
|
||||
*/
|
||||
_isMonorepoWorkspace() {
|
||||
_isMonorepoWorkspace () {
|
||||
try {
|
||||
const packageJsonPath = this.findPackageJson();
|
||||
const packageJsonPath = this.findPackageJson()
|
||||
if (packageJsonPath) {
|
||||
const packageJson = require(packageJsonPath);
|
||||
|
||||
const packageJson = require(packageJsonPath)
|
||||
|
||||
// 检查workspaces字段
|
||||
if (packageJson.workspaces) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// 检查是否在workspace包内
|
||||
const rootPackageJsonPath = this.findRootPackageJson();
|
||||
const rootPackageJsonPath = this.findRootPackageJson()
|
||||
if (rootPackageJsonPath && rootPackageJsonPath !== packageJsonPath) {
|
||||
const rootPackageJson = require(rootPackageJsonPath);
|
||||
return !!rootPackageJson.workspaces;
|
||||
const rootPackageJson = require(rootPackageJsonPath)
|
||||
return !!rootPackageJson.workspaces
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略错误
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否是npm link
|
||||
*/
|
||||
_isNpmLink() {
|
||||
_isNpmLink () {
|
||||
try {
|
||||
const currentPath = __dirname;
|
||||
const stats = require('fs').lstatSync(currentPath);
|
||||
return stats.isSymbolicLink();
|
||||
const currentPath = __dirname
|
||||
const stats = require('fs').lstatSync(currentPath)
|
||||
return stats.isSymbolicLink()
|
||||
} catch (error) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找package.json文件
|
||||
*/
|
||||
findPackageJson(startPath = __dirname) {
|
||||
let currentPath = path.resolve(startPath);
|
||||
|
||||
findPackageJson (startPath = __dirname) {
|
||||
let currentPath = path.resolve(startPath)
|
||||
|
||||
while (currentPath !== path.parse(currentPath).root) {
|
||||
const packageJsonPath = path.join(currentPath, 'package.json');
|
||||
const packageJsonPath = path.join(currentPath, 'package.json')
|
||||
if (require('fs').existsSync(packageJsonPath)) {
|
||||
return packageJsonPath;
|
||||
return packageJsonPath
|
||||
}
|
||||
currentPath = path.dirname(currentPath);
|
||||
currentPath = path.dirname(currentPath)
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找根package.json文件(用于monorepo检测)
|
||||
*/
|
||||
findRootPackageJson() {
|
||||
let currentPath = process.cwd();
|
||||
let lastValidPackageJson = null;
|
||||
|
||||
findRootPackageJson () {
|
||||
let currentPath = process.cwd()
|
||||
let lastValidPackageJson = null
|
||||
|
||||
while (currentPath !== path.parse(currentPath).root) {
|
||||
const packageJsonPath = path.join(currentPath, 'package.json');
|
||||
const packageJsonPath = path.join(currentPath, 'package.json')
|
||||
if (require('fs').existsSync(packageJsonPath)) {
|
||||
lastValidPackageJson = packageJsonPath;
|
||||
lastValidPackageJson = packageJsonPath
|
||||
}
|
||||
currentPath = path.dirname(currentPath);
|
||||
currentPath = path.dirname(currentPath)
|
||||
}
|
||||
|
||||
return lastValidPackageJson;
|
||||
|
||||
return lastValidPackageJson
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包根目录
|
||||
*/
|
||||
async getPackageRoot() {
|
||||
const mode = this.detectInstallMode();
|
||||
|
||||
async getPackageRoot () {
|
||||
const mode = this.detectInstallMode()
|
||||
|
||||
switch (mode) {
|
||||
case 'development':
|
||||
// 开发模式:查找项目根目录
|
||||
return this._findProjectRoot();
|
||||
|
||||
return this._findProjectRoot()
|
||||
|
||||
case 'global':
|
||||
// 全局安装:查找全局包目录
|
||||
return this._findGlobalPackageRoot();
|
||||
|
||||
return this._findGlobalPackageRoot()
|
||||
|
||||
case 'npx':
|
||||
// npx:查找临时包目录
|
||||
return this._findNpxPackageRoot();
|
||||
|
||||
return this._findNpxPackageRoot()
|
||||
|
||||
case 'monorepo':
|
||||
// monorepo:查找workspace包目录
|
||||
return this._findWorkspacePackageRoot();
|
||||
|
||||
return this._findWorkspacePackageRoot()
|
||||
|
||||
case 'link':
|
||||
// npm link:解析符号链接
|
||||
return this._findLinkedPackageRoot();
|
||||
|
||||
return this._findLinkedPackageRoot()
|
||||
|
||||
case 'local':
|
||||
default:
|
||||
// 本地安装:查找node_modules中的包目录
|
||||
return this._findLocalPackageRoot();
|
||||
return this._findLocalPackageRoot()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找项目根目录
|
||||
*/
|
||||
_findProjectRoot() {
|
||||
const packageJsonPath = this.findPackageJson();
|
||||
return packageJsonPath ? path.dirname(packageJsonPath) : process.cwd();
|
||||
_findProjectRoot () {
|
||||
const packageJsonPath = this.findPackageJson()
|
||||
return packageJsonPath ? path.dirname(packageJsonPath) : process.cwd()
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找全局包根目录
|
||||
*/
|
||||
_findGlobalPackageRoot() {
|
||||
_findGlobalPackageRoot () {
|
||||
// 从当前模块路径向上查找,直到找到package.json
|
||||
return this._findProjectRoot();
|
||||
return this._findProjectRoot()
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找npx包根目录
|
||||
*/
|
||||
_findNpxPackageRoot() {
|
||||
_findNpxPackageRoot () {
|
||||
// npx通常将包缓存在特定目录
|
||||
const packageJsonPath = this.findPackageJson();
|
||||
return packageJsonPath ? path.dirname(packageJsonPath) : process.cwd();
|
||||
const packageJsonPath = this.findPackageJson()
|
||||
return packageJsonPath ? path.dirname(packageJsonPath) : process.cwd()
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找workspace包根目录
|
||||
*/
|
||||
_findWorkspacePackageRoot() {
|
||||
_findWorkspacePackageRoot () {
|
||||
// 在monorepo中查找当前workspace的根目录
|
||||
return this._findProjectRoot();
|
||||
return this._findProjectRoot()
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找链接包根目录
|
||||
*/
|
||||
_findLinkedPackageRoot() {
|
||||
_findLinkedPackageRoot () {
|
||||
try {
|
||||
// 解析符号链接
|
||||
const realPath = require('fs').realpathSync(__dirname);
|
||||
const packageJsonPath = this.findPackageJson(realPath);
|
||||
return packageJsonPath ? path.dirname(packageJsonPath) : realPath;
|
||||
const realPath = require('fs').realpathSync(__dirname)
|
||||
const packageJsonPath = this.findPackageJson(realPath)
|
||||
return packageJsonPath ? path.dirname(packageJsonPath) : realPath
|
||||
} catch (error) {
|
||||
return this._findProjectRoot();
|
||||
return this._findProjectRoot()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找本地包根目录
|
||||
*/
|
||||
_findLocalPackageRoot() {
|
||||
_findLocalPackageRoot () {
|
||||
// 在node_modules中查找包根目录
|
||||
return this._findProjectRoot();
|
||||
return this._findProjectRoot()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -346,23 +346,23 @@ class PackageProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} params - 查询参数
|
||||
* @returns {Promise<string>} 解析后的绝对路径
|
||||
*/
|
||||
async resolvePath(relativePath, params = null) {
|
||||
async resolvePath (relativePath, params = null) {
|
||||
// 获取包根目录
|
||||
const packageRoot = await this.getPackageRoot();
|
||||
|
||||
const packageRoot = await this.getPackageRoot()
|
||||
|
||||
// 验证路径是否在package.json的files字段中
|
||||
this.validateFileAccess(packageRoot, relativePath);
|
||||
|
||||
// 直接处理路径,不需要目录映射
|
||||
const relativePathClean = relativePath.replace(/^\/+/, '');
|
||||
const fullPath = path.resolve(packageRoot, relativePathClean);
|
||||
|
||||
this.validateFileAccess(packageRoot, relativePath)
|
||||
|
||||
// 直接处理路径,不需要目录映射
|
||||
const relativePathClean = relativePath.replace(/^\/+/, '')
|
||||
const fullPath = path.resolve(packageRoot, relativePathClean)
|
||||
|
||||
// 安全检查:确保路径在包根目录内
|
||||
if (!fullPath.startsWith(packageRoot)) {
|
||||
throw new Error(`Path traversal detected: ${relativePath}`);
|
||||
throw new Error(`Path traversal detected: ${relativePath}`)
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
|
||||
return fullPath
|
||||
}
|
||||
|
||||
/**
|
||||
@ -370,68 +370,68 @@ class PackageProtocol extends ResourceProtocol {
|
||||
* @param {string} packageRoot - 包根目录
|
||||
* @param {string} relativePath - 相对路径
|
||||
*/
|
||||
validateFileAccess(packageRoot, relativePath) {
|
||||
validateFileAccess (packageRoot, relativePath) {
|
||||
try {
|
||||
const packageJsonPath = path.join(packageRoot, 'package.json');
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
|
||||
const packageJsonPath = path.join(packageRoot, 'package.json')
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
||||
|
||||
// 如果没有files字段,允许访问所有文件(开发模式)
|
||||
if (!packageJson.files || !Array.isArray(packageJson.files)) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 标准化路径
|
||||
const normalizedPath = relativePath.replace(/^\/+/, '').replace(/\\/g, '/');
|
||||
|
||||
const normalizedPath = relativePath.replace(/^\/+/, '').replace(/\\/g, '/')
|
||||
|
||||
// 检查是否匹配files字段中的任何模式
|
||||
const isAllowed = packageJson.files.some(filePattern => {
|
||||
// 标准化文件模式
|
||||
const normalizedPattern = filePattern.replace(/^\/+/, '').replace(/\\/g, '/');
|
||||
|
||||
const normalizedPattern = filePattern.replace(/^\/+/, '').replace(/\\/g, '/')
|
||||
|
||||
// 精确匹配
|
||||
if (normalizedPattern === normalizedPath) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// 目录匹配(以/结尾或包含/*)
|
||||
if (normalizedPattern.endsWith('/') || normalizedPattern.endsWith('/*')) {
|
||||
const dirPattern = normalizedPattern.replace(/\/?\*?$/, '/');
|
||||
return normalizedPath.startsWith(dirPattern);
|
||||
const dirPattern = normalizedPattern.replace(/\/?\*?$/, '/')
|
||||
return normalizedPath.startsWith(dirPattern)
|
||||
}
|
||||
|
||||
|
||||
// 通配符匹配
|
||||
if (normalizedPattern.includes('*')) {
|
||||
const regexPattern = normalizedPattern
|
||||
.replace(/\./g, '\\.')
|
||||
.replace(/\*/g, '.*');
|
||||
const regex = new RegExp(`^${regexPattern}$`);
|
||||
return regex.test(normalizedPath);
|
||||
.replace(/\*/g, '.*')
|
||||
const regex = new RegExp(`^${regexPattern}$`)
|
||||
return regex.test(normalizedPath)
|
||||
}
|
||||
|
||||
|
||||
// 目录前缀匹配
|
||||
if (normalizedPath.startsWith(normalizedPattern + '/')) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
if (!isAllowed) {
|
||||
// 在生产环境严格检查,开发环境只警告
|
||||
const installMode = this.detectInstallMode();
|
||||
const installMode = this.detectInstallMode()
|
||||
if (installMode === 'development') {
|
||||
console.warn(`⚠️ Warning: Path '${relativePath}' not in package.json files field. This may cause issues after publishing.`);
|
||||
console.warn(`⚠️ Warning: Path '${relativePath}' not in package.json files field. This may cause issues after publishing.`)
|
||||
} else {
|
||||
throw new Error(`Access denied: Path '${relativePath}' is not included in package.json files field`);
|
||||
throw new Error(`Access denied: Path '${relativePath}' is not included in package.json files field`)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果读取package.json失败,在开发模式下允许访问
|
||||
const installMode = this.detectInstallMode();
|
||||
const installMode = this.detectInstallMode()
|
||||
if (installMode === 'development') {
|
||||
console.warn(`⚠️ Warning: Could not validate file access for '${relativePath}': ${error.message}`);
|
||||
console.warn(`⚠️ Warning: Could not validate file access for '${relativePath}': ${error.message}`)
|
||||
} else {
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -439,27 +439,27 @@ class PackageProtocol extends ResourceProtocol {
|
||||
/**
|
||||
* 检查资源是否存在
|
||||
*/
|
||||
async exists(resourcePath, queryParams) {
|
||||
async exists (resourcePath, queryParams) {
|
||||
try {
|
||||
const resolvedPath = await this.resolvePath(resourcePath, queryParams);
|
||||
await fsPromises.access(resolvedPath);
|
||||
return true;
|
||||
const resolvedPath = await this.resolvePath(resourcePath, queryParams)
|
||||
await fsPromises.access(resolvedPath)
|
||||
return true
|
||||
} catch (error) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载资源内容
|
||||
*/
|
||||
async loadContent(resourcePath, queryParams) {
|
||||
const resolvedPath = await this.resolvePath(resourcePath, queryParams);
|
||||
|
||||
async loadContent (resourcePath, queryParams) {
|
||||
const resolvedPath = await this.resolvePath(resourcePath, queryParams)
|
||||
|
||||
try {
|
||||
await fsPromises.access(resolvedPath);
|
||||
const content = await fsPromises.readFile(resolvedPath, 'utf8');
|
||||
const stats = await fsPromises.stat(resolvedPath);
|
||||
|
||||
await fsPromises.access(resolvedPath)
|
||||
const content = await fsPromises.readFile(resolvedPath, 'utf8')
|
||||
const stats = await fsPromises.stat(resolvedPath)
|
||||
|
||||
return {
|
||||
content,
|
||||
path: resolvedPath,
|
||||
@ -471,21 +471,21 @@ class PackageProtocol extends ResourceProtocol {
|
||||
absolutePath: resolvedPath,
|
||||
relativePath: resourcePath
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
throw new Error(`包资源不存在: ${resourcePath} (解析为: ${resolvedPath})`);
|
||||
throw new Error(`包资源不存在: ${resourcePath} (解析为: ${resolvedPath})`)
|
||||
}
|
||||
throw new Error(`加载包资源失败: ${error.message}`);
|
||||
throw new Error(`加载包资源失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取调试信息
|
||||
*/
|
||||
getDebugInfo() {
|
||||
const mode = this.detectInstallMode();
|
||||
|
||||
getDebugInfo () {
|
||||
const mode = this.detectInstallMode()
|
||||
|
||||
return {
|
||||
protocol: this.name,
|
||||
installMode: mode,
|
||||
@ -498,16 +498,16 @@ class PackageProtocol extends ResourceProtocol {
|
||||
npm_config_cache: process.env.npm_config_cache
|
||||
},
|
||||
cacheSize: this.cache.size
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存
|
||||
*/
|
||||
clearCache() {
|
||||
super.clearCache();
|
||||
this.installModeCache.clear();
|
||||
clearCache () {
|
||||
super.clearCache()
|
||||
this.installModeCache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PackageProtocol;
|
||||
module.exports = PackageProtocol
|
||||
|
||||
@ -1,53 +1,53 @@
|
||||
const ResourceProtocol = require('./ResourceProtocol');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const ResourceProtocol = require('./ResourceProtocol')
|
||||
const path = require('path')
|
||||
const fs = require('fs').promises
|
||||
|
||||
/**
|
||||
* 项目协议实现
|
||||
* 实现@project://协议,通过查找.promptx目录确定项目根目录
|
||||
*/
|
||||
class ProjectProtocol extends ResourceProtocol {
|
||||
constructor(options = {}) {
|
||||
super('project', options);
|
||||
|
||||
constructor (options = {}) {
|
||||
super('project', options)
|
||||
|
||||
// 支持的项目结构目录映射
|
||||
this.projectDirs = {
|
||||
'root': '', // 项目根目录
|
||||
'src': 'src', // 源代码目录
|
||||
'lib': 'lib', // 库目录
|
||||
'build': 'build', // 构建输出目录
|
||||
'dist': 'dist', // 分发目录
|
||||
'docs': 'docs', // 文档目录
|
||||
'test': 'test', // 测试目录
|
||||
'tests': 'tests', // 测试目录(复数)
|
||||
'spec': 'spec', // 规范测试目录
|
||||
'config': 'config', // 配置目录
|
||||
'scripts': 'scripts', // 脚本目录
|
||||
'assets': 'assets', // 资源目录
|
||||
'public': 'public', // 公共资源目录
|
||||
'static': 'static', // 静态资源目录
|
||||
'templates': 'templates', // 模板目录
|
||||
'examples': 'examples', // 示例目录
|
||||
'tools': 'tools' // 工具目录
|
||||
};
|
||||
|
||||
root: '', // 项目根目录
|
||||
src: 'src', // 源代码目录
|
||||
lib: 'lib', // 库目录
|
||||
build: 'build', // 构建输出目录
|
||||
dist: 'dist', // 分发目录
|
||||
docs: 'docs', // 文档目录
|
||||
test: 'test', // 测试目录
|
||||
tests: 'tests', // 测试目录(复数)
|
||||
spec: 'spec', // 规范测试目录
|
||||
config: 'config', // 配置目录
|
||||
scripts: 'scripts', // 脚本目录
|
||||
assets: 'assets', // 资源目录
|
||||
public: 'public', // 公共资源目录
|
||||
static: 'static', // 静态资源目录
|
||||
templates: 'templates', // 模板目录
|
||||
examples: 'examples', // 示例目录
|
||||
tools: 'tools' // 工具目录
|
||||
}
|
||||
|
||||
// 项目根目录缓存
|
||||
this.projectRootCache = new Map();
|
||||
this.projectRootCache = new Map()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表(保持与其他协议的一致性)
|
||||
*/
|
||||
setRegistry(registry) {
|
||||
setRegistry (registry) {
|
||||
// Project协议不使用注册表,但为了一致性提供此方法
|
||||
this.registry = registry || {};
|
||||
this.registry = registry || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议信息
|
||||
* @returns {object} 协议信息
|
||||
*/
|
||||
getProtocolInfo() {
|
||||
getProtocolInfo () {
|
||||
return {
|
||||
name: 'project',
|
||||
description: '项目协议,通过.promptx目录标识提供项目结构访问',
|
||||
@ -62,21 +62,21 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
supportedDirectories: Object.keys(this.projectDirs),
|
||||
projectMarker: '.promptx',
|
||||
params: this.getSupportedParams()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持的查询参数
|
||||
* @returns {object} 参数说明
|
||||
*/
|
||||
getSupportedParams() {
|
||||
getSupportedParams () {
|
||||
return {
|
||||
...super.getSupportedParams(),
|
||||
from: 'string - 指定搜索起始目录',
|
||||
create: 'boolean - 如果目录不存在是否创建',
|
||||
exists: 'boolean - 仅返回存在的文件/目录',
|
||||
type: 'string - 过滤类型 (file|dir|both)'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,16 +84,16 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {string} resourcePath - 资源路径
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
validatePath(resourcePath) {
|
||||
validatePath (resourcePath) {
|
||||
if (!super.validatePath(resourcePath)) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
// 解析路径的第一部分(目录类型)
|
||||
const parts = resourcePath.split('/');
|
||||
const dirType = parts[0];
|
||||
|
||||
return this.projectDirs.hasOwnProperty(dirType);
|
||||
const parts = resourcePath.split('/')
|
||||
const dirType = parts[0]
|
||||
|
||||
return this.projectDirs.hasOwnProperty(dirType)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,31 +102,31 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {string} startDir - 开始搜索的目录
|
||||
* @returns {string|null} 找到的目录路径或null
|
||||
*/
|
||||
findUpDirectorySync(targetDir, startDir = process.cwd()) {
|
||||
let currentDir = path.resolve(startDir);
|
||||
const rootDir = path.parse(currentDir).root;
|
||||
findUpDirectorySync (targetDir, startDir = process.cwd()) {
|
||||
let currentDir = path.resolve(startDir)
|
||||
const rootDir = path.parse(currentDir).root
|
||||
|
||||
while (currentDir !== rootDir) {
|
||||
const targetPath = path.join(currentDir, targetDir);
|
||||
|
||||
const targetPath = path.join(currentDir, targetDir)
|
||||
|
||||
try {
|
||||
const stats = require('fs').statSync(targetPath);
|
||||
const stats = require('fs').statSync(targetPath)
|
||||
if (stats.isDirectory()) {
|
||||
return targetPath;
|
||||
return targetPath
|
||||
}
|
||||
} catch (error) {
|
||||
// 目录不存在,继续向上查找
|
||||
}
|
||||
|
||||
const parentDir = path.dirname(currentDir);
|
||||
const parentDir = path.dirname(currentDir)
|
||||
if (parentDir === currentDir) {
|
||||
// 已到达根目录
|
||||
break;
|
||||
break
|
||||
}
|
||||
currentDir = parentDir;
|
||||
currentDir = parentDir
|
||||
}
|
||||
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,29 +134,29 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {string} startDir - 开始搜索的目录
|
||||
* @returns {Promise<string|null>} 项目根目录路径
|
||||
*/
|
||||
async findProjectRoot(startDir = process.cwd()) {
|
||||
async findProjectRoot (startDir = process.cwd()) {
|
||||
// 检查缓存
|
||||
const cacheKey = path.resolve(startDir);
|
||||
const cacheKey = path.resolve(startDir)
|
||||
if (this.projectRootCache.has(cacheKey)) {
|
||||
return this.projectRootCache.get(cacheKey);
|
||||
return this.projectRootCache.get(cacheKey)
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用自实现的向上查找
|
||||
const promptxPath = this.findUpDirectorySync('.promptx', startDir);
|
||||
const promptxPath = this.findUpDirectorySync('.promptx', startDir)
|
||||
|
||||
let projectRoot = null;
|
||||
let projectRoot = null
|
||||
if (promptxPath) {
|
||||
// .promptx 目录的父目录就是项目根目录
|
||||
projectRoot = path.dirname(promptxPath);
|
||||
projectRoot = path.dirname(promptxPath)
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
this.projectRootCache.set(cacheKey, projectRoot);
|
||||
|
||||
return projectRoot;
|
||||
this.projectRootCache.set(cacheKey, projectRoot)
|
||||
|
||||
return projectRoot
|
||||
} catch (error) {
|
||||
throw new Error(`查找项目根目录失败: ${error.message}`);
|
||||
throw new Error(`查找项目根目录失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,46 +166,46 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 解析后的绝对路径
|
||||
*/
|
||||
async resolvePath(resourcePath, queryParams) {
|
||||
const parts = resourcePath.split('/');
|
||||
const dirType = parts[0];
|
||||
const relativePath = parts.slice(1).join('/');
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
const parts = resourcePath.split('/')
|
||||
const dirType = parts[0]
|
||||
const relativePath = parts.slice(1).join('/')
|
||||
|
||||
// 验证目录类型
|
||||
if (!this.projectDirs.hasOwnProperty(dirType)) {
|
||||
throw new Error(`不支持的项目目录类型: ${dirType}。支持的类型: ${Object.keys(this.projectDirs).join(', ')}`);
|
||||
throw new Error(`不支持的项目目录类型: ${dirType}。支持的类型: ${Object.keys(this.projectDirs).join(', ')}`)
|
||||
}
|
||||
|
||||
// 确定搜索起始点
|
||||
const startDir = queryParams?.get('from') || process.cwd();
|
||||
|
||||
const startDir = queryParams?.get('from') || process.cwd()
|
||||
|
||||
// 查找项目根目录
|
||||
const projectRoot = await this.findProjectRoot(startDir);
|
||||
const projectRoot = await this.findProjectRoot(startDir)
|
||||
if (!projectRoot) {
|
||||
throw new Error(`未找到项目根目录(.promptx标识)。请确保在项目目录内或使用 'from' 参数指定项目路径`);
|
||||
throw new Error('未找到项目根目录(.promptx标识)。请确保在项目目录内或使用 \'from\' 参数指定项目路径')
|
||||
}
|
||||
|
||||
// 构建目标目录路径
|
||||
const projectDirPath = this.projectDirs[dirType];
|
||||
const targetDir = projectDirPath ? path.join(projectRoot, projectDirPath) : projectRoot;
|
||||
|
||||
const projectDirPath = this.projectDirs[dirType]
|
||||
const targetDir = projectDirPath ? path.join(projectRoot, projectDirPath) : projectRoot
|
||||
|
||||
// 如果没有相对路径,返回目录本身
|
||||
if (!relativePath) {
|
||||
return targetDir;
|
||||
return targetDir
|
||||
}
|
||||
|
||||
// 拼接完整路径
|
||||
const fullPath = path.join(targetDir, relativePath);
|
||||
|
||||
const fullPath = path.join(targetDir, relativePath)
|
||||
|
||||
// 安全检查:确保路径在项目目录内
|
||||
const resolvedPath = path.resolve(fullPath);
|
||||
const resolvedProjectRoot = path.resolve(projectRoot);
|
||||
|
||||
const resolvedPath = path.resolve(fullPath)
|
||||
const resolvedProjectRoot = path.resolve(projectRoot)
|
||||
|
||||
if (!resolvedPath.startsWith(resolvedProjectRoot)) {
|
||||
throw new Error(`安全错误:路径超出项目目录范围: ${resolvedPath}`);
|
||||
throw new Error(`安全错误:路径超出项目目录范围: ${resolvedPath}`)
|
||||
}
|
||||
|
||||
return resolvedPath;
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,33 +214,33 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 资源内容
|
||||
*/
|
||||
async loadContent(resolvedPath, queryParams) {
|
||||
async loadContent (resolvedPath, queryParams) {
|
||||
try {
|
||||
// 检查路径是否存在
|
||||
const stats = await fs.stat(resolvedPath);
|
||||
|
||||
const stats = await fs.stat(resolvedPath)
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return await this.loadDirectoryContent(resolvedPath, queryParams);
|
||||
return await this.loadDirectoryContent(resolvedPath, queryParams)
|
||||
} else if (stats.isFile()) {
|
||||
return await this.loadFileContent(resolvedPath, queryParams);
|
||||
return await this.loadFileContent(resolvedPath, queryParams)
|
||||
} else {
|
||||
throw new Error(`不支持的文件类型: ${resolvedPath}`);
|
||||
throw new Error(`不支持的文件类型: ${resolvedPath}`)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
// 检查是否需要创建目录
|
||||
if (queryParams?.get('create') === 'true') {
|
||||
await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
|
||||
return ''; // 返回空内容
|
||||
await fs.mkdir(path.dirname(resolvedPath), { recursive: true })
|
||||
return '' // 返回空内容
|
||||
}
|
||||
|
||||
|
||||
// 如果设置了exists参数为false,返回空内容而不是错误
|
||||
if (queryParams?.get('exists') === 'false') {
|
||||
return '';
|
||||
return ''
|
||||
}
|
||||
throw new Error(`文件或目录不存在: ${resolvedPath}`);
|
||||
throw new Error(`文件或目录不存在: ${resolvedPath}`)
|
||||
}
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,9 +250,9 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 文件内容
|
||||
*/
|
||||
async loadFileContent(filePath, queryParams) {
|
||||
const encoding = queryParams?.get('encoding') || 'utf8';
|
||||
return await fs.readFile(filePath, encoding);
|
||||
async loadFileContent (filePath, queryParams) {
|
||||
const encoding = queryParams?.get('encoding') || 'utf8'
|
||||
return await fs.readFile(filePath, encoding)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -261,27 +261,27 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 目录内容列表
|
||||
*/
|
||||
async loadDirectoryContent(dirPath, queryParams) {
|
||||
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
||||
|
||||
async loadDirectoryContent (dirPath, queryParams) {
|
||||
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
||||
|
||||
// 应用类型过滤
|
||||
const typeFilter = queryParams?.get('type');
|
||||
let filteredEntries = entries;
|
||||
|
||||
const typeFilter = queryParams?.get('type')
|
||||
let filteredEntries = entries
|
||||
|
||||
if (typeFilter) {
|
||||
filteredEntries = entries.filter(entry => {
|
||||
switch (typeFilter) {
|
||||
case 'file': return entry.isFile();
|
||||
case 'dir': return entry.isDirectory();
|
||||
case 'both': return true;
|
||||
default: return true;
|
||||
case 'file': return entry.isFile()
|
||||
case 'dir': return entry.isDirectory()
|
||||
case 'both': return true
|
||||
default: return true
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 格式化输出
|
||||
const format = queryParams?.get('format') || 'list';
|
||||
|
||||
const format = queryParams?.get('format') || 'list'
|
||||
|
||||
switch (format) {
|
||||
case 'json':
|
||||
return JSON.stringify(
|
||||
@ -292,21 +292,21 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
})),
|
||||
null,
|
||||
2
|
||||
);
|
||||
|
||||
)
|
||||
|
||||
case 'paths':
|
||||
return filteredEntries
|
||||
.map(entry => path.join(dirPath, entry.name))
|
||||
.join('\n');
|
||||
|
||||
.join('\n')
|
||||
|
||||
case 'list':
|
||||
default:
|
||||
return filteredEntries
|
||||
.map(entry => {
|
||||
const type = entry.isDirectory() ? '[DIR]' : '[FILE]';
|
||||
return `${type} ${entry.name}`;
|
||||
const type = entry.isDirectory() ? '[DIR]' : '[FILE]'
|
||||
return `${type} ${entry.name}`
|
||||
})
|
||||
.join('\n');
|
||||
.join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,45 +315,45 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {string} startDir - 开始搜索的目录
|
||||
* @returns {Promise<object>} 项目信息
|
||||
*/
|
||||
async getProjectInfo(startDir = process.cwd()) {
|
||||
const projectRoot = await this.findProjectRoot(startDir);
|
||||
async getProjectInfo (startDir = process.cwd()) {
|
||||
const projectRoot = await this.findProjectRoot(startDir)
|
||||
if (!projectRoot) {
|
||||
return { error: '未找到项目根目录' };
|
||||
return { error: '未找到项目根目录' }
|
||||
}
|
||||
|
||||
const result = {
|
||||
projectRoot,
|
||||
promptxPath: path.join(projectRoot, '.promptx'),
|
||||
directories: {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
for (const [dirType, dirPath] of Object.entries(this.projectDirs)) {
|
||||
const fullPath = dirPath ? path.join(projectRoot, dirPath) : projectRoot;
|
||||
const fullPath = dirPath ? path.join(projectRoot, dirPath) : projectRoot
|
||||
try {
|
||||
const stats = await fs.stat(fullPath);
|
||||
const stats = await fs.stat(fullPath)
|
||||
result.directories[dirType] = {
|
||||
path: fullPath,
|
||||
exists: true,
|
||||
type: stats.isDirectory() ? 'directory' : 'file'
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
result.directories[dirType] = {
|
||||
path: fullPath,
|
||||
exists: false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
*/
|
||||
clearCache() {
|
||||
super.clearCache();
|
||||
this.projectRootCache.clear();
|
||||
clearCache () {
|
||||
super.clearCache()
|
||||
this.projectRootCache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ProjectProtocol;
|
||||
module.exports = ProjectProtocol
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
const ResourceProtocol = require('./ResourceProtocol');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const { glob } = require('glob');
|
||||
const { promisify } = require('util');
|
||||
const ResourceProtocol = require('./ResourceProtocol')
|
||||
const path = require('path')
|
||||
const fs = require('fs').promises
|
||||
const { glob } = require('glob')
|
||||
const { promisify } = require('util')
|
||||
|
||||
/**
|
||||
* PromptX内置提示词资源协议实现
|
||||
* 实现@prompt://协议,用于访问PromptX内置的提示词资源
|
||||
*/
|
||||
class PromptProtocol extends ResourceProtocol {
|
||||
constructor(options = {}) {
|
||||
super('prompt', options);
|
||||
|
||||
constructor (options = {}) {
|
||||
super('prompt', options)
|
||||
|
||||
// PromptX 内置资源注册表
|
||||
this.registry = new Map([
|
||||
['protocols', '@package://prompt/protocol/**/*.md'],
|
||||
@ -19,42 +19,42 @@ class PromptProtocol extends ResourceProtocol {
|
||||
['domain', '@package://prompt/domain/**/*.md'],
|
||||
['resource', '@package://prompt/resource/**/*.md'],
|
||||
['bootstrap', '@package://bootstrap.md']
|
||||
]);
|
||||
|
||||
])
|
||||
|
||||
// 依赖的其他协议
|
||||
this.packageProtocol = null;
|
||||
this.packageProtocol = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置依赖的协议
|
||||
* @param {PackageProtocol} packageProtocol - 包协议实例
|
||||
*/
|
||||
setPackageProtocol(packageProtocol) {
|
||||
this.packageProtocol = packageProtocol;
|
||||
setPackageProtocol (packageProtocol) {
|
||||
this.packageProtocol = packageProtocol
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表
|
||||
*/
|
||||
setRegistry(registry) {
|
||||
setRegistry (registry) {
|
||||
if (!registry) {
|
||||
this.registry = new Map();
|
||||
return;
|
||||
this.registry = new Map()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 如果传入的是普通对象,转换为Map
|
||||
if (registry instanceof Map) {
|
||||
this.registry = registry;
|
||||
this.registry = registry
|
||||
} else {
|
||||
// 从普通对象创建Map
|
||||
this.registry = new Map(Object.entries(registry));
|
||||
this.registry = new Map(Object.entries(registry))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议信息
|
||||
*/
|
||||
getProtocolInfo() {
|
||||
getProtocolInfo () {
|
||||
return {
|
||||
name: 'prompt',
|
||||
description: 'PromptX内置提示词资源协议',
|
||||
@ -67,200 +67,200 @@ class PromptProtocol extends ResourceProtocol {
|
||||
],
|
||||
availableResources: Array.from(this.registry.keys()),
|
||||
params: this.getSupportedParams()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持的查询参数
|
||||
*/
|
||||
getSupportedParams() {
|
||||
getSupportedParams () {
|
||||
return {
|
||||
...super.getSupportedParams(),
|
||||
merge: 'boolean - 是否合并多个文件内容',
|
||||
separator: 'string - 文件间分隔符',
|
||||
include_filename: 'boolean - 是否包含文件名标题'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证资源路径
|
||||
*/
|
||||
validatePath(resourcePath) {
|
||||
validatePath (resourcePath) {
|
||||
if (!super.validatePath(resourcePath)) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// 检查是否在注册表中
|
||||
return this.registry.has(resourcePath);
|
||||
return this.registry.has(resourcePath)
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析资源路径
|
||||
*/
|
||||
async resolvePath(resourcePath, queryParams) {
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
// 验证资源是否存在
|
||||
if (!this.registry.has(resourcePath)) {
|
||||
throw new Error(`未找到 prompt 资源: ${resourcePath}。可用资源: ${Array.from(this.registry.keys()).join(', ')}`);
|
||||
throw new Error(`未找到 prompt 资源: ${resourcePath}。可用资源: ${Array.from(this.registry.keys()).join(', ')}`)
|
||||
}
|
||||
|
||||
// 获取对应的包路径
|
||||
const packagePath = this.registry.get(resourcePath);
|
||||
return packagePath;
|
||||
const packagePath = this.registry.get(resourcePath)
|
||||
return packagePath
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载资源内容
|
||||
*/
|
||||
async loadContent(packagePath, queryParams) {
|
||||
async loadContent (packagePath, queryParams) {
|
||||
// 确保有包协议依赖
|
||||
if (!this.packageProtocol) {
|
||||
throw new Error('PromptProtocol 需要 PackageProtocol 依赖');
|
||||
throw new Error('PromptProtocol 需要 PackageProtocol 依赖')
|
||||
}
|
||||
|
||||
// 检查是否是通配符路径
|
||||
if (packagePath.includes('**') || packagePath.includes('*')) {
|
||||
return await this.loadMultipleFiles(packagePath, queryParams);
|
||||
return await this.loadMultipleFiles(packagePath, queryParams)
|
||||
} else {
|
||||
return await this.loadSingleFile(packagePath, queryParams);
|
||||
return await this.loadSingleFile(packagePath, queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载单个文件
|
||||
*/
|
||||
async loadSingleFile(packagePath, queryParams) {
|
||||
async loadSingleFile (packagePath, queryParams) {
|
||||
try {
|
||||
// 移除协议前缀
|
||||
const cleanPath = packagePath.replace('@package://', '');
|
||||
const result = await this.packageProtocol.loadContent(cleanPath, queryParams);
|
||||
return result.content || result;
|
||||
const cleanPath = packagePath.replace('@package://', '')
|
||||
const result = await this.packageProtocol.loadContent(cleanPath, queryParams)
|
||||
return result.content || result
|
||||
} catch (error) {
|
||||
throw new Error(`加载单个文件失败 ${packagePath}: ${error.message}`);
|
||||
throw new Error(`加载单个文件失败 ${packagePath}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载多个文件(通配符支持)
|
||||
*/
|
||||
async loadMultipleFiles(packagePath, queryParams) {
|
||||
async loadMultipleFiles (packagePath, queryParams) {
|
||||
try {
|
||||
// 获取包根目录
|
||||
const packageRoot = await this.packageProtocol.getPackageRoot();
|
||||
|
||||
const packageRoot = await this.packageProtocol.getPackageRoot()
|
||||
|
||||
// 移除协议前缀并构建搜索路径
|
||||
const cleanPath = packagePath.replace('@package://', '');
|
||||
const searchPattern = path.join(packageRoot, cleanPath);
|
||||
|
||||
const cleanPath = packagePath.replace('@package://', '')
|
||||
const searchPattern = path.join(packageRoot, cleanPath)
|
||||
|
||||
// 使用 glob 查找匹配的文件
|
||||
const files = await glob(searchPattern, {
|
||||
ignore: ['**/node_modules/**', '**/.git/**']
|
||||
});
|
||||
})
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error(`没有找到匹配的文件: ${packagePath}`);
|
||||
throw new Error(`没有找到匹配的文件: ${packagePath}`)
|
||||
}
|
||||
|
||||
// 读取所有文件内容
|
||||
const contents = [];
|
||||
const contents = []
|
||||
for (const filePath of files.sort()) {
|
||||
try {
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
const relativePath = path.relative(packageRoot, filePath);
|
||||
|
||||
const content = await fs.readFile(filePath, 'utf8')
|
||||
const relativePath = path.relative(packageRoot, filePath)
|
||||
|
||||
contents.push({
|
||||
path: relativePath,
|
||||
content: content
|
||||
});
|
||||
content
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn(`警告: 无法读取文件 ${filePath}: ${error.message}`);
|
||||
console.warn(`警告: 无法读取文件 ${filePath}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 合并内容
|
||||
return this.mergeContents(contents, queryParams);
|
||||
return this.mergeContents(contents, queryParams)
|
||||
} catch (error) {
|
||||
throw new Error(`加载多个文件失败 ${packagePath}: ${error.message}`);
|
||||
throw new Error(`加载多个文件失败 ${packagePath}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并多个文件内容
|
||||
*/
|
||||
mergeContents(contents, queryParams) {
|
||||
const merge = queryParams?.get('merge') !== 'false'; // 默认合并
|
||||
const separator = queryParams?.get('separator') || '\n\n---\n\n';
|
||||
const includeFilename = queryParams?.get('include_filename') !== 'false'; // 默认包含文件名
|
||||
mergeContents (contents, queryParams) {
|
||||
const merge = queryParams?.get('merge') !== 'false' // 默认合并
|
||||
const separator = queryParams?.get('separator') || '\n\n---\n\n'
|
||||
const includeFilename = queryParams?.get('include_filename') !== 'false' // 默认包含文件名
|
||||
|
||||
if (!merge) {
|
||||
// 不合并,返回 JSON 格式
|
||||
return JSON.stringify(contents, null, 2);
|
||||
return JSON.stringify(contents, null, 2)
|
||||
}
|
||||
|
||||
// 合并所有内容
|
||||
const mergedParts = contents.map(({ path, content }) => {
|
||||
let part = '';
|
||||
|
||||
if (includeFilename) {
|
||||
part += `# ${path}\n\n`;
|
||||
}
|
||||
|
||||
part += content;
|
||||
|
||||
return part;
|
||||
});
|
||||
let part = ''
|
||||
|
||||
return mergedParts.join(separator);
|
||||
if (includeFilename) {
|
||||
part += `# ${path}\n\n`
|
||||
}
|
||||
|
||||
part += content
|
||||
|
||||
return part
|
||||
})
|
||||
|
||||
return mergedParts.join(separator)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查资源是否存在
|
||||
*/
|
||||
async exists(resourcePath, queryParams) {
|
||||
async exists (resourcePath, queryParams) {
|
||||
try {
|
||||
const packagePath = await this.resolvePath(resourcePath, queryParams);
|
||||
|
||||
const packagePath = await this.resolvePath(resourcePath, queryParams)
|
||||
|
||||
if (packagePath.includes('**') || packagePath.includes('*')) {
|
||||
// 通配符路径:检查是否有匹配的文件
|
||||
const packageRoot = await this.packageProtocol.getPackageRoot();
|
||||
const cleanPath = packagePath.replace('@package://', '');
|
||||
const searchPattern = path.join(packageRoot, cleanPath);
|
||||
const files = await glob(searchPattern);
|
||||
return files.length > 0;
|
||||
const packageRoot = await this.packageProtocol.getPackageRoot()
|
||||
const cleanPath = packagePath.replace('@package://', '')
|
||||
const searchPattern = path.join(packageRoot, cleanPath)
|
||||
const files = await glob(searchPattern)
|
||||
return files.length > 0
|
||||
} else {
|
||||
// 单个文件:检查文件是否存在
|
||||
const cleanPath = packagePath.replace('@package://', '');
|
||||
return await this.packageProtocol.exists(cleanPath, queryParams);
|
||||
const cleanPath = packagePath.replace('@package://', '')
|
||||
return await this.packageProtocol.exists(cleanPath, queryParams)
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出所有可用资源
|
||||
*/
|
||||
listResources() {
|
||||
listResources () {
|
||||
return Array.from(this.registry.entries()).map(([key, value]) => ({
|
||||
id: key,
|
||||
path: value,
|
||||
description: this.getResourceDescription(key)
|
||||
}));
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资源描述
|
||||
*/
|
||||
getResourceDescription(resourceId) {
|
||||
getResourceDescription (resourceId) {
|
||||
const descriptions = {
|
||||
'protocols': 'DPML协议规范文档',
|
||||
'core': '核心思维和执行模式',
|
||||
'domain': '领域专家角色定义',
|
||||
'resource': '资源管理和路径解析',
|
||||
'bootstrap': 'PromptX启动引导文件'
|
||||
};
|
||||
|
||||
return descriptions[resourceId] || '未知资源';
|
||||
protocols: 'DPML协议规范文档',
|
||||
core: '核心思维和执行模式',
|
||||
domain: '领域专家角色定义',
|
||||
resource: '资源管理和路径解析',
|
||||
bootstrap: 'PromptX启动引导文件'
|
||||
}
|
||||
|
||||
return descriptions[resourceId] || '未知资源'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PromptProtocol;
|
||||
module.exports = PromptProtocol
|
||||
|
||||
@ -8,23 +8,23 @@ class ResourceProtocol {
|
||||
* @param {string} name - 协议名称
|
||||
* @param {object} options - 配置选项
|
||||
*/
|
||||
constructor(name, options = {}) {
|
||||
constructor (name, options = {}) {
|
||||
if (new.target === ResourceProtocol) {
|
||||
throw new Error('ResourceProtocol是抽象类,不能直接实例化');
|
||||
throw new Error('ResourceProtocol是抽象类,不能直接实例化')
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
this.options = options;
|
||||
this.cache = new Map();
|
||||
this.enableCache = options.enableCache !== false;
|
||||
|
||||
this.name = name
|
||||
this.options = options
|
||||
this.cache = new Map()
|
||||
this.enableCache = options.enableCache !== false
|
||||
}
|
||||
|
||||
/**
|
||||
* 协议信息 - 需要子类实现
|
||||
* @returns {object} 协议信息
|
||||
*/
|
||||
getProtocolInfo() {
|
||||
throw new Error('子类必须实现 getProtocolInfo() 方法');
|
||||
getProtocolInfo () {
|
||||
throw new Error('子类必须实现 getProtocolInfo() 方法')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,8 +33,8 @@ class ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 解析后的路径
|
||||
*/
|
||||
async resolvePath(resourcePath, queryParams) {
|
||||
throw new Error('子类必须实现 resolvePath() 方法');
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
throw new Error('子类必须实现 resolvePath() 方法')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,8 +43,8 @@ class ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 资源内容
|
||||
*/
|
||||
async loadContent(resolvedPath, queryParams) {
|
||||
throw new Error('子类必须实现 loadContent() 方法');
|
||||
async loadContent (resolvedPath, queryParams) {
|
||||
throw new Error('子类必须实现 loadContent() 方法')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,20 +52,20 @@ class ResourceProtocol {
|
||||
* @param {string} resourcePath - 资源路径
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
validatePath(resourcePath) {
|
||||
return typeof resourcePath === 'string' && resourcePath.length > 0;
|
||||
validatePath (resourcePath) {
|
||||
return typeof resourcePath === 'string' && resourcePath.length > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持的查询参数列表 - 可选实现
|
||||
* @returns {object} 参数说明
|
||||
*/
|
||||
getSupportedParams() {
|
||||
getSupportedParams () {
|
||||
return {
|
||||
line: 'string - 行范围,如 "1-10"',
|
||||
format: 'string - 输出格式',
|
||||
cache: 'boolean - 是否缓存'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,35 +74,35 @@ class ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 资源内容
|
||||
*/
|
||||
async resolve(resourcePath, queryParams) {
|
||||
async resolve (resourcePath, queryParams) {
|
||||
// 1. 验证路径格式
|
||||
if (!this.validatePath(resourcePath)) {
|
||||
throw new Error(`无效的资源路径: ${resourcePath}`);
|
||||
throw new Error(`无效的资源路径: ${resourcePath}`)
|
||||
}
|
||||
|
||||
// 2. 生成缓存键
|
||||
const cacheKey = this.generateCacheKey(resourcePath, queryParams);
|
||||
const cacheKey = this.generateCacheKey(resourcePath, queryParams)
|
||||
|
||||
// 3. 检查缓存
|
||||
if (this.enableCache && this.cache.has(cacheKey)) {
|
||||
return this.cache.get(cacheKey);
|
||||
return this.cache.get(cacheKey)
|
||||
}
|
||||
|
||||
// 4. 解析路径
|
||||
const resolvedPath = await this.resolvePath(resourcePath, queryParams);
|
||||
const resolvedPath = await this.resolvePath(resourcePath, queryParams)
|
||||
|
||||
// 5. 加载内容
|
||||
const content = await this.loadContent(resolvedPath, queryParams);
|
||||
const content = await this.loadContent(resolvedPath, queryParams)
|
||||
|
||||
// 6. 应用通用查询参数过滤
|
||||
const filteredContent = this.applyCommonParams(content, queryParams);
|
||||
const filteredContent = this.applyCommonParams(content, queryParams)
|
||||
|
||||
// 7. 缓存结果
|
||||
if (this.enableCache) {
|
||||
this.cache.set(cacheKey, filteredContent);
|
||||
this.cache.set(cacheKey, filteredContent)
|
||||
}
|
||||
|
||||
return filteredContent;
|
||||
return filteredContent
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,9 +111,9 @@ class ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {string} 缓存键
|
||||
*/
|
||||
generateCacheKey(resourcePath, queryParams) {
|
||||
const params = queryParams ? queryParams.getAll() : {};
|
||||
return `${this.name}:${resourcePath}:${JSON.stringify(params)}`;
|
||||
generateCacheKey (resourcePath, queryParams) {
|
||||
const params = queryParams ? queryParams.getAll() : {}
|
||||
return `${this.name}:${resourcePath}:${JSON.stringify(params)}`
|
||||
}
|
||||
|
||||
/**
|
||||
@ -122,24 +122,24 @@ class ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {string} 过滤后的内容
|
||||
*/
|
||||
applyCommonParams(content, queryParams) {
|
||||
applyCommonParams (content, queryParams) {
|
||||
if (!queryParams) {
|
||||
return content;
|
||||
return content
|
||||
}
|
||||
|
||||
let result = content;
|
||||
let result = content
|
||||
|
||||
// 应用行过滤
|
||||
if (queryParams.line) {
|
||||
result = this.applyLineFilter(result, queryParams.line);
|
||||
result = this.applyLineFilter(result, queryParams.line)
|
||||
}
|
||||
|
||||
// 应用格式化(基础实现,子类可以重写)
|
||||
if (queryParams.format && queryParams.format !== 'text') {
|
||||
result = this.applyFormat(result, queryParams.format);
|
||||
result = this.applyFormat(result, queryParams.format)
|
||||
}
|
||||
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@ -148,18 +148,18 @@ class ResourceProtocol {
|
||||
* @param {string} lineRange - 行范围,如 "5-10" 或 "5"
|
||||
* @returns {string} 过滤后的内容
|
||||
*/
|
||||
applyLineFilter(content, lineRange) {
|
||||
const lines = content.split('\n');
|
||||
|
||||
applyLineFilter (content, lineRange) {
|
||||
const lines = content.split('\n')
|
||||
|
||||
if (lineRange.includes('-')) {
|
||||
const [start, end] = lineRange.split('-').map(n => parseInt(n.trim(), 10));
|
||||
const startIndex = Math.max(0, start - 1);
|
||||
const endIndex = Math.min(lines.length, end);
|
||||
return lines.slice(startIndex, endIndex).join('\n');
|
||||
const [start, end] = lineRange.split('-').map(n => parseInt(n.trim(), 10))
|
||||
const startIndex = Math.max(0, start - 1)
|
||||
const endIndex = Math.min(lines.length, end)
|
||||
return lines.slice(startIndex, endIndex).join('\n')
|
||||
} else {
|
||||
const lineNum = parseInt(lineRange, 10);
|
||||
const lineIndex = lineNum - 1;
|
||||
return lines[lineIndex] || '';
|
||||
const lineNum = parseInt(lineRange, 10)
|
||||
const lineIndex = lineNum - 1
|
||||
return lines[lineIndex] || ''
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,40 +169,40 @@ class ResourceProtocol {
|
||||
* @param {string} format - 格式
|
||||
* @returns {string} 格式化后的内容
|
||||
*/
|
||||
applyFormat(content, format) {
|
||||
applyFormat (content, format) {
|
||||
// 基础实现,子类可以重写
|
||||
switch (format) {
|
||||
case 'json':
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(content), null, 2);
|
||||
return JSON.stringify(JSON.parse(content), null, 2)
|
||||
} catch {
|
||||
return content;
|
||||
return content
|
||||
}
|
||||
case 'trim':
|
||||
return content.trim();
|
||||
return content.trim()
|
||||
default:
|
||||
return content;
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
*/
|
||||
clearCache() {
|
||||
this.cache.clear();
|
||||
clearCache () {
|
||||
this.cache.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存统计
|
||||
* @returns {object} 缓存统计信息
|
||||
*/
|
||||
getCacheStats() {
|
||||
getCacheStats () {
|
||||
return {
|
||||
protocol: this.name,
|
||||
size: this.cache.size,
|
||||
enabled: this.enableCache
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ResourceProtocol;
|
||||
module.exports = ResourceProtocol
|
||||
|
||||
@ -1,28 +1,28 @@
|
||||
const ResourceProtocol = require('./ResourceProtocol');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const ResourceProtocol = require('./ResourceProtocol')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
|
||||
/**
|
||||
* AI角色协议处理器
|
||||
* 处理 role:// 协议的资源解析,直接加载完整role文件
|
||||
*/
|
||||
class RoleProtocol extends ResourceProtocol {
|
||||
constructor() {
|
||||
super('role');
|
||||
this.registry = {};
|
||||
constructor () {
|
||||
super('role')
|
||||
this.registry = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表
|
||||
*/
|
||||
setRegistry(registry) {
|
||||
this.registry = registry || {};
|
||||
setRegistry (registry) {
|
||||
this.registry = registry || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议信息
|
||||
*/
|
||||
getProtocolInfo() {
|
||||
getProtocolInfo () {
|
||||
return {
|
||||
name: 'role',
|
||||
description: 'AI角色资源协议',
|
||||
@ -33,47 +33,47 @@ class RoleProtocol extends ResourceProtocol {
|
||||
'role://assistant',
|
||||
'role://prompt-developer'
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析资源路径
|
||||
*/
|
||||
async resolvePath(resourcePath, queryParams) {
|
||||
const roleId = resourcePath.trim();
|
||||
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
const roleId = resourcePath.trim()
|
||||
|
||||
if (!this.registry[roleId]) {
|
||||
throw new Error(`角色 "${roleId}" 未在注册表中找到。可用角色:${Object.keys(this.registry).join(', ')}`);
|
||||
throw new Error(`角色 "${roleId}" 未在注册表中找到。可用角色:${Object.keys(this.registry).join(', ')}`)
|
||||
}
|
||||
|
||||
let resolvedPath = this.registry[roleId];
|
||||
|
||||
let resolvedPath = this.registry[roleId]
|
||||
|
||||
// 处理 @package:// 前缀
|
||||
if (resolvedPath.startsWith('@package://')) {
|
||||
resolvedPath = resolvedPath.replace('@package://', '');
|
||||
resolvedPath = resolvedPath.replace('@package://', '')
|
||||
}
|
||||
|
||||
return resolvedPath;
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载资源内容
|
||||
*/
|
||||
async loadContent(resolvedPath, queryParams) {
|
||||
async loadContent (resolvedPath, queryParams) {
|
||||
try {
|
||||
const content = await fs.readFile(resolvedPath, 'utf-8');
|
||||
return content;
|
||||
const content = await fs.readFile(resolvedPath, 'utf-8')
|
||||
return content
|
||||
} catch (error) {
|
||||
throw new Error(`无法加载角色文件 ${resolvedPath}: ${error.message}`);
|
||||
throw new Error(`无法加载角色文件 ${resolvedPath}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证资源路径
|
||||
*/
|
||||
validatePath(resourcePath) {
|
||||
return /^[a-zA-Z0-9_-]+$/.test(resourcePath);
|
||||
validatePath (resourcePath) {
|
||||
return /^[a-zA-Z0-9_-]+$/.test(resourcePath)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RoleProtocol;
|
||||
module.exports = RoleProtocol
|
||||
|
||||
@ -1,28 +1,28 @@
|
||||
const ResourceProtocol = require('./ResourceProtocol');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const ResourceProtocol = require('./ResourceProtocol')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
|
||||
/**
|
||||
* 思维模式协议处理器
|
||||
* 处理 thought:// 协议的资源解析
|
||||
*/
|
||||
class ThoughtProtocol extends ResourceProtocol {
|
||||
constructor() {
|
||||
super('thought');
|
||||
this.registry = {};
|
||||
constructor () {
|
||||
super('thought')
|
||||
this.registry = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表
|
||||
*/
|
||||
setRegistry(registry) {
|
||||
this.registry = registry || {};
|
||||
setRegistry (registry) {
|
||||
this.registry = registry || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议信息
|
||||
*/
|
||||
getProtocolInfo() {
|
||||
getProtocolInfo () {
|
||||
return {
|
||||
name: 'thought',
|
||||
description: '思维模式资源协议',
|
||||
@ -31,47 +31,47 @@ class ThoughtProtocol extends ResourceProtocol {
|
||||
'thought://prompt-developer',
|
||||
'thought://product-owner'
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析资源路径
|
||||
*/
|
||||
async resolvePath(resourcePath, queryParams) {
|
||||
const thoughtId = resourcePath.trim();
|
||||
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
const thoughtId = resourcePath.trim()
|
||||
|
||||
if (!this.registry[thoughtId]) {
|
||||
throw new Error(`思维模式 "${thoughtId}" 未在注册表中找到`);
|
||||
throw new Error(`思维模式 "${thoughtId}" 未在注册表中找到`)
|
||||
}
|
||||
|
||||
let resolvedPath = this.registry[thoughtId];
|
||||
|
||||
let resolvedPath = this.registry[thoughtId]
|
||||
|
||||
// 处理 @package:// 前缀
|
||||
if (resolvedPath.startsWith('@package://')) {
|
||||
resolvedPath = resolvedPath.replace('@package://', '');
|
||||
resolvedPath = resolvedPath.replace('@package://', '')
|
||||
}
|
||||
|
||||
return resolvedPath;
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载资源内容
|
||||
*/
|
||||
async loadContent(resolvedPath, queryParams) {
|
||||
async loadContent (resolvedPath, queryParams) {
|
||||
try {
|
||||
const content = await fs.readFile(resolvedPath, 'utf-8');
|
||||
return content;
|
||||
const content = await fs.readFile(resolvedPath, 'utf-8')
|
||||
return content
|
||||
} catch (error) {
|
||||
throw new Error(`无法加载思维模式文件 ${resolvedPath}: ${error.message}`);
|
||||
throw new Error(`无法加载思维模式文件 ${resolvedPath}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证资源路径
|
||||
*/
|
||||
validatePath(resourcePath) {
|
||||
return /^[a-zA-Z0-9_-]+$/.test(resourcePath);
|
||||
validatePath (resourcePath) {
|
||||
return /^[a-zA-Z0-9_-]+$/.test(resourcePath)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ThoughtProtocol;
|
||||
module.exports = ThoughtProtocol
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
const ResourceProtocol = require('./ResourceProtocol');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const ResourceProtocol = require('./ResourceProtocol')
|
||||
const path = require('path')
|
||||
const fs = require('fs').promises
|
||||
|
||||
// 延迟加载platform-folders以处理可能的原生模块依赖
|
||||
let platformFolders = null;
|
||||
let platformFolders = null
|
||||
const getPlatformFolders = () => {
|
||||
if (!platformFolders) {
|
||||
try {
|
||||
platformFolders = require('platform-folders');
|
||||
platformFolders = require('platform-folders')
|
||||
} catch (error) {
|
||||
// 如果platform-folders不可用,回退到os.homedir()
|
||||
const os = require('os');
|
||||
const os = require('os')
|
||||
platformFolders = {
|
||||
getHomeFolder: () => os.homedir(),
|
||||
getDesktopFolder: () => path.join(os.homedir(), 'Desktop'),
|
||||
@ -19,49 +19,49 @@ const getPlatformFolders = () => {
|
||||
getMusicFolder: () => path.join(os.homedir(), 'Music'),
|
||||
getPicturesFolder: () => path.join(os.homedir(), 'Pictures'),
|
||||
getVideosFolder: () => path.join(os.homedir(), 'Videos')
|
||||
};
|
||||
console.warn('platform-folders不可用,使用os.homedir()回退方案');
|
||||
}
|
||||
console.warn('platform-folders不可用,使用os.homedir()回退方案')
|
||||
}
|
||||
}
|
||||
return platformFolders;
|
||||
};
|
||||
return platformFolders
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户目录协议实现
|
||||
* 实现@user://协议,用于访问用户的标准目录(Documents、Desktop、Downloads等)
|
||||
*/
|
||||
class UserProtocol extends ResourceProtocol {
|
||||
constructor(options = {}) {
|
||||
super('user', options);
|
||||
|
||||
constructor (options = {}) {
|
||||
super('user', options)
|
||||
|
||||
// 支持的用户目录映射
|
||||
this.userDirs = {
|
||||
'home': 'getHomeFolder',
|
||||
'desktop': 'getDesktopFolder',
|
||||
'documents': 'getDocumentsFolder',
|
||||
'downloads': 'getDownloadsFolder',
|
||||
'music': 'getMusicFolder',
|
||||
'pictures': 'getPicturesFolder',
|
||||
'videos': 'getVideosFolder'
|
||||
};
|
||||
|
||||
home: 'getHomeFolder',
|
||||
desktop: 'getDesktopFolder',
|
||||
documents: 'getDocumentsFolder',
|
||||
downloads: 'getDownloadsFolder',
|
||||
music: 'getMusicFolder',
|
||||
pictures: 'getPicturesFolder',
|
||||
videos: 'getVideosFolder'
|
||||
}
|
||||
|
||||
// 目录路径缓存
|
||||
this.dirCache = new Map();
|
||||
this.dirCache = new Map()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表(保持与其他协议的一致性)
|
||||
*/
|
||||
setRegistry(registry) {
|
||||
setRegistry (registry) {
|
||||
// User协议不使用注册表,但为了一致性提供此方法
|
||||
this.registry = registry || {};
|
||||
this.registry = registry || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议信息
|
||||
* @returns {object} 协议信息
|
||||
*/
|
||||
getProtocolInfo() {
|
||||
getProtocolInfo () {
|
||||
return {
|
||||
name: 'user',
|
||||
description: '用户目录协议,提供跨平台的用户标准目录访问',
|
||||
@ -74,19 +74,19 @@ class UserProtocol extends ResourceProtocol {
|
||||
],
|
||||
supportedDirectories: Object.keys(this.userDirs),
|
||||
params: this.getSupportedParams()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持的查询参数
|
||||
* @returns {object} 参数说明
|
||||
*/
|
||||
getSupportedParams() {
|
||||
getSupportedParams () {
|
||||
return {
|
||||
...super.getSupportedParams(),
|
||||
exists: 'boolean - 仅返回存在的文件/目录',
|
||||
type: 'string - 过滤类型 (file|dir|both)'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,16 +94,16 @@ class UserProtocol extends ResourceProtocol {
|
||||
* @param {string} resourcePath - 资源路径
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
validatePath(resourcePath) {
|
||||
validatePath (resourcePath) {
|
||||
if (!super.validatePath(resourcePath)) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
// 解析路径的第一部分(目录类型)
|
||||
const parts = resourcePath.split('/');
|
||||
const dirType = parts[0];
|
||||
|
||||
return this.userDirs.hasOwnProperty(dirType);
|
||||
const parts = resourcePath.split('/')
|
||||
const dirType = parts[0]
|
||||
|
||||
return this.userDirs.hasOwnProperty(dirType)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,36 +112,36 @@ class UserProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 解析后的绝对路径
|
||||
*/
|
||||
async resolvePath(resourcePath, queryParams) {
|
||||
const parts = resourcePath.split('/');
|
||||
const dirType = parts[0];
|
||||
const relativePath = parts.slice(1).join('/');
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
const parts = resourcePath.split('/')
|
||||
const dirType = parts[0]
|
||||
const relativePath = parts.slice(1).join('/')
|
||||
|
||||
// 验证目录类型
|
||||
if (!this.userDirs[dirType]) {
|
||||
throw new Error(`不支持的用户目录类型: ${dirType}。支持的类型: ${Object.keys(this.userDirs).join(', ')}`);
|
||||
throw new Error(`不支持的用户目录类型: ${dirType}。支持的类型: ${Object.keys(this.userDirs).join(', ')}`)
|
||||
}
|
||||
|
||||
// 获取用户目录路径
|
||||
const userDirPath = await this.getUserDirectory(dirType);
|
||||
|
||||
const userDirPath = await this.getUserDirectory(dirType)
|
||||
|
||||
// 如果没有相对路径,返回目录本身
|
||||
if (!relativePath) {
|
||||
return userDirPath;
|
||||
return userDirPath
|
||||
}
|
||||
|
||||
// 拼接完整路径
|
||||
const fullPath = path.join(userDirPath, relativePath);
|
||||
|
||||
const fullPath = path.join(userDirPath, relativePath)
|
||||
|
||||
// 安全检查:确保路径在用户目录内
|
||||
const resolvedPath = path.resolve(fullPath);
|
||||
const resolvedUserDir = path.resolve(userDirPath);
|
||||
|
||||
const resolvedPath = path.resolve(fullPath)
|
||||
const resolvedUserDir = path.resolve(userDirPath)
|
||||
|
||||
if (!resolvedPath.startsWith(resolvedUserDir)) {
|
||||
throw new Error(`安全错误:路径超出用户目录范围: ${resolvedPath}`);
|
||||
throw new Error(`安全错误:路径超出用户目录范围: ${resolvedPath}`)
|
||||
}
|
||||
|
||||
return resolvedPath;
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
/**
|
||||
@ -149,35 +149,35 @@ class UserProtocol extends ResourceProtocol {
|
||||
* @param {string} dirType - 目录类型
|
||||
* @returns {Promise<string>} 目录路径
|
||||
*/
|
||||
async getUserDirectory(dirType) {
|
||||
async getUserDirectory (dirType) {
|
||||
// 检查缓存
|
||||
if (this.dirCache.has(dirType)) {
|
||||
return this.dirCache.get(dirType);
|
||||
return this.dirCache.get(dirType)
|
||||
}
|
||||
|
||||
const folders = getPlatformFolders();
|
||||
const methodName = this.userDirs[dirType];
|
||||
|
||||
const folders = getPlatformFolders()
|
||||
const methodName = this.userDirs[dirType]
|
||||
|
||||
if (!folders[methodName]) {
|
||||
throw new Error(`未找到用户目录获取方法: ${methodName}`);
|
||||
throw new Error(`未找到用户目录获取方法: ${methodName}`)
|
||||
}
|
||||
|
||||
try {
|
||||
let dirPath;
|
||||
|
||||
let dirPath
|
||||
|
||||
// 调用platform-folders方法
|
||||
if (typeof folders[methodName] === 'function') {
|
||||
dirPath = await folders[methodName]();
|
||||
dirPath = await folders[methodName]()
|
||||
} else {
|
||||
dirPath = folders[methodName];
|
||||
dirPath = folders[methodName]
|
||||
}
|
||||
|
||||
|
||||
// 缓存结果
|
||||
this.dirCache.set(dirType, dirPath);
|
||||
|
||||
return dirPath;
|
||||
this.dirCache.set(dirType, dirPath)
|
||||
|
||||
return dirPath
|
||||
} catch (error) {
|
||||
throw new Error(`获取用户目录失败 (${dirType}): ${error.message}`);
|
||||
throw new Error(`获取用户目录失败 (${dirType}): ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,27 +187,27 @@ class UserProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 资源内容
|
||||
*/
|
||||
async loadContent(resolvedPath, queryParams) {
|
||||
async loadContent (resolvedPath, queryParams) {
|
||||
try {
|
||||
// 检查路径是否存在
|
||||
const stats = await fs.stat(resolvedPath);
|
||||
|
||||
const stats = await fs.stat(resolvedPath)
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return await this.loadDirectoryContent(resolvedPath, queryParams);
|
||||
return await this.loadDirectoryContent(resolvedPath, queryParams)
|
||||
} else if (stats.isFile()) {
|
||||
return await this.loadFileContent(resolvedPath, queryParams);
|
||||
return await this.loadFileContent(resolvedPath, queryParams)
|
||||
} else {
|
||||
throw new Error(`不支持的文件类型: ${resolvedPath}`);
|
||||
throw new Error(`不支持的文件类型: ${resolvedPath}`)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
// 如果设置了exists参数为false,返回空内容而不是错误
|
||||
if (queryParams && queryParams.get('exists') === 'false') {
|
||||
return '';
|
||||
return ''
|
||||
}
|
||||
throw new Error(`文件或目录不存在: ${resolvedPath}`);
|
||||
throw new Error(`文件或目录不存在: ${resolvedPath}`)
|
||||
}
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,9 +217,9 @@ class UserProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 文件内容
|
||||
*/
|
||||
async loadFileContent(filePath, queryParams) {
|
||||
const encoding = queryParams?.get('encoding') || 'utf8';
|
||||
return await fs.readFile(filePath, encoding);
|
||||
async loadFileContent (filePath, queryParams) {
|
||||
const encoding = queryParams?.get('encoding') || 'utf8'
|
||||
return await fs.readFile(filePath, encoding)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -228,27 +228,27 @@ class UserProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 目录内容列表
|
||||
*/
|
||||
async loadDirectoryContent(dirPath, queryParams) {
|
||||
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
||||
|
||||
async loadDirectoryContent (dirPath, queryParams) {
|
||||
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
||||
|
||||
// 应用类型过滤
|
||||
const typeFilter = queryParams?.get('type');
|
||||
let filteredEntries = entries;
|
||||
|
||||
const typeFilter = queryParams?.get('type')
|
||||
let filteredEntries = entries
|
||||
|
||||
if (typeFilter) {
|
||||
filteredEntries = entries.filter(entry => {
|
||||
switch (typeFilter) {
|
||||
case 'file': return entry.isFile();
|
||||
case 'dir': return entry.isDirectory();
|
||||
case 'both': return true;
|
||||
default: return true;
|
||||
case 'file': return entry.isFile()
|
||||
case 'dir': return entry.isDirectory()
|
||||
case 'both': return true
|
||||
default: return true
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 格式化输出
|
||||
const format = queryParams?.get('format') || 'list';
|
||||
|
||||
const format = queryParams?.get('format') || 'list'
|
||||
|
||||
switch (format) {
|
||||
case 'json':
|
||||
return JSON.stringify(
|
||||
@ -259,21 +259,21 @@ class UserProtocol extends ResourceProtocol {
|
||||
})),
|
||||
null,
|
||||
2
|
||||
);
|
||||
|
||||
)
|
||||
|
||||
case 'paths':
|
||||
return filteredEntries
|
||||
.map(entry => path.join(dirPath, entry.name))
|
||||
.join('\n');
|
||||
|
||||
.join('\n')
|
||||
|
||||
case 'list':
|
||||
default:
|
||||
return filteredEntries
|
||||
.map(entry => {
|
||||
const type = entry.isDirectory() ? '[DIR]' : '[FILE]';
|
||||
return `${type} ${entry.name}`;
|
||||
const type = entry.isDirectory() ? '[DIR]' : '[FILE]'
|
||||
return `${type} ${entry.name}`
|
||||
})
|
||||
.join('\n');
|
||||
.join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,27 +281,27 @@ class UserProtocol extends ResourceProtocol {
|
||||
* 列出所有支持的用户目录
|
||||
* @returns {Promise<object>} 目录信息
|
||||
*/
|
||||
async listUserDirectories() {
|
||||
const result = {};
|
||||
|
||||
async listUserDirectories () {
|
||||
const result = {}
|
||||
|
||||
for (const dirType of Object.keys(this.userDirs)) {
|
||||
try {
|
||||
result[dirType] = await this.getUserDirectory(dirType);
|
||||
result[dirType] = await this.getUserDirectory(dirType)
|
||||
} catch (error) {
|
||||
result[dirType] = { error: error.message };
|
||||
result[dirType] = { error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除目录缓存
|
||||
*/
|
||||
clearCache() {
|
||||
super.clearCache();
|
||||
this.dirCache.clear();
|
||||
clearCache () {
|
||||
super.clearCache()
|
||||
this.dirCache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserProtocol;
|
||||
module.exports = UserProtocol
|
||||
|
||||
Reference in New Issue
Block a user