@synapta/skills 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +11 -4
- package/package.json +3 -4
- package/skills/ATTRIBUTION.md +80 -0
- package/skills/accessibility-audit/SKILL.md +325 -0
- package/skills/accessibility-audit/reference/wcag-checklist.md +103 -0
- package/skills/apns-notifier/SKILL.md +86 -0
- package/skills/approval-policy-enforcer/SKILL.md +66 -0
- package/skills/apps-sdk-builder/LICENSE.txt +201 -0
- package/skills/apps-sdk-builder/SKILL.md +328 -0
- package/skills/apps-sdk-builder/agents/openai.yaml +13 -0
- package/skills/apps-sdk-builder/references/app-archetypes.md +132 -0
- package/skills/apps-sdk-builder/references/apps-sdk-docs-workflow.md +135 -0
- package/skills/apps-sdk-builder/references/interactive-state-sync-patterns.md +113 -0
- package/skills/apps-sdk-builder/references/repo-contract-and-validation.md +93 -0
- package/skills/apps-sdk-builder/references/search-fetch-standard.md +67 -0
- package/skills/apps-sdk-builder/references/upstream-example-workflow.md +79 -0
- package/skills/apps-sdk-builder/references/window-openai-patterns.md +79 -0
- package/skills/apps-sdk-builder/scripts/scaffold_node_ext_apps.mjs +606 -0
- package/skills/architecture-selector/SKILL.md +64 -0
- package/skills/backlog-planner/SKILL.md +68 -0
- package/skills/carplay-entitlement-checker/SKILL.md +82 -0
- package/skills/concept-deepener/SKILL.md +86 -0
- package/skills/concept-discovery/SKILL.md +517 -0
- package/skills/concept-discovery/assets/sample-analysis.json +81 -0
- package/skills/concept-discovery/expected_outputs/sample-enum-dictionary.md +25 -0
- package/skills/concept-discovery/expected_outputs/sample-page-user-list.md +83 -0
- package/skills/concept-discovery/expected_outputs/sample-prd-readme.md +43 -0
- package/skills/concept-discovery/references/framework-patterns.md +228 -0
- package/skills/concept-discovery/references/prd-quality-checklist.md +65 -0
- package/skills/concept-discovery/scripts/codebase_analyzer.py +732 -0
- package/skills/concept-discovery/scripts/prd_scaffolder.py +435 -0
- package/skills/dast-zap/SKILL.md +453 -0
- package/skills/dast-zap/assets/.gitkeep +9 -0
- package/skills/dast-zap/assets/github_action.yml +207 -0
- package/skills/dast-zap/assets/gitlab_ci.yml +226 -0
- package/skills/dast-zap/assets/zap_automation.yaml +196 -0
- package/skills/dast-zap/assets/zap_context.xml +192 -0
- package/skills/dast-zap/references/EXAMPLE.md +40 -0
- package/skills/dast-zap/references/api_testing_guide.md +475 -0
- package/skills/dast-zap/references/authentication_guide.md +431 -0
- package/skills/dast-zap/references/false_positive_handling.md +427 -0
- package/skills/dast-zap/references/owasp_mapping.md +255 -0
- package/skills/dep-sbom-scan/SKILL.md +466 -0
- package/skills/deploy-cloudflare/SKILL.md +930 -0
- package/skills/deploy-docker/SKILL.md +55 -0
- package/skills/deploy-fly/SKILL.md +228 -0
- package/skills/deploy-k8s/SKILL.md +108 -0
- package/skills/deploy-k8s/assets/logo.png +0 -0
- package/skills/deploy-k8s/docs/README.md +29 -0
- package/skills/deploy-k8s/docs/SUMMARY.md +56 -0
- package/skills/deploy-k8s/docs/advanced/token-efficiency.md +61 -0
- package/skills/deploy-k8s/docs/architecture/multi-tenancy.md +96 -0
- package/skills/deploy-k8s/docs/architecture/storage-and-state.md +102 -0
- package/skills/deploy-k8s/docs/architecture/workload-patterns.md +87 -0
- package/skills/deploy-k8s/docs/book.json +16 -0
- package/skills/deploy-k8s/docs/community/changelog.md +34 -0
- package/skills/deploy-k8s/docs/community/contributing.md +67 -0
- package/skills/deploy-k8s/docs/core-concepts/failure-modes.md +153 -0
- package/skills/deploy-k8s/docs/core-concepts/philosophy.md +83 -0
- package/skills/deploy-k8s/docs/core-concepts/workflow.md +124 -0
- package/skills/deploy-k8s/docs/examples/bad-patterns.md +47 -0
- package/skills/deploy-k8s/docs/examples/do-dont-checklist.md +37 -0
- package/skills/deploy-k8s/docs/examples/good-patterns.md +49 -0
- package/skills/deploy-k8s/docs/failure-modes/api-drift.md +104 -0
- package/skills/deploy-k8s/docs/failure-modes/fragile-rollouts.md +99 -0
- package/skills/deploy-k8s/docs/failure-modes/insecure-workload-defaults.md +80 -0
- package/skills/deploy-k8s/docs/failure-modes/network-exposure.md +98 -0
- package/skills/deploy-k8s/docs/failure-modes/privilege-sprawl.md +91 -0
- package/skills/deploy-k8s/docs/failure-modes/resource-starvation.md +85 -0
- package/skills/deploy-k8s/docs/getting-started/installation.md +152 -0
- package/skills/deploy-k8s/docs/getting-started/quick-start.md +115 -0
- package/skills/deploy-k8s/docs/guides/helm-patterns.md +71 -0
- package/skills/deploy-k8s/docs/guides/kustomize-patterns.md +65 -0
- package/skills/deploy-k8s/docs/guides/observability.md +67 -0
- package/skills/deploy-k8s/docs/guides/security-hardening.md +59 -0
- package/skills/deploy-k8s/docs/guides/validation-and-policy.md +66 -0
- package/skills/deploy-k8s/docs/integrations/mcp-integration.md +52 -0
- package/skills/deploy-k8s/docs/package-lock.json +2892 -0
- package/skills/deploy-k8s/docs/package.json +13 -0
- package/skills/deploy-k8s/references/api-drift.md +298 -0
- package/skills/deploy-k8s/references/conditional/aks-patterns.md +70 -0
- package/skills/deploy-k8s/references/conditional/eks-patterns.md +79 -0
- package/skills/deploy-k8s/references/conditional/gitops-controllers.md +71 -0
- package/skills/deploy-k8s/references/conditional/gke-patterns.md +74 -0
- package/skills/deploy-k8s/references/conditional/observability-stacks.md +80 -0
- package/skills/deploy-k8s/references/conditional/openshift-patterns.md +67 -0
- package/skills/deploy-k8s/references/daemonset-operator-patterns.md +155 -0
- package/skills/deploy-k8s/references/deployment-patterns.md +146 -0
- package/skills/deploy-k8s/references/do-dont-patterns.md +87 -0
- package/skills/deploy-k8s/references/examples-bad.md +282 -0
- package/skills/deploy-k8s/references/examples-good.md +440 -0
- package/skills/deploy-k8s/references/fragile-rollouts.md +303 -0
- package/skills/deploy-k8s/references/helm-patterns.md +203 -0
- package/skills/deploy-k8s/references/insecure-workload-defaults.md +300 -0
- package/skills/deploy-k8s/references/job-patterns.md +120 -0
- package/skills/deploy-k8s/references/kustomize-patterns.md +239 -0
- package/skills/deploy-k8s/references/multi-tenancy.md +343 -0
- package/skills/deploy-k8s/references/network-exposure.md +481 -0
- package/skills/deploy-k8s/references/observability.md +302 -0
- package/skills/deploy-k8s/references/privilege-sprawl.md +273 -0
- package/skills/deploy-k8s/references/resource-starvation.md +374 -0
- package/skills/deploy-k8s/references/security-hardening.md +209 -0
- package/skills/deploy-k8s/references/stateful-patterns.md +130 -0
- package/skills/deploy-k8s/references/storage-and-state.md +330 -0
- package/skills/deploy-k8s/references/validation-and-policy.md +242 -0
- package/skills/deploy-railway/SKILL.md +235 -0
- package/skills/deploy-railway/references/analyze-db-mongo.md +84 -0
- package/skills/deploy-railway/references/analyze-db-mysql.md +254 -0
- package/skills/deploy-railway/references/analyze-db-postgres.md +479 -0
- package/skills/deploy-railway/references/analyze-db-redis.md +208 -0
- package/skills/deploy-railway/references/analyze-db.md +344 -0
- package/skills/deploy-railway/references/configure.md +309 -0
- package/skills/deploy-railway/references/deploy.md +195 -0
- package/skills/deploy-railway/references/operate.md +214 -0
- package/skills/deploy-railway/references/request.md +248 -0
- package/skills/deploy-railway/references/setup.md +312 -0
- package/skills/deploy-railway/scripts/analyze-mongo.py +1549 -0
- package/skills/deploy-railway/scripts/analyze-mysql.py +1195 -0
- package/skills/deploy-railway/scripts/analyze-postgres.py +3058 -0
- package/skills/deploy-railway/scripts/analyze-redis.py +1090 -0
- package/skills/deploy-railway/scripts/dal.py +671 -0
- package/skills/deploy-railway/scripts/enable-pg-stats.py +170 -0
- package/skills/deploy-railway/scripts/pg-extensions.py +370 -0
- package/skills/deploy-railway/scripts/railway-api.sh +52 -0
- package/skills/deploy-ssh/SKILL.md +91 -0
- package/skills/deploy-vercel/SKILL.md +304 -0
- package/skills/deploy-vercel/resources/deploy-codex.sh +301 -0
- package/skills/deploy-vercel/resources/deploy.sh +301 -0
- package/skills/docs-runbooks/SKILL.md +399 -0
- package/skills/drive-status-renderer/SKILL.md +62 -0
- package/skills/iac-scan/SKILL.md +680 -0
- package/skills/iac-scan/assets/.gitkeep +9 -0
- package/skills/iac-scan/assets/checkov_config.yaml +94 -0
- package/skills/iac-scan/assets/github_actions.yml +199 -0
- package/skills/iac-scan/assets/gitlab_ci.yml +218 -0
- package/skills/iac-scan/assets/pre_commit_config.yaml +92 -0
- package/skills/iac-scan/references/EXAMPLE.md +40 -0
- package/skills/iac-scan/references/compliance_mapping.md +237 -0
- package/skills/iac-scan/references/custom_policies.md +460 -0
- package/skills/iac-scan/references/suppression_guide.md +431 -0
- package/skills/incident-briefing/SKILL.md +66 -0
- package/skills/incident-triage/SKILL.md +481 -0
- package/{LICENSE → skills/mcp-builder/LICENSE.txt} +15 -14
- package/skills/mcp-builder/SKILL.md +244 -0
- package/skills/mcp-builder/reference/evaluation.md +602 -0
- package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/skills/mcp-builder/scripts/connections.py +151 -0
- package/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/skills/mobile-pairing/SKILL.md +52 -0
- package/skills/ops-sre/SKILL.md +297 -0
- package/skills/playwright-qa/LICENSE.txt +201 -0
- package/skills/playwright-qa/NOTICE.txt +14 -0
- package/skills/playwright-qa/SKILL.md +156 -0
- package/skills/playwright-qa/agents/openai.yaml +6 -0
- package/skills/playwright-qa/assets/playwright-small.svg +3 -0
- package/skills/playwright-qa/assets/playwright.png +0 -0
- package/skills/playwright-qa/references/cli.md +116 -0
- package/skills/playwright-qa/references/workflows.md +95 -0
- package/skills/playwright-qa/scripts/playwright_cli.sh +25 -0
- package/skills/release-publish/SKILL.md +85 -0
- package/skills/repo-bootstrap/SKILL.md +92 -0
- package/skills/repo-bootstrap/assets/example-workflows/validate-agents.yml +89 -0
- package/skills/repo-bootstrap/assets/root-thin.md +141 -0
- package/skills/repo-bootstrap/assets/root-verbose.md +149 -0
- package/skills/repo-bootstrap/assets/scoped/backend-go.md +107 -0
- package/skills/repo-bootstrap/assets/scoped/backend-php.md +94 -0
- package/skills/repo-bootstrap/assets/scoped/backend-python.md +84 -0
- package/skills/repo-bootstrap/assets/scoped/backend-typescript.md +89 -0
- package/skills/repo-bootstrap/assets/scoped/claude-code-skill.md +101 -0
- package/skills/repo-bootstrap/assets/scoped/cli.md +83 -0
- package/skills/repo-bootstrap/assets/scoped/concourse.md +196 -0
- package/skills/repo-bootstrap/assets/scoped/ddev.md +68 -0
- package/skills/repo-bootstrap/assets/scoped/docker.md +160 -0
- package/skills/repo-bootstrap/assets/scoped/documentation.md +98 -0
- package/skills/repo-bootstrap/assets/scoped/examples.md +96 -0
- package/skills/repo-bootstrap/assets/scoped/frontend-typescript.md +88 -0
- package/skills/repo-bootstrap/assets/scoped/github-actions.md +174 -0
- package/skills/repo-bootstrap/assets/scoped/gitlab-ci.md +174 -0
- package/skills/repo-bootstrap/assets/scoped/oro-bundle.md +209 -0
- package/skills/repo-bootstrap/assets/scoped/oro-project.md +170 -0
- package/skills/repo-bootstrap/assets/scoped/python-modern.md +170 -0
- package/skills/repo-bootstrap/assets/scoped/resources.md +96 -0
- package/skills/repo-bootstrap/assets/scoped/skill-repo.md +139 -0
- package/skills/repo-bootstrap/assets/scoped/symfony.md +168 -0
- package/skills/repo-bootstrap/assets/scoped/testing.md +87 -0
- package/skills/repo-bootstrap/assets/scoped/typo3-docs.md +103 -0
- package/skills/repo-bootstrap/assets/scoped/typo3-extension.md +133 -0
- package/skills/repo-bootstrap/assets/scoped/typo3-project.md +137 -0
- package/skills/repo-bootstrap/assets/scoped/typo3-testing.md +80 -0
- package/skills/repo-bootstrap/checkpoints.yaml +279 -0
- package/skills/repo-bootstrap/evals/evals.json +385 -0
- package/skills/repo-bootstrap/references/ai-contribution-guidelines.md +63 -0
- package/skills/repo-bootstrap/references/ai-tool-compatibility.md +223 -0
- package/skills/repo-bootstrap/references/directory-coverage.md +82 -0
- package/skills/repo-bootstrap/references/examples/coding-agent-cli/AGENTS.md +70 -0
- package/skills/repo-bootstrap/references/examples/coding-agent-cli/go.mod +3 -0
- package/skills/repo-bootstrap/references/examples/coding-agent-cli/scripts-AGENTS.md +389 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/.env.example +13 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/AGENTS.md +91 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/package.json +33 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/pnpm-lock.yaml +3 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/src/AGENTS.md +91 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/src/config.ts +28 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/src/controllers/userController.ts +74 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/src/index.ts +26 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/src/middleware/errorHandler.ts +45 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/src/middleware/requestLogger.ts +18 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/src/routes/health.ts +18 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/src/routes/users.ts +13 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/src/utils/errors.ts +40 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/src/utils/logger.ts +14 -0
- package/skills/repo-bootstrap/references/examples/express-api-ts/tsconfig.json +24 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/.env.example +19 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/AGENTS.md +92 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/pyproject.toml +88 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/AGENTS.md +85 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/__init__.py +3 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/config.py +49 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/main.py +66 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/models/__init__.py +13 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/models/item.py +43 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/models/user.py +40 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/routes/__init__.py +5 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/routes/health.py +20 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/routes/items.py +61 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/routes/users.py +55 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/services/__init__.py +6 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/services/item_service.py +77 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/src/services/user_service.py +69 -0
- package/skills/repo-bootstrap/references/examples/fastapi-app/uv.lock +4 -0
- package/skills/repo-bootstrap/references/examples/go-api-with-react-admin/.scopes +3 -0
- package/skills/repo-bootstrap/references/examples/go-api-with-react-admin/AGENTS.md +86 -0
- package/skills/repo-bootstrap/references/examples/go-api-with-react-admin/admin/package.json +20 -0
- package/skills/repo-bootstrap/references/examples/go-api-with-react-admin/admin/src/App.tsx +5 -0
- package/skills/repo-bootstrap/references/examples/go-api-with-react-admin/cmd/api/main.go +7 -0
- package/skills/repo-bootstrap/references/examples/go-api-with-react-admin/go.mod +2 -0
- package/skills/repo-bootstrap/references/examples/go-api-with-react-admin/main.go +7 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/.scopes +3 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/AGENTS.md +89 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/go.mod +2 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/internal/web/AGENTS.md +90 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/internal/web/package.json +17 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/internal/web/src/App.tsx +1 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/internal/web/src/Button.tsx +1 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/internal/web/src/Footer.tsx +1 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/internal/web/src/Header.tsx +1 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/internal/web/src/Sidebar.tsx +1 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/main.go +7 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/package-lock.json +0 -0
- package/skills/repo-bootstrap/references/examples/go-with-internal-web-tsx/package.json +12 -0
- package/skills/repo-bootstrap/references/examples/ldap-selfservice/AGENTS.md +70 -0
- package/skills/repo-bootstrap/references/examples/ldap-selfservice/go.mod +3 -0
- package/skills/repo-bootstrap/references/examples/ldap-selfservice/internal-AGENTS.md +371 -0
- package/skills/repo-bootstrap/references/examples/ldap-selfservice/internal-web-AGENTS.md +448 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/.scopes +3 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/AGENTS.md +91 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/composer.json +8 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/package.json +15 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/pnpm-lock.yaml +0 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/src/Controller.php +3 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/web/AGENTS.md +92 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/web/package.json +26 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/web/src/App.tsx +3 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/web/src/Button.tsx +10 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/web/src/Footer.tsx +9 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/web/src/Header.tsx +9 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/web/src/main.tsx +3 -0
- package/skills/repo-bootstrap/references/examples/php-with-frontend/web/tsconfig.json +13 -0
- package/skills/repo-bootstrap/references/examples/pnpm-workspace/AGENTS.md +75 -0
- package/skills/repo-bootstrap/references/examples/pnpm-workspace/package.json +7 -0
- package/skills/repo-bootstrap/references/examples/pnpm-workspace/packages/web/package.json +11 -0
- package/skills/repo-bootstrap/references/examples/pnpm-workspace/packages/web/src/index.ts +11 -0
- package/skills/repo-bootstrap/references/examples/pnpm-workspace/pnpm-lock.yaml +42 -0
- package/skills/repo-bootstrap/references/examples/pnpm-workspace/pnpm-workspace.yaml +2 -0
- package/skills/repo-bootstrap/references/examples/simple-ldap-go/AGENTS.md +70 -0
- package/skills/repo-bootstrap/references/examples/simple-ldap-go/examples-AGENTS.md +45 -0
- package/skills/repo-bootstrap/references/examples/simple-ldap-go/go.mod +3 -0
- package/skills/repo-bootstrap/references/examples/t3x-rte-ckeditor-image/AGENTS.md +70 -0
- package/skills/repo-bootstrap/references/examples/t3x-rte-ckeditor-image/Classes-AGENTS.md +392 -0
- package/skills/repo-bootstrap/references/examples/t3x-rte-ckeditor-image/composer.json +8 -0
- package/skills/repo-bootstrap/references/feedback-memory-schema.md +135 -0
- package/skills/repo-bootstrap/references/git-hooks-setup.md +79 -0
- package/skills/repo-bootstrap/references/output-structure.md +124 -0
- package/skills/repo-bootstrap/references/scripts-guide.md +175 -0
- package/skills/repo-bootstrap/references/verification-guide.md +137 -0
- package/skills/repo-bootstrap/scripts/analyze-git-history.sh +315 -0
- package/skills/repo-bootstrap/scripts/check-freshness.sh +230 -0
- package/skills/repo-bootstrap/scripts/detect-golden-samples.sh +161 -0
- package/skills/repo-bootstrap/scripts/detect-heuristics.sh +93 -0
- package/skills/repo-bootstrap/scripts/detect-project.sh +486 -0
- package/skills/repo-bootstrap/scripts/detect-scopes.sh +330 -0
- package/skills/repo-bootstrap/scripts/detect-utilities.sh +133 -0
- package/skills/repo-bootstrap/scripts/extract-adrs.sh +194 -0
- package/skills/repo-bootstrap/scripts/extract-agent-configs.sh +331 -0
- package/skills/repo-bootstrap/scripts/extract-architecture-rules.sh +522 -0
- package/skills/repo-bootstrap/scripts/extract-ci-commands.sh +385 -0
- package/skills/repo-bootstrap/scripts/extract-ci-rules.sh +384 -0
- package/skills/repo-bootstrap/scripts/extract-commands.sh +358 -0
- package/skills/repo-bootstrap/scripts/extract-documentation.sh +308 -0
- package/skills/repo-bootstrap/scripts/extract-github-rulesets.sh +96 -0
- package/skills/repo-bootstrap/scripts/extract-github-settings.sh +88 -0
- package/skills/repo-bootstrap/scripts/extract-ide-settings.sh +228 -0
- package/skills/repo-bootstrap/scripts/extract-platform-files.sh +290 -0
- package/skills/repo-bootstrap/scripts/extract-quality-configs.sh +442 -0
- package/skills/repo-bootstrap/scripts/generate-agents.sh +2424 -0
- package/skills/repo-bootstrap/scripts/generate-file-map.sh +153 -0
- package/skills/repo-bootstrap/scripts/lib/config-root.sh +211 -0
- package/skills/repo-bootstrap/scripts/lib/summary.sh +244 -0
- package/skills/repo-bootstrap/scripts/lib/template.sh +397 -0
- package/skills/repo-bootstrap/scripts/validate-structure.sh +324 -0
- package/skills/repo-bootstrap/scripts/verify-commands.sh +615 -0
- package/skills/repo-bootstrap/scripts/verify-content.sh +302 -0
- package/skills/schema-api-contracts/SKILL.md +56 -0
- package/skills/secret-hygiene/SKILL.md +511 -0
- package/skills/secret-hygiene/assets/.gitkeep +9 -0
- package/skills/secret-hygiene/assets/config-balanced.toml +81 -0
- package/skills/secret-hygiene/assets/config-custom.toml +178 -0
- package/skills/secret-hygiene/assets/config-strict.toml +48 -0
- package/skills/secret-hygiene/assets/github-action.yml +181 -0
- package/skills/secret-hygiene/assets/gitlab-ci.yml +257 -0
- package/skills/secret-hygiene/assets/precommit-config.yaml +70 -0
- package/skills/secret-hygiene/references/EXAMPLE.md +40 -0
- package/skills/secret-hygiene/references/compliance_mapping.md +538 -0
- package/skills/secret-hygiene/references/detection_rules.md +276 -0
- package/skills/secret-hygiene/references/false_positives.md +598 -0
- package/skills/secret-hygiene/references/remediation_guide.md +530 -0
- package/skills/stack-selector/SKILL.md +56 -0
- package/skills/telegram-control/SKILL.md +110 -0
- package/skills/telegram-control/references/architecture.md +184 -0
- package/skills/telegram-control/references/convex.md +173 -0
- package/skills/telegram-control/references/error_handling.md +212 -0
- package/skills/telegram-control/references/initial_setup.md +165 -0
- package/skills/telegram-control/references/telegram_api.md +156 -0
- package/skills/telegram-control/scripts/cancel_message.ts +53 -0
- package/skills/telegram-control/scripts/list_scheduled.ts +103 -0
- package/skills/telegram-control/scripts/logger.ts +121 -0
- package/skills/telegram-control/scripts/proxy-util.ts +11 -0
- package/skills/telegram-control/scripts/schedule_message.ts +216 -0
- package/skills/telegram-control/scripts/send_message.ts +115 -0
- package/skills/telegram-control/scripts/setup.ts +185 -0
- package/skills/telegram-control/scripts/types.ts +75 -0
- package/skills/telegram-control/scripts/view_history.ts +74 -0
- package/skills/test-strategy/SKILL.md +352 -0
- package/skills/threat-model/SKILL.md +303 -0
- package/skills/threat-model/examples/example-output.md +196 -0
- package/skills/threat-model/template.md +96 -0
- package/skills/ts-lint/SKILL.md +80 -0
- package/skills/ui-flow/SKILL.md +668 -0
- package/skills/voice-command-router/SKILL.md +51 -0
- package/skills/widget-live-activity-sync/SKILL.md +66 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Window.openai Patterns
|
|
2
|
+
|
|
3
|
+
Load this reference when a task needs ChatGPT-only widget features, when translating older examples that use an `app` wrapper, or when a React widget should read host globals safely.
|
|
4
|
+
|
|
5
|
+
## Core Rule
|
|
6
|
+
|
|
7
|
+
- Build baseline widget behavior on the MCP Apps bridge: `ui/*` notifications, `tools/call`, `ui/message`, and `ui/update-model-context`.
|
|
8
|
+
- Use `window.openai` only when the task specifically benefits from ChatGPT-only runtime conveniences.
|
|
9
|
+
- Treat `window.openai` as additive. The app should still have a coherent baseline path on the MCP Apps standard when possible.
|
|
10
|
+
|
|
11
|
+
## Canonical `window.openai` Surface
|
|
12
|
+
|
|
13
|
+
### State And Data
|
|
14
|
+
|
|
15
|
+
- `window.openai.toolInput`: tool arguments supplied by the host
|
|
16
|
+
- `window.openai.toolOutput`: current `structuredContent`
|
|
17
|
+
- `window.openai.toolResponseMetadata`: current `_meta` payload (widget-only)
|
|
18
|
+
- `window.openai.widgetState`: persisted widget-local snapshot
|
|
19
|
+
- `window.openai.setWidgetState(state)`: persist widget-local snapshot after meaningful UI changes
|
|
20
|
+
|
|
21
|
+
### Runtime APIs
|
|
22
|
+
|
|
23
|
+
- `window.openai.callTool(name, args)`: call another MCP tool from the widget
|
|
24
|
+
- `window.openai.sendFollowUpMessage({ prompt, scrollToBottom? })`: ask ChatGPT to post a widget-authored follow-up message
|
|
25
|
+
- `window.openai.openExternal({ href, redirectUrl? })`: open an external URL through ChatGPT's vetted flow
|
|
26
|
+
- `window.openai.requestDisplayMode({ mode })`: request `inline`, `pip`, or `fullscreen`
|
|
27
|
+
- `window.openai.requestModal({ params, template? })`: open a host-owned modal
|
|
28
|
+
- `window.openai.requestClose()`: ask ChatGPT to close the widget
|
|
29
|
+
- `window.openai.uploadFile(file, options?)`: upload a file from the widget
|
|
30
|
+
- `window.openai.selectFiles()`: open ChatGPT's file library picker and return app-authorized files
|
|
31
|
+
- `window.openai.getFileDownloadUrl({ fileId })`: resolve a temporary download URL
|
|
32
|
+
- `window.openai.notifyIntrinsicHeight(...)`: report dynamic height changes
|
|
33
|
+
- `window.openai.setOpenInAppUrl({ href })`: override the fullscreen punch-out target
|
|
34
|
+
|
|
35
|
+
### Context Signals
|
|
36
|
+
|
|
37
|
+
- `window.openai.theme`
|
|
38
|
+
- `window.openai.displayMode`
|
|
39
|
+
- `window.openai.maxHeight`
|
|
40
|
+
- `window.openai.safeArea`
|
|
41
|
+
- `window.openai.view`
|
|
42
|
+
- `window.openai.userAgent`
|
|
43
|
+
- `window.openai.locale`
|
|
44
|
+
|
|
45
|
+
## Mapping From Repo Wrapper Examples
|
|
46
|
+
|
|
47
|
+
- `app.callServerTool({ name, arguments })`:
|
|
48
|
+
Use `window.openai.callTool(name, args)` when you intentionally want the ChatGPT compatibility layer.
|
|
49
|
+
Use `tools/call` over the bridge when you want the portable MCP Apps path.
|
|
50
|
+
- `app.sendMessage(...)`:
|
|
51
|
+
Use `ui/message` for portable bridge messaging.
|
|
52
|
+
If the task is intentionally ChatGPT-specific, `window.openai.sendFollowUpMessage({ prompt })` is the closest supported path.
|
|
53
|
+
- `app.updateModelContext(...)`:
|
|
54
|
+
Use `ui/update-model-context` over the bridge.
|
|
55
|
+
This is part of the standard bridge, not a `window.openai` feature.
|
|
56
|
+
- `app.openLink({ url })`:
|
|
57
|
+
Use `window.openai.openExternal({ href: url })` when you intentionally want ChatGPT's external navigation flow.
|
|
58
|
+
- `app.requestDisplayMode({ mode })`:
|
|
59
|
+
Use `window.openai.requestDisplayMode({ mode })`.
|
|
60
|
+
- `app.getHostContext()`:
|
|
61
|
+
Read the documented globals directly (`theme`, `displayMode`, `locale`, `maxHeight`, `safeArea`, `userAgent`).
|
|
62
|
+
- `app.getHostCapabilities()` / `app.getHostVersion()`:
|
|
63
|
+
These are wrapper-level convenience APIs.
|
|
64
|
+
Prefer feature detection (`if (window.openai?.requestModal)`) and the documented globals instead of teaching these as the primary public surface.
|
|
65
|
+
|
|
66
|
+
## File Patterns
|
|
67
|
+
|
|
68
|
+
- Use `window.openai.uploadFile(file)` when the user is adding a new local file inside the widget.
|
|
69
|
+
- Use `window.openai.uploadFile(file, { library: true })` when the upload should also be saved into the user's ChatGPT file library.
|
|
70
|
+
- Use `window.openai.selectFiles()` when the user should be able to reuse files that are already in their ChatGPT file library instead of uploading again.
|
|
71
|
+
- Use `window.openai.getFileDownloadUrl({ fileId })` when the widget needs a temporary URL for previewing a file or forwarding it through a file-param payload.
|
|
72
|
+
- Feature-detect these helpers in the widget (`if (window.openai?.selectFiles)`) and provide a fallback upload flow when a ChatGPT-only helper is unavailable.
|
|
73
|
+
|
|
74
|
+
## React Helper Extraction
|
|
75
|
+
|
|
76
|
+
- The repo's `src/use-openai-global.ts` is a good baseline for subscribing to host global changes without scattering direct `window.openai` reads through components.
|
|
77
|
+
- The repo's `src/use-widget-state.ts` is a good baseline for mirroring React state into `window.openai.setWidgetState(...)`.
|
|
78
|
+
- The repo's `src/use-widget-props.ts` is a good baseline for reading typed `toolOutput` with a local fallback.
|
|
79
|
+
- Keep these helpers optional. Do not force a React abstraction when a simple vanilla widget is enough.
|
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { mkdirSync, writeFileSync, existsSync, readdirSync, lstatSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
function toSlug(value) {
|
|
7
|
+
const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8
|
+
return normalized || "example-chatgpt-app";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function toToolName(value) {
|
|
12
|
+
const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
13
|
+
return normalized || "show_example";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function toTitle(value) {
|
|
17
|
+
const parts = value.split(/[-_]+/).filter(Boolean);
|
|
18
|
+
return parts.map((part) => part[0].toUpperCase() + part.slice(1)).join(" ") || "Example";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function fillTemplate(template, mapping) {
|
|
22
|
+
let result = template;
|
|
23
|
+
for (const [key, value] of Object.entries(mapping)) {
|
|
24
|
+
result = result.replaceAll(key, value);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeFile(filePath, content) {
|
|
30
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
31
|
+
writeFileSync(filePath, content, "utf8");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ensureTargetDir(targetPath, force) {
|
|
35
|
+
if (existsSync(targetPath)) {
|
|
36
|
+
if (!lstatSync(targetPath).isDirectory()) {
|
|
37
|
+
throw new Error(`Output path exists and is not a directory: ${targetPath}`);
|
|
38
|
+
}
|
|
39
|
+
if (readdirSync(targetPath).length > 0 && !force) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Refusing to write into non-empty directory: ${targetPath}\nRe-run with --force to overwrite generated files.`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
mkdirSync(targetPath, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildPackageJson(appSlug) {
|
|
50
|
+
const packageJson = {
|
|
51
|
+
name: appSlug,
|
|
52
|
+
private: true,
|
|
53
|
+
type: "module",
|
|
54
|
+
scripts: {
|
|
55
|
+
dev: "tsx watch src/server.ts",
|
|
56
|
+
start: "tsx src/server.ts",
|
|
57
|
+
check: "tsc --noEmit",
|
|
58
|
+
},
|
|
59
|
+
dependencies: {
|
|
60
|
+
"@modelcontextprotocol/ext-apps": "^1.0.1",
|
|
61
|
+
"@modelcontextprotocol/sdk": "^1.20.2",
|
|
62
|
+
zod: "^3.25.76",
|
|
63
|
+
},
|
|
64
|
+
devDependencies: {
|
|
65
|
+
"@types/node": "^24.3.0",
|
|
66
|
+
tsx: "^4.19.4",
|
|
67
|
+
typescript: "^5.9.2",
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return `${JSON.stringify(packageJson, null, 2)}\n`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildTsconfig() {
|
|
75
|
+
return `{
|
|
76
|
+
"compilerOptions": {
|
|
77
|
+
"target": "ES2022",
|
|
78
|
+
"module": "NodeNext",
|
|
79
|
+
"moduleResolution": "NodeNext",
|
|
80
|
+
"strict": true,
|
|
81
|
+
"esModuleInterop": true,
|
|
82
|
+
"skipLibCheck": true,
|
|
83
|
+
"types": ["node"],
|
|
84
|
+
"outDir": "dist"
|
|
85
|
+
},
|
|
86
|
+
"include": ["src/**/*.ts"]
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const WIDGET_TEMPLATE = `<!DOCTYPE html>
|
|
92
|
+
<html lang="en">
|
|
93
|
+
<head>
|
|
94
|
+
<meta charset="utf-8" />
|
|
95
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
96
|
+
<title>__APP_TITLE__</title>
|
|
97
|
+
<style>
|
|
98
|
+
:root {
|
|
99
|
+
color: #0b0f19;
|
|
100
|
+
font-family: "Inter", system-ui, sans-serif;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
* {
|
|
104
|
+
box-sizing: border-box;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
body {
|
|
108
|
+
margin: 0;
|
|
109
|
+
min-height: 100vh;
|
|
110
|
+
padding: 16px;
|
|
111
|
+
background:
|
|
112
|
+
radial-gradient(circle at top right, #d8f3ff 0, transparent 40%),
|
|
113
|
+
linear-gradient(180deg, #f7fbff 0%, #edf3fb 100%);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
main {
|
|
117
|
+
width: 100%;
|
|
118
|
+
max-width: 420px;
|
|
119
|
+
margin: 0 auto;
|
|
120
|
+
padding: 20px;
|
|
121
|
+
border-radius: 18px;
|
|
122
|
+
background: rgba(255, 255, 255, 0.92);
|
|
123
|
+
border: 1px solid rgba(11, 15, 25, 0.08);
|
|
124
|
+
box-shadow: 0 14px 32px rgba(11, 15, 25, 0.08);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.eyebrow {
|
|
128
|
+
margin: 0 0 8px;
|
|
129
|
+
font-size: 12px;
|
|
130
|
+
font-weight: 700;
|
|
131
|
+
letter-spacing: 0.12em;
|
|
132
|
+
text-transform: uppercase;
|
|
133
|
+
color: #4f5d75;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
h1 {
|
|
137
|
+
margin: 0 0 10px;
|
|
138
|
+
font-size: 24px;
|
|
139
|
+
line-height: 1.15;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
p {
|
|
143
|
+
margin: 0;
|
|
144
|
+
line-height: 1.5;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.stack {
|
|
148
|
+
display: grid;
|
|
149
|
+
gap: 12px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
button {
|
|
153
|
+
border: 0;
|
|
154
|
+
border-radius: 999px;
|
|
155
|
+
padding: 10px 14px;
|
|
156
|
+
font: inherit;
|
|
157
|
+
font-weight: 600;
|
|
158
|
+
color: white;
|
|
159
|
+
background: #0f62fe;
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
button[hidden] {
|
|
164
|
+
display: none;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
button.secondary {
|
|
168
|
+
background: #0b0f19;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.meta {
|
|
172
|
+
padding: 12px;
|
|
173
|
+
border-radius: 14px;
|
|
174
|
+
background: #f5f8fc;
|
|
175
|
+
color: #4f5d75;
|
|
176
|
+
font-size: 13px;
|
|
177
|
+
}
|
|
178
|
+
</style>
|
|
179
|
+
</head>
|
|
180
|
+
<body>
|
|
181
|
+
<main class="stack">
|
|
182
|
+
<p class="eyebrow">__APP_TITLE__ starter</p>
|
|
183
|
+
<h1 id="headline">Waiting for tool output</h1>
|
|
184
|
+
<p id="message">Call the __TOOL_NAME__ tool to hydrate this widget.</p>
|
|
185
|
+
<button id="tool-button" type="button">Call __TOOL_NAME__ from the widget</button>
|
|
186
|
+
<button id="follow-up-button" class="secondary" type="button">
|
|
187
|
+
Ask the host to explain this app
|
|
188
|
+
</button>
|
|
189
|
+
<div class="meta" id="meta">
|
|
190
|
+
This widget uses the MCP Apps bridge by default.
|
|
191
|
+
</div>
|
|
192
|
+
</main>
|
|
193
|
+
|
|
194
|
+
<script type="module">
|
|
195
|
+
const headlineEl = document.querySelector("#headline");
|
|
196
|
+
const messageEl = document.querySelector("#message");
|
|
197
|
+
const metaEl = document.querySelector("#meta");
|
|
198
|
+
const toolButtonEl = document.querySelector("#tool-button");
|
|
199
|
+
const followUpButtonEl = document.querySelector("#follow-up-button");
|
|
200
|
+
|
|
201
|
+
let toolOutput = null;
|
|
202
|
+
let rpcId = 0;
|
|
203
|
+
const pendingRequests = new Map();
|
|
204
|
+
|
|
205
|
+
const render = () => {
|
|
206
|
+
const headline = toolOutput?.headline ?? "__APP_TITLE__";
|
|
207
|
+
const message =
|
|
208
|
+
toolOutput?.message ??
|
|
209
|
+
"Call the __TOOL_NAME__ tool to hydrate this widget.";
|
|
210
|
+
|
|
211
|
+
headlineEl.textContent = headline;
|
|
212
|
+
messageEl.textContent = message;
|
|
213
|
+
|
|
214
|
+
const theme = window.openai?.theme ?? "bridge-only";
|
|
215
|
+
metaEl.textContent =
|
|
216
|
+
"Runtime: " +
|
|
217
|
+
(window.openai ? "MCP Apps bridge + optional window.openai" : "MCP Apps bridge only") +
|
|
218
|
+
" | Theme: " +
|
|
219
|
+
theme;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const rpcNotify = (method, params) => {
|
|
223
|
+
window.parent.postMessage({ jsonrpc: "2.0", method, params }, "*");
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const rpcRequest = (method, params) =>
|
|
227
|
+
new Promise((resolve, reject) => {
|
|
228
|
+
const id = ++rpcId;
|
|
229
|
+
pendingRequests.set(id, { resolve, reject });
|
|
230
|
+
window.parent.postMessage({ jsonrpc: "2.0", id, method, params }, "*");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
window.addEventListener(
|
|
234
|
+
"message",
|
|
235
|
+
(event) => {
|
|
236
|
+
if (event.source !== window.parent) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const message = event.data;
|
|
241
|
+
if (!message || message.jsonrpc !== "2.0") {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (typeof message.id === "number") {
|
|
246
|
+
const pending = pendingRequests.get(message.id);
|
|
247
|
+
if (!pending) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
pendingRequests.delete(message.id);
|
|
252
|
+
if (message.error) {
|
|
253
|
+
pending.reject(message.error);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
pending.resolve(message.result);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (message.method === "ui/notifications/tool-result") {
|
|
262
|
+
toolOutput = message.params?.structuredContent ?? null;
|
|
263
|
+
render();
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
{ passive: true }
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const initializeBridge = async () => {
|
|
270
|
+
await rpcRequest("ui/initialize", {
|
|
271
|
+
appInfo: { name: "__APP_SLUG__-widget", version: "0.1.0" },
|
|
272
|
+
appCapabilities: {},
|
|
273
|
+
protocolVersion: "2026-01-26",
|
|
274
|
+
});
|
|
275
|
+
rpcNotify("ui/notifications/initialized", {});
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const bridgeReady = initializeBridge();
|
|
279
|
+
|
|
280
|
+
toolButtonEl.addEventListener("click", async () => {
|
|
281
|
+
await bridgeReady;
|
|
282
|
+
|
|
283
|
+
const response = await rpcRequest("tools/call", {
|
|
284
|
+
name: "__TOOL_NAME__",
|
|
285
|
+
arguments: {
|
|
286
|
+
message: "Tool call triggered from the widget.",
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
toolOutput = response?.structuredContent ?? toolOutput;
|
|
291
|
+
render();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
followUpButtonEl.addEventListener("click", async () => {
|
|
295
|
+
await bridgeReady;
|
|
296
|
+
|
|
297
|
+
rpcNotify("ui/message", {
|
|
298
|
+
role: "user",
|
|
299
|
+
content: [
|
|
300
|
+
{
|
|
301
|
+
type: "text",
|
|
302
|
+
text: "Explain how the __TOOL_NAME__ widget works.",
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
render();
|
|
309
|
+
</script>
|
|
310
|
+
</body>
|
|
311
|
+
</html>
|
|
312
|
+
`;
|
|
313
|
+
|
|
314
|
+
const SERVER_TEMPLATE = `import { createServer } from "node:http";
|
|
315
|
+
import { readFileSync } from "node:fs";
|
|
316
|
+
import path from "node:path";
|
|
317
|
+
import { fileURLToPath } from "node:url";
|
|
318
|
+
|
|
319
|
+
import {
|
|
320
|
+
registerAppResource,
|
|
321
|
+
registerAppTool,
|
|
322
|
+
RESOURCE_MIME_TYPE,
|
|
323
|
+
} from "@modelcontextprotocol/ext-apps/server";
|
|
324
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
325
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
326
|
+
import { z } from "zod";
|
|
327
|
+
|
|
328
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
329
|
+
const ROOT_DIR = path.resolve(__dirname, "..");
|
|
330
|
+
const WIDGET_URI = "__WIDGET_URI__";
|
|
331
|
+
const WIDGET_HTML = readFileSync(
|
|
332
|
+
path.join(ROOT_DIR, "public", "widget.html"),
|
|
333
|
+
"utf8"
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
function createAppServer(): McpServer {
|
|
337
|
+
const server = new McpServer({
|
|
338
|
+
name: "__APP_SLUG__",
|
|
339
|
+
version: "0.1.0",
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
registerAppResource(
|
|
343
|
+
server,
|
|
344
|
+
"main-widget",
|
|
345
|
+
WIDGET_URI,
|
|
346
|
+
{},
|
|
347
|
+
async () => ({
|
|
348
|
+
contents: [
|
|
349
|
+
{
|
|
350
|
+
uri: WIDGET_URI,
|
|
351
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
352
|
+
text: WIDGET_HTML,
|
|
353
|
+
_meta: {
|
|
354
|
+
ui: {
|
|
355
|
+
prefersBorder: true,
|
|
356
|
+
csp: {
|
|
357
|
+
connectDomains: [],
|
|
358
|
+
resourceDomains: [],
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
"openai/widgetDescription":
|
|
362
|
+
"__APP_TITLE__ starter widget rendered by the MCP server.",
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
})
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
registerAppTool(
|
|
370
|
+
server,
|
|
371
|
+
"__TOOL_NAME__",
|
|
372
|
+
{
|
|
373
|
+
title: "__APP_TITLE__",
|
|
374
|
+
description:
|
|
375
|
+
"Use this when the user wants to render the __APP_TITLE__ starter widget or inspect a minimal Apps SDK tool result.",
|
|
376
|
+
inputSchema: {
|
|
377
|
+
message: z
|
|
378
|
+
.string()
|
|
379
|
+
.optional()
|
|
380
|
+
.describe("Optional message to show inside the widget."),
|
|
381
|
+
},
|
|
382
|
+
annotations: {
|
|
383
|
+
readOnlyHint: true,
|
|
384
|
+
destructiveHint: false,
|
|
385
|
+
openWorldHint: false,
|
|
386
|
+
idempotentHint: true,
|
|
387
|
+
},
|
|
388
|
+
_meta: {
|
|
389
|
+
ui: { resourceUri: WIDGET_URI },
|
|
390
|
+
"openai/toolInvocation/invoking": "Loading __APP_TITLE__",
|
|
391
|
+
"openai/toolInvocation/invoked": "__APP_TITLE__ ready",
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
async ({ message }) => {
|
|
395
|
+
const resolvedMessage =
|
|
396
|
+
message?.trim() ||
|
|
397
|
+
"This starter uses the MCP Apps bridge first, keeps follow-up messaging on ui/message, and limits window.openai to optional host signals.";
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
content: [
|
|
401
|
+
{
|
|
402
|
+
type: "text" as const,
|
|
403
|
+
text: "Rendered the __APP_TITLE__ starter widget.",
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
structuredContent: {
|
|
407
|
+
headline: "__APP_TITLE__",
|
|
408
|
+
message: resolvedMessage,
|
|
409
|
+
source: "__TOOL_NAME__",
|
|
410
|
+
themeHint:
|
|
411
|
+
"Read window.openai.theme in the widget if you need ChatGPT theme information.",
|
|
412
|
+
},
|
|
413
|
+
_meta: {
|
|
414
|
+
"openai/outputTemplate": WIDGET_URI,
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
return server;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const port = Number(process.env.PORT ?? "__PORT__");
|
|
424
|
+
const MCP_PATH = "/mcp";
|
|
425
|
+
|
|
426
|
+
createServer(async (req, res) => {
|
|
427
|
+
if (!req.url) {
|
|
428
|
+
res.writeHead(400).end("Missing URL");
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const url = new URL(req.url, "http://" + (req.headers.host ?? "localhost"));
|
|
433
|
+
const isMcpRoute = url.pathname === MCP_PATH || url.pathname.startsWith(MCP_PATH + "/");
|
|
434
|
+
|
|
435
|
+
if (req.method === "OPTIONS" && isMcpRoute) {
|
|
436
|
+
res.writeHead(204, {
|
|
437
|
+
"Access-Control-Allow-Origin": "*",
|
|
438
|
+
"Access-Control-Allow-Methods": "POST, GET, DELETE, OPTIONS",
|
|
439
|
+
"Access-Control-Allow-Headers": "content-type, mcp-session-id",
|
|
440
|
+
"Access-Control-Expose-Headers": "Mcp-Session-Id",
|
|
441
|
+
});
|
|
442
|
+
res.end();
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (req.method === "GET" && url.pathname === "/") {
|
|
447
|
+
res.writeHead(200, { "content-type": "text/plain" }).end("__APP_TITLE__ MCP server");
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const transportMethods = new Set(["GET", "POST", "DELETE"]);
|
|
452
|
+
if (isMcpRoute && req.method && transportMethods.has(req.method)) {
|
|
453
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
454
|
+
res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
|
|
455
|
+
|
|
456
|
+
const server = createAppServer();
|
|
457
|
+
const transport = new StreamableHTTPServerTransport({
|
|
458
|
+
sessionIdGenerator: undefined,
|
|
459
|
+
enableJsonResponse: true,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
res.on("close", () => {
|
|
463
|
+
transport.close();
|
|
464
|
+
server.close();
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
try {
|
|
468
|
+
await server.connect(transport);
|
|
469
|
+
await transport.handleRequest(req, res);
|
|
470
|
+
} catch (error) {
|
|
471
|
+
console.error("Failed to handle MCP request:", error);
|
|
472
|
+
if (!res.headersSent) {
|
|
473
|
+
res.writeHead(500).end("Internal server error");
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
res.writeHead(404).end("Not Found");
|
|
480
|
+
}).listen(port, () => {
|
|
481
|
+
console.log("__APP_TITLE__ MCP server listening on http://localhost:" + port + MCP_PATH);
|
|
482
|
+
});
|
|
483
|
+
`;
|
|
484
|
+
|
|
485
|
+
function buildWidgetHtml(appSlug, appTitle, toolName) {
|
|
486
|
+
return fillTemplate(WIDGET_TEMPLATE, {
|
|
487
|
+
"__APP_SLUG__": appSlug,
|
|
488
|
+
"__APP_TITLE__": appTitle,
|
|
489
|
+
"__TOOL_NAME__": toolName,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function buildServerTs(appSlug, appTitle, toolName, widgetUri, port) {
|
|
494
|
+
return fillTemplate(SERVER_TEMPLATE, {
|
|
495
|
+
"__APP_SLUG__": appSlug,
|
|
496
|
+
"__APP_TITLE__": appTitle,
|
|
497
|
+
"__TOOL_NAME__": toolName,
|
|
498
|
+
"__WIDGET_URI__": widgetUri,
|
|
499
|
+
"__PORT__": String(port),
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function usage() {
|
|
504
|
+
return [
|
|
505
|
+
"Generate a minimal Node + @modelcontextprotocol/ext-apps starter with a vanilla widget that uses the MCP Apps bridge by default.",
|
|
506
|
+
"Prefer upstream examples first; use this scaffold as the fallback.",
|
|
507
|
+
"",
|
|
508
|
+
"Usage:",
|
|
509
|
+
" ./scripts/scaffold_node_ext_apps.mjs <output_dir> [--app-name <name>] [--tool-name <name>] [--port <number>] [--force]",
|
|
510
|
+
"",
|
|
511
|
+
"If the executable bit is unavailable, run:",
|
|
512
|
+
" node scripts/scaffold_node_ext_apps.mjs <output_dir> [--app-name <name>] [--tool-name <name>] [--port <number>] [--force]",
|
|
513
|
+
].join("\\n");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function parseArgs(argv) {
|
|
517
|
+
const args = {
|
|
518
|
+
outputDir: null,
|
|
519
|
+
appName: "example-chatgpt-app",
|
|
520
|
+
toolName: null,
|
|
521
|
+
port: 8787,
|
|
522
|
+
force: false,
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
const tokens = [...argv];
|
|
526
|
+
while (tokens.length > 0) {
|
|
527
|
+
const token = tokens.shift();
|
|
528
|
+
|
|
529
|
+
if (!args.outputDir && !token.startsWith("--")) {
|
|
530
|
+
args.outputDir = token;
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (token === "--app-name") {
|
|
535
|
+
args.appName = tokens.shift() ?? "";
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (token === "--tool-name") {
|
|
540
|
+
args.toolName = tokens.shift() ?? "";
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (token === "--port") {
|
|
545
|
+
const value = Number(tokens.shift());
|
|
546
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
547
|
+
throw new Error("Expected a positive integer after --port");
|
|
548
|
+
}
|
|
549
|
+
args.port = value;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (token === "--force") {
|
|
554
|
+
args.force = true;
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (token === "--help" || token === "-h") {
|
|
559
|
+
console.log(usage());
|
|
560
|
+
process.exit(0);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
throw new Error(`Unknown argument: ${token}`);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (!args.outputDir) {
|
|
567
|
+
throw new Error(`Missing required output directory.\\n\\n${usage()}`);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return args;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function main() {
|
|
574
|
+
const args = parseArgs(process.argv.slice(2));
|
|
575
|
+
|
|
576
|
+
const appSlug = toSlug(args.appName);
|
|
577
|
+
const toolName = toToolName(args.toolName || appSlug);
|
|
578
|
+
const appTitle = toTitle(appSlug);
|
|
579
|
+
const widgetUri = "ui://widget/main-v1.html";
|
|
580
|
+
|
|
581
|
+
const outputDir = path.resolve(args.outputDir);
|
|
582
|
+
ensureTargetDir(outputDir, args.force);
|
|
583
|
+
|
|
584
|
+
const files = new Map([
|
|
585
|
+
[path.join(outputDir, "package.json"), buildPackageJson(appSlug)],
|
|
586
|
+
[path.join(outputDir, "tsconfig.json"), buildTsconfig()],
|
|
587
|
+
[path.join(outputDir, "public", "widget.html"), buildWidgetHtml(appSlug, appTitle, toolName)],
|
|
588
|
+
[path.join(outputDir, "src", "server.ts"), buildServerTs(appSlug, appTitle, toolName, widgetUri, args.port)],
|
|
589
|
+
]);
|
|
590
|
+
|
|
591
|
+
for (const [filePath, content] of files) {
|
|
592
|
+
writeFile(filePath, content);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
console.log("Generated starter scaffold:");
|
|
596
|
+
for (const filePath of files.keys()) {
|
|
597
|
+
console.log(" -", path.relative(outputDir, filePath));
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
try {
|
|
602
|
+
main();
|
|
603
|
+
} catch (error) {
|
|
604
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
605
|
+
process.exit(1);
|
|
606
|
+
}
|