@tronsfey/ucli-server 0.5.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/README.md +423 -0
- package/README.zh.md +404 -0
- package/assets/logo.svg +138 -0
- package/dist/admin-ui/assets/index-BjdBylCY.css +1 -0
- package/dist/admin-ui/assets/index-CDwKIMTJ.js +117 -0
- package/dist/admin-ui/assets/remixicon-B25hvfAs.eot +0 -0
- package/dist/admin-ui/assets/remixicon-BTtOSOPh.svg +9709 -0
- package/dist/admin-ui/assets/remixicon-CZw4FkzQ.woff2 +0 -0
- package/dist/admin-ui/assets/remixicon-S6an_USy.woff +0 -0
- package/dist/admin-ui/assets/remixicon-sqouR8Ox.ttf +0 -0
- package/dist/admin-ui/dist/assets/index-BjdBylCY.css +1 -0
- package/dist/admin-ui/dist/assets/index-CDwKIMTJ.js +117 -0
- package/dist/admin-ui/dist/assets/index-CppEl63e.css +1 -0
- package/dist/admin-ui/dist/assets/index-DBkeGfMQ.js +117 -0
- package/dist/admin-ui/dist/assets/index-ss5EmsBH.js +117 -0
- package/dist/admin-ui/dist/assets/remixicon-B25hvfAs.eot +0 -0
- package/dist/admin-ui/dist/assets/remixicon-BTtOSOPh.svg +9709 -0
- package/dist/admin-ui/dist/assets/remixicon-CZw4FkzQ.woff2 +0 -0
- package/dist/admin-ui/dist/assets/remixicon-S6an_USy.woff +0 -0
- package/dist/admin-ui/dist/assets/remixicon-sqouR8Ox.ttf +0 -0
- package/dist/admin-ui/dist/index.html +14 -0
- package/dist/admin-ui/index.html +14 -0
- package/dist/app.module.d.ts +3 -0
- package/dist/app.module.d.ts.map +1 -0
- package/dist/app.module.js +65 -0
- package/dist/app.module.js.map +1 -0
- package/dist/auth/admin.guard.d.ts +8 -0
- package/dist/auth/admin.guard.d.ts.map +1 -0
- package/dist/auth/admin.guard.js +34 -0
- package/dist/auth/admin.guard.js.map +1 -0
- package/dist/auth/auth.module.d.ts +3 -0
- package/dist/auth/auth.module.d.ts.map +1 -0
- package/dist/auth/auth.module.js +24 -0
- package/dist/auth/auth.module.js.map +1 -0
- package/dist/auth/decorators/jwt-payload.decorator.d.ts +2 -0
- package/dist/auth/decorators/jwt-payload.decorator.d.ts.map +1 -0
- package/dist/auth/decorators/jwt-payload.decorator.js +10 -0
- package/dist/auth/decorators/jwt-payload.decorator.js.map +1 -0
- package/dist/auth/group-token.guard.d.ts +11 -0
- package/dist/auth/group-token.guard.d.ts.map +1 -0
- package/dist/auth/group-token.guard.js +53 -0
- package/dist/auth/group-token.guard.js.map +1 -0
- package/dist/cache/cache.interface.d.ts +8 -0
- package/dist/cache/cache.interface.d.ts.map +1 -0
- package/dist/cache/cache.interface.js +3 -0
- package/dist/cache/cache.interface.js.map +1 -0
- package/dist/cache/cache.module.d.ts +5 -0
- package/dist/cache/cache.module.d.ts.map +1 -0
- package/dist/cache/cache.module.js +56 -0
- package/dist/cache/cache.module.js.map +1 -0
- package/dist/cache/cache.token.d.ts +2 -0
- package/dist/cache/cache.token.d.ts.map +1 -0
- package/dist/cache/cache.token.js +5 -0
- package/dist/cache/cache.token.js.map +1 -0
- package/dist/cache/memory/memory-cache.adapter.d.ts +15 -0
- package/dist/cache/memory/memory-cache.adapter.d.ts.map +1 -0
- package/dist/cache/memory/memory-cache.adapter.js +52 -0
- package/dist/cache/memory/memory-cache.adapter.js.map +1 -0
- package/dist/cache/redis/redis-cache.adapter.d.ts +16 -0
- package/dist/cache/redis/redis-cache.adapter.d.ts.map +1 -0
- package/dist/cache/redis/redis-cache.adapter.js +63 -0
- package/dist/cache/redis/redis-cache.adapter.js.map +1 -0
- package/dist/config/app-config.module.d.ts +3 -0
- package/dist/config/app-config.module.d.ts.map +1 -0
- package/dist/config/app-config.module.js +100 -0
- package/dist/config/app-config.module.js.map +1 -0
- package/dist/config/app-config.service.d.ts +29 -0
- package/dist/config/app-config.service.d.ts.map +1 -0
- package/dist/config/app-config.service.js +66 -0
- package/dist/config/app-config.service.js.map +1 -0
- package/dist/crypto/crypto.module.d.ts +3 -0
- package/dist/crypto/crypto.module.d.ts.map +1 -0
- package/dist/crypto/crypto.module.js +22 -0
- package/dist/crypto/crypto.module.js.map +1 -0
- package/dist/crypto/encryption.service.d.ts +9 -0
- package/dist/crypto/encryption.service.d.ts.map +1 -0
- package/dist/crypto/encryption.service.js +59 -0
- package/dist/crypto/encryption.service.js.map +1 -0
- package/dist/crypto/jwt.service.d.ts +28 -0
- package/dist/crypto/jwt.service.d.ts.map +1 -0
- package/dist/crypto/jwt.service.js +72 -0
- package/dist/crypto/jwt.service.js.map +1 -0
- package/dist/groups/dto/create-group.dto.d.ts +5 -0
- package/dist/groups/dto/create-group.dto.d.ts.map +1 -0
- package/dist/groups/dto/create-group.dto.js +34 -0
- package/dist/groups/dto/create-group.dto.js.map +1 -0
- package/dist/groups/groups.controller.d.ts +9 -0
- package/dist/groups/groups.controller.d.ts.map +1 -0
- package/dist/groups/groups.controller.js +60 -0
- package/dist/groups/groups.controller.js.map +1 -0
- package/dist/groups/groups.module.d.ts +3 -0
- package/dist/groups/groups.module.d.ts.map +1 -0
- package/dist/groups/groups.module.js +25 -0
- package/dist/groups/groups.module.js.map +1 -0
- package/dist/groups/groups.service.d.ts +9 -0
- package/dist/groups/groups.service.d.ts.map +1 -0
- package/dist/groups/groups.service.js +45 -0
- package/dist/groups/groups.service.js.map +1 -0
- package/dist/health/health.controller.d.ts +13 -0
- package/dist/health/health.controller.d.ts.map +1 -0
- package/dist/health/health.controller.js +68 -0
- package/dist/health/health.controller.js.map +1 -0
- package/dist/health/health.module.d.ts +3 -0
- package/dist/health/health.module.d.ts.map +1 -0
- package/dist/health/health.module.js +22 -0
- package/dist/health/health.module.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +109 -0
- package/dist/main.js.map +1 -0
- package/dist/mcp/admin-mcp.controller.d.ts +13 -0
- package/dist/mcp/admin-mcp.controller.d.ts.map +1 -0
- package/dist/mcp/admin-mcp.controller.js +105 -0
- package/dist/mcp/admin-mcp.controller.js.map +1 -0
- package/dist/mcp/client-mcp.controller.d.ts +9 -0
- package/dist/mcp/client-mcp.controller.d.ts.map +1 -0
- package/dist/mcp/client-mcp.controller.js +63 -0
- package/dist/mcp/client-mcp.controller.js.map +1 -0
- package/dist/mcp/dto/create-mcp.dto.d.ts +11 -0
- package/dist/mcp/dto/create-mcp.dto.d.ts.map +1 -0
- package/dist/mcp/dto/create-mcp.dto.js +74 -0
- package/dist/mcp/dto/create-mcp.dto.js.map +1 -0
- package/dist/mcp/dto/update-mcp.dto.d.ts +11 -0
- package/dist/mcp/dto/update-mcp.dto.d.ts.map +1 -0
- package/dist/mcp/dto/update-mcp.dto.js +71 -0
- package/dist/mcp/dto/update-mcp.dto.js.map +1 -0
- package/dist/mcp/mcp.module.d.ts +3 -0
- package/dist/mcp/mcp.module.d.ts.map +1 -0
- package/dist/mcp/mcp.module.js +26 -0
- package/dist/mcp/mcp.module.js.map +1 -0
- package/dist/mcp/mcp.service.d.ts +16 -0
- package/dist/mcp/mcp.service.d.ts.map +1 -0
- package/dist/mcp/mcp.service.js +78 -0
- package/dist/mcp/mcp.service.js.map +1 -0
- package/dist/metrics/metrics.controller.d.ts +12 -0
- package/dist/metrics/metrics.controller.d.ts.map +1 -0
- package/dist/metrics/metrics.controller.js +71 -0
- package/dist/metrics/metrics.controller.js.map +1 -0
- package/dist/metrics/metrics.module.d.ts +3 -0
- package/dist/metrics/metrics.module.d.ts.map +1 -0
- package/dist/metrics/metrics.module.js +20 -0
- package/dist/metrics/metrics.module.js.map +1 -0
- package/dist/oas/admin-oas.controller.d.ts +12 -0
- package/dist/oas/admin-oas.controller.d.ts.map +1 -0
- package/dist/oas/admin-oas.controller.js +96 -0
- package/dist/oas/admin-oas.controller.js.map +1 -0
- package/dist/oas/client-oas.controller.d.ts +9 -0
- package/dist/oas/client-oas.controller.d.ts.map +1 -0
- package/dist/oas/client-oas.controller.js +63 -0
- package/dist/oas/client-oas.controller.js.map +1 -0
- package/dist/oas/dto/create-oas.dto.d.ts +12 -0
- package/dist/oas/dto/create-oas.dto.d.ts.map +1 -0
- package/dist/oas/dto/create-oas.dto.js +82 -0
- package/dist/oas/dto/create-oas.dto.js.map +1 -0
- package/dist/oas/dto/update-oas.dto.d.ts +12 -0
- package/dist/oas/dto/update-oas.dto.d.ts.map +1 -0
- package/dist/oas/dto/update-oas.dto.js +77 -0
- package/dist/oas/dto/update-oas.dto.js.map +1 -0
- package/dist/oas/oas.module.d.ts +3 -0
- package/dist/oas/oas.module.d.ts.map +1 -0
- package/dist/oas/oas.module.js +26 -0
- package/dist/oas/oas.module.js.map +1 -0
- package/dist/oas/oas.service.d.ts +16 -0
- package/dist/oas/oas.service.d.ts.map +1 -0
- package/dist/oas/oas.service.js +79 -0
- package/dist/oas/oas.service.js.map +1 -0
- package/dist/otel/otel.d.ts +25 -0
- package/dist/otel/otel.d.ts.map +1 -0
- package/dist/otel/otel.js +61 -0
- package/dist/otel/otel.js.map +1 -0
- package/dist/storage/interfaces/repos.interface.d.ts +158 -0
- package/dist/storage/interfaces/repos.interface.d.ts.map +1 -0
- package/dist/storage/interfaces/repos.interface.js +4 -0
- package/dist/storage/interfaces/repos.interface.js.map +1 -0
- package/dist/storage/memory/memory-group.repo.d.ts +9 -0
- package/dist/storage/memory/memory-group.repo.d.ts.map +1 -0
- package/dist/storage/memory/memory-group.repo.js +33 -0
- package/dist/storage/memory/memory-group.repo.js.map +1 -0
- package/dist/storage/memory/memory-mcp.repo.d.ts +13 -0
- package/dist/storage/memory/memory-mcp.repo.d.ts.map +1 -0
- package/dist/storage/memory/memory-mcp.repo.js +65 -0
- package/dist/storage/memory/memory-mcp.repo.js.map +1 -0
- package/dist/storage/memory/memory-oas.repo.d.ts +13 -0
- package/dist/storage/memory/memory-oas.repo.d.ts.map +1 -0
- package/dist/storage/memory/memory-oas.repo.js +55 -0
- package/dist/storage/memory/memory-oas.repo.js.map +1 -0
- package/dist/storage/memory/memory-token.repo.d.ts +11 -0
- package/dist/storage/memory/memory-token.repo.d.ts.map +1 -0
- package/dist/storage/memory/memory-token.repo.js +39 -0
- package/dist/storage/memory/memory-token.repo.js.map +1 -0
- package/dist/storage/storage.module.d.ts +5 -0
- package/dist/storage/storage.module.d.ts.map +1 -0
- package/dist/storage/storage.module.js +84 -0
- package/dist/storage/storage.module.js.map +1 -0
- package/dist/storage/storage.tokens.d.ts +5 -0
- package/dist/storage/storage.tokens.d.ts.map +1 -0
- package/dist/storage/storage.tokens.js +8 -0
- package/dist/storage/storage.tokens.js.map +1 -0
- package/dist/storage/typeorm/entities/group.entity.d.ts +12 -0
- package/dist/storage/typeorm/entities/group.entity.d.ts.map +1 -0
- package/dist/storage/typeorm/entities/group.entity.js +57 -0
- package/dist/storage/typeorm/entities/group.entity.js.map +1 -0
- package/dist/storage/typeorm/entities/mcp-entry.entity.d.ts +16 -0
- package/dist/storage/typeorm/entities/mcp-entry.entity.d.ts.map +1 -0
- package/dist/storage/typeorm/entities/mcp-entry.entity.js +82 -0
- package/dist/storage/typeorm/entities/mcp-entry.entity.js.map +1 -0
- package/dist/storage/typeorm/entities/oas-entry.entity.d.ts +17 -0
- package/dist/storage/typeorm/entities/oas-entry.entity.d.ts.map +1 -0
- package/dist/storage/typeorm/entities/oas-entry.entity.js +87 -0
- package/dist/storage/typeorm/entities/oas-entry.entity.js.map +1 -0
- package/dist/storage/typeorm/entities/token.entity.d.ts +13 -0
- package/dist/storage/typeorm/entities/token.entity.d.ts.map +1 -0
- package/dist/storage/typeorm/entities/token.entity.js +68 -0
- package/dist/storage/typeorm/entities/token.entity.js.map +1 -0
- package/dist/storage/typeorm/typeorm-group.repo.d.ts +12 -0
- package/dist/storage/typeorm/typeorm-group.repo.d.ts.map +1 -0
- package/dist/storage/typeorm/typeorm-group.repo.js +50 -0
- package/dist/storage/typeorm/typeorm-group.repo.js.map +1 -0
- package/dist/storage/typeorm/typeorm-mcp.repo.d.ts +15 -0
- package/dist/storage/typeorm/typeorm-mcp.repo.d.ts.map +1 -0
- package/dist/storage/typeorm/typeorm-mcp.repo.js +88 -0
- package/dist/storage/typeorm/typeorm-mcp.repo.js.map +1 -0
- package/dist/storage/typeorm/typeorm-oas.repo.d.ts +15 -0
- package/dist/storage/typeorm/typeorm-oas.repo.d.ts.map +1 -0
- package/dist/storage/typeorm/typeorm-oas.repo.js +92 -0
- package/dist/storage/typeorm/typeorm-oas.repo.js.map +1 -0
- package/dist/storage/typeorm/typeorm-token.repo.d.ts +13 -0
- package/dist/storage/typeorm/typeorm-token.repo.d.ts.map +1 -0
- package/dist/storage/typeorm/typeorm-token.repo.js +56 -0
- package/dist/storage/typeorm/typeorm-token.repo.js.map +1 -0
- package/dist/tokens/dto/issue-token.dto.d.ts +6 -0
- package/dist/tokens/dto/issue-token.dto.d.ts.map +1 -0
- package/dist/tokens/dto/issue-token.dto.js +41 -0
- package/dist/tokens/dto/issue-token.dto.js.map +1 -0
- package/dist/tokens/tokens.controller.d.ts +12 -0
- package/dist/tokens/tokens.controller.d.ts.map +1 -0
- package/dist/tokens/tokens.controller.js +89 -0
- package/dist/tokens/tokens.controller.js.map +1 -0
- package/dist/tokens/tokens.module.d.ts +3 -0
- package/dist/tokens/tokens.module.d.ts.map +1 -0
- package/dist/tokens/tokens.module.js +27 -0
- package/dist/tokens/tokens.module.js.map +1 -0
- package/dist/tokens/tokens.service.d.ts +25 -0
- package/dist/tokens/tokens.service.d.ts.map +1 -0
- package/dist/tokens/tokens.service.js +69 -0
- package/dist/tokens/tokens.service.js.map +1 -0
- package/package.json +81 -0
package/README.zh.md
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
<h1 align="center">ucli server</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://www.npmjs.com/package/@tronsfey/ucli-server"><img src="https://img.shields.io/npm/v/@tronsfey/ucli-server?color=7c3aed" alt="npm version"/></a>
|
|
5
|
+
<img src="https://img.shields.io/badge/NestJS-v11-e0234e" alt="NestJS"/>
|
|
6
|
+
<img src="https://img.shields.io/badge/node-%3E%3D18-38bdf8" alt="node"/>
|
|
7
|
+
<img src="https://img.shields.io/badge/license-MIT-22c55e" alt="license"/>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="./README.md">English</a> | 中文
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 概述
|
|
17
|
+
|
|
18
|
+
`@tronsfey/ucli-server` 是 ucli 的服务端组件,提供:
|
|
19
|
+
|
|
20
|
+
- **加密 OAS 存储** — OpenAPI 规范及认证配置以 AES-256-GCM 静态加密
|
|
21
|
+
- **加密 MCP 服务器存储** — MCP 服务器配置及认证信息(none / http_headers / env)以 AES-256-GCM 静态加密
|
|
22
|
+
- **群组级 JWT 签发** — RS256 签名令牌,控制客户端可访问的规范范围
|
|
23
|
+
- **令牌吊销** — 基于 JTI 的缓存黑名单机制
|
|
24
|
+
- **可插拔后端** — 通过环境变量切换存储(memory / PostgreSQL / MySQL)和缓存(memory / Redis)
|
|
25
|
+
- **可观测性** — Pino 结构化 JSON 日志、Prometheus 指标、健康/就绪探针
|
|
26
|
+
- **分布式追踪** — OpenTelemetry 自动埋点(HTTP、Express、PG、Redis),默认开启
|
|
27
|
+
|
|
28
|
+
## 架构图
|
|
29
|
+
|
|
30
|
+
```mermaid
|
|
31
|
+
graph TB
|
|
32
|
+
subgraph AppModule["AppModule(应用模块)"]
|
|
33
|
+
direction TB
|
|
34
|
+
Config["ConfigModule\n(Joi 校验环境变量)"]
|
|
35
|
+
Auth["AuthModule\nAdminGuard · GroupTokenGuard"]
|
|
36
|
+
Crypto["CryptoModule\nJwtService (RS256)\nEncryptionService (AES-256-GCM)"]
|
|
37
|
+
|
|
38
|
+
subgraph Storage["StorageModule.forRoot()(存储模块)"]
|
|
39
|
+
Mem1["MemoryGroupRepo\nMemoryTokenRepo\nMemoryOASRepo\nMemoryMCPRepo"]
|
|
40
|
+
DB1["TypeORM 仓库\n(postgres · mysql)"]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
subgraph Cache["CacheModule.forRoot()(缓存模块)"]
|
|
44
|
+
Mem2["MemoryCacheAdapter"]
|
|
45
|
+
Redis["RedisCacheAdapter\n(ioredis)"]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Groups["GroupsModule\nPOST/GET /admin/groups"]
|
|
49
|
+
Tokens["TokensModule\n令牌签发 + 吊销"]
|
|
50
|
+
OAS["OASModule\n管理端 CRUD + 客户端读取"]
|
|
51
|
+
MCP["MCPModule\n管理端 CRUD + 客户端读取"]
|
|
52
|
+
Health["HealthModule\n存活 · 就绪探针"]
|
|
53
|
+
Metrics["MetricsModule\nPrometheus 指标"]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
Config --> Auth
|
|
57
|
+
Config --> Storage
|
|
58
|
+
Config --> Cache
|
|
59
|
+
Crypto --> Tokens
|
|
60
|
+
Crypto --> OAS
|
|
61
|
+
Storage --> Groups
|
|
62
|
+
Storage --> Tokens
|
|
63
|
+
Storage --> OAS
|
|
64
|
+
Storage --> MCP
|
|
65
|
+
Cache --> Auth
|
|
66
|
+
Groups --> Tokens
|
|
67
|
+
Groups --> OAS
|
|
68
|
+
Crypto --> MCP
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 安装
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm install -g @tronsfey/ucli-server
|
|
75
|
+
# 或
|
|
76
|
+
pnpm add -g @tronsfey/ucli-server
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 快速开始(内存模式,无需 DB/Redis)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# 1. 生成 32 字节加密密钥
|
|
83
|
+
ENCRYPTION_KEY=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
|
|
84
|
+
|
|
85
|
+
# 2. 启动服务器
|
|
86
|
+
ADMIN_SECRET=my-secret ENCRYPTION_KEY=$ENCRYPTION_KEY ucli-server
|
|
87
|
+
|
|
88
|
+
# 服务启动于 http://localhost:3000
|
|
89
|
+
# Swagger UI: http://localhost:3000/api/docs
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 环境变量
|
|
93
|
+
|
|
94
|
+
| 变量 | 必填 | 默认值 | 说明 |
|
|
95
|
+
|------|------|--------|------|
|
|
96
|
+
| `ADMIN_SECRET` | **是** | — | `X-Admin-Secret` 请求头的值(≥ 8 字符) |
|
|
97
|
+
| `ENCRYPTION_KEY` | **是** | — | 64 位十六进制(32 字节),用于 AES-256-GCM |
|
|
98
|
+
| `PORT` | 否 | `3000` | HTTP 监听端口 |
|
|
99
|
+
| `HOST` | 否 | `0.0.0.0` | HTTP 监听主机 |
|
|
100
|
+
| `DB_TYPE` | 否 | `memory` | `memory` \| `postgres` \| `mysql` |
|
|
101
|
+
| `DATABASE_URL` | 使用 DB 时 | — | 数据库连接 URL |
|
|
102
|
+
| `CACHE_TYPE` | 否 | `memory` | `memory` \| `redis` |
|
|
103
|
+
| `REDIS_URL` | 使用 redis 时 | — | Redis 连接 URL |
|
|
104
|
+
| `JWT_PRIVATE_KEY` | 生产环境 | 自动生成 | Base64 编码的 PKCS8 PEM |
|
|
105
|
+
| `JWT_PUBLIC_KEY` | 生产环境 | 自动生成 | Base64 编码的 SPKI PEM |
|
|
106
|
+
| `JWT_DEFAULT_TTL` | 否 | `86400` | 令牌有效期(秒),`0` 表示永不过期 |
|
|
107
|
+
| `LOG_LEVEL` | 否 | `info` | `trace` \| `debug` \| `info` \| `warn` \| `error` \| `fatal` |
|
|
108
|
+
| `SWAGGER_ENABLED` | 否 | `true` | 设为 `false` 可在生产环境关闭 `/api/docs` |
|
|
109
|
+
|
|
110
|
+
## 存储后端
|
|
111
|
+
|
|
112
|
+
| `DB_TYPE` | 驱动 | 说明 |
|
|
113
|
+
|-----------|------|------|
|
|
114
|
+
| `memory` | — | 默认。无持久化,重启后数据丢失。 |
|
|
115
|
+
| `postgres` | `pg` | PostgreSQL 12+ |
|
|
116
|
+
| `mysql` | `mysql2` | MySQL 5.7+ / MariaDB 10.3+ |
|
|
117
|
+
|
|
118
|
+
首次运行时自动建表。
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# PostgreSQL
|
|
122
|
+
DB_TYPE=postgres \
|
|
123
|
+
DATABASE_URL=postgresql://user:pass@host:5432/oas_gateway \
|
|
124
|
+
ADMIN_SECRET=secret ENCRYPTION_KEY=<64位hex> ucli-server
|
|
125
|
+
|
|
126
|
+
# MySQL
|
|
127
|
+
DB_TYPE=mysql \
|
|
128
|
+
DATABASE_URL=mysql://user:pass@host:3306/oas_gateway \
|
|
129
|
+
ADMIN_SECRET=secret ENCRYPTION_KEY=<64位hex> ucli-server
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 缓存后端
|
|
133
|
+
|
|
134
|
+
| `CACHE_TYPE` | 说明 |
|
|
135
|
+
|--------------|------|
|
|
136
|
+
| `memory` | 默认。进程内 TTL 缓存,重启后失效。 |
|
|
137
|
+
| `redis` | Redis 6+(ioredis),支持多实例共享。 |
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
CACHE_TYPE=redis REDIS_URL=redis://:password@host:6379 \
|
|
141
|
+
ADMIN_SECRET=secret ENCRYPTION_KEY=<64位hex> ucli-server
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 生产部署
|
|
145
|
+
|
|
146
|
+
生成持久化 RS256 密钥对,使令牌在重启后仍然有效:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
node -e "
|
|
150
|
+
const { generateKeyPairSync } = require('crypto');
|
|
151
|
+
const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
|
|
152
|
+
console.log('JWT_PRIVATE_KEY=' + Buffer.from(privateKey.export({ type:'pkcs8', format:'pem' })).toString('base64'));
|
|
153
|
+
console.log('JWT_PUBLIC_KEY=' + Buffer.from(publicKey.export({ type:'spki', format:'pem' })).toString('base64'));
|
|
154
|
+
"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
使用仓库根目录的 `docker-compose.yml` 启动 PostgreSQL + Redis:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
docker-compose up -d
|
|
161
|
+
DB_TYPE=postgres CACHE_TYPE=redis \
|
|
162
|
+
DATABASE_URL=postgresql://oas_gateway:changeme@localhost:5432/oas_gateway \
|
|
163
|
+
REDIS_URL=redis://:changeme@localhost:6379 \
|
|
164
|
+
JWT_PRIVATE_KEY=<base64-pem> JWT_PUBLIC_KEY=<base64-pem> \
|
|
165
|
+
ADMIN_SECRET=<强密码> ENCRYPTION_KEY=<64位hex> \
|
|
166
|
+
ucli-server
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 管理 API 参考
|
|
170
|
+
|
|
171
|
+
所有管理端点均需要 `X-Admin-Secret: <ADMIN_SECRET>` 请求头。
|
|
172
|
+
|
|
173
|
+
### 群组管理
|
|
174
|
+
|
|
175
|
+
| 方法 | 路径 | 说明 |
|
|
176
|
+
|------|------|------|
|
|
177
|
+
| `POST` | `/admin/groups` | 创建群组 |
|
|
178
|
+
| `GET` | `/admin/groups` | 列出所有群组 |
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# 创建群组
|
|
182
|
+
curl -X POST http://localhost:3000/admin/groups \
|
|
183
|
+
-H "X-Admin-Secret: my-secret" \
|
|
184
|
+
-H "Content-Type: application/json" \
|
|
185
|
+
-d '{"name":"production","description":"生产环境智能体群组"}'
|
|
186
|
+
# → { "id": "uuid", "name": "production", "description": "..." }
|
|
187
|
+
|
|
188
|
+
# 列出群组
|
|
189
|
+
curl http://localhost:3000/admin/groups \
|
|
190
|
+
-H "X-Admin-Secret: my-secret"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 令牌管理
|
|
194
|
+
|
|
195
|
+
| 方法 | 路径 | 说明 |
|
|
196
|
+
|------|------|------|
|
|
197
|
+
| `POST` | `/admin/groups/:id/tokens` | 为群组签发 JWT |
|
|
198
|
+
| `DELETE` | `/admin/tokens/:id` | 吊销令牌 |
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# 签发令牌(返回的 JWT 仅显示一次,请妥善保存!)
|
|
202
|
+
curl -X POST http://localhost:3000/admin/groups/<group-id>/tokens \
|
|
203
|
+
-H "X-Admin-Secret: my-secret" \
|
|
204
|
+
-H "Content-Type: application/json" \
|
|
205
|
+
-d '{"name":"agent-token","ttlSec":86400}'
|
|
206
|
+
# → { "id": "jti-uuid", "token": "eyJ..." }
|
|
207
|
+
|
|
208
|
+
# 吊销令牌
|
|
209
|
+
curl -X DELETE http://localhost:3000/admin/tokens/<jti-uuid> \
|
|
210
|
+
-H "X-Admin-Secret: my-secret"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### OAS 条目管理
|
|
214
|
+
|
|
215
|
+
| 方法 | 路径 | 说明 |
|
|
216
|
+
|------|------|------|
|
|
217
|
+
| `POST` | `/admin/oas` | 注册 OAS 条目 |
|
|
218
|
+
| `GET` | `/admin/oas` | 列出所有 OAS 条目 |
|
|
219
|
+
| `PUT` | `/admin/oas/:id` | 更新 OAS 条目 |
|
|
220
|
+
| `DELETE` | `/admin/oas/:id` | 删除 OAS 条目 |
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# 注册 OAS 条目
|
|
224
|
+
curl -X POST http://localhost:3000/admin/oas \
|
|
225
|
+
-H "X-Admin-Secret: my-secret" \
|
|
226
|
+
-H "Content-Type: application/json" \
|
|
227
|
+
-d '{
|
|
228
|
+
"groupId": "<group-id>",
|
|
229
|
+
"name": "payments",
|
|
230
|
+
"remoteUrl": "https://api.example.com/openapi.json",
|
|
231
|
+
"authType": "bearer",
|
|
232
|
+
"authConfig": {"type":"bearer","token":"<api-token>"},
|
|
233
|
+
"cacheTtl": 3600
|
|
234
|
+
}'
|
|
235
|
+
|
|
236
|
+
# 列出所有条目
|
|
237
|
+
curl http://localhost:3000/admin/oas \
|
|
238
|
+
-H "X-Admin-Secret: my-secret"
|
|
239
|
+
|
|
240
|
+
# 更新条目
|
|
241
|
+
curl -X PUT http://localhost:3000/admin/oas/<oas-id> \
|
|
242
|
+
-H "X-Admin-Secret: my-secret" \
|
|
243
|
+
-H "Content-Type: application/json" \
|
|
244
|
+
-d '{"cacheTtl": 7200}'
|
|
245
|
+
|
|
246
|
+
# 删除条目
|
|
247
|
+
curl -X DELETE http://localhost:3000/admin/oas/<oas-id> \
|
|
248
|
+
-H "X-Admin-Secret: my-secret"
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### MCP 服务器管理
|
|
252
|
+
|
|
253
|
+
| 方法 | 路径 | 说明 |
|
|
254
|
+
|------|------|------|
|
|
255
|
+
| `POST` | `/admin/mcp` | 注册 MCP 服务器 |
|
|
256
|
+
| `GET` | `/admin/mcp` | 列出所有 MCP 服务器 |
|
|
257
|
+
| `GET` | `/admin/mcp/:id` | 获取单个 MCP 服务器 |
|
|
258
|
+
| `PUT` | `/admin/mcp/:id` | 更新 MCP 服务器 |
|
|
259
|
+
| `DELETE` | `/admin/mcp/:id` | 删除 MCP 服务器 |
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
# 注册(http transport + 无认证)
|
|
263
|
+
curl -X POST http://localhost:3000/admin/mcp \
|
|
264
|
+
-H "X-Admin-Secret: my-secret" \
|
|
265
|
+
-H "Content-Type: application/json" \
|
|
266
|
+
-d '{
|
|
267
|
+
"groupId": "<group-id>",
|
|
268
|
+
"name": "weather",
|
|
269
|
+
"transport": "http",
|
|
270
|
+
"serverUrl": "https://weather.mcp.example.com/sse",
|
|
271
|
+
"authConfig": {"type":"none"}
|
|
272
|
+
}'
|
|
273
|
+
|
|
274
|
+
# 注册(stdio transport + env 认证)
|
|
275
|
+
curl -X POST http://localhost:3000/admin/mcp \
|
|
276
|
+
-H "X-Admin-Secret: my-secret" \
|
|
277
|
+
-H "Content-Type: application/json" \
|
|
278
|
+
-d '{
|
|
279
|
+
"groupId": "<group-id>",
|
|
280
|
+
"name": "local-tools",
|
|
281
|
+
"transport": "stdio",
|
|
282
|
+
"command": "npx -y @myorg/mcp-tools",
|
|
283
|
+
"authConfig": {"type":"env","env":{"API_KEY":"<secret>"}}
|
|
284
|
+
}'
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## 客户端 API 参考
|
|
290
|
+
|
|
291
|
+
客户端端点需要 `Authorization: Bearer <group-jwt>`。
|
|
292
|
+
|
|
293
|
+
| 方法 | 路径 | 说明 |
|
|
294
|
+
|------|------|------|
|
|
295
|
+
| `GET` | `/api/v1/oas` | 列出当前令牌群组可访问的 OAS 条目 |
|
|
296
|
+
| `GET` | `/api/v1/oas/:name` | 获取单个 OAS 条目(含解密认证信息) |
|
|
297
|
+
| `GET` | `/api/v1/mcp` | 列出当前令牌群组可访问的 MCP 服务器(含解密认证信息) |
|
|
298
|
+
| `GET` | `/api/v1/mcp/:name` | 获取单个 MCP 服务器(含解密认证信息) |
|
|
299
|
+
|
|
300
|
+
## 认证类型
|
|
301
|
+
|
|
302
|
+
| `authType` | `authConfig` 结构 |
|
|
303
|
+
|------------|------------------|
|
|
304
|
+
| `none` | `{ "type": "none" }` |
|
|
305
|
+
| `bearer` | `{ "type": "bearer", "token": "..." }` |
|
|
306
|
+
| `api_key` | `{ "type": "api_key", "key": "...", "in": "header\|query", "name": "X-API-Key" }` |
|
|
307
|
+
| `basic` | `{ "type": "basic", "username": "...", "password": "..." }` |
|
|
308
|
+
| `oauth2_cc` | `{ "type": "oauth2_cc", "tokenUrl": "...", "clientId": "...", "clientSecret": "...", "scopes": [] }` |
|
|
309
|
+
|
|
310
|
+
认证配置在存储前以 AES-256-GCM 加密,仅在请求处理时在内存中解密。
|
|
311
|
+
|
|
312
|
+
## MCP 认证类型
|
|
313
|
+
|
|
314
|
+
| `authConfig.type` | 结构 |
|
|
315
|
+
|--------------------|------|
|
|
316
|
+
| `none` | `{ "type": "none" }` |
|
|
317
|
+
| `http_headers` | `{ "type": "http_headers", "headers": { "Authorization": "Bearer ..." } }` |
|
|
318
|
+
| `env` | `{ "type": "env", "env": { "API_KEY": "..." } }` (stdio transport 使用) |
|
|
319
|
+
|
|
320
|
+
MCP 认证配置与 OAS 认证配置采用相同的加密模型:静态 AES-256-GCM 加密,请求时在内存中解密。
|
|
321
|
+
|
|
322
|
+
## OpenTelemetry 分布式追踪
|
|
323
|
+
|
|
324
|
+
分布式追踪**默认开启**。OTEL SDK 自动埋点 HTTP、Express、PostgreSQL 和 Redis,无需修改任何业务代码。
|
|
325
|
+
|
|
326
|
+
### 与 Prometheus 的关系(不冲突)
|
|
327
|
+
|
|
328
|
+
| 关注点 | 技术 | 方式 |
|
|
329
|
+
|--------|------|------|
|
|
330
|
+
| 指标采集(Scrape) | `prom-client` | `GET /metrics` 被动拉取 |
|
|
331
|
+
| 分布式追踪(Trace) | OpenTelemetry | OTLP 主动推送到采集器 |
|
|
332
|
+
|
|
333
|
+
两者完全独立,互不干扰。Prometheus 照常抓取 `/metrics`;OTEL 将 Span 推送到你的追踪后端。
|
|
334
|
+
|
|
335
|
+
### OTEL 环境变量
|
|
336
|
+
|
|
337
|
+
| 变量 | 默认值 | 说明 |
|
|
338
|
+
|------|--------|------|
|
|
339
|
+
| `OTEL_ENABLED` | `true` | 设为 `false` 完全禁用 |
|
|
340
|
+
| `OTEL_SERVICE_NAME` | `ucli-server` | 所有 Span 上的服务名标签 |
|
|
341
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | — | 采集器 URL(如 `http://otel-collector:4318`)。不填则本地丢弃(no-op) |
|
|
342
|
+
| `OTEL_EXPORTER_OTLP_HEADERS` | — | 采集器认证头(如 `Authorization=Bearer token`) |
|
|
343
|
+
| `OTEL_PROPAGATORS` | `tracecontext,baggage` | W3C 上下文传播(标准) |
|
|
344
|
+
| `OTEL_TRACES_SAMPLER` | `parentbased_always_on` | 采样策略 |
|
|
345
|
+
|
|
346
|
+
### 本地快速体验(使用 Jaeger)
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
# 启动 Jaeger all-in-one(支持 OTLP + 自带 UI)
|
|
350
|
+
docker run -d --name jaeger \
|
|
351
|
+
-p 4318:4318 \
|
|
352
|
+
-p 16686:16686 \
|
|
353
|
+
jaegertracing/all-in-one:latest
|
|
354
|
+
|
|
355
|
+
# 启动 ucli-server 并开启追踪
|
|
356
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
|
|
357
|
+
ADMIN_SECRET=my-secret ENCRYPTION_KEY=<64位hex> ucli-server
|
|
358
|
+
|
|
359
|
+
# 打开 Jaeger UI
|
|
360
|
+
open http://localhost:16686
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### 禁用 OTEL
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
OTEL_ENABLED=false ADMIN_SECRET=my-secret ENCRYPTION_KEY=<64位hex> ucli-server
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## 管理后台
|
|
370
|
+
|
|
371
|
+
安装 npm 包后,`/admin-ui` 路径自动提供内置 Web 管理界面,功能包括:
|
|
372
|
+
|
|
373
|
+
- **仪表板** — 统计概览(分组数、OAS 条目数、MCP 服务器数、有效 Token 数)
|
|
374
|
+
- **分组管理** — 创建和删除分组
|
|
375
|
+
- **OAS 条目管理** — 注册、编辑和删除 OAS 条目及其认证配置
|
|
376
|
+
- **MCP 服务器管理** — 注册、编辑和删除 MCP 服务器配置及其认证信息
|
|
377
|
+
- **Token 管理** — 按分组签发 JWT Token(签发后一次性显示)、查看状态、吊销
|
|
378
|
+
|
|
379
|
+
管理界面自动从 npm 包内附带的 `dist/admin-ui/` 目录提供服务,无需额外配置,启动服务后打开 `http://localhost:3000/admin-ui` 即可访问。
|
|
380
|
+
|
|
381
|
+
也可以通过环境变量指定自定义目录:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
ADMIN_UI_PATH=/path/to/custom/dist ADMIN_SECRET=secret ENCRYPTION_KEY=<64位hex> ucli-server
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## 健康检查与可观测性
|
|
388
|
+
|
|
389
|
+
| 端点 | 方法 | 说明 |
|
|
390
|
+
|------|------|------|
|
|
391
|
+
| `/api/v1/health` | `GET` | 存活探针,始终返回 `200 OK` |
|
|
392
|
+
| `/api/v1/ready` | `GET` | 就绪探针,检查存储和缓存适配器 |
|
|
393
|
+
| `/metrics` | `GET` | Prometheus 指标(默认 IP 限制) |
|
|
394
|
+
| `/api/docs` | `GET` | Swagger UI(`SWAGGER_ENABLED=false` 可关闭) |
|
|
395
|
+
| `/api/openapi.json` | `GET` | OpenAPI 3.0 JSON 规范 |
|
|
396
|
+
| `/admin-ui` | `GET` | 管理后台 |
|
|
397
|
+
|
|
398
|
+
## 安全模型
|
|
399
|
+
|
|
400
|
+
- **静态加密**:`authConfig` 字段以 AES-256-GCM(256 位密钥,每条记录随机 IV)加密
|
|
401
|
+
- **传输安全**:解密后的认证信息仅通过 TLS 传输给已认证的 CLI 客户端
|
|
402
|
+
- **JWT 安全**:RS256 签名,通过缓存黑名单追踪 JTI 实现吊销
|
|
403
|
+
- **管理认证**:`X-Admin-Secret` 请求头通过常量时间比较验证
|
|
404
|
+
- **不落日志**:认证配置永不写入日志或错误消息
|
package/assets/logo.svg
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 160" width="480" height="160">
|
|
2
|
+
<defs>
|
|
3
|
+
<!-- Background gradient border -->
|
|
4
|
+
<linearGradient id="borderGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
5
|
+
<stop offset="0%" stop-color="#7c3aed"/>
|
|
6
|
+
<stop offset="100%" stop-color="#2563eb"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
<!-- Icon accent gradient -->
|
|
9
|
+
<linearGradient id="hexGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
10
|
+
<stop offset="0%" stop-color="#7c3aed"/>
|
|
11
|
+
<stop offset="100%" stop-color="#38bdf8"/>
|
|
12
|
+
</linearGradient>
|
|
13
|
+
<!-- Glow filter -->
|
|
14
|
+
<filter id="glow" x="-30%" y="-30%" width="160%" height="160%">
|
|
15
|
+
<feGaussianBlur stdDeviation="3" result="blur"/>
|
|
16
|
+
<feMerge>
|
|
17
|
+
<feMergeNode in="blur"/>
|
|
18
|
+
<feMergeNode in="SourceGraphic"/>
|
|
19
|
+
</feMerge>
|
|
20
|
+
</filter>
|
|
21
|
+
<filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
|
|
22
|
+
<feGaussianBlur stdDeviation="6" result="blur"/>
|
|
23
|
+
<feMerge>
|
|
24
|
+
<feMergeNode in="blur"/>
|
|
25
|
+
<feMergeNode in="SourceGraphic"/>
|
|
26
|
+
</feMerge>
|
|
27
|
+
</filter>
|
|
28
|
+
<!-- Clip path for rounded rect -->
|
|
29
|
+
<clipPath id="cardClip">
|
|
30
|
+
<rect width="480" height="160" rx="14" ry="14"/>
|
|
31
|
+
</clipPath>
|
|
32
|
+
</defs>
|
|
33
|
+
|
|
34
|
+
<!-- Card background -->
|
|
35
|
+
<rect width="480" height="160" rx="14" ry="14" fill="#0d1117"/>
|
|
36
|
+
|
|
37
|
+
<!-- Subtle gradient overlay -->
|
|
38
|
+
<rect width="480" height="160" rx="14" ry="14" fill="url(#borderGrad)" opacity="0.06"/>
|
|
39
|
+
|
|
40
|
+
<!-- Border stroke -->
|
|
41
|
+
<rect x="1" y="1" width="478" height="158" rx="13" ry="13"
|
|
42
|
+
fill="none" stroke="url(#borderGrad)" stroke-width="1.5" opacity="0.8"/>
|
|
43
|
+
|
|
44
|
+
<!-- ── Left panel: gateway icon ── -->
|
|
45
|
+
<!-- Central hexagon (API gateway node) -->
|
|
46
|
+
<g transform="translate(80, 80)" filter="url(#softGlow)">
|
|
47
|
+
<!-- Hexagon shape (flat-top) -->
|
|
48
|
+
<polygon
|
|
49
|
+
points="0,-36 31.2,-18 31.2,18 0,36 -31.2,18 -31.2,-18"
|
|
50
|
+
fill="#0d1117"
|
|
51
|
+
stroke="url(#hexGrad)"
|
|
52
|
+
stroke-width="2.5"
|
|
53
|
+
/>
|
|
54
|
+
<!-- Inner hex fill accent -->
|
|
55
|
+
<polygon
|
|
56
|
+
points="0,-26 22.5,-13 22.5,13 0,26 -22.5,13 -22.5,-13"
|
|
57
|
+
fill="#7c3aed"
|
|
58
|
+
opacity="0.15"
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
<!-- Center dot -->
|
|
62
|
+
<circle cx="0" cy="0" r="5" fill="#38bdf8" opacity="0.9"/>
|
|
63
|
+
|
|
64
|
+
<!-- Route lines (3 spokes, 120° apart) -->
|
|
65
|
+
<!-- Right spoke (0°) -->
|
|
66
|
+
<line x1="5" y1="0" x2="28" y2="0" stroke="#38bdf8" stroke-width="1.8" stroke-linecap="round" opacity="0.85"/>
|
|
67
|
+
<circle cx="28" cy="0" r="3.5" fill="#38bdf8" opacity="0.85"/>
|
|
68
|
+
|
|
69
|
+
<!-- Lower-left spoke (120°) -->
|
|
70
|
+
<line x1="-2.5" y1="4.3" x2="-16" y2="27.7" stroke="#38bdf8" stroke-width="1.8" stroke-linecap="round" opacity="0.85"/>
|
|
71
|
+
<circle cx="-16" cy="27.7" r="3.5" fill="#38bdf8" opacity="0.85"/>
|
|
72
|
+
|
|
73
|
+
<!-- Upper-left spoke (240°) -->
|
|
74
|
+
<line x1="-2.5" y1="-4.3" x2="-16" y2="-27.7" stroke="#38bdf8" stroke-width="1.8" stroke-linecap="round" opacity="0.85"/>
|
|
75
|
+
<circle cx="-16" cy="-27.7" r="3.5" fill="#38bdf8" opacity="0.85"/>
|
|
76
|
+
</g>
|
|
77
|
+
|
|
78
|
+
<!-- Vertical divider -->
|
|
79
|
+
<line x1="142" y1="24" x2="142" y2="136" stroke="#30363d" stroke-width="1"/>
|
|
80
|
+
|
|
81
|
+
<!-- ── Right panel: text ── -->
|
|
82
|
+
<!-- Main title -->
|
|
83
|
+
<text
|
|
84
|
+
x="165" y="68"
|
|
85
|
+
font-family="'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', ui-monospace, monospace"
|
|
86
|
+
font-size="32"
|
|
87
|
+
font-weight="700"
|
|
88
|
+
letter-spacing="1"
|
|
89
|
+
fill="#e6edf3"
|
|
90
|
+
>OAS GATEWAY</text>
|
|
91
|
+
|
|
92
|
+
<!-- Subtitle -->
|
|
93
|
+
<text
|
|
94
|
+
x="166" y="95"
|
|
95
|
+
font-family="'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', ui-monospace, monospace"
|
|
96
|
+
font-size="13"
|
|
97
|
+
font-weight="400"
|
|
98
|
+
letter-spacing="0.5"
|
|
99
|
+
fill="#7d8590"
|
|
100
|
+
>by tronsfey</text>
|
|
101
|
+
|
|
102
|
+
<!-- Tag line -->
|
|
103
|
+
<text
|
|
104
|
+
x="166" y="122"
|
|
105
|
+
font-family="'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', ui-monospace, monospace"
|
|
106
|
+
font-size="11"
|
|
107
|
+
font-weight="400"
|
|
108
|
+
letter-spacing="0.3"
|
|
109
|
+
fill="#3d444d"
|
|
110
|
+
>OpenAPI · NestJS · CLI · AES-256-GCM</text>
|
|
111
|
+
|
|
112
|
+
<!-- Version badge -->
|
|
113
|
+
<g transform="translate(165, 132)">
|
|
114
|
+
<rect x="0" y="0" width="62" height="18" rx="9" fill="#1a2332" stroke="#2563eb" stroke-width="1" opacity="0.9"/>
|
|
115
|
+
<text
|
|
116
|
+
x="31" y="13"
|
|
117
|
+
text-anchor="middle"
|
|
118
|
+
font-family="'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', ui-monospace, monospace"
|
|
119
|
+
font-size="9.5"
|
|
120
|
+
font-weight="600"
|
|
121
|
+
fill="#2563eb"
|
|
122
|
+
letter-spacing="0.3"
|
|
123
|
+
>npm public</text>
|
|
124
|
+
</g>
|
|
125
|
+
|
|
126
|
+
<g transform="translate(234, 132)">
|
|
127
|
+
<rect x="0" y="0" width="52" height="18" rx="9" fill="#1a2a1a" stroke="#22c55e" stroke-width="1" opacity="0.9"/>
|
|
128
|
+
<text
|
|
129
|
+
x="26" y="13"
|
|
130
|
+
text-anchor="middle"
|
|
131
|
+
font-family="'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', ui-monospace, monospace"
|
|
132
|
+
font-size="9.5"
|
|
133
|
+
font-weight="600"
|
|
134
|
+
fill="#22c55e"
|
|
135
|
+
letter-spacing="0.3"
|
|
136
|
+
>v0.2.0</text>
|
|
137
|
+
</g>
|
|
138
|
+
</svg>
|