@hcu-lab.me/mcp-device 1.0.10 → 1.0.12
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/PRIVACY.md +21 -0
- package/README.md +57 -2
- package/SECURITY.md +67 -0
- package/config/revoked-approvals.json +1 -0
- package/control-plane/config/mcp-servers.example.toml +52 -0
- package/control-plane/config/mcp-servers.toml +52 -0
- package/control-plane/contracts/device-public-contract-v1.json +733 -0
- package/control-plane/scripts/GATEWAY-LICENSE.txt +7 -0
- package/control-plane/scripts/account-http.mjs +220 -0
- package/control-plane/scripts/account-store.mjs +368 -0
- package/control-plane/scripts/admin.mjs +68 -0
- package/control-plane/scripts/auth-session.mjs +411 -0
- package/control-plane/scripts/authenticated-mcp-wrapper.mjs +1281 -0
- package/control-plane/scripts/benchmark-command.mjs +9 -0
- package/control-plane/scripts/benchmark-mcp-catalog.mjs +174 -0
- package/control-plane/scripts/catalog-budget.mjs +87 -0
- package/control-plane/scripts/check-device-public-contract.mjs +20 -0
- package/control-plane/scripts/custom-tools/index.mjs +221 -0
- package/control-plane/scripts/custom-tools/path-utils.mjs +117 -0
- package/control-plane/scripts/custom-tools/response-utils.mjs +34 -0
- package/control-plane/scripts/dashboard-http.mjs +598 -0
- package/control-plane/scripts/device-access-policy.mjs +205 -0
- package/control-plane/scripts/device-admin.mjs +43 -0
- package/control-plane/scripts/device-audit.mjs +63 -0
- package/control-plane/scripts/device-broker.mjs +1461 -0
- package/control-plane/scripts/device-execution-tool-definitions.mjs +217 -0
- package/control-plane/scripts/device-inventory.mjs +128 -0
- package/control-plane/scripts/device-pairing-http.mjs +109 -0
- package/control-plane/scripts/device-pairing-store.mjs +245 -0
- package/control-plane/scripts/device-public-contract.mjs +165 -0
- package/control-plane/scripts/device-release.mjs +95 -0
- package/control-plane/scripts/device-secure-transport.mjs +128 -0
- package/control-plane/scripts/device-store.mjs +283 -0
- package/control-plane/scripts/device-usage.mjs +657 -0
- package/control-plane/scripts/embedded-device.mjs +149 -0
- package/control-plane/scripts/enduser-ui.mjs +45 -0
- package/control-plane/scripts/external-tool-broker.mjs +179 -0
- package/control-plane/scripts/oauth-authorization-server.mjs +589 -0
- package/control-plane/scripts/operator-dashboard-http.mjs +81 -0
- package/control-plane/scripts/project-inspection.mjs +227 -0
- package/control-plane/scripts/projects/project-id.mjs +69 -0
- package/control-plane/scripts/projects/trusted-roots-projects.mjs +375 -0
- package/control-plane/scripts/prompts/index.mjs +60 -0
- package/control-plane/scripts/remote-process-sessions.mjs +78 -0
- package/control-plane/scripts/remote-tool-result.mjs +9 -0
- package/control-plane/scripts/resources/index.mjs +166 -0
- package/control-plane/scripts/runtime-profile.mjs +54 -0
- package/control-plane/scripts/shell-tool-descriptor.mjs +17 -0
- package/control-plane/scripts/skill-source-safety.mjs +46 -0
- package/control-plane/scripts/skills/README.md +70 -0
- package/control-plane/scripts/skills/_upstream_licenses/anthropic-THIRD_PARTY_NOTICES.md +405 -0
- package/control-plane/scripts/skills/_upstream_licenses/hallmark-LICENSE.txt +21 -0
- package/control-plane/scripts/skills/_upstream_licenses/ponytail-LICENSE.txt +21 -0
- package/control-plane/scripts/skills/_upstream_licenses/stitch-skills-LICENSE.txt +201 -0
- package/control-plane/scripts/skills/_upstream_licenses/superpowers-LICENSE.txt +21 -0
- package/control-plane/scripts/skills/algorithmic-art/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/algorithmic-art/SKILL.md +405 -0
- package/control-plane/scripts/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/control-plane/scripts/skills/algorithmic-art/templates/viewer.html +599 -0
- package/control-plane/scripts/skills/brainstorming/SKILL.md +285 -0
- package/control-plane/scripts/skills/brainstorming/scripts/frame-template.html +213 -0
- package/control-plane/scripts/skills/brainstorming/scripts/helper.js +167 -0
- package/control-plane/scripts/skills/brainstorming/scripts/server.cjs +723 -0
- package/control-plane/scripts/skills/brainstorming/scripts/start-server.sh +209 -0
- package/control-plane/scripts/skills/brainstorming/scripts/stop-server.sh +120 -0
- package/control-plane/scripts/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/control-plane/scripts/skills/brainstorming/visual-companion.md +299 -0
- package/control-plane/scripts/skills/brand-guidelines/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/brand-guidelines/SKILL.md +73 -0
- package/control-plane/scripts/skills/claude-api/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/claude-api/SKILL.md +570 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/README.md +362 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/batches.md +14 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/files-api.md +25 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/streaming.md +28 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/tool-use.md +164 -0
- package/control-plane/scripts/skills/claude-api/curl/examples.md +256 -0
- package/control-plane/scripts/skills/claude-api/curl/managed-agents.md +371 -0
- package/control-plane/scripts/skills/claude-api/go/claude-api/README.md +188 -0
- package/control-plane/scripts/skills/claude-api/go/claude-api/files-api.md +23 -0
- package/control-plane/scripts/skills/claude-api/go/claude-api/streaming.md +43 -0
- package/control-plane/scripts/skills/claude-api/go/claude-api/tool-use.md +220 -0
- package/control-plane/scripts/skills/claude-api/go/managed-agents/README.md +564 -0
- package/control-plane/scripts/skills/claude-api/java/claude-api/README.md +238 -0
- package/control-plane/scripts/skills/claude-api/java/claude-api/files-api.md +27 -0
- package/control-plane/scripts/skills/claude-api/java/claude-api/streaming.md +24 -0
- package/control-plane/scripts/skills/claude-api/java/claude-api/tool-use.md +228 -0
- package/control-plane/scripts/skills/claude-api/java/managed-agents/README.md +443 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/README.md +174 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/batches.md +15 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/files-api.md +13 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/streaming.md +27 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/tool-use.md +253 -0
- package/control-plane/scripts/skills/claude-api/php/managed-agents/README.md +443 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/README.md +565 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/batches.md +198 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/files-api.md +170 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/sdk-upgrade.md +286 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/streaming.md +179 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/tool-use.md +627 -0
- package/control-plane/scripts/skills/claude-api/python/managed-agents/README.md +335 -0
- package/control-plane/scripts/skills/claude-api/ruby/claude-api/README.md +134 -0
- package/control-plane/scripts/skills/claude-api/ruby/claude-api/streaming.md +16 -0
- package/control-plane/scripts/skills/claude-api/ruby/claude-api/tool-use.md +41 -0
- package/control-plane/scripts/skills/claude-api/ruby/managed-agents/README.md +394 -0
- package/control-plane/scripts/skills/claude-api/shared/admin-api.md +179 -0
- package/control-plane/scripts/skills/claude-api/shared/agent-design.md +101 -0
- package/control-plane/scripts/skills/claude-api/shared/anthropic-cli.md +269 -0
- package/control-plane/scripts/skills/claude-api/shared/claude-platform-on-aws.md +59 -0
- package/control-plane/scripts/skills/claude-api/shared/cost-optimization.md +233 -0
- package/control-plane/scripts/skills/claude-api/shared/error-codes.md +262 -0
- package/control-plane/scripts/skills/claude-api/shared/live-sources.md +166 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-api-reference.md +455 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-client-patterns.md +220 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-core.md +359 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-environments.md +226 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-events.md +290 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-memory.md +201 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-multiagent.md +260 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-onboarding.md +82 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-outcomes.md +108 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-overview.md +77 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-scheduled-deployments.md +156 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +282 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-tools.md +504 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-webhooks.md +143 -0
- package/control-plane/scripts/skills/claude-api/shared/model-migration.md +1872 -0
- package/control-plane/scripts/skills/claude-api/shared/models.md +142 -0
- package/control-plane/scripts/skills/claude-api/shared/platform-availability.md +52 -0
- package/control-plane/scripts/skills/claude-api/shared/prompt-audit.md +223 -0
- package/control-plane/scripts/skills/claude-api/shared/prompt-caching.md +292 -0
- package/control-plane/scripts/skills/claude-api/shared/token-counting.md +56 -0
- package/control-plane/scripts/skills/claude-api/shared/tool-use-concepts.md +526 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/README.md +405 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/batches.md +106 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/files-api.md +98 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/streaming.md +178 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/tool-use.md +586 -0
- package/control-plane/scripts/skills/claude-api/typescript/managed-agents/README.md +359 -0
- package/control-plane/scripts/skills/dispatching-parallel-agents/SKILL.md +167 -0
- package/control-plane/scripts/skills/enhance-prompt/README.md +34 -0
- package/control-plane/scripts/skills/enhance-prompt/SKILL.md +204 -0
- package/control-plane/scripts/skills/enhance-prompt/references/KEYWORDS.md +114 -0
- package/control-plane/scripts/skills/executing-plans/SKILL.md +371 -0
- package/control-plane/scripts/skills/executing-plans/scripts/task-done +52 -0
- package/control-plane/scripts/skills/executing-plans/scripts/task-start +28 -0
- package/control-plane/scripts/skills/extract-design-md/SKILL.md +358 -0
- package/control-plane/scripts/skills/extract-design-md/examples/DESIGN.md +210 -0
- package/control-plane/scripts/skills/extract-design-md/references/angular.md +122 -0
- package/control-plane/scripts/skills/extract-design-md/references/plain-css.md +161 -0
- package/control-plane/scripts/skills/extract-design-md/references/react-tailwind.md +157 -0
- package/control-plane/scripts/skills/extract-design-md/references/svelte.md +101 -0
- package/control-plane/scripts/skills/extract-design-md/references/vue.md +107 -0
- package/control-plane/scripts/skills/finishing-a-development-branch/SKILL.md +225 -0
- package/control-plane/scripts/skills/frontend-design/LICENSE.txt +177 -0
- package/control-plane/scripts/skills/frontend-design/SKILL.md +71 -0
- package/control-plane/scripts/skills/hallmark/SKILL.md +558 -0
- package/control-plane/scripts/skills/hallmark/references/anti-patterns.md +418 -0
- package/control-plane/scripts/skills/hallmark/references/assets.md +406 -0
- package/control-plane/scripts/skills/hallmark/references/color.md +95 -0
- package/control-plane/scripts/skills/hallmark/references/component-cookbook.md +263 -0
- package/control-plane/scripts/skills/hallmark/references/components/c1-outlined-chip.md +12 -0
- package/control-plane/scripts/skills/hallmark/references/components/c2-inline-form-as-cta.md +16 -0
- package/control-plane/scripts/skills/hallmark/references/components/c3-typographic-link.md +8 -0
- package/control-plane/scripts/skills/hallmark/references/components/c4-sticky-bottom-bar.md +16 -0
- package/control-plane/scripts/skills/hallmark/references/components/f1-bento-grid.md +20 -0
- package/control-plane/scripts/skills/hallmark/references/components/f2-sticky-scroll-stack.md +20 -0
- package/control-plane/scripts/skills/hallmark/references/components/f3-tabular-spec-sheet.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/f4-step-sequence.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/f5-annotated-screenshot.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/f6-product-card-grid.md +41 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft1-mast-headed.md +13 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft2-inline-rule-single-line.md +10 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft3-index-style-category-list.md +12 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft4-dense-typographic.md +10 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft5-statement.md +21 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft6-letter-close.md +19 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft7-newsletter-first.md +27 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft8-marquee-scroll.md +25 -0
- package/control-plane/scripts/skills/hallmark/references/components/h1-marquee.md +15 -0
- package/control-plane/scripts/skills/hallmark/references/components/h2-split-diptych.md +15 -0
- package/control-plane/scripts/skills/hallmark/references/components/h3-quote-led.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/h4-stat-led.md +14 -0
- package/control-plane/scripts/skills/hallmark/references/components/h5-letter-hero.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/h6-photographic-fold.md +16 -0
- package/control-plane/scripts/skills/hallmark/references/components/h7-demo-video-clipped-by-viewport-edge.md +27 -0
- package/control-plane/scripts/skills/hallmark/references/components/h8-mockup-split-browser-framed.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/components/h9-custom-illustration-centerpiece.md +27 -0
- package/control-plane/scripts/skills/hallmark/references/components/n1-wordmark-2-links.md +12 -0
- package/control-plane/scripts/skills/hallmark/references/components/n10-floating-on-scroll-morph.md +19 -0
- package/control-plane/scripts/skills/hallmark/references/components/n11-mega-menu.md +40 -0
- package/control-plane/scripts/skills/hallmark/references/components/n12-banner-retract.md +34 -0
- package/control-plane/scripts/skills/hallmark/references/components/n13-inline-cmdk-pill.md +39 -0
- package/control-plane/scripts/skills/hallmark/references/components/n1b-saas-three-section.md +35 -0
- package/control-plane/scripts/skills/hallmark/references/components/n2-floating-chip.md +14 -0
- package/control-plane/scripts/skills/hallmark/references/components/n3-side-rail.md +14 -0
- package/control-plane/scripts/skills/hallmark/references/components/n4-hidden-behind-k.md +9 -0
- package/control-plane/scripts/skills/hallmark/references/components/n5-floating-pill.md +28 -0
- package/control-plane/scripts/skills/hallmark/references/components/n6-newspaper-masthead.md +24 -0
- package/control-plane/scripts/skills/hallmark/references/components/n7-brutal-slab.md +22 -0
- package/control-plane/scripts/skills/hallmark/references/components/n8-terminal-command.md +21 -0
- package/control-plane/scripts/skills/hallmark/references/components/n9-edge-aligned-minimal.md +17 -0
- package/control-plane/scripts/skills/hallmark/references/components/s1-left-margin-numbered.md +15 -0
- package/control-plane/scripts/skills/hallmark/references/components/s2-hanging.md +13 -0
- package/control-plane/scripts/skills/hallmark/references/components/s3-sticky-pinned.md +19 -0
- package/control-plane/scripts/skills/hallmark/references/components/s4-inline-no-break.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/s5-bottom-anchored.md +13 -0
- package/control-plane/scripts/skills/hallmark/references/components/t1-pull-quote-with-marginalia.md +12 -0
- package/control-plane/scripts/skills/hallmark/references/components/t2-logo-wall-hairline.md +19 -0
- package/control-plane/scripts/skills/hallmark/references/components/t3-single-huge-quote.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/t4-numbered-stat-strip.md +14 -0
- package/control-plane/scripts/skills/hallmark/references/contract.md +25 -0
- package/control-plane/scripts/skills/hallmark/references/copy.md +182 -0
- package/control-plane/scripts/skills/hallmark/references/custom-craft.md +626 -0
- package/control-plane/scripts/skills/hallmark/references/custom-theme.md +367 -0
- package/control-plane/scripts/skills/hallmark/references/design-md.md +116 -0
- package/control-plane/scripts/skills/hallmark/references/export-formats.md +329 -0
- package/control-plane/scripts/skills/hallmark/references/floating-nav.md +89 -0
- package/control-plane/scripts/skills/hallmark/references/genres/atmospheric.md +69 -0
- package/control-plane/scripts/skills/hallmark/references/genres/editorial.md +72 -0
- package/control-plane/scripts/skills/hallmark/references/genres/modern-minimal.md +71 -0
- package/control-plane/scripts/skills/hallmark/references/genres/playful.md +70 -0
- package/control-plane/scripts/skills/hallmark/references/hero-enrichment.md +475 -0
- package/control-plane/scripts/skills/hallmark/references/imagery-kit.md +170 -0
- package/control-plane/scripts/skills/hallmark/references/interaction-and-states.md +208 -0
- package/control-plane/scripts/skills/hallmark/references/layout-and-space.md +112 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/01-bento-grid.md +35 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/02-long-document.md +34 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/03-marquee-hero.md +31 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/04-stat-led.md +34 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/05-workbench.md +32 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/06-conversational-faq.md +33 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/07-manifesto.md +32 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/08-photographic.md +34 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/09-quote-led.md +32 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/10-specimen.md +32 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/11-catalogue.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/12-letter.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/13-index-first.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/14-narrative-workflow.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/15-split-studio.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/16-feature-stack.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/17-type-specimen.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/18-portfolio-grid.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/19-map-diagram.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/20-ecosystem-index.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/21-component-playground.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures.md +89 -0
- package/control-plane/scripts/skills/hallmark/references/microinteractions.md +258 -0
- package/control-plane/scripts/skills/hallmark/references/motion.md +109 -0
- package/control-plane/scripts/skills/hallmark/references/preview-examples.md +49 -0
- package/control-plane/scripts/skills/hallmark/references/responsive.md +138 -0
- package/control-plane/scripts/skills/hallmark/references/slop-test.md +192 -0
- package/control-plane/scripts/skills/hallmark/references/structure.md +160 -0
- package/control-plane/scripts/skills/hallmark/references/study.md +509 -0
- package/control-plane/scripts/skills/hallmark/references/themes/carnival.md +301 -0
- package/control-plane/scripts/skills/hallmark/references/themes/cobalt.md +146 -0
- package/control-plane/scripts/skills/hallmark/references/themes/grid.md +161 -0
- package/control-plane/scripts/skills/hallmark/references/themes/hum.md +403 -0
- package/control-plane/scripts/skills/hallmark/references/themes/lumen.md +478 -0
- package/control-plane/scripts/skills/hallmark/references/typography.md +243 -0
- package/control-plane/scripts/skills/hallmark/references/upstream/recipes.md +186 -0
- package/control-plane/scripts/skills/hallmark/references/upstream/study-examples.md +176 -0
- package/control-plane/scripts/skills/hallmark/references/upstream/tokens.css +1221 -0
- package/control-plane/scripts/skills/hallmark/references/verbs/audit.md +25 -0
- package/control-plane/scripts/skills/hallmark/references/verbs/redesign.md +269 -0
- package/control-plane/scripts/skills/human-comms/SKILL.md +29 -0
- package/control-plane/scripts/skills/index.mjs +432 -0
- package/control-plane/scripts/skills/internal-comms/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/internal-comms/SKILL.md +32 -0
- package/control-plane/scripts/skills/internal-comms/examples/3p-updates.md +47 -0
- package/control-plane/scripts/skills/internal-comms/examples/company-newsletter.md +65 -0
- package/control-plane/scripts/skills/internal-comms/examples/faq-answers.md +30 -0
- package/control-plane/scripts/skills/internal-comms/examples/general-comms.md +16 -0
- package/control-plane/scripts/skills/mcp-adapter.mjs +94 -0
- package/control-plane/scripts/skills/mcp-builder/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/mcp-builder/SKILL.md +236 -0
- package/control-plane/scripts/skills/mcp-builder/reference/evaluation.md +602 -0
- package/control-plane/scripts/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/control-plane/scripts/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/control-plane/scripts/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/control-plane/scripts/skills/mcp-builder/scripts/connections.py +151 -0
- package/control-plane/scripts/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/control-plane/scripts/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/control-plane/scripts/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/control-plane/scripts/skills/ponytail/SKILL.md +120 -0
- package/control-plane/scripts/skills/ponytail-audit/SKILL.md +41 -0
- package/control-plane/scripts/skills/ponytail-debt/SKILL.md +44 -0
- package/control-plane/scripts/skills/ponytail-gain/SKILL.md +50 -0
- package/control-plane/scripts/skills/ponytail-help/SKILL.md +71 -0
- package/control-plane/scripts/skills/ponytail-review/SKILL.md +57 -0
- package/control-plane/scripts/skills/receiving-code-review/SKILL.md +205 -0
- package/control-plane/scripts/skills/requesting-code-review/SKILL.md +95 -0
- package/control-plane/scripts/skills/requesting-code-review/code-reviewer.md +198 -0
- package/control-plane/scripts/skills/skill-creator/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/skill-creator/SKILL.md +485 -0
- package/control-plane/scripts/skills/skill-creator/agents/analyzer.md +274 -0
- package/control-plane/scripts/skills/skill-creator/agents/comparator.md +202 -0
- package/control-plane/scripts/skills/skill-creator/agents/grader.md +223 -0
- package/control-plane/scripts/skills/skill-creator/assets/eval_review.html +146 -0
- package/control-plane/scripts/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/control-plane/scripts/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/control-plane/scripts/skills/skill-creator/references/schemas.md +430 -0
- package/control-plane/scripts/skills/skill-creator/scripts/__init__.py +0 -0
- package/control-plane/scripts/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/control-plane/scripts/skills/skill-creator/scripts/generate_report.py +326 -0
- package/control-plane/scripts/skills/skill-creator/scripts/improve_description.py +247 -0
- package/control-plane/scripts/skills/skill-creator/scripts/package_skill.py +136 -0
- package/control-plane/scripts/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/control-plane/scripts/skills/skill-creator/scripts/run_eval.py +310 -0
- package/control-plane/scripts/skills/skill-creator/scripts/run_loop.py +328 -0
- package/control-plane/scripts/skills/skill-creator/scripts/utils.py +47 -0
- package/control-plane/scripts/skills/slack-gif-creator/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/slack-gif-creator/SKILL.md +254 -0
- package/control-plane/scripts/skills/slack-gif-creator/core/easing.py +234 -0
- package/control-plane/scripts/skills/slack-gif-creator/core/frame_composer.py +176 -0
- package/control-plane/scripts/skills/slack-gif-creator/core/gif_builder.py +269 -0
- package/control-plane/scripts/skills/slack-gif-creator/core/validators.py +136 -0
- package/control-plane/scripts/skills/slack-gif-creator/requirements.txt +4 -0
- package/control-plane/scripts/skills/sources.json +401 -0
- package/control-plane/scripts/skills/sources.lock.json +416 -0
- package/control-plane/scripts/skills/subagent-driven-development/SKILL.md +570 -0
- package/control-plane/scripts/skills/subagent-driven-development/implementer-prompt.md +154 -0
- package/control-plane/scripts/skills/subagent-driven-development/re-review-prompt.md +115 -0
- package/control-plane/scripts/skills/subagent-driven-development/scripts/review-package +53 -0
- package/control-plane/scripts/skills/subagent-driven-development/scripts/sdd-workspace +82 -0
- package/control-plane/scripts/skills/subagent-driven-development/scripts/task-brief +43 -0
- package/control-plane/scripts/skills/subagent-driven-development/task-reviewer-prompt.md +207 -0
- package/control-plane/scripts/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/control-plane/scripts/skills/systematic-debugging/SKILL.md +283 -0
- package/control-plane/scripts/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/control-plane/scripts/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/control-plane/scripts/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/control-plane/scripts/skills/systematic-debugging/find-polluter.sh +72 -0
- package/control-plane/scripts/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/control-plane/scripts/skills/systematic-debugging/test-academic.md +14 -0
- package/control-plane/scripts/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/control-plane/scripts/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/control-plane/scripts/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/control-plane/scripts/skills/test-driven-development/SKILL.md +330 -0
- package/control-plane/scripts/skills/test-driven-development/writing-good-tests.md +198 -0
- package/control-plane/scripts/skills/theme-factory/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/theme-factory/SKILL.md +59 -0
- package/control-plane/scripts/skills/theme-factory/theme-showcase.pdf +0 -0
- package/control-plane/scripts/skills/theme-factory/themes/arctic-frost.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/botanical-garden.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/desert-rose.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/forest-canopy.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/golden-hour.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/midnight-galaxy.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/modern-minimalist.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/ocean-depths.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/sunset-boulevard.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/tech-innovation.md +19 -0
- package/control-plane/scripts/skills/using-git-worktrees/SKILL.md +167 -0
- package/control-plane/scripts/skills/verification-before-completion/SKILL.md +120 -0
- package/control-plane/scripts/skills/web-artifacts-builder/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/web-artifacts-builder/SKILL.md +74 -0
- package/control-plane/scripts/skills/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/control-plane/scripts/skills/web-artifacts-builder/scripts/init-artifact.sh +322 -0
- package/control-plane/scripts/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/control-plane/scripts/skills/webapp-testing/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/webapp-testing/SKILL.md +96 -0
- package/control-plane/scripts/skills/webapp-testing/examples/console_logging.py +35 -0
- package/control-plane/scripts/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/control-plane/scripts/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/control-plane/scripts/skills/webapp-testing/scripts/with_server.py +106 -0
- package/control-plane/scripts/skills/writing-plans/SKILL.md +192 -0
- package/control-plane/scripts/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/control-plane/scripts/skills/writing-skills/SKILL.md +681 -0
- package/control-plane/scripts/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/control-plane/scripts/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/control-plane/scripts/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/control-plane/scripts/skills/writing-skills/persuasion-principles.md +187 -0
- package/control-plane/scripts/skills/writing-skills/render-graphs.js +169 -0
- package/control-plane/scripts/skills/writing-skills/testing-skills-with-subagents.md +384 -0
- package/control-plane/scripts/smoke-mcp-schemas.mjs +30 -0
- package/control-plane/scripts/surface-config.mjs +45 -0
- package/control-plane/scripts/sync-skills.mjs +370 -0
- package/control-plane/scripts/tool-metrics.mjs +74 -0
- package/control-plane/scripts/tool-risk.mjs +156 -0
- package/control-plane/scripts/tool-surface-stability.mjs +38 -0
- package/control-plane/scripts/upstreams/catalog-cache.mjs +28 -0
- package/control-plane/scripts/upstreams/config.mjs +158 -0
- package/control-plane/scripts/upstreams/diagnostics.mjs +21 -0
- package/control-plane/scripts/upstreams/http-client.mjs +67 -0
- package/control-plane/scripts/upstreams/manager.mjs +535 -0
- package/control-plane/scripts/upstreams/names.mjs +55 -0
- package/control-plane/scripts/upstreams/presets.mjs +60 -0
- package/control-plane/scripts/upstreams/resource-uri.mjs +29 -0
- package/control-plane/scripts/upstreams/stdio-client.mjs +73 -0
- package/control-plane/scripts/workspace-registry.mjs +147 -0
- package/dist/context/authorization.d.ts +5 -0
- package/dist/context/authorization.js +70 -0
- package/dist/context/capture.d.ts +46 -0
- package/dist/context/capture.js +187 -0
- package/dist/context/cli.d.ts +47 -0
- package/dist/context/cli.js +287 -0
- package/dist/context/device-adapter.d.ts +102 -0
- package/dist/context/device-adapter.js +79 -0
- package/dist/context/gateway.d.ts +354 -0
- package/dist/context/gateway.js +50 -0
- package/dist/context/graph.d.ts +63 -0
- package/dist/context/graph.js +214 -0
- package/dist/context/importer.d.ts +8 -0
- package/dist/context/importer.js +69 -0
- package/dist/context/indexer.d.ts +14 -0
- package/dist/context/indexer.js +57 -0
- package/dist/context/migrations/index.d.ts +4 -0
- package/dist/context/migrations/index.js +87 -0
- package/dist/context/redact.d.ts +15 -0
- package/dist/context/redact.js +43 -0
- package/dist/context/repositories.d.ts +9 -0
- package/dist/context/repositories.js +45 -0
- package/dist/context/search.d.ts +63 -0
- package/dist/context/search.js +207 -0
- package/dist/context/service.d.ts +278 -0
- package/dist/context/service.js +160 -0
- package/dist/context/store.d.ts +134 -0
- package/dist/context/store.js +722 -0
- package/dist/context/text.d.ts +4 -0
- package/dist/context/text.js +14 -0
- package/dist/context/tool-contract.d.ts +534 -0
- package/dist/context/tool-contract.js +45 -0
- package/dist/device/core.d.ts +29 -0
- package/dist/device/core.js +66 -0
- package/dist/device/device.d.ts +5 -1
- package/dist/device/device.js +87 -39
- package/dist/device/gateway-channel.d.ts +8 -1
- package/dist/device/gateway-channel.js +51 -3
- package/dist/device/gateway-tool-adapter.d.ts +9 -0
- package/dist/device/gateway-tool-adapter.js +20 -1
- package/dist/device/official-trust.d.ts +12 -0
- package/dist/device/official-trust.js +25 -1
- package/dist/device/runtime-owner.js +9 -1
- package/dist/distribution.d.ts +4 -0
- package/dist/distribution.js +8 -0
- package/dist/log-palace/cli.d.ts +7 -0
- package/dist/log-palace/cli.js +89 -0
- package/dist/log-palace/formats.d.ts +26 -0
- package/dist/log-palace/formats.js +126 -0
- package/dist/log-palace/store.d.ts +129 -0
- package/dist/log-palace/store.js +856 -0
- package/dist/mcp-device.js +27 -5
- package/dist/npm-scripts/remote.js +1 -0
- package/dist/platform/activity-journal.d.ts +16 -0
- package/dist/platform/activity-journal.js +68 -0
- package/dist/platform/cli.d.ts +1 -0
- package/dist/platform/cli.js +300 -0
- package/dist/platform/coordination-lock.d.ts +45 -0
- package/dist/platform/coordination-lock.js +233 -0
- package/dist/platform/device-runtime.d.ts +16 -0
- package/dist/platform/device-runtime.js +83 -0
- package/dist/platform/knowledge-contract.d.ts +46 -0
- package/dist/platform/knowledge-contract.js +1 -0
- package/dist/platform/knowledge-mcp.d.ts +29 -0
- package/dist/platform/knowledge-mcp.js +95 -0
- package/dist/platform/knowledge-router.d.ts +64 -0
- package/dist/platform/knowledge-router.js +320 -0
- package/dist/platform/plugin-child.d.ts +1 -0
- package/dist/platform/plugin-child.js +115 -0
- package/dist/platform/plugin-contract.d.ts +76 -0
- package/dist/platform/plugin-contract.js +53 -0
- package/dist/platform/plugin-driver.d.ts +8 -0
- package/dist/platform/plugin-driver.js +443 -0
- package/dist/platform/plugin-host.d.ts +88 -0
- package/dist/platform/plugin-host.js +817 -0
- package/dist/platform/plugin-policy.d.ts +3 -0
- package/dist/platform/plugin-policy.js +29 -0
- package/dist/platform/plugin-services.d.ts +14 -0
- package/dist/platform/plugin-services.js +32 -0
- package/dist/platform/process-identity.d.ts +9 -0
- package/dist/platform/process-identity.js +68 -0
- package/dist/platform/runtime-lease.d.ts +4 -0
- package/dist/platform/runtime-lease.js +58 -0
- package/dist/platform/runtime-root.d.ts +9 -0
- package/dist/platform/runtime-root.js +61 -0
- package/dist/platform/state-backup.d.ts +92 -0
- package/dist/platform/state-backup.js +399 -0
- package/dist/plugins/reference/plugin.d.ts +3 -0
- package/dist/plugins/reference/plugin.js +13 -0
- package/dist/server.js +5 -1
- package/dist/terminal-manager.d.ts +7 -0
- package/dist/terminal-manager.js +10 -0
- package/dist/tool-dispatcher.js +5 -1
- package/dist/tools/improved-process-tools.js +4 -0
- package/dist/types.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +29 -6
- package/skills/mcp-device-context/SKILL.md +40 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ren Zimeng
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the software, subject to including this copyright notice and permission notice in all copies or substantial portions of the software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const ACCOUNT_COOKIE = 'hcu_account_session';
|
|
4
|
+
const SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
5
|
+
|
|
6
|
+
function escapeHtml(value) {
|
|
7
|
+
return String(value ?? '')
|
|
8
|
+
.replaceAll('&', '&')
|
|
9
|
+
.replaceAll('<', '<')
|
|
10
|
+
.replaceAll('>', '>')
|
|
11
|
+
.replaceAll('"', '"')
|
|
12
|
+
.replaceAll("'", ''');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseCookies(header = '') {
|
|
16
|
+
return String(header)
|
|
17
|
+
.split(';')
|
|
18
|
+
.map(part => part.trim())
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.reduce((result, part) => {
|
|
21
|
+
const index = part.indexOf('=');
|
|
22
|
+
if (index <= 0) return result;
|
|
23
|
+
try { result[part.slice(0, index)] = decodeURIComponent(part.slice(index + 1)); } catch {}
|
|
24
|
+
return result;
|
|
25
|
+
}, {});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function safeReturnTo(value) {
|
|
29
|
+
const target = String(value || '').trim();
|
|
30
|
+
if (!target || target.length > 2048 || !target.startsWith('/') || target.startsWith('//') || target.includes('\\')) return '/dashboard';
|
|
31
|
+
return target;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function secureRequest(req) {
|
|
35
|
+
const forwarded = String(req.headers?.['x-forwarded-proto'] || '').split(',')[0].trim().toLowerCase();
|
|
36
|
+
return Boolean(req.secure || forwarded === 'https');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function sourceKey(req) {
|
|
40
|
+
return String(req.headers?.['cf-connecting-ip'] || req.ip || req.socket?.remoteAddress || 'unknown').trim().slice(0, 128);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function identityDigest(value) {
|
|
44
|
+
return crypto.createHash('sha256').update(String(value || '').trim().toLowerCase(), 'utf8').digest('hex').slice(0, 24);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createLoginRateLimiter({ now = () => Date.now() } = {}) {
|
|
48
|
+
const states = new Map();
|
|
49
|
+
const resetAfterMs = 15 * 60_000;
|
|
50
|
+
|
|
51
|
+
function key(identity, source) {
|
|
52
|
+
return `${String(source || 'unknown').slice(0, 128)}\n${identityDigest(identity)}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function canAttempt({ identity, source }) {
|
|
56
|
+
const state = states.get(key(identity, source));
|
|
57
|
+
return !state || Number(state.blockedUntil || 0) <= Number(now());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function recordFailure({ identity, source }) {
|
|
61
|
+
const id = key(identity, source);
|
|
62
|
+
const time = Number(now());
|
|
63
|
+
const previous = states.get(id);
|
|
64
|
+
const state = !previous || time - previous.lastFailure > resetAfterMs
|
|
65
|
+
? { failures: 0, lastFailure: time, blockedUntil: 0 }
|
|
66
|
+
: previous;
|
|
67
|
+
state.failures += 1;
|
|
68
|
+
state.lastFailure = time;
|
|
69
|
+
if (state.failures >= 5) state.blockedUntil = time + 60 * 60_000;
|
|
70
|
+
else if (state.failures === 4) state.blockedUntil = time + 5 * 60_000;
|
|
71
|
+
else if (state.failures === 3) state.blockedUntil = time + 60_000;
|
|
72
|
+
states.set(id, state);
|
|
73
|
+
return { ...state };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function recordSuccess({ identity, source }) {
|
|
77
|
+
states.delete(key(identity, source));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { canAttempt, recordFailure, recordSuccess };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function publicGatePage(body, { failed = false } = {}) {
|
|
84
|
+
return `<!doctype html>
|
|
85
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title></title>
|
|
86
|
+
<style>
|
|
87
|
+
:root{color-scheme:dark}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;background:#050505;color:#eee;font:15px ui-monospace,SFMono-Regular,Consolas,monospace}.gate{width:min(92vw,440px);display:grid;gap:10px}.row{display:flex;gap:10px}.row>*{min-width:0}form{margin:0}.go-form{display:flex;gap:10px;flex:1}.signup-form{display:grid;gap:10px;flex:1}.gate-error{color:#b96a62;font-size:12px;letter-spacing:.08em}input{min-width:0;flex:1;background:#0b0b0b;color:#f4f4f4;border:1px solid ${failed ? '#733' : '#292929'};padding:13px 14px;outline:none}input::placeholder{color:#666}input:focus{border-color:#666}button,a{border:0;padding:13px 16px;background:#222;color:#eee;text-decoration:none;font:inherit;cursor:pointer}.go{background:#176b45;color:#f2f2f2}.out{background:#8f342c;color:#f2f2f2}
|
|
88
|
+
</style></head><body><main class="gate">${body}</main></body></html>`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function loginIdentityPage(returnTo = '/dashboard') {
|
|
92
|
+
const target = safeReturnTo(returnTo);
|
|
93
|
+
return publicGatePage(`<div class="row"><form class="go-form" method="post" action="/login/email"><input name="identity" type="email" autocomplete="username" aria-label="Email" placeholder="email" autofocus required><input type="hidden" name="return_to" value="${escapeHtml(target)}"><button class="go" type="submit">GO</button></form><a class="out" href="/signup?return_to=${encodeURIComponent(target)}">OUT</a></div>`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function loginPasswordPage(identity, returnTo = '/dashboard', failed = false) {
|
|
97
|
+
const target = safeReturnTo(returnTo);
|
|
98
|
+
return publicGatePage(`<div class="row"><form class="go-form" method="post" action="/login"><input name="password" type="password" autocomplete="current-password" aria-label="Password" placeholder="password" aria-invalid="${failed ? 'true' : 'false'}" autofocus required><input type="hidden" name="identity" value="${escapeHtml(identity)}"><input type="hidden" name="return_to" value="${escapeHtml(target)}"><button class="go" type="submit">GO</button></form><a class="out" href="/signup?return_to=${encodeURIComponent(target)}">OUT</a></div>`, { failed });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function signupPage(returnTo = '/dashboard', failed = false, needInvite = true, inviteInvalid = false) {
|
|
102
|
+
const target = safeReturnTo(returnTo);
|
|
103
|
+
const inviteInput = inviteInvalid
|
|
104
|
+
? '<input name="invite" autocomplete="one-time-code" aria-label="Invite code" aria-invalid="true" placeholder="invalid invite code" maxlength="8" autofocus required>'
|
|
105
|
+
: '<input name="invite" autocomplete="one-time-code" aria-label="Invite code" placeholder="invite code" maxlength="8" required>';
|
|
106
|
+
return publicGatePage(`<div class="row"><form id="signup-form" class="signup-form" method="post" action="/signup"><input name="identity" type="email" autocomplete="username" aria-label="Email" placeholder="email" aria-invalid="${failed ? 'true' : 'false'}" required><input name="password" type="password" autocomplete="new-password" aria-label="Password" placeholder="password" required>${needInvite ? inviteInput : ''}${inviteInvalid ? '<div class="gate-error" role="alert">INVALID INVITE</div>' : ''}<input type="hidden" name="return_to" value="${escapeHtml(target)}"><a href="/login?return_to=${encodeURIComponent(target)}" class="out">OUT</a></form><button class="go" type="submit" form="signup-form">GO</button></div>`, { failed });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function installAccountRoutes(app, {
|
|
110
|
+
accountStore,
|
|
111
|
+
needInvite = true,
|
|
112
|
+
allowAdminLogin = false,
|
|
113
|
+
now = () => Date.now(),
|
|
114
|
+
loginLimiter = createLoginRateLimiter({ now }),
|
|
115
|
+
sessionTtlMs = SESSION_TTL_MS
|
|
116
|
+
} = {}) {
|
|
117
|
+
if (!app || !accountStore) throw new Error('app and accountStore are required.');
|
|
118
|
+
const requiresInvite = () => typeof needInvite === 'function' ? Boolean(needInvite()) : Boolean(needInvite);
|
|
119
|
+
|
|
120
|
+
function sessionAccountFromRequest(req) {
|
|
121
|
+
const sessionId = parseCookies(req?.headers?.cookie || '')[ACCOUNT_COOKIE];
|
|
122
|
+
return sessionId ? accountStore.getSessionAccount(sessionId) : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function accountFromRequest(req) {
|
|
126
|
+
const account = sessionAccountFromRequest(req);
|
|
127
|
+
return account?.role === 'user' ? account : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function setSession(req, res, accountId) {
|
|
131
|
+
const session = accountStore.createSession(accountId, { ttlMs: sessionTtlMs });
|
|
132
|
+
res.cookie(ACCOUNT_COOKIE, session.sessionId, {
|
|
133
|
+
httpOnly: true,
|
|
134
|
+
sameSite: 'lax',
|
|
135
|
+
secure: secureRequest(req),
|
|
136
|
+
maxAge: sessionTtlMs,
|
|
137
|
+
path: '/'
|
|
138
|
+
});
|
|
139
|
+
return session;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function clearSession(req, res) {
|
|
143
|
+
const sessionId = parseCookies(req.headers?.cookie || '')[ACCOUNT_COOKIE];
|
|
144
|
+
if (sessionId) accountStore.revokeSession(sessionId);
|
|
145
|
+
res.clearCookie(ACCOUNT_COOKIE, { path: '/' });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function sessionBindingFromRequest(req) {
|
|
149
|
+
const sessionId = parseCookies(req?.headers?.cookie || '')[ACCOUNT_COOKIE];
|
|
150
|
+
if (!sessionId || !accountStore.getSessionAccount(sessionId)) return null;
|
|
151
|
+
return crypto.createHash('sha256').update(sessionId, 'utf8').digest('hex');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function loginLocation(returnTo) {
|
|
155
|
+
const target = safeReturnTo(returnTo);
|
|
156
|
+
return target === '/dashboard' ? '/login' : `/login?return_to=${encodeURIComponent(target)}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
app.get('/', (req, res) => {
|
|
160
|
+
res.redirect(302, accountFromRequest(req) ? '/dashboard' : '/login');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
app.get('/login', (req, res) => {
|
|
164
|
+
res.status(200).type('html').send(loginIdentityPage(req.query?.return_to));
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
app.post('/login/email', (req, res) => {
|
|
168
|
+
res.status(200).type('html').send(loginPasswordPage(String(req.body?.identity || '').trim(), req.body?.return_to, false));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
app.post('/login', (req, res) => {
|
|
172
|
+
const identity = String(req.body?.identity || '').trim();
|
|
173
|
+
const returnTo = safeReturnTo(req.body?.return_to);
|
|
174
|
+
const source = sourceKey(req);
|
|
175
|
+
const limiterKey = { identity, source };
|
|
176
|
+
if (!loginLimiter.canAttempt(limiterKey)) {
|
|
177
|
+
res.status(200).type('html').send(loginPasswordPage(identity, returnTo, true));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const account = accountStore.verifyPassword(identity, req.body?.password);
|
|
182
|
+
if (!account || (account.role !== 'user' && !(allowAdminLogin && account.role === 'admin'))) {
|
|
183
|
+
loginLimiter.recordFailure(limiterKey);
|
|
184
|
+
res.status(200).type('html').send(loginPasswordPage(identity, returnTo, true));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
loginLimiter.recordSuccess(limiterKey);
|
|
189
|
+
setSession(req, res, account.accountId);
|
|
190
|
+
res.redirect(302, account.role === 'admin' && returnTo === '/dashboard' ? '/operator' : returnTo);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
app.get('/signup', (req, res) => {
|
|
194
|
+
res.status(200).type('html').send(signupPage(req.query?.return_to, false, requiresInvite()));
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
app.post('/signup', (req, res) => {
|
|
198
|
+
const returnTo = safeReturnTo(req.body?.return_to);
|
|
199
|
+
try {
|
|
200
|
+
const account = accountStore.createAccount({
|
|
201
|
+
email: req.body?.identity,
|
|
202
|
+
password: req.body?.password,
|
|
203
|
+
inviteCode: req.body?.invite,
|
|
204
|
+
requireInvite: requiresInvite()
|
|
205
|
+
});
|
|
206
|
+
setSession(req, res, account.accountId);
|
|
207
|
+
res.redirect(302, returnTo);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
const inviteInvalid = error?.code === 'INVITE_REQUIRED' || error?.code === 'INVITE_INVALID';
|
|
210
|
+
res.status(200).type('html').send(signupPage(returnTo, true, requiresInvite(), inviteInvalid));
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
app.post('/logout', (req, res) => {
|
|
215
|
+
clearSession(req, res);
|
|
216
|
+
res.redirect(302, '/login');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return { accountFromRequest, sessionAccountFromRequest, cookieName: ACCOUNT_COOKIE, clearSession, loginLocation, sessionBindingFromRequest };
|
|
220
|
+
}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
5
|
+
|
|
6
|
+
const INVITE_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
|
7
|
+
const INVITE_LENGTH = 8;
|
|
8
|
+
const SCRYPT_N = 16384;
|
|
9
|
+
const SCRYPT_R = 8;
|
|
10
|
+
const SCRYPT_P = 1;
|
|
11
|
+
const SCRYPT_KEYLEN = 32;
|
|
12
|
+
|
|
13
|
+
function accountError(code, message) {
|
|
14
|
+
const error = new Error(`${code}: ${message}`);
|
|
15
|
+
error.code = code;
|
|
16
|
+
return error;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function normalizeAccountEmail(value) {
|
|
20
|
+
const email = String(value || '').trim().toLowerCase();
|
|
21
|
+
if (!email || email.length > 320 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
22
|
+
throw accountError('INVALID_EMAIL', 'A valid email is required.');
|
|
23
|
+
}
|
|
24
|
+
return email;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizePassword(value) {
|
|
28
|
+
const password = String(value || '');
|
|
29
|
+
if (password.length < 8 || password.length > 1024) {
|
|
30
|
+
throw accountError('INVALID_PASSWORD', 'Password must be between 8 and 1024 characters.');
|
|
31
|
+
}
|
|
32
|
+
return password;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function hashPassword(password) {
|
|
36
|
+
const salt = crypto.randomBytes(16);
|
|
37
|
+
const digest = crypto.scryptSync(normalizePassword(password), salt, SCRYPT_KEYLEN, {
|
|
38
|
+
N: SCRYPT_N,
|
|
39
|
+
r: SCRYPT_R,
|
|
40
|
+
p: SCRYPT_P,
|
|
41
|
+
maxmem: 64 * 1024 * 1024
|
|
42
|
+
});
|
|
43
|
+
return `scrypt$${SCRYPT_N}$${SCRYPT_R}$${SCRYPT_P}$${salt.toString('base64url')}$${digest.toString('base64url')}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function verifyPasswordHash(password, encoded) {
|
|
47
|
+
try {
|
|
48
|
+
const [kind, nText, rText, pText, saltText, digestText] = String(encoded || '').split('$');
|
|
49
|
+
if (kind !== 'scrypt') return false;
|
|
50
|
+
const N = Number(nText);
|
|
51
|
+
const r = Number(rText);
|
|
52
|
+
const p = Number(pText);
|
|
53
|
+
if (!Number.isInteger(N) || !Number.isInteger(r) || !Number.isInteger(p)) return false;
|
|
54
|
+
const salt = Buffer.from(saltText, 'base64url');
|
|
55
|
+
const expected = Buffer.from(digestText, 'base64url');
|
|
56
|
+
const actual = crypto.scryptSync(String(password || ''), salt, expected.length, {
|
|
57
|
+
N,
|
|
58
|
+
r,
|
|
59
|
+
p,
|
|
60
|
+
maxmem: 64 * 1024 * 1024
|
|
61
|
+
});
|
|
62
|
+
return expected.length === actual.length && crypto.timingSafeEqual(expected, actual);
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const DUMMY_PASSWORD_HASH = hashPassword('hcu-dummy-password-not-an-account');
|
|
69
|
+
|
|
70
|
+
function sha256Hex(value) {
|
|
71
|
+
return crypto.createHash('sha256').update(String(value || ''), 'utf8').digest('hex');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function inviteHash(code) {
|
|
75
|
+
return sha256Hex(String(code || '').trim().toUpperCase());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function sessionHash(sessionId) {
|
|
79
|
+
return sha256Hex(String(sessionId || '').trim());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function generateInviteCode() {
|
|
83
|
+
const bytes = crypto.randomBytes(INVITE_LENGTH);
|
|
84
|
+
let value = '';
|
|
85
|
+
for (let index = 0; index < INVITE_LENGTH; index += 1) {
|
|
86
|
+
value += INVITE_ALPHABET[bytes[index] % INVITE_ALPHABET.length];
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function rowToAccount(row) {
|
|
92
|
+
if (!row) return null;
|
|
93
|
+
return {
|
|
94
|
+
accountId: row.account_id,
|
|
95
|
+
email: row.email_normalized,
|
|
96
|
+
role: row.role,
|
|
97
|
+
createdAt: Number(row.created_at),
|
|
98
|
+
revokedAt: row.revoked_at === null ? null : Number(row.revoked_at)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function rowToInvite(row) {
|
|
103
|
+
if (!row) return null;
|
|
104
|
+
return {
|
|
105
|
+
inviteHash: row.invite_hash,
|
|
106
|
+
createdAt: Number(row.created_at),
|
|
107
|
+
usedAt: row.used_at === null ? null : Number(row.used_at),
|
|
108
|
+
revokedAt: row.revoked_at === null ? null : Number(row.revoked_at)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createAccountStore({ dbPath, now = () => Date.now() } = {}) {
|
|
113
|
+
if (!dbPath) throw new Error('dbPath is required for account store.');
|
|
114
|
+
const resolved = path.resolve(dbPath);
|
|
115
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
116
|
+
const db = new DatabaseSync(resolved);
|
|
117
|
+
db.exec(`
|
|
118
|
+
PRAGMA journal_mode = WAL;
|
|
119
|
+
PRAGMA synchronous = NORMAL;
|
|
120
|
+
PRAGMA busy_timeout = 5000;
|
|
121
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
122
|
+
account_id TEXT PRIMARY KEY,
|
|
123
|
+
email_normalized TEXT NOT NULL UNIQUE,
|
|
124
|
+
password_hash TEXT NOT NULL,
|
|
125
|
+
role TEXT NOT NULL CHECK (role IN ('user', 'admin')),
|
|
126
|
+
created_at INTEGER NOT NULL,
|
|
127
|
+
revoked_at INTEGER
|
|
128
|
+
);
|
|
129
|
+
CREATE TABLE IF NOT EXISTS invites (
|
|
130
|
+
invite_hash TEXT PRIMARY KEY,
|
|
131
|
+
created_at INTEGER NOT NULL,
|
|
132
|
+
used_at INTEGER,
|
|
133
|
+
revoked_at INTEGER
|
|
134
|
+
);
|
|
135
|
+
CREATE TABLE IF NOT EXISTS account_sessions (
|
|
136
|
+
session_hash TEXT PRIMARY KEY,
|
|
137
|
+
account_id TEXT NOT NULL,
|
|
138
|
+
created_at INTEGER NOT NULL,
|
|
139
|
+
expires_at INTEGER NOT NULL,
|
|
140
|
+
revoked_at INTEGER
|
|
141
|
+
);
|
|
142
|
+
CREATE INDEX IF NOT EXISTS accounts_email_idx ON accounts(email_normalized);
|
|
143
|
+
CREATE INDEX IF NOT EXISTS account_sessions_account_idx ON account_sessions(account_id);
|
|
144
|
+
`);
|
|
145
|
+
|
|
146
|
+
const getByIdStatement = db.prepare(`
|
|
147
|
+
SELECT account_id, email_normalized, password_hash, role, created_at, revoked_at
|
|
148
|
+
FROM accounts WHERE account_id = ?
|
|
149
|
+
`);
|
|
150
|
+
const getByEmailStatement = db.prepare(`
|
|
151
|
+
SELECT account_id, email_normalized, password_hash, role, created_at, revoked_at
|
|
152
|
+
FROM accounts WHERE email_normalized = ?
|
|
153
|
+
`);
|
|
154
|
+
const listAccountsStatement = db.prepare(`
|
|
155
|
+
SELECT account_id, email_normalized, password_hash, role, created_at, revoked_at
|
|
156
|
+
FROM accounts ORDER BY created_at, account_id
|
|
157
|
+
`);
|
|
158
|
+
const insertAccountStatement = db.prepare(`
|
|
159
|
+
INSERT INTO accounts (account_id, email_normalized, password_hash, role, created_at, revoked_at)
|
|
160
|
+
VALUES (?, ?, ?, ?, ?, NULL)
|
|
161
|
+
`);
|
|
162
|
+
const revokeAccountStatement = db.prepare(`
|
|
163
|
+
UPDATE accounts SET revoked_at = ? WHERE account_id = ? AND revoked_at IS NULL
|
|
164
|
+
`);
|
|
165
|
+
const deleteAccountStatement = db.prepare('DELETE FROM accounts WHERE account_id = ?');
|
|
166
|
+
const insertSessionStatement = db.prepare(`
|
|
167
|
+
INSERT INTO account_sessions (session_hash, account_id, created_at, expires_at, revoked_at)
|
|
168
|
+
VALUES (?, ?, ?, ?, NULL)
|
|
169
|
+
`);
|
|
170
|
+
const getSessionAccountStatement = db.prepare(`
|
|
171
|
+
SELECT a.account_id, a.email_normalized, a.password_hash, a.role, a.created_at, a.revoked_at
|
|
172
|
+
FROM account_sessions s
|
|
173
|
+
JOIN accounts a ON a.account_id = s.account_id
|
|
174
|
+
WHERE s.session_hash = ? AND s.revoked_at IS NULL AND s.expires_at >= ? AND a.revoked_at IS NULL
|
|
175
|
+
`);
|
|
176
|
+
const revokeSessionStatement = db.prepare('UPDATE account_sessions SET revoked_at = ? WHERE session_hash = ? AND revoked_at IS NULL');
|
|
177
|
+
const revokeAccountSessionsStatement = db.prepare('UPDATE account_sessions SET revoked_at = ? WHERE account_id = ? AND revoked_at IS NULL');
|
|
178
|
+
const deleteAccountSessionsStatement = db.prepare('DELETE FROM account_sessions WHERE account_id = ?');
|
|
179
|
+
|
|
180
|
+
const getInviteStatement = db.prepare('SELECT invite_hash, created_at, used_at, revoked_at FROM invites WHERE invite_hash = ?');
|
|
181
|
+
const listInvitesStatement = db.prepare('SELECT invite_hash, created_at, used_at, revoked_at FROM invites ORDER BY created_at, invite_hash');
|
|
182
|
+
const insertInviteStatement = db.prepare('INSERT INTO invites (invite_hash, created_at, used_at, revoked_at) VALUES (?, ?, NULL, NULL)');
|
|
183
|
+
const consumeInviteStatement = db.prepare('UPDATE invites SET used_at = ? WHERE invite_hash = ? AND used_at IS NULL AND revoked_at IS NULL');
|
|
184
|
+
const revokeInviteStatement = db.prepare('UPDATE invites SET revoked_at = ? WHERE invite_hash = ? AND used_at IS NULL AND revoked_at IS NULL');
|
|
185
|
+
|
|
186
|
+
function getAccount(accountId) {
|
|
187
|
+
return rowToAccount(getByIdStatement.get(String(accountId || '').trim()));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getAccountByEmail(email) {
|
|
191
|
+
return rowToAccount(getByEmailStatement.get(normalizeAccountEmail(email)));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function listAccounts() {
|
|
195
|
+
return listAccountsStatement.all().map(rowToAccount);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function createAccountInternal({ email, password, role, inviteCode, requireInvite }) {
|
|
199
|
+
const normalizedEmail = normalizeAccountEmail(email);
|
|
200
|
+
const passwordHash = hashPassword(password);
|
|
201
|
+
const createdAt = Number(now());
|
|
202
|
+
const accountId = crypto.randomUUID();
|
|
203
|
+
const invite = String(inviteCode || '').trim().toUpperCase();
|
|
204
|
+
const hash = invite ? inviteHash(invite) : null;
|
|
205
|
+
let began = false;
|
|
206
|
+
try {
|
|
207
|
+
db.exec('BEGIN IMMEDIATE');
|
|
208
|
+
began = true;
|
|
209
|
+
if (requireInvite) {
|
|
210
|
+
if (!invite) throw accountError('INVITE_REQUIRED', 'A valid invite code is required.');
|
|
211
|
+
const row = getInviteStatement.get(hash);
|
|
212
|
+
if (!row || row.used_at !== null || row.revoked_at !== null) {
|
|
213
|
+
throw accountError('INVITE_INVALID', 'Invite code is invalid, used, or revoked.');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
insertAccountStatement.run(accountId, normalizedEmail, passwordHash, role, createdAt);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (String(error?.message || '').toLowerCase().includes('unique')) {
|
|
220
|
+
throw accountError('ACCOUNT_EXISTS', 'Account already exists.');
|
|
221
|
+
}
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
if (requireInvite) {
|
|
225
|
+
const changed = consumeInviteStatement.run(createdAt, hash);
|
|
226
|
+
if (Number(changed.changes) !== 1) throw accountError('INVITE_INVALID', 'Invite code is invalid, used, or revoked.');
|
|
227
|
+
}
|
|
228
|
+
db.exec('COMMIT');
|
|
229
|
+
began = false;
|
|
230
|
+
return getAccount(accountId);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (began) { try { db.exec('ROLLBACK'); } catch {} }
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function createAccount({ email, password, inviteCode, requireInvite = false }) {
|
|
238
|
+
return createAccountInternal({ email, password, role: 'user', inviteCode, requireInvite: Boolean(requireInvite) });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function createAdmin({ email, password }) {
|
|
242
|
+
return createAccountInternal({ email, password, role: 'admin', requireInvite: false });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function verifyPassword(email, password) {
|
|
246
|
+
let normalizedEmail;
|
|
247
|
+
try { normalizedEmail = normalizeAccountEmail(email); } catch { normalizedEmail = null; }
|
|
248
|
+
const row = normalizedEmail ? getByEmailStatement.get(normalizedEmail) : null;
|
|
249
|
+
const matches = verifyPasswordHash(password, row?.password_hash || DUMMY_PASSWORD_HASH);
|
|
250
|
+
if (!row || row.revoked_at !== null || !matches) return null;
|
|
251
|
+
return rowToAccount(row);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function createSession(accountId, { ttlMs = 7 * 24 * 60 * 60 * 1000 } = {}) {
|
|
255
|
+
const id = String(accountId || '').trim();
|
|
256
|
+
const account = getAccount(id);
|
|
257
|
+
if (!account || account.revokedAt !== null) throw accountError('ACCOUNT_NOT_FOUND', 'Unknown or revoked account.');
|
|
258
|
+
if (!Number.isInteger(ttlMs) || ttlMs < 60_000 || ttlMs > 30 * 24 * 60 * 60 * 1000) {
|
|
259
|
+
throw accountError('INVALID_SESSION_TTL', 'Session ttlMs must be between 60000 and 2592000000.');
|
|
260
|
+
}
|
|
261
|
+
const sessionId = crypto.randomBytes(32).toString('base64url');
|
|
262
|
+
const createdAt = Number(now());
|
|
263
|
+
const expiresAt = createdAt + ttlMs;
|
|
264
|
+
insertSessionStatement.run(sessionHash(sessionId), id, createdAt, expiresAt);
|
|
265
|
+
return { sessionId, accountId: id, createdAt, expiresAt };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function getSessionAccount(sessionId) {
|
|
269
|
+
const id = String(sessionId || '').trim();
|
|
270
|
+
if (!id) return null;
|
|
271
|
+
return rowToAccount(getSessionAccountStatement.get(sessionHash(id), Number(now())));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function revokeSession(sessionId) {
|
|
275
|
+
const id = String(sessionId || '').trim();
|
|
276
|
+
if (!id) return false;
|
|
277
|
+
return Number(revokeSessionStatement.run(Number(now()), sessionHash(id)).changes) === 1;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function revokeAccount(accountId) {
|
|
281
|
+
const id = String(accountId || '').trim();
|
|
282
|
+
const existing = getAccount(id);
|
|
283
|
+
if (!existing) throw accountError('ACCOUNT_NOT_FOUND', 'Unknown account.');
|
|
284
|
+
if (existing.revokedAt !== null) return existing;
|
|
285
|
+
const revokedAt = Number(now());
|
|
286
|
+
let began = false;
|
|
287
|
+
try {
|
|
288
|
+
db.exec('BEGIN IMMEDIATE');
|
|
289
|
+
began = true;
|
|
290
|
+
revokeAccountStatement.run(revokedAt, id);
|
|
291
|
+
revokeAccountSessionsStatement.run(revokedAt, id);
|
|
292
|
+
db.exec('COMMIT');
|
|
293
|
+
began = false;
|
|
294
|
+
return getAccount(id);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (began) { try { db.exec('ROLLBACK'); } catch {} }
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function deleteAccount(accountId) {
|
|
302
|
+
const id = String(accountId || '').trim();
|
|
303
|
+
let began = false;
|
|
304
|
+
try {
|
|
305
|
+
db.exec('BEGIN IMMEDIATE');
|
|
306
|
+
began = true;
|
|
307
|
+
deleteAccountSessionsStatement.run(id);
|
|
308
|
+
const deleted = Number(deleteAccountStatement.run(id).changes) === 1;
|
|
309
|
+
db.exec('COMMIT');
|
|
310
|
+
began = false;
|
|
311
|
+
return deleted;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
if (began) { try { db.exec('ROLLBACK'); } catch {} }
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function createInvite() {
|
|
319
|
+
const createdAt = Number(now());
|
|
320
|
+
for (let attempt = 0; attempt < 16; attempt += 1) {
|
|
321
|
+
const code = generateInviteCode();
|
|
322
|
+
try {
|
|
323
|
+
insertInviteStatement.run(inviteHash(code), createdAt);
|
|
324
|
+
return { code, createdAt };
|
|
325
|
+
} catch (error) {
|
|
326
|
+
if (!String(error?.message || '').toLowerCase().includes('unique')) throw error;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
throw new Error('Unable to allocate a unique invite code.');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function listInvites() {
|
|
333
|
+
return listInvitesStatement.all().map(rowToInvite);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function revokeInvite(code) {
|
|
337
|
+
const normalized = String(code || '').trim().toUpperCase();
|
|
338
|
+
if (!normalized) throw accountError('INVITE_INVALID', 'Invite code is required.');
|
|
339
|
+
const hash = inviteHash(normalized);
|
|
340
|
+
const existing = getInviteStatement.get(hash);
|
|
341
|
+
if (!existing) throw accountError('INVITE_INVALID', 'Invite code is invalid.');
|
|
342
|
+
if (existing.used_at !== null) throw accountError('INVITE_INVALID', 'Invite code is already used.');
|
|
343
|
+
if (existing.revoked_at === null) revokeInviteStatement.run(Number(now()), hash);
|
|
344
|
+
return rowToInvite(getInviteStatement.get(hash));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function close() {
|
|
348
|
+
db.close();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
getAccount,
|
|
353
|
+
getAccountByEmail,
|
|
354
|
+
listAccounts,
|
|
355
|
+
createAccount,
|
|
356
|
+
createAdmin,
|
|
357
|
+
verifyPassword,
|
|
358
|
+
createSession,
|
|
359
|
+
getSessionAccount,
|
|
360
|
+
revokeSession,
|
|
361
|
+
revokeAccount,
|
|
362
|
+
deleteAccount,
|
|
363
|
+
createInvite,
|
|
364
|
+
listInvites,
|
|
365
|
+
revokeInvite,
|
|
366
|
+
close
|
|
367
|
+
};
|
|
368
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import { createAccountStore } from './account-store.mjs';
|
|
7
|
+
|
|
8
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
const runtimeDirectory = path.resolve(process.env.MCP_RUNTIME_DIR || path.join(packageRoot, '.runtime'));
|
|
10
|
+
const dbPath = path.resolve(process.env.MCP_GATEWAY_DB_PATH || path.join(runtimeDirectory, 'gateway.sqlite'));
|
|
11
|
+
const [area, action, ...args] = process.argv.slice(2);
|
|
12
|
+
|
|
13
|
+
function usage() {
|
|
14
|
+
console.error([
|
|
15
|
+
'Usage:',
|
|
16
|
+
' node scripts/admin.mjs account create-admin <email> # password from HCU_ADMIN_PASSWORD',
|
|
17
|
+
' node scripts/admin.mjs account list',
|
|
18
|
+
' node scripts/admin.mjs account revoke <account_id|email>',
|
|
19
|
+
' node scripts/admin.mjs account delete <account_id|email>',
|
|
20
|
+
' node scripts/admin.mjs invite create [count]',
|
|
21
|
+
' node scripts/admin.mjs invite list',
|
|
22
|
+
' node scripts/admin.mjs invite revoke <code>'
|
|
23
|
+
].join('\n'));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function print(value) {
|
|
27
|
+
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveAccount(store, value) {
|
|
31
|
+
const key = String(value || '').trim();
|
|
32
|
+
if (!key) throw new Error('account_id or email is required.');
|
|
33
|
+
return key.includes('@') ? store.getAccountByEmail(key) : store.getAccount(key);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const store = createAccountStore({ dbPath });
|
|
37
|
+
try {
|
|
38
|
+
if (area === 'account' && action === 'create-admin') {
|
|
39
|
+
const email = args[0];
|
|
40
|
+
const password = process.env.HCU_ADMIN_PASSWORD;
|
|
41
|
+
if (!email || !password) throw new Error('create-admin requires <email> and HCU_ADMIN_PASSWORD in the local environment.');
|
|
42
|
+
print({ ok: true, account: store.createAdmin({ email, password }) });
|
|
43
|
+
} else if (area === 'account' && action === 'list') {
|
|
44
|
+
print({ ok: true, accounts: store.listAccounts() });
|
|
45
|
+
} else if (area === 'account' && ['revoke', 'delete'].includes(action)) {
|
|
46
|
+
const account = resolveAccount(store, args[0]);
|
|
47
|
+
if (!account) throw new Error('Unknown account.');
|
|
48
|
+
if (action === 'revoke') print({ ok: true, account: store.revokeAccount(account.accountId) });
|
|
49
|
+
else print({ ok: true, account_id: account.accountId, deleted: store.deleteAccount(account.accountId) });
|
|
50
|
+
} else if (area === 'invite' && action === 'create') {
|
|
51
|
+
const count = args[0] === undefined ? 1 : Number(args[0]);
|
|
52
|
+
if (!Number.isInteger(count) || count < 1 || count > 100) throw new Error('invite create count must be an integer from 1 to 100.');
|
|
53
|
+
const codes = Array.from({ length: count }, () => store.createInvite().code);
|
|
54
|
+
print({ ok: true, codes });
|
|
55
|
+
} else if (area === 'invite' && action === 'list') {
|
|
56
|
+
print({ ok: true, invites: store.listInvites() });
|
|
57
|
+
} else if (area === 'invite' && action === 'revoke') {
|
|
58
|
+
print({ ok: true, invite: store.revokeInvite(args[0]) });
|
|
59
|
+
} else {
|
|
60
|
+
usage();
|
|
61
|
+
process.exitCode = 2;
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(JSON.stringify({ ok: false, code: error?.code || 'ADMIN_COMMAND_FAILED', error: error?.message || String(error) }));
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
} finally {
|
|
67
|
+
store.close();
|
|
68
|
+
}
|