@xiaobuyu/nodesign 0.0.1 → 0.0.2
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 +156 -106
- package/bin/nodesign.js +0 -0
- package/package.json +1 -1
- package/server/api/README.md +45 -45
- package/server/api/_guard.js +75 -75
- package/server/api/admin.js +187 -187
- package/server/api/assets/docx-page.js +103 -103
- package/server/api/assets/helpers.js +22 -22
- package/server/api/assets/notes.js +144 -144
- package/server/api/assets.js +896 -896
- package/server/api/board.js +57 -57
- package/server/api/browse.js +138 -138
- package/server/api/canvas.js +444 -444
- package/server/api/chatai.js +324 -324
- package/server/api/exports/build-standalone.js +980 -980
- package/server/api/exports/cards.js +118 -118
- package/server/api/exports/docx-pdf.js +46 -46
- package/server/api/exports/export-page.js +142 -142
- package/server/api/exports/handoff.js +147 -147
- package/server/api/exports.js +690 -690
- package/server/api/instruction.js +75 -75
- package/server/api/me.js +116 -116
- package/server/api/memory.js +185 -185
- package/server/api/pending-changes.js +366 -366
- package/server/api/plugins.js +164 -164
- package/server/api/projects.js +220 -220
- package/server/api/publish.js +64 -64
- package/server/api/recent.js +73 -73
- package/server/api/sessions.js +589 -589
- package/server/api/skills.js +77 -77
- package/server/api/standalone-fit.js +168 -168
- package/server/api/turn-compose.js +180 -180
- package/server/api/turn-inflight.js +44 -44
- package/server/auth/README.md +17 -17
- package/server/auth/middleware.js +138 -138
- package/server/auth/origin-guard.js +71 -71
- package/server/auth/session.js +108 -108
- package/server/auth/tier.js +113 -113
- package/server/auth/users-store.js +348 -348
- package/server/edit/README.md +29 -29
- package/server/engine/README.md +113 -113
- package/server/engine/agent/agent-shared.js +550 -550
- package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -4
- package/server/engine/agent/auto-mode-rules.js +78 -78
- package/server/engine/agent/context.js +418 -418
- package/server/engine/agent/events.js +464 -464
- package/server/engine/agent/hooks/canvas-validate.js +199 -199
- package/server/engine/agent/hooks/failure.js +120 -120
- package/server/engine/agent/hooks/file-events.js +75 -75
- package/server/engine/agent/hooks/lifecycle.js +233 -233
- package/server/engine/agent/hooks/post-canvas-focus.js +80 -80
- package/server/engine/agent/hooks/post-guidance.js +206 -206
- package/server/engine/agent/hooks/post-subagent-report.js +104 -104
- package/server/engine/agent/hooks/post-trim.js +58 -58
- package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -34
- package/server/engine/agent/hooks/pre-defaults.js +84 -84
- package/server/engine/agent/hooks/pre-injectors.js +277 -277
- package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -78
- package/server/engine/agent/hooks/pre-starter-files.js +61 -61
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -78
- package/server/engine/agent/hooks/site-validate.js +94 -94
- package/server/engine/agent/hooks/tool-prompts.js +31 -31
- package/server/engine/agent/hooks/turn-state-memory.js +56 -56
- package/server/engine/agent/hooks/user-prompt-submit.js +215 -215
- package/server/engine/agent/hooks.js +354 -354
- package/server/engine/agent/init-contract.js +69 -69
- package/server/engine/agent/isolation.js +159 -159
- package/server/engine/agent/model-table.js +292 -292
- package/server/engine/agent/plugin-loader.js +266 -266
- package/server/engine/agent/prompts/nodesign-prelude.md +336 -336
- package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -71
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -98
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -583
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -483
- package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -100
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -270
- package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -70
- package/server/engine/agent/prompts/tools/site-reference.md +106 -106
- package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -125
- package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -48
- package/server/engine/agent/session-loop.js +1024 -1024
- package/server/engine/agent/session-model.js +164 -164
- package/server/engine/agent/skill.js +204 -204
- package/server/engine/agent/task-events.js +78 -78
- package/server/engine/agents/ds-extractor.md +132 -132
- package/server/engine/agents/explorer.md +190 -190
- package/server/engine/agents/index.js +230 -230
- package/server/engine/agents/schemas/design-system.json +156 -156
- package/server/engine/agents/schemas/tweak-schema.json +113 -113
- package/server/engine/agents/tweak-proposer.md +127 -127
- package/server/engine/agents/vision-checker.md +254 -254
- package/server/engine/browse/capture.js +422 -422
- package/server/engine/browse/card.js +137 -137
- package/server/engine/browse/handover.js +59 -59
- package/server/engine/browse/page-digest.js +119 -119
- package/server/engine/browse/refs.js +196 -196
- package/server/engine/browse/registry.js +307 -307
- package/server/engine/browse/screencast.js +170 -170
- package/server/engine/browse/state.js +121 -121
- package/server/engine/chatai/chat-log.js +79 -79
- package/server/engine/chatai/index.js +193 -193
- package/server/engine/chatai/openai-compat.js +152 -152
- package/server/engine/chatai/orchestrate.js +309 -309
- package/server/engine/chatai/perform.js +40 -40
- package/server/engine/chatai/summarize.js +79 -79
- package/server/engine/mcp/capability-gate.js +52 -52
- package/server/engine/mcp/index.js +339 -339
- package/server/engine/mcp/param-sanitizer.js +142 -142
- package/server/engine/mcp/tools/arrange-on-board.js +124 -124
- package/server/engine/mcp/tools/artifact-session.js +278 -278
- package/server/engine/mcp/tools/browse-computer.js +421 -421
- package/server/engine/mcp/tools/browse-find-batch.js +183 -183
- package/server/engine/mcp/tools/browse-screenshot.js +157 -157
- package/server/engine/mcp/tools/browse.js +480 -480
- package/server/engine/mcp/tools/build-docx.js +101 -101
- package/server/engine/mcp/tools/clear-pending-changes.js +99 -99
- package/server/engine/mcp/tools/create-on-board.js +155 -155
- package/server/engine/mcp/tools/crystallize-skill.js +168 -168
- package/server/engine/mcp/tools/explain-style.js +237 -237
- package/server/engine/mcp/tools/export-handoff.js +133 -133
- package/server/engine/mcp/tools/expose-tweaks.js +149 -149
- package/server/engine/mcp/tools/generate-image.js +842 -842
- package/server/engine/mcp/tools/get-computed-styles.js +164 -164
- package/server/engine/mcp/tools/get-pending-changes.js +205 -205
- package/server/engine/mcp/tools/h3box-ssh.js +84 -84
- package/server/engine/mcp/tools/helpers/acquire-page.js +82 -82
- package/server/engine/mcp/tools/helpers/motion-lab.js +554 -554
- package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -112
- package/server/engine/mcp/tools/helpers/perception-page.js +215 -215
- package/server/engine/mcp/tools/helpers/reference-download.js +103 -103
- package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -80
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -280
- package/server/engine/mcp/tools/highlight.js +68 -68
- package/server/engine/mcp/tools/list-pages.js +222 -222
- package/server/engine/mcp/tools/lookup-tags.js +59 -59
- package/server/engine/mcp/tools/navigate-to-page.js +60 -60
- package/server/engine/mcp/tools/organize-board.js +64 -64
- package/server/engine/mcp/tools/paint-still.js +434 -434
- package/server/engine/mcp/tools/pin-to-board.js +130 -130
- package/server/engine/mcp/tools/preview-deck.js +130 -130
- package/server/engine/mcp/tools/profile-scroll.js +305 -305
- package/server/engine/mcp/tools/publish-site.js +102 -102
- package/server/engine/mcp/tools/query-elements.js +195 -195
- package/server/engine/mcp/tools/read-board.js +103 -103
- package/server/engine/mcp/tools/read-document.js +93 -93
- package/server/engine/mcp/tools/read-page.js +264 -264
- package/server/engine/mcp/tools/read-tavern-json.js +143 -143
- package/server/engine/mcp/tools/record-decision.js +140 -140
- package/server/engine/mcp/tools/relate-on-board.js +110 -110
- package/server/engine/mcp/tools/report-issue.js +115 -115
- package/server/engine/mcp/tools/roll-film.js +266 -266
- package/server/engine/mcp/tools/screenshot-docx.js +138 -138
- package/server/engine/mcp/tools/screenshot-url.js +195 -195
- package/server/engine/mcp/tools/screenshot.js +583 -583
- package/server/engine/mcp/tools/tier-gate.js +96 -96
- package/server/engine/mcp/tools/trace-motion.js +215 -215
- package/server/engine/mcp/tools/web-search.js +548 -548
- package/server/engine/motion/inventory.js +349 -349
- package/server/engine/perception/session.js +235 -235
- package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -5
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -192
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -667
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -22
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -25
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -21
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -21
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -162
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -407
- package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -80
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -228
- package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +78 -78
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -268
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -323
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -134
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -61
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -163
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -86
- package/server/engine/runs/active-runs.js +698 -698
- package/server/engine/runs/live-turn.js +252 -252
- package/server/engine/runs/store.js +364 -364
- package/server/engine/runs/turn-relay.js +217 -217
- package/server/engine/runtime/workspace.js +164 -164
- package/server/lib/artifact-file-path.js +80 -80
- package/server/lib/artifact-target.js +284 -284
- package/server/lib/asset-refs.js +239 -239
- package/server/lib/async-queue.js +104 -104
- package/server/lib/auto-relations.js +130 -130
- package/server/lib/binding-types.js +60 -60
- package/server/lib/board-kind-sizes.js +62 -62
- package/server/lib/board-relations.js +133 -133
- package/server/lib/browse-proxy.js +180 -180
- package/server/lib/canvas-id.js +36 -36
- package/server/lib/cover.js +227 -227
- package/server/lib/danbooru-tags.js +291 -291
- package/server/lib/doc-extract.js +156 -156
- package/server/lib/docx/build-from-source.js +260 -260
- package/server/lib/docx/build.js +490 -490
- package/server/lib/docx/dump-styles.js +287 -287
- package/server/lib/docx/fonts/nodesign-cjk.conf +164 -164
- package/server/lib/docx/merge-runs.js +129 -129
- package/server/lib/docx/numbering.js +218 -218
- package/server/lib/docx/order.js +152 -152
- package/server/lib/docx/rawzip.js +172 -172
- package/server/lib/docx/render.js +92 -92
- package/server/lib/docx/text-lint.js +225 -225
- package/server/lib/docx/tokens.js +335 -335
- package/server/lib/docx/units.js +29 -29
- package/server/lib/docx/xml.js +291 -291
- package/server/lib/docx-pages.js +187 -187
- package/server/lib/export-collect.js +289 -289
- package/server/lib/export-package.js +210 -210
- package/server/lib/html-srcset.js +145 -145
- package/server/lib/image-variant.js +460 -460
- package/server/lib/ingress/forward-openai-chat.js +305 -305
- package/server/lib/ingress/openai-chat.js +491 -491
- package/server/lib/ingress/session-notice.js +66 -66
- package/server/lib/ingress/session-routes.js +68 -68
- package/server/lib/ingress/slot-probe.js +130 -130
- package/server/lib/ingress/upstream-billing.js +52 -52
- package/server/lib/ingress/upstream-fail-streak.js +72 -72
- package/server/lib/ingress/upstream-truncation.js +48 -48
- package/server/lib/issues-store.js +182 -182
- package/server/lib/kinds/deck.js +72 -72
- package/server/lib/kinds/docx.js +283 -283
- package/server/lib/kinds/file-kinds.js +113 -113
- package/server/lib/kinds/index.js +202 -202
- package/server/lib/kinds/site.js +161 -161
- package/server/lib/model-ingress.js +543 -543
- package/server/lib/moderation.js +255 -255
- package/server/lib/notice-store.js +103 -103
- package/server/lib/plugin-install.js +104 -104
- package/server/lib/plugin-validator.js +721 -721
- package/server/lib/publish-store.js +115 -115
- package/server/lib/quick-summary.js +52 -52
- package/server/lib/quota.js +270 -270
- package/server/lib/rate-window.js +29 -29
- package/server/lib/reference-assets.js +92 -92
- package/server/lib/region-shot.js +135 -135
- package/server/lib/safe-path.js +73 -73
- package/server/lib/sdk-session.js +33 -33
- package/server/lib/showcase-store.js +92 -92
- package/server/lib/site-publish.js +465 -465
- package/server/lib/ssrf-guard.js +432 -432
- package/server/lib/task-scan.js +158 -158
- package/server/lib/tavern-json.js +154 -154
- package/server/lib/video-variant.js +237 -237
- package/server/lib/workspace-path.js +33 -33
- package/server/ops/fix-sdk-musl.mjs +40 -40
- package/server/ops/install-macos-fonts.sh +114 -114
- package/server/ops/macos-fonts.conf +336 -336
- package/server/ops/sandbox-shim/bwrap +57 -57
- package/server/projects/assets-summary.js +119 -119
- package/server/projects/auto-name.js +53 -53
- package/server/projects/board-store.js +640 -640
- package/server/projects/move-entry.js +103 -103
- package/server/projects/store.js +270 -270
- package/server/projects/ui-config.js +54 -54
- package/server/projects/workspace-templates.js +53 -53
- package/server/projects/workspace.js +1025 -1025
- package/server/runtime/local-config.js +177 -177
- package/server/runtime/profile.js +72 -72
- package/server/services/rembg-service.py +334 -334
- package/server/shared/README.md +25 -25
- package/server/shared/deck.js +54 -54
- package/server/shared/time.js +49 -49
- package/server/style-pipeline/README.md +53 -53
- package/server/ws/broker.js +36 -36
- package/server/ws/browse-channel.js +177 -177
- package/server/ws/index.js +386 -386
- package/web/README.md +89 -89
- package/web/dist/assets/BrowserWindow-BYCgizHw.js +11 -0
- package/web/dist/assets/{DeckWindow-4G0xX7P3.js → DeckWindow-pNydEM4L.js} +1 -1
- package/web/dist/assets/{DocxWindow-BXQsoPkh.js → DocxWindow-PPgzh3Gq.js} +1 -1
- package/web/dist/assets/{SiteWindow-D6eS-2gM.js → SiteWindow-DM0LQPCO.js} +1 -1
- package/web/dist/assets/{index-Cbl3ATvp.js → index-BHTBJlpY.js} +32 -32
- package/web/dist/assets/{pending-edit-apply-DTjo2geW.js → pending-edit-apply-C0I13CbD.js} +1 -1
- package/web/dist/assets/{rotate-cw-LMT3x1Zj.js → rotate-cw-B1UQ8Hld.js} +1 -1
- package/web/dist/assets/{square-dashed-mouse-pointer-CBoAkfhi.js → square-dashed-mouse-pointer-8lrgqmfv.js} +1 -1
- package/web/dist/index.html +17 -17
- package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +0 -11
package/server/api/plugins.js
CHANGED
|
@@ -1,164 +1,164 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* server/api/plugins.js — Plugin 上传 / 列表 / 卸载(用户级 + project 级 两套 router)
|
|
3
|
-
*
|
|
4
|
-
* 用户级(跨 project 全局,挂在 /api/plugins):
|
|
5
|
-
* GET /api/plugins 列用户级 plugin
|
|
6
|
-
* POST /api/plugins/install 上传 zip → ~/.nodesign/plugins/
|
|
7
|
-
* DELETE /api/plugins/:name 卸载用户级 plugin
|
|
8
|
-
*
|
|
9
|
-
* Project 级(仅当前 project,挂在 /api/projects 跟 assets/turn 同前缀):
|
|
10
|
-
* GET /api/projects/:pid/plugins 列 project 级 plugin
|
|
11
|
-
* POST /api/projects/:pid/plugins/install 上传 zip → <pid>/shared/.claude/plugins/
|
|
12
|
-
* DELETE /api/projects/:pid/plugins/:name 卸载 project 级 plugin
|
|
13
|
-
*
|
|
14
|
-
* 上传流程(两轨同套):
|
|
15
|
-
* 1. multer 收 multipart `file` 字段到 memory buffer
|
|
16
|
-
* 2. validatePluginZip(buffer) — hard-fail 立即 400 + 详细 errors[]
|
|
17
|
-
* 3. 解压到 staging 临时目录(同一 root 下 .staging/<tmpId>/)
|
|
18
|
-
* 4. 目标 plugin name 已存在:没 `?force=true` 返 409 + 当前装的 manifest;
|
|
19
|
-
* 有 force:先 rm -rf 旧目录
|
|
20
|
-
* 5. atomic rename staging → 目标
|
|
21
|
-
* 6. 返 manifest + skills + warnings
|
|
22
|
-
*
|
|
23
|
-
* 安全:validator 已拦 path traversal / 保留 name / 大小 / 格式;extractPluginZip
|
|
24
|
-
* 二次防御解压路径不出 staging dir;DELETE 校验 name 合规 + 不撞保留前缀。
|
|
25
|
-
*
|
|
26
|
-
* 复用:multer memoryStorage pattern from server/api/assets.js:13-26
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import express from 'express';
|
|
30
|
-
import multer from 'multer';
|
|
31
|
-
|
|
32
|
-
import { validateProjectId, getProject } from '../projects/store.js';
|
|
33
|
-
import { guardProject } from './_guard.js';
|
|
34
|
-
import {
|
|
35
|
-
getUserPluginsRoot,
|
|
36
|
-
getProjectPluginsRoot,
|
|
37
|
-
listInstalledPluginsDetailed,
|
|
38
|
-
} from '../engine/agent/plugin-loader.js';
|
|
39
|
-
import { LIMITS } from '../lib/plugin-validator.js';
|
|
40
|
-
import {
|
|
41
|
-
installPluginToRoot,
|
|
42
|
-
uninstallFromRoot,
|
|
43
|
-
} from '../lib/plugin-install.js';
|
|
44
|
-
|
|
45
|
-
// 允许的上传 mime / 后缀(双轨:单 .md / zip)。multer fileFilter 拦其他类型。
|
|
46
|
-
const ALLOWED_MIME = new Set([
|
|
47
|
-
'application/zip',
|
|
48
|
-
'application/x-zip-compressed',
|
|
49
|
-
'application/octet-stream', // 部分 OS 上 .zip 拿不到准确 mime;按 extension 兜底校验
|
|
50
|
-
'text/markdown',
|
|
51
|
-
'text/x-markdown',
|
|
52
|
-
'text/plain', // 部分 OS 上 .md 报 text/plain
|
|
53
|
-
]);
|
|
54
|
-
const ALLOWED_EXT_RE = /\.(md|zip)$/i;
|
|
55
|
-
|
|
56
|
-
const upload = multer({
|
|
57
|
-
storage: multer.memoryStorage(),
|
|
58
|
-
limits: { fileSize: LIMITS.ZIP_MAX_BYTES },
|
|
59
|
-
// fileFilter 不 throw error(避免被 express generic error handler 截到返 500),
|
|
60
|
-
// 把 validation 错误存到 req 上 + reject 文件;handler 检查 req.fileValidationError 返 400
|
|
61
|
-
fileFilter: (req, file, cb) => {
|
|
62
|
-
const mimeOk = ALLOWED_MIME.has(file.mimetype);
|
|
63
|
-
const extOk = ALLOWED_EXT_RE.test(file.originalname || '');
|
|
64
|
-
if (mimeOk || extOk) return cb(null, true);
|
|
65
|
-
req.fileValidationError = `不支持的文件类型 (mime=${file.mimetype} name=${file.originalname}) — 仅接受 .md 或 .zip`;
|
|
66
|
-
cb(null, false);
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
/** 统一处理 multer 阶段 reject:fileValidationError 优先于 no-file */
|
|
71
|
-
function rejectInvalidFile(req, res) {
|
|
72
|
-
if (req.fileValidationError) {
|
|
73
|
-
res.status(400).json({ error: req.fileValidationError });
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
if (!req.file) {
|
|
77
|
-
res.status(400).json({ error: 'no file (field name: file)' });
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ── Router 1:用户级(挂 /api/plugins) ──
|
|
84
|
-
//
|
|
85
|
-
// 2026-07-30 起每个用户一个根(~/.nodesign/plugins/<userId>/)。之前是全站一个
|
|
86
|
-
// 共享目录:任何登录用户装的 plugin 会加载进所有人的 agent 会话,也能删别人的。
|
|
87
|
-
// 这里的 req.user 由 authGuard 挂(登录墙关闭时是 `_anon`),拿不到就 401 —— 没有
|
|
88
|
-
// 身份就没有"用户级"可言,不能退回共享根。
|
|
89
|
-
|
|
90
|
-
export const userPluginsRouter = express.Router();
|
|
91
|
-
|
|
92
|
-
/** 取当前请求者的 plugin 根;没有合法身份时直接回 401 并返 null */
|
|
93
|
-
function userRootOf(req, res) {
|
|
94
|
-
const root = getUserPluginsRoot(req.user?.id);
|
|
95
|
-
if (!root) {
|
|
96
|
-
res.status(401).json({ error: 'unauthorized' });
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
return root;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
userPluginsRouter.get('/', async (req, res, next) => {
|
|
103
|
-
try {
|
|
104
|
-
const root = userRootOf(req, res);
|
|
105
|
-
if (!root) return;
|
|
106
|
-
const plugins = await listInstalledPluginsDetailed(root);
|
|
107
|
-
res.json({ plugins });
|
|
108
|
-
} catch (err) { next(err); }
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
userPluginsRouter.post('/install', upload.single('file'), async (req, res, next) => {
|
|
112
|
-
try {
|
|
113
|
-
const root = userRootOf(req, res);
|
|
114
|
-
if (!root) return;
|
|
115
|
-
if (rejectInvalidFile(req, res)) return;
|
|
116
|
-
const force = req.query.force === 'true';
|
|
117
|
-
const result = await installPluginToRoot(req.file.buffer, root, { force });
|
|
118
|
-
res.status(result.status).json(result.body);
|
|
119
|
-
} catch (err) { next(err); }
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
userPluginsRouter.delete('/:name', async (req, res, next) => {
|
|
123
|
-
try {
|
|
124
|
-
const root = userRootOf(req, res);
|
|
125
|
-
if (!root) return;
|
|
126
|
-
const result = await uninstallFromRoot(root, req.params.name);
|
|
127
|
-
res.status(result.status).json(result.body);
|
|
128
|
-
} catch (err) { next(err); }
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// ── Router 2:project 级(挂 /api/projects,跟 assets/turn 同前缀) ──
|
|
132
|
-
|
|
133
|
-
export const projectPluginsRouter = express.Router();
|
|
134
|
-
|
|
135
|
-
projectPluginsRouter.get('/:pid/plugins', async (req, res, next) => {
|
|
136
|
-
try {
|
|
137
|
-
if (!guardProject(req, res)) return;
|
|
138
|
-
const root = getProjectPluginsRoot(req.params.pid);
|
|
139
|
-
const plugins = await listInstalledPluginsDetailed(root);
|
|
140
|
-
res.json({ plugins });
|
|
141
|
-
} catch (err) { next(err); }
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
projectPluginsRouter.post('/:pid/plugins/install', upload.single('file'), async (req, res, next) => {
|
|
145
|
-
try {
|
|
146
|
-
if (!guardProject(req, res)) return;
|
|
147
|
-
if (rejectInvalidFile(req, res)) return;
|
|
148
|
-
const force = req.query.force === 'true';
|
|
149
|
-
const root = getProjectPluginsRoot(req.params.pid);
|
|
150
|
-
const result = await installPluginToRoot(req.file.buffer, root, { force });
|
|
151
|
-
res.status(result.status).json(result.body);
|
|
152
|
-
} catch (err) { next(err); }
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
projectPluginsRouter.delete('/:pid/plugins/:name', async (req, res, next) => {
|
|
156
|
-
try {
|
|
157
|
-
if (!guardProject(req, res)) return;
|
|
158
|
-
const root = getProjectPluginsRoot(req.params.pid);
|
|
159
|
-
const result = await uninstallFromRoot(root, req.params.name);
|
|
160
|
-
res.status(result.status).json(result.body);
|
|
161
|
-
} catch (err) { next(err); }
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
export default { userPluginsRouter, projectPluginsRouter };
|
|
1
|
+
/**
|
|
2
|
+
* server/api/plugins.js — Plugin 上传 / 列表 / 卸载(用户级 + project 级 两套 router)
|
|
3
|
+
*
|
|
4
|
+
* 用户级(跨 project 全局,挂在 /api/plugins):
|
|
5
|
+
* GET /api/plugins 列用户级 plugin
|
|
6
|
+
* POST /api/plugins/install 上传 zip → ~/.nodesign/plugins/
|
|
7
|
+
* DELETE /api/plugins/:name 卸载用户级 plugin
|
|
8
|
+
*
|
|
9
|
+
* Project 级(仅当前 project,挂在 /api/projects 跟 assets/turn 同前缀):
|
|
10
|
+
* GET /api/projects/:pid/plugins 列 project 级 plugin
|
|
11
|
+
* POST /api/projects/:pid/plugins/install 上传 zip → <pid>/shared/.claude/plugins/
|
|
12
|
+
* DELETE /api/projects/:pid/plugins/:name 卸载 project 级 plugin
|
|
13
|
+
*
|
|
14
|
+
* 上传流程(两轨同套):
|
|
15
|
+
* 1. multer 收 multipart `file` 字段到 memory buffer
|
|
16
|
+
* 2. validatePluginZip(buffer) — hard-fail 立即 400 + 详细 errors[]
|
|
17
|
+
* 3. 解压到 staging 临时目录(同一 root 下 .staging/<tmpId>/)
|
|
18
|
+
* 4. 目标 plugin name 已存在:没 `?force=true` 返 409 + 当前装的 manifest;
|
|
19
|
+
* 有 force:先 rm -rf 旧目录
|
|
20
|
+
* 5. atomic rename staging → 目标
|
|
21
|
+
* 6. 返 manifest + skills + warnings
|
|
22
|
+
*
|
|
23
|
+
* 安全:validator 已拦 path traversal / 保留 name / 大小 / 格式;extractPluginZip
|
|
24
|
+
* 二次防御解压路径不出 staging dir;DELETE 校验 name 合规 + 不撞保留前缀。
|
|
25
|
+
*
|
|
26
|
+
* 复用:multer memoryStorage pattern from server/api/assets.js:13-26
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import express from 'express';
|
|
30
|
+
import multer from 'multer';
|
|
31
|
+
|
|
32
|
+
import { validateProjectId, getProject } from '../projects/store.js';
|
|
33
|
+
import { guardProject } from './_guard.js';
|
|
34
|
+
import {
|
|
35
|
+
getUserPluginsRoot,
|
|
36
|
+
getProjectPluginsRoot,
|
|
37
|
+
listInstalledPluginsDetailed,
|
|
38
|
+
} from '../engine/agent/plugin-loader.js';
|
|
39
|
+
import { LIMITS } from '../lib/plugin-validator.js';
|
|
40
|
+
import {
|
|
41
|
+
installPluginToRoot,
|
|
42
|
+
uninstallFromRoot,
|
|
43
|
+
} from '../lib/plugin-install.js';
|
|
44
|
+
|
|
45
|
+
// 允许的上传 mime / 后缀(双轨:单 .md / zip)。multer fileFilter 拦其他类型。
|
|
46
|
+
const ALLOWED_MIME = new Set([
|
|
47
|
+
'application/zip',
|
|
48
|
+
'application/x-zip-compressed',
|
|
49
|
+
'application/octet-stream', // 部分 OS 上 .zip 拿不到准确 mime;按 extension 兜底校验
|
|
50
|
+
'text/markdown',
|
|
51
|
+
'text/x-markdown',
|
|
52
|
+
'text/plain', // 部分 OS 上 .md 报 text/plain
|
|
53
|
+
]);
|
|
54
|
+
const ALLOWED_EXT_RE = /\.(md|zip)$/i;
|
|
55
|
+
|
|
56
|
+
const upload = multer({
|
|
57
|
+
storage: multer.memoryStorage(),
|
|
58
|
+
limits: { fileSize: LIMITS.ZIP_MAX_BYTES },
|
|
59
|
+
// fileFilter 不 throw error(避免被 express generic error handler 截到返 500),
|
|
60
|
+
// 把 validation 错误存到 req 上 + reject 文件;handler 检查 req.fileValidationError 返 400
|
|
61
|
+
fileFilter: (req, file, cb) => {
|
|
62
|
+
const mimeOk = ALLOWED_MIME.has(file.mimetype);
|
|
63
|
+
const extOk = ALLOWED_EXT_RE.test(file.originalname || '');
|
|
64
|
+
if (mimeOk || extOk) return cb(null, true);
|
|
65
|
+
req.fileValidationError = `不支持的文件类型 (mime=${file.mimetype} name=${file.originalname}) — 仅接受 .md 或 .zip`;
|
|
66
|
+
cb(null, false);
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/** 统一处理 multer 阶段 reject:fileValidationError 优先于 no-file */
|
|
71
|
+
function rejectInvalidFile(req, res) {
|
|
72
|
+
if (req.fileValidationError) {
|
|
73
|
+
res.status(400).json({ error: req.fileValidationError });
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (!req.file) {
|
|
77
|
+
res.status(400).json({ error: 'no file (field name: file)' });
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Router 1:用户级(挂 /api/plugins) ──
|
|
84
|
+
//
|
|
85
|
+
// 2026-07-30 起每个用户一个根(~/.nodesign/plugins/<userId>/)。之前是全站一个
|
|
86
|
+
// 共享目录:任何登录用户装的 plugin 会加载进所有人的 agent 会话,也能删别人的。
|
|
87
|
+
// 这里的 req.user 由 authGuard 挂(登录墙关闭时是 `_anon`),拿不到就 401 —— 没有
|
|
88
|
+
// 身份就没有"用户级"可言,不能退回共享根。
|
|
89
|
+
|
|
90
|
+
export const userPluginsRouter = express.Router();
|
|
91
|
+
|
|
92
|
+
/** 取当前请求者的 plugin 根;没有合法身份时直接回 401 并返 null */
|
|
93
|
+
function userRootOf(req, res) {
|
|
94
|
+
const root = getUserPluginsRoot(req.user?.id);
|
|
95
|
+
if (!root) {
|
|
96
|
+
res.status(401).json({ error: 'unauthorized' });
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return root;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
userPluginsRouter.get('/', async (req, res, next) => {
|
|
103
|
+
try {
|
|
104
|
+
const root = userRootOf(req, res);
|
|
105
|
+
if (!root) return;
|
|
106
|
+
const plugins = await listInstalledPluginsDetailed(root);
|
|
107
|
+
res.json({ plugins });
|
|
108
|
+
} catch (err) { next(err); }
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
userPluginsRouter.post('/install', upload.single('file'), async (req, res, next) => {
|
|
112
|
+
try {
|
|
113
|
+
const root = userRootOf(req, res);
|
|
114
|
+
if (!root) return;
|
|
115
|
+
if (rejectInvalidFile(req, res)) return;
|
|
116
|
+
const force = req.query.force === 'true';
|
|
117
|
+
const result = await installPluginToRoot(req.file.buffer, root, { force });
|
|
118
|
+
res.status(result.status).json(result.body);
|
|
119
|
+
} catch (err) { next(err); }
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
userPluginsRouter.delete('/:name', async (req, res, next) => {
|
|
123
|
+
try {
|
|
124
|
+
const root = userRootOf(req, res);
|
|
125
|
+
if (!root) return;
|
|
126
|
+
const result = await uninstallFromRoot(root, req.params.name);
|
|
127
|
+
res.status(result.status).json(result.body);
|
|
128
|
+
} catch (err) { next(err); }
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ── Router 2:project 级(挂 /api/projects,跟 assets/turn 同前缀) ──
|
|
132
|
+
|
|
133
|
+
export const projectPluginsRouter = express.Router();
|
|
134
|
+
|
|
135
|
+
projectPluginsRouter.get('/:pid/plugins', async (req, res, next) => {
|
|
136
|
+
try {
|
|
137
|
+
if (!guardProject(req, res)) return;
|
|
138
|
+
const root = getProjectPluginsRoot(req.params.pid);
|
|
139
|
+
const plugins = await listInstalledPluginsDetailed(root);
|
|
140
|
+
res.json({ plugins });
|
|
141
|
+
} catch (err) { next(err); }
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
projectPluginsRouter.post('/:pid/plugins/install', upload.single('file'), async (req, res, next) => {
|
|
145
|
+
try {
|
|
146
|
+
if (!guardProject(req, res)) return;
|
|
147
|
+
if (rejectInvalidFile(req, res)) return;
|
|
148
|
+
const force = req.query.force === 'true';
|
|
149
|
+
const root = getProjectPluginsRoot(req.params.pid);
|
|
150
|
+
const result = await installPluginToRoot(req.file.buffer, root, { force });
|
|
151
|
+
res.status(result.status).json(result.body);
|
|
152
|
+
} catch (err) { next(err); }
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
projectPluginsRouter.delete('/:pid/plugins/:name', async (req, res, next) => {
|
|
156
|
+
try {
|
|
157
|
+
if (!guardProject(req, res)) return;
|
|
158
|
+
const root = getProjectPluginsRoot(req.params.pid);
|
|
159
|
+
const result = await uninstallFromRoot(root, req.params.name);
|
|
160
|
+
res.status(result.status).json(result.body);
|
|
161
|
+
} catch (err) { next(err); }
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
export default { userPluginsRouter, projectPluginsRouter };
|