foliko 1.0.83 → 1.0.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agent/ARCHITECTURE.md +288 -0
- package/.agent/agents/ambient-agent.md +57 -0
- package/.agent/agents/debugger.md +55 -0
- package/.agent/agents/email-assistant.md +49 -0
- package/.agent/agents/file-manager.md +42 -0
- package/.agent/agents/python-developer.md +60 -0
- package/.agent/agents/scheduler.md +59 -0
- package/.agent/agents/web-developer.md +45 -0
- package/.agent/data/default.json +355 -6
- package/.agent/data/plugins-state.json +185 -146
- package/.agent/data/puppeteer-sessions/undefined.json +6 -0
- package/.agent/mcp_config.json +0 -1
- package/.agent/mcp_config_updated.json +12 -0
- package/.agent/plugins/puppeteer-plugin/README.md +147 -0
- package/.agent/plugins/puppeteer-plugin/index.js +1418 -0
- package/.agent/plugins/puppeteer-plugin/package.json +9 -0
- package/.agent/plugins.json +5 -14
- package/.agent/rules/GEMINI.md +273 -0
- package/.agent/rules/allow-rule.md +77 -0
- package/.agent/rules/log-rule.md +83 -0
- package/.agent/rules/security-rule.md +93 -0
- package/.agent/scripts/auto_preview.py +148 -0
- package/.agent/scripts/checklist.py +217 -0
- package/.agent/scripts/session_manager.py +120 -0
- package/.agent/scripts/verify_all.py +327 -0
- package/.agent/skills/api-patterns/SKILL.md +81 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/architecture/SKILL.md +55 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/clean-code/SKILL.md +201 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/frontend-design/SKILL.md +418 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +311 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +237 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +81 -0
- package/.agent/workflows/simple-test.md +42 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/structured-orchestrate.md +180 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +296 -0
- package/.claude/settings.local.json +178 -171
- package/.env.example +56 -56
- package/cli/src/commands/plugin.js +482 -0
- package/cli/src/index.js +7 -0
- package/cli/src/utils/plugin-config.js +50 -0
- package/package.json +1 -1
- package/plugins/audit-plugin.js +2 -0
- package/plugins/extension-executor-plugin.js +38 -0
- package/plugins/plugin-manager-plugin.js +402 -0
- package/plugins/session-plugin.js +3 -3
- package/skills/find-skills/AGENTS.md +162 -162
- package/skills/find-skills/SKILL.md +133 -133
- package/skills/foliko-dev/SKILL.md +563 -563
- package/skills/plugin-guide/SKILL.md +139 -0
- package/skills/python-plugin-dev/SKILL.md +238 -238
- package/src/core/agent-chat.js +103 -45
- package/src/core/framework.js +42 -1
- package/src/executors/mcp-executor.js +33 -0
- package/src/utils/index.js +153 -0
- package/xhs_auth.json +268 -0
- package/.agent/agents/code-assistant.json +0 -14
- package/.agent/agents/email-assistant.json +0 -14
- package/.agent/agents/file-assistant.json +0 -15
- package/.agent/agents/system-assistant.json +0 -15
- package/.agent/agents/web-assistant.json +0 -12
- package/.agent/data/ambient/goals.json +0 -50
- package/.agent/data/ambient/memories.json +0 -7
- package/.agent/data/scheduler/tasks.json +0 -1
- package/.agent/package.json +0 -8
- package/.agent/plugins/__pycache__/test_plugin.cpython-312.pyc +0 -0
- package/.agent/plugins/system-info/index.js +0 -387
- package/.agent/plugins/system-info/package.json +0 -4
- package/.agent/plugins/system-info/test.js +0 -40
- package/.agent/plugins/test_plugin.py +0 -304
- package/.agent/python-scripts/test_sample.py +0 -24
- package/.agent/skills/sysinfo/SKILL.md +0 -38
- package/.agent/skills/sysinfo/system-info.sh +0 -130
- package/.agent/skills/workflow/SKILL.md +0 -324
- package/.agent/workflows/email-digest.json +0 -50
- package/.agent/workflows/file-backup.json +0 -21
- package/.agent/workflows/get-ip-notify.json +0 -32
- package/.agent/workflows/news-aggregator.json +0 -93
- package/.agent/workflows/news-dashboard-v2.json +0 -94
- package/.agent/workflows/notification-batch.json +0 -32
- package/reports/system-health-report-20260401.md +0 -79
- package/test/tool-registry-validation.test.js +0 -218
- package/test_report.md +0 -70
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin 命令实现
|
|
3
|
+
* 支持插件的上传(publish)和下载(install)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
const https = require('https');
|
|
10
|
+
const http = require('http');
|
|
11
|
+
const { DEFAULT_REPO, shouldIgnore } = require('../utils/plugin-config');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 递归复制目录(带过滤)
|
|
15
|
+
*/
|
|
16
|
+
function copyDirRecursive(src, dest, ignorePatterns = []) {
|
|
17
|
+
if (!fs.existsSync(src)) return;
|
|
18
|
+
|
|
19
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
20
|
+
|
|
21
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
22
|
+
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
const srcPath = path.join(src, entry.name);
|
|
25
|
+
const destPath = path.join(dest, entry.name);
|
|
26
|
+
|
|
27
|
+
// 检查是否忽略
|
|
28
|
+
if (shouldIgnore(entry.name)) {
|
|
29
|
+
console.log(` Ignoring: ${entry.name}`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
copyDirRecursive(srcPath, destPath, ignorePatterns);
|
|
35
|
+
} else {
|
|
36
|
+
fs.copyFileSync(srcPath, destPath);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 解析 Git URL 获取信息
|
|
43
|
+
*/
|
|
44
|
+
function parseGitUrl(url) {
|
|
45
|
+
// 支持多种格式
|
|
46
|
+
const patterns = [
|
|
47
|
+
/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/,
|
|
48
|
+
/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/,
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
for (const pattern of patterns) {
|
|
52
|
+
const match = url.match(pattern);
|
|
53
|
+
if (match) {
|
|
54
|
+
return { owner: match[1], repo: match[2] };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 执行 Git 命令
|
|
62
|
+
*/
|
|
63
|
+
function gitCommand(args, cwd) {
|
|
64
|
+
try {
|
|
65
|
+
return execSync(`git ${args}`, { cwd, encoding: 'utf-8', stdio: 'pipe' });
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return err.stdout || err.stderr || '';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 下载文件
|
|
73
|
+
*/
|
|
74
|
+
function downloadFile(url, dest) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const file = fs.createWriteStream(dest);
|
|
77
|
+
const protocol = url.startsWith('https') ? https : http;
|
|
78
|
+
|
|
79
|
+
protocol
|
|
80
|
+
.get(url, (response) => {
|
|
81
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
82
|
+
// 处理重定向
|
|
83
|
+
file.close();
|
|
84
|
+
downloadFile(response.headers.location, dest).then(resolve).catch(reject);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (response.statusCode !== 200) {
|
|
89
|
+
file.close();
|
|
90
|
+
reject(new Error(`HTTP ${response.statusCode}`));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
response.pipe(file);
|
|
95
|
+
file.on('finish', () => {
|
|
96
|
+
file.close();
|
|
97
|
+
resolve();
|
|
98
|
+
});
|
|
99
|
+
})
|
|
100
|
+
.on('error', (err) => {
|
|
101
|
+
file.close();
|
|
102
|
+
fs.unlink(dest, () => {});
|
|
103
|
+
reject(err);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 发布插件到 Git
|
|
110
|
+
*/
|
|
111
|
+
async function publishCommand(pluginName, options) {
|
|
112
|
+
// 支持 .agent/plugins 和 plugins 两个目录
|
|
113
|
+
const agentPluginsDir = path.resolve(process.cwd(), '.agent', 'plugins');
|
|
114
|
+
const localPluginsDir = path.resolve(process.cwd(), 'plugins');
|
|
115
|
+
const pluginsDir = fs.existsSync(agentPluginsDir) ? agentPluginsDir : localPluginsDir;
|
|
116
|
+
const pluginPath = path.join(pluginsDir, `${pluginName}.js`);
|
|
117
|
+
const pluginSourceDir = path.join(pluginsDir, pluginName);
|
|
118
|
+
|
|
119
|
+
// 检查插件是否存在(可以是 .js 文件或目录)
|
|
120
|
+
if (!fs.existsSync(pluginPath) && !fs.existsSync(pluginSourceDir)) {
|
|
121
|
+
console.error(`Plugin not found: ${pluginName}`);
|
|
122
|
+
console.error(`Searched in: ${pluginsDir}`);
|
|
123
|
+
console.error('Available plugins:');
|
|
124
|
+
if (fs.existsSync(pluginsDir)) {
|
|
125
|
+
fs.readdirSync(pluginsDir).forEach((f) => {
|
|
126
|
+
if (f.endsWith('.js')) {
|
|
127
|
+
console.error(` - ${f.replace('.js', '')} (file)`);
|
|
128
|
+
} else if (fs.statSync(path.join(pluginsDir, f)).isDirectory()) {
|
|
129
|
+
console.error(` - ${f} (directory)`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (fs.existsSync(localPluginsDir) && localPluginsDir !== pluginsDir) {
|
|
134
|
+
fs.readdirSync(localPluginsDir).forEach((f) => {
|
|
135
|
+
if (fs.statSync(path.join(localPluginsDir, f)).isDirectory()) {
|
|
136
|
+
console.error(` - ${f} (directory)`);
|
|
137
|
+
} else if (f.endsWith('.js')) {
|
|
138
|
+
console.error(` - ${f.replace('.js', '')} (file)`);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const repo = options.repo || DEFAULT_REPO;
|
|
146
|
+
const tmpDir = path.join(require('os').tmpdir(), `foliko-plugin-publish-${Date.now()}`);
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
console.log(`Publishing plugin "${pluginName}" to ${repo}...`);
|
|
150
|
+
|
|
151
|
+
// 1. 克隆仓库(如果不存在)或拉取更新
|
|
152
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
153
|
+
|
|
154
|
+
const repoInfo = parseGitUrl(repo);
|
|
155
|
+
if (!repoInfo) {
|
|
156
|
+
console.error('Invalid repository URL');
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 尝试克隆
|
|
161
|
+
let isNewRepo = false;
|
|
162
|
+
try {
|
|
163
|
+
console.log('Cloning repository...');
|
|
164
|
+
gitCommand(`clone ${repo} "${tmpDir}" --depth 1`, process.cwd());
|
|
165
|
+
} catch (err) {
|
|
166
|
+
// 仓库可能为空,尝试初始化
|
|
167
|
+
console.log('Initializing new repository...');
|
|
168
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
169
|
+
gitCommand('init', tmpDir);
|
|
170
|
+
gitCommand(`remote add origin ${repo}`, tmpDir);
|
|
171
|
+
isNewRepo = true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 2. 创建插件目录
|
|
175
|
+
const pluginDir = path.join(tmpDir, pluginName);
|
|
176
|
+
fs.mkdirSync(pluginDir, { recursive: true });
|
|
177
|
+
|
|
178
|
+
// 3. 复制插件(整个目录或单个文件)
|
|
179
|
+
let pluginContent = null;
|
|
180
|
+
if (fs.existsSync(pluginSourceDir) && fs.statSync(pluginSourceDir).isDirectory()) {
|
|
181
|
+
// 插件在子目录中
|
|
182
|
+
console.log('Copying plugin directory...');
|
|
183
|
+
copyDirRecursive(pluginSourceDir, pluginDir);
|
|
184
|
+
|
|
185
|
+
// 尝试从主 js 文件读取描述
|
|
186
|
+
const mainJsPath = path.join(pluginSourceDir, `${pluginName}.js`);
|
|
187
|
+
if (fs.existsSync(mainJsPath)) {
|
|
188
|
+
pluginContent = fs.readFileSync(mainJsPath, 'utf-8');
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
// 单个插件文件
|
|
192
|
+
console.log('Copying plugin file...');
|
|
193
|
+
pluginContent = fs.readFileSync(pluginPath, 'utf-8');
|
|
194
|
+
const targetPath = path.join(pluginDir, `${pluginName}.js`);
|
|
195
|
+
fs.writeFileSync(targetPath, pluginContent);
|
|
196
|
+
|
|
197
|
+
// 复制配置文件(如果存在)
|
|
198
|
+
const configPath = path.join(pluginsDir, `${pluginName}.json`);
|
|
199
|
+
if (fs.existsSync(configPath)) {
|
|
200
|
+
fs.copyFileSync(configPath, path.join(pluginDir, `${pluginName}.json`));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 4. 创建 README.md(如果没有)
|
|
205
|
+
const readmePath = path.join(pluginDir, 'README.md');
|
|
206
|
+
if (!fs.existsSync(readmePath)) {
|
|
207
|
+
// 尝试从插件文件中提取描述
|
|
208
|
+
const descMatch =
|
|
209
|
+
pluginContent?.match(/\*\*Description\*\*:\s*(.+)/) ||
|
|
210
|
+
pluginContent?.match(/description[:\s]+(.+)/i);
|
|
211
|
+
const desc = descMatch ? descMatch[1] : `Foliko plugin: ${pluginName}`;
|
|
212
|
+
fs.writeFileSync(readmePath, `# ${pluginName}\n\n${desc}\n`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 6. Git 提交和推送
|
|
216
|
+
gitCommand('add .', tmpDir);
|
|
217
|
+
|
|
218
|
+
const status = gitCommand('status --porcelain', tmpDir);
|
|
219
|
+
if (!status.trim()) {
|
|
220
|
+
console.log('No changes to commit.');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
gitCommand(`commit -m "Add/update plugin: ${pluginName}"`, tmpDir);
|
|
225
|
+
gitCommand(`push ${isNewRepo ? '-u' : ''} origin main`, tmpDir);
|
|
226
|
+
|
|
227
|
+
console.log(`\nPlugin "${pluginName}" published successfully!`);
|
|
228
|
+
console.log(`Repository: ${repo}`);
|
|
229
|
+
console.log(`Path: ${pluginName}/${pluginName}.js`);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
console.error('Publish failed:', err.message);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
} finally {
|
|
234
|
+
// 清理临时目录
|
|
235
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 从 Git 安装插件
|
|
241
|
+
*/
|
|
242
|
+
async function installCommand(pluginName, options) {
|
|
243
|
+
const repo = options.repo || DEFAULT_REPO;
|
|
244
|
+
const tmpDir = path.join(require('os').tmpdir(), `foliko-plugin-install-${Date.now()}`);
|
|
245
|
+
const localPluginsDir = path.resolve(process.cwd(), 'plugins');
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
console.log(`Installing plugin "${pluginName}" from ${repo}...`);
|
|
249
|
+
|
|
250
|
+
// 1. 克隆仓库
|
|
251
|
+
console.log('Fetching plugin repository...');
|
|
252
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
253
|
+
gitCommand(`clone ${repo} "${tmpDir}" --depth 1`, process.cwd());
|
|
254
|
+
|
|
255
|
+
// 2. 检查插件目录/文件
|
|
256
|
+
const pluginDir = path.join(tmpDir, pluginName);
|
|
257
|
+
let sourcePath;
|
|
258
|
+
|
|
259
|
+
if (fs.existsSync(pluginDir) && fs.statSync(pluginDir).isDirectory()) {
|
|
260
|
+
// 插件在子目录中
|
|
261
|
+
sourcePath = path.join(pluginDir, `${pluginName}.js`);
|
|
262
|
+
} else {
|
|
263
|
+
// 插件在仓库根目录
|
|
264
|
+
sourcePath = path.join(tmpDir, `${pluginName}.js`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!fs.existsSync(sourcePath)) {
|
|
268
|
+
// 列出可用插件
|
|
269
|
+
console.error(`Plugin "${pluginName}" not found in repository.`);
|
|
270
|
+
console.error('\nAvailable plugins:');
|
|
271
|
+
|
|
272
|
+
const repos = fs.readdirSync(tmpDir).filter((f) => {
|
|
273
|
+
const fullPath = path.join(tmpDir, f);
|
|
274
|
+
return fs.statSync(fullPath).isDirectory();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
if (repos.length === 0) {
|
|
278
|
+
// 检查根目录的 js 文件
|
|
279
|
+
fs.readdirSync(tmpDir)
|
|
280
|
+
.filter((f) => f.endsWith('.js') && !f.startsWith('.'))
|
|
281
|
+
.forEach((f) => console.error(` - ${f.replace('.js', '')}`));
|
|
282
|
+
} else {
|
|
283
|
+
repos.forEach((r) => console.error(` - ${r}`));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 3. 确保本地 plugins 目录存在
|
|
290
|
+
if (!fs.existsSync(localPluginsDir)) {
|
|
291
|
+
fs.mkdirSync(localPluginsDir, { recursive: true });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 4. 复制插件文件
|
|
295
|
+
const targetPath = path.join(localPluginsDir, `${pluginName}.js`);
|
|
296
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
297
|
+
console.log(`Plugin copied to: ${targetPath}`);
|
|
298
|
+
|
|
299
|
+
// 5. 复制配置文件(如果存在)
|
|
300
|
+
const configSource = path.join(path.dirname(sourcePath), `${pluginName}.json`);
|
|
301
|
+
if (fs.existsSync(configSource)) {
|
|
302
|
+
const configTarget = path.join(localPluginsDir, `${pluginName}.json`);
|
|
303
|
+
fs.copyFileSync(configSource, configTarget);
|
|
304
|
+
console.log(`Config copied to: ${configTarget}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
console.log(`\nPlugin "${pluginName}" installed successfully!`);
|
|
308
|
+
} catch (err) {
|
|
309
|
+
console.error('Install failed:', err.message);
|
|
310
|
+
process.exit(1);
|
|
311
|
+
} finally {
|
|
312
|
+
// 清理临时目录
|
|
313
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 列出远程仓库中的插件
|
|
319
|
+
*/
|
|
320
|
+
async function listCommand(options) {
|
|
321
|
+
const repo = options.repo || DEFAULT_REPO;
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
console.log(`Fetching plugins from ${repo}...`);
|
|
325
|
+
|
|
326
|
+
const repoInfo = parseGitUrl(repo);
|
|
327
|
+
if (!repoInfo) {
|
|
328
|
+
console.error('Invalid repository URL');
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 使用 GitHub API 获取仓库内容
|
|
333
|
+
const apiUrl = `https://api.github.com/repos/${repoInfo.owner}/${repoInfo.repo}/contents`;
|
|
334
|
+
|
|
335
|
+
const response = await fetch(apiUrl);
|
|
336
|
+
|
|
337
|
+
if (!response.ok) {
|
|
338
|
+
if (response.status === 404) {
|
|
339
|
+
console.error('Repository not found or is empty.');
|
|
340
|
+
} else {
|
|
341
|
+
console.error(`GitHub API error: ${response.status}`);
|
|
342
|
+
}
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const contents = await response.json();
|
|
347
|
+
|
|
348
|
+
if (!Array.isArray(contents) || contents.length === 0) {
|
|
349
|
+
console.log('No plugins found in repository.');
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
console.log(`\nPlugins in ${repo}:\n`);
|
|
354
|
+
console.log('Name Type Description');
|
|
355
|
+
console.log('---------------- -------- ------------------------------------------');
|
|
356
|
+
|
|
357
|
+
for (const item of contents) {
|
|
358
|
+
if (item.type === 'dir') {
|
|
359
|
+
// 尝试读取 README
|
|
360
|
+
let desc = '-';
|
|
361
|
+
try {
|
|
362
|
+
const readmeUrl = `https://raw.githubusercontent.com/${repoInfo.owner}/${repoInfo.repo}/main/${item.name}/README.md`;
|
|
363
|
+
const readmeResp = await fetch(readmeUrl);
|
|
364
|
+
if (readmeResp.ok) {
|
|
365
|
+
const readmeText = await readmeResp.text();
|
|
366
|
+
const lines = readmeText.split('\n').filter((l) => l.trim());
|
|
367
|
+
if (lines.length > 1) {
|
|
368
|
+
desc = lines.slice(1).join(' ').slice(0, 40);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} catch (e) {}
|
|
372
|
+
|
|
373
|
+
console.log(`${item.name.padEnd(16)} plugin ${desc}`);
|
|
374
|
+
} else if (item.type === 'file' && item.name.endsWith('.js')) {
|
|
375
|
+
const name = item.name.replace('.js', '');
|
|
376
|
+
console.log(`${name.padEnd(16)} plugin (root file)`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} catch (err) {
|
|
380
|
+
console.error('List failed:', err.message);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* 插件命令主入口
|
|
387
|
+
*/
|
|
388
|
+
async function pluginCommand(args) {
|
|
389
|
+
let subcommand = args[0];
|
|
390
|
+
|
|
391
|
+
// 处理帮助选项
|
|
392
|
+
if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
|
|
393
|
+
printHelp();
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!subcommand) {
|
|
398
|
+
printHelp();
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const options = {
|
|
403
|
+
repo: process.env.FOLIKO_PLUGIN_REPO || DEFAULT_REPO,
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// 解析全局选项
|
|
407
|
+
const remainingArgs = [];
|
|
408
|
+
for (let i = 1; i < args.length; i++) {
|
|
409
|
+
if (args[i] === '--repo' && args[i + 1]) {
|
|
410
|
+
options.repo = args[i + 1];
|
|
411
|
+
i++;
|
|
412
|
+
} else {
|
|
413
|
+
remainingArgs.push(args[i]);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
switch (subcommand) {
|
|
418
|
+
case 'publish':
|
|
419
|
+
case 'push': {
|
|
420
|
+
const pluginName = remainingArgs[0];
|
|
421
|
+
if (!pluginName) {
|
|
422
|
+
console.error('Plugin name required: foliko plugin publish <name>');
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
await publishCommand(pluginName, options);
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
case 'install':
|
|
430
|
+
case 'add': {
|
|
431
|
+
const pluginName = remainingArgs[0];
|
|
432
|
+
if (!pluginName) {
|
|
433
|
+
console.error('Plugin name required: foliko plugin install <name>');
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
await installCommand(pluginName, options);
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
case 'list':
|
|
441
|
+
case 'ls': {
|
|
442
|
+
await listCommand(options);
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
case 'help':
|
|
447
|
+
printHelp();
|
|
448
|
+
break;
|
|
449
|
+
|
|
450
|
+
default:
|
|
451
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
452
|
+
printHelp();
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function printHelp() {
|
|
458
|
+
console.log(`
|
|
459
|
+
Foliko Plugin Manager - Publish and install plugins to/from Git
|
|
460
|
+
|
|
461
|
+
Usage: foliko plugin <command> [options]
|
|
462
|
+
|
|
463
|
+
Commands:
|
|
464
|
+
list, ls List available plugins from repository
|
|
465
|
+
publish <name> Publish a plugin to the repository
|
|
466
|
+
install <name> Install a plugin from the repository
|
|
467
|
+
|
|
468
|
+
Options:
|
|
469
|
+
--repo <url> Specify repository URL (default: ${DEFAULT_REPO})
|
|
470
|
+
|
|
471
|
+
Environment Variables:
|
|
472
|
+
FOLIKO_PLUGIN_REPO Default repository URL
|
|
473
|
+
|
|
474
|
+
Examples:
|
|
475
|
+
foliko plugin list
|
|
476
|
+
foliko plugin publish my-plugin
|
|
477
|
+
foliko plugin install my-plugin
|
|
478
|
+
foliko plugin install other-plugin --repo https://github.com/user/other-plugins.git
|
|
479
|
+
`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
module.exports = { pluginCommand };
|
package/cli/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const { chatCommand } = require('./commands/chat');
|
|
6
6
|
const { listCommand } = require('./commands/list');
|
|
7
|
+
const { pluginCommand } = require('./commands/plugin');
|
|
7
8
|
const fs = require('fs');
|
|
8
9
|
const path = require('path');
|
|
9
10
|
/**
|
|
@@ -24,6 +25,11 @@ async function cli() {
|
|
|
24
25
|
await listCommand();
|
|
25
26
|
break;
|
|
26
27
|
|
|
28
|
+
case 'plugin':
|
|
29
|
+
case 'plugins':
|
|
30
|
+
await pluginCommand(args.slice(1));
|
|
31
|
+
break;
|
|
32
|
+
|
|
27
33
|
case 'help':
|
|
28
34
|
case '--help':
|
|
29
35
|
case '-h':
|
|
@@ -56,6 +62,7 @@ Usage: foliko <command> [options]
|
|
|
56
62
|
Commands:
|
|
57
63
|
chat 启动持续对话聊天
|
|
58
64
|
list 列出所有子Agent配置
|
|
65
|
+
plugin 插件管理 (publish/install/list)
|
|
59
66
|
help 显示帮助信息
|
|
60
67
|
version 显示版本号
|
|
61
68
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 插件管理器公共配置
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 默认插件仓库
|
|
6
|
+
const DEFAULT_REPO = 'https://github.com/chnak/foliko-plugins.git';
|
|
7
|
+
|
|
8
|
+
// 发布时忽略的文件和目录
|
|
9
|
+
const IGNORE_PATTERNS = [
|
|
10
|
+
'node_modules',
|
|
11
|
+
'.git',
|
|
12
|
+
'.env',
|
|
13
|
+
'.DS_Store',
|
|
14
|
+
'Thumbs.db',
|
|
15
|
+
'*.log',
|
|
16
|
+
'*.lock',
|
|
17
|
+
'*.bak',
|
|
18
|
+
'.claude',
|
|
19
|
+
'.agent',
|
|
20
|
+
'examples',
|
|
21
|
+
'dist',
|
|
22
|
+
'build',
|
|
23
|
+
'coverage',
|
|
24
|
+
'tests',
|
|
25
|
+
'__tests__',
|
|
26
|
+
'*.test.js',
|
|
27
|
+
'*.spec.js',
|
|
28
|
+
'package-lock.json',
|
|
29
|
+
'yarn.lock',
|
|
30
|
+
'pnpm-lock.yaml',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 检查文件/目录是否应该被忽略
|
|
35
|
+
*/
|
|
36
|
+
function shouldIgnore(name) {
|
|
37
|
+
return IGNORE_PATTERNS.some((pattern) => {
|
|
38
|
+
if (pattern.startsWith('*.')) {
|
|
39
|
+
const ext = pattern.slice(1);
|
|
40
|
+
return name.endsWith(ext);
|
|
41
|
+
}
|
|
42
|
+
return name === pattern;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
DEFAULT_REPO,
|
|
48
|
+
IGNORE_PATTERNS,
|
|
49
|
+
shouldIgnore,
|
|
50
|
+
};
|
package/package.json
CHANGED
package/plugins/audit-plugin.js
CHANGED
|
@@ -30,7 +30,9 @@ class AuditPlugin extends Plugin {
|
|
|
30
30
|
this._framework = framework
|
|
31
31
|
|
|
32
32
|
// 监听框架事件
|
|
33
|
+
// tool:call 和 tool-call 两种格式都监听(兼容不同来源)
|
|
33
34
|
framework.on('tool:call', (data) => this._log('tool_call', data))
|
|
35
|
+
framework.on('tool-call', (data) => this._log('tool_call', data))
|
|
34
36
|
framework.on('tool:result', (data) => this._log('tool_result', data))
|
|
35
37
|
framework.on('tool:error', (data) => this._log('tool_error', data))
|
|
36
38
|
framework.on('agent:message', (data) => this._log('agent_message', data))
|
|
@@ -102,11 +102,41 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
try {
|
|
105
|
+
// 触发扩展工具开始事件
|
|
106
|
+
framework.emit('tool:call', {
|
|
107
|
+
name: `${plugin}:${tool}`,
|
|
108
|
+
args: toolArgs,
|
|
109
|
+
source: 'extension'
|
|
110
|
+
});
|
|
111
|
+
framework.emit('tool-call', {
|
|
112
|
+
name: `${plugin}:${tool}`,
|
|
113
|
+
args: toolArgs,
|
|
114
|
+
source: 'extension'
|
|
115
|
+
});
|
|
116
|
+
|
|
105
117
|
// 统一 execute 签名为 (args, framework)
|
|
106
118
|
const result = await toolDef.execute(toolArgs, framework);
|
|
119
|
+
|
|
120
|
+
// 触发扩展工具完成事件
|
|
121
|
+
framework.emit('tool:result', {
|
|
122
|
+
name: `${plugin}:${tool}`,
|
|
123
|
+
args: toolArgs,
|
|
124
|
+
result,
|
|
125
|
+
source: 'extension'
|
|
126
|
+
});
|
|
127
|
+
|
|
107
128
|
return { success: true, result };
|
|
108
129
|
} catch (err) {
|
|
109
130
|
log.error(` Tool '${tool}' failed:`, err.message);
|
|
131
|
+
|
|
132
|
+
// 触发扩展工具错误事件
|
|
133
|
+
framework.emit('tool:error', {
|
|
134
|
+
name: `${plugin}:${tool}`,
|
|
135
|
+
args: toolArgs,
|
|
136
|
+
error: err.message,
|
|
137
|
+
source: 'extension'
|
|
138
|
+
});
|
|
139
|
+
|
|
110
140
|
return { success: false, error: err.message };
|
|
111
141
|
}
|
|
112
142
|
},
|
|
@@ -315,6 +345,14 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
315
345
|
|
|
316
346
|
reload(framework) {
|
|
317
347
|
this._framework = framework;
|
|
348
|
+
// 重新扫描所有已加载插件的 tools
|
|
349
|
+
this._extensions.clear();
|
|
350
|
+
const plugins = framework.pluginManager.getAll();
|
|
351
|
+
for (const { instance: plugin } of plugins) {
|
|
352
|
+
this._scanPluginTools(plugin);
|
|
353
|
+
}
|
|
354
|
+
// 刷新所有 Agent 的扩展提示词
|
|
355
|
+
this._refreshAllAgentsExtPrompt(framework);
|
|
318
356
|
}
|
|
319
357
|
|
|
320
358
|
async uninstall(framework) {
|