@xiaobuyu/nodesign 0.0.1
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 +661 -0
- package/README.md +140 -0
- package/bin/nodesign.js +176 -0
- package/package.json +104 -0
- package/server/api/README.md +45 -0
- package/server/api/_guard.js +75 -0
- package/server/api/admin.js +187 -0
- package/server/api/assets/docx-page.js +103 -0
- package/server/api/assets/helpers.js +22 -0
- package/server/api/assets/notes.js +144 -0
- package/server/api/assets.js +896 -0
- package/server/api/board.js +57 -0
- package/server/api/browse.js +138 -0
- package/server/api/canvas.js +444 -0
- package/server/api/chatai.js +324 -0
- package/server/api/exports/build-standalone.js +980 -0
- package/server/api/exports/cards.js +118 -0
- package/server/api/exports/docx-pdf.js +46 -0
- package/server/api/exports/export-page.js +142 -0
- package/server/api/exports/handoff.js +147 -0
- package/server/api/exports.js +690 -0
- package/server/api/instruction.js +75 -0
- package/server/api/local.js +115 -0
- package/server/api/me.js +116 -0
- package/server/api/memory.js +185 -0
- package/server/api/pending-changes.js +366 -0
- package/server/api/plugins.js +164 -0
- package/server/api/projects.js +220 -0
- package/server/api/publish.js +64 -0
- package/server/api/recent.js +73 -0
- package/server/api/sessions.js +589 -0
- package/server/api/skills.js +77 -0
- package/server/api/standalone-fit.js +168 -0
- package/server/api/turn-compose.js +180 -0
- package/server/api/turn-inflight.js +44 -0
- package/server/api/turn.js +669 -0
- package/server/auth/README.md +17 -0
- package/server/auth/middleware.js +138 -0
- package/server/auth/origin-guard.js +71 -0
- package/server/auth/session.js +108 -0
- package/server/auth/tier.js +113 -0
- package/server/auth/users-store.js +348 -0
- package/server/edit/README.md +29 -0
- package/server/engine/README.md +113 -0
- package/server/engine/agent/agent-shared.js +550 -0
- package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -0
- package/server/engine/agent/auto-mode-rules.js +78 -0
- package/server/engine/agent/context.js +418 -0
- package/server/engine/agent/events.js +464 -0
- package/server/engine/agent/hooks/canvas-validate.js +199 -0
- package/server/engine/agent/hooks/failure.js +120 -0
- package/server/engine/agent/hooks/file-events.js +75 -0
- package/server/engine/agent/hooks/lifecycle.js +233 -0
- package/server/engine/agent/hooks/post-canvas-focus.js +80 -0
- package/server/engine/agent/hooks/post-guidance.js +206 -0
- package/server/engine/agent/hooks/post-subagent-report.js +104 -0
- package/server/engine/agent/hooks/post-trim.js +58 -0
- package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -0
- package/server/engine/agent/hooks/pre-defaults.js +84 -0
- package/server/engine/agent/hooks/pre-injectors.js +277 -0
- package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -0
- package/server/engine/agent/hooks/pre-starter-files.js +61 -0
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -0
- package/server/engine/agent/hooks/site-validate.js +94 -0
- package/server/engine/agent/hooks/tool-prompts.js +31 -0
- package/server/engine/agent/hooks/turn-state-memory.js +56 -0
- package/server/engine/agent/hooks/user-prompt-submit.js +215 -0
- package/server/engine/agent/hooks.js +354 -0
- package/server/engine/agent/init-contract.js +69 -0
- package/server/engine/agent/isolation.js +159 -0
- package/server/engine/agent/model-context.js +359 -0
- package/server/engine/agent/model-table.js +292 -0
- package/server/engine/agent/plugin-loader.js +266 -0
- package/server/engine/agent/prompts/nodesign-prelude.md +336 -0
- package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -0
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -0
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -0
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -0
- package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -0
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -0
- package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -0
- package/server/engine/agent/prompts/tools/site-reference.md +106 -0
- package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -0
- package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -0
- package/server/engine/agent/session-loop.js +1024 -0
- package/server/engine/agent/session-model.js +164 -0
- package/server/engine/agent/skill.js +204 -0
- package/server/engine/agent/system-prompts.js +86 -0
- package/server/engine/agent/task-events.js +78 -0
- package/server/engine/agents/ds-extractor.md +132 -0
- package/server/engine/agents/explorer.md +190 -0
- package/server/engine/agents/index.js +230 -0
- package/server/engine/agents/schemas/design-system.json +156 -0
- package/server/engine/agents/schemas/tweak-schema.json +113 -0
- package/server/engine/agents/tweak-proposer.md +127 -0
- package/server/engine/agents/vision-checker.md +254 -0
- package/server/engine/browse/capture.js +422 -0
- package/server/engine/browse/card.js +137 -0
- package/server/engine/browse/handover.js +59 -0
- package/server/engine/browse/page-digest.js +119 -0
- package/server/engine/browse/refs.js +196 -0
- package/server/engine/browse/registry.js +307 -0
- package/server/engine/browse/screencast.js +170 -0
- package/server/engine/browse/state.js +121 -0
- package/server/engine/chatai/chat-log.js +79 -0
- package/server/engine/chatai/index.js +193 -0
- package/server/engine/chatai/openai-compat.js +152 -0
- package/server/engine/chatai/orchestrate.js +309 -0
- package/server/engine/chatai/perform.js +40 -0
- package/server/engine/chatai/summarize.js +79 -0
- package/server/engine/mcp/capability-gate.js +52 -0
- package/server/engine/mcp/index.js +339 -0
- package/server/engine/mcp/param-sanitizer.js +142 -0
- package/server/engine/mcp/tools/arrange-on-board.js +124 -0
- package/server/engine/mcp/tools/artifact-session.js +278 -0
- package/server/engine/mcp/tools/browse-computer.js +421 -0
- package/server/engine/mcp/tools/browse-find-batch.js +183 -0
- package/server/engine/mcp/tools/browse-screenshot.js +157 -0
- package/server/engine/mcp/tools/browse.js +480 -0
- package/server/engine/mcp/tools/build-docx.js +101 -0
- package/server/engine/mcp/tools/clear-pending-changes.js +99 -0
- package/server/engine/mcp/tools/create-on-board.js +155 -0
- package/server/engine/mcp/tools/crystallize-skill.js +168 -0
- package/server/engine/mcp/tools/deliver-files.js +0 -0
- package/server/engine/mcp/tools/explain-style.js +237 -0
- package/server/engine/mcp/tools/export-handoff.js +133 -0
- package/server/engine/mcp/tools/expose-tweaks.js +149 -0
- package/server/engine/mcp/tools/generate-image.js +842 -0
- package/server/engine/mcp/tools/get-computed-styles.js +164 -0
- package/server/engine/mcp/tools/get-pending-changes.js +205 -0
- package/server/engine/mcp/tools/h3box-ssh.js +84 -0
- package/server/engine/mcp/tools/helpers/acquire-page.js +82 -0
- package/server/engine/mcp/tools/helpers/motion-lab.js +554 -0
- package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -0
- package/server/engine/mcp/tools/helpers/perception-page.js +215 -0
- package/server/engine/mcp/tools/helpers/reference-download.js +103 -0
- package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -0
- package/server/engine/mcp/tools/helpers/rembg.js +273 -0
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -0
- package/server/engine/mcp/tools/highlight.js +68 -0
- package/server/engine/mcp/tools/list-pages.js +222 -0
- package/server/engine/mcp/tools/lookup-tags.js +59 -0
- package/server/engine/mcp/tools/navigate-to-page.js +60 -0
- package/server/engine/mcp/tools/organize-board.js +64 -0
- package/server/engine/mcp/tools/paint-still.js +434 -0
- package/server/engine/mcp/tools/pin-to-board.js +130 -0
- package/server/engine/mcp/tools/preview-deck.js +130 -0
- package/server/engine/mcp/tools/profile-scroll.js +305 -0
- package/server/engine/mcp/tools/publish-site.js +102 -0
- package/server/engine/mcp/tools/query-elements.js +195 -0
- package/server/engine/mcp/tools/read-board.js +103 -0
- package/server/engine/mcp/tools/read-document.js +93 -0
- package/server/engine/mcp/tools/read-page.js +264 -0
- package/server/engine/mcp/tools/read-tavern-json.js +143 -0
- package/server/engine/mcp/tools/record-decision.js +140 -0
- package/server/engine/mcp/tools/relate-on-board.js +110 -0
- package/server/engine/mcp/tools/remove-background.js +391 -0
- package/server/engine/mcp/tools/report-issue.js +115 -0
- package/server/engine/mcp/tools/roll-film.js +266 -0
- package/server/engine/mcp/tools/screenshot-docx.js +138 -0
- package/server/engine/mcp/tools/screenshot-url.js +195 -0
- package/server/engine/mcp/tools/screenshot.js +583 -0
- package/server/engine/mcp/tools/tier-gate.js +96 -0
- package/server/engine/mcp/tools/trace-motion.js +215 -0
- package/server/engine/mcp/tools/web-search.js +548 -0
- package/server/engine/motion/inventory.js +349 -0
- package/server/engine/perception/session.js +235 -0
- package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -0
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -0
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -0
- package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -0
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -0
- 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 -0
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -0
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -0
- package/server/engine/runs/active-runs.js +698 -0
- package/server/engine/runs/live-turn.js +252 -0
- package/server/engine/runs/store.js +364 -0
- package/server/engine/runs/turn-relay.js +217 -0
- package/server/engine/runtime/workspace.js +164 -0
- package/server/index.js +235 -0
- package/server/lib/artifact-file-path.js +80 -0
- package/server/lib/artifact-target.js +284 -0
- package/server/lib/asset-refs.js +239 -0
- package/server/lib/async-queue.js +104 -0
- package/server/lib/auto-relations.js +130 -0
- package/server/lib/binding-types.js +60 -0
- package/server/lib/board-kind-sizes.js +62 -0
- package/server/lib/board-relations.js +133 -0
- package/server/lib/browse-proxy.js +180 -0
- package/server/lib/canvas-id.js +36 -0
- package/server/lib/cover.js +227 -0
- package/server/lib/danbooru-tags.js +291 -0
- package/server/lib/doc-extract.js +156 -0
- package/server/lib/docx/build-from-source.js +260 -0
- package/server/lib/docx/build.js +490 -0
- package/server/lib/docx/dump-styles.js +287 -0
- package/server/lib/docx/fonts/nodesign-cjk.conf +164 -0
- package/server/lib/docx/merge-runs.js +129 -0
- package/server/lib/docx/numbering.js +218 -0
- package/server/lib/docx/order.js +152 -0
- package/server/lib/docx/rawzip.js +172 -0
- package/server/lib/docx/render.js +92 -0
- package/server/lib/docx/text-lint.js +225 -0
- package/server/lib/docx/tokens.js +335 -0
- package/server/lib/docx/units.js +29 -0
- package/server/lib/docx/xml.js +291 -0
- package/server/lib/docx-pages.js +187 -0
- package/server/lib/export-collect.js +289 -0
- package/server/lib/export-package.js +210 -0
- package/server/lib/html-srcset.js +145 -0
- package/server/lib/image-variant.js +460 -0
- package/server/lib/ingress/forward-openai-chat.js +305 -0
- package/server/lib/ingress/openai-chat.js +491 -0
- package/server/lib/ingress/session-notice.js +66 -0
- package/server/lib/ingress/session-routes.js +68 -0
- package/server/lib/ingress/slot-probe.js +130 -0
- package/server/lib/ingress/upstream-billing.js +52 -0
- package/server/lib/ingress/upstream-fail-streak.js +72 -0
- package/server/lib/ingress/upstream-truncation.js +48 -0
- package/server/lib/issues-store.js +182 -0
- package/server/lib/kinds/deck.js +72 -0
- package/server/lib/kinds/docx.js +283 -0
- package/server/lib/kinds/file-kinds.js +113 -0
- package/server/lib/kinds/index.js +202 -0
- package/server/lib/kinds/site.js +161 -0
- package/server/lib/model-ingress.js +543 -0
- package/server/lib/moderation.js +255 -0
- package/server/lib/notice-store.js +103 -0
- package/server/lib/plugin-install.js +104 -0
- package/server/lib/plugin-validator.js +721 -0
- package/server/lib/publish-store.js +115 -0
- package/server/lib/quick-summary.js +52 -0
- package/server/lib/quota.js +270 -0
- package/server/lib/rate-window.js +29 -0
- package/server/lib/reference-assets.js +92 -0
- package/server/lib/region-shot.js +135 -0
- package/server/lib/safe-path.js +73 -0
- package/server/lib/sdk-session.js +33 -0
- package/server/lib/showcase-store.js +92 -0
- package/server/lib/site-publish.js +465 -0
- package/server/lib/ssrf-guard.js +432 -0
- package/server/lib/task-scan.js +158 -0
- package/server/lib/tavern-json.js +154 -0
- package/server/lib/video-variant.js +237 -0
- package/server/lib/workspace-path.js +33 -0
- package/server/ops/fix-sdk-musl.mjs +40 -0
- package/server/ops/install-macos-fonts.sh +114 -0
- package/server/ops/macos-fonts.conf +336 -0
- package/server/ops/sandbox-shim/bwrap +57 -0
- package/server/projects/assets-summary.js +119 -0
- package/server/projects/auto-name.js +53 -0
- package/server/projects/board-store.js +640 -0
- package/server/projects/move-entry.js +103 -0
- package/server/projects/store.js +270 -0
- package/server/projects/ui-config.js +54 -0
- package/server/projects/workspace-templates.js +53 -0
- package/server/projects/workspace.js +1025 -0
- package/server/runtime/capabilities.js +153 -0
- package/server/runtime/local-config.js +177 -0
- package/server/runtime/local-env.js +88 -0
- package/server/runtime/platform.js +342 -0
- package/server/runtime/profile.js +72 -0
- package/server/services/rembg-launcher.js +313 -0
- package/server/services/rembg-service.py +334 -0
- package/server/shared/README.md +25 -0
- package/server/shared/deck.js +54 -0
- package/server/shared/time.js +49 -0
- package/server/style-pipeline/README.md +53 -0
- package/server/ws/broker.js +36 -0
- package/server/ws/browse-channel.js +177 -0
- package/server/ws/index.js +386 -0
- package/web/README.md +89 -0
- package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +11 -0
- package/web/dist/assets/DeckWindow-4G0xX7P3.js +16 -0
- package/web/dist/assets/DocxWindow-BXQsoPkh.js +16 -0
- package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/web/dist/assets/SiteWindow-D6eS-2gM.js +18 -0
- package/web/dist/assets/again-D8tfgKKO.webp +0 -0
- package/web/dist/assets/caveat-nd-CP6HlsNg.woff2 +0 -0
- package/web/dist/assets/clock-xJhvfYpS.webp +0 -0
- package/web/dist/assets/film-cut-BBKMwH7q.webp +0 -0
- package/web/dist/assets/film-still-BZ_DL9Tt.webp +0 -0
- package/web/dist/assets/index-C0KUf6Wd.css +1 -0
- package/web/dist/assets/index-Cbl3ATvp.js +2125 -0
- package/web/dist/assets/ink-desk-BRbxeDsR.webp +0 -0
- package/web/dist/assets/ink-night-Bz4BT3XH.webp +0 -0
- package/web/dist/assets/ink-portrait-C0DM4V8Q.webp +0 -0
- package/web/dist/assets/longcang-regular-C0uvTuKa.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
- package/web/dist/assets/pending-edit-apply-DTjo2geW.js +31 -0
- package/web/dist/assets/question-8H4HSqxh.webp +0 -0
- package/web/dist/assets/reject-BaPJqElj.webp +0 -0
- package/web/dist/assets/rotate-cw-LMT3x1Zj.js +11 -0
- package/web/dist/assets/rp-portrait-CbrGpew2.webp +0 -0
- package/web/dist/assets/rp-street-C4jVKjxx.webp +0 -0
- package/web/dist/assets/square-dashed-mouse-pointer-CBoAkfhi.js +11 -0
- package/web/dist/assets/tangle-DIxHVNuT.webp +0 -0
- package/web/dist/assets/thisone-BylZKglV.webp +0 -0
- package/web/dist/assets/thumb-DO1wfK1I.webp +0 -0
- package/web/dist/index.html +18 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* server/auth/users-store.js — 用户与邀请码(2026-07-30 内测多用户)
|
|
3
|
+
*
|
|
4
|
+
* 复用 engine/runs/store.js 的 better-sqlite3 连接(与 projects/runs 同一个
|
|
5
|
+
* nodesign.db)。建表走仓里的既有范式:import 副作用式幂等 DDL。
|
|
6
|
+
*
|
|
7
|
+
* 密码:node 内置 crypto.scrypt,无新依赖。存储格式
|
|
8
|
+
* scrypt$<N>$<saltHex>$<hashHex>
|
|
9
|
+
* 参数变更时旧记录仍能按自记录的 N 校验。
|
|
10
|
+
*
|
|
11
|
+
* 邀请码:admin 生成,限次数/可过期;注册时事务内 used_count+1 防并发超发。
|
|
12
|
+
*
|
|
13
|
+
* bootstrapAuth()(index.js 启动时调,幂等):
|
|
14
|
+
* - users 空 && NODESIGN_AUTH_PASSWORD 存在 → 用该密码建 admin 账号
|
|
15
|
+
* (单密码墙 → 多用户的无感迁移:你用老密码 + 用户名 admin 重登即可)
|
|
16
|
+
* - projects.owner_id 为 NULL 的存量行 → 回填 admin(历史项目全归你)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import crypto from 'node:crypto';
|
|
20
|
+
import db from '../engine/runs/store.js';
|
|
21
|
+
import { PLANS, basicDefaultDailyUsd } from './tier.js';
|
|
22
|
+
import { platform } from '../runtime/platform.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 登录墙关闭时的请求者(HTTP / WS / 工具内 owner 反查三处共用同一个对象)。
|
|
26
|
+
* 形状对齐 rowToUser:role admin ⇒ tier admin ⇒ 全能力、免额度、免外审;没有 plan /
|
|
27
|
+
* moderationLevel ⇒ 档位默认值。id 进 projects.owner_id / runs.user_id 这些列,所以
|
|
28
|
+
* 必须稳定不变 —— 改了它,老项目在本地版里会「找不到」。
|
|
29
|
+
*/
|
|
30
|
+
export const LOCAL_OWNER = Object.freeze({ id: '_anon', username: 'anon', role: 'admin', dailyTokenLimit: null, disabled: false });
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 登录墙是否启用。
|
|
34
|
+
* - local profile:钉死关闭(单租户;服务只绑环回,见 runtime/profile.js)。users 表里有没有行都不看 ——
|
|
35
|
+
* 开发者用同一个 DB 切过 profile 也不会突然被一堵墙拦住。
|
|
36
|
+
* - hosted:有用户就启用;一个用户都没有且没密码 = 关闭(本地开发)。
|
|
37
|
+
*/
|
|
38
|
+
export function authEnabled() {
|
|
39
|
+
if (platform.isLocal) return false;
|
|
40
|
+
return countUsers() > 0 || (process.env.NODESIGN_AUTH_PASSWORD || '').length > 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
db.exec(`
|
|
44
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
45
|
+
id TEXT PRIMARY KEY,
|
|
46
|
+
username TEXT NOT NULL UNIQUE,
|
|
47
|
+
password_hash TEXT NOT NULL,
|
|
48
|
+
role TEXT NOT NULL DEFAULT 'user',
|
|
49
|
+
daily_token_limit INTEGER,
|
|
50
|
+
disabled INTEGER NOT NULL DEFAULT 0,
|
|
51
|
+
invite_code TEXT,
|
|
52
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
53
|
+
);
|
|
54
|
+
CREATE TABLE IF NOT EXISTS invites (
|
|
55
|
+
code TEXT PRIMARY KEY,
|
|
56
|
+
created_by TEXT,
|
|
57
|
+
max_uses INTEGER NOT NULL DEFAULT 1,
|
|
58
|
+
used_count INTEGER NOT NULL DEFAULT 0,
|
|
59
|
+
expires_at TEXT,
|
|
60
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
61
|
+
);
|
|
62
|
+
`);
|
|
63
|
+
|
|
64
|
+
// 老 DB 补列(幂等,同 projects/store.js 范式):07-31 限额口径从 token 换成
|
|
65
|
+
// 金额,per-user 覆盖也跟着换单位。daily_token_limit 保留不删 —— 它是老口径的
|
|
66
|
+
// 存量数据,删了就没法回溯当时给谁开过什么口子。
|
|
67
|
+
const userCols = new Set(db.prepare('PRAGMA table_info(users)').all().map(c => c.name));
|
|
68
|
+
if (!userCols.has('daily_cost_limit_usd')) {
|
|
69
|
+
db.exec('ALTER TABLE users ADD COLUMN daily_cost_limit_usd REAL');
|
|
70
|
+
console.log('[users-store] users.daily_cost_limit_usd column added');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 08-02 试用账号(简历上的通用邀请码):终身额度,烧满不刷新。非空即生效,
|
|
74
|
+
// 且**取代**日限而不是叠加 —— 试用要的是完整体验一晚,不是细水长流。
|
|
75
|
+
// 注册时从 invites.grant_lifetime_usd 复制过来,之后改码删码不影响已注册的号。
|
|
76
|
+
if (!userCols.has('lifetime_cost_limit_usd')) {
|
|
77
|
+
db.exec('ALTER TABLE users ADD COLUMN lifetime_cost_limit_usd REAL');
|
|
78
|
+
console.log('[users-store] users.lifetime_cost_limit_usd column added');
|
|
79
|
+
}
|
|
80
|
+
// 08-02 内容外审强度(见 lib/moderation.js):'off' | 'loose' | 'strict'。
|
|
81
|
+
// NULL = 跟随该账号的默认档(试用号 strict / 正式号 loose / admin off),
|
|
82
|
+
// 站主可 per-user 覆盖。列存的是覆盖值,不是最终值 —— 默认档改了,
|
|
83
|
+
// 没设过的号跟着走。
|
|
84
|
+
if (!userCols.has('moderation_level')) {
|
|
85
|
+
db.exec('ALTER TABLE users ADD COLUMN moderation_level TEXT');
|
|
86
|
+
console.log('[users-store] users.moderation_level column added');
|
|
87
|
+
}
|
|
88
|
+
// 08-20 外审档按模型通路拆成两个旋钮:moderation_level 只管订阅模型(Sonnet/Opus,
|
|
89
|
+
// 跑在站主账号上),moderation_level_api 管本地 qwen / 中转站那些走 ingress 的 API 行。
|
|
90
|
+
// 站主要的是"给朋友开 qwen 无审查"不必顺带放开 Sonnet、"收紧 Sonnet"不必顺带收紧 qwen。
|
|
91
|
+
// 迁移口径:已显式设过档位的号把现值**复制**进 API 旋钮 —— 迁移当天谁的行为都不变
|
|
92
|
+
// (否则昨天为 qwen 设了 off 的朋友,今天 qwen 突然变 loose),之后站主按人调。
|
|
93
|
+
if (!userCols.has('moderation_level_api')) {
|
|
94
|
+
db.exec('ALTER TABLE users ADD COLUMN moderation_level_api TEXT');
|
|
95
|
+
const n = db.prepare('UPDATE users SET moderation_level_api = moderation_level WHERE moderation_level IS NOT NULL').run().changes;
|
|
96
|
+
console.log(`[users-store] users.moderation_level_api column added (copied ${n} explicit level(s) from moderation_level)`);
|
|
97
|
+
}
|
|
98
|
+
// 本地产线(roll_film/paint_still)批准制:admin 天生有,其余账号站主点开才有
|
|
99
|
+
if (!userCols.has('local_gen')) {
|
|
100
|
+
db.exec('ALTER TABLE users ADD COLUMN local_gen INTEGER NOT NULL DEFAULT 0');
|
|
101
|
+
console.log('[users-store] users.local_gen column added');
|
|
102
|
+
}
|
|
103
|
+
// 08-21 经营态转向:订阅 Claude 资格按账号发。新列默认 0(公开注册号只能用免费模型),
|
|
104
|
+
// **迁移时把已有用户全部置 1**(老号保留,用户说他手动关);邀请码注册的号拿 1。
|
|
105
|
+
if (!userCols.has('allow_subscription')) {
|
|
106
|
+
db.exec('ALTER TABLE users ADD COLUMN allow_subscription INTEGER NOT NULL DEFAULT 0');
|
|
107
|
+
const n = db.prepare('UPDATE users SET allow_subscription = 1').run().changes;
|
|
108
|
+
console.log(`[users-store] users.allow_subscription column added(存量 ${n} 个账号置 1)`);
|
|
109
|
+
}
|
|
110
|
+
// 08-21 晚 账号档位真相源(auth/tier.js):users.plan ∈ 'pro' | 'basic',admin 由 role 派生。
|
|
111
|
+
// 取代 allow_subscription 那个单能力布尔 —— 订阅资格、生图、发布、外审默认档全从档位派生,
|
|
112
|
+
// 消费方问能力不问字段。迁移口径一比一复制(allow_subscription=1 → pro,0 → basic),
|
|
113
|
+
// 迁移当天谁的行为都不变。allow_subscription 列**退役**:不读不写,留着只是 SQLite 不爱删列。
|
|
114
|
+
if (!userCols.has('plan')) {
|
|
115
|
+
db.exec("ALTER TABLE users ADD COLUMN plan TEXT NOT NULL DEFAULT 'basic'");
|
|
116
|
+
const n = db.prepare("UPDATE users SET plan = 'pro' WHERE allow_subscription = 1").run().changes;
|
|
117
|
+
console.log(`[users-store] users.plan column added(${n} 个账号按 allow_subscription 迁成 pro,其余 basic)`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const inviteCols = new Set(db.prepare('PRAGMA table_info(invites)').all().map(c => c.name));
|
|
121
|
+
if (!inviteCols.has('grant_lifetime_usd')) {
|
|
122
|
+
db.exec('ALTER TABLE invites ADD COLUMN grant_lifetime_usd REAL');
|
|
123
|
+
console.log('[users-store] invites.grant_lifetime_usd column added');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── 密码 ──
|
|
127
|
+
|
|
128
|
+
const SCRYPT_N = 16384;
|
|
129
|
+
|
|
130
|
+
export function hashPassword(password) {
|
|
131
|
+
const salt = crypto.randomBytes(16).toString('hex');
|
|
132
|
+
const hash = crypto.scryptSync(String(password), salt, 64, { N: SCRYPT_N, r: 8, p: 1 }).toString('hex');
|
|
133
|
+
return `scrypt$${SCRYPT_N}$${salt}$${hash}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function verifyPassword(password, stored) {
|
|
137
|
+
const m = /^scrypt\$(\d+)\$([0-9a-f]+)\$([0-9a-f]+)$/.exec(stored || '');
|
|
138
|
+
if (!m) return false;
|
|
139
|
+
const [, nStr, salt, hashHex] = m;
|
|
140
|
+
try {
|
|
141
|
+
const calc = crypto.scryptSync(String(password), salt, hashHex.length / 2, { N: Number(nStr), r: 8, p: 1 });
|
|
142
|
+
return crypto.timingSafeEqual(calc, Buffer.from(hashHex, 'hex'));
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── 用户 ──
|
|
149
|
+
|
|
150
|
+
const USERNAME_RE = /^[A-Za-z0-9_一-鿿-]{2,32}$/;
|
|
151
|
+
|
|
152
|
+
export function validUsername(name) {
|
|
153
|
+
return typeof name === 'string' && USERNAME_RE.test(name);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function newUserId() {
|
|
157
|
+
return `u_${Date.now().toString(36)}_${crypto.randomBytes(3).toString('hex')}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function rowToUser(row) {
|
|
161
|
+
if (!row) return null;
|
|
162
|
+
return {
|
|
163
|
+
id: row.id,
|
|
164
|
+
username: row.username,
|
|
165
|
+
role: row.role,
|
|
166
|
+
dailyCostLimitUsd: row.daily_cost_limit_usd ?? null,
|
|
167
|
+
lifetimeCostLimitUsd: row.lifetime_cost_limit_usd ?? null,
|
|
168
|
+
dailyTokenLimit: row.daily_token_limit ?? null, // 老口径存量,只读不用
|
|
169
|
+
moderationLevel: row.moderation_level ?? null, // 订阅模型的外审档;null = 跟随默认档
|
|
170
|
+
moderationLevelApi: row.moderation_level_api ?? null, // 本地 qwen / 中转站的外审档;null = 跟随默认档
|
|
171
|
+
allowLocalGen: !!row.local_gen, // 本地产线逐人批准(叠在档位之上;见 auth/tier.js localGenApproved)
|
|
172
|
+
plan: row.plan === 'pro' ? 'pro' : 'basic', // 档位真相源:pro(邀请码)| basic(公开注册);admin 看 role。能力问 auth/tier.js
|
|
173
|
+
disabled: !!row.disabled,
|
|
174
|
+
inviteCode: row.invite_code || null,
|
|
175
|
+
createdAt: row.created_at,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// requestUser 每个请求都要查 —— 60s 内存缓存压掉热路径的 SQLite 读。
|
|
180
|
+
// disable 用户最迟 60s 生效,内测语境可接受。
|
|
181
|
+
const userCache = new Map(); // id → { user, at }
|
|
182
|
+
const USER_CACHE_MS = 60_000;
|
|
183
|
+
|
|
184
|
+
export function getUserById(id) {
|
|
185
|
+
// 登录墙关闭时 owner 是 LOCAL_OWNER,它不在表里。不在这里接住,session-loop 的订阅资格断言 /
|
|
186
|
+
// tier-gate / paint_still 这些按 ownerId 反查的地方会拿到 null ⇒ fail-closed ⇒ 本地版什么都跑不了。
|
|
187
|
+
// 登录墙开着时 '_anon' 不是合法身份(表里没有这行),照常查表得 null。
|
|
188
|
+
if (id === LOCAL_OWNER.id && !authEnabled()) return LOCAL_OWNER;
|
|
189
|
+
const hit = userCache.get(id);
|
|
190
|
+
if (hit && Date.now() - hit.at < USER_CACHE_MS) return hit.user;
|
|
191
|
+
const user = rowToUser(db.prepare('SELECT * FROM users WHERE id = ?').get(id));
|
|
192
|
+
userCache.set(id, { user, at: Date.now() });
|
|
193
|
+
return user;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function getUserByUsername(username) {
|
|
197
|
+
return rowToUser(db.prepare('SELECT * FROM users WHERE username = ?').get(username));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** 登录用:要拿 hash 比对,不走 rowToUser(hash 不出模块) */
|
|
201
|
+
export function getCredential(username) {
|
|
202
|
+
const row = db.prepare('SELECT id, password_hash, disabled FROM users WHERE username = ?').get(username);
|
|
203
|
+
return row ? { id: row.id, passwordHash: row.password_hash, disabled: !!row.disabled } : null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function createUser({ username, password, role = 'user', inviteCode = null, lifetimeCostLimitUsd = null, dailyCostLimitUsd = null, plan = 'basic' }) {
|
|
207
|
+
if (!PLANS.includes(plan)) throw new Error(`createUser: plan 需为 ${PLANS.join('/')},收到 '${plan}'`);
|
|
208
|
+
const id = newUserId();
|
|
209
|
+
db.prepare(`INSERT INTO users (id, username, password_hash, role, invite_code, lifetime_cost_limit_usd, daily_cost_limit_usd, plan) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
210
|
+
.run(id, username, hashPassword(password), role, inviteCode, lifetimeCostLimitUsd, dailyCostLimitUsd, plan);
|
|
211
|
+
return getUserById(id);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function listUsers() {
|
|
215
|
+
return db.prepare('SELECT * FROM users ORDER BY created_at ASC').all().map(rowToUser);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function updateUser(id, { disabled, dailyTokenLimit, dailyCostLimitUsd, lifetimeCostLimitUsd, role, moderationLevel, moderationLevelApi, localGen, plan } = {}) {
|
|
219
|
+
const sets = [];
|
|
220
|
+
const args = [];
|
|
221
|
+
if (disabled !== undefined) { sets.push('disabled = ?'); args.push(disabled ? 1 : 0); }
|
|
222
|
+
if (moderationLevel !== undefined) { sets.push('moderation_level = ?'); args.push(moderationLevel ?? null); }
|
|
223
|
+
if (moderationLevelApi !== undefined) { sets.push('moderation_level_api = ?'); args.push(moderationLevelApi ?? null); }
|
|
224
|
+
if (localGen !== undefined) { sets.push('local_gen = ?'); args.push(localGen ? 1 : 0); }
|
|
225
|
+
if (plan !== undefined) {
|
|
226
|
+
if (!PLANS.includes(plan)) throw new Error(`updateUser: plan 需为 ${PLANS.join('/')},收到 '${plan}'`);
|
|
227
|
+
sets.push('plan = ?'); args.push(plan);
|
|
228
|
+
}
|
|
229
|
+
if (dailyCostLimitUsd !== undefined) { sets.push('daily_cost_limit_usd = ?'); args.push(dailyCostLimitUsd ?? null); }
|
|
230
|
+
if (lifetimeCostLimitUsd !== undefined) { sets.push('lifetime_cost_limit_usd = ?'); args.push(lifetimeCostLimitUsd ?? null); }
|
|
231
|
+
if (dailyTokenLimit !== undefined) { sets.push('daily_token_limit = ?'); args.push(dailyTokenLimit ?? null); }
|
|
232
|
+
if (role !== undefined) { sets.push('role = ?'); args.push(role); }
|
|
233
|
+
if (!sets.length) return getUserById(id);
|
|
234
|
+
db.prepare(`UPDATE users SET ${sets.join(', ')} WHERE id = ?`).run(...args, id);
|
|
235
|
+
userCache.delete(id);
|
|
236
|
+
return getUserById(id);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function countUsers() {
|
|
240
|
+
return db.prepare('SELECT COUNT(*) AS n FROM users').get().n;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── 邀请码 ──
|
|
244
|
+
|
|
245
|
+
export function createInvite({ createdBy = null, maxUses = 1, expiresAt = null, grantLifetimeUsd = null } = {}) {
|
|
246
|
+
// 可读形态:nd-xxxxxxxx(发群里手输不痛苦;去掉易混字符)
|
|
247
|
+
const alphabet = 'abcdefghjkmnpqrstuvwxyz23456789';
|
|
248
|
+
let code = 'nd-';
|
|
249
|
+
for (const b of crypto.randomBytes(8)) code += alphabet[b % alphabet.length];
|
|
250
|
+
db.prepare('INSERT INTO invites (code, created_by, max_uses, expires_at, grant_lifetime_usd) VALUES (?, ?, ?, ?, ?)')
|
|
251
|
+
.run(code, createdBy, maxUses, expiresAt, grantLifetimeUsd);
|
|
252
|
+
return getInvite(code);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function getInvite(code) {
|
|
256
|
+
return db.prepare('SELECT * FROM invites WHERE code = ?').get(code) || null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 改邀请码的可用次数(控制台用)。语义是**总次数**不是剩余次数 ——
|
|
261
|
+
* 改到 ≤ used_count 等于立即封死这个码(简历码泄漏时的一键止血)。
|
|
262
|
+
*/
|
|
263
|
+
export function updateInvite(code, { maxUses } = {}) {
|
|
264
|
+
if (maxUses !== undefined) {
|
|
265
|
+
db.prepare('UPDATE invites SET max_uses = ? WHERE code = ?').run(maxUses, code);
|
|
266
|
+
}
|
|
267
|
+
return getInvite(code);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function listInvites() {
|
|
271
|
+
return db.prepare('SELECT * FROM invites ORDER BY created_at DESC').all();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** 开放注册开关(.env NODESIGN_OPEN_REGISTRATION=1)。关着时没邀请码照旧拒 */
|
|
275
|
+
export function openRegistrationEnabled() {
|
|
276
|
+
return /^(1|true|yes)$/i.test(String(process.env.NODESIGN_OPEN_REGISTRATION || ''));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** 邀请码注册的号默认**每日**额度(美元,08-21 晚用户拍板 $20)。env NODESIGN_INVITE_DEFAULT_DAILY_USD;0 或非法 = 不写(走全局默认日限) */
|
|
280
|
+
export function defaultInviteDailyUsd(env = process.env) {
|
|
281
|
+
const raw = env.NODESIGN_INVITE_DEFAULT_DAILY_USD;
|
|
282
|
+
if (raw === undefined || raw === '') return 20;
|
|
283
|
+
const v = Number(raw);
|
|
284
|
+
return Number.isFinite(v) && v > 0 ? v : null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 注册主流程(08-21 起两条路):
|
|
289
|
+
* - 带邀请码:校验 + 消耗 + 建用户,落 **pro 档**(plan='pro')+ 每日 $20(默认)+ 码上的终身额度(写了才有,取代日限)
|
|
290
|
+
* - 不带邀请码:开放注册开着才放行,落 **basic 档**(plan='basic':只能用免费模型/搜索,
|
|
291
|
+
* 不开生图、不开发布;能力表在 auth/tier.js)
|
|
292
|
+
* 单事务(used_count+1 与 INSERT 原子,两人同抢最后一个名额只有一个成)。失败抛带 .code 的 Error。
|
|
293
|
+
*/
|
|
294
|
+
export const registerUser = db.transaction(({ username, password, inviteCode }) => {
|
|
295
|
+
if (!validUsername(username)) {
|
|
296
|
+
throw Object.assign(new Error('用户名 2-32 位,仅限字母数字下划线连字符和中文'), { code: 'BAD_USERNAME' });
|
|
297
|
+
}
|
|
298
|
+
if (typeof password !== 'string' || password.length < 8) {
|
|
299
|
+
throw Object.assign(new Error('密码至少 8 位'), { code: 'BAD_PASSWORD' });
|
|
300
|
+
}
|
|
301
|
+
if (getUserByUsername(username)) {
|
|
302
|
+
throw Object.assign(new Error('用户名已被使用'), { code: 'USERNAME_TAKEN' });
|
|
303
|
+
}
|
|
304
|
+
const code = String(inviteCode || '').trim();
|
|
305
|
+
if (!code) {
|
|
306
|
+
if (!openRegistrationEnabled()) throw Object.assign(new Error('邀请码无效'), { code: 'BAD_INVITE' });
|
|
307
|
+
// basic:每人每天 $5 总额度(Go 付费行按表价 + 生图 $0.20/张 同一本账;Ox 免费行不计)
|
|
308
|
+
return createUser({ username, password, role: 'user', inviteCode: null, plan: 'basic', dailyCostLimitUsd: basicDefaultDailyUsd() });
|
|
309
|
+
}
|
|
310
|
+
const inv = getInvite(code);
|
|
311
|
+
if (!inv) throw Object.assign(new Error('邀请码无效'), { code: 'BAD_INVITE' });
|
|
312
|
+
if (inv.expires_at && new Date(inv.expires_at).getTime() < Date.now()) {
|
|
313
|
+
throw Object.assign(new Error('邀请码已过期'), { code: 'INVITE_EXPIRED' });
|
|
314
|
+
}
|
|
315
|
+
if (inv.used_count >= inv.max_uses) {
|
|
316
|
+
throw Object.assign(new Error('邀请码已用完'), { code: 'INVITE_EXHAUSTED' });
|
|
317
|
+
}
|
|
318
|
+
db.prepare('UPDATE invites SET used_count = used_count + 1 WHERE code = ?').run(inv.code);
|
|
319
|
+
return createUser({
|
|
320
|
+
username, password, role: 'user', inviteCode: inv.code,
|
|
321
|
+
// 花费上限(不是档位):终身额度只在码上写了才有(非空即取代日限);日限默认 $20(08-21 晚起,以前跟全局默认 $50)
|
|
322
|
+
lifetimeCostLimitUsd: inv.grant_lifetime_usd ?? null,
|
|
323
|
+
dailyCostLimitUsd: defaultInviteDailyUsd(),
|
|
324
|
+
plan: 'pro',
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// ── 启动 bootstrap(index.js 调,幂等)──
|
|
329
|
+
|
|
330
|
+
export function bootstrapAuth() {
|
|
331
|
+
let admin = db.prepare("SELECT * FROM users WHERE role = 'admin' ORDER BY created_at ASC").get();
|
|
332
|
+
if (!admin && countUsers() === 0) {
|
|
333
|
+
const pw = process.env.NODESIGN_AUTH_PASSWORD || '';
|
|
334
|
+
if (pw) {
|
|
335
|
+
const created = createUser({ username: 'admin', password: pw, role: 'admin' });
|
|
336
|
+
admin = db.prepare('SELECT * FROM users WHERE id = ?').get(created.id);
|
|
337
|
+
console.log('[auth] bootstrap: 用 NODESIGN_AUTH_PASSWORD 创建了 admin 账号(用户名 admin)');
|
|
338
|
+
} else {
|
|
339
|
+
console.warn('[auth] users 表为空且未配置 NODESIGN_AUTH_PASSWORD —— 登录墙关闭(仅限本地开发)');
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (admin) {
|
|
343
|
+
// 存量项目回填归属(owner_id 列由 projects/store.js 幂等 ALTER 加上)
|
|
344
|
+
const r = db.prepare('UPDATE projects SET owner_id = ? WHERE owner_id IS NULL').run(admin.id);
|
|
345
|
+
if (r.changes > 0) console.log(`[auth] bootstrap: ${r.changes} 个存量项目归属到 admin`);
|
|
346
|
+
}
|
|
347
|
+
return admin ? rowToUser(admin) : null;
|
|
348
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# server/edit/ — 元素级 Edit Pipeline
|
|
2
|
+
|
|
3
|
+
用户在产物 deck.html 上**选元素 → 自然语言指令 → AI 修改**。
|
|
4
|
+
|
|
5
|
+
## 为什么这个模块在后端,不在前端
|
|
6
|
+
|
|
7
|
+
前端壳负责"选哪个元素"和"输入什么指令",但**修改决策**需要 deck 上下文(design-notes、token 系统、跨页一致性)——这些信息在 engine 的 run workspace 里。
|
|
8
|
+
|
|
9
|
+
设计倾向:edit 是 engine agent loop 的复用 + 一个 `edit_element` 工具集,不是独立服务。
|
|
10
|
+
|
|
11
|
+
## 颗粒度待组长定义
|
|
12
|
+
|
|
13
|
+
候选:
|
|
14
|
+
1. **轻量 DOM 编辑**:选元素 → 输 prompt → AI 改本元素 HTML/CSS → 替换
|
|
15
|
+
2. **AI 协同迭代**:可多轮对话,AI 提多个改法让用户选
|
|
16
|
+
3. **链式修改**:改一处可触发其他页同类元素同步(保持视觉一致)
|
|
17
|
+
|
|
18
|
+
颗粒度越粗后端越复杂。MVP 优先级取决于组长定义。
|
|
19
|
+
|
|
20
|
+
## 与 engine 共享
|
|
21
|
+
|
|
22
|
+
- 同一个 agent loop(`server/shared/agent-loop.js`)
|
|
23
|
+
- 同一个 LLM 客户端(Kimi K2.6)
|
|
24
|
+
- 同一个 workspace 沙盒
|
|
25
|
+
- 不同的 system prompt("你正在修改一份已生成的 deck")和工具集(少一些生成工具,多一些精修工具)
|
|
26
|
+
|
|
27
|
+
## 责任划分
|
|
28
|
+
|
|
29
|
+
负责人:你。新东西。等 engine MVP 跑通后启动。
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# server/engine/ — Skill Runner
|
|
2
|
+
|
|
3
|
+
Nodesign 后端核心。**包 Claude Agent SDK 的 query() 跑 agent 循环**,加载任意 SKILL.md。
|
|
4
|
+
|
|
5
|
+
## 设计哲学(2026-04-29 战略调整后)
|
|
6
|
+
|
|
7
|
+
- 不再造 agent loop 轮子 —— 包 Claude Agent SDK 的 `query()`(SDK 是 Claude Code 子进程的程序化包装)
|
|
8
|
+
- 通过 `ANTHROPIC_BASE_URL` env 透传给子进程,路由到 Kimi gateway(tokendance 等)
|
|
9
|
+
- 工具用 SDK 内置 23 个,按 Nodesign 业务 allowlist:`Read / Write / Edit / Glob / Grep / TodoWrite`
|
|
10
|
+
- Skill 是 `SKILL.md`(YAML frontmatter + Markdown body),body 直接当 systemPrompt
|
|
11
|
+
- Workspace 是沙盒(每 run 独占 `runs/<runId>/workspace/`),SDK `cwd` 指过去
|
|
12
|
+
- Run 状态机走 SQLite(`runs/store.js`),SDK 自己的 session 持久化关掉(`persistSession: false`)
|
|
13
|
+
|
|
14
|
+
## 当前目录结构
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
engine/
|
|
18
|
+
├── agent/ ★ Agent 模块(包 SDK)
|
|
19
|
+
│ ├── context.js AgentContext(runId / EventBus / abort / counters)
|
|
20
|
+
│ ├── events.js EventBus + 标准事件 schema
|
|
21
|
+
│ ├── session-loop.js ★ runSession:streamInput long-running query(生产入口)
|
|
22
|
+
│ ├── agent-shared.js 常量 + handleSDKMessage 翻译层(被 session-loop 复用)
|
|
23
|
+
│ ├── hooks.js SDK hooks(FileChanged / PreToolUse / PostToolUse 等)
|
|
24
|
+
│ ├── skill.js SKILL.md loader(frontmatter + body)+ ensureSkillStarterFiles
|
|
25
|
+
│ ├── prompts/ nodesign-prelude.md + tools/*.md(按需注入的胖文案)
|
|
26
|
+
│ └── _smoke.js 烟雾测试(无 key 也能跑非 LLM 部分)
|
|
27
|
+
│
|
|
28
|
+
├── skills/ 内置 skill 库
|
|
29
|
+
│ └── hello-world/SKILL.md P3 链路验证 skill
|
|
30
|
+
│
|
|
31
|
+
├── runs/
|
|
32
|
+
│ └── store.js SQLite 状态机(pending → running → succeeded/failed/cancelled)
|
|
33
|
+
│
|
|
34
|
+
├── runtime/
|
|
35
|
+
│ └── workspace.js 沙盒路径解析 + safeResolve + read/write/list 包装
|
|
36
|
+
│
|
|
37
|
+
└── _smoke.js 底层骨架烟雾测试(store + workspace)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 入口(待 P3 起 Express 后实现)
|
|
41
|
+
|
|
42
|
+
- HTTP:`POST /api/runs { skillId, brief }` → 异步返回 runId
|
|
43
|
+
- WebSocket:每 project 一条连接,订阅 EventBus 把事件推前端
|
|
44
|
+
- 内部:`runSession({ sessionId, projectId, sessionWorkspaceRoot, eventBus, inputQueue, skillId, ... })` from `agent/session-loop.js` —— streamInput 长 query,一个 session 复用 query handle 跑多 turn(cancel / setModel / rewindFiles / setPermissionMode 全靠它)
|
|
45
|
+
|
|
46
|
+
## 跑测试
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 不需要 gateway key,验证非 LLM 部分(EventBus / AgentContext / parseFrontmatter / loadSkill)
|
|
50
|
+
npm run smoke:agent
|
|
51
|
+
|
|
52
|
+
# 验证底层骨架(runs/store + runtime/workspace)
|
|
53
|
+
npm run smoke:engine
|
|
54
|
+
|
|
55
|
+
# Live LLM 调用(需要 NODESIGN_GATEWAY_KEY 在 .env)
|
|
56
|
+
NODESIGN_GATEWAY_KEY=xxx npm run smoke:agent
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## SDK 配置要点
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
63
|
+
|
|
64
|
+
query({
|
|
65
|
+
prompt: brief,
|
|
66
|
+
options: {
|
|
67
|
+
cwd: workspaceRoot, // 沙盒
|
|
68
|
+
abortController: ctx.abortController,
|
|
69
|
+
env: {
|
|
70
|
+
...process.env,
|
|
71
|
+
ANTHROPIC_BASE_URL: NODESIGN_GATEWAY_URL,
|
|
72
|
+
ANTHROPIC_API_KEY: NODESIGN_GATEWAY_KEY,
|
|
73
|
+
},
|
|
74
|
+
model: 'kimi-k2.6',
|
|
75
|
+
tools: ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'TodoWrite'],
|
|
76
|
+
allowedTools: [...同上...], // 自动允许,不弹权限
|
|
77
|
+
systemPrompt: skill.systemPrompt, // 我们自己拼,不用 claude_code preset
|
|
78
|
+
persistSession: false, // 不写 ~/.claude/projects 的 session
|
|
79
|
+
settingSources: [], // 不读外部 settings 文件
|
|
80
|
+
includePartialMessages: true, // 流增量
|
|
81
|
+
thinking: { type: 'adaptive' },
|
|
82
|
+
effort: 'medium',
|
|
83
|
+
maxTurns: 50,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 事件流(EventBus 标准 schema)
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
run.start
|
|
92
|
+
run.sdk.session { sessionId }
|
|
93
|
+
run.delta.text { round, text }
|
|
94
|
+
run.delta.thinking { round, text }
|
|
95
|
+
run.delta.tool_use { round, blockId, name, input }
|
|
96
|
+
run.delta.tool_result { round, blockId, name, ok, output | error }
|
|
97
|
+
run.compact_boundary { compactMetadata } // 上下文压缩点
|
|
98
|
+
run.status { status } // compacting | requesting | null
|
|
99
|
+
run.rate_limit { info }
|
|
100
|
+
run.cancelled { reason }
|
|
101
|
+
run.done { finalText, artifactPath?, snapshot }
|
|
102
|
+
run.error { message, code, stack }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
外层把 `eventBus.subscribe('*', handler)` 桥到 WebSocket / 审计日志 / 测试 buffer。
|
|
106
|
+
|
|
107
|
+
## 决定 / 不做的事
|
|
108
|
+
|
|
109
|
+
- ❌ 不消费 MCP(HANDOVER §11;Nodesign 暴露 REST/WS,不做 MCP client)
|
|
110
|
+
- ❌ 不开 Bash 工具(沙盒 cwd 隔离了,但 shell 越界风险高;P5+ 真要 chart 库时再开)
|
|
111
|
+
- ❌ 不开 WebFetch/WebSearch(P5 真做参考系统时再开,需要 SSRF 防护)
|
|
112
|
+
- ❌ 不开 Sub-agents/Task(P5+ 多方向探索可考虑,前期单 agent 单 run)
|
|
113
|
+
- ❌ 不用 SDK 的 `skills: ['xxx']` 自动加载(我们自己读 SKILL.md,给 systemPrompt 字段)
|