dpml-prompt
Version:
DPML-powered AI prompt framework - Revolutionary AI-First CLI system based on Deepractice Prompt Markup Language. Build sophisticated AI agents with structured prompts, memory systems, and execution frameworks.
311 lines (270 loc) • 8.62 kB
JavaScript
const ResourceProtocol = require('./ResourceProtocol')
const path = require('path')
const fs = require('fs').promises
// 使用env-paths提供跨平台用户目录支持
const envPaths = require('env-paths')
const os = require('os')
/**
* 获取跨平台用户目录路径
* 使用env-paths替代platform-folders,提供更好的跨平台兼容性
*/
const getUserDirectories = () => {
const promptxPaths = envPaths('promptx')
return {
getHomeFolder: () => os.homedir(),
getDesktopFolder: () => path.join(os.homedir(), 'Desktop'),
getDocumentsFolder: () => path.join(os.homedir(), 'Documents'),
getDownloadsFolder: () => path.join(os.homedir(), 'Downloads'),
getMusicFolder: () => path.join(os.homedir(), 'Music'),
getPicturesFolder: () => path.join(os.homedir(), 'Pictures'),
getVideosFolder: () => path.join(os.homedir(), 'Videos'),
// 新增:env-paths标准目录
getDataFolder: () => promptxPaths.data,
getConfigFolder: () => promptxPaths.config,
getCacheFolder: () => promptxPaths.cache,
getLogFolder: () => promptxPaths.log,
getTempFolder: () => promptxPaths.temp
}
}
/**
* 用户目录协议实现
* 实现@user://协议,直接映射到用户主目录,提供简洁的用户文件访问
*/
class UserProtocol extends ResourceProtocol {
constructor (options = {}) {
super('user', options)
// 支持的用户目录映射
this.userDirs = {
home: 'getHomeFolder',
desktop: 'getDesktopFolder',
documents: 'getDocumentsFolder',
downloads: 'getDownloadsFolder',
music: 'getMusicFolder',
pictures: 'getPicturesFolder',
videos: 'getVideosFolder',
// 新增:env-paths标准目录
data: 'getDataFolder',
config: 'getConfigFolder',
cache: 'getCacheFolder',
log: 'getLogFolder',
temp: 'getTempFolder'
}
// 目录路径缓存
this.dirCache = new Map()
}
/**
* 设置注册表(保持与其他协议的一致性)
*/
setRegistry (registry) {
// User协议不使用注册表,但为了一致性提供此方法
this.registry = registry || {}
}
/**
* 获取协议信息
* @returns {object} 协议信息
*/
getProtocolInfo () {
return {
name: 'user',
description: '用户目录协议,直接映射到用户主目录',
location: 'user://{path}',
examples: [
'user://.promptx/toolbox/text-analyzer',
'user://.bashrc',
'user://Documents/notes.txt',
'user://Desktop/readme.md',
'user://Downloads/file.zip',
'user://.promptx/config.json'
],
basePath: '用户主目录 (~)',
params: this.getSupportedParams()
}
}
/**
* 支持的查询参数
* @returns {object} 参数说明
*/
getSupportedParams () {
return {
...super.getSupportedParams(),
exists: 'boolean - 仅返回存在的文件/目录',
type: 'string - 过滤类型 (file|dir|both)'
}
}
/**
* 验证用户协议路径
* @param {string} resourcePath - 资源路径
* @returns {boolean} 是否有效
*/
validatePath (resourcePath) {
if (!super.validatePath(resourcePath)) {
return false
}
// 解析路径的第一部分(目录类型)
const parts = resourcePath.split('/')
const dirType = parts[0]
return this.userDirs.hasOwnProperty(dirType)
}
/**
* 解析用户目录路径
* @param {string} resourcePath - 原始资源路径,如 ".promptx/toolbox/test-tool"
* @param {QueryParams} queryParams - 查询参数
* @returns {Promise<string>} 解析后的绝对路径
*/
async resolvePath (resourcePath, queryParams) {
// 直接使用用户主目录作为基础路径
const userHomeDir = getUserDirectories().getHomeFolder()
// 如果没有相对路径,返回用户主目录本身
if (!resourcePath) {
return userHomeDir
}
// 拼接完整路径
const fullPath = path.join(userHomeDir, resourcePath)
// 安全检查:确保路径在用户主目录内
const resolvedPath = path.resolve(fullPath)
const resolvedUserDir = path.resolve(userHomeDir)
if (!resolvedPath.startsWith(resolvedUserDir)) {
throw new Error(`安全错误:路径超出用户目录范围: ${resolvedPath}`)
}
return resolvedPath
}
/**
* 获取用户目录路径
* @param {string} dirType - 目录类型
* @returns {Promise<string>} 目录路径
*/
async getUserDirectory (dirType) {
// 检查缓存
if (this.dirCache.has(dirType)) {
return this.dirCache.get(dirType)
}
const userDirectories = getUserDirectories()
const methodName = this.userDirs[dirType]
if (!userDirectories[methodName]) {
throw new Error(`未找到用户目录获取方法: ${methodName}`)
}
try {
let dirPath
// 调用用户目录获取方法
if (typeof userDirectories[methodName] === 'function') {
dirPath = userDirectories[methodName]()
} else {
dirPath = userDirectories[methodName]
}
// 缓存结果
this.dirCache.set(dirType, dirPath)
return dirPath
} catch (error) {
throw new Error(`获取用户目录失败 (${dirType}): ${error.message}`)
}
}
/**
* 加载资源内容
* @param {string} resolvedPath - 解析后的路径
* @param {QueryParams} queryParams - 查询参数
* @returns {Promise<string>} 资源内容
*/
async loadContent (resolvedPath, queryParams) {
try {
// 检查路径是否存在
const stats = await fs.stat(resolvedPath)
if (stats.isDirectory()) {
return await this.loadDirectoryContent(resolvedPath, queryParams)
} else if (stats.isFile()) {
return await this.loadFileContent(resolvedPath, queryParams)
} else {
throw new Error(`不支持的文件类型: ${resolvedPath}`)
}
} catch (error) {
if (error.code === 'ENOENT') {
// 如果设置了exists参数为false,返回空内容而不是错误
if (queryParams && queryParams.get('exists') === 'false') {
return ''
}
throw new Error(`文件或目录不存在: ${resolvedPath}`)
}
throw error
}
}
/**
* 加载文件内容
* @param {string} filePath - 文件路径
* @param {QueryParams} queryParams - 查询参数
* @returns {Promise<string>} 文件内容
*/
async loadFileContent (filePath, queryParams) {
const encoding = queryParams?.get('encoding') || 'utf8'
return await fs.readFile(filePath, encoding)
}
/**
* 加载目录内容
* @param {string} dirPath - 目录路径
* @param {QueryParams} queryParams - 查询参数
* @returns {Promise<string>} 目录内容列表
*/
async loadDirectoryContent (dirPath, queryParams) {
const entries = await fs.readdir(dirPath, { withFileTypes: true })
// 应用类型过滤
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
}
})
}
// 格式化输出
const format = queryParams?.get('format') || 'list'
switch (format) {
case 'json':
return JSON.stringify(
filteredEntries.map(entry => ({
name: entry.name,
type: entry.isDirectory() ? 'directory' : 'file',
path: path.join(dirPath, entry.name)
})),
null,
2
)
case 'paths':
return filteredEntries
.map(entry => path.join(dirPath, entry.name))
.join('\n')
case 'list':
default:
return filteredEntries
.map(entry => {
const type = entry.isDirectory() ? '[DIR]' : '[FILE]'
return `${type} ${entry.name}`
})
.join('\n')
}
}
/**
* 列出所有支持的用户目录
* @returns {Promise<object>} 目录信息
*/
async listUserDirectories () {
const result = {}
for (const dirType of Object.keys(this.userDirs)) {
try {
result[dirType] = await this.getUserDirectory(dirType)
} catch (error) {
result[dirType] = { error: error.message }
}
}
return result
}
/**
* 清除目录缓存
*/
clearCache () {
super.clearCache()
this.dirCache.clear()
}
}
module.exports = UserProtocol