@zshuangmu/agenthub 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 AgentHub
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ <div align="center">
2
+
3
+ # 🤖 AgentHub
4
+
5
+ **The Open Source Marketplace for AI Agents**
6
+
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
8
+ [![Node: >=18](https://img.shields.io/badge/Node.js->=18-green.svg)](https://nodejs.org/)
9
+ [![GitHub Stars](https://img.shields.io/github/stars/agenthub/agenthub?style=social)](https://github.com/agenthub/agenthub)
10
+
11
+ **Package and upload your Agent's full capabilities in one command.<br>Download and gain those powers with one click.**
12
+
13
+ 🌐 **Live Demo**: [https://agenthub.cyou](https://agenthub.cyou/)
14
+
15
+ [English](README.md) | [中文](README_CN.md)
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ ## 🎯 What is AgentHub?
22
+
23
+ AgentHub is an open-source platform for packaging and distributing AI Agents. It allows you to:
24
+
25
+ - **📦 Package** your Agent's personality, memory, and skills into a single bundle
26
+ - **🚀 Publish** to a local or remote registry with one command
27
+ - **🔍 Discover** and search agents from the marketplace
28
+ - **⚡ Install** agents to any workspace instantly
29
+
30
+ Perfect for teams who want to share AI capabilities across projects, or individuals who want to backup and version their agent configurations.
31
+
32
+ ## ✨ Features
33
+
34
+ | Feature | Description |
35
+ |---------|-------------|
36
+ | 📦 **One-Click Packaging** | Automatically scan workspace and package Agent capabilities |
37
+ | 🚀 **Local-First** | No cloud required, runs entirely on your machine |
38
+ | 🌐 **Web Interface** | Beautiful dark-themed UI with i18n support (EN/中文) |
39
+ | 🔐 **Memory Layers** | Three-tier memory: public, portable, private |
40
+ | 🔄 **Version Control** | Full version history with rollback support |
41
+ | 📊 **Analytics** | Built-in download tracking and statistics |
42
+
43
+ ## 📸 Screenshots
44
+
45
+ ![alt text](./docs/image.png)
46
+ ## 🚀 Quick Start
47
+
48
+ ### Installation
49
+
50
+ ```bash
51
+ # Clone the repository
52
+ git clone https://github.com/agenthub/agenthub.git
53
+ cd agenthub
54
+
55
+ # Install dependencies
56
+ npm install
57
+
58
+ # Link globally (optional)
59
+ npm link
60
+ ```
61
+
62
+ ### Basic Usage
63
+
64
+ ```bash
65
+ # 1. Pack your agent
66
+ agenthub pack --workspace ./my-agent --config openclaw.json
67
+
68
+ # 2. Publish to registry
69
+ agenthub publish ./bundles/my-agent.agent --registry ./.registry
70
+
71
+ # 3. Start web interface
72
+ agenthub serve --registry ./.registry --port 3000
73
+ ```
74
+
75
+ Visit http://localhost:3000 to see your agents!
76
+
77
+ ## 📖 Documentation
78
+
79
+ ### Commands
80
+
81
+ | Command | Description |
82
+ |---------|-------------|
83
+ | `pack` | Pack workspace into Agent Bundle |
84
+ | `publish` | Publish to local registry |
85
+ | `publish-remote` | Publish to remote server |
86
+ | `search` | Search agents in registry |
87
+ | `info` | View agent details |
88
+ | `install` | Install agent to workspace |
89
+ | `serve` | Start web + API service |
90
+ | `api` | Start API server only |
91
+ | `web` | Start web frontend only |
92
+
93
+ ### HTTP API
94
+
95
+ ```bash
96
+ # List all agents
97
+ curl http://localhost:3001/api/agents
98
+
99
+ # Search agents
100
+ curl "http://localhost:3001/api/agents?q=react"
101
+
102
+ # Get agent details
103
+ curl http://localhost:3001/api/agents/my-agent
104
+
105
+ # Get statistics
106
+ curl http://localhost:3001/api/stats
107
+ ```
108
+
109
+ ### AI Auto-Discovery
110
+
111
+ Let your AI assistant automatically discover available agents:
112
+
113
+ ```bash
114
+ curl http://localhost:3001/api/skills/agenthub-discover
115
+ ```
116
+
117
+ ## 🏗️ Architecture
118
+
119
+ ```
120
+ ┌─────────────────┐ pack ┌─────────────────┐ publish ┌─────────────────┐
121
+ │ OpenClaw │ ──────────► │ Bundle │ ──────────► │ Registry │
122
+ │ Workspace │ │ (*.agent) │ │ (.registry) │
123
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
124
+
125
+ ┌───────────────────────────────┘
126
+
127
+
128
+ ┌─────────────────┐
129
+ │ AgentHub │
130
+ │ Web + API │
131
+ └─────────────────┘
132
+ ```
133
+
134
+ ## 🧪 Development
135
+
136
+ ```bash
137
+ # Run tests
138
+ npm test
139
+
140
+ # Start development server
141
+ node src/cli.js serve --registry ./.registry
142
+ ```
143
+
144
+ ## 🐳 Docker Deployment
145
+
146
+ ```bash
147
+ # Production start (in container)
148
+ NODE_ENV=production node src/cli.js serve --registry ./.registry --port 3000 --host 0.0.0.0
149
+ ```
150
+
151
+ ## 🤝 Contributing
152
+
153
+ We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
154
+
155
+ - 🐛 [Report a Bug](https://github.com/agenthub/agenthub/issues/new?template=bug_report.md)
156
+ - 💡 [Request a Feature](https://github.com/agenthub/agenthub/issues/new?template=feature_request.md)
157
+ - 🔧 [Submit a Pull Request](https://github.com/agenthub/agenthub/pulls)
158
+
159
+ ## 📄 License
160
+
161
+ [MIT License](LICENSE) © AgentHub Team
162
+
163
+ ## 🙏 Acknowledgments
164
+
165
+ - Built for [OpenClaw](https://github.com/openclaw) ecosystem
166
+ - Inspired by npm and Docker Hub
167
+
168
+ ---
169
+
170
+ <div align="center">
171
+
172
+ **[⬆ Back to Top](#agenthub)**
173
+
174
+ Made with ❤️ by the AgentHub Team
175
+
176
+ </div>
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@zshuangmu/agenthub",
3
+ "version": "0.1.0",
4
+ "description": "AI Agent 打包与分发平台 - 一句话打包上传,一键下载获得能力",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "agenthub": "src/cli.js"
9
+ },
10
+ "files": [
11
+ "src/"
12
+ ],
13
+ "scripts": {
14
+ "test": "NODE_OPTIONS='' node --test",
15
+ "start": "node src/cli.js serve --registry ./.registry --port 3000 --host 0.0.0.0"
16
+ },
17
+ "keywords": [
18
+ "ai",
19
+ "agent",
20
+ "openclaw",
21
+ "package-manager",
22
+ "registry",
23
+ "cli"
24
+ ],
25
+ "author": "AgentHub Team",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/itshaungmu/AgentHub.git"
30
+ },
31
+ "bugs": {
32
+ "url": "https://github.com/itshaungmu/AgentHub/issues"
33
+ },
34
+ "homepage": "https://github.com/itshaungmu/AgentHub#readme",
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ },
38
+ "dependencies": {
39
+ "sql.js": "^1.14.1"
40
+ }
41
+ }
@@ -0,0 +1,195 @@
1
+ import http from "node:http";
2
+ import path from "node:path";
3
+ import { readFile } from "node:fs/promises";
4
+ import { infoCommand, installCommand, publishCommand, searchCommand } from "./index.js";
5
+ import { publishUploadedBundle } from "./lib/bundle-transfer.js";
6
+ import { notFound, readJsonBody, sendJson } from "./lib/http.js";
7
+ import {
8
+ initDatabase,
9
+ incrementDownloads,
10
+ getAgentDownloads,
11
+ getAgentsDownloads,
12
+ getTotalDownloads,
13
+ getDownloadRanking,
14
+ getRecentDownloads,
15
+ getDatabaseStats
16
+ } from "./lib/database.js";
17
+
18
+ // 安全配置
19
+ const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(",") || ["*"];
20
+ const MAX_UPLOAD_SIZE = parseInt(process.env.MAX_UPLOAD_SIZE || "10485760", 10); // 10MB default
21
+ const RATE_LIMIT_WINDOW = 60000; // 1 minute
22
+ const RATE_LIMIT_MAX = parseInt(process.env.RATE_LIMIT_MAX || "100", 10); // 100 requests per minute
23
+
24
+ // 简单的速率限制器
25
+ const rateLimiter = new Map();
26
+
27
+ function checkRateLimit(ip) {
28
+ const now = Date.now();
29
+ const windowStart = now - RATE_LIMIT_WINDOW;
30
+ const requests = rateLimiter.get(ip) || [];
31
+ const recentRequests = requests.filter(t => t > windowStart);
32
+ if (recentRequests.length >= RATE_LIMIT_MAX) {
33
+ return false;
34
+ }
35
+ recentRequests.push(now);
36
+ rateLimiter.set(ip, recentRequests);
37
+ return true;
38
+ }
39
+
40
+ // slug 格式验证
41
+ function isValidSlug(slug) {
42
+ return /^[a-z0-9-]+$/.test(slug) && slug.length <= 100;
43
+ }
44
+
45
+ export async function createApiServer({ registryDir, port = 3000 }) {
46
+ // 初始化数据库
47
+ await initDatabase(registryDir);
48
+
49
+ const server = http.createServer(async (request, response) => {
50
+ const origin = request.headers.origin || "*";
51
+ const allowedOrigin = ALLOWED_ORIGINS.includes("*") || ALLOWED_ORIGINS.includes(origin)
52
+ ? (ALLOWED_ORIGINS.includes("*") ? "*" : origin)
53
+ : ALLOWED_ORIGINS[0];
54
+
55
+ // CORS 头
56
+ const corsHeaders = {
57
+ "Access-Control-Allow-Origin": allowedOrigin,
58
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
59
+ "Access-Control-Allow-Headers": "Content-Type",
60
+ "X-Content-Type-Options": "nosniff",
61
+ "X-Frame-Options": "DENY"
62
+ };
63
+
64
+ // 速率限制检查
65
+ const clientIp = request.socket.remoteAddress;
66
+ if (!checkRateLimit(clientIp)) {
67
+ response.writeHead(429, { "Content-Type": "application/json", ...corsHeaders });
68
+ response.end(JSON.stringify({ error: "Too many requests" }));
69
+ return;
70
+ }
71
+
72
+ // 处理预检请求
73
+ if (request.method === "OPTIONS") {
74
+ response.writeHead(204, corsHeaders);
75
+ response.end();
76
+ return;
77
+ }
78
+
79
+ try {
80
+ const url = new URL(request.url, "http://127.0.0.1");
81
+
82
+ // API: 获取 AgentHub Discover Skill
83
+ if (url.pathname === "/api/skills/agenthub-discover") {
84
+ try {
85
+ const skillPath = path.join(process.cwd(), "skills", "agenthub-discover", "SKILL.md");
86
+ const content = await readFile(skillPath, "utf8");
87
+ response.writeHead(200, {
88
+ "Content-Type": "text/markdown; charset=utf-8",
89
+ ...corsHeaders
90
+ });
91
+ response.end(content);
92
+ } catch {
93
+ response.writeHead(404, { "Content-Type": "application/json", ...corsHeaders });
94
+ response.end(JSON.stringify({ error: "Skill not found" }));
95
+ }
96
+ return;
97
+ }
98
+
99
+ // API: 获取 Agent 列表
100
+ if (url.pathname === "/api/agents") {
101
+ const agents = await searchCommand(url.searchParams.get("q") ?? "", { registry: registryDir });
102
+ const slugs = agents.map(a => a.slug);
103
+ const downloads = await getAgentsDownloads(registryDir, slugs);
104
+ const agentsWithDownloads = agents.map(a => ({ ...a, downloads: downloads[a.slug] || 0 }));
105
+ sendJson(response, 200, { agents: agentsWithDownloads }, corsHeaders);
106
+ return;
107
+ }
108
+
109
+ // API: 获取单个 Agent 详情
110
+ if (url.pathname.startsWith("/api/agents/")) {
111
+ const slug = url.pathname.slice("/api/agents/".length);
112
+ // 安全检查:验证 slug 格式
113
+ if (!isValidSlug(slug)) {
114
+ sendJson(response, 400, { error: "Invalid slug format" }, corsHeaders);
115
+ return;
116
+ }
117
+ const manifest = await infoCommand(slug, { registry: registryDir });
118
+ const downloads = await getAgentDownloads(registryDir, slug);
119
+ sendJson(response, 200, { ...manifest, downloads }, corsHeaders);
120
+ return;
121
+ }
122
+
123
+ // API: 发布 Agent
124
+ if (url.pathname === "/api/publish" && request.method === "POST") {
125
+ const body = await readJsonBody(request);
126
+ const manifest = await publishCommand(body.bundleDir, { registry: registryDir });
127
+ sendJson(response, 200, manifest, corsHeaders);
128
+ return;
129
+ }
130
+
131
+ // API: 上传发布 Agent
132
+ if (url.pathname === "/api/publish-upload" && request.method === "POST") {
133
+ const body = await readJsonBody(request);
134
+ const manifest = await publishUploadedBundle({ payload: body, registryDir });
135
+ sendJson(response, 200, manifest, corsHeaders);
136
+ return;
137
+ }
138
+
139
+ // API: 安装 Agent(记录下载)
140
+ if (url.pathname === "/api/install" && request.method === "POST") {
141
+ const body = await readJsonBody(request);
142
+ const result = await installCommand(body.agent, {
143
+ registry: registryDir,
144
+ targetWorkspace: body.targetWorkspace,
145
+ });
146
+ const slug = body.agent.split(":")[0];
147
+ await incrementDownloads(registryDir, slug, {
148
+ targetWorkspace: body.targetWorkspace,
149
+ ip: request.socket.remoteAddress,
150
+ userAgent: request.headers['user-agent']
151
+ });
152
+ sendJson(response, 200, result, corsHeaders);
153
+ return;
154
+ }
155
+
156
+ // API: 获取下载统计
157
+ if (url.pathname === "/api/stats") {
158
+ const stats = await getDatabaseStats(registryDir);
159
+ const ranking = await getDownloadRanking(registryDir, 10);
160
+ const recent = await getRecentDownloads(registryDir, 20);
161
+ sendJson(response, 200, { stats, ranking, recent }, corsHeaders);
162
+ return;
163
+ }
164
+
165
+ // API: 获取下载排行
166
+ if (url.pathname === "/api/stats/ranking") {
167
+ const limit = parseInt(url.searchParams.get("limit") || "10", 10);
168
+ const ranking = await getDownloadRanking(registryDir, limit);
169
+ sendJson(response, 200, { ranking }, corsHeaders);
170
+ return;
171
+ }
172
+
173
+ // API: 健康检查
174
+ if (url.pathname === "/api/health") {
175
+ sendJson(response, 200, { status: "ok", timestamp: new Date().toISOString() }, corsHeaders);
176
+ return;
177
+ }
178
+
179
+ notFound(response, corsHeaders);
180
+ } catch (error) {
181
+ sendJson(response, 500, { error: error.message }, corsHeaders);
182
+ }
183
+ });
184
+
185
+ await new Promise((resolve) => server.listen(port, "0.0.0.0", resolve));
186
+ const address = server.address();
187
+ const actualPort = typeof address === "object" && address ? address.port : port;
188
+
189
+ return {
190
+ server,
191
+ port: actualPort,
192
+ baseUrl: `http://127.0.0.1:${actualPort}`,
193
+ close: () => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))),
194
+ };
195
+ }