352 lines
9.9 KiB
JavaScript
Executable File
352 lines
9.9 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
// 简单的命令行参数处理
|
||
const args = process.argv.slice(2);
|
||
const command = args[0] || 'protocols'; // 默认执行protocols命令,而不是help
|
||
const param = args[1]; // role命令时的角色文件路径
|
||
|
||
// 获取脚本所在目录和PromptX根目录
|
||
const scriptDir = __dirname;
|
||
const promptxDir = scriptDir; // 脚本现在就在PromptX目录内
|
||
|
||
/**
|
||
* 打印所有协议内容
|
||
*/
|
||
function printProtocols() {
|
||
// 定义目录优先级顺序
|
||
const directories = [
|
||
{ path: path.join(promptxDir, 'protocol'), name: 'protocol' },
|
||
{ path: path.join(promptxDir, 'core'), name: 'core' },
|
||
{ path: path.join(promptxDir, 'resource'), name: 'resource' }
|
||
];
|
||
|
||
let allFiles = [];
|
||
|
||
// 递归查找文件函数
|
||
function collectMarkdownFiles(dir) {
|
||
if (!fs.existsSync(dir)) {
|
||
console.warn(`警告: 目录不存在 ${dir}`);
|
||
return [];
|
||
}
|
||
|
||
let files = [];
|
||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||
|
||
for (const entry of entries) {
|
||
const fullPath = path.join(dir, entry.name);
|
||
|
||
if (entry.isDirectory()) {
|
||
files = files.concat(collectMarkdownFiles(fullPath));
|
||
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
||
files.push(fullPath);
|
||
}
|
||
}
|
||
|
||
return files;
|
||
}
|
||
|
||
// 按目录优先级收集文件
|
||
for (const dir of directories) {
|
||
const dirFiles = collectMarkdownFiles(dir.path);
|
||
|
||
// 每个目录内的文件按字母顺序排序
|
||
dirFiles.sort();
|
||
|
||
// 合并到总文件列表
|
||
allFiles = allFiles.concat(dirFiles);
|
||
|
||
console.log(`从 ${dir.name} 目录收集了 ${dirFiles.length} 个文件`);
|
||
}
|
||
|
||
// 没有文件时的提示
|
||
if (allFiles.length === 0) {
|
||
console.log("未找到任何协议文件。请确认PromptX目录结构是否正确。");
|
||
return;
|
||
}
|
||
|
||
// 打印每个文件
|
||
for (const file of allFiles) {
|
||
const relativePath = path.relative(promptxDir, file);
|
||
const separator = "=".repeat(80);
|
||
console.log(`\n${separator}\n### 文件: ${relativePath}\n${separator}\n`);
|
||
|
||
try {
|
||
const content = fs.readFileSync(file, 'utf8');
|
||
console.log(content);
|
||
} catch (err) {
|
||
console.error(`读取文件错误: ${file}`, err);
|
||
}
|
||
}
|
||
|
||
console.log(`\n总计读取了 ${allFiles.length} 个协议文件。`);
|
||
}
|
||
|
||
/**
|
||
* 打印指定角色内容
|
||
*/
|
||
function printRole(rolePath) {
|
||
// 如果传入的是相对路径,则基于PromptX目录解析
|
||
let fullPath;
|
||
if (path.isAbsolute(rolePath)) {
|
||
fullPath = rolePath;
|
||
} else {
|
||
fullPath = path.join(promptxDir, rolePath);
|
||
}
|
||
|
||
if (!fs.existsSync(fullPath)) {
|
||
console.error(`错误: 角色文件不存在: ${fullPath}`);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const content = fs.readFileSync(fullPath, 'utf8');
|
||
const separator = "=".repeat(80);
|
||
console.log(`\n${separator}\n### 角色文件: ${path.relative(promptxDir, fullPath)}\n${separator}\n`);
|
||
console.log(content);
|
||
} catch (err) {
|
||
console.error(`读取角色文件错误: ${fullPath}`, err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 打印指定路径的文件内容
|
||
*/
|
||
function printFile(filePath) {
|
||
// 如果传入的是相对路径,则基于PromptX目录解析
|
||
let fullPath;
|
||
if (path.isAbsolute(filePath)) {
|
||
fullPath = filePath;
|
||
} else {
|
||
fullPath = path.join(promptxDir, filePath);
|
||
}
|
||
|
||
if (!fs.existsSync(fullPath)) {
|
||
console.error(`错误: 文件不存在: ${fullPath}`);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const content = fs.readFileSync(fullPath, 'utf8');
|
||
const separator = "=".repeat(80);
|
||
console.log(`\n${separator}\n### 文件: ${path.relative(promptxDir, fullPath)}\n${separator}\n`);
|
||
console.log(content);
|
||
} catch (err) {
|
||
console.error(`读取文件错误: ${fullPath}`, err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解析记忆命令参数
|
||
* @param {string} content - 记忆内容,可能包含标签、评分和有效期
|
||
* @param {string[]} args - 其他参数
|
||
*/
|
||
function parseMemoryArgs(content, args) {
|
||
const options = {
|
||
tags: [],
|
||
score: 5,
|
||
duration: '短期'
|
||
};
|
||
|
||
// 从内容中提取标签、评分和有效期
|
||
const contentParts = content.trim().split(/\s+/);
|
||
let cleanContent = [];
|
||
|
||
for (let part of contentParts) {
|
||
if (part.startsWith('#')) {
|
||
// 检查是否是特殊标记
|
||
if (part.includes('评分:')) {
|
||
const scoreText = part.split('评分:')[1].trim();
|
||
const score = parseInt(scoreText);
|
||
if (!isNaN(score) && score >= 1 && score <= 10) {
|
||
options.score = score;
|
||
}
|
||
} else if (part.includes('有效期:')) {
|
||
const duration = part.split('有效期:')[1].trim();
|
||
if (['短期', '中期', '长期'].includes(duration)) {
|
||
options.duration = duration;
|
||
}
|
||
} else {
|
||
// 普通标签
|
||
const tag = part.slice(1);
|
||
if (tag) {
|
||
options.tags.push(tag);
|
||
}
|
||
}
|
||
} else {
|
||
cleanContent.push(part);
|
||
}
|
||
}
|
||
|
||
// 处理剩余的命令行参数
|
||
for (let arg of args) {
|
||
// 移除参数前后的引号(如果有)
|
||
arg = arg.replace(/^['"]|['"]$/g, '').trim();
|
||
|
||
if (arg.startsWith('#')) {
|
||
// 检查是否是特殊标记
|
||
if (arg.includes('评分:')) {
|
||
const score = parseInt(arg.split('评分:')[1]);
|
||
if (!isNaN(score) && score >= 1 && score <= 10) {
|
||
options.score = score;
|
||
}
|
||
} else if (arg.includes('有效期:')) {
|
||
const duration = arg.split('有效期:')[1];
|
||
if (['短期', '中期', '长期'].includes(duration)) {
|
||
options.duration = duration;
|
||
}
|
||
} else {
|
||
// 普通标签
|
||
const tag = arg.slice(1);
|
||
if (tag) {
|
||
options.tags.push(tag);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果没有标签,使用默认标签
|
||
if (options.tags.length === 0) {
|
||
options.tags = ['其他'];
|
||
}
|
||
|
||
// 返回清理后的内容和选项
|
||
return {
|
||
cleanContent: cleanContent.join(' '),
|
||
options: options
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 添加记忆条目
|
||
* @param {string} content - 记忆内容
|
||
* @param {object} options - 配置选项
|
||
*/
|
||
function addMemory(content, options = {}) {
|
||
const defaultOptions = {
|
||
tags: ['其他'],
|
||
score: 5,
|
||
duration: '短期',
|
||
timestamp: new Date().toLocaleString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
})
|
||
};
|
||
|
||
// 确保选项中的数组和对象被正确合并
|
||
const finalOptions = {
|
||
...defaultOptions,
|
||
...options,
|
||
tags: options.tags && options.tags.length > 0 ? options.tags : defaultOptions.tags
|
||
};
|
||
|
||
// 构建记忆条目,确保格式统一
|
||
const memoryEntry = `\n- ${finalOptions.timestamp} ${content.trim()} ${finalOptions.tags.map(tag => `#${tag}`).join(' ')} #评分:${finalOptions.score} #有效期:${finalOptions.duration}\n`;
|
||
|
||
// 确保.memory目录存在
|
||
const memoryDir = path.join(process.cwd(), '.memory');
|
||
if (!fs.existsSync(memoryDir)) {
|
||
fs.mkdirSync(memoryDir, { recursive: true });
|
||
}
|
||
|
||
// 追加到记忆文件
|
||
const memoryFile = path.join(memoryDir, 'declarative.md');
|
||
try {
|
||
// 如果文件不存在,创建文件并添加标题
|
||
if (!fs.existsSync(memoryFile)) {
|
||
fs.writeFileSync(memoryFile, '# 陈述性记忆\n\n## 高价值记忆(评分 ≥ 7)\n');
|
||
}
|
||
|
||
fs.appendFileSync(memoryFile, memoryEntry);
|
||
console.log('✅ 记忆已成功保存');
|
||
|
||
// 如果评分大于等于7,输出高价值提醒
|
||
if (finalOptions.score >= 7) {
|
||
console.log('🌟 这是一条高价值记忆');
|
||
}
|
||
} catch (err) {
|
||
console.error('❌ 记忆保存失败:', err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 打印帮助信息
|
||
*/
|
||
function printHelp() {
|
||
console.log(`
|
||
PromptX 工具 - 协议和角色内容查看器
|
||
|
||
使用方法:
|
||
node promptx.js - 打印所有协议内容 (按protocol、core、resource顺序)
|
||
node promptx.js protocols - 同上,打印所有协议内容
|
||
node promptx.js role <路径> - 打印指定角色文件内容
|
||
node promptx.js file <路径> - 打印指定文件内容
|
||
node promptx.js remember <内容> - 添加记忆条目,标签、评分和有效期可直接包含在内容中
|
||
node promptx.js help - 显示此帮助信息
|
||
|
||
记忆命令用法:
|
||
将标签、评分和有效期直接包含在内容字符串中,时间戳会自动添加到行首
|
||
#标签名 - 添加标签 (可多个)
|
||
#评分:数字 - 设置重要性评分 (1-10)
|
||
#有效期:时长 - 设置有效期 (短期/中期/长期)
|
||
|
||
示例:
|
||
node promptx.js remember "用户提出了重要建议 #用户反馈 #改进建议 #评分:8 #有效期:长期"
|
||
node promptx.js remember "临时配置信息 #配置 #评分:3 #有效期:短期"
|
||
`);
|
||
}
|
||
|
||
// 根据命令执行相应功能
|
||
switch (command) {
|
||
case 'protocols':
|
||
printProtocols();
|
||
break;
|
||
case 'role':
|
||
if (!param) {
|
||
console.error('错误: 缺少角色文件路径');
|
||
printHelp();
|
||
} else {
|
||
printRole(param);
|
||
}
|
||
break;
|
||
case 'file':
|
||
if (!param) {
|
||
console.error('错误: 缺少文件路径');
|
||
printHelp();
|
||
} else {
|
||
printFile(param);
|
||
}
|
||
break;
|
||
case 'remember':
|
||
if (!param) {
|
||
console.error('错误: 缺少记忆内容');
|
||
console.log('使用方法: node promptx.js remember "记忆内容 #标签1 #标签2 #评分:8 #有效期:长期"');
|
||
} else {
|
||
try {
|
||
// 记忆内容就是传入的第一个参数
|
||
const memoryContent = param;
|
||
|
||
// 解析内容中的标签、评分和有效期
|
||
const { cleanContent, options } = parseMemoryArgs(memoryContent, []);
|
||
|
||
// 添加记忆
|
||
addMemory(cleanContent, options);
|
||
} catch (err) {
|
||
console.error('错误:', err.message);
|
||
console.log('使用方法: node promptx.js remember "记忆内容 #标签1 #标签2 #评分:8 #有效期:长期"');
|
||
}
|
||
}
|
||
break;
|
||
case 'help':
|
||
printHelp();
|
||
break;
|
||
default:
|
||
console.error(`错误: 未知命令 "${command}"`);
|
||
printHelp();
|
||
break;
|
||
}
|