@claudelaw/taichu 0.6.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.
Files changed (93) hide show
  1. package/.dockerignore +13 -0
  2. package/Dockerfile +51 -0
  3. package/LICENSE +21 -0
  4. package/README.md +208 -0
  5. package/docker-compose.yml +42 -0
  6. package/docs/ROADMAP.md +101 -0
  7. package/docs/api/README.md +102 -0
  8. package/docs/architecture/001-zero-dependency-core.md +61 -0
  9. package/docs/architecture/002-structured-content-model.md +70 -0
  10. package/docs/architecture/003-hook-based-extension.md +82 -0
  11. package/docs/architecture/004-api-first-architecture.md +122 -0
  12. package/docs/architecture/README.md +24 -0
  13. package/docs/logo.svg +40 -0
  14. package/docs/research/ai-era-cms-user-research.md +247 -0
  15. package/docs/zh/README.md +81 -0
  16. package/docs/zh/guides/deploy.md +75 -0
  17. package/docs/zh/guides/mcp.md +84 -0
  18. package/docs/zh/guides/promotion.md +51 -0
  19. package/marketplace.json +78 -0
  20. package/package.json +60 -0
  21. package/packages/core/src/auth.js +158 -0
  22. package/packages/core/src/content-type.js +244 -0
  23. package/packages/core/src/core.test.js +406 -0
  24. package/packages/core/src/errors.js +60 -0
  25. package/packages/core/src/hooks.js +104 -0
  26. package/packages/core/src/index.js +16 -0
  27. package/packages/core/src/server.test.js +149 -0
  28. package/packages/core/src/sm-crypto.js +31 -0
  29. package/packages/core/src/sqlite-store.js +354 -0
  30. package/packages/core/src/store.js +174 -0
  31. package/packages/core/src/tokenizer.js +89 -0
  32. package/packages/core/src/vector-index.js +131 -0
  33. package/packages/llm-providers/src/index.js +181 -0
  34. package/packages/mcp/src/index.js +355 -0
  35. package/packages/server/public/admin/assets/index-DApxOVTx.js +191 -0
  36. package/packages/server/public/admin/assets/index-DtMvdQm9.css +1 -0
  37. package/packages/server/public/admin/index.html +28 -0
  38. package/packages/server/public/aurora/style.css +1173 -0
  39. package/packages/server/public/favicon.svg +46 -0
  40. package/packages/server/public/theme/index.html +288 -0
  41. package/packages/server/public/theme/style.css +133 -0
  42. package/packages/server/public/theme-minimal/index.html +223 -0
  43. package/packages/server/public/theme-minimal/style.css +109 -0
  44. package/packages/server/public/ws-test.html +106 -0
  45. package/packages/server/src/activitypub.js +228 -0
  46. package/packages/server/src/audit.js +104 -0
  47. package/packages/server/src/auth-provider.js +76 -0
  48. package/packages/server/src/body-parser.js +52 -0
  49. package/packages/server/src/bootstrap.js +272 -0
  50. package/packages/server/src/collab.js +154 -0
  51. package/packages/server/src/config.js +136 -0
  52. package/packages/server/src/context.js +86 -0
  53. package/packages/server/src/email.js +317 -0
  54. package/packages/server/src/index.js +195 -0
  55. package/packages/server/src/logger.js +78 -0
  56. package/packages/server/src/media-store.js +213 -0
  57. package/packages/server/src/middleware/auth.js +203 -0
  58. package/packages/server/src/middleware/cors.js +15 -0
  59. package/packages/server/src/middleware/error-handler.js +49 -0
  60. package/packages/server/src/middleware/rate-limit.js +118 -0
  61. package/packages/server/src/multipart.js +150 -0
  62. package/packages/server/src/notify.js +126 -0
  63. package/packages/server/src/pipeline.js +206 -0
  64. package/packages/server/src/plugin-installer.js +139 -0
  65. package/packages/server/src/plugin-manager.js +165 -0
  66. package/packages/server/src/relationships.js +217 -0
  67. package/packages/server/src/revisions.js +114 -0
  68. package/packages/server/src/router.js +194 -0
  69. package/packages/server/src/routes/activitypub.js +140 -0
  70. package/packages/server/src/routes/api.js +363 -0
  71. package/packages/server/src/routes/audit.js +222 -0
  72. package/packages/server/src/routes/auth.js +205 -0
  73. package/packages/server/src/routes/collab.js +90 -0
  74. package/packages/server/src/routes/export.js +77 -0
  75. package/packages/server/src/routes/graphql.js +344 -0
  76. package/packages/server/src/routes/media.js +169 -0
  77. package/packages/server/src/routes/plugin-marketplace.js +171 -0
  78. package/packages/server/src/routes/relationships.js +133 -0
  79. package/packages/server/src/routes/rss.js +92 -0
  80. package/packages/server/src/routes/sso.js +211 -0
  81. package/packages/server/src/routes/theme.js +119 -0
  82. package/packages/server/src/routes/webhook.js +94 -0
  83. package/packages/server/src/routes/wechat.js +115 -0
  84. package/packages/server/src/routes/workflow.js +157 -0
  85. package/packages/server/src/scheduler.js +96 -0
  86. package/packages/server/src/search.js +100 -0
  87. package/packages/server/src/server.test.js +295 -0
  88. package/packages/server/src/sso-analytics.js +78 -0
  89. package/packages/server/src/static.js +70 -0
  90. package/packages/server/src/theme-engine.js +119 -0
  91. package/packages/server/src/webhook.js +192 -0
  92. package/packages/server/src/websocket.js +308 -0
  93. package/scripts/cli.js +90 -0
@@ -0,0 +1,75 @@
1
+ # 部署指南
2
+
3
+ ## Docker
4
+
5
+ ```bash
6
+ docker pull registry.cn-hangzhou.aliyuncs.com/caludelaw/taichu:latest
7
+ docker run -d -p 3120:3120 -v taichu-data:/app/.taichu \
8
+ -e TAICHU_STORAGE=sqlite \
9
+ --name taichu \
10
+ registry.cn-hangzhou.aliyuncs.com/caludelaw/taichu:latest
11
+ ```
12
+
13
+ ## 阿里云 ECS
14
+
15
+ ```bash
16
+ # 安装 Node.js 22+
17
+ curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
18
+ sudo apt-get install -y nodejs
19
+
20
+ # 克隆并启动
21
+ git clone https://gitee.com/Caludelaw/Taichu.git
22
+ cd Taichu
23
+
24
+ # 配置环境变量
25
+ cat > .env << EOF
26
+ TAICHU_STORAGE=sqlite
27
+ TAICHU_PORT=3120
28
+ TAICHU_HOST=0.0.0.0
29
+ TAICHU_JWT_SECRET=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
30
+ EOF
31
+
32
+ # 持久化运行
33
+ npm install -g pm2
34
+ pm2 start packages/server/src/index.js --name taichu
35
+ pm2 save
36
+ pm2 startup
37
+ ```
38
+
39
+ ## 腾讯云轻量应用服务器
40
+
41
+ ```bash
42
+ # 同阿里云 ECS 步骤,替换 clone URL 为 Gitee 镜像
43
+ git clone https://gitee.com/Caludelaw/Taichu.git
44
+ cd Taichu && npm start
45
+ ```
46
+
47
+ ## Nginx 反向代理
48
+
49
+ ```nginx
50
+ server {
51
+ listen 80;
52
+ server_name your-domain.com;
53
+
54
+ location / {
55
+ proxy_pass http://127.0.0.1:3120;
56
+ proxy_http_version 1.1;
57
+ proxy_set_header Upgrade $http_upgrade;
58
+ proxy_set_header Connection "upgrade";
59
+ proxy_set_header Host $host;
60
+ proxy_set_header X-Real-IP $remote_addr;
61
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
62
+ }
63
+ }
64
+ ```
65
+
66
+ > WebSocket 需要 `Upgrade` 和 `Connection` 头支持。
67
+
68
+ ## 备份策略
69
+
70
+ SQLite 数据库文件位于 `.taichu/data/taichu.db`,建议:
71
+
72
+ ```bash
73
+ # 定时备份(crontab,每天凌晨 3 点)
74
+ 0 3 * * * cp /path/to/taichu/.taichu/data/taichu.db /backup/taichu-$(date +\%Y\%m\%d).db
75
+ ```
@@ -0,0 +1,84 @@
1
+ # MCP 接入指南
2
+
3
+ Taichu 内置 24 个 MCP Tools,Agent 可通过 Model Context Protocol 直接操控 CMS。
4
+
5
+ ## 配置
6
+
7
+ ### Claude Desktop
8
+
9
+ ```json
10
+ {
11
+ "mcpServers": {
12
+ "taichu": {
13
+ "command": "node",
14
+ "args": ["/path/to/taichu/packages/mcp/src/index.js"],
15
+ "env": {
16
+ "TAICHU_API": "http://localhost:3120",
17
+ "TAICHU_AGENT_KEY": "taichu_xxxx..."
18
+ }
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ ### WorkBuddy (mcp.json)
25
+
26
+ ```json
27
+ {
28
+ "taichu": {
29
+ "command": "node",
30
+ "args": ["packages/mcp/src/index.js"],
31
+ "env": {
32
+ "TAICHU_API": "http://localhost:3120",
33
+ "TAICHU_AGENT_KEY": "taichu_xxxx..."
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ ## 24 MCP Tools
40
+
41
+ | 类别 | Tool | 说明 |
42
+ |------|------|------|
43
+ | **CRUD** | `list_content` | 列出某类型文档 |
44
+ | | `get_content` | 获取单篇 |
45
+ | | `create_content` | 创建文档 |
46
+ | | `update_content` | 更新文档 |
47
+ | | `delete_content` | 删除文档 |
48
+ | **搜索** | `search_content` | TF-IDF 语义搜索 |
49
+ | | `get_content_by_field` | 按字段值查找 |
50
+ | **Schema** | `list_content_types` | 列出所有内容类型 |
51
+ | | `get_content_type_schema` | 获取 Schema 详情 |
52
+ | **发布** | `publish_content` | 发布草稿 |
53
+ | | `archive_content` | 归档文档 |
54
+ | **批量** | `batch_create_content` | 批量创建 |
55
+ | | `batch_update_content` | 批量更新 |
56
+ | | `clear_content` | 清空某类型 |
57
+ | **导入导出** | `export_content` | 导出为 JSON |
58
+ | | `import_content` | 批量导入 |
59
+ | **媒体** | `list_media` | 媒体列表 |
60
+ | **系统** | `get_stats` | 系统统计 |
61
+ | | `health_check` | 健康检查 |
62
+ | | `rebuild_search_index` | 重建搜索索引 |
63
+ | **密钥** | `get_api_keys` | 列出 API Keys |
64
+ | | `create_api_key` | 创建 API Key |
65
+ | **关系** | `get_content_relations` | 发现关联内容 |
66
+
67
+ ## Agent 权限
68
+
69
+ 创建 API Key 时指定 scope:
70
+
71
+ ```bash
72
+ curl -X POST http://localhost:3120/api/auth/apikeys \
73
+ -H "Authorization: Bearer $JWT_TOKEN" \
74
+ -d '{"label":"My Agent","scopes":["article:read","article:write"]}'
75
+ ```
76
+
77
+ scope 格式:`<类型>:<操作>`,支持 `*:*`(管理员)、`*:read`(只读)。
78
+
79
+ ## Agent 注意事项
80
+
81
+ 1. **速率限制**:默认 300 req/min(已认证 Agent),超限返回 429
82
+ 2. **乐观锁**:更新时传入 `_version` 避免覆盖
83
+ 3. **审计日志**:所有 Agent 操作记录在 AuditLog 中
84
+ 4. **来源标记**:Agent 创建的内容自动标记 `_meta.createdBy.agentId`
@@ -0,0 +1,51 @@
1
+ # Taichu CMS — 社区推广内容
2
+
3
+ ## 一句话定位
4
+
5
+ > Taichu — AI Agent 时代的开源 CMS。人类和 Agent 同为内容的第一公民。npm start 即可跑,MIT 协议。
6
+
7
+ ## 掘金 / CSDN / 思否首文
8
+
9
+ ### 标题
10
+
11
+ **"我们用 20 个 commits 造了一个 AI Agent-Native CMS —— 开源、MIT、零外部依赖"**
12
+
13
+ ### 正文要点
14
+
15
+ - **痛点**:WordPress 太老、Strapi 太重、Ghost 不够。现有 CMS 没有一个把 AI Agent 当作一等公民。
16
+ - **解决**:Taichu 从第一行代码起,API 层就同时为人类(JWT)和 Agent(API Key)设计。
17
+ - **三通道 API**:REST + GraphQL + MCP(29 tools)。Agent 通过 MCP 可以自动发现 CMS 能力,无需硬编码。
18
+ - **中国市场友好**:中文文档、ICP 备案配置、jieba 分词搜索、通义千问/文心一言/DeepSeek/Moonshot 四个国产 LLM 适配。
19
+ - **零外部依赖核心**:`npm start` 即跑,SQLite 持久化,Docker 一键部署。
20
+ - **开源 MIT**:完全免费,自托管,无 vendor lock-in。
21
+
22
+ ### 结尾 CTA
23
+
24
+ - GitHub: https://github.com/Caludelaw/Taichu
25
+ - Gitee: https://gitee.com/Caludelaw/Taichu
26
+ - 中文文档: docs/zh/README.md
27
+
28
+ ## Bilibili 视频脚本(3 分钟)
29
+
30
+ 1. (0-30s) 痛点:传统 CMS 的 Agent 困境
31
+ 2. (30-60s) Demo:`npm start` → Admin SPA 注册 → 创建文章
32
+ 3. (60-120s) 亮点:MCP 24+ tools + AI Agent 自动操作内容
33
+ 4. (120-180s) 中国市场:ICP 配置、中文搜索、国产 LLM
34
+
35
+ ## 微信群运营计划
36
+
37
+ - 群名:Taichu CMS 开发者交流群
38
+ - 定位:中文用户反馈 + 功能讨论 + 贡献指南
39
+ - 人数目标:首月 200 人
40
+
41
+ ## 各平台发布计划
42
+
43
+ | 平台 | 内容 | 时间 |
44
+ |------|------|------|
45
+ | 掘金 | 首发技术长文 | Day 1 |
46
+ | CSDN | 转载 | Day 1 |
47
+ | 思否 | Q&A + 推广 | Day 2 |
48
+ | 知乎 | 专栏文章 | Day 2 |
49
+ | V2EX | 创造分享帖 | Day 3 |
50
+ | Bilibili | 3 分钟演示视频 | Day 5 |
51
+ | GitHub Trending | Star + 分享 | 持续 |
@@ -0,0 +1,78 @@
1
+ {
2
+ "version": 1,
3
+ "name": "Taichu Plugin Marketplace",
4
+ "description": "Official plugin registry for Taichu CMS",
5
+ "lastUpdated": "2026-06-08T14:00:00Z",
6
+ "plugins": [
7
+ {
8
+ "name": "@taichu/plugin-seo",
9
+ "version": "0.1.0",
10
+ "description": "自动生成 meta tags、OG 标签、结构化数据,提升搜索引擎可见性",
11
+ "author": "Taichu Team",
12
+ "repository": "https://github.com/Caludelaw/taichu-plugin-seo",
13
+ "license": "MIT",
14
+ "keywords": ["seo", "meta", "opengraph", "structured-data", "sitemap"],
15
+ "category": "seo"
16
+ },
17
+ {
18
+ "name": "@taichu/plugin-sitemap",
19
+ "version": "0.1.0",
20
+ "description": "自动生成 XML sitemap,提交至 Google/Bing 搜索引擎",
21
+ "author": "Taichu Team",
22
+ "repository": "https://github.com/Caludelaw/taichu-plugin-sitemap",
23
+ "license": "MIT",
24
+ "keywords": ["sitemap", "seo", "google", "bing"],
25
+ "category": "seo"
26
+ },
27
+ {
28
+ "name": "@taichu/plugin-analytics",
29
+ "version": "0.1.0",
30
+ "description": "集成 Google Analytics、百度统计等分析工具到前端主题",
31
+ "author": "Taichu Team",
32
+ "repository": "https://github.com/Caludelaw/taichu-plugin-analytics",
33
+ "license": "MIT",
34
+ "keywords": ["analytics", "google-analytics", "baidu", "tracking"],
35
+ "category": "analytics"
36
+ },
37
+ {
38
+ "name": "@taichu/plugin-comments",
39
+ "version": "0.1.0",
40
+ "description": "评论系统集成:支持 Giscus、Disqus、Waline 等多种评论引擎",
41
+ "author": "Taichu Team",
42
+ "repository": "https://github.com/Caludelaw/taichu-plugin-comments",
43
+ "license": "MIT",
44
+ "keywords": ["comments", "giscus", "disqus", "waline"],
45
+ "category": "interaction"
46
+ },
47
+ {
48
+ "name": "@taichu/plugin-ai-writer",
49
+ "version": "0.1.0",
50
+ "description": "AI 写作助手:接入 OpenAI/Claude 等 LLM,辅助内容创作与优化",
51
+ "author": "Taichu Team",
52
+ "repository": "https://github.com/Caludelaw/taichu-plugin-ai-writer",
53
+ "license": "MIT",
54
+ "keywords": ["ai", "writing", "llm", "openai", "claude", "content"],
55
+ "category": "ai"
56
+ },
57
+ {
58
+ "name": "@taichu/plugin-image-optimizer",
59
+ "version": "0.1.0",
60
+ "description": "图片自动优化:上传时自动压缩、转 WebP、生成响应式尺寸",
61
+ "author": "Taichu Team",
62
+ "repository": "https://github.com/Caludelaw/taichu-plugin-image-optimizer",
63
+ "license": "MIT",
64
+ "keywords": ["image", "optimization", "webp", "compression"],
65
+ "category": "media"
66
+ },
67
+ {
68
+ "name": "@taichu/plugin-i18n",
69
+ "version": "0.1.0",
70
+ "description": "多语言内容管理:翻译工作流、语言切换、URL 本地化",
71
+ "author": "Taichu Team",
72
+ "repository": "https://github.com/Caludelaw/taichu-plugin-i18n",
73
+ "license": "MIT",
74
+ "keywords": ["i18n", "translation", "localization", "multilingual"],
75
+ "category": "content"
76
+ }
77
+ ]
78
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@claudelaw/taichu",
3
+ "version": "0.6.0",
4
+ "private": false,
5
+ "description": "AI Agent-Native CMS — content infrastructure built for the agent era",
6
+ "workspaces": [
7
+ "packages/*"
8
+ ],
9
+ "bin": {
10
+ "taichu": "scripts/cli.js"
11
+ },
12
+ "scripts": {
13
+ "dev": "node packages/server/src/index.js",
14
+ "start": "TAICHU_PUBLIC_READ=1 node packages/server/src/index.js",
15
+ "build": "cd packages/admin && npx vite build",
16
+ "test": "node --test packages/core/src/core.test.js packages/core/src/server.test.js",
17
+ "test:integration": "node --test packages/server/src/server.test.js",
18
+ "lint": "eslint .",
19
+ "lint:fix": "eslint . --fix",
20
+ "clean": "rm -rf packages/*/dist packages/*/node_modules"
21
+ },
22
+ "engines": {
23
+ "node": ">=18.0.0"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/Caludelaw/Taichu"
28
+ },
29
+ "keywords": [
30
+ "cms",
31
+ "headless-cms",
32
+ "ai-agent",
33
+ "mcp",
34
+ "content-management",
35
+ "agent-native",
36
+ "blog",
37
+ "static-site"
38
+ ],
39
+ "author": "Liu Huai'an",
40
+ "license": "MIT",
41
+ "files": [
42
+ "scripts/cli.js",
43
+ "packages/core/src/",
44
+ "packages/server/src/",
45
+ "packages/server/public/",
46
+ "packages/mcp/src/",
47
+ "packages/llm-providers/src/",
48
+ "marketplace.json",
49
+ "Dockerfile",
50
+ "docker-compose.yml",
51
+ ".dockerignore",
52
+ "docs/",
53
+ "README.md",
54
+ "LICENSE"
55
+ ],
56
+ "devDependencies": {
57
+ "@eslint/js": "^10.0.1",
58
+ "eslint": "^10.4.1"
59
+ }
60
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Auth — 零依赖认证模块
3
+ *
4
+ * 双通道认证:
5
+ * JWT → 人类用户(登录后签发,Bearer Token)
6
+ * API Key → AI Agent(预生成,X-Taichu-Agent-Key Header)
7
+ *
8
+ * 所有实现基于 Node.js 内置 crypto 模块,零外部依赖。
9
+ */
10
+
11
+ import crypto from 'node:crypto';
12
+
13
+ // ─── 密码哈希 ───────────────────────────────────────────────
14
+
15
+ /**
16
+ * 使用 PBKDF2 哈希密码(迭代 10 万次,128-bit salt)
17
+ * 输出格式:pbkdf2_sha256$100000$salt_hex$hash_hex
18
+ */
19
+ export function hashPassword(password, saltHex) {
20
+ const salt = saltHex ? Buffer.from(saltHex, 'hex') : crypto.randomBytes(16);
21
+ const hash = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256').toString('hex');
22
+ return `pbkdf2_sha256$100000$${salt.toString('hex')}$${hash}`;
23
+ }
24
+
25
+ /**
26
+ * 验证密码是否匹配哈希
27
+ */
28
+ export function verifyPassword(password, hashed) {
29
+ try {
30
+ const [, iterations, saltHex, hash] = hashed.split('$');
31
+ if (!iterations || !saltHex || !hash) return false;
32
+ const salt = Buffer.from(saltHex, 'hex');
33
+ const computed = crypto.pbkdf2Sync(password, salt, parseInt(iterations), 32, 'sha256').toString('hex');
34
+ return crypto.timingSafeEqual(Buffer.from(computed, 'hex'), Buffer.from(hash, 'hex'));
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ // ─── JWT ────────────────────────────────────────────────────
41
+
42
+ function base64urlEncode(str) {
43
+ return Buffer.from(str)
44
+ .toString('base64url');
45
+ }
46
+
47
+ function base64urlDecode(str) {
48
+ return Buffer.from(str, 'base64url').toString('utf8');
49
+ }
50
+
51
+ /**
52
+ * 签发 JWT Token
53
+ *
54
+ * @param {object} payload — 载荷(sub, role, scope 等)
55
+ * @param {string} secret — 签名密钥
56
+ * @param {object} [options]
57
+ * @param {string} [options.expiresIn] — 过期时间,如 '24h', '7d'
58
+ * @returns {string} JWT token
59
+ */
60
+ export function signJWT(payload, secret, options = {}) {
61
+ const header = { alg: 'HS256', typ: 'JWT' };
62
+ const now = Math.floor(Date.now() / 1000);
63
+ const expMap = { s: 1, m: 60, h: 3600, d: 86400 };
64
+
65
+ let exp = null;
66
+ if (options.expiresIn) {
67
+ const match = options.expiresIn.match(/^(\d+)([smhd])$/);
68
+ if (match) {
69
+ exp = now + parseInt(match[1]) * (expMap[match[2]] || 3600);
70
+ }
71
+ }
72
+
73
+ const claims = {
74
+ ...payload,
75
+ iat: now,
76
+ ...(exp ? { exp } : {})
77
+ };
78
+
79
+ const headerB64 = base64urlEncode(JSON.stringify(header));
80
+ const payloadB64 = base64urlEncode(JSON.stringify(claims));
81
+ const signature = crypto
82
+ .createHmac('sha256', secret)
83
+ .update(`${headerB64}.${payloadB64}`)
84
+ .digest('base64url');
85
+
86
+ return `${headerB64}.${payloadB64}.${signature}`;
87
+ }
88
+
89
+ /**
90
+ * 验证并解析 JWT Token
91
+ *
92
+ * @param {string} token — JWT token 字符串
93
+ * @param {string} secret — 签名密钥
94
+ * @returns {{ valid: boolean, payload?: object, error?: string }}
95
+ */
96
+ export function verifyJWT(token, secret) {
97
+ try {
98
+ const parts = token.split('.');
99
+ if (parts.length !== 3) {
100
+ return { valid: false, error: 'Invalid token format' };
101
+ }
102
+
103
+ const [headerB64, payloadB64, sigB64] = parts;
104
+
105
+ // Verify signature
106
+ const expectedSig = crypto
107
+ .createHmac('sha256', secret)
108
+ .update(`${headerB64}.${payloadB64}`)
109
+ .digest('base64url');
110
+
111
+ if (sigB64 !== expectedSig) {
112
+ return { valid: false, error: 'Invalid signature' };
113
+ }
114
+
115
+ // Parse claims
116
+ const claims = JSON.parse(base64urlDecode(payloadB64));
117
+
118
+ // Check expiration
119
+ if (claims.exp && claims.exp < Math.floor(Date.now() / 1000)) {
120
+ return { valid: false, error: 'Token expired' };
121
+ }
122
+
123
+ return { valid: true, payload: claims };
124
+ } catch (err) {
125
+ return { valid: false, error: err.message };
126
+ }
127
+ }
128
+
129
+ // ─── API Key ─────────────────────────────────────────────────
130
+
131
+ /**
132
+ * 生成 API Key
133
+ * 格式:taichu_<32字节随机hex>
134
+ *
135
+ * @param {string} [label] — 标签(备忘用,如 "My Super Niuma Agent")
136
+ * @returns {{ key: string, prefix: string, label: string, createdAt: string }}
137
+ */
138
+ export function generateAPIKey(label = '') {
139
+ const key = `taichu_${crypto.randomBytes(32).toString('hex')}`;
140
+ const prefix = key.substring(0, 14); // "taichu_a1b2c3"
141
+ // Store the hash, not the raw key
142
+ const hash = crypto.createHash('sha256').update(key).digest('hex');
143
+ return {
144
+ key, // 仅生成时返回一次
145
+ prefix, // 用于 UI 展示 "taichu_a1b2***"
146
+ hash, // 存储到数据库
147
+ label,
148
+ createdAt: new Date().toISOString()
149
+ };
150
+ }
151
+
152
+ /**
153
+ * 验证 API Key 是否匹配已存储的哈希
154
+ */
155
+ export function verifyAPIKey(key, storedHash) {
156
+ const hash = crypto.createHash('sha256').update(key).digest('hex');
157
+ return crypto.timingSafeEqual(Buffer.from(hash, 'hex'), Buffer.from(storedHash, 'hex'));
158
+ }