foliko 1.1.92 → 2.0.0
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/.claude/settings.local.json +2 -1
- package/CLAUDE.md +56 -30
- package/REFACTORING_PLAN.md +645 -0
- package/docs/architecture.md +131 -0
- package/docs/migration.md +57 -0
- package/docs/public-api.md +138 -0
- package/docs/usage.md +385 -0
- package/examples/ambient-example.js +20 -137
- package/examples/basic.js +21 -48
- package/examples/bootstrap.js +16 -74
- package/examples/mcp-example.js +6 -29
- package/examples/skill-example.js +6 -19
- package/examples/workflow.js +8 -56
- package/package.json +8 -4
- package/plugins/README.md +49 -0
- package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
- package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
- package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
- package/plugins/ambient/README.md +14 -0
- package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
- package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
- package/plugins/{ambient-agent → ambient}/index.js +2 -2
- package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
- package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
- package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
- package/plugins/core/default/bootstrap.js +202 -0
- package/plugins/core/default/config.js +220 -0
- package/plugins/core/default/index.js +58 -0
- package/plugins/core/mcp/index.js +1 -0
- package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
- package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
- package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
- package/plugins/{session-plugin.js → core/session/index.js} +9 -73
- package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
- package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
- package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
- package/plugins/{think-plugin.js → core/think/index.js} +24 -91
- package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
- package/plugins/default-plugins.js +6 -720
- package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
- package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
- package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
- package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
- package/plugins/install/README.md +9 -0
- package/plugins/{install-plugin.js → install/index.js} +3 -3
- package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
- package/plugins/{web-plugin.js → io/web/index.js} +11 -113
- package/plugins/memory/README.md +13 -0
- package/plugins/{memory-plugin.js → memory/index.js} +4 -18
- package/plugins/messaging/email/README.md +19 -0
- package/plugins/{email → messaging/email}/index.js +2 -2
- package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +3 -3
- package/plugins/{qq-plugin.js → messaging/qq/index.js} +5 -16
- package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +3 -3
- package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +15 -15
- package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
- package/plugins/{tools-plugin.js → tools/index.js} +68 -116
- package/plugins/trading/README.md +15 -0
- package/plugins/{gate-trading.js → trading/index.js} +8 -8
- package/{examples → sandbox}/test-concurrent-chat.js +2 -2
- package/{examples → sandbox}/test-long-chat.js +2 -2
- package/{examples → sandbox}/test-session-chat.js +2 -2
- package/{examples → sandbox}/test-web-plugin.js +1 -1
- package/{examples → sandbox}/test-weixin-feishu.js +2 -2
- package/src/agent/base.js +56 -0
- package/src/{core/agent-chat.js → agent/chat.js} +11 -11
- package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
- package/src/agent/index.js +111 -0
- package/src/agent/main.js +337 -0
- package/src/agent/prompt.js +78 -0
- package/src/agent/sub.js +198 -0
- package/src/agent/worker.js +104 -0
- package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
- package/{cli/src → src/cli}/commands/chat.js +25 -21
- package/{cli/src → src/cli}/index.js +1 -0
- package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
- package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
- package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
- package/src/{core → common}/constants.js +3 -0
- package/src/common/errors.js +402 -0
- package/src/{utils → common}/logger.js +33 -0
- package/src/{utils/chat-queue.js → common/queue.js} +2 -2
- package/src/config/plugin-config.js +50 -0
- package/src/context/agent.js +32 -0
- package/src/context/compaction-prompts.js +170 -0
- package/src/context/compaction-utils.js +191 -0
- package/src/context/compressor.js +413 -0
- package/src/context/index.js +9 -0
- package/src/{core/context-manager.js → context/manager.js} +1 -1
- package/src/context/request.js +50 -0
- package/src/context/session.js +33 -0
- package/src/context/storage.js +30 -0
- package/src/executors/mcp-client.js +153 -0
- package/src/executors/mcp-desc.js +236 -0
- package/src/executors/mcp-executor.js +91 -956
- package/src/{core → framework}/command-registry.js +1 -1
- package/src/framework/framework.js +300 -0
- package/src/framework/index.js +18 -0
- package/src/framework/lifecycle.js +203 -0
- package/src/framework/loader.js +78 -0
- package/src/framework/registry.js +86 -0
- package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
- package/src/index.js +130 -15
- package/src/llm/index.js +26 -0
- package/src/llm/provider.js +212 -0
- package/src/llm/registry.js +11 -0
- package/src/{core/token-counter.js → llm/tokens.js} +4 -37
- package/src/{core/plugin-base.js → plugin/base.js} +10 -136
- package/src/plugin/index.js +14 -0
- package/src/plugin/loader.js +101 -0
- package/src/plugin/manager.js +261 -0
- package/src/{core → session}/branch-summary-auto.js +2 -2
- package/src/{core/chat-session.js → session/chat.js} +2 -2
- package/src/session/index.js +7 -0
- package/src/{core/session-manager.js → session/session.js} +2 -2
- package/src/session/ttl.js +92 -0
- package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
- package/src/tool/executor.js +85 -0
- package/src/tool/index.js +15 -0
- package/src/tool/registry.js +143 -0
- package/src/{core/tool-router.js → tool/router.js} +17 -124
- package/src/tool/schema.js +108 -0
- package/src/utils/data-splitter.js +1 -1
- package/src/utils/download.js +1 -1
- package/src/utils/index.js +6 -6
- package/src/utils/message-validator.js +1 -1
- package/tests/core/context-storage.test.js +46 -0
- package/tests/core/llm.test.js +54 -0
- package/tests/core/plugin.test.js +42 -0
- package/tests/core/tool.test.js +60 -0
- package/tests/setup.js +10 -0
- package/tests/smoke.test.js +58 -0
- package/vitest.config.js +9 -0
- package/cli/src/daemon.js +0 -149
- package/docs/CONTEXT_DESIGN.md +0 -1596
- package/docs/ai-sdk-optimization.md +0 -655
- package/docs/features.md +0 -120
- package/docs/qq-bot.md +0 -976
- package/docs/quick-reference.md +0 -160
- package/docs/user-manual.md +0 -1391
- package/images/geometric_shapes.jpg +0 -0
- package/images/sunset_mountain_lake.jpg +0 -0
- package/skills/poster-guide/SKILL.md +0 -792
- package/src/capabilities/index.js +0 -11
- package/src/core/agent.js +0 -808
- package/src/core/context-compressor.js +0 -959
- package/src/core/enhanced-context-compressor.js +0 -210
- package/src/core/framework.js +0 -1422
- package/src/core/index.js +0 -30
- package/src/core/plugin-manager.js +0 -961
- package/src/core/provider-registry.js +0 -159
- package/src/core/provider.js +0 -156
- package/src/core/request-context.js +0 -98
- package/src/core/subagent.js +0 -442
- package/src/core/system-prompt-builder.js +0 -120
- package/src/core/tool-executor.js +0 -202
- package/src/core/tool-registry.js +0 -517
- package/src/core/worker-agent.js +0 -192
- package/src/executors/executor-base.js +0 -58
- package/src/utils/error-boundary.js +0 -363
- package/src/utils/error.js +0 -374
- package/system.md +0 -1645
- package/website_v2/README.md +0 -57
- package/website_v2/SPEC.md +0 -1
- package/website_v2/docs/api.html +0 -128
- package/website_v2/docs/configuration.html +0 -147
- package/website_v2/docs/plugin-development.html +0 -129
- package/website_v2/docs/project-structure.html +0 -89
- package/website_v2/docs/skill-development.html +0 -85
- package/website_v2/index.html +0 -489
- package/website_v2/scripts/main.js +0 -93
- package/website_v2/styles/animations.css +0 -8
- package/website_v2/styles/docs.css +0 -83
- package/website_v2/styles/main.css +0 -417
- package/xhs_auth.json +0 -268
- package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
- /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/handlers.js +0 -0
- /package/plugins/{email → messaging/email}/monitor.js +0 -0
- /package/plugins/{email → messaging/email}/parser.js +0 -0
- /package/plugins/{email → messaging/email}/reply.js +0 -0
- /package/plugins/{email → messaging/email}/utils.js +0 -0
- /package/{examples → sandbox}/test-chat.js +0 -0
- /package/{examples → sandbox}/test-mcp.js +0 -0
- /package/{examples → sandbox}/test-reload.js +0 -0
- /package/{examples → sandbox}/test-telegram.js +0 -0
- /package/{examples → sandbox}/test-tg-bot.js +0 -0
- /package/{examples → sandbox}/test-tg-simple.js +0 -0
- /package/{examples → sandbox}/test-tg.js +0 -0
- /package/{examples → sandbox}/test-think.js +0 -0
- /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
- /package/{cli/src → src/cli}/commands/daemon.js +0 -0
- /package/{cli/src → src/cli}/commands/list.js +0 -0
- /package/{cli/src → src/cli}/commands/plugin.js +0 -0
- /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
- /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
- /package/{cli/src → src/cli}/utils/ansi.js +0 -0
- /package/{cli/src → src/cli}/utils/config.js +0 -0
- /package/{cli/src → src/cli}/utils/markdown.js +0 -0
- /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
- /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
- /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
- /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
- /package/src/{utils/event-emitter.js → common/events.js} +0 -0
- /package/src/{utils → common}/id.js +0 -0
- /package/src/{utils → common}/retry.js +0 -0
- /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
- /package/src/{core/session-entry.js → session/entry.js} +0 -0
- /package/src/{core/storage-manager.js → storage/manager.js} +0 -0
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
* - allowedUsers: 允许交互的用户 openid 列表(空数组表示允许所有人)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const { Plugin } = require('
|
|
12
|
-
const { logger } = require('
|
|
11
|
+
const { Plugin } = require('../../../src/plugin/base')
|
|
12
|
+
const { logger } = require('../../../src/common/logger')
|
|
13
13
|
const log = logger.child('QQ')
|
|
14
|
-
const { cleanResponse } = require('
|
|
15
|
-
const { FileDownloader } = require('
|
|
14
|
+
const { cleanResponse } = require('../../../src/utils')
|
|
15
|
+
const { FileDownloader } = require('../../../src/utils/download')
|
|
16
16
|
const { z } = require('zod')
|
|
17
17
|
const { createQQBotClient, QQBotAPIClient, StreamSession } = require('@chnak/qq-bot')
|
|
18
18
|
const path = require('path')
|
|
@@ -374,14 +374,6 @@ class QQPlugin extends Plugin {
|
|
|
374
374
|
},
|
|
375
375
|
})
|
|
376
376
|
|
|
377
|
-
// 连接后可以从事件中获取机器人信息,先直接连接
|
|
378
|
-
// 获取机器人自身的 openid(用于发送消息)
|
|
379
|
-
// try {
|
|
380
|
-
// const token = await this._client.api.getToken()
|
|
381
|
-
// } catch (err) {
|
|
382
|
-
// log.warn('Failed to get access token:', err.message)
|
|
383
|
-
// }
|
|
384
|
-
|
|
385
377
|
// 监听私聊消息
|
|
386
378
|
this._client.on('C2C_MESSAGE_CREATE', async (event) => {
|
|
387
379
|
await this._handleC2CMessage(event)
|
|
@@ -484,9 +476,6 @@ class QQPlugin extends Plugin {
|
|
|
484
476
|
|
|
485
477
|
if (!actualContent) return
|
|
486
478
|
|
|
487
|
-
// 权限检查(可以按群或用户检查)
|
|
488
|
-
// if (!this._checkGroupPermission(groupOpenid, userId)) { ... }
|
|
489
|
-
|
|
490
479
|
// 处理命令
|
|
491
480
|
if (actualContent.startsWith('/')) {
|
|
492
481
|
await this._handleGroupCommand(groupOpenid, username, actualContent, event)
|
|
@@ -1096,4 +1085,4 @@ function interval(fn, ms) {
|
|
|
1096
1085
|
return { start, stop }
|
|
1097
1086
|
}
|
|
1098
1087
|
|
|
1099
|
-
module.exports = QQPlugin
|
|
1088
|
+
module.exports = QQPlugin
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* - prefix: 命令前缀
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const { Plugin } = require('
|
|
13
|
-
const { logger } = require('
|
|
12
|
+
const { Plugin } = require('../../../src/plugin/base')
|
|
13
|
+
const { logger } = require('../../../src/common/logger')
|
|
14
14
|
const log = logger.child('Telegram')
|
|
15
15
|
const { z } = require('zod')
|
|
16
16
|
|
|
@@ -29,7 +29,7 @@ class TelegramPlugin extends Plugin {
|
|
|
29
29
|
this.priority = 80
|
|
30
30
|
this.enabled = false
|
|
31
31
|
this.systemPrompt = `你是一个有帮助的AI助手。回复内容不要使用markdown格式文本。
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
**重要:** 子Agent 匹配规则必须遵守:
|
|
34
34
|
- 根据【子 Agent 匹配表】,将任务委托给最匹配的子Agent处理
|
|
35
35
|
- 使用 subagent_call 工具并指定 agentName 来委托任务
|
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
* - forceLogin: 是否强制重新扫码登录
|
|
7
7
|
* - qrcodeTerminal: 是否在终端渲染二维码 (默认 true)
|
|
8
8
|
*/
|
|
9
|
-
const { CLEAR_LINE, CYAN, DIM, GREEN, RED, YELLOW, colored } = require('
|
|
10
|
-
const { renderLine } = require('
|
|
11
|
-
const { debounce, throttle } = require('
|
|
9
|
+
const { CLEAR_LINE, CYAN, DIM, GREEN, RED, YELLOW, colored } = require('../../../cli/src/utils/ansi');
|
|
10
|
+
const { renderLine } = require('../../../cli/src/utils/markdown');
|
|
11
|
+
const { debounce, throttle } = require('../../../src/utils');
|
|
12
12
|
const interval = (fn) => {
|
|
13
13
|
const handle = setInterval(fn, 3000);
|
|
14
14
|
return { start: () => handle, stop: () => clearInterval(handle) };
|
|
15
15
|
};
|
|
16
|
-
const { Plugin } = require('
|
|
17
|
-
const { logger } = require('
|
|
16
|
+
const { Plugin } = require('../../../src/plugin/base')
|
|
17
|
+
const { logger } = require('../../../src/common/logger')
|
|
18
18
|
const log = logger.child('WeChat')
|
|
19
|
-
const {cleanResponse} =require('
|
|
20
|
-
const {FileDownloader} = require('
|
|
19
|
+
const {cleanResponse} =require('../../../src/utils')
|
|
20
|
+
const {FileDownloader} = require('../../../src/utils/download')
|
|
21
21
|
const removeMarkdown = require('remove-markdown');
|
|
22
22
|
const { z } = require('zod')
|
|
23
23
|
const { WeixinBot} = require('@chnak/weixin-bot')
|
|
@@ -338,7 +338,7 @@ class WeixinPlugin extends Plugin {
|
|
|
338
338
|
// log.info(' 已记录机器人 userId:', this._myUserId)
|
|
339
339
|
const {agent,sessionId} = this._getSessionAgent(creds.userId)
|
|
340
340
|
const sessionScope = agent.createSessionScope(sessionId);
|
|
341
|
-
|
|
341
|
+
|
|
342
342
|
// 注册消息处理
|
|
343
343
|
this._bot.onMessage(async (msg) => {
|
|
344
344
|
await this._handleMessage(msg)
|
|
@@ -417,9 +417,9 @@ class WeixinPlugin extends Plugin {
|
|
|
417
417
|
|
|
418
418
|
// 消息完成
|
|
419
419
|
// sessionScope.on('message:complete', async ({ content }) => {
|
|
420
|
-
|
|
420
|
+
|
|
421
421
|
// if (!content) {
|
|
422
|
-
|
|
422
|
+
|
|
423
423
|
// }
|
|
424
424
|
// });
|
|
425
425
|
|
|
@@ -478,7 +478,7 @@ class WeixinPlugin extends Plugin {
|
|
|
478
478
|
sharedPrompt: `工作目录: {{WORK_DIR}}`,
|
|
479
479
|
metadata: { WORK_DIR: this._framework?.getCwd?.() ?? process.cwd() }
|
|
480
480
|
})
|
|
481
|
-
|
|
481
|
+
|
|
482
482
|
this._sessionAgents.set(userId, agent)
|
|
483
483
|
// log.info(' Created new session agent for userId:', userId)
|
|
484
484
|
|
|
@@ -528,7 +528,7 @@ class WeixinPlugin extends Plugin {
|
|
|
528
528
|
voice:"音频"
|
|
529
529
|
}
|
|
530
530
|
const files=await this._save_file(msg)
|
|
531
|
-
|
|
531
|
+
|
|
532
532
|
await this._processChat(userId, `
|
|
533
533
|
[用户发送了${type_dict[msg.type]}消息]\n
|
|
534
534
|
文件列表:\n
|
|
@@ -549,7 +549,7 @@ class WeixinPlugin extends Plugin {
|
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
551
|
}
|
|
552
|
-
|
|
552
|
+
|
|
553
553
|
async _save_file(msg){
|
|
554
554
|
const files=[]
|
|
555
555
|
try{
|
|
@@ -562,7 +562,7 @@ class WeixinPlugin extends Plugin {
|
|
|
562
562
|
const file=await this.downloader.fromWeXin(url,aesKey)
|
|
563
563
|
files.push(file.path)
|
|
564
564
|
}
|
|
565
|
-
|
|
565
|
+
|
|
566
566
|
}
|
|
567
567
|
}catch(err){
|
|
568
568
|
console.log(err)
|
|
@@ -1044,4 +1044,4 @@ class WeixinPlugin extends Plugin {
|
|
|
1044
1044
|
}
|
|
1045
1045
|
}
|
|
1046
1046
|
|
|
1047
|
-
module.exports = WeixinPlugin
|
|
1047
|
+
module.exports = WeixinPlugin
|
|
@@ -6,67 +6,40 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { execSync } = require('child_process');
|
|
9
|
-
const { Plugin } = require('
|
|
10
|
-
const { logger } = require('
|
|
9
|
+
const { Plugin } = require('../../src/plugin/base');
|
|
10
|
+
const { logger } = require('../../src/common/logger');
|
|
11
11
|
const { z } = require('zod');
|
|
12
|
-
|
|
13
|
-
// 从共享配置加载
|
|
14
|
-
const { DEFAULT_REPO, shouldIgnore } = require('../cli/src/utils/plugin-config');
|
|
12
|
+
const { DEFAULT_REPO, shouldIgnore } = require('../../src/cli/utils/plugin-config');
|
|
15
13
|
|
|
16
14
|
const log = logger.child('PluginManagerPlugin');
|
|
17
15
|
|
|
18
|
-
/**
|
|
19
|
-
* 递归复制目录(带过滤)
|
|
20
|
-
*/
|
|
21
16
|
function copyDirRecursive(src, dest) {
|
|
22
17
|
if (!fs.existsSync(src)) return;
|
|
23
|
-
|
|
24
18
|
fs.mkdirSync(dest, { recursive: true });
|
|
25
19
|
|
|
26
20
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
27
|
-
|
|
28
21
|
for (const entry of entries) {
|
|
29
22
|
const srcPath = path.join(src, entry.name);
|
|
30
23
|
const destPath = path.join(dest, entry.name);
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (entry.isDirectory()) {
|
|
38
|
-
copyDirRecursive(srcPath, destPath);
|
|
39
|
-
} else {
|
|
40
|
-
fs.copyFileSync(srcPath, destPath);
|
|
41
|
-
}
|
|
24
|
+
if (shouldIgnore(entry.name)) { log.debug(`Ignoring: ${entry.name}`); continue; }
|
|
25
|
+
if (entry.isDirectory()) { copyDirRecursive(srcPath, destPath); }
|
|
26
|
+
else { fs.copyFileSync(srcPath, destPath); }
|
|
42
27
|
}
|
|
43
28
|
}
|
|
44
29
|
|
|
45
|
-
/**
|
|
46
|
-
* 执行 Git 命令
|
|
47
|
-
*/
|
|
48
30
|
function gitCommand(args, cwd) {
|
|
49
|
-
try {
|
|
50
|
-
|
|
51
|
-
} catch (err) {
|
|
52
|
-
return err.stdout || err.stderr || '';
|
|
53
|
-
}
|
|
31
|
+
try { return execSync(`git ${args}`, { cwd, encoding: 'utf-8', stdio: 'pipe' }); }
|
|
32
|
+
catch (err) { return err.stdout || err.stderr || ''; }
|
|
54
33
|
}
|
|
55
34
|
|
|
56
|
-
/**
|
|
57
|
-
* 解析 Git URL 获取信息
|
|
58
|
-
*/
|
|
59
35
|
function parseGitUrl(url) {
|
|
60
36
|
const patterns = [
|
|
61
37
|
/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/,
|
|
62
38
|
/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/,
|
|
63
39
|
];
|
|
64
|
-
|
|
65
40
|
for (const pattern of patterns) {
|
|
66
41
|
const match = url.match(pattern);
|
|
67
|
-
if (match) {
|
|
68
|
-
return { owner: match[1], repo: match[2] };
|
|
69
|
-
}
|
|
42
|
+
if (match) return { owner: match[1], repo: match[2] };
|
|
70
43
|
}
|
|
71
44
|
return null;
|
|
72
45
|
}
|
|
@@ -74,12 +47,12 @@ function parseGitUrl(url) {
|
|
|
74
47
|
class PluginManagerPlugin extends Plugin {
|
|
75
48
|
constructor(config = {}) {
|
|
76
49
|
super();
|
|
77
|
-
this.name = '
|
|
50
|
+
this.name = 'plugin-manager';
|
|
78
51
|
this.version = '1.0.0';
|
|
79
52
|
this.description = '管理远程插件:列表、发布、安装';
|
|
80
53
|
this._repo = config.repo || process.env.FOLIKO_PLUGIN_REPO || DEFAULT_REPO;
|
|
81
54
|
this.system = true;
|
|
82
|
-
this.tools = {};
|
|
55
|
+
this.tools = {};
|
|
83
56
|
}
|
|
84
57
|
|
|
85
58
|
install(framework) {
|
|
@@ -88,7 +61,6 @@ class PluginManagerPlugin extends Plugin {
|
|
|
88
61
|
}
|
|
89
62
|
|
|
90
63
|
start(framework) {
|
|
91
|
-
// 1. plugin_list - 列出远程仓库插件
|
|
92
64
|
framework.registerTool({
|
|
93
65
|
name: 'plugin_list',
|
|
94
66
|
description: '列出远程插件仓库中所有可用的插件',
|
|
@@ -101,7 +73,6 @@ class PluginManagerPlugin extends Plugin {
|
|
|
101
73
|
}
|
|
102
74
|
});
|
|
103
75
|
|
|
104
|
-
// 2. plugin_publish - 发布本地插件到远程仓库
|
|
105
76
|
framework.registerTool({
|
|
106
77
|
name: 'plugin_publish',
|
|
107
78
|
description: '将本地插件发布到远程 Git 仓库(需要仓库写权限)',
|
|
@@ -116,7 +87,6 @@ class PluginManagerPlugin extends Plugin {
|
|
|
116
87
|
}
|
|
117
88
|
});
|
|
118
89
|
|
|
119
|
-
// 3. plugin_install - 从远程仓库安装插件到本地
|
|
120
90
|
framework.registerTool({
|
|
121
91
|
name: 'plugin_install',
|
|
122
92
|
description: '从远程 Git 仓库安装插件到本地 .foliko/plugins 目录',
|
|
@@ -134,40 +104,26 @@ class PluginManagerPlugin extends Plugin {
|
|
|
134
104
|
return this;
|
|
135
105
|
}
|
|
136
106
|
|
|
137
|
-
/**
|
|
138
|
-
* 列出远程仓库的插件
|
|
139
|
-
*/
|
|
140
107
|
async _listPlugins(repo) {
|
|
141
108
|
try {
|
|
142
|
-
//log.info(`Listing plugins from ${repo}...`);
|
|
143
|
-
|
|
144
109
|
const repoInfo = parseGitUrl(repo);
|
|
145
|
-
if (!repoInfo) {
|
|
146
|
-
return { success: false, error: 'Invalid repository URL' };
|
|
147
|
-
}
|
|
110
|
+
if (!repoInfo) return { success: false, error: 'Invalid repository URL' };
|
|
148
111
|
|
|
149
|
-
// 使用 GitHub API 获取仓库内容
|
|
150
112
|
const apiUrl = `https://api.github.com/repos/${repoInfo.owner}/${repoInfo.repo}/contents`;
|
|
151
113
|
const response = await fetch(apiUrl);
|
|
152
|
-
|
|
153
114
|
if (!response.ok) {
|
|
154
|
-
if (response.status === 404) {
|
|
155
|
-
return { success: false, error: `Repository not found: ${repo}` };
|
|
156
|
-
}
|
|
115
|
+
if (response.status === 404) return { success: false, error: `Repository not found: ${repo}` };
|
|
157
116
|
return { success: false, error: `GitHub API error: ${response.status}` };
|
|
158
117
|
}
|
|
159
118
|
|
|
160
119
|
const contents = await response.json();
|
|
161
|
-
|
|
162
120
|
if (!Array.isArray(contents) || contents.length === 0) {
|
|
163
121
|
return { success: true, data: [], metadata: { message: 'No plugins found in repository' } };
|
|
164
122
|
}
|
|
165
123
|
|
|
166
124
|
const plugins = [];
|
|
167
|
-
|
|
168
125
|
for (const item of contents) {
|
|
169
126
|
if (item.type === 'dir') {
|
|
170
|
-
// 获取 README 描述
|
|
171
127
|
let description = '-';
|
|
172
128
|
try {
|
|
173
129
|
const readmeUrl = `https://raw.githubusercontent.com/${repoInfo.owner}/${repoInfo.repo}/main/${item.name}/README.md`;
|
|
@@ -175,151 +131,83 @@ class PluginManagerPlugin extends Plugin {
|
|
|
175
131
|
if (readmeResp.ok) {
|
|
176
132
|
const readmeText = await readmeResp.text();
|
|
177
133
|
const lines = readmeText.split('\n').filter(l => l.trim());
|
|
178
|
-
if (lines.length > 1)
|
|
179
|
-
description = lines.slice(1).join(' ').slice(0, 100);
|
|
180
|
-
}
|
|
134
|
+
if (lines.length > 1) description = lines.slice(1).join(' ').slice(0, 100);
|
|
181
135
|
}
|
|
182
|
-
} catch (e) {}
|
|
183
|
-
|
|
184
|
-
plugins.push({
|
|
185
|
-
name: item.name,
|
|
186
|
-
type: 'directory',
|
|
187
|
-
description,
|
|
188
|
-
});
|
|
136
|
+
} catch (e) { /* ignore */ }
|
|
137
|
+
plugins.push({ name: item.name, type: 'directory', description });
|
|
189
138
|
} else if (item.type === 'file' && item.name.endsWith('.js')) {
|
|
190
|
-
|
|
191
|
-
plugins.push({
|
|
192
|
-
name,
|
|
193
|
-
type: 'file',
|
|
194
|
-
description: '(root file)',
|
|
195
|
-
});
|
|
139
|
+
plugins.push({ name: item.name.replace('.js', ''), type: 'file', description: '(root file)' });
|
|
196
140
|
}
|
|
197
141
|
}
|
|
198
142
|
|
|
199
|
-
return {
|
|
200
|
-
success: true,
|
|
201
|
-
data: plugins,
|
|
202
|
-
metadata: { repo, count: plugins.length },
|
|
203
|
-
};
|
|
204
|
-
|
|
143
|
+
return { success: true, data: plugins, metadata: { repo, count: plugins.length } };
|
|
205
144
|
} catch (err) {
|
|
206
145
|
log.error(`List plugins failed: ${err.message}`);
|
|
207
146
|
return { success: false, error: err.message };
|
|
208
147
|
}
|
|
209
148
|
}
|
|
210
149
|
|
|
211
|
-
/**
|
|
212
|
-
* 发布插件到远程仓库
|
|
213
|
-
*/
|
|
214
150
|
async _publishPlugin(pluginName, repo) {
|
|
215
151
|
const cwd = this._framework?.getCwd?.() ?? process.cwd();
|
|
216
152
|
const pluginsDir = path.resolve(cwd, '.foliko', 'plugins');
|
|
217
153
|
const localPluginsDir = path.resolve(cwd, 'plugins');
|
|
218
154
|
|
|
219
|
-
// 确定插件目录
|
|
220
155
|
let actualPluginsDir = pluginsDir;
|
|
221
156
|
if (!fs.existsSync(pluginsDir)) {
|
|
222
|
-
if (fs.existsSync(localPluginsDir)) {
|
|
223
|
-
|
|
224
|
-
} else {
|
|
225
|
-
return { success: false, error: `Plugins directory not found` };
|
|
226
|
-
}
|
|
157
|
+
if (fs.existsSync(localPluginsDir)) { actualPluginsDir = localPluginsDir; }
|
|
158
|
+
else { return { success: false, error: 'Plugins directory not found' }; }
|
|
227
159
|
}
|
|
228
160
|
|
|
229
|
-
const
|
|
161
|
+
const pluginPathFile = path.join(actualPluginsDir, `${pluginName}.js`);
|
|
230
162
|
const pluginSourceDir = path.join(actualPluginsDir, pluginName);
|
|
231
163
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return {
|
|
235
|
-
success: false,
|
|
236
|
-
error: `Plugin "${pluginName}" not found`,
|
|
237
|
-
available: fs.readdirSync(actualPluginsDir).filter(f => {
|
|
238
|
-
if (f.endsWith('.js')) return true;
|
|
239
|
-
const fullPath = path.join(actualPluginsDir, f);
|
|
240
|
-
return fs.statSync(fullPath).isDirectory();
|
|
241
|
-
}),
|
|
242
|
-
};
|
|
164
|
+
if (!fs.existsSync(pluginPathFile) && !fs.existsSync(pluginSourceDir)) {
|
|
165
|
+
return { success: false, error: `Plugin "${pluginName}" not found`, available: fs.readdirSync(actualPluginsDir).filter(f => f.endsWith('.js') || fs.statSync(path.join(actualPluginsDir, f)).isDirectory()) };
|
|
243
166
|
}
|
|
244
167
|
|
|
245
168
|
const tmpDir = path.join(require('os').tmpdir(), `foliko-plugin-publish-${Date.now()}`);
|
|
246
|
-
|
|
247
169
|
try {
|
|
248
|
-
//log.info(`Publishing plugin "${pluginName}" to ${repo}...`);
|
|
249
|
-
|
|
250
|
-
// 克隆仓库
|
|
251
170
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
252
171
|
|
|
253
172
|
let isNewRepo = false;
|
|
254
|
-
try {
|
|
255
|
-
|
|
256
|
-
} catch (err) {
|
|
257
|
-
//log.info('Initializing new repository...');
|
|
173
|
+
try { gitCommand(`clone ${repo} "${tmpDir}" --depth 1`, cwd); }
|
|
174
|
+
catch (err) {
|
|
258
175
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
259
176
|
gitCommand('init', tmpDir);
|
|
260
177
|
gitCommand(`remote add origin ${repo}`, tmpDir);
|
|
261
178
|
isNewRepo = true;
|
|
262
179
|
}
|
|
263
180
|
|
|
264
|
-
// 创建插件目录
|
|
265
181
|
const pluginDir = path.join(tmpDir, pluginName);
|
|
266
182
|
fs.mkdirSync(pluginDir, { recursive: true });
|
|
267
183
|
|
|
268
|
-
// 复制插件
|
|
269
184
|
let pluginContent = null;
|
|
270
185
|
if (fs.existsSync(pluginSourceDir) && fs.statSync(pluginSourceDir).isDirectory()) {
|
|
271
|
-
// log.info('Copying plugin directory...');
|
|
272
186
|
copyDirRecursive(pluginSourceDir, pluginDir);
|
|
273
|
-
|
|
274
187
|
const mainJsPath = path.join(pluginSourceDir, `${pluginName}.js`);
|
|
275
|
-
if (fs.existsSync(mainJsPath))
|
|
276
|
-
pluginContent = fs.readFileSync(mainJsPath, 'utf-8');
|
|
277
|
-
}
|
|
188
|
+
if (fs.existsSync(mainJsPath)) pluginContent = fs.readFileSync(mainJsPath, 'utf-8');
|
|
278
189
|
} else {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const targetPath = path.join(pluginDir, `${pluginName}.js`);
|
|
282
|
-
fs.writeFileSync(targetPath, pluginContent);
|
|
283
|
-
|
|
190
|
+
pluginContent = fs.readFileSync(pluginPathFile, 'utf-8');
|
|
191
|
+
fs.writeFileSync(path.join(pluginDir, `${pluginName}.js`), pluginContent);
|
|
284
192
|
const configPath = path.join(actualPluginsDir, `${pluginName}.json`);
|
|
285
|
-
if (fs.existsSync(configPath)) {
|
|
286
|
-
fs.copyFileSync(configPath, path.join(pluginDir, `${pluginName}.json`));
|
|
287
|
-
}
|
|
193
|
+
if (fs.existsSync(configPath)) fs.copyFileSync(configPath, path.join(pluginDir, `${pluginName}.json`));
|
|
288
194
|
}
|
|
289
195
|
|
|
290
|
-
// 创建 README.md
|
|
291
196
|
const readmePath = path.join(pluginDir, 'README.md');
|
|
292
197
|
if (!fs.existsSync(readmePath)) {
|
|
293
|
-
|
|
294
|
-
const descMatch = pluginContent?.match(/\*\*Description\*\*:\s*(.+)/) ||
|
|
295
|
-
pluginContent?.match(/description[:\s]+(.+)/i);
|
|
198
|
+
const descMatch = pluginContent?.match(/\*\*Description\*\*:\s*(.+)/) || pluginContent?.match(/description[:\s]+(.+)/i);
|
|
296
199
|
const desc = descMatch ? descMatch[1] : `Foliko plugin: ${pluginName}`;
|
|
297
200
|
fs.writeFileSync(readmePath, `# ${pluginName}\n\n${desc}\n`);
|
|
298
201
|
}
|
|
299
202
|
|
|
300
|
-
// Git 提交和推送
|
|
301
203
|
gitCommand('add .', tmpDir);
|
|
302
|
-
|
|
303
204
|
const status = gitCommand('status --porcelain', tmpDir);
|
|
304
|
-
if (!status.trim()) {
|
|
305
|
-
return {
|
|
306
|
-
success: true,
|
|
307
|
-
data: `No changes to commit for plugin "${pluginName}"`,
|
|
308
|
-
metadata: { repo },
|
|
309
|
-
};
|
|
310
|
-
}
|
|
205
|
+
if (!status.trim()) return { success: true, data: `No changes to commit for plugin "${pluginName}"`, metadata: { repo } };
|
|
311
206
|
|
|
312
207
|
gitCommand(`commit -m "Add/update plugin: ${pluginName}"`, tmpDir);
|
|
313
208
|
gitCommand(`push ${isNewRepo ? '-u' : ''} origin main`, tmpDir);
|
|
314
209
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
success: true,
|
|
319
|
-
data: `Plugin "${pluginName}" published successfully`,
|
|
320
|
-
metadata: { repo, path: `${pluginName}/${pluginName}.js` },
|
|
321
|
-
};
|
|
322
|
-
|
|
210
|
+
return { success: true, data: `Plugin "${pluginName}" published successfully`, metadata: { repo, path: `${pluginName}/${pluginName}.js` } };
|
|
323
211
|
} catch (err) {
|
|
324
212
|
log.error(`Publish failed: ${err.message}`);
|
|
325
213
|
return { success: false, error: err.message };
|
|
@@ -328,25 +216,17 @@ class PluginManagerPlugin extends Plugin {
|
|
|
328
216
|
}
|
|
329
217
|
}
|
|
330
218
|
|
|
331
|
-
/**
|
|
332
|
-
* 从远程仓库安装插件
|
|
333
|
-
*/
|
|
334
219
|
async _installPlugin(pluginName, repo) {
|
|
335
220
|
const cwd = this._framework?.getCwd?.() ?? process.cwd();
|
|
336
221
|
const localPluginsDir = path.resolve(cwd, '.foliko', 'plugins');
|
|
337
222
|
const tmpDir = path.join(require('os').tmpdir(), `foliko-plugin-install-${Date.now()}`);
|
|
338
223
|
|
|
339
224
|
try {
|
|
340
|
-
//log.info(`Installing plugin "${pluginName}" from ${repo}...`);
|
|
341
|
-
|
|
342
|
-
// 克隆仓库
|
|
343
225
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
344
226
|
gitCommand(`clone ${repo} "${tmpDir}" --depth 1`, cwd);
|
|
345
227
|
|
|
346
|
-
// 查找插件
|
|
347
228
|
const pluginDir = path.join(tmpDir, pluginName);
|
|
348
229
|
let sourcePath;
|
|
349
|
-
|
|
350
230
|
if (fs.existsSync(pluginDir) && fs.statSync(pluginDir).isDirectory()) {
|
|
351
231
|
sourcePath = path.join(pluginDir, `${pluginName}.js`);
|
|
352
232
|
} else {
|
|
@@ -354,44 +234,20 @@ class PluginManagerPlugin extends Plugin {
|
|
|
354
234
|
}
|
|
355
235
|
|
|
356
236
|
if (!fs.existsSync(sourcePath)) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const fullPath = path.join(tmpDir, f);
|
|
360
|
-
return fs.statSync(fullPath).isDirectory();
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
return {
|
|
364
|
-
success: false,
|
|
365
|
-
error: `Plugin "${pluginName}" not found in repository`,
|
|
366
|
-
available,
|
|
367
|
-
};
|
|
237
|
+
const available = fs.readdirSync(tmpDir).filter(f => fs.statSync(path.join(tmpDir, f)).isDirectory());
|
|
238
|
+
return { success: false, error: `Plugin "${pluginName}" not found in repository`, available };
|
|
368
239
|
}
|
|
369
240
|
|
|
370
|
-
|
|
371
|
-
if (!fs.existsSync(localPluginsDir)) {
|
|
372
|
-
fs.mkdirSync(localPluginsDir, { recursive: true });
|
|
373
|
-
}
|
|
241
|
+
if (!fs.existsSync(localPluginsDir)) fs.mkdirSync(localPluginsDir, { recursive: true });
|
|
374
242
|
|
|
375
|
-
// 复制插件到本地
|
|
376
243
|
const targetDir = path.join(localPluginsDir, pluginName);
|
|
377
244
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
378
|
-
|
|
379
|
-
// 复制目录内容
|
|
380
245
|
copyDirRecursive(path.dirname(sourcePath), targetDir);
|
|
381
|
-
|
|
382
|
-
// 如果是单文件插件,复制主文件
|
|
383
246
|
if (!fs.existsSync(path.join(targetDir, `${pluginName}.js`))) {
|
|
384
247
|
fs.copyFileSync(sourcePath, path.join(targetDir, `${pluginName}.js`));
|
|
385
248
|
}
|
|
386
249
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
return {
|
|
390
|
-
success: true,
|
|
391
|
-
data: `Plugin "${pluginName}" installed successfully`,
|
|
392
|
-
metadata: { path: targetDir },
|
|
393
|
-
};
|
|
394
|
-
|
|
250
|
+
return { success: true, data: `Plugin "${pluginName}" installed successfully`, metadata: { path: targetDir } };
|
|
395
251
|
} catch (err) {
|
|
396
252
|
log.error(`Install failed: ${err.message}`);
|
|
397
253
|
return { success: false, error: err.message };
|