diff --git a/README.md b/README.md index a400e81c..11a1d897 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Each plugin lives in `plugins/`. The directory name is the install keyword | `typescript-lsp` | TypeScript language service `goto_definition` support. | | `weather-metrics` | Demo weather tool plus runtime metrics hooks. | | `web-search` | Exa-backed web search as a Cline tool. | +| `workos` | WorkOS identity platform skills for AuthKit, SSO, Directory Sync, RBAC, widgets, and migrations. | ## Install From Source diff --git a/plugins/workos/NOTICE.workos-skills b/plugins/workos/NOTICE.workos-skills new file mode 100644 index 00000000..cce41333 --- /dev/null +++ b/plugins/workos/NOTICE.workos-skills @@ -0,0 +1,47 @@ +WorkOS skills notice + +This plugin bundles WorkOS skill material from WorkOS plugin metadata that declares: + +- Author: WorkOS +- Homepage: https://workos.com +- Repository: https://github.com/workos/skills +- License: MIT + +MIT license text for bundled WorkOS skill material: + +Copyright (c) WorkOS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE 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, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +The bundled `skills/workos-widgets/references/scripts/query-spec.cjs` helper +contains code from `yaml`, licensed under ISC: + +Copyright Eemeli Aro + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/plugins/workos/README.md b/plugins/workos/README.md new file mode 100644 index 00000000..08404538 --- /dev/null +++ b/plugins/workos/README.md @@ -0,0 +1,21 @@ +# WorkOS + +WorkOS adds Cline skills for implementing and debugging enterprise identity workflows. + +## Cline Primitives + +- Skills: `workos` routes WorkOS AuthKit, SSO/SAML, Directory Sync, RBAC, FGA, MFA, Vault, Audit Logs, Admin Portal, Pipes, Feature Flags, Radar, Events, Custom Domains, API references, CLI lifecycle, and migration requests to bundled WorkOS references. +- Skills: `workos-widgets` helps integrate WorkOS widgets such as User Management, User Profile, Admin Portal SSO Connection, and Admin Portal Domain Verification across common frontend and backend stacks. +- The bundled skills protect secrets and production identity resources, require verified CLI/dashboard guidance, and gate mutating WorkOS operations behind explicit user confirmation. + +## Requirements + +- WorkOS account access and the relevant WorkOS environment variables for live implementation work, commonly `WORKOS_API_KEY`, `WORKOS_CLIENT_ID`, cookie/session secrets, webhook secrets, and widget token material. +- The WorkOS CLI when the user asks Cline to run WorkOS setup or management commands. Cline should run it with `WORKOS_MODE=agent`, and with `--json` when command output is parsed. +- Network access when the task requires current WorkOS documentation, CLI installation, or live WorkOS API calls. + +## Trust Boundaries + +The plugin does not register an MCP server, start background services, install dependencies, or call WorkOS at install time. It only contributes bundled skills. Live WorkOS changes remain user-approved runtime actions. + +Bundled WorkOS skill material is from WorkOS plugin metadata declaring MIT licensing. See `NOTICE.workos-skills`. diff --git a/plugins/workos/index.ts b/plugins/workos/index.ts new file mode 100644 index 00000000..82890106 --- /dev/null +++ b/plugins/workos/index.ts @@ -0,0 +1,10 @@ +import type { AgentPlugin } from "@cline/sdk" + +const plugin: AgentPlugin = { + name: "workos", + manifest: { + capabilities: ["skills"], + }, +} + +export default plugin diff --git a/plugins/workos/package.json b/plugins/workos/package.json new file mode 100644 index 00000000..e9f57f74 --- /dev/null +++ b/plugins/workos/package.json @@ -0,0 +1,30 @@ +{ + "name": "workos", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "WorkOS skills for AuthKit, SSO, Directory Sync, RBAC, Widgets, migrations, and identity platform operations.", + "exports": { + ".": "./index.ts" + }, + "cline": { + "plugins": [ + { + "paths": [ + "./index.ts" + ], + "capabilities": [ + "skills" + ] + } + ] + }, + "peerDependencies": { + "@cline/sdk": "*" + }, + "peerDependenciesMeta": { + "@cline/sdk": { + "optional": true + } + } +} diff --git a/plugins/workos/skills/workos-widgets/SKILL.md b/plugins/workos/skills/workos-widgets/SKILL.md new file mode 100644 index 00000000..3544b9a4 --- /dev/null +++ b/plugins/workos/skills/workos-widgets/SKILL.md @@ -0,0 +1,136 @@ +--- +name: workos-widgets +description: Use when the user is implementing, embedding, or debugging a WorkOS Widget -- specifically the User Management, User Profile, Admin Portal SSO Connection, or Admin Portal Domain Verification widgets. Handles the full stack -- detecting the frontend (Next.js, React, React Router, TanStack Start, Vite, SvelteKit), generating access tokens via the backend SDK in use (Node, Python, Go, Ruby, PHP, Java, .NET), and wiring up the widget component correctly per the bundled OpenAPI spec. Also use when code imports from @workos-inc/widgets or the user pastes or JSX. +--- + +# WorkOS Widgets + +## Cline Compatibility + +- Use Cline's available file-reading tools for bundled references. +- Install dependencies or run `WORKOS_MODE=agent npx workos@latest install` only after explicit user approval. This command can mutate the user's project. +- If AuthKit or WorkOS is missing and the user does not want Cline to run the installer, offer a manual integration path using the relevant bundled framework reference. +- Treat widget access tokens, WorkOS API keys, cookies, session secrets, and organization/user data as sensitive. +- Prefer existing project conventions over broad rewrites, and ask one focused question only when repository inspection cannot resolve a necessary implementation choice. +- When running `query-spec.cjs`, use the absolute path to this plugin's bundled `references/scripts/query-spec.cjs` file, or `cd` into the `workos-widgets` skill directory first. + +## Workflow Overview + +1. Identify widget target from the user request (`user-management`, `user-profile`, `admin-portal-sso-connection`, `admin-portal-domain-verification`). +2. Scan project files in this order: + - package/dependency manifests + - framework/router entrypoints + - auth/token utilities + - styling/component patterns +3. Detect stack, data-layer style, styling, component system, and package manager using [references/detection.md](references/detection.md). +4. Check for AuthKit/WorkOS presence: + - if detected, continue; + - if not detected, ask for approval before running `WORKOS_MODE=agent npx workos@latest install`, or offer to wire the widget manually from the relevant framework reference. +5. If detection is ambiguous or conflicting, ask one focused question, then continue. +6. Load only the relevant reference files for the detected stack and widget. +7. Implement integration based on stack shape: + - frontend route/page + widget component when widget UI lives in the same app + - token endpoint/service + client integration surface when backend-first/multi-app architecture is detected +8. Validate routing/wiring, imports, and token/API usage before finishing. + +## Canonical Inputs + +Accept these inputs from the user request when available: + +- widget type (or infer from request intent) +- optional component path +- optional page/route path +- optional token endpoint/service preference +- optional constraints (for example: avoid broad refactors) + +When input is missing, infer from existing project conventions and detected stack. + +## Detection and Ambiguity Protocol + +- Apply detection heuristics from [references/detection.md](references/detection.md). +- Explore before asking. Ask only when ambiguity remains after checking manifests and route/auth entrypoints. +- Ask a single concrete question that resolves one decision. +- Default to the strongest detected ownership signals when no user response is available. +- When installs are required, use the package manager detected from project files/lockfiles. + +## Reference Loading Map + +Always load these core references: + +- [references/detection.md](references/detection.md) +- [references/token-strategies.md](references/token-strategies.md) +- [references/fetching-apis.md](references/fetching-apis.md) +- [references/styling-and-components.md](references/styling-and-components.md) + +For React/TypeScript stacks (Next.js, React Router, TanStack Router, TanStack Start, Vite), also load: + +- [references/react-ts-standards.md](references/react-ts-standards.md) + +Load stack-specific reference guidance: + +- Next.js: [references/framework-nextjs.md](references/framework-nextjs.md) +- React Router: [references/framework-react-router.md](references/framework-react-router.md) +- TanStack Router: [references/framework-tanstack-router.md](references/framework-tanstack-router.md) +- TanStack Start: [references/framework-tanstack-start.md](references/framework-tanstack-start.md) +- Vite: [references/framework-vite.md](references/framework-vite.md) +- SvelteKit: [references/framework-sveltekit.md](references/framework-sveltekit.md) +- Ruby: [references/framework-ruby.md](references/framework-ruby.md) +- Python: [references/framework-python.md](references/framework-python.md) +- Go: [references/framework-go.md](references/framework-go.md) +- PHP: [references/framework-php.md](references/framework-php.md) +- Java: [references/framework-java.md](references/framework-java.md) +- Mixed repositories: [references/framework-mixed-repositories.md](references/framework-mixed-repositories.md) + +Then load exactly one widget reference: + +- User Management: [references/widget-user-management.md](references/widget-user-management.md) +- User Profile: [references/widget-user-profile.md](references/widget-user-profile.md) +- Admin Portal SSO Connection: [references/widget-admin-portal-sso-connection.md](references/widget-admin-portal-sso-connection.md) +- Admin Portal Domain Verification: [references/widget-admin-portal-domain-verification.md](references/widget-admin-portal-domain-verification.md) + +## Global Widget Guidance + +- Implement widget operations using endpoint paths/methods from [references/fetching-apis.md](references/fetching-apis.md). When building request bodies or parsing responses, query the OpenAPI spec for the relevant widget's schemas: + ```bash + node references/scripts/query-spec.cjs --widget + ``` + Use `--list` to see available widget groups. +- Keep loading, empty, and error states explicit and user-visible. +- Keep mutation outcomes visible and refresh/reload affected data after successful changes. +- Align table/list/action UI with existing project conventions. +- Keep behavior resilient for partial/optional data and avoid brittle UI assumptions. + +## Core Guidelines + +- Reuse existing domain types from the host project and OpenAPI schemas; avoid duplicating model definitions. +- Build widget requests using [references/fetching-apis.md](references/fetching-apis.md) for paths, methods, and schema queries. +- Use direct `fetch`/HTTP calls (or equivalent server HTTP client) for endpoint calls. +- Implement a consistent authorization layer for widget requests, including elevated-token handling for sensitive endpoints when required. +- If the app already uses React Query or SWR, use them as orchestration/cache layers around those direct calls. +- For React/TypeScript widget code quality expectations, follow [references/react-ts-standards.md](references/react-ts-standards.md). +- If AuthKit/WorkOS is missing, ask for approval before running `WORKOS_MODE=agent npx workos@latest install`, or offer to wire the widget manually from the relevant framework reference. `WORKOS_MODE=agent` keeps the installer deterministic (no prompts, no browser, no host-trust); pass `--json` when you need to parse the output. +- Install additional dependencies only when strictly necessary, using the detected package manager/tooling. +- Keep server-state handling aligned with the selected data-layer approach. +- Use local state/reducers for UI interaction state as needed. +- Prefer existing design system and styling conventions. +- Avoid broad unrelated refactors and global style rewrites. + +## Completion Requirements + +Before finishing, verify all relevant items: + +1. Widget component exists and accepts `accessToken: string` when component-level integration is in scope. +2. Route/page wiring is complete when route integration is in scope. +3. Token source matches existing app architecture (AuthKit client flow or backend WorkOS token flow). +4. API methods and paths match the bundled OpenAPI spec, and data-layer usage matches project conventions. +5. Loading and error branches exist for required query/mutation flows. + +## Validation Checklist + +1. Confirm endpoint paths and HTTP methods come from the bundled OpenAPI spec. +2. Confirm request/response handling follows schema expectations from the spec. +3. Confirm query/mutation invalidation/refetch is applied after successful mutations where required. +4. Confirm empty/error/loading states are explicit and user-visible. +5. Confirm package installs (if any) used the detected package manager/tooling. +6. Confirm implementation stays aligned with existing codebase conventions. +7. Confirm no existing component has been passed `className` or `style` props to override its built-in styling. Use each component as-is or via its own props API (`variant`, `size`, etc.). diff --git a/plugins/workos/skills/workos-widgets/references/detection.md b/plugins/workos/skills/workos-widgets/references/detection.md new file mode 100644 index 00000000..7a6de2d5 --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/detection.md @@ -0,0 +1,89 @@ +# Detection + +## Objective + +Identify the active stack and integration surface using repository signals, then choose an approach that fits existing architecture. + +## Suggested Scan Order + +1. dependency manifests (`package.json`, `Gemfile`, `composer.json`, `pyproject.toml`, `requirements*.txt`, `go.mod`, `pom.xml`, `build.gradle*`) +2. framework/router entrypoints +3. auth and token utilities +4. styling and component patterns +5. package manager and lockfiles + +## Common Stack Signals + +### JavaScript/TypeScript + +- Next.js: `next` +- TanStack Start: `@tanstack/react-start` +- TanStack Router: `@tanstack/react-router` +- React Router: `react-router` or `react-router-dom` +- Vite: `vite` or `vite.config.*` +- SvelteKit: `@sveltejs/kit` or `svelte.config.*` + +### Other Stacks + +- Ruby: `Gemfile` and WorkOS/AuthKit gems +- PHP: `composer.json` and WorkOS/AuthKit packages +- Python: `pyproject.toml` or `requirements*.txt` with WorkOS/AuthKit packages +- Go: `go.mod` with `github.com/workos/workos-go` +- Java: `pom.xml` or `build.gradle*` with WorkOS dependencies/imports + +## AuthKit/WorkOS Presence Signals + +Look for any existing AuthKit/WorkOS usage before implementing widgets. + +- JavaScript/TypeScript: `@workos-inc/*`, `@workos/*`, or WorkOS/AuthKit imports in app/server code +- Ruby: WorkOS/AuthKit gems or initialization code +- PHP: WorkOS/AuthKit composer packages or bootstrap usage +- Python: WorkOS/AuthKit packages/imports and config usage +- Go: WorkOS Go module imports/config +- Java: WorkOS Java dependency/imports/config + +If no AuthKit/WorkOS signal is found, see SKILL.md step 4. + +## Detection Heuristics + +- Prefer entrypoint ownership over dependency names alone. +- In mixed repositories, identify which app owns UI rendering and which service owns token generation. +- Use the strongest cluster of signals, then validate by checking real route/auth files. + +## Data-Layer Signals + +- React Query signal: `@tanstack/react-query` +- SWR signal: `swr` +- No query library signal: use the project's native async/data approach with direct fetch/http calls. + +When React Query or SWR is already established, keep using it for caching/invalidation and wrap direct endpoint calls with it. + +## Package Manager/Tool Detection + +Use the project's existing package manager/tooling when installing missing dependencies. + +- JavaScript/TypeScript: + - `pnpm-lock.yaml` -> `pnpm` + - `yarn.lock` -> `yarn` + - `bun.lockb` or `bun.lock` -> `bun` + - `package-lock.json` -> `npm` + - if unclear, use existing install scripts or ask once +- Ruby: use `bundle`/Bundler with `Gemfile` +- PHP: use `composer` with `composer.json` +- Python: follow existing tooling (`poetry.lock`, `uv.lock`, `requirements*.txt`, or existing project scripts) +- Go: use Go modules tooling (`go mod` / existing project scripts) +- Java: follow project tooling (`mvn`/Maven or `gradle`/Gradle wrappers) + +Install dependencies only when strictly necessary for the selected integration approach. + +## Ambiguity Handling + +- If multiple stacks/frameworks look active, ask one focused question. +- If ownership is still unclear, choose the least invasive path and note assumptions. + +## Focused Question Examples + +- "I found multiple routers. Which one currently owns app routes?" +- "I found backend and frontend apps. Which app should host the widget UI?" +- "I found multiple data-fetching patterns. Which one should new widget code follow?" +- "I found multiple services that could issue widget tokens. Which one should own token generation?" diff --git a/plugins/workos/skills/workos-widgets/references/fetching-apis.md b/plugins/workos/skills/workos-widgets/references/fetching-apis.md new file mode 100644 index 00000000..2963ee3a --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/fetching-apis.md @@ -0,0 +1,129 @@ +# Fetching APIs + +## Objective + +Implement Widgets API calls using the endpoint tables and query script below, matching the host application's data layer. + +## Source of Truth + +Use the endpoint tables below for paths and methods. For request/response schemas, run: + +```bash +node references/scripts/query-spec.cjs --widget +``` + +## Guidance + +- Build direct fetch/http client functions from the OpenAPI endpoints. +- Keep request and mutation handling consistent with existing code style. +- If React Query or SWR already exists, use it for query/mutation orchestration on top of the direct endpoint functions. +- Prefer one consistent data pattern per widget flow unless the project already mixes patterns. +- Reuse existing error/loading conventions from the host project. + +## Base URL + +Use `process.env.WORKOS_BASE_API_URL` (or the equivalent env access for the stack) as the base URL for all widget API calls. Fall back to `https://api.workos.com` when the env variable is not set. + +## Authorization Layer + +- Add a small shared request layer that injects authorization consistently for all widget calls. +- Send the widget bearer token in the app's standard authenticated request path. +- Keep authorization wiring close to existing auth/session utilities instead of duplicating token logic across components. +- Handle `401`/`403` responses explicitly and surface clear recovery actions. + +## Error Responses + +All error responses (`400`, `403`, `404`, `422`) return a JSON object with a single `message` string field: + +```json +{ "message": "Description of the error" } +``` + +For full request/response schemas, run `node references/scripts/query-spec.cjs --widget `. + +## Elevated Access Endpoints + +- Check the endpoint's description (via `node references/scripts/query-spec.cjs --widget `) before calling it -- not on failure. If it mentions elevated access, acquire the elevated token first. +- Use `POST /_widgets/UserProfile/verify` to obtain an elevated token, then pass it in header `x-elevated-access-token`. +- Treat elevated tokens as short-lived (10 minutes) and scope them to sensitive operations only. + +## Endpoint Reference + +All available endpoints, grouped by widget. For request/response schemas, run `node references/scripts/query-spec.cjs --widget `. + +### User Management + +| Method | Path | +| -------- | -------------------------------------------------- | +| `GET` | `/_widgets/UserManagement/members` | +| `POST` | `/_widgets/UserManagement/members/{userId}` | +| `DELETE` | `/_widgets/UserManagement/members/{userId}` | +| `GET` | `/_widgets/UserManagement/roles` | +| `GET` | `/_widgets/UserManagement/roles-and-config` | +| `GET` | `/_widgets/UserManagement/organizations` | +| `POST` | `/_widgets/UserManagement/invite-user` | +| `POST` | `/_widgets/UserManagement/invites/{userId}/resend` | +| `DELETE` | `/_widgets/UserManagement/invites/{userId}` | + +### User Profile + +| Method | Path | +| -------- | -------------------------------------------------------- | +| `GET` | `/_widgets/UserProfile/me` | +| `POST` | `/_widgets/UserProfile/me` | +| `GET` | `/_widgets/UserProfile/authentication-information` | +| `POST` | `/_widgets/UserProfile/send-verification` | +| `POST` | `/_widgets/UserProfile/verify` | +| `POST` | `/_widgets/UserProfile/update-password` | +| `POST` | `/_widgets/UserProfile/create-password` ⚠️ elevated | +| `POST` | `/_widgets/UserProfile/create-totp-factor` ⚠️ elevated | +| `POST` | `/_widgets/UserProfile/verify-totp-factor` ⚠️ elevated | +| `DELETE` | `/_widgets/UserProfile/totp-factors` ⚠️ elevated | +| `POST` | `/_widgets/UserProfile/passkeys` ⚠️ elevated | +| `POST` | `/_widgets/UserProfile/passkeys/verify` ⚠️ elevated | +| `DELETE` | `/_widgets/UserProfile/passkeys/{passkeyId}` ⚠️ elevated | +| `GET` | `/_widgets/UserProfile/sessions` | +| `DELETE` | `/_widgets/UserProfile/sessions/revoke/{sessionId}` | +| `DELETE` | `/_widgets/UserProfile/sessions/revoke-all` | + +### Admin Portal -- SSO Connection + +| Method | Path | +| ------ | ---------------------------------------- | +| `GET` | `/_widgets/admin-portal/sso-connections` | +| `POST` | `/_widgets/admin-portal/generate-link` | + +### Admin Portal -- Domain Verification + +| Method | Path | +| -------- | ----------------------------------------------------------------- | +| `GET` | `/_widgets/admin-portal/organization-domains` | +| `DELETE` | `/_widgets/admin-portal/organization-domains/{domainId}` | +| `POST` | `/_widgets/admin-portal/organization-domains/{domainId}/reverify` | +| `POST` | `/_widgets/admin-portal/generate-link` | + +### Other + +| Method | Path | +| -------- | ----------------------------------------------------------------------------- | +| `GET` | `/_widgets/settings` | +| `POST` | `/_widgets/ApiKeys/organization-api-keys` | +| `GET` | `/_widgets/ApiKeys/organization-api-keys` | +| `GET` | `/_widgets/ApiKeys/permissions` | +| `DELETE` | `/_widgets/ApiKeys/{apiKeyId}` | +| `GET` | `/_widgets/DataIntegrations/mine` | +| `GET` | `/_widgets/DataIntegrations/{slug}/authorize` | +| `GET` | `/_widgets/DataIntegrations/{dataIntegrationId}/authorization-status/{state}` | +| `DELETE` | `/_widgets/DataIntegrations/installations/{installationId}` | +| `GET` | `/_widgets/directory-sync/directories` | +| `GET` | `/_widgets/directory-sync/directories/{directoryId}` | + +## Pagination + +List endpoints use cursor-based pagination. Query parameters: + +- `limit` -- number of results per page +- `before` -- cursor for the previous page +- `after` -- cursor for the next page + +Responses include a `list_metadata` object with `before` and `after` cursor strings. Pass `after` from the current response as the `after` param of the next request to advance pages. diff --git a/plugins/workos/skills/workos-widgets/references/framework-go.md b/plugins/workos/skills/workos-widgets/references/framework-go.md new file mode 100644 index 00000000..53882e91 --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/framework-go.md @@ -0,0 +1,34 @@ +# Framework: Go + +## Scope + +Use this guide for Go services that issue widget tokens and support widget API integration. + +## Guidance + +- Use the official WorkOS Go SDK. +- Keep API key in environment configuration. +- Place token generation in existing handler/service layers. +- Reuse existing auth/session middleware to derive organization/user identifiers. + +## Token Pattern + +```go +import ( + "context" + "os" + + "github.com/workos/workos-go/v4/pkg/widgets" +) + +widgets.SetAPIKey(os.Getenv("WORKOS_API_KEY")) + +token, err := widgets.GetToken( + context.Background(), + widgets.GetTokenOpts{ + OrganizationID: organizationID, + UserID: userID, + Scopes: []widgets.WidgetScope{widgets.UsersTableManage}, + }, +) +``` diff --git a/plugins/workos/skills/workos-widgets/references/framework-java.md b/plugins/workos/skills/workos-widgets/references/framework-java.md new file mode 100644 index 00000000..31ac0df2 --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/framework-java.md @@ -0,0 +1,32 @@ +# Framework: Java + +## Scope + +Use this guide for Java services/apps that issue widget tokens and support widget integrations. + +## Guidance + +- Use the official WorkOS Java SDK. +- Keep API key in environment configuration. +- Place token creation in existing service/controller boundaries. +- Reuse existing auth/session context for organization and user identifiers. + +## Token Pattern + +```java +import com.workos.WorkOS; +import com.workos.widgets.WidgetsApi.GetTokenOptions; +import com.workos.widgets.models.WidgetScope; +import com.workos.widgets.models.WidgetTokenResponse; + +WorkOS workos = new WorkOS(System.getenv("WORKOS_API_KEY")); + +GetTokenOptions options = GetTokenOptions.builder() + .organizationID(organizationId) + .userID(userId) + .scopes(Arrays.asList(WidgetScope.WidgetsUsersTableManage)) + .build(); + +WidgetTokenResponse response = workos.widgets.getToken(options); +String token = response.token; +``` diff --git a/plugins/workos/skills/workos-widgets/references/framework-mixed-repositories.md b/plugins/workos/skills/workos-widgets/references/framework-mixed-repositories.md new file mode 100644 index 00000000..6aee5884 --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/framework-mixed-repositories.md @@ -0,0 +1,13 @@ +# Framework: Mixed Repositories + +## Objective + +Handle repositories with multiple apps/services by integrating widgets at existing boundaries. + +## Guidance + +- Detect which app owns widget UI rendering. +- Detect which service owns authenticated token generation. +- Keep each side in its native conventions and integrate through existing API boundaries. +- Avoid broad architecture moves when additive wiring is enough. +- If unsure, prompt the user. diff --git a/plugins/workos/skills/workos-widgets/references/framework-nextjs.md b/plugins/workos/skills/workos-widgets/references/framework-nextjs.md new file mode 100644 index 00000000..f177c085 --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/framework-nextjs.md @@ -0,0 +1,13 @@ +# Framework: Next.js + +## Guidance + +- Detect whether the project uses App Router or Pages Router, then follow that structure. +- Place widget routes/pages where existing route modules live. +- Keep token acquisition in the same server/client boundary already used by the app. +- For JS/TS token strategy details (AuthKit token vs backend `getToken` with scopes), follow [token-strategies.md](token-strategies.md). +- Integrate widget components through existing layout and provider patterns. + +## Server Token Pattern (JS/TS) + +For the token code pattern, see [token-strategies.md](token-strategies.md) → JS/TS Authorization Tokens. Token generation belongs in a Next.js server boundary (Server Component, Route Handler, or `getServerSideProps`). diff --git a/plugins/workos/skills/workos-widgets/references/framework-php.md b/plugins/workos/skills/workos-widgets/references/framework-php.md new file mode 100644 index 00000000..6304f53b --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/framework-php.md @@ -0,0 +1,29 @@ +# Framework: PHP + +## Scope + +Use this guide for PHP apps (for example Laravel/Symfony) that create widget tokens and integrate widget APIs. + +## Guidance + +- Use the official WorkOS PHP SDK. +- Keep API key in environment configuration. +- Place token generation in existing controller/service boundaries. +- Reuse current auth/session context to resolve organization/user identifiers. + +## Token Pattern + +```php +getToken( + organization_id: $organizationId, + user_id: $userId, + scopes: [WidgetScope::UsersTableManage] +); +``` diff --git a/plugins/workos/skills/workos-widgets/references/framework-python.md b/plugins/workos/skills/workos-widgets/references/framework-python.md new file mode 100644 index 00000000..2df73e8c --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/framework-python.md @@ -0,0 +1,29 @@ +# Framework: Python + +## Scope + +Use this guide for Python apps (for example Django/Flask/FastAPI) that generate widget tokens and/or broker widget API requests. + +## Guidance + +- Use the official WorkOS Python SDK. +- Keep API key and client id in environment configuration. +- Place token generation in existing service/view/router boundaries. +- Reuse established auth/session context for `organization_id` and `user_id`. + +## Token Pattern + +```py +from workos import WorkOSClient + +workos_client = WorkOSClient( + api_key=os.environ["WORKOS_API_KEY"], + client_id=os.environ["WORKOS_CLIENT_ID"], +) + +token_response = workos_client.widgets.get_token( + organization_id=organization_id, + user_id=user_id, + scopes=["widgets:users-table:manage"], +) +``` diff --git a/plugins/workos/skills/workos-widgets/references/framework-react-router.md b/plugins/workos/skills/workos-widgets/references/framework-react-router.md new file mode 100644 index 00000000..7cfe1658 --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/framework-react-router.md @@ -0,0 +1,13 @@ +# Framework: React Router + +## Guidance + +- Follow the repository's route definition style (file-based or config-based). +- Add widget routes/components in the same structure used by existing features. +- Reuse existing loader/action or component-level token patterns. +- For JS/TS token strategy details (AuthKit token vs backend `getToken` with scopes), follow [token-strategies.md](token-strategies.md). +- Preserve current router/provider setup and conventions. + +## Server Token Pattern (JS/TS) + +For the token code pattern, see [token-strategies.md](token-strategies.md) → JS/TS Authorization Tokens. Token generation belongs in a loader, action, or dedicated server route. diff --git a/plugins/workos/skills/workos-widgets/references/framework-ruby.md b/plugins/workos/skills/workos-widgets/references/framework-ruby.md new file mode 100644 index 00000000..4e55e8c9 --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/framework-ruby.md @@ -0,0 +1,28 @@ +# Framework: Ruby + +## Scope + +Use this guide for Ruby apps (for example Rails/Sinatra) that generate widget tokens and/or proxy widget API calls. + +## Guidance + +- Use the official WorkOS Ruby SDK. +- Keep API key in environment configuration. +- Place token generation in existing service/controller boundaries. +- Reuse existing session/auth context to resolve `organization_id` and `user_id`. + +## Token Pattern + +```rb +require "workos" + +WorkOS.configure do |config| + config.key = ENV.fetch("WORKOS_API_KEY") +end + +token = WorkOS::Widgets.get_token( + organization_id: organization_id, + user_id: user_id, + scopes: ["widgets:users-table:manage"] +) +``` diff --git a/plugins/workos/skills/workos-widgets/references/framework-sveltekit.md b/plugins/workos/skills/workos-widgets/references/framework-sveltekit.md new file mode 100644 index 00000000..85f178ad --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/framework-sveltekit.md @@ -0,0 +1,13 @@ +# Framework: SvelteKit + +## Guidance + +- Follow existing `+page`, `+layout`, and `+server`/`+page.server` conventions. +- Keep token generation in server/load boundaries that already handle auth/session context. +- For JS/TS token strategy details (AuthKit token vs backend `getToken` with scopes), follow [token-strategies.md](token-strategies.md). +- Keep frontend data calls aligned with current SvelteKit patterns. +- Never embed a widget directly in a `+page.svelte`. Always extract it into its own `.svelte` component file. The page imports and renders that component. + +## Server Token Pattern (JS/TS) + +For the token code pattern, see [token-strategies.md](token-strategies.md) → JS/TS Authorization Tokens. Token generation belongs in a `+page.server.ts`, `+layout.server.ts`, or `+server.ts` boundary. diff --git a/plugins/workos/skills/workos-widgets/references/framework-tanstack-router.md b/plugins/workos/skills/workos-widgets/references/framework-tanstack-router.md new file mode 100644 index 00000000..1055fed0 --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/framework-tanstack-router.md @@ -0,0 +1,13 @@ +# Framework: TanStack Router + +## Guidance + +- Follow current route module conventions and route tree workflow. +- Place widget route files where the router expects them. +- Keep token retrieval aligned with existing loader/client boundaries. +- For JS/TS token strategy details (AuthKit token vs backend `getToken` with scopes), follow [token-strategies.md](token-strategies.md). +- Reuse existing typing and routing patterns from the project. + +## Server Token Pattern (JS/TS) + +For the token code pattern, see [token-strategies.md](token-strategies.md) → JS/TS Authorization Tokens. Token generation belongs in the route's loader or a dedicated server handler. diff --git a/plugins/workos/skills/workos-widgets/references/framework-tanstack-start.md b/plugins/workos/skills/workos-widgets/references/framework-tanstack-start.md new file mode 100644 index 00000000..59659e80 --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/framework-tanstack-start.md @@ -0,0 +1,13 @@ +# Framework: TanStack Start + +## Guidance + +- Follow established Start route/file conventions. +- Keep server/client boundaries consistent with existing auth and data flows. +- Add widget integration with minimal structural changes. +- For JS/TS token strategy details (AuthKit token vs backend `getToken` with scopes), follow [token-strategies.md](token-strategies.md). +- Reuse current route and module organization patterns. + +## Server Token Pattern (JS/TS) + +For the token code pattern, see [token-strategies.md](token-strategies.md) → JS/TS Authorization Tokens. Token generation belongs in a Start server function or server boundary. diff --git a/plugins/workos/skills/workos-widgets/references/framework-vite.md b/plugins/workos/skills/workos-widgets/references/framework-vite.md new file mode 100644 index 00000000..3454a47a --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/framework-vite.md @@ -0,0 +1,13 @@ +# Framework: Vite + +## Guidance + +- Detect whether routing is framework-based or custom, then integrate accordingly. +- Place widget pages/components in existing feature/page structure. +- Reuse existing token/auth utilities rather than introducing new architecture. +- For JS/TS token strategy details (AuthKit token vs backend `getToken` with scopes), follow [token-strategies.md](token-strategies.md). +- Keep integration small and aligned with current app layout. + +## Server Token Pattern (JS/TS) + +For the token code pattern, see [token-strategies.md](token-strategies.md) → JS/TS Authorization Tokens. In a Vite app, token generation typically lives in an existing backend service or API route rather than the Vite dev server. diff --git a/plugins/workos/skills/workos-widgets/references/react-ts-standards.md b/plugins/workos/skills/workos-widgets/references/react-ts-standards.md new file mode 100644 index 00000000..6551e96c --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/react-ts-standards.md @@ -0,0 +1,42 @@ +# React/TypeScript Standards + +## Objective + +Keep React + TypeScript widget code predictable, type-safe, and easy to maintain. + +## TypeScript Rules + +- Prefer inference and explicit interface/type definitions over type assertions. +- Avoid `as` casts unless narrowing cannot be expressed safely another way. +- Avoid `any`; use concrete types or `unknown` with safe narrowing. +- Keep API response typing close to request functions and reuse shared domain types. + +## React State and Hooks Rules + +- Keep `useEffect` minimal and focused on true side effects. +- Do not use `useEffect` for derivable render data. +- Avoid `setState` inside `useEffect` unless syncing from external systems or subscriptions. +- Prefer deriving values from props/query state with memoization only when needed. +- Keep a single source of truth for server state (query/cache layer or explicit request state). +- Use local state for transient UI interactions only. + +## Component Design Rules + +- Keep components small and composable; extract repeated logic into hooks/utilities. +- Keep event handlers explicit and colocated with relevant UI. +- Avoid deep prop drilling when existing context/provider patterns already exist. +- Use clear loading/error/empty branches instead of implicit fallthrough behavior. +- Never embed a widget directly in a page. Always extract it into its own component file. The page imports and renders that component. + +## Async and Mutation Rules + +- Keep async request functions separate from view rendering logic. +- Prevent duplicate submits/actions while mutations are pending. +- Reflect mutation success/failure in UI state clearly. +- Refresh or invalidate affected data after successful mutations. + +## General Code Quality + +- Follow existing lint/format conventions in the host project. +- Prefer readable code over clever abstractions. +- Keep changes scoped to widget integration; avoid unrelated refactors. diff --git a/plugins/workos/skills/workos-widgets/references/scripts/query-spec.cjs b/plugins/workos/skills/workos-widgets/references/scripts/query-spec.cjs new file mode 100755 index 00000000..b8ebcbd2 --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/scripts/query-spec.cjs @@ -0,0 +1,7535 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/identity.js +var require_identity = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/identity.js"(exports2) { + "use strict"; + var ALIAS = /* @__PURE__ */ Symbol.for("yaml.alias"); + var DOC = /* @__PURE__ */ Symbol.for("yaml.document"); + var MAP = /* @__PURE__ */ Symbol.for("yaml.map"); + var PAIR = /* @__PURE__ */ Symbol.for("yaml.pair"); + var SCALAR = /* @__PURE__ */ Symbol.for("yaml.scalar"); + var SEQ = /* @__PURE__ */ Symbol.for("yaml.seq"); + var NODE_TYPE = /* @__PURE__ */ Symbol.for("yaml.node.type"); + var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS; + var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC; + var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP; + var isPair = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === PAIR; + var isScalar = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SCALAR; + var isSeq = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SEQ; + function isCollection(node) { + if (node && typeof node === "object") + switch (node[NODE_TYPE]) { + case MAP: + case SEQ: + return true; + } + return false; + } + function isNode(node) { + if (node && typeof node === "object") + switch (node[NODE_TYPE]) { + case ALIAS: + case MAP: + case SCALAR: + case SEQ: + return true; + } + return false; + } + var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor; + exports2.ALIAS = ALIAS; + exports2.DOC = DOC; + exports2.MAP = MAP; + exports2.NODE_TYPE = NODE_TYPE; + exports2.PAIR = PAIR; + exports2.SCALAR = SCALAR; + exports2.SEQ = SEQ; + exports2.hasAnchor = hasAnchor; + exports2.isAlias = isAlias; + exports2.isCollection = isCollection; + exports2.isDocument = isDocument; + exports2.isMap = isMap; + exports2.isNode = isNode; + exports2.isPair = isPair; + exports2.isScalar = isScalar; + exports2.isSeq = isSeq; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/visit.js +var require_visit = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/visit.js"(exports2) { + "use strict"; + var identity = require_identity(); + var BREAK = /* @__PURE__ */ Symbol("break visit"); + var SKIP = /* @__PURE__ */ Symbol("skip children"); + var REMOVE = /* @__PURE__ */ Symbol("remove node"); + function visit(node, visitor) { + const visitor_ = initVisitor(visitor); + if (identity.isDocument(node)) { + const cd = visit_(null, node.contents, visitor_, Object.freeze([node])); + if (cd === REMOVE) + node.contents = null; + } else + visit_(null, node, visitor_, Object.freeze([])); + } + visit.BREAK = BREAK; + visit.SKIP = SKIP; + visit.REMOVE = REMOVE; + function visit_(key, node, visitor, path) { + const ctrl = callVisitor(key, node, visitor, path); + if (identity.isNode(ctrl) || identity.isPair(ctrl)) { + replaceNode(key, path, ctrl); + return visit_(key, ctrl, visitor, path); + } + if (typeof ctrl !== "symbol") { + if (identity.isCollection(node)) { + path = Object.freeze(path.concat(node)); + for (let i = 0; i < node.items.length; ++i) { + const ci = visit_(i, node.items[i], visitor, path); + if (typeof ci === "number") + i = ci - 1; + else if (ci === BREAK) + return BREAK; + else if (ci === REMOVE) { + node.items.splice(i, 1); + i -= 1; + } + } + } else if (identity.isPair(node)) { + path = Object.freeze(path.concat(node)); + const ck = visit_("key", node.key, visitor, path); + if (ck === BREAK) + return BREAK; + else if (ck === REMOVE) + node.key = null; + const cv = visit_("value", node.value, visitor, path); + if (cv === BREAK) + return BREAK; + else if (cv === REMOVE) + node.value = null; + } + } + return ctrl; + } + async function visitAsync(node, visitor) { + const visitor_ = initVisitor(visitor); + if (identity.isDocument(node)) { + const cd = await visitAsync_(null, node.contents, visitor_, Object.freeze([node])); + if (cd === REMOVE) + node.contents = null; + } else + await visitAsync_(null, node, visitor_, Object.freeze([])); + } + visitAsync.BREAK = BREAK; + visitAsync.SKIP = SKIP; + visitAsync.REMOVE = REMOVE; + async function visitAsync_(key, node, visitor, path) { + const ctrl = await callVisitor(key, node, visitor, path); + if (identity.isNode(ctrl) || identity.isPair(ctrl)) { + replaceNode(key, path, ctrl); + return visitAsync_(key, ctrl, visitor, path); + } + if (typeof ctrl !== "symbol") { + if (identity.isCollection(node)) { + path = Object.freeze(path.concat(node)); + for (let i = 0; i < node.items.length; ++i) { + const ci = await visitAsync_(i, node.items[i], visitor, path); + if (typeof ci === "number") + i = ci - 1; + else if (ci === BREAK) + return BREAK; + else if (ci === REMOVE) { + node.items.splice(i, 1); + i -= 1; + } + } + } else if (identity.isPair(node)) { + path = Object.freeze(path.concat(node)); + const ck = await visitAsync_("key", node.key, visitor, path); + if (ck === BREAK) + return BREAK; + else if (ck === REMOVE) + node.key = null; + const cv = await visitAsync_("value", node.value, visitor, path); + if (cv === BREAK) + return BREAK; + else if (cv === REMOVE) + node.value = null; + } + } + return ctrl; + } + function initVisitor(visitor) { + if (typeof visitor === "object" && (visitor.Collection || visitor.Node || visitor.Value)) { + return Object.assign({ + Alias: visitor.Node, + Map: visitor.Node, + Scalar: visitor.Node, + Seq: visitor.Node + }, visitor.Value && { + Map: visitor.Value, + Scalar: visitor.Value, + Seq: visitor.Value + }, visitor.Collection && { + Map: visitor.Collection, + Seq: visitor.Collection + }, visitor); + } + return visitor; + } + function callVisitor(key, node, visitor, path) { + if (typeof visitor === "function") + return visitor(key, node, path); + if (identity.isMap(node)) + return visitor.Map?.(key, node, path); + if (identity.isSeq(node)) + return visitor.Seq?.(key, node, path); + if (identity.isPair(node)) + return visitor.Pair?.(key, node, path); + if (identity.isScalar(node)) + return visitor.Scalar?.(key, node, path); + if (identity.isAlias(node)) + return visitor.Alias?.(key, node, path); + return void 0; + } + function replaceNode(key, path, node) { + const parent = path[path.length - 1]; + if (identity.isCollection(parent)) { + parent.items[key] = node; + } else if (identity.isPair(parent)) { + if (key === "key") + parent.key = node; + else + parent.value = node; + } else if (identity.isDocument(parent)) { + parent.contents = node; + } else { + const pt = identity.isAlias(parent) ? "alias" : "scalar"; + throw new Error(`Cannot replace node with ${pt} parent`); + } + } + exports2.visit = visit; + exports2.visitAsync = visitAsync; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/doc/directives.js +var require_directives = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/doc/directives.js"(exports2) { + "use strict"; + var identity = require_identity(); + var visit = require_visit(); + var escapeChars = { + "!": "%21", + ",": "%2C", + "[": "%5B", + "]": "%5D", + "{": "%7B", + "}": "%7D" + }; + var escapeTagName = (tn) => tn.replace(/[!,[\]{}]/g, (ch) => escapeChars[ch]); + var Directives = class _Directives { + constructor(yaml, tags) { + this.docStart = null; + this.docEnd = false; + this.yaml = Object.assign({}, _Directives.defaultYaml, yaml); + this.tags = Object.assign({}, _Directives.defaultTags, tags); + } + clone() { + const copy = new _Directives(this.yaml, this.tags); + copy.docStart = this.docStart; + return copy; + } + /** + * During parsing, get a Directives instance for the current document and + * update the stream state according to the current version's spec. + */ + atDocument() { + const res = new _Directives(this.yaml, this.tags); + switch (this.yaml.version) { + case "1.1": + this.atNextDocument = true; + break; + case "1.2": + this.atNextDocument = false; + this.yaml = { + explicit: _Directives.defaultYaml.explicit, + version: "1.2" + }; + this.tags = Object.assign({}, _Directives.defaultTags); + break; + } + return res; + } + /** + * @param onError - May be called even if the action was successful + * @returns `true` on success + */ + add(line, onError) { + if (this.atNextDocument) { + this.yaml = { explicit: _Directives.defaultYaml.explicit, version: "1.1" }; + this.tags = Object.assign({}, _Directives.defaultTags); + this.atNextDocument = false; + } + const parts = line.trim().split(/[ \t]+/); + const name = parts.shift(); + switch (name) { + case "%TAG": { + if (parts.length !== 2) { + onError(0, "%TAG directive should contain exactly two parts"); + if (parts.length < 2) + return false; + } + const [handle, prefix] = parts; + this.tags[handle] = prefix; + return true; + } + case "%YAML": { + this.yaml.explicit = true; + if (parts.length !== 1) { + onError(0, "%YAML directive should contain exactly one part"); + return false; + } + const [version] = parts; + if (version === "1.1" || version === "1.2") { + this.yaml.version = version; + return true; + } else { + const isValid = /^\d+\.\d+$/.test(version); + onError(6, `Unsupported YAML version ${version}`, isValid); + return false; + } + } + default: + onError(0, `Unknown directive ${name}`, true); + return false; + } + } + /** + * Resolves a tag, matching handles to those defined in %TAG directives. + * + * @returns Resolved tag, which may also be the non-specific tag `'!'` or a + * `'!local'` tag, or `null` if unresolvable. + */ + tagName(source, onError) { + if (source === "!") + return "!"; + if (source[0] !== "!") { + onError(`Not a valid tag: ${source}`); + return null; + } + if (source[1] === "<") { + const verbatim = source.slice(2, -1); + if (verbatim === "!" || verbatim === "!!") { + onError(`Verbatim tags aren't resolved, so ${source} is invalid.`); + return null; + } + if (source[source.length - 1] !== ">") + onError("Verbatim tags must end with a >"); + return verbatim; + } + const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s); + if (!suffix) + onError(`The ${source} tag has no suffix`); + const prefix = this.tags[handle]; + if (prefix) { + try { + return prefix + decodeURIComponent(suffix); + } catch (error) { + onError(String(error)); + return null; + } + } + if (handle === "!") + return source; + onError(`Could not resolve tag: ${source}`); + return null; + } + /** + * Given a fully resolved tag, returns its printable string form, + * taking into account current tag prefixes and defaults. + */ + tagString(tag) { + for (const [handle, prefix] of Object.entries(this.tags)) { + if (tag.startsWith(prefix)) + return handle + escapeTagName(tag.substring(prefix.length)); + } + return tag[0] === "!" ? tag : `!<${tag}>`; + } + toString(doc) { + const lines = this.yaml.explicit ? [`%YAML ${this.yaml.version || "1.2"}`] : []; + const tagEntries = Object.entries(this.tags); + let tagNames; + if (doc && tagEntries.length > 0 && identity.isNode(doc.contents)) { + const tags = {}; + visit.visit(doc.contents, (_key, node) => { + if (identity.isNode(node) && node.tag) + tags[node.tag] = true; + }); + tagNames = Object.keys(tags); + } else + tagNames = []; + for (const [handle, prefix] of tagEntries) { + if (handle === "!!" && prefix === "tag:yaml.org,2002:") + continue; + if (!doc || tagNames.some((tn) => tn.startsWith(prefix))) + lines.push(`%TAG ${handle} ${prefix}`); + } + return lines.join("\n"); + } + }; + Directives.defaultYaml = { explicit: false, version: "1.2" }; + Directives.defaultTags = { "!!": "tag:yaml.org,2002:" }; + exports2.Directives = Directives; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/doc/anchors.js +var require_anchors = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/doc/anchors.js"(exports2) { + "use strict"; + var identity = require_identity(); + var visit = require_visit(); + function anchorIsValid(anchor) { + if (/[\x00-\x19\s,[\]{}]/.test(anchor)) { + const sa = JSON.stringify(anchor); + const msg = `Anchor must not contain whitespace or control characters: ${sa}`; + throw new Error(msg); + } + return true; + } + function anchorNames(root) { + const anchors = /* @__PURE__ */ new Set(); + visit.visit(root, { + Value(_key, node) { + if (node.anchor) + anchors.add(node.anchor); + } + }); + return anchors; + } + function findNewAnchor(prefix, exclude) { + for (let i = 1; true; ++i) { + const name = `${prefix}${i}`; + if (!exclude.has(name)) + return name; + } + } + function createNodeAnchors(doc, prefix) { + const aliasObjects = []; + const sourceObjects = /* @__PURE__ */ new Map(); + let prevAnchors = null; + return { + onAnchor: (source) => { + aliasObjects.push(source); + prevAnchors ?? (prevAnchors = anchorNames(doc)); + const anchor = findNewAnchor(prefix, prevAnchors); + prevAnchors.add(anchor); + return anchor; + }, + /** + * With circular references, the source node is only resolved after all + * of its child nodes are. This is why anchors are set only after all of + * the nodes have been created. + */ + setAnchors: () => { + for (const source of aliasObjects) { + const ref = sourceObjects.get(source); + if (typeof ref === "object" && ref.anchor && (identity.isScalar(ref.node) || identity.isCollection(ref.node))) { + ref.node.anchor = ref.anchor; + } else { + const error = new Error("Failed to resolve repeated object (this should not happen)"); + error.source = source; + throw error; + } + } + }, + sourceObjects + }; + } + exports2.anchorIsValid = anchorIsValid; + exports2.anchorNames = anchorNames; + exports2.createNodeAnchors = createNodeAnchors; + exports2.findNewAnchor = findNewAnchor; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/doc/applyReviver.js +var require_applyReviver = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/doc/applyReviver.js"(exports2) { + "use strict"; + function applyReviver(reviver, obj, key, val) { + if (val && typeof val === "object") { + if (Array.isArray(val)) { + for (let i = 0, len = val.length; i < len; ++i) { + const v0 = val[i]; + const v1 = applyReviver(reviver, val, String(i), v0); + if (v1 === void 0) + delete val[i]; + else if (v1 !== v0) + val[i] = v1; + } + } else if (val instanceof Map) { + for (const k of Array.from(val.keys())) { + const v0 = val.get(k); + const v1 = applyReviver(reviver, val, k, v0); + if (v1 === void 0) + val.delete(k); + else if (v1 !== v0) + val.set(k, v1); + } + } else if (val instanceof Set) { + for (const v0 of Array.from(val)) { + const v1 = applyReviver(reviver, val, v0, v0); + if (v1 === void 0) + val.delete(v0); + else if (v1 !== v0) { + val.delete(v0); + val.add(v1); + } + } + } else { + for (const [k, v0] of Object.entries(val)) { + const v1 = applyReviver(reviver, val, k, v0); + if (v1 === void 0) + delete val[k]; + else if (v1 !== v0) + val[k] = v1; + } + } + } + return reviver.call(obj, key, val); + } + exports2.applyReviver = applyReviver; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/toJS.js +var require_toJS = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/toJS.js"(exports2) { + "use strict"; + var identity = require_identity(); + function toJS(value, arg, ctx) { + if (Array.isArray(value)) + return value.map((v, i) => toJS(v, String(i), ctx)); + if (value && typeof value.toJSON === "function") { + if (!ctx || !identity.hasAnchor(value)) + return value.toJSON(arg, ctx); + const data = { aliasCount: 0, count: 1, res: void 0 }; + ctx.anchors.set(value, data); + ctx.onCreate = (res2) => { + data.res = res2; + delete ctx.onCreate; + }; + const res = value.toJSON(arg, ctx); + if (ctx.onCreate) + ctx.onCreate(res); + return res; + } + if (typeof value === "bigint" && !ctx?.keep) + return Number(value); + return value; + } + exports2.toJS = toJS; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/Node.js +var require_Node = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/Node.js"(exports2) { + "use strict"; + var applyReviver = require_applyReviver(); + var identity = require_identity(); + var toJS = require_toJS(); + var NodeBase = class { + constructor(type) { + Object.defineProperty(this, identity.NODE_TYPE, { value: type }); + } + /** Create a copy of this node. */ + clone() { + const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** A plain JavaScript representation of this node. */ + toJS(doc, { mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { + if (!identity.isDocument(doc)) + throw new TypeError("A document argument is required"); + const ctx = { + anchors: /* @__PURE__ */ new Map(), + doc, + keep: true, + mapAsMap: mapAsMap === true, + mapKeyWarned: false, + maxAliasCount: typeof maxAliasCount === "number" ? maxAliasCount : 100 + }; + const res = toJS.toJS(this, "", ctx); + if (typeof onAnchor === "function") + for (const { count, res: res2 } of ctx.anchors.values()) + onAnchor(res2, count); + return typeof reviver === "function" ? applyReviver.applyReviver(reviver, { "": res }, "", res) : res; + } + }; + exports2.NodeBase = NodeBase; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/Alias.js +var require_Alias = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/Alias.js"(exports2) { + "use strict"; + var anchors = require_anchors(); + var visit = require_visit(); + var identity = require_identity(); + var Node = require_Node(); + var toJS = require_toJS(); + var Alias = class extends Node.NodeBase { + constructor(source) { + super(identity.ALIAS); + this.source = source; + Object.defineProperty(this, "tag", { + set() { + throw new Error("Alias nodes cannot have tags"); + } + }); + } + /** + * Resolve the value of this alias within `doc`, finding the last + * instance of the `source` anchor before this node. + */ + resolve(doc, ctx) { + let nodes; + if (ctx?.aliasResolveCache) { + nodes = ctx.aliasResolveCache; + } else { + nodes = []; + visit.visit(doc, { + Node: (_key, node) => { + if (identity.isAlias(node) || identity.hasAnchor(node)) + nodes.push(node); + } + }); + if (ctx) + ctx.aliasResolveCache = nodes; + } + let found = void 0; + for (const node of nodes) { + if (node === this) + break; + if (node.anchor === this.source) + found = node; + } + return found; + } + toJSON(_arg, ctx) { + if (!ctx) + return { source: this.source }; + const { anchors: anchors2, doc, maxAliasCount } = ctx; + const source = this.resolve(doc, ctx); + if (!source) { + const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; + throw new ReferenceError(msg); + } + let data = anchors2.get(source); + if (!data) { + toJS.toJS(source, null, ctx); + data = anchors2.get(source); + } + if (data?.res === void 0) { + const msg = "This should not happen: Alias anchor was not resolved?"; + throw new ReferenceError(msg); + } + if (maxAliasCount >= 0) { + data.count += 1; + if (data.aliasCount === 0) + data.aliasCount = getAliasCount(doc, source, anchors2); + if (data.count * data.aliasCount > maxAliasCount) { + const msg = "Excessive alias count indicates a resource exhaustion attack"; + throw new ReferenceError(msg); + } + } + return data.res; + } + toString(ctx, _onComment, _onChompKeep) { + const src = `*${this.source}`; + if (ctx) { + anchors.anchorIsValid(this.source); + if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) { + const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; + throw new Error(msg); + } + if (ctx.implicitKey) + return `${src} `; + } + return src; + } + }; + function getAliasCount(doc, node, anchors2) { + if (identity.isAlias(node)) { + const source = node.resolve(doc); + const anchor = anchors2 && source && anchors2.get(source); + return anchor ? anchor.count * anchor.aliasCount : 0; + } else if (identity.isCollection(node)) { + let count = 0; + for (const item of node.items) { + const c = getAliasCount(doc, item, anchors2); + if (c > count) + count = c; + } + return count; + } else if (identity.isPair(node)) { + const kc = getAliasCount(doc, node.key, anchors2); + const vc = getAliasCount(doc, node.value, anchors2); + return Math.max(kc, vc); + } + return 1; + } + exports2.Alias = Alias; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/Scalar.js +var require_Scalar = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/Scalar.js"(exports2) { + "use strict"; + var identity = require_identity(); + var Node = require_Node(); + var toJS = require_toJS(); + var isScalarValue = (value) => !value || typeof value !== "function" && typeof value !== "object"; + var Scalar = class extends Node.NodeBase { + constructor(value) { + super(identity.SCALAR); + this.value = value; + } + toJSON(arg, ctx) { + return ctx?.keep ? this.value : toJS.toJS(this.value, arg, ctx); + } + toString() { + return String(this.value); + } + }; + Scalar.BLOCK_FOLDED = "BLOCK_FOLDED"; + Scalar.BLOCK_LITERAL = "BLOCK_LITERAL"; + Scalar.PLAIN = "PLAIN"; + Scalar.QUOTE_DOUBLE = "QUOTE_DOUBLE"; + Scalar.QUOTE_SINGLE = "QUOTE_SINGLE"; + exports2.Scalar = Scalar; + exports2.isScalarValue = isScalarValue; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/doc/createNode.js +var require_createNode = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/doc/createNode.js"(exports2) { + "use strict"; + var Alias = require_Alias(); + var identity = require_identity(); + var Scalar = require_Scalar(); + var defaultTagPrefix = "tag:yaml.org,2002:"; + function findTagObject(value, tagName, tags) { + if (tagName) { + const match = tags.filter((t) => t.tag === tagName); + const tagObj = match.find((t) => !t.format) ?? match[0]; + if (!tagObj) + throw new Error(`Tag ${tagName} not found`); + return tagObj; + } + return tags.find((t) => t.identify?.(value) && !t.format); + } + function createNode(value, tagName, ctx) { + if (identity.isDocument(value)) + value = value.contents; + if (identity.isNode(value)) + return value; + if (identity.isPair(value)) { + const map = ctx.schema[identity.MAP].createNode?.(ctx.schema, null, ctx); + map.items.push(value); + return map; + } + if (value instanceof String || value instanceof Number || value instanceof Boolean || typeof BigInt !== "undefined" && value instanceof BigInt) { + value = value.valueOf(); + } + const { aliasDuplicateObjects, onAnchor, onTagObj, schema, sourceObjects } = ctx; + let ref = void 0; + if (aliasDuplicateObjects && value && typeof value === "object") { + ref = sourceObjects.get(value); + if (ref) { + ref.anchor ?? (ref.anchor = onAnchor(value)); + return new Alias.Alias(ref.anchor); + } else { + ref = { anchor: null, node: null }; + sourceObjects.set(value, ref); + } + } + if (tagName?.startsWith("!!")) + tagName = defaultTagPrefix + tagName.slice(2); + let tagObj = findTagObject(value, tagName, schema.tags); + if (!tagObj) { + if (value && typeof value.toJSON === "function") { + value = value.toJSON(); + } + if (!value || typeof value !== "object") { + const node2 = new Scalar.Scalar(value); + if (ref) + ref.node = node2; + return node2; + } + tagObj = value instanceof Map ? schema[identity.MAP] : Symbol.iterator in Object(value) ? schema[identity.SEQ] : schema[identity.MAP]; + } + if (onTagObj) { + onTagObj(tagObj); + delete ctx.onTagObj; + } + const node = tagObj?.createNode ? tagObj.createNode(ctx.schema, value, ctx) : typeof tagObj?.nodeClass?.from === "function" ? tagObj.nodeClass.from(ctx.schema, value, ctx) : new Scalar.Scalar(value); + if (tagName) + node.tag = tagName; + else if (!tagObj.default) + node.tag = tagObj.tag; + if (ref) + ref.node = node; + return node; + } + exports2.createNode = createNode; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/Collection.js +var require_Collection = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/Collection.js"(exports2) { + "use strict"; + var createNode = require_createNode(); + var identity = require_identity(); + var Node = require_Node(); + function collectionFromPath(schema, path, value) { + let v = value; + for (let i = path.length - 1; i >= 0; --i) { + const k = path[i]; + if (typeof k === "number" && Number.isInteger(k) && k >= 0) { + const a = []; + a[k] = v; + v = a; + } else { + v = /* @__PURE__ */ new Map([[k, v]]); + } + } + return createNode.createNode(v, void 0, { + aliasDuplicateObjects: false, + keepUndefined: false, + onAnchor: () => { + throw new Error("This should not happen, please report a bug."); + }, + schema, + sourceObjects: /* @__PURE__ */ new Map() + }); + } + var isEmptyPath = (path) => path == null || typeof path === "object" && !!path[Symbol.iterator]().next().done; + var Collection = class extends Node.NodeBase { + constructor(type, schema) { + super(type); + Object.defineProperty(this, "schema", { + value: schema, + configurable: true, + enumerable: false, + writable: true + }); + } + /** + * Create a copy of this collection. + * + * @param schema - If defined, overwrites the original's schema + */ + clone(schema) { + const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); + if (schema) + copy.schema = schema; + copy.items = copy.items.map((it) => identity.isNode(it) || identity.isPair(it) ? it.clone(schema) : it); + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** + * Adds a value to the collection. For `!!map` and `!!omap` the value must + * be a Pair instance or a `{ key, value }` object, which may not have a key + * that already exists in the map. + */ + addIn(path, value) { + if (isEmptyPath(path)) + this.add(value); + else { + const [key, ...rest] = path; + const node = this.get(key, true); + if (identity.isCollection(node)) + node.addIn(rest, value); + else if (node === void 0 && this.schema) + this.set(key, collectionFromPath(this.schema, rest, value)); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + } + /** + * Removes a value from the collection. + * @returns `true` if the item was found and removed. + */ + deleteIn(path) { + const [key, ...rest] = path; + if (rest.length === 0) + return this.delete(key); + const node = this.get(key, true); + if (identity.isCollection(node)) + return node.deleteIn(rest); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + /** + * Returns item at `key`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + getIn(path, keepScalar) { + const [key, ...rest] = path; + const node = this.get(key, true); + if (rest.length === 0) + return !keepScalar && identity.isScalar(node) ? node.value : node; + else + return identity.isCollection(node) ? node.getIn(rest, keepScalar) : void 0; + } + hasAllNullValues(allowScalar) { + return this.items.every((node) => { + if (!identity.isPair(node)) + return false; + const n = node.value; + return n == null || allowScalar && identity.isScalar(n) && n.value == null && !n.commentBefore && !n.comment && !n.tag; + }); + } + /** + * Checks if the collection includes a value with the key `key`. + */ + hasIn(path) { + const [key, ...rest] = path; + if (rest.length === 0) + return this.has(key); + const node = this.get(key, true); + return identity.isCollection(node) ? node.hasIn(rest) : false; + } + /** + * Sets a value in this collection. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + setIn(path, value) { + const [key, ...rest] = path; + if (rest.length === 0) { + this.set(key, value); + } else { + const node = this.get(key, true); + if (identity.isCollection(node)) + node.setIn(rest, value); + else if (node === void 0 && this.schema) + this.set(key, collectionFromPath(this.schema, rest, value)); + else + throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + } + }; + exports2.Collection = Collection; + exports2.collectionFromPath = collectionFromPath; + exports2.isEmptyPath = isEmptyPath; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyComment.js +var require_stringifyComment = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyComment.js"(exports2) { + "use strict"; + var stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, "#"); + function indentComment(comment, indent) { + if (/^\n+$/.test(comment)) + return comment.substring(1); + return indent ? comment.replace(/^(?! *$)/gm, indent) : comment; + } + var lineComment = (str, indent, comment) => str.endsWith("\n") ? indentComment(comment, indent) : comment.includes("\n") ? "\n" + indentComment(comment, indent) : (str.endsWith(" ") ? "" : " ") + comment; + exports2.indentComment = indentComment; + exports2.lineComment = lineComment; + exports2.stringifyComment = stringifyComment; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/foldFlowLines.js +var require_foldFlowLines = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/foldFlowLines.js"(exports2) { + "use strict"; + var FOLD_FLOW = "flow"; + var FOLD_BLOCK = "block"; + var FOLD_QUOTED = "quoted"; + function foldFlowLines(text, indent, mode = "flow", { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) { + if (!lineWidth || lineWidth < 0) + return text; + if (lineWidth < minContentWidth) + minContentWidth = 0; + const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length); + if (text.length <= endStep) + return text; + const folds = []; + const escapedFolds = {}; + let end = lineWidth - indent.length; + if (typeof indentAtStart === "number") { + if (indentAtStart > lineWidth - Math.max(2, minContentWidth)) + folds.push(0); + else + end = lineWidth - indentAtStart; + } + let split = void 0; + let prev = void 0; + let overflow = false; + let i = -1; + let escStart = -1; + let escEnd = -1; + if (mode === FOLD_BLOCK) { + i = consumeMoreIndentedLines(text, i, indent.length); + if (i !== -1) + end = i + endStep; + } + for (let ch; ch = text[i += 1]; ) { + if (mode === FOLD_QUOTED && ch === "\\") { + escStart = i; + switch (text[i + 1]) { + case "x": + i += 3; + break; + case "u": + i += 5; + break; + case "U": + i += 9; + break; + default: + i += 1; + } + escEnd = i; + } + if (ch === "\n") { + if (mode === FOLD_BLOCK) + i = consumeMoreIndentedLines(text, i, indent.length); + end = i + indent.length + endStep; + split = void 0; + } else { + if (ch === " " && prev && prev !== " " && prev !== "\n" && prev !== " ") { + const next = text[i + 1]; + if (next && next !== " " && next !== "\n" && next !== " ") + split = i; + } + if (i >= end) { + if (split) { + folds.push(split); + end = split + endStep; + split = void 0; + } else if (mode === FOLD_QUOTED) { + while (prev === " " || prev === " ") { + prev = ch; + ch = text[i += 1]; + overflow = true; + } + const j = i > escEnd + 1 ? i - 2 : escStart - 1; + if (escapedFolds[j]) + return text; + folds.push(j); + escapedFolds[j] = true; + end = j + endStep; + split = void 0; + } else { + overflow = true; + } + } + } + prev = ch; + } + if (overflow && onOverflow) + onOverflow(); + if (folds.length === 0) + return text; + if (onFold) + onFold(); + let res = text.slice(0, folds[0]); + for (let i2 = 0; i2 < folds.length; ++i2) { + const fold = folds[i2]; + const end2 = folds[i2 + 1] || text.length; + if (fold === 0) + res = ` +${indent}${text.slice(0, end2)}`; + else { + if (mode === FOLD_QUOTED && escapedFolds[fold]) + res += `${text[fold]}\\`; + res += ` +${indent}${text.slice(fold + 1, end2)}`; + } + } + return res; + } + function consumeMoreIndentedLines(text, i, indent) { + let end = i; + let start = i + 1; + let ch = text[start]; + while (ch === " " || ch === " ") { + if (i < start + indent) { + ch = text[++i]; + } else { + do { + ch = text[++i]; + } while (ch && ch !== "\n"); + end = i; + start = i + 1; + ch = text[start]; + } + } + return end; + } + exports2.FOLD_BLOCK = FOLD_BLOCK; + exports2.FOLD_FLOW = FOLD_FLOW; + exports2.FOLD_QUOTED = FOLD_QUOTED; + exports2.foldFlowLines = foldFlowLines; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyString.js +var require_stringifyString = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyString.js"(exports2) { + "use strict"; + var Scalar = require_Scalar(); + var foldFlowLines = require_foldFlowLines(); + var getFoldOptions = (ctx, isBlock) => ({ + indentAtStart: isBlock ? ctx.indent.length : ctx.indentAtStart, + lineWidth: ctx.options.lineWidth, + minContentWidth: ctx.options.minContentWidth + }); + var containsDocumentMarker = (str) => /^(%|---|\.\.\.)/m.test(str); + function lineLengthOverLimit(str, lineWidth, indentLength) { + if (!lineWidth || lineWidth < 0) + return false; + const limit = lineWidth - indentLength; + const strLen = str.length; + if (strLen <= limit) + return false; + for (let i = 0, start = 0; i < strLen; ++i) { + if (str[i] === "\n") { + if (i - start > limit) + return true; + start = i + 1; + if (strLen - start <= limit) + return false; + } + } + return true; + } + function doubleQuotedString(value, ctx) { + const json = JSON.stringify(value); + if (ctx.options.doubleQuotedAsJSON) + return json; + const { implicitKey } = ctx; + const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength; + const indent = ctx.indent || (containsDocumentMarker(value) ? " " : ""); + let str = ""; + let start = 0; + for (let i = 0, ch = json[i]; ch; ch = json[++i]) { + if (ch === " " && json[i + 1] === "\\" && json[i + 2] === "n") { + str += json.slice(start, i) + "\\ "; + i += 1; + start = i; + ch = "\\"; + } + if (ch === "\\") + switch (json[i + 1]) { + case "u": + { + str += json.slice(start, i); + const code = json.substr(i + 2, 4); + switch (code) { + case "0000": + str += "\\0"; + break; + case "0007": + str += "\\a"; + break; + case "000b": + str += "\\v"; + break; + case "001b": + str += "\\e"; + break; + case "0085": + str += "\\N"; + break; + case "00a0": + str += "\\_"; + break; + case "2028": + str += "\\L"; + break; + case "2029": + str += "\\P"; + break; + default: + if (code.substr(0, 2) === "00") + str += "\\x" + code.substr(2); + else + str += json.substr(i, 6); + } + i += 5; + start = i + 1; + } + break; + case "n": + if (implicitKey || json[i + 2] === '"' || json.length < minMultiLineLength) { + i += 1; + } else { + str += json.slice(start, i) + "\n\n"; + while (json[i + 2] === "\\" && json[i + 3] === "n" && json[i + 4] !== '"') { + str += "\n"; + i += 2; + } + str += indent; + if (json[i + 2] === " ") + str += "\\"; + i += 1; + start = i + 1; + } + break; + default: + i += 1; + } + } + str = start ? str + json.slice(start) : json; + return implicitKey ? str : foldFlowLines.foldFlowLines(str, indent, foldFlowLines.FOLD_QUOTED, getFoldOptions(ctx, false)); + } + function singleQuotedString(value, ctx) { + if (ctx.options.singleQuote === false || ctx.implicitKey && value.includes("\n") || /[ \t]\n|\n[ \t]/.test(value)) + return doubleQuotedString(value, ctx); + const indent = ctx.indent || (containsDocumentMarker(value) ? " " : ""); + const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$& +${indent}`) + "'"; + return ctx.implicitKey ? res : foldFlowLines.foldFlowLines(res, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false)); + } + function quotedString(value, ctx) { + const { singleQuote } = ctx.options; + let qs; + if (singleQuote === false) + qs = doubleQuotedString; + else { + const hasDouble = value.includes('"'); + const hasSingle = value.includes("'"); + if (hasDouble && !hasSingle) + qs = singleQuotedString; + else if (hasSingle && !hasDouble) + qs = doubleQuotedString; + else + qs = singleQuote ? singleQuotedString : doubleQuotedString; + } + return qs(value, ctx); + } + var blockEndNewlines; + try { + blockEndNewlines = new RegExp("(^|(?\n"; + let chomp; + let endStart; + for (endStart = value.length; endStart > 0; --endStart) { + const ch = value[endStart - 1]; + if (ch !== "\n" && ch !== " " && ch !== " ") + break; + } + let end = value.substring(endStart); + const endNlPos = end.indexOf("\n"); + if (endNlPos === -1) { + chomp = "-"; + } else if (value === end || endNlPos !== end.length - 1) { + chomp = "+"; + if (onChompKeep) + onChompKeep(); + } else { + chomp = ""; + } + if (end) { + value = value.slice(0, -end.length); + if (end[end.length - 1] === "\n") + end = end.slice(0, -1); + end = end.replace(blockEndNewlines, `$&${indent}`); + } + let startWithSpace = false; + let startEnd; + let startNlPos = -1; + for (startEnd = 0; startEnd < value.length; ++startEnd) { + const ch = value[startEnd]; + if (ch === " ") + startWithSpace = true; + else if (ch === "\n") + startNlPos = startEnd; + else + break; + } + let start = value.substring(0, startNlPos < startEnd ? startNlPos + 1 : startEnd); + if (start) { + value = value.substring(start.length); + start = start.replace(/\n+/g, `$&${indent}`); + } + const indentSize = indent ? "2" : "1"; + let header = (startWithSpace ? indentSize : "") + chomp; + if (comment) { + header += " " + commentString(comment.replace(/ ?[\r\n]+/g, " ")); + if (onComment) + onComment(); + } + if (!literal) { + const foldedValue = value.replace(/\n+/g, "\n$&").replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, "$1$2").replace(/\n+/g, `$&${indent}`); + let literalFallback = false; + const foldOptions = getFoldOptions(ctx, true); + if (blockQuote !== "folded" && type !== Scalar.Scalar.BLOCK_FOLDED) { + foldOptions.onOverflow = () => { + literalFallback = true; + }; + } + const body = foldFlowLines.foldFlowLines(`${start}${foldedValue}${end}`, indent, foldFlowLines.FOLD_BLOCK, foldOptions); + if (!literalFallback) + return `>${header} +${indent}${body}`; + } + value = value.replace(/\n+/g, `$&${indent}`); + return `|${header} +${indent}${start}${value}${end}`; + } + function plainString(item, ctx, onComment, onChompKeep) { + const { type, value } = item; + const { actualString, implicitKey, indent, indentStep, inFlow } = ctx; + if (implicitKey && value.includes("\n") || inFlow && /[[\]{},]/.test(value)) { + return quotedString(value, ctx); + } + if (/^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) { + return implicitKey || inFlow || !value.includes("\n") ? quotedString(value, ctx) : blockString(item, ctx, onComment, onChompKeep); + } + if (!implicitKey && !inFlow && type !== Scalar.Scalar.PLAIN && value.includes("\n")) { + return blockString(item, ctx, onComment, onChompKeep); + } + if (containsDocumentMarker(value)) { + if (indent === "") { + ctx.forceBlockIndent = true; + return blockString(item, ctx, onComment, onChompKeep); + } else if (implicitKey && indent === indentStep) { + return quotedString(value, ctx); + } + } + const str = value.replace(/\n+/g, `$& +${indent}`); + if (actualString) { + const test = (tag) => tag.default && tag.tag !== "tag:yaml.org,2002:str" && tag.test?.test(str); + const { compat, tags } = ctx.doc.schema; + if (tags.some(test) || compat?.some(test)) + return quotedString(value, ctx); + } + return implicitKey ? str : foldFlowLines.foldFlowLines(str, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false)); + } + function stringifyString(item, ctx, onComment, onChompKeep) { + const { implicitKey, inFlow } = ctx; + const ss = typeof item.value === "string" ? item : Object.assign({}, item, { value: String(item.value) }); + let { type } = item; + if (type !== Scalar.Scalar.QUOTE_DOUBLE) { + if (/[\x00-\x08\x0b-\x1f\x7f-\x9f\u{D800}-\u{DFFF}]/u.test(ss.value)) + type = Scalar.Scalar.QUOTE_DOUBLE; + } + const _stringify = (_type) => { + switch (_type) { + case Scalar.Scalar.BLOCK_FOLDED: + case Scalar.Scalar.BLOCK_LITERAL: + return implicitKey || inFlow ? quotedString(ss.value, ctx) : blockString(ss, ctx, onComment, onChompKeep); + case Scalar.Scalar.QUOTE_DOUBLE: + return doubleQuotedString(ss.value, ctx); + case Scalar.Scalar.QUOTE_SINGLE: + return singleQuotedString(ss.value, ctx); + case Scalar.Scalar.PLAIN: + return plainString(ss, ctx, onComment, onChompKeep); + default: + return null; + } + }; + let res = _stringify(type); + if (res === null) { + const { defaultKeyType, defaultStringType } = ctx.options; + const t = implicitKey && defaultKeyType || defaultStringType; + res = _stringify(t); + if (res === null) + throw new Error(`Unsupported default string type ${t}`); + } + return res; + } + exports2.stringifyString = stringifyString; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringify.js +var require_stringify = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringify.js"(exports2) { + "use strict"; + var anchors = require_anchors(); + var identity = require_identity(); + var stringifyComment = require_stringifyComment(); + var stringifyString = require_stringifyString(); + function createStringifyContext(doc, options) { + const opt = Object.assign({ + blockQuote: true, + commentString: stringifyComment.stringifyComment, + defaultKeyType: null, + defaultStringType: "PLAIN", + directives: null, + doubleQuotedAsJSON: false, + doubleQuotedMinMultiLineLength: 40, + falseStr: "false", + flowCollectionPadding: true, + indentSeq: true, + lineWidth: 80, + minContentWidth: 20, + nullStr: "null", + simpleKeys: false, + singleQuote: null, + trueStr: "true", + verifyAliasOrder: true + }, doc.schema.toStringOptions, options); + let inFlow; + switch (opt.collectionStyle) { + case "block": + inFlow = false; + break; + case "flow": + inFlow = true; + break; + default: + inFlow = null; + } + return { + anchors: /* @__PURE__ */ new Set(), + doc, + flowCollectionPadding: opt.flowCollectionPadding ? " " : "", + indent: "", + indentStep: typeof opt.indent === "number" ? " ".repeat(opt.indent) : " ", + inFlow, + options: opt + }; + } + function getTagObject(tags, item) { + if (item.tag) { + const match = tags.filter((t) => t.tag === item.tag); + if (match.length > 0) + return match.find((t) => t.format === item.format) ?? match[0]; + } + let tagObj = void 0; + let obj; + if (identity.isScalar(item)) { + obj = item.value; + let match = tags.filter((t) => t.identify?.(obj)); + if (match.length > 1) { + const testMatch = match.filter((t) => t.test); + if (testMatch.length > 0) + match = testMatch; + } + tagObj = match.find((t) => t.format === item.format) ?? match.find((t) => !t.format); + } else { + obj = item; + tagObj = tags.find((t) => t.nodeClass && obj instanceof t.nodeClass); + } + if (!tagObj) { + const name = obj?.constructor?.name ?? (obj === null ? "null" : typeof obj); + throw new Error(`Tag not resolved for ${name} value`); + } + return tagObj; + } + function stringifyProps(node, tagObj, { anchors: anchors$1, doc }) { + if (!doc.directives) + return ""; + const props = []; + const anchor = (identity.isScalar(node) || identity.isCollection(node)) && node.anchor; + if (anchor && anchors.anchorIsValid(anchor)) { + anchors$1.add(anchor); + props.push(`&${anchor}`); + } + const tag = node.tag ?? (tagObj.default ? null : tagObj.tag); + if (tag) + props.push(doc.directives.tagString(tag)); + return props.join(" "); + } + function stringify(item, ctx, onComment, onChompKeep) { + if (identity.isPair(item)) + return item.toString(ctx, onComment, onChompKeep); + if (identity.isAlias(item)) { + if (ctx.doc.directives) + return item.toString(ctx); + if (ctx.resolvedAliases?.has(item)) { + throw new TypeError(`Cannot stringify circular structure without alias nodes`); + } else { + if (ctx.resolvedAliases) + ctx.resolvedAliases.add(item); + else + ctx.resolvedAliases = /* @__PURE__ */ new Set([item]); + item = item.resolve(ctx.doc); + } + } + let tagObj = void 0; + const node = identity.isNode(item) ? item : ctx.doc.createNode(item, { onTagObj: (o) => tagObj = o }); + tagObj ?? (tagObj = getTagObject(ctx.doc.schema.tags, node)); + const props = stringifyProps(node, tagObj, ctx); + if (props.length > 0) + ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1; + const str = typeof tagObj.stringify === "function" ? tagObj.stringify(node, ctx, onComment, onChompKeep) : identity.isScalar(node) ? stringifyString.stringifyString(node, ctx, onComment, onChompKeep) : node.toString(ctx, onComment, onChompKeep); + if (!props) + return str; + return identity.isScalar(node) || str[0] === "{" || str[0] === "[" ? `${props} ${str}` : `${props} +${ctx.indent}${str}`; + } + exports2.createStringifyContext = createStringifyContext; + exports2.stringify = stringify; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyPair.js +var require_stringifyPair = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyPair.js"(exports2) { + "use strict"; + var identity = require_identity(); + var Scalar = require_Scalar(); + var stringify = require_stringify(); + var stringifyComment = require_stringifyComment(); + function stringifyPair({ key, value }, ctx, onComment, onChompKeep) { + const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx; + let keyComment = identity.isNode(key) && key.comment || null; + if (simpleKeys) { + if (keyComment) { + throw new Error("With simple keys, key nodes cannot have comments"); + } + if (identity.isCollection(key) || !identity.isNode(key) && typeof key === "object") { + const msg = "With simple keys, collection cannot be used as a key value"; + throw new Error(msg); + } + } + let explicitKey = !simpleKeys && (!key || keyComment && value == null && !ctx.inFlow || identity.isCollection(key) || (identity.isScalar(key) ? key.type === Scalar.Scalar.BLOCK_FOLDED || key.type === Scalar.Scalar.BLOCK_LITERAL : typeof key === "object")); + ctx = Object.assign({}, ctx, { + allNullValues: false, + implicitKey: !explicitKey && (simpleKeys || !allNullValues), + indent: indent + indentStep + }); + let keyCommentDone = false; + let chompKeep = false; + let str = stringify.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true); + if (!explicitKey && !ctx.inFlow && str.length > 1024) { + if (simpleKeys) + throw new Error("With simple keys, single line scalar must not span more than 1024 characters"); + explicitKey = true; + } + if (ctx.inFlow) { + if (allNullValues || value == null) { + if (keyCommentDone && onComment) + onComment(); + return str === "" ? "?" : explicitKey ? `? ${str}` : str; + } + } else if (allNullValues && !simpleKeys || value == null && explicitKey) { + str = `? ${str}`; + if (keyComment && !keyCommentDone) { + str += stringifyComment.lineComment(str, ctx.indent, commentString(keyComment)); + } else if (chompKeep && onChompKeep) + onChompKeep(); + return str; + } + if (keyCommentDone) + keyComment = null; + if (explicitKey) { + if (keyComment) + str += stringifyComment.lineComment(str, ctx.indent, commentString(keyComment)); + str = `? ${str} +${indent}:`; + } else { + str = `${str}:`; + if (keyComment) + str += stringifyComment.lineComment(str, ctx.indent, commentString(keyComment)); + } + let vsb, vcb, valueComment; + if (identity.isNode(value)) { + vsb = !!value.spaceBefore; + vcb = value.commentBefore; + valueComment = value.comment; + } else { + vsb = false; + vcb = null; + valueComment = null; + if (value && typeof value === "object") + value = doc.createNode(value); + } + ctx.implicitKey = false; + if (!explicitKey && !keyComment && identity.isScalar(value)) + ctx.indentAtStart = str.length + 1; + chompKeep = false; + if (!indentSeq && indentStep.length >= 2 && !ctx.inFlow && !explicitKey && identity.isSeq(value) && !value.flow && !value.tag && !value.anchor) { + ctx.indent = ctx.indent.substring(2); + } + let valueCommentDone = false; + const valueStr = stringify.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true); + let ws = " "; + if (keyComment || vsb || vcb) { + ws = vsb ? "\n" : ""; + if (vcb) { + const cs = commentString(vcb); + ws += ` +${stringifyComment.indentComment(cs, ctx.indent)}`; + } + if (valueStr === "" && !ctx.inFlow) { + if (ws === "\n" && valueComment) + ws = "\n\n"; + } else { + ws += ` +${ctx.indent}`; + } + } else if (!explicitKey && identity.isCollection(value)) { + const vs0 = valueStr[0]; + const nl0 = valueStr.indexOf("\n"); + const hasNewline = nl0 !== -1; + const flow = ctx.inFlow ?? value.flow ?? value.items.length === 0; + if (hasNewline || !flow) { + let hasPropsLine = false; + if (hasNewline && (vs0 === "&" || vs0 === "!")) { + let sp0 = valueStr.indexOf(" "); + if (vs0 === "&" && sp0 !== -1 && sp0 < nl0 && valueStr[sp0 + 1] === "!") { + sp0 = valueStr.indexOf(" ", sp0 + 1); + } + if (sp0 === -1 || nl0 < sp0) + hasPropsLine = true; + } + if (!hasPropsLine) + ws = ` +${ctx.indent}`; + } + } else if (valueStr === "" || valueStr[0] === "\n") { + ws = ""; + } + str += ws + valueStr; + if (ctx.inFlow) { + if (valueCommentDone && onComment) + onComment(); + } else if (valueComment && !valueCommentDone) { + str += stringifyComment.lineComment(str, ctx.indent, commentString(valueComment)); + } else if (chompKeep && onChompKeep) { + onChompKeep(); + } + return str; + } + exports2.stringifyPair = stringifyPair; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/log.js +var require_log = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/log.js"(exports2) { + "use strict"; + var node_process = require("process"); + function debug(logLevel, ...messages) { + if (logLevel === "debug") + console.log(...messages); + } + function warn(logLevel, warning) { + if (logLevel === "debug" || logLevel === "warn") { + if (typeof node_process.emitWarning === "function") + node_process.emitWarning(warning); + else + console.warn(warning); + } + } + exports2.debug = debug; + exports2.warn = warn; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/merge.js +var require_merge = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/merge.js"(exports2) { + "use strict"; + var identity = require_identity(); + var Scalar = require_Scalar(); + var MERGE_KEY = "<<"; + var merge = { + identify: (value) => value === MERGE_KEY || typeof value === "symbol" && value.description === MERGE_KEY, + default: "key", + tag: "tag:yaml.org,2002:merge", + test: /^<<$/, + resolve: () => Object.assign(new Scalar.Scalar(Symbol(MERGE_KEY)), { + addToJSMap: addMergeToJSMap + }), + stringify: () => MERGE_KEY + }; + var isMergeKey = (ctx, key) => (merge.identify(key) || identity.isScalar(key) && (!key.type || key.type === Scalar.Scalar.PLAIN) && merge.identify(key.value)) && ctx?.doc.schema.tags.some((tag) => tag.tag === merge.tag && tag.default); + function addMergeToJSMap(ctx, map, value) { + value = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value; + if (identity.isSeq(value)) + for (const it of value.items) + mergeValue(ctx, map, it); + else if (Array.isArray(value)) + for (const it of value) + mergeValue(ctx, map, it); + else + mergeValue(ctx, map, value); + } + function mergeValue(ctx, map, value) { + const source = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value; + if (!identity.isMap(source)) + throw new Error("Merge sources must be maps or map aliases"); + const srcMap = source.toJSON(null, ctx, Map); + for (const [key, value2] of srcMap) { + if (map instanceof Map) { + if (!map.has(key)) + map.set(key, value2); + } else if (map instanceof Set) { + map.add(key); + } else if (!Object.prototype.hasOwnProperty.call(map, key)) { + Object.defineProperty(map, key, { + value: value2, + writable: true, + enumerable: true, + configurable: true + }); + } + } + return map; + } + exports2.addMergeToJSMap = addMergeToJSMap; + exports2.isMergeKey = isMergeKey; + exports2.merge = merge; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/addPairToJSMap.js +var require_addPairToJSMap = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/addPairToJSMap.js"(exports2) { + "use strict"; + var log = require_log(); + var merge = require_merge(); + var stringify = require_stringify(); + var identity = require_identity(); + var toJS = require_toJS(); + function addPairToJSMap(ctx, map, { key, value }) { + if (identity.isNode(key) && key.addToJSMap) + key.addToJSMap(ctx, map, value); + else if (merge.isMergeKey(ctx, key)) + merge.addMergeToJSMap(ctx, map, value); + else { + const jsKey = toJS.toJS(key, "", ctx); + if (map instanceof Map) { + map.set(jsKey, toJS.toJS(value, jsKey, ctx)); + } else if (map instanceof Set) { + map.add(jsKey); + } else { + const stringKey = stringifyKey(key, jsKey, ctx); + const jsValue = toJS.toJS(value, stringKey, ctx); + if (stringKey in map) + Object.defineProperty(map, stringKey, { + value: jsValue, + writable: true, + enumerable: true, + configurable: true + }); + else + map[stringKey] = jsValue; + } + } + return map; + } + function stringifyKey(key, jsKey, ctx) { + if (jsKey === null) + return ""; + if (typeof jsKey !== "object") + return String(jsKey); + if (identity.isNode(key) && ctx?.doc) { + const strCtx = stringify.createStringifyContext(ctx.doc, {}); + strCtx.anchors = /* @__PURE__ */ new Set(); + for (const node of ctx.anchors.keys()) + strCtx.anchors.add(node.anchor); + strCtx.inFlow = true; + strCtx.inStringifyKey = true; + const strKey = key.toString(strCtx); + if (!ctx.mapKeyWarned) { + let jsonStr = JSON.stringify(strKey); + if (jsonStr.length > 40) + jsonStr = jsonStr.substring(0, 36) + '..."'; + log.warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`); + ctx.mapKeyWarned = true; + } + return strKey; + } + return JSON.stringify(jsKey); + } + exports2.addPairToJSMap = addPairToJSMap; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/Pair.js +var require_Pair = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/Pair.js"(exports2) { + "use strict"; + var createNode = require_createNode(); + var stringifyPair = require_stringifyPair(); + var addPairToJSMap = require_addPairToJSMap(); + var identity = require_identity(); + function createPair(key, value, ctx) { + const k = createNode.createNode(key, void 0, ctx); + const v = createNode.createNode(value, void 0, ctx); + return new Pair(k, v); + } + var Pair = class _Pair { + constructor(key, value = null) { + Object.defineProperty(this, identity.NODE_TYPE, { value: identity.PAIR }); + this.key = key; + this.value = value; + } + clone(schema) { + let { key, value } = this; + if (identity.isNode(key)) + key = key.clone(schema); + if (identity.isNode(value)) + value = value.clone(schema); + return new _Pair(key, value); + } + toJSON(_, ctx) { + const pair = ctx?.mapAsMap ? /* @__PURE__ */ new Map() : {}; + return addPairToJSMap.addPairToJSMap(ctx, pair, this); + } + toString(ctx, onComment, onChompKeep) { + return ctx?.doc ? stringifyPair.stringifyPair(this, ctx, onComment, onChompKeep) : JSON.stringify(this); + } + }; + exports2.Pair = Pair; + exports2.createPair = createPair; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyCollection.js +var require_stringifyCollection = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyCollection.js"(exports2) { + "use strict"; + var identity = require_identity(); + var stringify = require_stringify(); + var stringifyComment = require_stringifyComment(); + function stringifyCollection(collection, ctx, options) { + const flow = ctx.inFlow ?? collection.flow; + const stringify2 = flow ? stringifyFlowCollection : stringifyBlockCollection; + return stringify2(collection, ctx, options); + } + function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) { + const { indent, options: { commentString } } = ctx; + const itemCtx = Object.assign({}, ctx, { indent: itemIndent, type: null }); + let chompKeep = false; + const lines = []; + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + let comment2 = null; + if (identity.isNode(item)) { + if (!chompKeep && item.spaceBefore) + lines.push(""); + addCommentBefore(ctx, lines, item.commentBefore, chompKeep); + if (item.comment) + comment2 = item.comment; + } else if (identity.isPair(item)) { + const ik = identity.isNode(item.key) ? item.key : null; + if (ik) { + if (!chompKeep && ik.spaceBefore) + lines.push(""); + addCommentBefore(ctx, lines, ik.commentBefore, chompKeep); + } + } + chompKeep = false; + let str2 = stringify.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true); + if (comment2) + str2 += stringifyComment.lineComment(str2, itemIndent, commentString(comment2)); + if (chompKeep && comment2) + chompKeep = false; + lines.push(blockItemPrefix + str2); + } + let str; + if (lines.length === 0) { + str = flowChars.start + flowChars.end; + } else { + str = lines[0]; + for (let i = 1; i < lines.length; ++i) { + const line = lines[i]; + str += line ? ` +${indent}${line}` : "\n"; + } + } + if (comment) { + str += "\n" + stringifyComment.indentComment(commentString(comment), indent); + if (onComment) + onComment(); + } else if (chompKeep && onChompKeep) + onChompKeep(); + return str; + } + function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) { + const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx; + itemIndent += indentStep; + const itemCtx = Object.assign({}, ctx, { + indent: itemIndent, + inFlow: true, + type: null + }); + let reqNewline = false; + let linesAtValue = 0; + const lines = []; + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + let comment = null; + if (identity.isNode(item)) { + if (item.spaceBefore) + lines.push(""); + addCommentBefore(ctx, lines, item.commentBefore, false); + if (item.comment) + comment = item.comment; + } else if (identity.isPair(item)) { + const ik = identity.isNode(item.key) ? item.key : null; + if (ik) { + if (ik.spaceBefore) + lines.push(""); + addCommentBefore(ctx, lines, ik.commentBefore, false); + if (ik.comment) + reqNewline = true; + } + const iv = identity.isNode(item.value) ? item.value : null; + if (iv) { + if (iv.comment) + comment = iv.comment; + if (iv.commentBefore) + reqNewline = true; + } else if (item.value == null && ik?.comment) { + comment = ik.comment; + } + } + if (comment) + reqNewline = true; + let str = stringify.stringify(item, itemCtx, () => comment = null); + if (i < items.length - 1) + str += ","; + if (comment) + str += stringifyComment.lineComment(str, itemIndent, commentString(comment)); + if (!reqNewline && (lines.length > linesAtValue || str.includes("\n"))) + reqNewline = true; + lines.push(str); + linesAtValue = lines.length; + } + const { start, end } = flowChars; + if (lines.length === 0) { + return start + end; + } else { + if (!reqNewline) { + const len = lines.reduce((sum, line) => sum + line.length + 2, 2); + reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth; + } + if (reqNewline) { + let str = start; + for (const line of lines) + str += line ? ` +${indentStep}${indent}${line}` : "\n"; + return `${str} +${indent}${end}`; + } else { + return `${start}${fcPadding}${lines.join(" ")}${fcPadding}${end}`; + } + } + } + function addCommentBefore({ indent, options: { commentString } }, lines, comment, chompKeep) { + if (comment && chompKeep) + comment = comment.replace(/^\n+/, ""); + if (comment) { + const ic = stringifyComment.indentComment(commentString(comment), indent); + lines.push(ic.trimStart()); + } + } + exports2.stringifyCollection = stringifyCollection; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/YAMLMap.js +var require_YAMLMap = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/YAMLMap.js"(exports2) { + "use strict"; + var stringifyCollection = require_stringifyCollection(); + var addPairToJSMap = require_addPairToJSMap(); + var Collection = require_Collection(); + var identity = require_identity(); + var Pair = require_Pair(); + var Scalar = require_Scalar(); + function findPair(items, key) { + const k = identity.isScalar(key) ? key.value : key; + for (const it of items) { + if (identity.isPair(it)) { + if (it.key === key || it.key === k) + return it; + if (identity.isScalar(it.key) && it.key.value === k) + return it; + } + } + return void 0; + } + var YAMLMap = class extends Collection.Collection { + static get tagName() { + return "tag:yaml.org,2002:map"; + } + constructor(schema) { + super(identity.MAP, schema); + this.items = []; + } + /** + * A generic collection parsing method that can be extended + * to other node classes that inherit from YAMLMap + */ + static from(schema, obj, ctx) { + const { keepUndefined, replacer } = ctx; + const map = new this(schema); + const add = (key, value) => { + if (typeof replacer === "function") + value = replacer.call(obj, key, value); + else if (Array.isArray(replacer) && !replacer.includes(key)) + return; + if (value !== void 0 || keepUndefined) + map.items.push(Pair.createPair(key, value, ctx)); + }; + if (obj instanceof Map) { + for (const [key, value] of obj) + add(key, value); + } else if (obj && typeof obj === "object") { + for (const key of Object.keys(obj)) + add(key, obj[key]); + } + if (typeof schema.sortMapEntries === "function") { + map.items.sort(schema.sortMapEntries); + } + return map; + } + /** + * Adds a value to the collection. + * + * @param overwrite - If not set `true`, using a key that is already in the + * collection will throw. Otherwise, overwrites the previous value. + */ + add(pair, overwrite) { + let _pair; + if (identity.isPair(pair)) + _pair = pair; + else if (!pair || typeof pair !== "object" || !("key" in pair)) { + _pair = new Pair.Pair(pair, pair?.value); + } else + _pair = new Pair.Pair(pair.key, pair.value); + const prev = findPair(this.items, _pair.key); + const sortEntries = this.schema?.sortMapEntries; + if (prev) { + if (!overwrite) + throw new Error(`Key ${_pair.key} already set`); + if (identity.isScalar(prev.value) && Scalar.isScalarValue(_pair.value)) + prev.value.value = _pair.value; + else + prev.value = _pair.value; + } else if (sortEntries) { + const i = this.items.findIndex((item) => sortEntries(_pair, item) < 0); + if (i === -1) + this.items.push(_pair); + else + this.items.splice(i, 0, _pair); + } else { + this.items.push(_pair); + } + } + delete(key) { + const it = findPair(this.items, key); + if (!it) + return false; + const del = this.items.splice(this.items.indexOf(it), 1); + return del.length > 0; + } + get(key, keepScalar) { + const it = findPair(this.items, key); + const node = it?.value; + return (!keepScalar && identity.isScalar(node) ? node.value : node) ?? void 0; + } + has(key) { + return !!findPair(this.items, key); + } + set(key, value) { + this.add(new Pair.Pair(key, value), true); + } + /** + * @param ctx - Conversion context, originally set in Document#toJS() + * @param {Class} Type - If set, forces the returned collection type + * @returns Instance of Type, Map, or Object + */ + toJSON(_, ctx, Type) { + const map = Type ? new Type() : ctx?.mapAsMap ? /* @__PURE__ */ new Map() : {}; + if (ctx?.onCreate) + ctx.onCreate(map); + for (const item of this.items) + addPairToJSMap.addPairToJSMap(ctx, map, item); + return map; + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + for (const item of this.items) { + if (!identity.isPair(item)) + throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`); + } + if (!ctx.allNullValues && this.hasAllNullValues(false)) + ctx = Object.assign({}, ctx, { allNullValues: true }); + return stringifyCollection.stringifyCollection(this, ctx, { + blockItemPrefix: "", + flowChars: { start: "{", end: "}" }, + itemIndent: ctx.indent || "", + onChompKeep, + onComment + }); + } + }; + exports2.YAMLMap = YAMLMap; + exports2.findPair = findPair; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/common/map.js +var require_map = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/common/map.js"(exports2) { + "use strict"; + var identity = require_identity(); + var YAMLMap = require_YAMLMap(); + var map = { + collection: "map", + default: true, + nodeClass: YAMLMap.YAMLMap, + tag: "tag:yaml.org,2002:map", + resolve(map2, onError) { + if (!identity.isMap(map2)) + onError("Expected a mapping for this tag"); + return map2; + }, + createNode: (schema, obj, ctx) => YAMLMap.YAMLMap.from(schema, obj, ctx) + }; + exports2.map = map; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/YAMLSeq.js +var require_YAMLSeq = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/YAMLSeq.js"(exports2) { + "use strict"; + var createNode = require_createNode(); + var stringifyCollection = require_stringifyCollection(); + var Collection = require_Collection(); + var identity = require_identity(); + var Scalar = require_Scalar(); + var toJS = require_toJS(); + var YAMLSeq = class extends Collection.Collection { + static get tagName() { + return "tag:yaml.org,2002:seq"; + } + constructor(schema) { + super(identity.SEQ, schema); + this.items = []; + } + add(value) { + this.items.push(value); + } + /** + * Removes a value from the collection. + * + * `key` must contain a representation of an integer for this to succeed. + * It may be wrapped in a `Scalar`. + * + * @returns `true` if the item was found and removed. + */ + delete(key) { + const idx = asItemIndex(key); + if (typeof idx !== "number") + return false; + const del = this.items.splice(idx, 1); + return del.length > 0; + } + get(key, keepScalar) { + const idx = asItemIndex(key); + if (typeof idx !== "number") + return void 0; + const it = this.items[idx]; + return !keepScalar && identity.isScalar(it) ? it.value : it; + } + /** + * Checks if the collection includes a value with the key `key`. + * + * `key` must contain a representation of an integer for this to succeed. + * It may be wrapped in a `Scalar`. + */ + has(key) { + const idx = asItemIndex(key); + return typeof idx === "number" && idx < this.items.length; + } + /** + * Sets a value in this collection. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + * + * If `key` does not contain a representation of an integer, this will throw. + * It may be wrapped in a `Scalar`. + */ + set(key, value) { + const idx = asItemIndex(key); + if (typeof idx !== "number") + throw new Error(`Expected a valid index, not ${key}.`); + const prev = this.items[idx]; + if (identity.isScalar(prev) && Scalar.isScalarValue(value)) + prev.value = value; + else + this.items[idx] = value; + } + toJSON(_, ctx) { + const seq = []; + if (ctx?.onCreate) + ctx.onCreate(seq); + let i = 0; + for (const item of this.items) + seq.push(toJS.toJS(item, String(i++), ctx)); + return seq; + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + return stringifyCollection.stringifyCollection(this, ctx, { + blockItemPrefix: "- ", + flowChars: { start: "[", end: "]" }, + itemIndent: (ctx.indent || "") + " ", + onChompKeep, + onComment + }); + } + static from(schema, obj, ctx) { + const { replacer } = ctx; + const seq = new this(schema); + if (obj && Symbol.iterator in Object(obj)) { + let i = 0; + for (let it of obj) { + if (typeof replacer === "function") { + const key = obj instanceof Set ? it : String(i++); + it = replacer.call(obj, key, it); + } + seq.items.push(createNode.createNode(it, void 0, ctx)); + } + } + return seq; + } + }; + function asItemIndex(key) { + let idx = identity.isScalar(key) ? key.value : key; + if (idx && typeof idx === "string") + idx = Number(idx); + return typeof idx === "number" && Number.isInteger(idx) && idx >= 0 ? idx : null; + } + exports2.YAMLSeq = YAMLSeq; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/common/seq.js +var require_seq = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/common/seq.js"(exports2) { + "use strict"; + var identity = require_identity(); + var YAMLSeq = require_YAMLSeq(); + var seq = { + collection: "seq", + default: true, + nodeClass: YAMLSeq.YAMLSeq, + tag: "tag:yaml.org,2002:seq", + resolve(seq2, onError) { + if (!identity.isSeq(seq2)) + onError("Expected a sequence for this tag"); + return seq2; + }, + createNode: (schema, obj, ctx) => YAMLSeq.YAMLSeq.from(schema, obj, ctx) + }; + exports2.seq = seq; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/common/string.js +var require_string = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/common/string.js"(exports2) { + "use strict"; + var stringifyString = require_stringifyString(); + var string = { + identify: (value) => typeof value === "string", + default: true, + tag: "tag:yaml.org,2002:str", + resolve: (str) => str, + stringify(item, ctx, onComment, onChompKeep) { + ctx = Object.assign({ actualString: true }, ctx); + return stringifyString.stringifyString(item, ctx, onComment, onChompKeep); + } + }; + exports2.string = string; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/common/null.js +var require_null = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/common/null.js"(exports2) { + "use strict"; + var Scalar = require_Scalar(); + var nullTag = { + identify: (value) => value == null, + createNode: () => new Scalar.Scalar(null), + default: true, + tag: "tag:yaml.org,2002:null", + test: /^(?:~|[Nn]ull|NULL)?$/, + resolve: () => new Scalar.Scalar(null), + stringify: ({ source }, ctx) => typeof source === "string" && nullTag.test.test(source) ? source : ctx.options.nullStr + }; + exports2.nullTag = nullTag; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/core/bool.js +var require_bool = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/core/bool.js"(exports2) { + "use strict"; + var Scalar = require_Scalar(); + var boolTag = { + identify: (value) => typeof value === "boolean", + default: true, + tag: "tag:yaml.org,2002:bool", + test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/, + resolve: (str) => new Scalar.Scalar(str[0] === "t" || str[0] === "T"), + stringify({ source, value }, ctx) { + if (source && boolTag.test.test(source)) { + const sv = source[0] === "t" || source[0] === "T"; + if (value === sv) + return source; + } + return value ? ctx.options.trueStr : ctx.options.falseStr; + } + }; + exports2.boolTag = boolTag; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyNumber.js +var require_stringifyNumber = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyNumber.js"(exports2) { + "use strict"; + function stringifyNumber({ format, minFractionDigits, tag, value }) { + if (typeof value === "bigint") + return String(value); + const num = typeof value === "number" ? value : Number(value); + if (!isFinite(num)) + return isNaN(num) ? ".nan" : num < 0 ? "-.inf" : ".inf"; + let n = Object.is(value, -0) ? "-0" : JSON.stringify(value); + if (!format && minFractionDigits && (!tag || tag === "tag:yaml.org,2002:float") && /^\d/.test(n)) { + let i = n.indexOf("."); + if (i < 0) { + i = n.length; + n += "."; + } + let d = minFractionDigits - (n.length - i - 1); + while (d-- > 0) + n += "0"; + } + return n; + } + exports2.stringifyNumber = stringifyNumber; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/core/float.js +var require_float = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/core/float.js"(exports2) { + "use strict"; + var Scalar = require_Scalar(); + var stringifyNumber = require_stringifyNumber(); + var floatNaN = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, + resolve: (str) => str.slice(-3).toLowerCase() === "nan" ? NaN : str[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY, + stringify: stringifyNumber.stringifyNumber + }; + var floatExp = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + format: "EXP", + test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/, + resolve: (str) => parseFloat(str), + stringify(node) { + const num = Number(node.value); + return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node); + } + }; + var float = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/, + resolve(str) { + const node = new Scalar.Scalar(parseFloat(str)); + const dot = str.indexOf("."); + if (dot !== -1 && str[str.length - 1] === "0") + node.minFractionDigits = str.length - dot - 1; + return node; + }, + stringify: stringifyNumber.stringifyNumber + }; + exports2.float = float; + exports2.floatExp = floatExp; + exports2.floatNaN = floatNaN; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/core/int.js +var require_int = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/core/int.js"(exports2) { + "use strict"; + var stringifyNumber = require_stringifyNumber(); + var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value); + var intResolve = (str, offset, radix, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str.substring(offset), radix); + function intStringify(node, radix, prefix) { + const { value } = node; + if (intIdentify(value) && value >= 0) + return prefix + value.toString(radix); + return stringifyNumber.stringifyNumber(node); + } + var intOct = { + identify: (value) => intIdentify(value) && value >= 0, + default: true, + tag: "tag:yaml.org,2002:int", + format: "OCT", + test: /^0o[0-7]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 8, opt), + stringify: (node) => intStringify(node, 8, "0o") + }; + var int = { + identify: intIdentify, + default: true, + tag: "tag:yaml.org,2002:int", + test: /^[-+]?[0-9]+$/, + resolve: (str, _onError, opt) => intResolve(str, 0, 10, opt), + stringify: stringifyNumber.stringifyNumber + }; + var intHex = { + identify: (value) => intIdentify(value) && value >= 0, + default: true, + tag: "tag:yaml.org,2002:int", + format: "HEX", + test: /^0x[0-9a-fA-F]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt), + stringify: (node) => intStringify(node, 16, "0x") + }; + exports2.int = int; + exports2.intHex = intHex; + exports2.intOct = intOct; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/core/schema.js +var require_schema = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/core/schema.js"(exports2) { + "use strict"; + var map = require_map(); + var _null = require_null(); + var seq = require_seq(); + var string = require_string(); + var bool = require_bool(); + var float = require_float(); + var int = require_int(); + var schema = [ + map.map, + seq.seq, + string.string, + _null.nullTag, + bool.boolTag, + int.intOct, + int.int, + int.intHex, + float.floatNaN, + float.floatExp, + float.float + ]; + exports2.schema = schema; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/json/schema.js +var require_schema2 = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/json/schema.js"(exports2) { + "use strict"; + var Scalar = require_Scalar(); + var map = require_map(); + var seq = require_seq(); + function intIdentify(value) { + return typeof value === "bigint" || Number.isInteger(value); + } + var stringifyJSON = ({ value }) => JSON.stringify(value); + var jsonScalars = [ + { + identify: (value) => typeof value === "string", + default: true, + tag: "tag:yaml.org,2002:str", + resolve: (str) => str, + stringify: stringifyJSON + }, + { + identify: (value) => value == null, + createNode: () => new Scalar.Scalar(null), + default: true, + tag: "tag:yaml.org,2002:null", + test: /^null$/, + resolve: () => null, + stringify: stringifyJSON + }, + { + identify: (value) => typeof value === "boolean", + default: true, + tag: "tag:yaml.org,2002:bool", + test: /^true$|^false$/, + resolve: (str) => str === "true", + stringify: stringifyJSON + }, + { + identify: intIdentify, + default: true, + tag: "tag:yaml.org,2002:int", + test: /^-?(?:0|[1-9][0-9]*)$/, + resolve: (str, _onError, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str, 10), + stringify: ({ value }) => intIdentify(value) ? value.toString() : JSON.stringify(value) + }, + { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/, + resolve: (str) => parseFloat(str), + stringify: stringifyJSON + } + ]; + var jsonError = { + default: true, + tag: "", + test: /^/, + resolve(str, onError) { + onError(`Unresolved plain scalar ${JSON.stringify(str)}`); + return str; + } + }; + var schema = [map.map, seq.seq].concat(jsonScalars, jsonError); + exports2.schema = schema; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/binary.js +var require_binary = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/binary.js"(exports2) { + "use strict"; + var node_buffer = require("buffer"); + var Scalar = require_Scalar(); + var stringifyString = require_stringifyString(); + var binary = { + identify: (value) => value instanceof Uint8Array, + // Buffer inherits from Uint8Array + default: false, + tag: "tag:yaml.org,2002:binary", + /** + * Returns a Buffer in node and an Uint8Array in browsers + * + * To use the resulting buffer as an image, you'll want to do something like: + * + * const blob = new Blob([buffer], { type: 'image/jpeg' }) + * document.querySelector('#photo').src = URL.createObjectURL(blob) + */ + resolve(src, onError) { + if (typeof node_buffer.Buffer === "function") { + return node_buffer.Buffer.from(src, "base64"); + } else if (typeof atob === "function") { + const str = atob(src.replace(/[\n\r]/g, "")); + const buffer = new Uint8Array(str.length); + for (let i = 0; i < str.length; ++i) + buffer[i] = str.charCodeAt(i); + return buffer; + } else { + onError("This environment does not support reading binary tags; either Buffer or atob is required"); + return src; + } + }, + stringify({ comment, type, value }, ctx, onComment, onChompKeep) { + if (!value) + return ""; + const buf = value; + let str; + if (typeof node_buffer.Buffer === "function") { + str = buf instanceof node_buffer.Buffer ? buf.toString("base64") : node_buffer.Buffer.from(buf.buffer).toString("base64"); + } else if (typeof btoa === "function") { + let s = ""; + for (let i = 0; i < buf.length; ++i) + s += String.fromCharCode(buf[i]); + str = btoa(s); + } else { + throw new Error("This environment does not support writing binary tags; either Buffer or btoa is required"); + } + type ?? (type = Scalar.Scalar.BLOCK_LITERAL); + if (type !== Scalar.Scalar.QUOTE_DOUBLE) { + const lineWidth = Math.max(ctx.options.lineWidth - ctx.indent.length, ctx.options.minContentWidth); + const n = Math.ceil(str.length / lineWidth); + const lines = new Array(n); + for (let i = 0, o = 0; i < n; ++i, o += lineWidth) { + lines[i] = str.substr(o, lineWidth); + } + str = lines.join(type === Scalar.Scalar.BLOCK_LITERAL ? "\n" : " "); + } + return stringifyString.stringifyString({ comment, type, value: str }, ctx, onComment, onChompKeep); + } + }; + exports2.binary = binary; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +var require_pairs = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/pairs.js"(exports2) { + "use strict"; + var identity = require_identity(); + var Pair = require_Pair(); + var Scalar = require_Scalar(); + var YAMLSeq = require_YAMLSeq(); + function resolvePairs(seq, onError) { + if (identity.isSeq(seq)) { + for (let i = 0; i < seq.items.length; ++i) { + let item = seq.items[i]; + if (identity.isPair(item)) + continue; + else if (identity.isMap(item)) { + if (item.items.length > 1) + onError("Each pair must have its own sequence indicator"); + const pair = item.items[0] || new Pair.Pair(new Scalar.Scalar(null)); + if (item.commentBefore) + pair.key.commentBefore = pair.key.commentBefore ? `${item.commentBefore} +${pair.key.commentBefore}` : item.commentBefore; + if (item.comment) { + const cn = pair.value ?? pair.key; + cn.comment = cn.comment ? `${item.comment} +${cn.comment}` : item.comment; + } + item = pair; + } + seq.items[i] = identity.isPair(item) ? item : new Pair.Pair(item); + } + } else + onError("Expected a sequence for this tag"); + return seq; + } + function createPairs(schema, iterable, ctx) { + const { replacer } = ctx; + const pairs2 = new YAMLSeq.YAMLSeq(schema); + pairs2.tag = "tag:yaml.org,2002:pairs"; + let i = 0; + if (iterable && Symbol.iterator in Object(iterable)) + for (let it of iterable) { + if (typeof replacer === "function") + it = replacer.call(iterable, String(i++), it); + let key, value; + if (Array.isArray(it)) { + if (it.length === 2) { + key = it[0]; + value = it[1]; + } else + throw new TypeError(`Expected [key, value] tuple: ${it}`); + } else if (it && it instanceof Object) { + const keys = Object.keys(it); + if (keys.length === 1) { + key = keys[0]; + value = it[key]; + } else { + throw new TypeError(`Expected tuple with one key, not ${keys.length} keys`); + } + } else { + key = it; + } + pairs2.items.push(Pair.createPair(key, value, ctx)); + } + return pairs2; + } + var pairs = { + collection: "seq", + default: false, + tag: "tag:yaml.org,2002:pairs", + resolve: resolvePairs, + createNode: createPairs + }; + exports2.createPairs = createPairs; + exports2.pairs = pairs; + exports2.resolvePairs = resolvePairs; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/omap.js +var require_omap = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/omap.js"(exports2) { + "use strict"; + var identity = require_identity(); + var toJS = require_toJS(); + var YAMLMap = require_YAMLMap(); + var YAMLSeq = require_YAMLSeq(); + var pairs = require_pairs(); + var YAMLOMap = class _YAMLOMap extends YAMLSeq.YAMLSeq { + constructor() { + super(); + this.add = YAMLMap.YAMLMap.prototype.add.bind(this); + this.delete = YAMLMap.YAMLMap.prototype.delete.bind(this); + this.get = YAMLMap.YAMLMap.prototype.get.bind(this); + this.has = YAMLMap.YAMLMap.prototype.has.bind(this); + this.set = YAMLMap.YAMLMap.prototype.set.bind(this); + this.tag = _YAMLOMap.tag; + } + /** + * If `ctx` is given, the return type is actually `Map`, + * but TypeScript won't allow widening the signature of a child method. + */ + toJSON(_, ctx) { + if (!ctx) + return super.toJSON(_); + const map = /* @__PURE__ */ new Map(); + if (ctx?.onCreate) + ctx.onCreate(map); + for (const pair of this.items) { + let key, value; + if (identity.isPair(pair)) { + key = toJS.toJS(pair.key, "", ctx); + value = toJS.toJS(pair.value, key, ctx); + } else { + key = toJS.toJS(pair, "", ctx); + } + if (map.has(key)) + throw new Error("Ordered maps must not include duplicate keys"); + map.set(key, value); + } + return map; + } + static from(schema, iterable, ctx) { + const pairs$1 = pairs.createPairs(schema, iterable, ctx); + const omap2 = new this(); + omap2.items = pairs$1.items; + return omap2; + } + }; + YAMLOMap.tag = "tag:yaml.org,2002:omap"; + var omap = { + collection: "seq", + identify: (value) => value instanceof Map, + nodeClass: YAMLOMap, + default: false, + tag: "tag:yaml.org,2002:omap", + resolve(seq, onError) { + const pairs$1 = pairs.resolvePairs(seq, onError); + const seenKeys = []; + for (const { key } of pairs$1.items) { + if (identity.isScalar(key)) { + if (seenKeys.includes(key.value)) { + onError(`Ordered maps must not include duplicate keys: ${key.value}`); + } else { + seenKeys.push(key.value); + } + } + } + return Object.assign(new YAMLOMap(), pairs$1); + }, + createNode: (schema, iterable, ctx) => YAMLOMap.from(schema, iterable, ctx) + }; + exports2.YAMLOMap = YAMLOMap; + exports2.omap = omap; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/bool.js +var require_bool2 = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/bool.js"(exports2) { + "use strict"; + var Scalar = require_Scalar(); + function boolStringify({ value, source }, ctx) { + const boolObj = value ? trueTag : falseTag; + if (source && boolObj.test.test(source)) + return source; + return value ? ctx.options.trueStr : ctx.options.falseStr; + } + var trueTag = { + identify: (value) => value === true, + default: true, + tag: "tag:yaml.org,2002:bool", + test: /^(?:Y|y|[Yy]es|YES|[Tt]rue|TRUE|[Oo]n|ON)$/, + resolve: () => new Scalar.Scalar(true), + stringify: boolStringify + }; + var falseTag = { + identify: (value) => value === false, + default: true, + tag: "tag:yaml.org,2002:bool", + test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/, + resolve: () => new Scalar.Scalar(false), + stringify: boolStringify + }; + exports2.falseTag = falseTag; + exports2.trueTag = trueTag; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/float.js +var require_float2 = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/float.js"(exports2) { + "use strict"; + var Scalar = require_Scalar(); + var stringifyNumber = require_stringifyNumber(); + var floatNaN = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, + resolve: (str) => str.slice(-3).toLowerCase() === "nan" ? NaN : str[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY, + stringify: stringifyNumber.stringifyNumber + }; + var floatExp = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + format: "EXP", + test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/, + resolve: (str) => parseFloat(str.replace(/_/g, "")), + stringify(node) { + const num = Number(node.value); + return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node); + } + }; + var float = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/, + resolve(str) { + const node = new Scalar.Scalar(parseFloat(str.replace(/_/g, ""))); + const dot = str.indexOf("."); + if (dot !== -1) { + const f = str.substring(dot + 1).replace(/_/g, ""); + if (f[f.length - 1] === "0") + node.minFractionDigits = f.length; + } + return node; + }, + stringify: stringifyNumber.stringifyNumber + }; + exports2.float = float; + exports2.floatExp = floatExp; + exports2.floatNaN = floatNaN; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/int.js +var require_int2 = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/int.js"(exports2) { + "use strict"; + var stringifyNumber = require_stringifyNumber(); + var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value); + function intResolve(str, offset, radix, { intAsBigInt }) { + const sign = str[0]; + if (sign === "-" || sign === "+") + offset += 1; + str = str.substring(offset).replace(/_/g, ""); + if (intAsBigInt) { + switch (radix) { + case 2: + str = `0b${str}`; + break; + case 8: + str = `0o${str}`; + break; + case 16: + str = `0x${str}`; + break; + } + const n2 = BigInt(str); + return sign === "-" ? BigInt(-1) * n2 : n2; + } + const n = parseInt(str, radix); + return sign === "-" ? -1 * n : n; + } + function intStringify(node, radix, prefix) { + const { value } = node; + if (intIdentify(value)) { + const str = value.toString(radix); + return value < 0 ? "-" + prefix + str.substr(1) : prefix + str; + } + return stringifyNumber.stringifyNumber(node); + } + var intBin = { + identify: intIdentify, + default: true, + tag: "tag:yaml.org,2002:int", + format: "BIN", + test: /^[-+]?0b[0-1_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 2, opt), + stringify: (node) => intStringify(node, 2, "0b") + }; + var intOct = { + identify: intIdentify, + default: true, + tag: "tag:yaml.org,2002:int", + format: "OCT", + test: /^[-+]?0[0-7_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 1, 8, opt), + stringify: (node) => intStringify(node, 8, "0") + }; + var int = { + identify: intIdentify, + default: true, + tag: "tag:yaml.org,2002:int", + test: /^[-+]?[0-9][0-9_]*$/, + resolve: (str, _onError, opt) => intResolve(str, 0, 10, opt), + stringify: stringifyNumber.stringifyNumber + }; + var intHex = { + identify: intIdentify, + default: true, + tag: "tag:yaml.org,2002:int", + format: "HEX", + test: /^[-+]?0x[0-9a-fA-F_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt), + stringify: (node) => intStringify(node, 16, "0x") + }; + exports2.int = int; + exports2.intBin = intBin; + exports2.intHex = intHex; + exports2.intOct = intOct; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/set.js +var require_set = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/set.js"(exports2) { + "use strict"; + var identity = require_identity(); + var Pair = require_Pair(); + var YAMLMap = require_YAMLMap(); + var YAMLSet = class _YAMLSet extends YAMLMap.YAMLMap { + constructor(schema) { + super(schema); + this.tag = _YAMLSet.tag; + } + add(key) { + let pair; + if (identity.isPair(key)) + pair = key; + else if (key && typeof key === "object" && "key" in key && "value" in key && key.value === null) + pair = new Pair.Pair(key.key, null); + else + pair = new Pair.Pair(key, null); + const prev = YAMLMap.findPair(this.items, pair.key); + if (!prev) + this.items.push(pair); + } + /** + * If `keepPair` is `true`, returns the Pair matching `key`. + * Otherwise, returns the value of that Pair's key. + */ + get(key, keepPair) { + const pair = YAMLMap.findPair(this.items, key); + return !keepPair && identity.isPair(pair) ? identity.isScalar(pair.key) ? pair.key.value : pair.key : pair; + } + set(key, value) { + if (typeof value !== "boolean") + throw new Error(`Expected boolean value for set(key, value) in a YAML set, not ${typeof value}`); + const prev = YAMLMap.findPair(this.items, key); + if (prev && !value) { + this.items.splice(this.items.indexOf(prev), 1); + } else if (!prev && value) { + this.items.push(new Pair.Pair(key)); + } + } + toJSON(_, ctx) { + return super.toJSON(_, ctx, Set); + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) + return JSON.stringify(this); + if (this.hasAllNullValues(true)) + return super.toString(Object.assign({}, ctx, { allNullValues: true }), onComment, onChompKeep); + else + throw new Error("Set items must all have null values"); + } + static from(schema, iterable, ctx) { + const { replacer } = ctx; + const set2 = new this(schema); + if (iterable && Symbol.iterator in Object(iterable)) + for (let value of iterable) { + if (typeof replacer === "function") + value = replacer.call(iterable, value, value); + set2.items.push(Pair.createPair(value, null, ctx)); + } + return set2; + } + }; + YAMLSet.tag = "tag:yaml.org,2002:set"; + var set = { + collection: "map", + identify: (value) => value instanceof Set, + nodeClass: YAMLSet, + default: false, + tag: "tag:yaml.org,2002:set", + createNode: (schema, iterable, ctx) => YAMLSet.from(schema, iterable, ctx), + resolve(map, onError) { + if (identity.isMap(map)) { + if (map.hasAllNullValues(true)) + return Object.assign(new YAMLSet(), map); + else + onError("Set items must all have null values"); + } else + onError("Expected a mapping for this tag"); + return map; + } + }; + exports2.YAMLSet = YAMLSet; + exports2.set = set; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +var require_timestamp = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js"(exports2) { + "use strict"; + var stringifyNumber = require_stringifyNumber(); + function parseSexagesimal(str, asBigInt) { + const sign = str[0]; + const parts = sign === "-" || sign === "+" ? str.substring(1) : str; + const num = (n) => asBigInt ? BigInt(n) : Number(n); + const res = parts.replace(/_/g, "").split(":").reduce((res2, p) => res2 * num(60) + num(p), num(0)); + return sign === "-" ? num(-1) * res : res; + } + function stringifySexagesimal(node) { + let { value } = node; + let num = (n) => n; + if (typeof value === "bigint") + num = (n) => BigInt(n); + else if (isNaN(value) || !isFinite(value)) + return stringifyNumber.stringifyNumber(node); + let sign = ""; + if (value < 0) { + sign = "-"; + value *= num(-1); + } + const _60 = num(60); + const parts = [value % _60]; + if (value < 60) { + parts.unshift(0); + } else { + value = (value - parts[0]) / _60; + parts.unshift(value % _60); + if (value >= 60) { + value = (value - parts[0]) / _60; + parts.unshift(value); + } + } + return sign + parts.map((n) => String(n).padStart(2, "0")).join(":").replace(/000000\d*$/, ""); + } + var intTime = { + identify: (value) => typeof value === "bigint" || Number.isInteger(value), + default: true, + tag: "tag:yaml.org,2002:int", + format: "TIME", + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/, + resolve: (str, _onError, { intAsBigInt }) => parseSexagesimal(str, intAsBigInt), + stringify: stringifySexagesimal + }; + var floatTime = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + format: "TIME", + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/, + resolve: (str) => parseSexagesimal(str, false), + stringify: stringifySexagesimal + }; + var timestamp = { + identify: (value) => value instanceof Date, + default: true, + tag: "tag:yaml.org,2002:timestamp", + // If the time zone is omitted, the timestamp is assumed to be specified in UTC. The time part + // may be omitted altogether, resulting in a date format. In such a case, the time part is + // assumed to be 00:00:00Z (start of day, UTC). + test: RegExp("^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})(?:(?:t|T|[ \\t]+)([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?)?$"), + resolve(str) { + const match = str.match(timestamp.test); + if (!match) + throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd"); + const [, year, month, day, hour, minute, second] = match.map(Number); + const millisec = match[7] ? Number((match[7] + "00").substr(1, 3)) : 0; + let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec); + const tz = match[8]; + if (tz && tz !== "Z") { + let d = parseSexagesimal(tz, false); + if (Math.abs(d) < 30) + d *= 60; + date -= 6e4 * d; + } + return new Date(date); + }, + stringify: ({ value }) => value?.toISOString().replace(/(T00:00:00)?\.000Z$/, "") ?? "" + }; + exports2.floatTime = floatTime; + exports2.intTime = intTime; + exports2.timestamp = timestamp; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/schema.js +var require_schema3 = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/schema.js"(exports2) { + "use strict"; + var map = require_map(); + var _null = require_null(); + var seq = require_seq(); + var string = require_string(); + var binary = require_binary(); + var bool = require_bool2(); + var float = require_float2(); + var int = require_int2(); + var merge = require_merge(); + var omap = require_omap(); + var pairs = require_pairs(); + var set = require_set(); + var timestamp = require_timestamp(); + var schema = [ + map.map, + seq.seq, + string.string, + _null.nullTag, + bool.trueTag, + bool.falseTag, + int.intBin, + int.intOct, + int.int, + int.intHex, + float.floatNaN, + float.floatExp, + float.float, + binary.binary, + merge.merge, + omap.omap, + pairs.pairs, + set.set, + timestamp.intTime, + timestamp.floatTime, + timestamp.timestamp + ]; + exports2.schema = schema; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/tags.js +var require_tags = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/tags.js"(exports2) { + "use strict"; + var map = require_map(); + var _null = require_null(); + var seq = require_seq(); + var string = require_string(); + var bool = require_bool(); + var float = require_float(); + var int = require_int(); + var schema = require_schema(); + var schema$1 = require_schema2(); + var binary = require_binary(); + var merge = require_merge(); + var omap = require_omap(); + var pairs = require_pairs(); + var schema$2 = require_schema3(); + var set = require_set(); + var timestamp = require_timestamp(); + var schemas = /* @__PURE__ */ new Map([ + ["core", schema.schema], + ["failsafe", [map.map, seq.seq, string.string]], + ["json", schema$1.schema], + ["yaml11", schema$2.schema], + ["yaml-1.1", schema$2.schema] + ]); + var tagsByName = { + binary: binary.binary, + bool: bool.boolTag, + float: float.float, + floatExp: float.floatExp, + floatNaN: float.floatNaN, + floatTime: timestamp.floatTime, + int: int.int, + intHex: int.intHex, + intOct: int.intOct, + intTime: timestamp.intTime, + map: map.map, + merge: merge.merge, + null: _null.nullTag, + omap: omap.omap, + pairs: pairs.pairs, + seq: seq.seq, + set: set.set, + timestamp: timestamp.timestamp + }; + var coreKnownTags = { + "tag:yaml.org,2002:binary": binary.binary, + "tag:yaml.org,2002:merge": merge.merge, + "tag:yaml.org,2002:omap": omap.omap, + "tag:yaml.org,2002:pairs": pairs.pairs, + "tag:yaml.org,2002:set": set.set, + "tag:yaml.org,2002:timestamp": timestamp.timestamp + }; + function getTags(customTags, schemaName, addMergeTag) { + const schemaTags = schemas.get(schemaName); + if (schemaTags && !customTags) { + return addMergeTag && !schemaTags.includes(merge.merge) ? schemaTags.concat(merge.merge) : schemaTags.slice(); + } + let tags = schemaTags; + if (!tags) { + if (Array.isArray(customTags)) + tags = []; + else { + const keys = Array.from(schemas.keys()).filter((key) => key !== "yaml11").map((key) => JSON.stringify(key)).join(", "); + throw new Error(`Unknown schema "${schemaName}"; use one of ${keys} or define customTags array`); + } + } + if (Array.isArray(customTags)) { + for (const tag of customTags) + tags = tags.concat(tag); + } else if (typeof customTags === "function") { + tags = customTags(tags.slice()); + } + if (addMergeTag) + tags = tags.concat(merge.merge); + return tags.reduce((tags2, tag) => { + const tagObj = typeof tag === "string" ? tagsByName[tag] : tag; + if (!tagObj) { + const tagName = JSON.stringify(tag); + const keys = Object.keys(tagsByName).map((key) => JSON.stringify(key)).join(", "); + throw new Error(`Unknown custom tag ${tagName}; use one of ${keys}`); + } + if (!tags2.includes(tagObj)) + tags2.push(tagObj); + return tags2; + }, []); + } + exports2.coreKnownTags = coreKnownTags; + exports2.getTags = getTags; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/Schema.js +var require_Schema = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/schema/Schema.js"(exports2) { + "use strict"; + var identity = require_identity(); + var map = require_map(); + var seq = require_seq(); + var string = require_string(); + var tags = require_tags(); + var sortMapEntriesByKey = (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0; + var Schema = class _Schema { + constructor({ compat, customTags, merge, resolveKnownTags, schema, sortMapEntries, toStringDefaults }) { + this.compat = Array.isArray(compat) ? tags.getTags(compat, "compat") : compat ? tags.getTags(null, compat) : null; + this.name = typeof schema === "string" && schema || "core"; + this.knownTags = resolveKnownTags ? tags.coreKnownTags : {}; + this.tags = tags.getTags(customTags, this.name, merge); + this.toStringOptions = toStringDefaults ?? null; + Object.defineProperty(this, identity.MAP, { value: map.map }); + Object.defineProperty(this, identity.SCALAR, { value: string.string }); + Object.defineProperty(this, identity.SEQ, { value: seq.seq }); + this.sortMapEntries = typeof sortMapEntries === "function" ? sortMapEntries : sortMapEntries === true ? sortMapEntriesByKey : null; + } + clone() { + const copy = Object.create(_Schema.prototype, Object.getOwnPropertyDescriptors(this)); + copy.tags = this.tags.slice(); + return copy; + } + }; + exports2.Schema = Schema; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyDocument.js +var require_stringifyDocument = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyDocument.js"(exports2) { + "use strict"; + var identity = require_identity(); + var stringify = require_stringify(); + var stringifyComment = require_stringifyComment(); + function stringifyDocument(doc, options) { + const lines = []; + let hasDirectives = options.directives === true; + if (options.directives !== false && doc.directives) { + const dir = doc.directives.toString(doc); + if (dir) { + lines.push(dir); + hasDirectives = true; + } else if (doc.directives.docStart) + hasDirectives = true; + } + if (hasDirectives) + lines.push("---"); + const ctx = stringify.createStringifyContext(doc, options); + const { commentString } = ctx.options; + if (doc.commentBefore) { + if (lines.length !== 1) + lines.unshift(""); + const cs = commentString(doc.commentBefore); + lines.unshift(stringifyComment.indentComment(cs, "")); + } + let chompKeep = false; + let contentComment = null; + if (doc.contents) { + if (identity.isNode(doc.contents)) { + if (doc.contents.spaceBefore && hasDirectives) + lines.push(""); + if (doc.contents.commentBefore) { + const cs = commentString(doc.contents.commentBefore); + lines.push(stringifyComment.indentComment(cs, "")); + } + ctx.forceBlockIndent = !!doc.comment; + contentComment = doc.contents.comment; + } + const onChompKeep = contentComment ? void 0 : () => chompKeep = true; + let body = stringify.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep); + if (contentComment) + body += stringifyComment.lineComment(body, "", commentString(contentComment)); + if ((body[0] === "|" || body[0] === ">") && lines[lines.length - 1] === "---") { + lines[lines.length - 1] = `--- ${body}`; + } else + lines.push(body); + } else { + lines.push(stringify.stringify(doc.contents, ctx)); + } + if (doc.directives?.docEnd) { + if (doc.comment) { + const cs = commentString(doc.comment); + if (cs.includes("\n")) { + lines.push("..."); + lines.push(stringifyComment.indentComment(cs, "")); + } else { + lines.push(`... ${cs}`); + } + } else { + lines.push("..."); + } + } else { + let dc = doc.comment; + if (dc && chompKeep) + dc = dc.replace(/^\n+/, ""); + if (dc) { + if ((!chompKeep || contentComment) && lines[lines.length - 1] !== "") + lines.push(""); + lines.push(stringifyComment.indentComment(commentString(dc), "")); + } + } + return lines.join("\n") + "\n"; + } + exports2.stringifyDocument = stringifyDocument; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/doc/Document.js +var require_Document = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/doc/Document.js"(exports2) { + "use strict"; + var Alias = require_Alias(); + var Collection = require_Collection(); + var identity = require_identity(); + var Pair = require_Pair(); + var toJS = require_toJS(); + var Schema = require_Schema(); + var stringifyDocument = require_stringifyDocument(); + var anchors = require_anchors(); + var applyReviver = require_applyReviver(); + var createNode = require_createNode(); + var directives = require_directives(); + var Document = class _Document { + constructor(value, replacer, options) { + this.commentBefore = null; + this.comment = null; + this.errors = []; + this.warnings = []; + Object.defineProperty(this, identity.NODE_TYPE, { value: identity.DOC }); + let _replacer = null; + if (typeof replacer === "function" || Array.isArray(replacer)) { + _replacer = replacer; + } else if (options === void 0 && replacer) { + options = replacer; + replacer = void 0; + } + const opt = Object.assign({ + intAsBigInt: false, + keepSourceTokens: false, + logLevel: "warn", + prettyErrors: true, + strict: true, + stringKeys: false, + uniqueKeys: true, + version: "1.2" + }, options); + this.options = opt; + let { version } = opt; + if (options?._directives) { + this.directives = options._directives.atDocument(); + if (this.directives.yaml.explicit) + version = this.directives.yaml.version; + } else + this.directives = new directives.Directives({ version }); + this.setSchema(version, options); + this.contents = value === void 0 ? null : this.createNode(value, _replacer, options); + } + /** + * Create a deep copy of this Document and its contents. + * + * Custom Node values that inherit from `Object` still refer to their original instances. + */ + clone() { + const copy = Object.create(_Document.prototype, { + [identity.NODE_TYPE]: { value: identity.DOC } + }); + copy.commentBefore = this.commentBefore; + copy.comment = this.comment; + copy.errors = this.errors.slice(); + copy.warnings = this.warnings.slice(); + copy.options = Object.assign({}, this.options); + if (this.directives) + copy.directives = this.directives.clone(); + copy.schema = this.schema.clone(); + copy.contents = identity.isNode(this.contents) ? this.contents.clone(copy.schema) : this.contents; + if (this.range) + copy.range = this.range.slice(); + return copy; + } + /** Adds a value to the document. */ + add(value) { + if (assertCollection(this.contents)) + this.contents.add(value); + } + /** Adds a value to the document. */ + addIn(path, value) { + if (assertCollection(this.contents)) + this.contents.addIn(path, value); + } + /** + * Create a new `Alias` node, ensuring that the target `node` has the required anchor. + * + * If `node` already has an anchor, `name` is ignored. + * Otherwise, the `node.anchor` value will be set to `name`, + * or if an anchor with that name is already present in the document, + * `name` will be used as a prefix for a new unique anchor. + * If `name` is undefined, the generated anchor will use 'a' as a prefix. + */ + createAlias(node, name) { + if (!node.anchor) { + const prev = anchors.anchorNames(this); + node.anchor = // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + !name || prev.has(name) ? anchors.findNewAnchor(name || "a", prev) : name; + } + return new Alias.Alias(node.anchor); + } + createNode(value, replacer, options) { + let _replacer = void 0; + if (typeof replacer === "function") { + value = replacer.call({ "": value }, "", value); + _replacer = replacer; + } else if (Array.isArray(replacer)) { + const keyToStr = (v) => typeof v === "number" || v instanceof String || v instanceof Number; + const asStr = replacer.filter(keyToStr).map(String); + if (asStr.length > 0) + replacer = replacer.concat(asStr); + _replacer = replacer; + } else if (options === void 0 && replacer) { + options = replacer; + replacer = void 0; + } + const { aliasDuplicateObjects, anchorPrefix, flow, keepUndefined, onTagObj, tag } = options ?? {}; + const { onAnchor, setAnchors, sourceObjects } = anchors.createNodeAnchors( + this, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + anchorPrefix || "a" + ); + const ctx = { + aliasDuplicateObjects: aliasDuplicateObjects ?? true, + keepUndefined: keepUndefined ?? false, + onAnchor, + onTagObj, + replacer: _replacer, + schema: this.schema, + sourceObjects + }; + const node = createNode.createNode(value, tag, ctx); + if (flow && identity.isCollection(node)) + node.flow = true; + setAnchors(); + return node; + } + /** + * Convert a key and a value into a `Pair` using the current schema, + * recursively wrapping all values as `Scalar` or `Collection` nodes. + */ + createPair(key, value, options = {}) { + const k = this.createNode(key, null, options); + const v = this.createNode(value, null, options); + return new Pair.Pair(k, v); + } + /** + * Removes a value from the document. + * @returns `true` if the item was found and removed. + */ + delete(key) { + return assertCollection(this.contents) ? this.contents.delete(key) : false; + } + /** + * Removes a value from the document. + * @returns `true` if the item was found and removed. + */ + deleteIn(path) { + if (Collection.isEmptyPath(path)) { + if (this.contents == null) + return false; + this.contents = null; + return true; + } + return assertCollection(this.contents) ? this.contents.deleteIn(path) : false; + } + /** + * Returns item at `key`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + get(key, keepScalar) { + return identity.isCollection(this.contents) ? this.contents.get(key, keepScalar) : void 0; + } + /** + * Returns item at `path`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + getIn(path, keepScalar) { + if (Collection.isEmptyPath(path)) + return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents; + return identity.isCollection(this.contents) ? this.contents.getIn(path, keepScalar) : void 0; + } + /** + * Checks if the document includes a value with the key `key`. + */ + has(key) { + return identity.isCollection(this.contents) ? this.contents.has(key) : false; + } + /** + * Checks if the document includes a value at `path`. + */ + hasIn(path) { + if (Collection.isEmptyPath(path)) + return this.contents !== void 0; + return identity.isCollection(this.contents) ? this.contents.hasIn(path) : false; + } + /** + * Sets a value in this document. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + set(key, value) { + if (this.contents == null) { + this.contents = Collection.collectionFromPath(this.schema, [key], value); + } else if (assertCollection(this.contents)) { + this.contents.set(key, value); + } + } + /** + * Sets a value in this document. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + setIn(path, value) { + if (Collection.isEmptyPath(path)) { + this.contents = value; + } else if (this.contents == null) { + this.contents = Collection.collectionFromPath(this.schema, Array.from(path), value); + } else if (assertCollection(this.contents)) { + this.contents.setIn(path, value); + } + } + /** + * Change the YAML version and schema used by the document. + * A `null` version disables support for directives, explicit tags, anchors, and aliases. + * It also requires the `schema` option to be given as a `Schema` instance value. + * + * Overrides all previously set schema options. + */ + setSchema(version, options = {}) { + if (typeof version === "number") + version = String(version); + let opt; + switch (version) { + case "1.1": + if (this.directives) + this.directives.yaml.version = "1.1"; + else + this.directives = new directives.Directives({ version: "1.1" }); + opt = { resolveKnownTags: false, schema: "yaml-1.1" }; + break; + case "1.2": + case "next": + if (this.directives) + this.directives.yaml.version = version; + else + this.directives = new directives.Directives({ version }); + opt = { resolveKnownTags: true, schema: "core" }; + break; + case null: + if (this.directives) + delete this.directives; + opt = null; + break; + default: { + const sv = JSON.stringify(version); + throw new Error(`Expected '1.1', '1.2' or null as first argument, but found: ${sv}`); + } + } + if (options.schema instanceof Object) + this.schema = options.schema; + else if (opt) + this.schema = new Schema.Schema(Object.assign(opt, options)); + else + throw new Error(`With a null YAML version, the { schema: Schema } option is required`); + } + // json & jsonArg are only used from toJSON() + toJS({ json, jsonArg, mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { + const ctx = { + anchors: /* @__PURE__ */ new Map(), + doc: this, + keep: !json, + mapAsMap: mapAsMap === true, + mapKeyWarned: false, + maxAliasCount: typeof maxAliasCount === "number" ? maxAliasCount : 100 + }; + const res = toJS.toJS(this.contents, jsonArg ?? "", ctx); + if (typeof onAnchor === "function") + for (const { count, res: res2 } of ctx.anchors.values()) + onAnchor(res2, count); + return typeof reviver === "function" ? applyReviver.applyReviver(reviver, { "": res }, "", res) : res; + } + /** + * A JSON representation of the document `contents`. + * + * @param jsonArg Used by `JSON.stringify` to indicate the array index or + * property name. + */ + toJSON(jsonArg, onAnchor) { + return this.toJS({ json: true, jsonArg, mapAsMap: false, onAnchor }); + } + /** A YAML representation of the document. */ + toString(options = {}) { + if (this.errors.length > 0) + throw new Error("Document with errors cannot be stringified"); + if ("indent" in options && (!Number.isInteger(options.indent) || Number(options.indent) <= 0)) { + const s = JSON.stringify(options.indent); + throw new Error(`"indent" option must be a positive integer, not ${s}`); + } + return stringifyDocument.stringifyDocument(this, options); + } + }; + function assertCollection(contents) { + if (identity.isCollection(contents)) + return true; + throw new Error("Expected a YAML collection as document contents"); + } + exports2.Document = Document; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/errors.js +var require_errors = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/errors.js"(exports2) { + "use strict"; + var YAMLError = class extends Error { + constructor(name, pos, code, message) { + super(); + this.name = name; + this.code = code; + this.message = message; + this.pos = pos; + } + }; + var YAMLParseError = class extends YAMLError { + constructor(pos, code, message) { + super("YAMLParseError", pos, code, message); + } + }; + var YAMLWarning = class extends YAMLError { + constructor(pos, code, message) { + super("YAMLWarning", pos, code, message); + } + }; + var prettifyError = (src, lc) => (error) => { + if (error.pos[0] === -1) + return; + error.linePos = error.pos.map((pos) => lc.linePos(pos)); + const { line, col } = error.linePos[0]; + error.message += ` at line ${line}, column ${col}`; + let ci = col - 1; + let lineStr = src.substring(lc.lineStarts[line - 1], lc.lineStarts[line]).replace(/[\n\r]+$/, ""); + if (ci >= 60 && lineStr.length > 80) { + const trimStart = Math.min(ci - 39, lineStr.length - 79); + lineStr = "\u2026" + lineStr.substring(trimStart); + ci -= trimStart - 1; + } + if (lineStr.length > 80) + lineStr = lineStr.substring(0, 79) + "\u2026"; + if (line > 1 && /^ *$/.test(lineStr.substring(0, ci))) { + let prev = src.substring(lc.lineStarts[line - 2], lc.lineStarts[line - 1]); + if (prev.length > 80) + prev = prev.substring(0, 79) + "\u2026\n"; + lineStr = prev + lineStr; + } + if (/[^ ]/.test(lineStr)) { + let count = 1; + const end = error.linePos[1]; + if (end?.line === line && end.col > col) { + count = Math.max(1, Math.min(end.col - col, 80 - ci)); + } + const pointer = " ".repeat(ci) + "^".repeat(count); + error.message += `: + +${lineStr} +${pointer} +`; + } + }; + exports2.YAMLError = YAMLError; + exports2.YAMLParseError = YAMLParseError; + exports2.YAMLWarning = YAMLWarning; + exports2.prettifyError = prettifyError; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-props.js +var require_resolve_props = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-props.js"(exports2) { + "use strict"; + function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) { + let spaceBefore = false; + let atNewline = startOnNewline; + let hasSpace = startOnNewline; + let comment = ""; + let commentSep = ""; + let hasNewline = false; + let reqSpace = false; + let tab = null; + let anchor = null; + let tag = null; + let newlineAfterProp = null; + let comma = null; + let found = null; + let start = null; + for (const token of tokens) { + if (reqSpace) { + if (token.type !== "space" && token.type !== "newline" && token.type !== "comma") + onError(token.offset, "MISSING_CHAR", "Tags and anchors must be separated from the next token by white space"); + reqSpace = false; + } + if (tab) { + if (atNewline && token.type !== "comment" && token.type !== "newline") { + onError(tab, "TAB_AS_INDENT", "Tabs are not allowed as indentation"); + } + tab = null; + } + switch (token.type) { + case "space": + if (!flow && (indicator !== "doc-start" || next?.type !== "flow-collection") && token.source.includes(" ")) { + tab = token; + } + hasSpace = true; + break; + case "comment": { + if (!hasSpace) + onError(token, "MISSING_CHAR", "Comments must be separated from other tokens by white space characters"); + const cb = token.source.substring(1) || " "; + if (!comment) + comment = cb; + else + comment += commentSep + cb; + commentSep = ""; + atNewline = false; + break; + } + case "newline": + if (atNewline) { + if (comment) + comment += token.source; + else if (!found || indicator !== "seq-item-ind") + spaceBefore = true; + } else + commentSep += token.source; + atNewline = true; + hasNewline = true; + if (anchor || tag) + newlineAfterProp = token; + hasSpace = true; + break; + case "anchor": + if (anchor) + onError(token, "MULTIPLE_ANCHORS", "A node can have at most one anchor"); + if (token.source.endsWith(":")) + onError(token.offset + token.source.length - 1, "BAD_ALIAS", "Anchor ending in : is ambiguous", true); + anchor = token; + start ?? (start = token.offset); + atNewline = false; + hasSpace = false; + reqSpace = true; + break; + case "tag": { + if (tag) + onError(token, "MULTIPLE_TAGS", "A node can have at most one tag"); + tag = token; + start ?? (start = token.offset); + atNewline = false; + hasSpace = false; + reqSpace = true; + break; + } + case indicator: + if (anchor || tag) + onError(token, "BAD_PROP_ORDER", `Anchors and tags must be after the ${token.source} indicator`); + if (found) + onError(token, "UNEXPECTED_TOKEN", `Unexpected ${token.source} in ${flow ?? "collection"}`); + found = token; + atNewline = indicator === "seq-item-ind" || indicator === "explicit-key-ind"; + hasSpace = false; + break; + case "comma": + if (flow) { + if (comma) + onError(token, "UNEXPECTED_TOKEN", `Unexpected , in ${flow}`); + comma = token; + atNewline = false; + hasSpace = false; + break; + } + // else fallthrough + default: + onError(token, "UNEXPECTED_TOKEN", `Unexpected ${token.type} token`); + atNewline = false; + hasSpace = false; + } + } + const last = tokens[tokens.length - 1]; + const end = last ? last.offset + last.source.length : offset; + if (reqSpace && next && next.type !== "space" && next.type !== "newline" && next.type !== "comma" && (next.type !== "scalar" || next.source !== "")) { + onError(next.offset, "MISSING_CHAR", "Tags and anchors must be separated from the next token by white space"); + } + if (tab && (atNewline && tab.indent <= parentIndent || next?.type === "block-map" || next?.type === "block-seq")) + onError(tab, "TAB_AS_INDENT", "Tabs are not allowed as indentation"); + return { + comma, + found, + spaceBefore, + comment, + hasNewline, + anchor, + tag, + newlineAfterProp, + end, + start: start ?? end + }; + } + exports2.resolveProps = resolveProps; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/util-contains-newline.js +var require_util_contains_newline = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/util-contains-newline.js"(exports2) { + "use strict"; + function containsNewline(key) { + if (!key) + return null; + switch (key.type) { + case "alias": + case "scalar": + case "double-quoted-scalar": + case "single-quoted-scalar": + if (key.source.includes("\n")) + return true; + if (key.end) { + for (const st of key.end) + if (st.type === "newline") + return true; + } + return false; + case "flow-collection": + for (const it of key.items) { + for (const st of it.start) + if (st.type === "newline") + return true; + if (it.sep) { + for (const st of it.sep) + if (st.type === "newline") + return true; + } + if (containsNewline(it.key) || containsNewline(it.value)) + return true; + } + return false; + default: + return true; + } + } + exports2.containsNewline = containsNewline; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/util-flow-indent-check.js +var require_util_flow_indent_check = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/util-flow-indent-check.js"(exports2) { + "use strict"; + var utilContainsNewline = require_util_contains_newline(); + function flowIndentCheck(indent, fc, onError) { + if (fc?.type === "flow-collection") { + const end = fc.end[0]; + if (end.indent === indent && (end.source === "]" || end.source === "}") && utilContainsNewline.containsNewline(fc)) { + const msg = "Flow end indicator should be more indented than parent"; + onError(end, "BAD_INDENT", msg, true); + } + } + } + exports2.flowIndentCheck = flowIndentCheck; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/util-map-includes.js +var require_util_map_includes = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/util-map-includes.js"(exports2) { + "use strict"; + var identity = require_identity(); + function mapIncludes(ctx, items, search) { + const { uniqueKeys } = ctx.options; + if (uniqueKeys === false) + return false; + const isEqual = typeof uniqueKeys === "function" ? uniqueKeys : (a, b) => a === b || identity.isScalar(a) && identity.isScalar(b) && a.value === b.value; + return items.some((pair) => isEqual(pair.key, search)); + } + exports2.mapIncludes = mapIncludes; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-block-map.js +var require_resolve_block_map = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-block-map.js"(exports2) { + "use strict"; + var Pair = require_Pair(); + var YAMLMap = require_YAMLMap(); + var resolveProps = require_resolve_props(); + var utilContainsNewline = require_util_contains_newline(); + var utilFlowIndentCheck = require_util_flow_indent_check(); + var utilMapIncludes = require_util_map_includes(); + var startColMsg = "All mapping items must start at the same column"; + function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError, tag) { + const NodeClass = tag?.nodeClass ?? YAMLMap.YAMLMap; + const map = new NodeClass(ctx.schema); + if (ctx.atRoot) + ctx.atRoot = false; + let offset = bm.offset; + let commentEnd = null; + for (const collItem of bm.items) { + const { start, key, sep, value } = collItem; + const keyProps = resolveProps.resolveProps(start, { + indicator: "explicit-key-ind", + next: key ?? sep?.[0], + offset, + onError, + parentIndent: bm.indent, + startOnNewline: true + }); + const implicitKey = !keyProps.found; + if (implicitKey) { + if (key) { + if (key.type === "block-seq") + onError(offset, "BLOCK_AS_IMPLICIT_KEY", "A block sequence may not be used as an implicit map key"); + else if ("indent" in key && key.indent !== bm.indent) + onError(offset, "BAD_INDENT", startColMsg); + } + if (!keyProps.anchor && !keyProps.tag && !sep) { + commentEnd = keyProps.end; + if (keyProps.comment) { + if (map.comment) + map.comment += "\n" + keyProps.comment; + else + map.comment = keyProps.comment; + } + continue; + } + if (keyProps.newlineAfterProp || utilContainsNewline.containsNewline(key)) { + onError(key ?? start[start.length - 1], "MULTILINE_IMPLICIT_KEY", "Implicit keys need to be on a single line"); + } + } else if (keyProps.found?.indent !== bm.indent) { + onError(offset, "BAD_INDENT", startColMsg); + } + ctx.atKey = true; + const keyStart = keyProps.end; + const keyNode = key ? composeNode(ctx, key, keyProps, onError) : composeEmptyNode(ctx, keyStart, start, null, keyProps, onError); + if (ctx.schema.compat) + utilFlowIndentCheck.flowIndentCheck(bm.indent, key, onError); + ctx.atKey = false; + if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode)) + onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique"); + const valueProps = resolveProps.resolveProps(sep ?? [], { + indicator: "map-value-ind", + next: value, + offset: keyNode.range[2], + onError, + parentIndent: bm.indent, + startOnNewline: !key || key.type === "block-scalar" + }); + offset = valueProps.end; + if (valueProps.found) { + if (implicitKey) { + if (value?.type === "block-map" && !valueProps.hasNewline) + onError(offset, "BLOCK_AS_IMPLICIT_KEY", "Nested mappings are not allowed in compact mappings"); + if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024) + onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key"); + } + const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep, null, valueProps, onError); + if (ctx.schema.compat) + utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError); + offset = valueNode.range[2]; + const pair = new Pair.Pair(keyNode, valueNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + map.items.push(pair); + } else { + if (implicitKey) + onError(keyNode.range, "MISSING_CHAR", "Implicit map keys need to be followed by map values"); + if (valueProps.comment) { + if (keyNode.comment) + keyNode.comment += "\n" + valueProps.comment; + else + keyNode.comment = valueProps.comment; + } + const pair = new Pair.Pair(keyNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + map.items.push(pair); + } + } + if (commentEnd && commentEnd < offset) + onError(commentEnd, "IMPOSSIBLE", "Map comment with trailing content"); + map.range = [bm.offset, offset, commentEnd ?? offset]; + return map; + } + exports2.resolveBlockMap = resolveBlockMap; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-block-seq.js +var require_resolve_block_seq = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-block-seq.js"(exports2) { + "use strict"; + var YAMLSeq = require_YAMLSeq(); + var resolveProps = require_resolve_props(); + var utilFlowIndentCheck = require_util_flow_indent_check(); + function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError, tag) { + const NodeClass = tag?.nodeClass ?? YAMLSeq.YAMLSeq; + const seq = new NodeClass(ctx.schema); + if (ctx.atRoot) + ctx.atRoot = false; + if (ctx.atKey) + ctx.atKey = false; + let offset = bs.offset; + let commentEnd = null; + for (const { start, value } of bs.items) { + const props = resolveProps.resolveProps(start, { + indicator: "seq-item-ind", + next: value, + offset, + onError, + parentIndent: bs.indent, + startOnNewline: true + }); + if (!props.found) { + if (props.anchor || props.tag || value) { + if (value?.type === "block-seq") + onError(props.end, "BAD_INDENT", "All sequence items must start at the same column"); + else + onError(offset, "MISSING_CHAR", "Sequence item without - indicator"); + } else { + commentEnd = props.end; + if (props.comment) + seq.comment = props.comment; + continue; + } + } + const node = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, start, null, props, onError); + if (ctx.schema.compat) + utilFlowIndentCheck.flowIndentCheck(bs.indent, value, onError); + offset = node.range[2]; + seq.items.push(node); + } + seq.range = [bs.offset, offset, commentEnd ?? offset]; + return seq; + } + exports2.resolveBlockSeq = resolveBlockSeq; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-end.js +var require_resolve_end = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-end.js"(exports2) { + "use strict"; + function resolveEnd(end, offset, reqSpace, onError) { + let comment = ""; + if (end) { + let hasSpace = false; + let sep = ""; + for (const token of end) { + const { source, type } = token; + switch (type) { + case "space": + hasSpace = true; + break; + case "comment": { + if (reqSpace && !hasSpace) + onError(token, "MISSING_CHAR", "Comments must be separated from other tokens by white space characters"); + const cb = source.substring(1) || " "; + if (!comment) + comment = cb; + else + comment += sep + cb; + sep = ""; + break; + } + case "newline": + if (comment) + sep += source; + hasSpace = true; + break; + default: + onError(token, "UNEXPECTED_TOKEN", `Unexpected ${type} at node end`); + } + offset += source.length; + } + } + return { comment, offset }; + } + exports2.resolveEnd = resolveEnd; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-flow-collection.js +var require_resolve_flow_collection = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-flow-collection.js"(exports2) { + "use strict"; + var identity = require_identity(); + var Pair = require_Pair(); + var YAMLMap = require_YAMLMap(); + var YAMLSeq = require_YAMLSeq(); + var resolveEnd = require_resolve_end(); + var resolveProps = require_resolve_props(); + var utilContainsNewline = require_util_contains_newline(); + var utilMapIncludes = require_util_map_includes(); + var blockMsg = "Block collections are not allowed within flow collections"; + var isBlock = (token) => token && (token.type === "block-map" || token.type === "block-seq"); + function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onError, tag) { + const isMap = fc.start.source === "{"; + const fcName = isMap ? "flow map" : "flow sequence"; + const NodeClass = tag?.nodeClass ?? (isMap ? YAMLMap.YAMLMap : YAMLSeq.YAMLSeq); + const coll = new NodeClass(ctx.schema); + coll.flow = true; + const atRoot = ctx.atRoot; + if (atRoot) + ctx.atRoot = false; + if (ctx.atKey) + ctx.atKey = false; + let offset = fc.offset + fc.start.source.length; + for (let i = 0; i < fc.items.length; ++i) { + const collItem = fc.items[i]; + const { start, key, sep, value } = collItem; + const props = resolveProps.resolveProps(start, { + flow: fcName, + indicator: "explicit-key-ind", + next: key ?? sep?.[0], + offset, + onError, + parentIndent: fc.indent, + startOnNewline: false + }); + if (!props.found) { + if (!props.anchor && !props.tag && !sep && !value) { + if (i === 0 && props.comma) + onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`); + else if (i < fc.items.length - 1) + onError(props.start, "UNEXPECTED_TOKEN", `Unexpected empty item in ${fcName}`); + if (props.comment) { + if (coll.comment) + coll.comment += "\n" + props.comment; + else + coll.comment = props.comment; + } + offset = props.end; + continue; + } + if (!isMap && ctx.options.strict && utilContainsNewline.containsNewline(key)) + onError( + key, + // checked by containsNewline() + "MULTILINE_IMPLICIT_KEY", + "Implicit keys of flow sequence pairs need to be on a single line" + ); + } + if (i === 0) { + if (props.comma) + onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`); + } else { + if (!props.comma) + onError(props.start, "MISSING_CHAR", `Missing , between ${fcName} items`); + if (props.comment) { + let prevItemComment = ""; + loop: for (const st of start) { + switch (st.type) { + case "comma": + case "space": + break; + case "comment": + prevItemComment = st.source.substring(1); + break loop; + default: + break loop; + } + } + if (prevItemComment) { + let prev = coll.items[coll.items.length - 1]; + if (identity.isPair(prev)) + prev = prev.value ?? prev.key; + if (prev.comment) + prev.comment += "\n" + prevItemComment; + else + prev.comment = prevItemComment; + props.comment = props.comment.substring(prevItemComment.length + 1); + } + } + } + if (!isMap && !sep && !props.found) { + const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep, null, props, onError); + coll.items.push(valueNode); + offset = valueNode.range[2]; + if (isBlock(value)) + onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg); + } else { + ctx.atKey = true; + const keyStart = props.end; + const keyNode = key ? composeNode(ctx, key, props, onError) : composeEmptyNode(ctx, keyStart, start, null, props, onError); + if (isBlock(key)) + onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg); + ctx.atKey = false; + const valueProps = resolveProps.resolveProps(sep ?? [], { + flow: fcName, + indicator: "map-value-ind", + next: value, + offset: keyNode.range[2], + onError, + parentIndent: fc.indent, + startOnNewline: false + }); + if (valueProps.found) { + if (!isMap && !props.found && ctx.options.strict) { + if (sep) + for (const st of sep) { + if (st === valueProps.found) + break; + if (st.type === "newline") { + onError(st, "MULTILINE_IMPLICIT_KEY", "Implicit keys of flow sequence pairs need to be on a single line"); + break; + } + } + if (props.start < valueProps.found.offset - 1024) + onError(valueProps.found, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit flow sequence key"); + } + } else if (value) { + if ("source" in value && value.source?.[0] === ":") + onError(value, "MISSING_CHAR", `Missing space after : in ${fcName}`); + else + onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`); + } + const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError) : null; + if (valueNode) { + if (isBlock(value)) + onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg); + } else if (valueProps.comment) { + if (keyNode.comment) + keyNode.comment += "\n" + valueProps.comment; + else + keyNode.comment = valueProps.comment; + } + const pair = new Pair.Pair(keyNode, valueNode); + if (ctx.options.keepSourceTokens) + pair.srcToken = collItem; + if (isMap) { + const map = coll; + if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode)) + onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique"); + map.items.push(pair); + } else { + const map = new YAMLMap.YAMLMap(ctx.schema); + map.flow = true; + map.items.push(pair); + const endRange = (valueNode ?? keyNode).range; + map.range = [keyNode.range[0], endRange[1], endRange[2]]; + coll.items.push(map); + } + offset = valueNode ? valueNode.range[2] : valueProps.end; + } + } + const expectedEnd = isMap ? "}" : "]"; + const [ce, ...ee] = fc.end; + let cePos = offset; + if (ce?.source === expectedEnd) + cePos = ce.offset + ce.source.length; + else { + const name = fcName[0].toUpperCase() + fcName.substring(1); + const msg = atRoot ? `${name} must end with a ${expectedEnd}` : `${name} in block collection must be sufficiently indented and end with a ${expectedEnd}`; + onError(offset, atRoot ? "MISSING_CHAR" : "BAD_INDENT", msg); + if (ce && ce.source.length !== 1) + ee.unshift(ce); + } + if (ee.length > 0) { + const end = resolveEnd.resolveEnd(ee, cePos, ctx.options.strict, onError); + if (end.comment) { + if (coll.comment) + coll.comment += "\n" + end.comment; + else + coll.comment = end.comment; + } + coll.range = [fc.offset, cePos, end.offset]; + } else { + coll.range = [fc.offset, cePos, cePos]; + } + return coll; + } + exports2.resolveFlowCollection = resolveFlowCollection; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/compose-collection.js +var require_compose_collection = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/compose-collection.js"(exports2) { + "use strict"; + var identity = require_identity(); + var Scalar = require_Scalar(); + var YAMLMap = require_YAMLMap(); + var YAMLSeq = require_YAMLSeq(); + var resolveBlockMap = require_resolve_block_map(); + var resolveBlockSeq = require_resolve_block_seq(); + var resolveFlowCollection = require_resolve_flow_collection(); + function resolveCollection(CN, ctx, token, onError, tagName, tag) { + const coll = token.type === "block-map" ? resolveBlockMap.resolveBlockMap(CN, ctx, token, onError, tag) : token.type === "block-seq" ? resolveBlockSeq.resolveBlockSeq(CN, ctx, token, onError, tag) : resolveFlowCollection.resolveFlowCollection(CN, ctx, token, onError, tag); + const Coll = coll.constructor; + if (tagName === "!" || tagName === Coll.tagName) { + coll.tag = Coll.tagName; + return coll; + } + if (tagName) + coll.tag = tagName; + return coll; + } + function composeCollection(CN, ctx, token, props, onError) { + const tagToken = props.tag; + const tagName = !tagToken ? null : ctx.directives.tagName(tagToken.source, (msg) => onError(tagToken, "TAG_RESOLVE_FAILED", msg)); + if (token.type === "block-seq") { + const { anchor, newlineAfterProp: nl } = props; + const lastProp = anchor && tagToken ? anchor.offset > tagToken.offset ? anchor : tagToken : anchor ?? tagToken; + if (lastProp && (!nl || nl.offset < lastProp.offset)) { + const message = "Missing newline after block sequence props"; + onError(lastProp, "MISSING_CHAR", message); + } + } + const expType = token.type === "block-map" ? "map" : token.type === "block-seq" ? "seq" : token.start.source === "{" ? "map" : "seq"; + if (!tagToken || !tagName || tagName === "!" || tagName === YAMLMap.YAMLMap.tagName && expType === "map" || tagName === YAMLSeq.YAMLSeq.tagName && expType === "seq") { + return resolveCollection(CN, ctx, token, onError, tagName); + } + let tag = ctx.schema.tags.find((t) => t.tag === tagName && t.collection === expType); + if (!tag) { + const kt = ctx.schema.knownTags[tagName]; + if (kt?.collection === expType) { + ctx.schema.tags.push(Object.assign({}, kt, { default: false })); + tag = kt; + } else { + if (kt) { + onError(tagToken, "BAD_COLLECTION_TYPE", `${kt.tag} used for ${expType} collection, but expects ${kt.collection ?? "scalar"}`, true); + } else { + onError(tagToken, "TAG_RESOLVE_FAILED", `Unresolved tag: ${tagName}`, true); + } + return resolveCollection(CN, ctx, token, onError, tagName); + } + } + const coll = resolveCollection(CN, ctx, token, onError, tagName, tag); + const res = tag.resolve?.(coll, (msg) => onError(tagToken, "TAG_RESOLVE_FAILED", msg), ctx.options) ?? coll; + const node = identity.isNode(res) ? res : new Scalar.Scalar(res); + node.range = coll.range; + node.tag = tagName; + if (tag?.format) + node.format = tag.format; + return node; + } + exports2.composeCollection = composeCollection; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-block-scalar.js +var require_resolve_block_scalar = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-block-scalar.js"(exports2) { + "use strict"; + var Scalar = require_Scalar(); + function resolveBlockScalar(ctx, scalar, onError) { + const start = scalar.offset; + const header = parseBlockScalarHeader(scalar, ctx.options.strict, onError); + if (!header) + return { value: "", type: null, comment: "", range: [start, start, start] }; + const type = header.mode === ">" ? Scalar.Scalar.BLOCK_FOLDED : Scalar.Scalar.BLOCK_LITERAL; + const lines = scalar.source ? splitLines(scalar.source) : []; + let chompStart = lines.length; + for (let i = lines.length - 1; i >= 0; --i) { + const content = lines[i][1]; + if (content === "" || content === "\r") + chompStart = i; + else + break; + } + if (chompStart === 0) { + const value2 = header.chomp === "+" && lines.length > 0 ? "\n".repeat(Math.max(1, lines.length - 1)) : ""; + let end2 = start + header.length; + if (scalar.source) + end2 += scalar.source.length; + return { value: value2, type, comment: header.comment, range: [start, end2, end2] }; + } + let trimIndent = scalar.indent + header.indent; + let offset = scalar.offset + header.length; + let contentStart = 0; + for (let i = 0; i < chompStart; ++i) { + const [indent, content] = lines[i]; + if (content === "" || content === "\r") { + if (header.indent === 0 && indent.length > trimIndent) + trimIndent = indent.length; + } else { + if (indent.length < trimIndent) { + const message = "Block scalars with more-indented leading empty lines must use an explicit indentation indicator"; + onError(offset + indent.length, "MISSING_CHAR", message); + } + if (header.indent === 0) + trimIndent = indent.length; + contentStart = i; + if (trimIndent === 0 && !ctx.atRoot) { + const message = "Block scalar values in collections must be indented"; + onError(offset, "BAD_INDENT", message); + } + break; + } + offset += indent.length + content.length + 1; + } + for (let i = lines.length - 1; i >= chompStart; --i) { + if (lines[i][0].length > trimIndent) + chompStart = i + 1; + } + let value = ""; + let sep = ""; + let prevMoreIndented = false; + for (let i = 0; i < contentStart; ++i) + value += lines[i][0].slice(trimIndent) + "\n"; + for (let i = contentStart; i < chompStart; ++i) { + let [indent, content] = lines[i]; + offset += indent.length + content.length + 1; + const crlf = content[content.length - 1] === "\r"; + if (crlf) + content = content.slice(0, -1); + if (content && indent.length < trimIndent) { + const src = header.indent ? "explicit indentation indicator" : "first line"; + const message = `Block scalar lines must not be less indented than their ${src}`; + onError(offset - content.length - (crlf ? 2 : 1), "BAD_INDENT", message); + indent = ""; + } + if (type === Scalar.Scalar.BLOCK_LITERAL) { + value += sep + indent.slice(trimIndent) + content; + sep = "\n"; + } else if (indent.length > trimIndent || content[0] === " ") { + if (sep === " ") + sep = "\n"; + else if (!prevMoreIndented && sep === "\n") + sep = "\n\n"; + value += sep + indent.slice(trimIndent) + content; + sep = "\n"; + prevMoreIndented = true; + } else if (content === "") { + if (sep === "\n") + value += "\n"; + else + sep = "\n"; + } else { + value += sep + content; + sep = " "; + prevMoreIndented = false; + } + } + switch (header.chomp) { + case "-": + break; + case "+": + for (let i = chompStart; i < lines.length; ++i) + value += "\n" + lines[i][0].slice(trimIndent); + if (value[value.length - 1] !== "\n") + value += "\n"; + break; + default: + value += "\n"; + } + const end = start + header.length + scalar.source.length; + return { value, type, comment: header.comment, range: [start, end, end] }; + } + function parseBlockScalarHeader({ offset, props }, strict, onError) { + if (props[0].type !== "block-scalar-header") { + onError(props[0], "IMPOSSIBLE", "Block scalar header not found"); + return null; + } + const { source } = props[0]; + const mode = source[0]; + let indent = 0; + let chomp = ""; + let error = -1; + for (let i = 1; i < source.length; ++i) { + const ch = source[i]; + if (!chomp && (ch === "-" || ch === "+")) + chomp = ch; + else { + const n = Number(ch); + if (!indent && n) + indent = n; + else if (error === -1) + error = offset + i; + } + } + if (error !== -1) + onError(error, "UNEXPECTED_TOKEN", `Block scalar header includes extra characters: ${source}`); + let hasSpace = false; + let comment = ""; + let length = source.length; + for (let i = 1; i < props.length; ++i) { + const token = props[i]; + switch (token.type) { + case "space": + hasSpace = true; + // fallthrough + case "newline": + length += token.source.length; + break; + case "comment": + if (strict && !hasSpace) { + const message = "Comments must be separated from other tokens by white space characters"; + onError(token, "MISSING_CHAR", message); + } + length += token.source.length; + comment = token.source.substring(1); + break; + case "error": + onError(token, "UNEXPECTED_TOKEN", token.message); + length += token.source.length; + break; + /* istanbul ignore next should not happen */ + default: { + const message = `Unexpected token in block scalar header: ${token.type}`; + onError(token, "UNEXPECTED_TOKEN", message); + const ts = token.source; + if (ts && typeof ts === "string") + length += ts.length; + } + } + } + return { mode, indent, chomp, comment, length }; + } + function splitLines(source) { + const split = source.split(/\n( *)/); + const first = split[0]; + const m = first.match(/^( *)/); + const line0 = m?.[1] ? [m[1], first.slice(m[1].length)] : ["", first]; + const lines = [line0]; + for (let i = 1; i < split.length; i += 2) + lines.push([split[i], split[i + 1]]); + return lines; + } + exports2.resolveBlockScalar = resolveBlockScalar; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-flow-scalar.js +var require_resolve_flow_scalar = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-flow-scalar.js"(exports2) { + "use strict"; + var Scalar = require_Scalar(); + var resolveEnd = require_resolve_end(); + function resolveFlowScalar(scalar, strict, onError) { + const { offset, type, source, end } = scalar; + let _type; + let value; + const _onError = (rel, code, msg) => onError(offset + rel, code, msg); + switch (type) { + case "scalar": + _type = Scalar.Scalar.PLAIN; + value = plainValue(source, _onError); + break; + case "single-quoted-scalar": + _type = Scalar.Scalar.QUOTE_SINGLE; + value = singleQuotedValue(source, _onError); + break; + case "double-quoted-scalar": + _type = Scalar.Scalar.QUOTE_DOUBLE; + value = doubleQuotedValue(source, _onError); + break; + /* istanbul ignore next should not happen */ + default: + onError(scalar, "UNEXPECTED_TOKEN", `Expected a flow scalar value, but found: ${type}`); + return { + value: "", + type: null, + comment: "", + range: [offset, offset + source.length, offset + source.length] + }; + } + const valueEnd = offset + source.length; + const re = resolveEnd.resolveEnd(end, valueEnd, strict, onError); + return { + value, + type: _type, + comment: re.comment, + range: [offset, valueEnd, re.offset] + }; + } + function plainValue(source, onError) { + let badChar = ""; + switch (source[0]) { + /* istanbul ignore next should not happen */ + case " ": + badChar = "a tab character"; + break; + case ",": + badChar = "flow indicator character ,"; + break; + case "%": + badChar = "directive indicator character %"; + break; + case "|": + case ">": { + badChar = `block scalar indicator ${source[0]}`; + break; + } + case "@": + case "`": { + badChar = `reserved character ${source[0]}`; + break; + } + } + if (badChar) + onError(0, "BAD_SCALAR_START", `Plain value cannot start with ${badChar}`); + return foldLines(source); + } + function singleQuotedValue(source, onError) { + if (source[source.length - 1] !== "'" || source.length === 1) + onError(source.length, "MISSING_CHAR", "Missing closing 'quote"); + return foldLines(source.slice(1, -1)).replace(/''/g, "'"); + } + function foldLines(source) { + let first, line; + try { + first = new RegExp("(.*?)(? wsStart ? source.slice(wsStart, i + 1) : ch; + } else { + res += ch; + } + } + if (source[source.length - 1] !== '"' || source.length === 1) + onError(source.length, "MISSING_CHAR", 'Missing closing "quote'); + return res; + } + function foldNewline(source, offset) { + let fold = ""; + let ch = source[offset + 1]; + while (ch === " " || ch === " " || ch === "\n" || ch === "\r") { + if (ch === "\r" && source[offset + 2] !== "\n") + break; + if (ch === "\n") + fold += "\n"; + offset += 1; + ch = source[offset + 1]; + } + if (!fold) + fold = " "; + return { fold, offset }; + } + var escapeCodes = { + "0": "\0", + // null character + a: "\x07", + // bell character + b: "\b", + // backspace + e: "\x1B", + // escape character + f: "\f", + // form feed + n: "\n", + // line feed + r: "\r", + // carriage return + t: " ", + // horizontal tab + v: "\v", + // vertical tab + N: "\x85", + // Unicode next line + _: "\xA0", + // Unicode non-breaking space + L: "\u2028", + // Unicode line separator + P: "\u2029", + // Unicode paragraph separator + " ": " ", + '"': '"', + "/": "/", + "\\": "\\", + " ": " " + }; + function parseCharCode(source, offset, length, onError) { + const cc = source.substr(offset, length); + const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc); + const code = ok ? parseInt(cc, 16) : NaN; + if (isNaN(code)) { + const raw = source.substr(offset - 2, length + 2); + onError(offset - 2, "BAD_DQ_ESCAPE", `Invalid escape sequence ${raw}`); + return raw; + } + return String.fromCodePoint(code); + } + exports2.resolveFlowScalar = resolveFlowScalar; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/compose-scalar.js +var require_compose_scalar = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/compose-scalar.js"(exports2) { + "use strict"; + var identity = require_identity(); + var Scalar = require_Scalar(); + var resolveBlockScalar = require_resolve_block_scalar(); + var resolveFlowScalar = require_resolve_flow_scalar(); + function composeScalar(ctx, token, tagToken, onError) { + const { value, type, comment, range } = token.type === "block-scalar" ? resolveBlockScalar.resolveBlockScalar(ctx, token, onError) : resolveFlowScalar.resolveFlowScalar(token, ctx.options.strict, onError); + const tagName = tagToken ? ctx.directives.tagName(tagToken.source, (msg) => onError(tagToken, "TAG_RESOLVE_FAILED", msg)) : null; + let tag; + if (ctx.options.stringKeys && ctx.atKey) { + tag = ctx.schema[identity.SCALAR]; + } else if (tagName) + tag = findScalarTagByName(ctx.schema, value, tagName, tagToken, onError); + else if (token.type === "scalar") + tag = findScalarTagByTest(ctx, value, token, onError); + else + tag = ctx.schema[identity.SCALAR]; + let scalar; + try { + const res = tag.resolve(value, (msg) => onError(tagToken ?? token, "TAG_RESOLVE_FAILED", msg), ctx.options); + scalar = identity.isScalar(res) ? res : new Scalar.Scalar(res); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + onError(tagToken ?? token, "TAG_RESOLVE_FAILED", msg); + scalar = new Scalar.Scalar(value); + } + scalar.range = range; + scalar.source = value; + if (type) + scalar.type = type; + if (tagName) + scalar.tag = tagName; + if (tag.format) + scalar.format = tag.format; + if (comment) + scalar.comment = comment; + return scalar; + } + function findScalarTagByName(schema, value, tagName, tagToken, onError) { + if (tagName === "!") + return schema[identity.SCALAR]; + const matchWithTest = []; + for (const tag of schema.tags) { + if (!tag.collection && tag.tag === tagName) { + if (tag.default && tag.test) + matchWithTest.push(tag); + else + return tag; + } + } + for (const tag of matchWithTest) + if (tag.test?.test(value)) + return tag; + const kt = schema.knownTags[tagName]; + if (kt && !kt.collection) { + schema.tags.push(Object.assign({}, kt, { default: false, test: void 0 })); + return kt; + } + onError(tagToken, "TAG_RESOLVE_FAILED", `Unresolved tag: ${tagName}`, tagName !== "tag:yaml.org,2002:str"); + return schema[identity.SCALAR]; + } + function findScalarTagByTest({ atKey, directives, schema }, value, token, onError) { + const tag = schema.tags.find((tag2) => (tag2.default === true || atKey && tag2.default === "key") && tag2.test?.test(value)) || schema[identity.SCALAR]; + if (schema.compat) { + const compat = schema.compat.find((tag2) => tag2.default && tag2.test?.test(value)) ?? schema[identity.SCALAR]; + if (tag.tag !== compat.tag) { + const ts = directives.tagString(tag.tag); + const cs = directives.tagString(compat.tag); + const msg = `Value may be parsed as either ${ts} or ${cs}`; + onError(token, "TAG_RESOLVE_FAILED", msg, true); + } + } + return tag; + } + exports2.composeScalar = composeScalar; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/util-empty-scalar-position.js +var require_util_empty_scalar_position = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/util-empty-scalar-position.js"(exports2) { + "use strict"; + function emptyScalarPosition(offset, before, pos) { + if (before) { + pos ?? (pos = before.length); + for (let i = pos - 1; i >= 0; --i) { + let st = before[i]; + switch (st.type) { + case "space": + case "comment": + case "newline": + offset -= st.source.length; + continue; + } + st = before[++i]; + while (st?.type === "space") { + offset += st.source.length; + st = before[++i]; + } + break; + } + } + return offset; + } + exports2.emptyScalarPosition = emptyScalarPosition; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/compose-node.js +var require_compose_node = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/compose-node.js"(exports2) { + "use strict"; + var Alias = require_Alias(); + var identity = require_identity(); + var composeCollection = require_compose_collection(); + var composeScalar = require_compose_scalar(); + var resolveEnd = require_resolve_end(); + var utilEmptyScalarPosition = require_util_empty_scalar_position(); + var CN = { composeNode, composeEmptyNode }; + function composeNode(ctx, token, props, onError) { + const atKey = ctx.atKey; + const { spaceBefore, comment, anchor, tag } = props; + let node; + let isSrcToken = true; + switch (token.type) { + case "alias": + node = composeAlias(ctx, token, onError); + if (anchor || tag) + onError(token, "ALIAS_PROPS", "An alias node must not specify any properties"); + break; + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": + case "block-scalar": + node = composeScalar.composeScalar(ctx, token, tag, onError); + if (anchor) + node.anchor = anchor.source.substring(1); + break; + case "block-map": + case "block-seq": + case "flow-collection": + node = composeCollection.composeCollection(CN, ctx, token, props, onError); + if (anchor) + node.anchor = anchor.source.substring(1); + break; + default: { + const message = token.type === "error" ? token.message : `Unsupported token (type: ${token.type})`; + onError(token, "UNEXPECTED_TOKEN", message); + node = composeEmptyNode(ctx, token.offset, void 0, null, props, onError); + isSrcToken = false; + } + } + if (anchor && node.anchor === "") + onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string"); + if (atKey && ctx.options.stringKeys && (!identity.isScalar(node) || typeof node.value !== "string" || node.tag && node.tag !== "tag:yaml.org,2002:str")) { + const msg = "With stringKeys, all keys must be strings"; + onError(tag ?? token, "NON_STRING_KEY", msg); + } + if (spaceBefore) + node.spaceBefore = true; + if (comment) { + if (token.type === "scalar" && token.source === "") + node.comment = comment; + else + node.commentBefore = comment; + } + if (ctx.options.keepSourceTokens && isSrcToken) + node.srcToken = token; + return node; + } + function composeEmptyNode(ctx, offset, before, pos, { spaceBefore, comment, anchor, tag, end }, onError) { + const token = { + type: "scalar", + offset: utilEmptyScalarPosition.emptyScalarPosition(offset, before, pos), + indent: -1, + source: "" + }; + const node = composeScalar.composeScalar(ctx, token, tag, onError); + if (anchor) { + node.anchor = anchor.source.substring(1); + if (node.anchor === "") + onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string"); + } + if (spaceBefore) + node.spaceBefore = true; + if (comment) { + node.comment = comment; + node.range[2] = end; + } + return node; + } + function composeAlias({ options }, { offset, source, end }, onError) { + const alias = new Alias.Alias(source.substring(1)); + if (alias.source === "") + onError(offset, "BAD_ALIAS", "Alias cannot be an empty string"); + if (alias.source.endsWith(":")) + onError(offset + source.length - 1, "BAD_ALIAS", "Alias ending in : is ambiguous", true); + const valueEnd = offset + source.length; + const re = resolveEnd.resolveEnd(end, valueEnd, options.strict, onError); + alias.range = [offset, valueEnd, re.offset]; + if (re.comment) + alias.comment = re.comment; + return alias; + } + exports2.composeEmptyNode = composeEmptyNode; + exports2.composeNode = composeNode; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/compose-doc.js +var require_compose_doc = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/compose-doc.js"(exports2) { + "use strict"; + var Document = require_Document(); + var composeNode = require_compose_node(); + var resolveEnd = require_resolve_end(); + var resolveProps = require_resolve_props(); + function composeDoc(options, directives, { offset, start, value, end }, onError) { + const opts = Object.assign({ _directives: directives }, options); + const doc = new Document.Document(void 0, opts); + const ctx = { + atKey: false, + atRoot: true, + directives: doc.directives, + options: doc.options, + schema: doc.schema + }; + const props = resolveProps.resolveProps(start, { + indicator: "doc-start", + next: value ?? end?.[0], + offset, + onError, + parentIndent: 0, + startOnNewline: true + }); + if (props.found) { + doc.directives.docStart = true; + if (value && (value.type === "block-map" || value.type === "block-seq") && !props.hasNewline) + onError(props.end, "MISSING_CHAR", "Block collection cannot start on same line with directives-end marker"); + } + doc.contents = value ? composeNode.composeNode(ctx, value, props, onError) : composeNode.composeEmptyNode(ctx, props.end, start, null, props, onError); + const contentEnd = doc.contents.range[2]; + const re = resolveEnd.resolveEnd(end, contentEnd, false, onError); + if (re.comment) + doc.comment = re.comment; + doc.range = [offset, contentEnd, re.offset]; + return doc; + } + exports2.composeDoc = composeDoc; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/composer.js +var require_composer = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/compose/composer.js"(exports2) { + "use strict"; + var node_process = require("process"); + var directives = require_directives(); + var Document = require_Document(); + var errors = require_errors(); + var identity = require_identity(); + var composeDoc = require_compose_doc(); + var resolveEnd = require_resolve_end(); + function getErrorPos(src) { + if (typeof src === "number") + return [src, src + 1]; + if (Array.isArray(src)) + return src.length === 2 ? src : [src[0], src[1]]; + const { offset, source } = src; + return [offset, offset + (typeof source === "string" ? source.length : 1)]; + } + function parsePrelude(prelude) { + let comment = ""; + let atComment = false; + let afterEmptyLine = false; + for (let i = 0; i < prelude.length; ++i) { + const source = prelude[i]; + switch (source[0]) { + case "#": + comment += (comment === "" ? "" : afterEmptyLine ? "\n\n" : "\n") + (source.substring(1) || " "); + atComment = true; + afterEmptyLine = false; + break; + case "%": + if (prelude[i + 1]?.[0] !== "#") + i += 1; + atComment = false; + break; + default: + if (!atComment) + afterEmptyLine = true; + atComment = false; + } + } + return { comment, afterEmptyLine }; + } + var Composer = class { + constructor(options = {}) { + this.doc = null; + this.atDirectives = false; + this.prelude = []; + this.errors = []; + this.warnings = []; + this.onError = (source, code, message, warning) => { + const pos = getErrorPos(source); + if (warning) + this.warnings.push(new errors.YAMLWarning(pos, code, message)); + else + this.errors.push(new errors.YAMLParseError(pos, code, message)); + }; + this.directives = new directives.Directives({ version: options.version || "1.2" }); + this.options = options; + } + decorate(doc, afterDoc) { + const { comment, afterEmptyLine } = parsePrelude(this.prelude); + if (comment) { + const dc = doc.contents; + if (afterDoc) { + doc.comment = doc.comment ? `${doc.comment} +${comment}` : comment; + } else if (afterEmptyLine || doc.directives.docStart || !dc) { + doc.commentBefore = comment; + } else if (identity.isCollection(dc) && !dc.flow && dc.items.length > 0) { + let it = dc.items[0]; + if (identity.isPair(it)) + it = it.key; + const cb = it.commentBefore; + it.commentBefore = cb ? `${comment} +${cb}` : comment; + } else { + const cb = dc.commentBefore; + dc.commentBefore = cb ? `${comment} +${cb}` : comment; + } + } + if (afterDoc) { + Array.prototype.push.apply(doc.errors, this.errors); + Array.prototype.push.apply(doc.warnings, this.warnings); + } else { + doc.errors = this.errors; + doc.warnings = this.warnings; + } + this.prelude = []; + this.errors = []; + this.warnings = []; + } + /** + * Current stream status information. + * + * Mostly useful at the end of input for an empty stream. + */ + streamInfo() { + return { + comment: parsePrelude(this.prelude).comment, + directives: this.directives, + errors: this.errors, + warnings: this.warnings + }; + } + /** + * Compose tokens into documents. + * + * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. + * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. + */ + *compose(tokens, forceDoc = false, endOffset = -1) { + for (const token of tokens) + yield* this.next(token); + yield* this.end(forceDoc, endOffset); + } + /** Advance the composer by one CST token. */ + *next(token) { + if (node_process.env.LOG_STREAM) + console.dir(token, { depth: null }); + switch (token.type) { + case "directive": + this.directives.add(token.source, (offset, message, warning) => { + const pos = getErrorPos(token); + pos[0] += offset; + this.onError(pos, "BAD_DIRECTIVE", message, warning); + }); + this.prelude.push(token.source); + this.atDirectives = true; + break; + case "document": { + const doc = composeDoc.composeDoc(this.options, this.directives, token, this.onError); + if (this.atDirectives && !doc.directives.docStart) + this.onError(token, "MISSING_CHAR", "Missing directives-end/doc-start indicator line"); + this.decorate(doc, false); + if (this.doc) + yield this.doc; + this.doc = doc; + this.atDirectives = false; + break; + } + case "byte-order-mark": + case "space": + break; + case "comment": + case "newline": + this.prelude.push(token.source); + break; + case "error": { + const msg = token.source ? `${token.message}: ${JSON.stringify(token.source)}` : token.message; + const error = new errors.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg); + if (this.atDirectives || !this.doc) + this.errors.push(error); + else + this.doc.errors.push(error); + break; + } + case "doc-end": { + if (!this.doc) { + const msg = "Unexpected doc-end without preceding document"; + this.errors.push(new errors.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg)); + break; + } + this.doc.directives.docEnd = true; + const end = resolveEnd.resolveEnd(token.end, token.offset + token.source.length, this.doc.options.strict, this.onError); + this.decorate(this.doc, true); + if (end.comment) { + const dc = this.doc.comment; + this.doc.comment = dc ? `${dc} +${end.comment}` : end.comment; + } + this.doc.range[2] = end.offset; + break; + } + default: + this.errors.push(new errors.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`)); + } + } + /** + * Call at end of input to yield any remaining document. + * + * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. + * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. + */ + *end(forceDoc = false, endOffset = -1) { + if (this.doc) { + this.decorate(this.doc, true); + yield this.doc; + this.doc = null; + } else if (forceDoc) { + const opts = Object.assign({ _directives: this.directives }, this.options); + const doc = new Document.Document(void 0, opts); + if (this.atDirectives) + this.onError(endOffset, "MISSING_CHAR", "Missing directives-end indicator line"); + doc.range = [0, endOffset, endOffset]; + this.decorate(doc, false); + yield doc; + } + } + }; + exports2.Composer = Composer; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/cst-scalar.js +var require_cst_scalar = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/cst-scalar.js"(exports2) { + "use strict"; + var resolveBlockScalar = require_resolve_block_scalar(); + var resolveFlowScalar = require_resolve_flow_scalar(); + var errors = require_errors(); + var stringifyString = require_stringifyString(); + function resolveAsScalar(token, strict = true, onError) { + if (token) { + const _onError = (pos, code, message) => { + const offset = typeof pos === "number" ? pos : Array.isArray(pos) ? pos[0] : pos.offset; + if (onError) + onError(offset, code, message); + else + throw new errors.YAMLParseError([offset, offset + 1], code, message); + }; + switch (token.type) { + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": + return resolveFlowScalar.resolveFlowScalar(token, strict, _onError); + case "block-scalar": + return resolveBlockScalar.resolveBlockScalar({ options: { strict } }, token, _onError); + } + } + return null; + } + function createScalarToken(value, context) { + const { implicitKey = false, indent, inFlow = false, offset = -1, type = "PLAIN" } = context; + const source = stringifyString.stringifyString({ type, value }, { + implicitKey, + indent: indent > 0 ? " ".repeat(indent) : "", + inFlow, + options: { blockQuote: true, lineWidth: -1 } + }); + const end = context.end ?? [ + { type: "newline", offset: -1, indent, source: "\n" } + ]; + switch (source[0]) { + case "|": + case ">": { + const he = source.indexOf("\n"); + const head = source.substring(0, he); + const body = source.substring(he + 1) + "\n"; + const props = [ + { type: "block-scalar-header", offset, indent, source: head } + ]; + if (!addEndtoBlockProps(props, end)) + props.push({ type: "newline", offset: -1, indent, source: "\n" }); + return { type: "block-scalar", offset, indent, props, source: body }; + } + case '"': + return { type: "double-quoted-scalar", offset, indent, source, end }; + case "'": + return { type: "single-quoted-scalar", offset, indent, source, end }; + default: + return { type: "scalar", offset, indent, source, end }; + } + } + function setScalarValue(token, value, context = {}) { + let { afterKey = false, implicitKey = false, inFlow = false, type } = context; + let indent = "indent" in token ? token.indent : null; + if (afterKey && typeof indent === "number") + indent += 2; + if (!type) + switch (token.type) { + case "single-quoted-scalar": + type = "QUOTE_SINGLE"; + break; + case "double-quoted-scalar": + type = "QUOTE_DOUBLE"; + break; + case "block-scalar": { + const header = token.props[0]; + if (header.type !== "block-scalar-header") + throw new Error("Invalid block scalar header"); + type = header.source[0] === ">" ? "BLOCK_FOLDED" : "BLOCK_LITERAL"; + break; + } + default: + type = "PLAIN"; + } + const source = stringifyString.stringifyString({ type, value }, { + implicitKey: implicitKey || indent === null, + indent: indent !== null && indent > 0 ? " ".repeat(indent) : "", + inFlow, + options: { blockQuote: true, lineWidth: -1 } + }); + switch (source[0]) { + case "|": + case ">": + setBlockScalarValue(token, source); + break; + case '"': + setFlowScalarValue(token, source, "double-quoted-scalar"); + break; + case "'": + setFlowScalarValue(token, source, "single-quoted-scalar"); + break; + default: + setFlowScalarValue(token, source, "scalar"); + } + } + function setBlockScalarValue(token, source) { + const he = source.indexOf("\n"); + const head = source.substring(0, he); + const body = source.substring(he + 1) + "\n"; + if (token.type === "block-scalar") { + const header = token.props[0]; + if (header.type !== "block-scalar-header") + throw new Error("Invalid block scalar header"); + header.source = head; + token.source = body; + } else { + const { offset } = token; + const indent = "indent" in token ? token.indent : -1; + const props = [ + { type: "block-scalar-header", offset, indent, source: head } + ]; + if (!addEndtoBlockProps(props, "end" in token ? token.end : void 0)) + props.push({ type: "newline", offset: -1, indent, source: "\n" }); + for (const key of Object.keys(token)) + if (key !== "type" && key !== "offset") + delete token[key]; + Object.assign(token, { type: "block-scalar", indent, props, source: body }); + } + } + function addEndtoBlockProps(props, end) { + if (end) + for (const st of end) + switch (st.type) { + case "space": + case "comment": + props.push(st); + break; + case "newline": + props.push(st); + return true; + } + return false; + } + function setFlowScalarValue(token, source, type) { + switch (token.type) { + case "scalar": + case "double-quoted-scalar": + case "single-quoted-scalar": + token.type = type; + token.source = source; + break; + case "block-scalar": { + const end = token.props.slice(1); + let oa = source.length; + if (token.props[0].type === "block-scalar-header") + oa -= token.props[0].source.length; + for (const tok of end) + tok.offset += oa; + delete token.props; + Object.assign(token, { type, source, end }); + break; + } + case "block-map": + case "block-seq": { + const offset = token.offset + source.length; + const nl = { type: "newline", offset, indent: token.indent, source: "\n" }; + delete token.items; + Object.assign(token, { type, source, end: [nl] }); + break; + } + default: { + const indent = "indent" in token ? token.indent : -1; + const end = "end" in token && Array.isArray(token.end) ? token.end.filter((st) => st.type === "space" || st.type === "comment" || st.type === "newline") : []; + for (const key of Object.keys(token)) + if (key !== "type" && key !== "offset") + delete token[key]; + Object.assign(token, { type, indent, source, end }); + } + } + } + exports2.createScalarToken = createScalarToken; + exports2.resolveAsScalar = resolveAsScalar; + exports2.setScalarValue = setScalarValue; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/cst-stringify.js +var require_cst_stringify = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/cst-stringify.js"(exports2) { + "use strict"; + var stringify = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst); + function stringifyToken(token) { + switch (token.type) { + case "block-scalar": { + let res = ""; + for (const tok of token.props) + res += stringifyToken(tok); + return res + token.source; + } + case "block-map": + case "block-seq": { + let res = ""; + for (const item of token.items) + res += stringifyItem(item); + return res; + } + case "flow-collection": { + let res = token.start.source; + for (const item of token.items) + res += stringifyItem(item); + for (const st of token.end) + res += st.source; + return res; + } + case "document": { + let res = stringifyItem(token); + if (token.end) + for (const st of token.end) + res += st.source; + return res; + } + default: { + let res = token.source; + if ("end" in token && token.end) + for (const st of token.end) + res += st.source; + return res; + } + } + } + function stringifyItem({ start, key, sep, value }) { + let res = ""; + for (const st of start) + res += st.source; + if (key) + res += stringifyToken(key); + if (sep) + for (const st of sep) + res += st.source; + if (value) + res += stringifyToken(value); + return res; + } + exports2.stringify = stringify; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/cst-visit.js +var require_cst_visit = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/cst-visit.js"(exports2) { + "use strict"; + var BREAK = /* @__PURE__ */ Symbol("break visit"); + var SKIP = /* @__PURE__ */ Symbol("skip children"); + var REMOVE = /* @__PURE__ */ Symbol("remove item"); + function visit(cst, visitor) { + if ("type" in cst && cst.type === "document") + cst = { start: cst.start, value: cst.value }; + _visit(Object.freeze([]), cst, visitor); + } + visit.BREAK = BREAK; + visit.SKIP = SKIP; + visit.REMOVE = REMOVE; + visit.itemAtPath = (cst, path) => { + let item = cst; + for (const [field, index] of path) { + const tok = item?.[field]; + if (tok && "items" in tok) { + item = tok.items[index]; + } else + return void 0; + } + return item; + }; + visit.parentCollection = (cst, path) => { + const parent = visit.itemAtPath(cst, path.slice(0, -1)); + const field = path[path.length - 1][0]; + const coll = parent?.[field]; + if (coll && "items" in coll) + return coll; + throw new Error("Parent collection not found"); + }; + function _visit(path, item, visitor) { + let ctrl = visitor(item, path); + if (typeof ctrl === "symbol") + return ctrl; + for (const field of ["key", "value"]) { + const token = item[field]; + if (token && "items" in token) { + for (let i = 0; i < token.items.length; ++i) { + const ci = _visit(Object.freeze(path.concat([[field, i]])), token.items[i], visitor); + if (typeof ci === "number") + i = ci - 1; + else if (ci === BREAK) + return BREAK; + else if (ci === REMOVE) { + token.items.splice(i, 1); + i -= 1; + } + } + if (typeof ctrl === "function" && field === "key") + ctrl = ctrl(item, path); + } + } + return typeof ctrl === "function" ? ctrl(item, path) : ctrl; + } + exports2.visit = visit; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/cst.js +var require_cst = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/cst.js"(exports2) { + "use strict"; + var cstScalar = require_cst_scalar(); + var cstStringify = require_cst_stringify(); + var cstVisit = require_cst_visit(); + var BOM = "\uFEFF"; + var DOCUMENT = ""; + var FLOW_END = ""; + var SCALAR = ""; + var isCollection = (token) => !!token && "items" in token; + var isScalar = (token) => !!token && (token.type === "scalar" || token.type === "single-quoted-scalar" || token.type === "double-quoted-scalar" || token.type === "block-scalar"); + function prettyToken(token) { + switch (token) { + case BOM: + return ""; + case DOCUMENT: + return ""; + case FLOW_END: + return ""; + case SCALAR: + return ""; + default: + return JSON.stringify(token); + } + } + function tokenType(source) { + switch (source) { + case BOM: + return "byte-order-mark"; + case DOCUMENT: + return "doc-mode"; + case FLOW_END: + return "flow-error-end"; + case SCALAR: + return "scalar"; + case "---": + return "doc-start"; + case "...": + return "doc-end"; + case "": + case "\n": + case "\r\n": + return "newline"; + case "-": + return "seq-item-ind"; + case "?": + return "explicit-key-ind"; + case ":": + return "map-value-ind"; + case "{": + return "flow-map-start"; + case "}": + return "flow-map-end"; + case "[": + return "flow-seq-start"; + case "]": + return "flow-seq-end"; + case ",": + return "comma"; + } + switch (source[0]) { + case " ": + case " ": + return "space"; + case "#": + return "comment"; + case "%": + return "directive-line"; + case "*": + return "alias"; + case "&": + return "anchor"; + case "!": + return "tag"; + case "'": + return "single-quoted-scalar"; + case '"': + return "double-quoted-scalar"; + case "|": + case ">": + return "block-scalar-header"; + } + return null; + } + exports2.createScalarToken = cstScalar.createScalarToken; + exports2.resolveAsScalar = cstScalar.resolveAsScalar; + exports2.setScalarValue = cstScalar.setScalarValue; + exports2.stringify = cstStringify.stringify; + exports2.visit = cstVisit.visit; + exports2.BOM = BOM; + exports2.DOCUMENT = DOCUMENT; + exports2.FLOW_END = FLOW_END; + exports2.SCALAR = SCALAR; + exports2.isCollection = isCollection; + exports2.isScalar = isScalar; + exports2.prettyToken = prettyToken; + exports2.tokenType = tokenType; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/lexer.js +var require_lexer = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/lexer.js"(exports2) { + "use strict"; + var cst = require_cst(); + function isEmpty(ch) { + switch (ch) { + case void 0: + case " ": + case "\n": + case "\r": + case " ": + return true; + default: + return false; + } + } + var hexDigits = new Set("0123456789ABCDEFabcdef"); + var tagChars = new Set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()"); + var flowIndicatorChars = new Set(",[]{}"); + var invalidAnchorChars = new Set(" ,[]{}\n\r "); + var isNotAnchorChar = (ch) => !ch || invalidAnchorChars.has(ch); + var Lexer = class { + constructor() { + this.atEnd = false; + this.blockScalarIndent = -1; + this.blockScalarKeep = false; + this.buffer = ""; + this.flowKey = false; + this.flowLevel = 0; + this.indentNext = 0; + this.indentValue = 0; + this.lineEndPos = null; + this.next = null; + this.pos = 0; + } + /** + * Generate YAML tokens from the `source` string. If `incomplete`, + * a part of the last line may be left as a buffer for the next call. + * + * @returns A generator of lexical tokens + */ + *lex(source, incomplete = false) { + if (source) { + if (typeof source !== "string") + throw TypeError("source is not a string"); + this.buffer = this.buffer ? this.buffer + source : source; + this.lineEndPos = null; + } + this.atEnd = !incomplete; + let next = this.next ?? "stream"; + while (next && (incomplete || this.hasChars(1))) + next = yield* this.parseNext(next); + } + atLineEnd() { + let i = this.pos; + let ch = this.buffer[i]; + while (ch === " " || ch === " ") + ch = this.buffer[++i]; + if (!ch || ch === "#" || ch === "\n") + return true; + if (ch === "\r") + return this.buffer[i + 1] === "\n"; + return false; + } + charAt(n) { + return this.buffer[this.pos + n]; + } + continueScalar(offset) { + let ch = this.buffer[offset]; + if (this.indentNext > 0) { + let indent = 0; + while (ch === " ") + ch = this.buffer[++indent + offset]; + if (ch === "\r") { + const next = this.buffer[indent + offset + 1]; + if (next === "\n" || !next && !this.atEnd) + return offset + indent + 1; + } + return ch === "\n" || indent >= this.indentNext || !ch && !this.atEnd ? offset + indent : -1; + } + if (ch === "-" || ch === ".") { + const dt = this.buffer.substr(offset, 3); + if ((dt === "---" || dt === "...") && isEmpty(this.buffer[offset + 3])) + return -1; + } + return offset; + } + getLine() { + let end = this.lineEndPos; + if (typeof end !== "number" || end !== -1 && end < this.pos) { + end = this.buffer.indexOf("\n", this.pos); + this.lineEndPos = end; + } + if (end === -1) + return this.atEnd ? this.buffer.substring(this.pos) : null; + if (this.buffer[end - 1] === "\r") + end -= 1; + return this.buffer.substring(this.pos, end); + } + hasChars(n) { + return this.pos + n <= this.buffer.length; + } + setNext(state) { + this.buffer = this.buffer.substring(this.pos); + this.pos = 0; + this.lineEndPos = null; + this.next = state; + return null; + } + peek(n) { + return this.buffer.substr(this.pos, n); + } + *parseNext(next) { + switch (next) { + case "stream": + return yield* this.parseStream(); + case "line-start": + return yield* this.parseLineStart(); + case "block-start": + return yield* this.parseBlockStart(); + case "doc": + return yield* this.parseDocument(); + case "flow": + return yield* this.parseFlowCollection(); + case "quoted-scalar": + return yield* this.parseQuotedScalar(); + case "block-scalar": + return yield* this.parseBlockScalar(); + case "plain-scalar": + return yield* this.parsePlainScalar(); + } + } + *parseStream() { + let line = this.getLine(); + if (line === null) + return this.setNext("stream"); + if (line[0] === cst.BOM) { + yield* this.pushCount(1); + line = line.substring(1); + } + if (line[0] === "%") { + let dirEnd = line.length; + let cs = line.indexOf("#"); + while (cs !== -1) { + const ch = line[cs - 1]; + if (ch === " " || ch === " ") { + dirEnd = cs - 1; + break; + } else { + cs = line.indexOf("#", cs + 1); + } + } + while (true) { + const ch = line[dirEnd - 1]; + if (ch === " " || ch === " ") + dirEnd -= 1; + else + break; + } + const n = (yield* this.pushCount(dirEnd)) + (yield* this.pushSpaces(true)); + yield* this.pushCount(line.length - n); + this.pushNewline(); + return "stream"; + } + if (this.atLineEnd()) { + const sp = yield* this.pushSpaces(true); + yield* this.pushCount(line.length - sp); + yield* this.pushNewline(); + return "stream"; + } + yield cst.DOCUMENT; + return yield* this.parseLineStart(); + } + *parseLineStart() { + const ch = this.charAt(0); + if (!ch && !this.atEnd) + return this.setNext("line-start"); + if (ch === "-" || ch === ".") { + if (!this.atEnd && !this.hasChars(4)) + return this.setNext("line-start"); + const s = this.peek(3); + if ((s === "---" || s === "...") && isEmpty(this.charAt(3))) { + yield* this.pushCount(3); + this.indentValue = 0; + this.indentNext = 0; + return s === "---" ? "doc" : "stream"; + } + } + this.indentValue = yield* this.pushSpaces(false); + if (this.indentNext > this.indentValue && !isEmpty(this.charAt(1))) + this.indentNext = this.indentValue; + return yield* this.parseBlockStart(); + } + *parseBlockStart() { + const [ch0, ch1] = this.peek(2); + if (!ch1 && !this.atEnd) + return this.setNext("block-start"); + if ((ch0 === "-" || ch0 === "?" || ch0 === ":") && isEmpty(ch1)) { + const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true)); + this.indentNext = this.indentValue + 1; + this.indentValue += n; + return yield* this.parseBlockStart(); + } + return "doc"; + } + *parseDocument() { + yield* this.pushSpaces(true); + const line = this.getLine(); + if (line === null) + return this.setNext("doc"); + let n = yield* this.pushIndicators(); + switch (line[n]) { + case "#": + yield* this.pushCount(line.length - n); + // fallthrough + case void 0: + yield* this.pushNewline(); + return yield* this.parseLineStart(); + case "{": + case "[": + yield* this.pushCount(1); + this.flowKey = false; + this.flowLevel = 1; + return "flow"; + case "}": + case "]": + yield* this.pushCount(1); + return "doc"; + case "*": + yield* this.pushUntil(isNotAnchorChar); + return "doc"; + case '"': + case "'": + return yield* this.parseQuotedScalar(); + case "|": + case ">": + n += yield* this.parseBlockScalarHeader(); + n += yield* this.pushSpaces(true); + yield* this.pushCount(line.length - n); + yield* this.pushNewline(); + return yield* this.parseBlockScalar(); + default: + return yield* this.parsePlainScalar(); + } + } + *parseFlowCollection() { + let nl, sp; + let indent = -1; + do { + nl = yield* this.pushNewline(); + if (nl > 0) { + sp = yield* this.pushSpaces(false); + this.indentValue = indent = sp; + } else { + sp = 0; + } + sp += yield* this.pushSpaces(true); + } while (nl + sp > 0); + const line = this.getLine(); + if (line === null) + return this.setNext("flow"); + if (indent !== -1 && indent < this.indentNext && line[0] !== "#" || indent === 0 && (line.startsWith("---") || line.startsWith("...")) && isEmpty(line[3])) { + const atFlowEndMarker = indent === this.indentNext - 1 && this.flowLevel === 1 && (line[0] === "]" || line[0] === "}"); + if (!atFlowEndMarker) { + this.flowLevel = 0; + yield cst.FLOW_END; + return yield* this.parseLineStart(); + } + } + let n = 0; + while (line[n] === ",") { + n += yield* this.pushCount(1); + n += yield* this.pushSpaces(true); + this.flowKey = false; + } + n += yield* this.pushIndicators(); + switch (line[n]) { + case void 0: + return "flow"; + case "#": + yield* this.pushCount(line.length - n); + return "flow"; + case "{": + case "[": + yield* this.pushCount(1); + this.flowKey = false; + this.flowLevel += 1; + return "flow"; + case "}": + case "]": + yield* this.pushCount(1); + this.flowKey = true; + this.flowLevel -= 1; + return this.flowLevel ? "flow" : "doc"; + case "*": + yield* this.pushUntil(isNotAnchorChar); + return "flow"; + case '"': + case "'": + this.flowKey = true; + return yield* this.parseQuotedScalar(); + case ":": { + const next = this.charAt(1); + if (this.flowKey || isEmpty(next) || next === ",") { + this.flowKey = false; + yield* this.pushCount(1); + yield* this.pushSpaces(true); + return "flow"; + } + } + // fallthrough + default: + this.flowKey = false; + return yield* this.parsePlainScalar(); + } + } + *parseQuotedScalar() { + const quote = this.charAt(0); + let end = this.buffer.indexOf(quote, this.pos + 1); + if (quote === "'") { + while (end !== -1 && this.buffer[end + 1] === "'") + end = this.buffer.indexOf("'", end + 2); + } else { + while (end !== -1) { + let n = 0; + while (this.buffer[end - 1 - n] === "\\") + n += 1; + if (n % 2 === 0) + break; + end = this.buffer.indexOf('"', end + 1); + } + } + const qb = this.buffer.substring(0, end); + let nl = qb.indexOf("\n", this.pos); + if (nl !== -1) { + while (nl !== -1) { + const cs = this.continueScalar(nl + 1); + if (cs === -1) + break; + nl = qb.indexOf("\n", cs); + } + if (nl !== -1) { + end = nl - (qb[nl - 1] === "\r" ? 2 : 1); + } + } + if (end === -1) { + if (!this.atEnd) + return this.setNext("quoted-scalar"); + end = this.buffer.length; + } + yield* this.pushToIndex(end + 1, false); + return this.flowLevel ? "flow" : "doc"; + } + *parseBlockScalarHeader() { + this.blockScalarIndent = -1; + this.blockScalarKeep = false; + let i = this.pos; + while (true) { + const ch = this.buffer[++i]; + if (ch === "+") + this.blockScalarKeep = true; + else if (ch > "0" && ch <= "9") + this.blockScalarIndent = Number(ch) - 1; + else if (ch !== "-") + break; + } + return yield* this.pushUntil((ch) => isEmpty(ch) || ch === "#"); + } + *parseBlockScalar() { + let nl = this.pos - 1; + let indent = 0; + let ch; + loop: for (let i2 = this.pos; ch = this.buffer[i2]; ++i2) { + switch (ch) { + case " ": + indent += 1; + break; + case "\n": + nl = i2; + indent = 0; + break; + case "\r": { + const next = this.buffer[i2 + 1]; + if (!next && !this.atEnd) + return this.setNext("block-scalar"); + if (next === "\n") + break; + } + // fallthrough + default: + break loop; + } + } + if (!ch && !this.atEnd) + return this.setNext("block-scalar"); + if (indent >= this.indentNext) { + if (this.blockScalarIndent === -1) + this.indentNext = indent; + else { + this.indentNext = this.blockScalarIndent + (this.indentNext === 0 ? 1 : this.indentNext); + } + do { + const cs = this.continueScalar(nl + 1); + if (cs === -1) + break; + nl = this.buffer.indexOf("\n", cs); + } while (nl !== -1); + if (nl === -1) { + if (!this.atEnd) + return this.setNext("block-scalar"); + nl = this.buffer.length; + } + } + let i = nl + 1; + ch = this.buffer[i]; + while (ch === " ") + ch = this.buffer[++i]; + if (ch === " ") { + while (ch === " " || ch === " " || ch === "\r" || ch === "\n") + ch = this.buffer[++i]; + nl = i - 1; + } else if (!this.blockScalarKeep) { + do { + let i2 = nl - 1; + let ch2 = this.buffer[i2]; + if (ch2 === "\r") + ch2 = this.buffer[--i2]; + const lastChar = i2; + while (ch2 === " ") + ch2 = this.buffer[--i2]; + if (ch2 === "\n" && i2 >= this.pos && i2 + 1 + indent > lastChar) + nl = i2; + else + break; + } while (true); + } + yield cst.SCALAR; + yield* this.pushToIndex(nl + 1, true); + return yield* this.parseLineStart(); + } + *parsePlainScalar() { + const inFlow = this.flowLevel > 0; + let end = this.pos - 1; + let i = this.pos - 1; + let ch; + while (ch = this.buffer[++i]) { + if (ch === ":") { + const next = this.buffer[i + 1]; + if (isEmpty(next) || inFlow && flowIndicatorChars.has(next)) + break; + end = i; + } else if (isEmpty(ch)) { + let next = this.buffer[i + 1]; + if (ch === "\r") { + if (next === "\n") { + i += 1; + ch = "\n"; + next = this.buffer[i + 1]; + } else + end = i; + } + if (next === "#" || inFlow && flowIndicatorChars.has(next)) + break; + if (ch === "\n") { + const cs = this.continueScalar(i + 1); + if (cs === -1) + break; + i = Math.max(i, cs - 2); + } + } else { + if (inFlow && flowIndicatorChars.has(ch)) + break; + end = i; + } + } + if (!ch && !this.atEnd) + return this.setNext("plain-scalar"); + yield cst.SCALAR; + yield* this.pushToIndex(end + 1, true); + return inFlow ? "flow" : "doc"; + } + *pushCount(n) { + if (n > 0) { + yield this.buffer.substr(this.pos, n); + this.pos += n; + return n; + } + return 0; + } + *pushToIndex(i, allowEmpty) { + const s = this.buffer.slice(this.pos, i); + if (s) { + yield s; + this.pos += s.length; + return s.length; + } else if (allowEmpty) + yield ""; + return 0; + } + *pushIndicators() { + switch (this.charAt(0)) { + case "!": + return (yield* this.pushTag()) + (yield* this.pushSpaces(true)) + (yield* this.pushIndicators()); + case "&": + return (yield* this.pushUntil(isNotAnchorChar)) + (yield* this.pushSpaces(true)) + (yield* this.pushIndicators()); + case "-": + // this is an error + case "?": + // this is an error outside flow collections + case ":": { + const inFlow = this.flowLevel > 0; + const ch1 = this.charAt(1); + if (isEmpty(ch1) || inFlow && flowIndicatorChars.has(ch1)) { + if (!inFlow) + this.indentNext = this.indentValue + 1; + else if (this.flowKey) + this.flowKey = false; + return (yield* this.pushCount(1)) + (yield* this.pushSpaces(true)) + (yield* this.pushIndicators()); + } + } + } + return 0; + } + *pushTag() { + if (this.charAt(1) === "<") { + let i = this.pos + 2; + let ch = this.buffer[i]; + while (!isEmpty(ch) && ch !== ">") + ch = this.buffer[++i]; + return yield* this.pushToIndex(ch === ">" ? i + 1 : i, false); + } else { + let i = this.pos + 1; + let ch = this.buffer[i]; + while (ch) { + if (tagChars.has(ch)) + ch = this.buffer[++i]; + else if (ch === "%" && hexDigits.has(this.buffer[i + 1]) && hexDigits.has(this.buffer[i + 2])) { + ch = this.buffer[i += 3]; + } else + break; + } + return yield* this.pushToIndex(i, false); + } + } + *pushNewline() { + const ch = this.buffer[this.pos]; + if (ch === "\n") + return yield* this.pushCount(1); + else if (ch === "\r" && this.charAt(1) === "\n") + return yield* this.pushCount(2); + else + return 0; + } + *pushSpaces(allowTabs) { + let i = this.pos - 1; + let ch; + do { + ch = this.buffer[++i]; + } while (ch === " " || allowTabs && ch === " "); + const n = i - this.pos; + if (n > 0) { + yield this.buffer.substr(this.pos, n); + this.pos = i; + } + return n; + } + *pushUntil(test) { + let i = this.pos; + let ch = this.buffer[i]; + while (!test(ch)) + ch = this.buffer[++i]; + return yield* this.pushToIndex(i, false); + } + }; + exports2.Lexer = Lexer; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/line-counter.js +var require_line_counter = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/line-counter.js"(exports2) { + "use strict"; + var LineCounter = class { + constructor() { + this.lineStarts = []; + this.addNewLine = (offset) => this.lineStarts.push(offset); + this.linePos = (offset) => { + let low = 0; + let high = this.lineStarts.length; + while (low < high) { + const mid = low + high >> 1; + if (this.lineStarts[mid] < offset) + low = mid + 1; + else + high = mid; + } + if (this.lineStarts[low] === offset) + return { line: low + 1, col: 1 }; + if (low === 0) + return { line: 0, col: offset }; + const start = this.lineStarts[low - 1]; + return { line: low, col: offset - start + 1 }; + }; + } + }; + exports2.LineCounter = LineCounter; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/parser.js +var require_parser = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/parse/parser.js"(exports2) { + "use strict"; + var node_process = require("process"); + var cst = require_cst(); + var lexer = require_lexer(); + function includesToken(list, type) { + for (let i = 0; i < list.length; ++i) + if (list[i].type === type) + return true; + return false; + } + function findNonEmptyIndex(list) { + for (let i = 0; i < list.length; ++i) { + switch (list[i].type) { + case "space": + case "comment": + case "newline": + break; + default: + return i; + } + } + return -1; + } + function isFlowToken(token) { + switch (token?.type) { + case "alias": + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": + case "flow-collection": + return true; + default: + return false; + } + } + function getPrevProps(parent) { + switch (parent.type) { + case "document": + return parent.start; + case "block-map": { + const it = parent.items[parent.items.length - 1]; + return it.sep ?? it.start; + } + case "block-seq": + return parent.items[parent.items.length - 1].start; + /* istanbul ignore next should not happen */ + default: + return []; + } + } + function getFirstKeyStartProps(prev) { + if (prev.length === 0) + return []; + let i = prev.length; + loop: while (--i >= 0) { + switch (prev[i].type) { + case "doc-start": + case "explicit-key-ind": + case "map-value-ind": + case "seq-item-ind": + case "newline": + break loop; + } + } + while (prev[++i]?.type === "space") { + } + return prev.splice(i, prev.length); + } + function fixFlowSeqItems(fc) { + if (fc.start.type === "flow-seq-start") { + for (const it of fc.items) { + if (it.sep && !it.value && !includesToken(it.start, "explicit-key-ind") && !includesToken(it.sep, "map-value-ind")) { + if (it.key) + it.value = it.key; + delete it.key; + if (isFlowToken(it.value)) { + if (it.value.end) + Array.prototype.push.apply(it.value.end, it.sep); + else + it.value.end = it.sep; + } else + Array.prototype.push.apply(it.start, it.sep); + delete it.sep; + } + } + } + } + var Parser = class { + /** + * @param onNewLine - If defined, called separately with the start position of + * each new line (in `parse()`, including the start of input). + */ + constructor(onNewLine) { + this.atNewLine = true; + this.atScalar = false; + this.indent = 0; + this.offset = 0; + this.onKeyLine = false; + this.stack = []; + this.source = ""; + this.type = ""; + this.lexer = new lexer.Lexer(); + this.onNewLine = onNewLine; + } + /** + * Parse `source` as a YAML stream. + * If `incomplete`, a part of the last line may be left as a buffer for the next call. + * + * Errors are not thrown, but yielded as `{ type: 'error', message }` tokens. + * + * @returns A generator of tokens representing each directive, document, and other structure. + */ + *parse(source, incomplete = false) { + if (this.onNewLine && this.offset === 0) + this.onNewLine(0); + for (const lexeme of this.lexer.lex(source, incomplete)) + yield* this.next(lexeme); + if (!incomplete) + yield* this.end(); + } + /** + * Advance the parser by the `source` of one lexical token. + */ + *next(source) { + this.source = source; + if (node_process.env.LOG_TOKENS) + console.log("|", cst.prettyToken(source)); + if (this.atScalar) { + this.atScalar = false; + yield* this.step(); + this.offset += source.length; + return; + } + const type = cst.tokenType(source); + if (!type) { + const message = `Not a YAML token: ${source}`; + yield* this.pop({ type: "error", offset: this.offset, message, source }); + this.offset += source.length; + } else if (type === "scalar") { + this.atNewLine = false; + this.atScalar = true; + this.type = "scalar"; + } else { + this.type = type; + yield* this.step(); + switch (type) { + case "newline": + this.atNewLine = true; + this.indent = 0; + if (this.onNewLine) + this.onNewLine(this.offset + source.length); + break; + case "space": + if (this.atNewLine && source[0] === " ") + this.indent += source.length; + break; + case "explicit-key-ind": + case "map-value-ind": + case "seq-item-ind": + if (this.atNewLine) + this.indent += source.length; + break; + case "doc-mode": + case "flow-error-end": + return; + default: + this.atNewLine = false; + } + this.offset += source.length; + } + } + /** Call at end of input to push out any remaining constructions */ + *end() { + while (this.stack.length > 0) + yield* this.pop(); + } + get sourceToken() { + const st = { + type: this.type, + offset: this.offset, + indent: this.indent, + source: this.source + }; + return st; + } + *step() { + const top = this.peek(1); + if (this.type === "doc-end" && top?.type !== "doc-end") { + while (this.stack.length > 0) + yield* this.pop(); + this.stack.push({ + type: "doc-end", + offset: this.offset, + source: this.source + }); + return; + } + if (!top) + return yield* this.stream(); + switch (top.type) { + case "document": + return yield* this.document(top); + case "alias": + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": + return yield* this.scalar(top); + case "block-scalar": + return yield* this.blockScalar(top); + case "block-map": + return yield* this.blockMap(top); + case "block-seq": + return yield* this.blockSequence(top); + case "flow-collection": + return yield* this.flowCollection(top); + case "doc-end": + return yield* this.documentEnd(top); + } + yield* this.pop(); + } + peek(n) { + return this.stack[this.stack.length - n]; + } + *pop(error) { + const token = error ?? this.stack.pop(); + if (!token) { + const message = "Tried to pop an empty stack"; + yield { type: "error", offset: this.offset, source: "", message }; + } else if (this.stack.length === 0) { + yield token; + } else { + const top = this.peek(1); + if (token.type === "block-scalar") { + token.indent = "indent" in top ? top.indent : 0; + } else if (token.type === "flow-collection" && top.type === "document") { + token.indent = 0; + } + if (token.type === "flow-collection") + fixFlowSeqItems(token); + switch (top.type) { + case "document": + top.value = token; + break; + case "block-scalar": + top.props.push(token); + break; + case "block-map": { + const it = top.items[top.items.length - 1]; + if (it.value) { + top.items.push({ start: [], key: token, sep: [] }); + this.onKeyLine = true; + return; + } else if (it.sep) { + it.value = token; + } else { + Object.assign(it, { key: token, sep: [] }); + this.onKeyLine = !it.explicitKey; + return; + } + break; + } + case "block-seq": { + const it = top.items[top.items.length - 1]; + if (it.value) + top.items.push({ start: [], value: token }); + else + it.value = token; + break; + } + case "flow-collection": { + const it = top.items[top.items.length - 1]; + if (!it || it.value) + top.items.push({ start: [], key: token, sep: [] }); + else if (it.sep) + it.value = token; + else + Object.assign(it, { key: token, sep: [] }); + return; + } + /* istanbul ignore next should not happen */ + default: + yield* this.pop(); + yield* this.pop(token); + } + if ((top.type === "document" || top.type === "block-map" || top.type === "block-seq") && (token.type === "block-map" || token.type === "block-seq")) { + const last = token.items[token.items.length - 1]; + if (last && !last.sep && !last.value && last.start.length > 0 && findNonEmptyIndex(last.start) === -1 && (token.indent === 0 || last.start.every((st) => st.type !== "comment" || st.indent < token.indent))) { + if (top.type === "document") + top.end = last.start; + else + top.items.push({ start: last.start }); + token.items.splice(-1, 1); + } + } + } + } + *stream() { + switch (this.type) { + case "directive-line": + yield { type: "directive", offset: this.offset, source: this.source }; + return; + case "byte-order-mark": + case "space": + case "comment": + case "newline": + yield this.sourceToken; + return; + case "doc-mode": + case "doc-start": { + const doc = { + type: "document", + offset: this.offset, + start: [] + }; + if (this.type === "doc-start") + doc.start.push(this.sourceToken); + this.stack.push(doc); + return; + } + } + yield { + type: "error", + offset: this.offset, + message: `Unexpected ${this.type} token in YAML stream`, + source: this.source + }; + } + *document(doc) { + if (doc.value) + return yield* this.lineEnd(doc); + switch (this.type) { + case "doc-start": { + if (findNonEmptyIndex(doc.start) !== -1) { + yield* this.pop(); + yield* this.step(); + } else + doc.start.push(this.sourceToken); + return; + } + case "anchor": + case "tag": + case "space": + case "comment": + case "newline": + doc.start.push(this.sourceToken); + return; + } + const bv = this.startBlockValue(doc); + if (bv) + this.stack.push(bv); + else { + yield { + type: "error", + offset: this.offset, + message: `Unexpected ${this.type} token in YAML document`, + source: this.source + }; + } + } + *scalar(scalar) { + if (this.type === "map-value-ind") { + const prev = getPrevProps(this.peek(2)); + const start = getFirstKeyStartProps(prev); + let sep; + if (scalar.end) { + sep = scalar.end; + sep.push(this.sourceToken); + delete scalar.end; + } else + sep = [this.sourceToken]; + const map = { + type: "block-map", + offset: scalar.offset, + indent: scalar.indent, + items: [{ start, key: scalar, sep }] + }; + this.onKeyLine = true; + this.stack[this.stack.length - 1] = map; + } else + yield* this.lineEnd(scalar); + } + *blockScalar(scalar) { + switch (this.type) { + case "space": + case "comment": + case "newline": + scalar.props.push(this.sourceToken); + return; + case "scalar": + scalar.source = this.source; + this.atNewLine = true; + this.indent = 0; + if (this.onNewLine) { + let nl = this.source.indexOf("\n") + 1; + while (nl !== 0) { + this.onNewLine(this.offset + nl); + nl = this.source.indexOf("\n", nl) + 1; + } + } + yield* this.pop(); + break; + /* istanbul ignore next should not happen */ + default: + yield* this.pop(); + yield* this.step(); + } + } + *blockMap(map) { + const it = map.items[map.items.length - 1]; + switch (this.type) { + case "newline": + this.onKeyLine = false; + if (it.value) { + const end = "end" in it.value ? it.value.end : void 0; + const last = Array.isArray(end) ? end[end.length - 1] : void 0; + if (last?.type === "comment") + end?.push(this.sourceToken); + else + map.items.push({ start: [this.sourceToken] }); + } else if (it.sep) { + it.sep.push(this.sourceToken); + } else { + it.start.push(this.sourceToken); + } + return; + case "space": + case "comment": + if (it.value) { + map.items.push({ start: [this.sourceToken] }); + } else if (it.sep) { + it.sep.push(this.sourceToken); + } else { + if (this.atIndentedComment(it.start, map.indent)) { + const prev = map.items[map.items.length - 2]; + const end = prev?.value?.end; + if (Array.isArray(end)) { + Array.prototype.push.apply(end, it.start); + end.push(this.sourceToken); + map.items.pop(); + return; + } + } + it.start.push(this.sourceToken); + } + return; + } + if (this.indent >= map.indent) { + const atMapIndent = !this.onKeyLine && this.indent === map.indent; + const atNextItem = atMapIndent && (it.sep || it.explicitKey) && this.type !== "seq-item-ind"; + let start = []; + if (atNextItem && it.sep && !it.value) { + const nl = []; + for (let i = 0; i < it.sep.length; ++i) { + const st = it.sep[i]; + switch (st.type) { + case "newline": + nl.push(i); + break; + case "space": + break; + case "comment": + if (st.indent > map.indent) + nl.length = 0; + break; + default: + nl.length = 0; + } + } + if (nl.length >= 2) + start = it.sep.splice(nl[1]); + } + switch (this.type) { + case "anchor": + case "tag": + if (atNextItem || it.value) { + start.push(this.sourceToken); + map.items.push({ start }); + this.onKeyLine = true; + } else if (it.sep) { + it.sep.push(this.sourceToken); + } else { + it.start.push(this.sourceToken); + } + return; + case "explicit-key-ind": + if (!it.sep && !it.explicitKey) { + it.start.push(this.sourceToken); + it.explicitKey = true; + } else if (atNextItem || it.value) { + start.push(this.sourceToken); + map.items.push({ start, explicitKey: true }); + } else { + this.stack.push({ + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ start: [this.sourceToken], explicitKey: true }] + }); + } + this.onKeyLine = true; + return; + case "map-value-ind": + if (it.explicitKey) { + if (!it.sep) { + if (includesToken(it.start, "newline")) { + Object.assign(it, { key: null, sep: [this.sourceToken] }); + } else { + const start2 = getFirstKeyStartProps(it.start); + this.stack.push({ + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ start: start2, key: null, sep: [this.sourceToken] }] + }); + } + } else if (it.value) { + map.items.push({ start: [], key: null, sep: [this.sourceToken] }); + } else if (includesToken(it.sep, "map-value-ind")) { + this.stack.push({ + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }); + } else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) { + const start2 = getFirstKeyStartProps(it.start); + const key = it.key; + const sep = it.sep; + sep.push(this.sourceToken); + delete it.key; + delete it.sep; + this.stack.push({ + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ start: start2, key, sep }] + }); + } else if (start.length > 0) { + it.sep = it.sep.concat(start, this.sourceToken); + } else { + it.sep.push(this.sourceToken); + } + } else { + if (!it.sep) { + Object.assign(it, { key: null, sep: [this.sourceToken] }); + } else if (it.value || atNextItem) { + map.items.push({ start, key: null, sep: [this.sourceToken] }); + } else if (includesToken(it.sep, "map-value-ind")) { + this.stack.push({ + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ start: [], key: null, sep: [this.sourceToken] }] + }); + } else { + it.sep.push(this.sourceToken); + } + } + this.onKeyLine = true; + return; + case "alias": + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": { + const fs = this.flowScalar(this.type); + if (atNextItem || it.value) { + map.items.push({ start, key: fs, sep: [] }); + this.onKeyLine = true; + } else if (it.sep) { + this.stack.push(fs); + } else { + Object.assign(it, { key: fs, sep: [] }); + this.onKeyLine = true; + } + return; + } + default: { + const bv = this.startBlockValue(map); + if (bv) { + if (bv.type === "block-seq") { + if (!it.explicitKey && it.sep && !includesToken(it.sep, "newline")) { + yield* this.pop({ + type: "error", + offset: this.offset, + message: "Unexpected block-seq-ind on same line with key", + source: this.source + }); + return; + } + } else if (atMapIndent) { + map.items.push({ start }); + } + this.stack.push(bv); + return; + } + } + } + } + yield* this.pop(); + yield* this.step(); + } + *blockSequence(seq) { + const it = seq.items[seq.items.length - 1]; + switch (this.type) { + case "newline": + if (it.value) { + const end = "end" in it.value ? it.value.end : void 0; + const last = Array.isArray(end) ? end[end.length - 1] : void 0; + if (last?.type === "comment") + end?.push(this.sourceToken); + else + seq.items.push({ start: [this.sourceToken] }); + } else + it.start.push(this.sourceToken); + return; + case "space": + case "comment": + if (it.value) + seq.items.push({ start: [this.sourceToken] }); + else { + if (this.atIndentedComment(it.start, seq.indent)) { + const prev = seq.items[seq.items.length - 2]; + const end = prev?.value?.end; + if (Array.isArray(end)) { + Array.prototype.push.apply(end, it.start); + end.push(this.sourceToken); + seq.items.pop(); + return; + } + } + it.start.push(this.sourceToken); + } + return; + case "anchor": + case "tag": + if (it.value || this.indent <= seq.indent) + break; + it.start.push(this.sourceToken); + return; + case "seq-item-ind": + if (this.indent !== seq.indent) + break; + if (it.value || includesToken(it.start, "seq-item-ind")) + seq.items.push({ start: [this.sourceToken] }); + else + it.start.push(this.sourceToken); + return; + } + if (this.indent > seq.indent) { + const bv = this.startBlockValue(seq); + if (bv) { + this.stack.push(bv); + return; + } + } + yield* this.pop(); + yield* this.step(); + } + *flowCollection(fc) { + const it = fc.items[fc.items.length - 1]; + if (this.type === "flow-error-end") { + let top; + do { + yield* this.pop(); + top = this.peek(1); + } while (top?.type === "flow-collection"); + } else if (fc.end.length === 0) { + switch (this.type) { + case "comma": + case "explicit-key-ind": + if (!it || it.sep) + fc.items.push({ start: [this.sourceToken] }); + else + it.start.push(this.sourceToken); + return; + case "map-value-ind": + if (!it || it.value) + fc.items.push({ start: [], key: null, sep: [this.sourceToken] }); + else if (it.sep) + it.sep.push(this.sourceToken); + else + Object.assign(it, { key: null, sep: [this.sourceToken] }); + return; + case "space": + case "comment": + case "newline": + case "anchor": + case "tag": + if (!it || it.value) + fc.items.push({ start: [this.sourceToken] }); + else if (it.sep) + it.sep.push(this.sourceToken); + else + it.start.push(this.sourceToken); + return; + case "alias": + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": { + const fs = this.flowScalar(this.type); + if (!it || it.value) + fc.items.push({ start: [], key: fs, sep: [] }); + else if (it.sep) + this.stack.push(fs); + else + Object.assign(it, { key: fs, sep: [] }); + return; + } + case "flow-map-end": + case "flow-seq-end": + fc.end.push(this.sourceToken); + return; + } + const bv = this.startBlockValue(fc); + if (bv) + this.stack.push(bv); + else { + yield* this.pop(); + yield* this.step(); + } + } else { + const parent = this.peek(2); + if (parent.type === "block-map" && (this.type === "map-value-ind" && parent.indent === fc.indent || this.type === "newline" && !parent.items[parent.items.length - 1].sep)) { + yield* this.pop(); + yield* this.step(); + } else if (this.type === "map-value-ind" && parent.type !== "flow-collection") { + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + fixFlowSeqItems(fc); + const sep = fc.end.splice(1, fc.end.length); + sep.push(this.sourceToken); + const map = { + type: "block-map", + offset: fc.offset, + indent: fc.indent, + items: [{ start, key: fc, sep }] + }; + this.onKeyLine = true; + this.stack[this.stack.length - 1] = map; + } else { + yield* this.lineEnd(fc); + } + } + } + flowScalar(type) { + if (this.onNewLine) { + let nl = this.source.indexOf("\n") + 1; + while (nl !== 0) { + this.onNewLine(this.offset + nl); + nl = this.source.indexOf("\n", nl) + 1; + } + } + return { + type, + offset: this.offset, + indent: this.indent, + source: this.source + }; + } + startBlockValue(parent) { + switch (this.type) { + case "alias": + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": + return this.flowScalar(this.type); + case "block-scalar-header": + return { + type: "block-scalar", + offset: this.offset, + indent: this.indent, + props: [this.sourceToken], + source: "" + }; + case "flow-map-start": + case "flow-seq-start": + return { + type: "flow-collection", + offset: this.offset, + indent: this.indent, + start: this.sourceToken, + items: [], + end: [] + }; + case "seq-item-ind": + return { + type: "block-seq", + offset: this.offset, + indent: this.indent, + items: [{ start: [this.sourceToken] }] + }; + case "explicit-key-ind": { + this.onKeyLine = true; + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + start.push(this.sourceToken); + return { + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ start, explicitKey: true }] + }; + } + case "map-value-ind": { + this.onKeyLine = true; + const prev = getPrevProps(parent); + const start = getFirstKeyStartProps(prev); + return { + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ start, key: null, sep: [this.sourceToken] }] + }; + } + } + return null; + } + atIndentedComment(start, indent) { + if (this.type !== "comment") + return false; + if (this.indent <= indent) + return false; + return start.every((st) => st.type === "newline" || st.type === "space"); + } + *documentEnd(docEnd) { + if (this.type !== "doc-mode") { + if (docEnd.end) + docEnd.end.push(this.sourceToken); + else + docEnd.end = [this.sourceToken]; + if (this.type === "newline") + yield* this.pop(); + } + } + *lineEnd(token) { + switch (this.type) { + case "comma": + case "doc-start": + case "doc-end": + case "flow-seq-end": + case "flow-map-end": + case "map-value-ind": + yield* this.pop(); + yield* this.step(); + break; + case "newline": + this.onKeyLine = false; + // fallthrough + case "space": + case "comment": + default: + if (token.end) + token.end.push(this.sourceToken); + else + token.end = [this.sourceToken]; + if (this.type === "newline") + yield* this.pop(); + } + } + }; + exports2.Parser = Parser; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/public-api.js +var require_public_api = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/public-api.js"(exports2) { + "use strict"; + var composer = require_composer(); + var Document = require_Document(); + var errors = require_errors(); + var log = require_log(); + var identity = require_identity(); + var lineCounter = require_line_counter(); + var parser = require_parser(); + function parseOptions(options) { + const prettyErrors = options.prettyErrors !== false; + const lineCounter$1 = options.lineCounter || prettyErrors && new lineCounter.LineCounter() || null; + return { lineCounter: lineCounter$1, prettyErrors }; + } + function parseAllDocuments(source, options = {}) { + const { lineCounter: lineCounter2, prettyErrors } = parseOptions(options); + const parser$1 = new parser.Parser(lineCounter2?.addNewLine); + const composer$1 = new composer.Composer(options); + const docs = Array.from(composer$1.compose(parser$1.parse(source))); + if (prettyErrors && lineCounter2) + for (const doc of docs) { + doc.errors.forEach(errors.prettifyError(source, lineCounter2)); + doc.warnings.forEach(errors.prettifyError(source, lineCounter2)); + } + if (docs.length > 0) + return docs; + return Object.assign([], { empty: true }, composer$1.streamInfo()); + } + function parseDocument(source, options = {}) { + const { lineCounter: lineCounter2, prettyErrors } = parseOptions(options); + const parser$1 = new parser.Parser(lineCounter2?.addNewLine); + const composer$1 = new composer.Composer(options); + let doc = null; + for (const _doc of composer$1.compose(parser$1.parse(source), true, source.length)) { + if (!doc) + doc = _doc; + else if (doc.options.logLevel !== "silent") { + doc.errors.push(new errors.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()")); + break; + } + } + if (prettyErrors && lineCounter2) { + doc.errors.forEach(errors.prettifyError(source, lineCounter2)); + doc.warnings.forEach(errors.prettifyError(source, lineCounter2)); + } + return doc; + } + function parse2(src, reviver, options) { + let _reviver = void 0; + if (typeof reviver === "function") { + _reviver = reviver; + } else if (options === void 0 && reviver && typeof reviver === "object") { + options = reviver; + } + const doc = parseDocument(src, options); + if (!doc) + return null; + doc.warnings.forEach((warning) => log.warn(doc.options.logLevel, warning)); + if (doc.errors.length > 0) { + if (doc.options.logLevel !== "silent") + throw doc.errors[0]; + else + doc.errors = []; + } + return doc.toJS(Object.assign({ reviver: _reviver }, options)); + } + function stringify(value, replacer, options) { + let _replacer = null; + if (typeof replacer === "function" || Array.isArray(replacer)) { + _replacer = replacer; + } else if (options === void 0 && replacer) { + options = replacer; + } + if (typeof options === "string") + options = options.length; + if (typeof options === "number") { + const indent = Math.round(options); + options = indent < 1 ? void 0 : indent > 8 ? { indent: 8 } : { indent }; + } + if (value === void 0) { + const { keepUndefined } = options ?? replacer ?? {}; + if (!keepUndefined) + return void 0; + } + if (identity.isDocument(value) && !_replacer) + return value.toString(options); + return new Document.Document(value, _replacer, options).toString(options); + } + exports2.parse = parse2; + exports2.parseAllDocuments = parseAllDocuments; + exports2.parseDocument = parseDocument; + exports2.stringify = stringify; + } +}); + +// node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/index.js +var require_dist = __commonJS({ + "node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/index.js"(exports2) { + "use strict"; + var composer = require_composer(); + var Document = require_Document(); + var Schema = require_Schema(); + var errors = require_errors(); + var Alias = require_Alias(); + var identity = require_identity(); + var Pair = require_Pair(); + var Scalar = require_Scalar(); + var YAMLMap = require_YAMLMap(); + var YAMLSeq = require_YAMLSeq(); + var cst = require_cst(); + var lexer = require_lexer(); + var lineCounter = require_line_counter(); + var parser = require_parser(); + var publicApi = require_public_api(); + var visit = require_visit(); + exports2.Composer = composer.Composer; + exports2.Document = Document.Document; + exports2.Schema = Schema.Schema; + exports2.YAMLError = errors.YAMLError; + exports2.YAMLParseError = errors.YAMLParseError; + exports2.YAMLWarning = errors.YAMLWarning; + exports2.Alias = Alias.Alias; + exports2.isAlias = identity.isAlias; + exports2.isCollection = identity.isCollection; + exports2.isDocument = identity.isDocument; + exports2.isMap = identity.isMap; + exports2.isNode = identity.isNode; + exports2.isPair = identity.isPair; + exports2.isScalar = identity.isScalar; + exports2.isSeq = identity.isSeq; + exports2.Pair = Pair.Pair; + exports2.Scalar = Scalar.Scalar; + exports2.YAMLMap = YAMLMap.YAMLMap; + exports2.YAMLSeq = YAMLSeq.YAMLSeq; + exports2.CST = cst; + exports2.Lexer = lexer.Lexer; + exports2.LineCounter = lineCounter.LineCounter; + exports2.Parser = parser.Parser; + exports2.parse = publicApi.parse; + exports2.parseAllDocuments = publicApi.parseAllDocuments; + exports2.parseDocument = publicApi.parseDocument; + exports2.stringify = publicApi.stringify; + exports2.visit = visit.visit; + exports2.visitAsync = visit.visitAsync; + } +}); + +// plugins/workos/skills/workos-widgets/references/scripts/query-spec.ts +var query_spec_exports = {}; +__export(query_spec_exports, { + WIDGET_PREFIXES: () => WIDGET_PREFIXES, + extractEndpoints: () => extractEndpoints, + formatEndpoint: () => formatEndpoint, + groupPathsByWidget: () => groupPathsByWidget, + loadSpec: () => loadSpec, + resolveRef: () => resolveRef, + resolveSchema: () => resolveSchema +}); +module.exports = __toCommonJS(query_spec_exports); +var import_node_fs = require("node:fs"); +var import_node_path = require("node:path"); +var import_yaml = __toESM(require_dist(), 1); +var import_meta = {}; +var _dir = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path.dirname)(new URL(import_meta.url).pathname); +var SPEC_PATH = (0, import_node_path.join)(_dir, "../widgets-open-api-spec.yaml"); +var WIDGET_PREFIXES = { + // Primary names (match SKILL.md widget slugs) + "user-management": "/_widgets/UserManagement", + "user-profile": "/_widgets/UserProfile", + "admin-portal-sso-connection": "/_widgets/admin-portal/sso-connections", + "admin-portal-domain-verification": "/_widgets/admin-portal/organization-domains", + // Alternate forms + usermanagement: "/_widgets/UserManagement", + userprofile: "/_widgets/UserProfile", + "admin-portal": "/_widgets/admin-portal", + adminportal: "/_widgets/admin-portal", + "sso-connection": "/_widgets/admin-portal/sso-connections", + "domain-verification": "/_widgets/admin-portal/organization-domains", + apikeys: "/_widgets/ApiKeys", + dataintegrations: "/_widgets/DataIntegrations", + "directory-sync": "/_widgets/directory-sync", + settings: "/_widgets/settings" +}; +function loadSpec(path) { + return (0, import_yaml.parse)((0, import_node_fs.readFileSync)(path ?? SPEC_PATH, "utf-8")); +} +function resolveRef(spec, ref) { + const parts = ref.replace("#/", "").split("/"); + let current = spec; + for (const part of parts) { + if (current && typeof current === "object") { + current = current[part]; + } else { + return void 0; + } + } + return current; +} +function resolveSchema(spec, schema) { + if (!schema || typeof schema !== "object") return schema; + if (Array.isArray(schema)) { + return schema.map((item) => resolveSchema(spec, item)); + } + const s = schema; + if (s.$ref && typeof s.$ref === "string") { + return resolveSchema(spec, resolveRef(spec, s.$ref)); + } + const result = {}; + for (const [key, value] of Object.entries(s)) { + if (value && typeof value === "object") { + result[key] = resolveSchema(spec, value); + } else { + result[key] = value; + } + } + return result; +} +function extractEndpoints(spec, pathFilter) { + const paths = spec.paths; + if (!paths) return []; + const endpoints = []; + for (const [path, methods] of Object.entries(paths)) { + if (!pathFilter(path)) continue; + for (const [method, operation] of Object.entries(methods)) { + if (!operation || typeof operation !== "object") continue; + const op = operation; + const responses = {}; + const opResponses = op.responses; + if (opResponses) { + for (const [code, resp] of Object.entries(opResponses)) { + const r = resp; + const content = r?.content; + const json = content?.["application/json"]; + if (json?.schema) { + responses[code] = { + description: r.description, + schema: resolveSchema(spec, json.schema) + }; + } else { + responses[code] = { description: r?.description }; + } + } + } + const endpoint = { + path, + method: method.toUpperCase(), + responses + }; + if (op.description) endpoint.description = op.description; + if (op.parameters) endpoint.parameters = op.parameters; + if (op.requestBody) { + const body = op.requestBody; + const content = body.content; + const json = content?.["application/json"]; + if (json?.schema) { + endpoint.requestBody = resolveSchema(spec, json.schema); + } + } + endpoints.push(endpoint); + } + } + return endpoints; +} +function formatEndpoint(ep) { + const lines = []; + lines.push(`## ${ep.method} ${ep.path}`); + if (ep.description) lines.push(` +${ep.description}`); + if (ep.parameters && Array.isArray(ep.parameters) && ep.parameters.length > 0) { + lines.push("\n### Parameters\n"); + for (const p of ep.parameters) { + const param = p; + lines.push(`- \`${param.name}\` (${param.in}): ${param.schema ? JSON.stringify(param.schema) : "unknown"}`); + } + } + if (ep.requestBody) { + lines.push("\n### Request Body\n"); + lines.push("```json"); + lines.push(JSON.stringify(ep.requestBody, null, 2)); + lines.push("```"); + } + for (const [code, resp] of Object.entries(ep.responses)) { + const r = resp; + lines.push(` +### Response ${code}${r.description ? ` \u2014 ${r.description}` : ""} +`); + if (r.schema) { + lines.push("```json"); + lines.push(JSON.stringify(r.schema, null, 2)); + lines.push("```"); + } + } + return lines.join("\n"); +} +function groupPathsByWidget(paths) { + const grouped = {}; + for (const p of paths) { + const parts = p.split("/").filter(Boolean); + const group = parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0] || "other"; + if (!grouped[group]) grouped[group] = []; + grouped[group].push(p); + } + return grouped; +} +var isDirectExecution = process.argv[1]?.includes("query-spec"); +if (isDirectExecution) { + const args = process.argv.slice(2); + if (args.includes("--list")) { + const spec2 = loadSpec(); + const grouped = groupPathsByWidget(Object.keys(spec2.paths ?? {})); + for (const [group, groupPaths] of Object.entries(grouped)) { + console.log(` +${group}:`); + for (const p of groupPaths) console.log(` ${p}`); + } + process.exit(0); + } + const widgetIdx = args.indexOf("--widget"); + const pathIdx = args.indexOf("--path"); + if (widgetIdx === -1 && pathIdx === -1) { + console.error("Usage: query-spec.ts --widget | --path | --list"); + console.error("Widgets:", Object.keys(WIDGET_PREFIXES).join(", ")); + process.exit(1); + } + const spec = loadSpec(); + let filter; + if (widgetIdx !== -1) { + const widgetName = (args[widgetIdx + 1] || "").toLowerCase(); + const prefix = WIDGET_PREFIXES[widgetName]; + if (!prefix) { + console.error(`Unknown widget: ${args[widgetIdx + 1]}`); + console.error("Known widgets:", Object.keys(WIDGET_PREFIXES).join(", ")); + process.exit(1); + } + filter = (path) => path.startsWith(prefix); + } else { + const pathPattern = args[pathIdx + 1] || ""; + filter = (path) => path.includes(pathPattern); + } + const endpoints = extractEndpoints(spec, filter); + if (endpoints.length === 0) { + console.error("No matching endpoints found."); + process.exit(1); + } + console.log(`# Matched ${endpoints.length} endpoint(s) +`); + for (const ep of endpoints) { + console.log(formatEndpoint(ep)); + console.log("\n---\n"); + } +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + WIDGET_PREFIXES, + extractEndpoints, + formatEndpoint, + groupPathsByWidget, + loadSpec, + resolveRef, + resolveSchema +}); diff --git a/plugins/workos/skills/workos-widgets/references/scripts/query-spec.ts b/plugins/workos/skills/workos-widgets/references/scripts/query-spec.ts new file mode 100644 index 00000000..c1d508fd --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/scripts/query-spec.ts @@ -0,0 +1,239 @@ +/** + * Query the WorkOS Widgets OpenAPI spec for specific widget endpoints. + * + * Source file -- build with: pnpm build:query-spec + * Runtime usage (via bundled .cjs): + * node references/scripts/query-spec.cjs --widget UserProfile + * node references/scripts/query-spec.cjs --widget UserManagement + * node references/scripts/query-spec.cjs --widget admin-portal + * node references/scripts/query-spec.cjs --path /_widgets/UserProfile/me + * node references/scripts/query-spec.cjs --list + * + * Outputs matching endpoints with their request/response schemas (resolved $refs). + */ + +import { readFileSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { parse } from 'yaml'; + +// Support both ESM (import.meta.url) and CJS (__dirname) for bundled output +const _dir = typeof __dirname !== 'undefined' ? __dirname : dirname(new URL(import.meta.url).pathname); +const SPEC_PATH = join(_dir, '../widgets-open-api-spec.yaml'); + +export const WIDGET_PREFIXES: Record = { + // Primary names (match SKILL.md widget slugs) + 'user-management': '/_widgets/UserManagement', + 'user-profile': '/_widgets/UserProfile', + 'admin-portal-sso-connection': '/_widgets/admin-portal/sso-connections', + 'admin-portal-domain-verification': '/_widgets/admin-portal/organization-domains', + // Alternate forms + usermanagement: '/_widgets/UserManagement', + userprofile: '/_widgets/UserProfile', + 'admin-portal': '/_widgets/admin-portal', + adminportal: '/_widgets/admin-portal', + 'sso-connection': '/_widgets/admin-portal/sso-connections', + 'domain-verification': '/_widgets/admin-portal/organization-domains', + apikeys: '/_widgets/ApiKeys', + dataintegrations: '/_widgets/DataIntegrations', + 'directory-sync': '/_widgets/directory-sync', + settings: '/_widgets/settings', +}; + +export function loadSpec(path?: string) { + return parse(readFileSync(path ?? SPEC_PATH, 'utf-8')); +} + +export function resolveRef(spec: Record, ref: string): unknown { + const parts = ref.replace('#/', '').split('/'); + let current: unknown = spec; + for (const part of parts) { + if (current && typeof current === 'object') { + current = (current as Record)[part]; + } else { + return undefined; + } + } + return current; +} + +export function resolveSchema(spec: Record, schema: unknown): unknown { + if (!schema || typeof schema !== 'object') return schema; + if (Array.isArray(schema)) { + return schema.map((item) => resolveSchema(spec, item)); + } + const s = schema as Record; + if (s.$ref && typeof s.$ref === 'string') { + return resolveSchema(spec, resolveRef(spec, s.$ref)); + } + const result: Record = {}; + for (const [key, value] of Object.entries(s)) { + if (value && typeof value === 'object') { + result[key] = resolveSchema(spec, value); + } else { + result[key] = value; + } + } + return result; +} + +export interface EndpointInfo { + path: string; + method: string; + description?: string; + parameters?: unknown; + requestBody?: unknown; + responses: Record; +} + +export function extractEndpoints(spec: Record, pathFilter: (path: string) => boolean): EndpointInfo[] { + const paths = spec.paths as Record> | undefined; + if (!paths) return []; + + const endpoints: EndpointInfo[] = []; + for (const [path, methods] of Object.entries(paths)) { + if (!pathFilter(path)) continue; + for (const [method, operation] of Object.entries(methods)) { + if (!operation || typeof operation !== 'object') continue; + const op = operation as Record; + + const responses: Record = {}; + const opResponses = op.responses as Record | undefined; + if (opResponses) { + for (const [code, resp] of Object.entries(opResponses)) { + const r = resp as Record; + const content = r?.content as Record | undefined; + const json = content?.['application/json'] as Record | undefined; + if (json?.schema) { + responses[code] = { + description: r.description, + schema: resolveSchema(spec, json.schema), + }; + } else { + responses[code] = { description: r?.description }; + } + } + } + + const endpoint: EndpointInfo = { + path, + method: method.toUpperCase(), + responses, + }; + + if (op.description) endpoint.description = op.description as string; + if (op.parameters) endpoint.parameters = op.parameters; + if (op.requestBody) { + const body = op.requestBody as Record; + const content = body.content as Record | undefined; + const json = content?.['application/json'] as Record | undefined; + if (json?.schema) { + endpoint.requestBody = resolveSchema(spec, json.schema); + } + } + + endpoints.push(endpoint); + } + } + return endpoints; +} + +export function formatEndpoint(ep: EndpointInfo): string { + const lines: string[] = []; + lines.push(`## ${ep.method} ${ep.path}`); + if (ep.description) lines.push(`\n${ep.description}`); + + if (ep.parameters && Array.isArray(ep.parameters) && ep.parameters.length > 0) { + lines.push('\n### Parameters\n'); + for (const p of ep.parameters) { + const param = p as Record; + lines.push(`- \`${param.name}\` (${param.in}): ${param.schema ? JSON.stringify(param.schema) : 'unknown'}`); + } + } + + if (ep.requestBody) { + lines.push('\n### Request Body\n'); + lines.push('```json'); + lines.push(JSON.stringify(ep.requestBody, null, 2)); + lines.push('```'); + } + + for (const [code, resp] of Object.entries(ep.responses)) { + const r = resp as Record; + lines.push(`\n### Response ${code}${r.description ? ` -- ${r.description}` : ''}\n`); + if (r.schema) { + lines.push('```json'); + lines.push(JSON.stringify(r.schema, null, 2)); + lines.push('```'); + } + } + + return lines.join('\n'); +} + +export function groupPathsByWidget(paths: string[]): Record { + const grouped: Record = {}; + for (const p of paths) { + const parts = p.split('/').filter(Boolean); + const group = parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0] || 'other'; + if (!grouped[group]) grouped[group] = []; + grouped[group].push(p); + } + return grouped; +} + +// --- CLI (only runs when executed directly) --- + +const isDirectExecution = process.argv[1]?.includes('query-spec'); + +if (isDirectExecution) { + const args = process.argv.slice(2); + + if (args.includes('--list')) { + const spec = loadSpec(); + const grouped = groupPathsByWidget(Object.keys(spec.paths ?? {})); + for (const [group, groupPaths] of Object.entries(grouped)) { + console.log(`\n${group}:`); + for (const p of groupPaths) console.log(` ${p}`); + } + process.exit(0); + } + + const widgetIdx = args.indexOf('--widget'); + const pathIdx = args.indexOf('--path'); + + if (widgetIdx === -1 && pathIdx === -1) { + console.error('Usage: query-spec.ts --widget | --path | --list'); + console.error('Widgets:', Object.keys(WIDGET_PREFIXES).join(', ')); + process.exit(1); + } + + const spec = loadSpec(); + let filter: (path: string) => boolean; + + if (widgetIdx !== -1) { + const widgetName = (args[widgetIdx + 1] || '').toLowerCase(); + const prefix = WIDGET_PREFIXES[widgetName]; + if (!prefix) { + console.error(`Unknown widget: ${args[widgetIdx + 1]}`); + console.error('Known widgets:', Object.keys(WIDGET_PREFIXES).join(', ')); + process.exit(1); + } + filter = (path) => path.startsWith(prefix); + } else { + const pathPattern = args[pathIdx + 1] || ''; + filter = (path) => path.includes(pathPattern); + } + + const endpoints = extractEndpoints(spec, filter); + + if (endpoints.length === 0) { + console.error('No matching endpoints found.'); + process.exit(1); + } + + console.log(`# Matched ${endpoints.length} endpoint(s)\n`); + for (const ep of endpoints) { + console.log(formatEndpoint(ep)); + console.log('\n---\n'); + } +} diff --git a/plugins/workos/skills/workos-widgets/references/styling-and-components.md b/plugins/workos/skills/workos-widgets/references/styling-and-components.md new file mode 100644 index 00000000..61b670e1 --- /dev/null +++ b/plugins/workos/skills/workos-widgets/references/styling-and-components.md @@ -0,0 +1,25 @@ +# Styling and Components + +## Objective + +Match widget UI to the host application's existing component and styling systems. + +## Do + +- Use detected styling approach (Tailwind, CSS, CSS Modules, SCSS, styled-components, Emotion). +- Prefer existing shared UI components before introducing new primitives. +- Keep naming and file placement aligned with existing conventions. +- Add only minimal new styles required for widget UX. + +## Don't + +- Don't introduce a new styling system when one is already established; if in doubt, confirm with the user. +- Don't break or modify existing styles outside widget integration scope. +- Don't break established spacing/typography conventions. +- Don't override existing component styles by passing `className` or `style` props to components that already have their own styling. Use ` + ); +} +``` + +### If you need a server-generated sign-in URL + +Call `getSignInUrl()` only inside a Server Action or Route Handler. It is the safe wrapper for AuthKit sign-in/sign-up flows. + +### Sign out with a POST server action, never a GET route + +Sign-out mutates state -- it clears the session -- so it must never be a `GET` route handler. A `GET /auth/signout` is unsafe: Next.js `` prefetch can trigger it on hover (logging users out unexpectedly), and it is CSRF-exposable via ``. `workos doctor` flags this as `SIGNOUT_GET_HANDLER`. + +Use a POST server action. In a Server Component, an inline action is fine: + +```tsx +
{ 'use server'; await signOut(); }}> + +
+``` + +A client component (for example a nav that needs `useAuth()`) cannot define an inline `'use server'` action -- put it in a separate server-action module and import it: + +```tsx +// app/auth/actions.ts +'use server'; +import { signOut } from '@workos-inc/authkit-nextjs'; // verify export path in README +export async function signOutAction() { + await signOut(); +} +``` + +```tsx +'use client'; +import { signOutAction } from '@/app/auth/actions'; +// ... +
+ +
+``` + +`signOut()` accepts an optional `{ returnTo }`; with none, it redirects to the Logout URI configured in your WorkOS dashboard. If a generated `GET` sign-out route exists, delete it rather than switching it to `POST` -- that removes the extra logout surface entirely. + +### Critical auth URL gotchas + +- Never call `getSignInUrl()` / `getSignUpUrl()` inside a Server Component render (`page.tsx`, `layout.tsx`, async `nav-auth.tsx`, etc.). +- Never use raw `getAuthorizationUrl()` for AuthKit UI flows. It returns an object like `{ url, sealedState }`, not a URL string. +- If you bypass `getSignInUrl()`, the PKCE cookie is never set (`sealedState` is discarded), which causes `OAuth state mismatch` on the callback. +- If you pass the raw `getAuthorizationUrl()` result to `window.location.href`, the browser will navigate to `/[object Object]`. +- For server-side redirects, use `getSignInUrl()` in a Server Action / Route Handler. For client-side nav buttons, use `refreshAuth({ ensureSignedIn: true })`. + +Note: The SDK renamed `getUser` to `withAuth` in newer versions. Use whichever function the installed SDK version exports -- do NOT rename existing working imports. + +## Verification Checklist (ALL MUST PASS) + +Run these commands to confirm integration. Do not mark complete until all pass: + +```bash +# 1. Check middleware/proxy exists (one should match) +ls proxy.ts middleware.ts src/proxy.ts src/middleware.ts 2>/dev/null + +# 2. CRITICAL: Check AuthKitProvider is in layout (REQUIRED) +grep "AuthKitProvider" app/layout.tsx || echo "FAIL: AuthKitProvider missing from layout" + +# 3. Check callback route exists +find app -name "route.ts" -path "*/callback/*" + +# 4. Audit for raw getAuthorizationUrl usage -- always unsafe in app sign-in/sign-up flows +rg -n "getAuthorizationUrl|window\.location\.href\s*=\s*auth\.signInUrl" app src/app src 2>/dev/null || true + +# 5. Audit getSignInUrl() usage -- safe in Server Actions/Route Handlers, unsafe in page/layout/component render +rg -n "getSignInUrl\(" app src/app 2>/dev/null || true + +# 6. CRITICAL: Audit for an unsafe GET sign-out route -- sign-out mutates state, so it must be a POST server action, never a GET handler +rg -n -g '/{signout,sign-out,logout}/route.*' "export (async )?function GET|export const GET" app src/app 2>/dev/null && echo "FAIL: sign-out is a GET route -- convert to a POST server action and delete the GET route (see 'Sign out with a POST server action, never a GET route')" || echo "OK: no GET sign-out route" + +# 7. Build succeeds +npm run build +``` + +If check #2 fails: Go back to Step 6 and add AuthKitProvider. This is not optional. + +Manual verification before marking complete:** + +1. Click Sign in and confirm the browser goes to a real WorkOS URL -- not `/[object Object]` +2. Complete auth and confirm callback succeeds without `OAuth state mismatch` +3. If `rg` finds `getSignInUrl(` in `page.tsx`, `layout.tsx`, or async server-rendered nav components, move that logic to a client component, Server Action, or Route Handler +4. If `rg` finds `getAuthorizationUrl` in sign-in/sign-up code paths, replace it with `getSignInUrl()` / `getSignUpUrl()` + +## Error Recovery + +### "cookies was called outside a request scope" (Next.js 15+) + +Most common cause: Route handler not properly async or missing await. + +Fix for callback route: + +1. Check that `handleAuth()` is exported directly: `export const GET = handleAuth();` +2. If using custom wrapper, ensure it's `async` and awaits any cookie operations +3. Verify authkit-nextjs SDK version supports Next.js 15+ (check README for compatibility) +4. Never call `cookies()` at module level - only inside request handlers + +This error causes OAuth codes to expire ("invalid_grant"), so fix the handler first. + +### "Cookies can only be modified in a Server Action or Route Handler" + +Cause: `getSignInUrl()` or `getSignUpUrl()` called directly in a Server Component. These functions set a PKCE cookie internally and must run in a Server Action or Route Handler. + +Fix: + +1. Move the `getSignInUrl()` call to a Server Action +2. Or create a Route Handler that redirects to the sign-in URL +3. Or convert the shared auth UI to a client component and call `refreshAuth({ ensureSignedIn: true })` +4. Do NOT call `getSignInUrl()` at the top level of a page component + +### `GET /[object%20Object]` or `window.location.href = "[object Object]"` + +Cause: Code used raw `getAuthorizationUrl()` output as `signInUrl`. That helper returns `{ url, sealedState }`, not a string. + +Fix: + +1. Replace raw `getAuthorizationUrl()` usage with `getSignInUrl()` (or `getSignUpUrl()`) +2. If you must inspect the lower-level helper, extract `.url` and preserve `sealedState` -- but prefer the AuthKit wrapper +3. Verify the provider/browser redirect receives a real string URL before assigning `window.location.href` + +### "OAuth state mismatch" + +Cause: The code bypassed `getSignInUrl()` and discarded `sealedState`, so the PKCE state cookie was never set. + +Fix: + +1. Use `getSignInUrl()` in a Server Action or Route Handler so AuthKit sets the PKCE cookie +2. For client-side sign-in buttons, use `refreshAuth({ ensureSignedIn: true })` +3. Do not hand-roll the sign-in action with raw `getAuthorizationUrl()` unless you also persist `sealedState` exactly as the SDK expects + +### `SIGNOUT_GET_HANDLER` (flagged by `workos doctor`) + +Cause: The sign-out route is a `GET` handler (e.g. `export async function GET() { return signOut(); }`), often paired with a `
`. A `GET` with a side effect is unsafe -- Next.js prefetch and CSRF (``) can trigger logout. + +Fix: + +1. Move sign-out to a POST server action (see "Sign out with a POST server action, never a GET route" above). +2. Delete the `GET` sign-out route entirely. +3. Ensure the sign-out `` uses a server-action `action={...}` (or `method="POST"`), not `method="GET"`. +4. Re-run `workos doctor` to confirm the finding clears. + +### "middleware.ts not found" + +- Check: File at project root or `src/`, not inside `app/` +- Check: Filename matches Next.js version (proxy.ts for 16+, middleware.ts for 13-15) + +### "Both middleware file and proxy file are detected" (Next.js 16+) + +- Next.js 16 throws error E900 if both `middleware.ts` and `proxy.ts` exist +- Delete `middleware.ts` and use only `proxy.ts` +- If `middleware.ts` has custom logic, migrate it into `proxy.ts` + +### "withAuth route not covered by middleware" but middleware/proxy file exists + +- Most common cause: File is at the wrong level. Next.js only discovers middleware/proxy files in the parent directory of `app/`. For `src/app/` projects, the file must be in `src/`, not at the project root. +- Check: Is `app/` at `src/app/`? Then middleware/proxy must be at `src/middleware.ts` or `src/proxy.ts` +- Check: Matcher config must include the route path being accessed + +### "Cannot use getUser in client component" + +- Check: Component has no `'use client'` directive, or +- Check: Move auth logic to server component/API route + +### "Module not found" for SDK import + +- Check: SDK installed before writing imports +- Check: SDK package directory exists in node_modules + +### "withAuth route not covered by middleware" + +- Check: Middleware/proxy file exists at correct location +- Check: Matcher config includes the route path + +### Build fails after AuthKitProvider + +- Check: Import path matches what README specifies (root export vs `/components` subpath) +- Check: No client/server boundary violations + +### NEXT*PUBLIC* prefix issues + +- Client components need `NEXT_PUBLIC_*` prefix +- Server components use plain env var names diff --git a/plugins/workos/skills/workos/references/workos-authkit-react-router.md b/plugins/workos/skills/workos/references/workos-authkit-react-router.md new file mode 100644 index 00000000..004a696f --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-authkit-react-router.md @@ -0,0 +1,112 @@ +# WorkOS AuthKit for React Router + +## Decision Tree + +``` +1. Fetch README (BLOCKING) +2. Detect router mode +3. Follow README for that mode +4. Verify with checklist below +``` + +## Phase 1: Fetch SDK Documentation (BLOCKING) + +STOP - Do not write any code until this completes. + +WebFetch: `https://raw.githubusercontent.com/workos/authkit-react-router/main/README.md` + +The README is the source of truth. If this skill conflicts with README, follow the README. + +## Phase 2: Detect Router Mode + +| Mode | Detection Signal | Key Indicator | +| -------------- | ------------------------------- | ------------------------- | +| v7 Framework | `react-router.config.ts` exists | Routes in `app/routes/` | +| v7 Data | `createBrowserRouter` in source | Loaders in route config | +| v7 Declarative | `` component | Routes as JSX, no loaders | +| v6 | package.json version `"6.x"` | Similar to v7 Declarative | + +Detection order: + +1. Check for `react-router.config.ts` (Framework mode) +2. Grep for `createBrowserRouter` (Data mode) +3. Check package.json version (v6 vs v7) +4. Default to Declarative if v7 with `` + +## Phase 3: Follow README + +Based on detected mode, apply the corresponding README section. The README contains current API signatures and code patterns. + +## Critical Distinctions + +### authLoader vs authkitLoader + +| Function | Purpose | Where to use | +| --------------- | ------------------------- | ---------------------- | +| `authLoader` | OAuth callback handler | Callback route ONLY | +| `authkitLoader` | Fetch user data in routes | Any route needing auth | + +Common mistake: Using `authkitLoader` for callback route. Use `authLoader()`. + +### Root Route Requirement + +Auth loader MUST be on root route for child routes to access auth context. + +Wrong: Auth loader only on `/dashboard` +Right: Auth loader on `/` (root), children inherit context + +## Environment Variables + +Required in `.env` or `.env.local`: + +- `WORKOS_API_KEY` - starts with `sk_` +- `WORKOS_CLIENT_ID` - starts with `client_` +- `WORKOS_REDIRECT_URI` - full URL (e.g., `http://localhost:3000/auth/callback`) +- `WORKOS_COOKIE_PASSWORD` - 32+ chars (server modes only) + +## Verification Checklist (ALL MUST PASS) + +Run these commands to confirm integration. Do not mark complete until all pass: + +```bash +# 1. Check SDK installed +ls node_modules/@workos-inc/authkit-react-router 2>/dev/null || echo "FAIL: SDK not installed" + +# 2. Check callback route exists and matches WORKOS_REDIRECT_URI +grep -r "authLoader\|handleCallbackRoute" src/ app/ 2>/dev/null + +# 3. Check auth loader/provider on root route +grep -r "authkitLoader\|AuthKitProvider" src/ app/ 2>/dev/null + +# 4. Build succeeds +npm run build +``` + +If check #2 fails: Callback route path must match WORKOS_REDIRECT_URI exactly. Use authLoader (not authkitLoader) for the callback route. + +## Error Recovery + +### "loader is not a function" + +Cause: Using loader pattern in Declarative/v6 mode +Fix: Declarative/v6 modes use `AuthKitProvider` + `useAuth` hook, not loaders + +### Auth state not available in child routes + +Cause: Auth loader missing from root route +Fix: Add `authkitLoader` (or `AuthKitProvider`) to root route so children inherit context + +### useAuth returns undefined + +Cause: Missing `AuthKitProvider` wrapper +Fix: Wrap app with `AuthKitProvider` (required for Declarative/v6 modes) + +### Callback route 404 + +Cause: Route path mismatch with `WORKOS_REDIRECT_URI` +Fix: Extract exact path from env var, create route at that path + +### "Module not found" for SDK + +Cause: SDK not installed +Fix: Install SDK, wait for completion, verify `node_modules` before writing imports diff --git a/plugins/workos/skills/workos/references/workos-authkit-react.md b/plugins/workos/skills/workos/references/workos-authkit-react.md new file mode 100644 index 00000000..269d3400 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-authkit-react.md @@ -0,0 +1,95 @@ +# WorkOS AuthKit for React (SPA) + +## Decision Tree + +``` +START + │ + ├─► Fetch README (BLOCKING) + │ raw.githubusercontent.com/workos/authkit-react/main/README.md + │ README is source of truth. Stop if fetch fails. + │ + ├─► Detect Build Tool + │ ├─ vite.config.ts exists? → Vite + │ └─ otherwise → Create React App + │ + ├─► Set Env Var Prefix + │ ├─ Vite → VITE_WORKOS_CLIENT_ID + │ └─ CRA → REACT_APP_WORKOS_CLIENT_ID + │ + └─► Implement per README +``` + +## Critical: Build Tool Detection + +| Marker File | Build Tool | Env Prefix | Access Pattern | +| ------------------------- | ---------- | ------------ | ------------------------- | +| `vite.config.ts` | Vite | `VITE_` | `import.meta.env.VITE_*` | +| `craco.config.js` or none | CRA | `REACT_APP_` | `process.env.REACT_APP_*` | + +Wrong prefix = undefined values at runtime. This is the #1 integration failure. + +## Key Clarification: No Callback Route + +The React SDK handles OAuth callbacks internally via AuthKitProvider. + +- No server-side callback route needed +- SDK intercepts redirect URI client-side +- Token exchange happens automatically + +Just ensure redirect URI env var matches WorkOS Dashboard exactly. + +## Required Environment Variables + +``` +{PREFIX}WORKOS_CLIENT_ID=client_... +{PREFIX}WORKOS_REDIRECT_URI=http://localhost:5173/callback +``` + +No `WORKOS_API_KEY` needed. Client-side only SDK. + +## Verification Checklist (ALL MUST PASS) + +Run these commands to confirm integration. Do not mark complete until all pass: + +```bash +# 1. Check env var prefix matches build tool +grep -E "VITE_WORKOS_CLIENT_ID|REACT_APP_WORKOS_CLIENT_ID" .env .env.local 2>/dev/null + +# 2. Check AuthKitProvider wraps app root +grep "AuthKitProvider" src/main.tsx src/index.tsx 2>/dev/null || echo "FAIL: AuthKitProvider missing" + +# 3. Check no server framework present (wrong skill if found) +grep -E '"next"|"react-router"' package.json && echo "WARN: Server framework detected" + +# 4. Build succeeds +pnpm build +``` + +If check #2 fails: AuthKitProvider must wrap the app root in main.tsx/index.tsx. This is required for useAuth() to work. + +## Error Recovery + +### "clientId is required" + +Cause: Env var inaccessible (wrong prefix) + +Check: Does prefix match build tool? Vite needs `VITE_`, CRA needs `REACT_APP_`. + +### Auth state lost on refresh + +Cause: Token persistence issue + +Check: Browser dev tools → Application → Local Storage. SDK stores tokens here automatically. + +### useAuth returns undefined + +Cause: Component outside provider tree + +Check: Entry file (`main.tsx` or `index.tsx`) wraps `` in ``. + +### Callback redirect fails + +Cause: URI mismatch + +Check: Env var redirect URI exactly matches WorkOS Dashboard → Redirects configuration. diff --git a/plugins/workos/skills/workos/references/workos-authkit-sveltekit.md b/plugins/workos/skills/workos/references/workos-authkit-sveltekit.md new file mode 100644 index 00000000..aa676bec --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-authkit-sveltekit.md @@ -0,0 +1,203 @@ +# WorkOS AuthKit for SvelteKit + +## Step 1: Fetch SDK Documentation (BLOCKING) + +STOP. Do not proceed until complete. + +WebFetch: `https://raw.githubusercontent.com/workos/authkit-sveltekit/main/README.md` + +The README is the source of truth. If this skill conflicts with README, follow README. + +## Step 2: Pre-Flight Validation + +### Project Structure + +- Confirm `svelte.config.js` (or `svelte.config.ts`) exists +- Confirm `package.json` contains `@sveltejs/kit` dependency +- Confirm `src/routes/` directory exists + +### Environment Variables + +Check `.env` or `.env.local` for: + +- `WORKOS_API_KEY` - starts with `sk_` +- `WORKOS_CLIENT_ID` - starts with `client_` +- `WORKOS_REDIRECT_URI` - valid callback URL +- `WORKOS_COOKIE_PASSWORD` - 32+ characters + +SvelteKit uses `$env/static/private` and `$env/dynamic/private` natively. The agent should write env vars to `.env` (SvelteKit's default) or `.env.local`. + +## Step 2b: Partial Install Recovery + +Before installing the SDK, check if a previous AuthKit attempt already exists: + +1. Check if `@workos/authkit-sveltekit` is already in `package.json` +2. Check for incomplete setup signals: + - `src/hooks.server.ts` has commented-out `authkitHandle` import or exports a passthrough handle + - `src/routes/+layout.server.ts` has TODO comments about loading the session + - No callback `+server.ts` route exists in `src/routes/` + - No `WORKOS_COOKIE_PASSWORD` in `.env` +3. If partial install detected: + - Do NOT reinstall the SDK (it's already there) + - Read existing files to understand what's done vs missing + - Complete the integration by filling gaps rather than starting fresh + - The most common gap is the missing callback route -- create it + - Wire up `authkitHandle` in hooks.server.ts properly (use `sequence()` if other hooks exist) + - Complete the layout load function + - Ensure `WORKOS_COOKIE_PASSWORD` is set in `.env` + +## Step 2c: Existing Auth System Detection + +Check for existing authentication before integrating: + +``` +package.json has 'lucia'? → Lucia v3 session auth +package.json has '@auth0/auth0-spa-js'? → Auth0 SPA auth +package.json has '@auth/sveltekit'? → Auth.js SvelteKit +src/hooks.server.ts handles cookies? → Custom session middleware +``` + +If existing auth detected (Lucia is most common in SvelteKit): + +- Do NOT remove or disable the existing auth system +- Use `sequence()` from `@sveltejs/kit/hooks` to compose handles: + + ```typescript + import { sequence } from '@sveltejs/kit/hooks'; + import { authkitHandle } from '@workos/authkit-sveltekit'; + + // Keep existing handle, compose with AuthKit + export const handle = sequence(authkitHandle, existingHandle); + ``` + +- AuthKit handle should come FIRST in `sequence()` so it runs before other middleware +- Create separate WorkOS routes if `/login` or `/callback` are already taken (e.g., use `/auth/callback`) +- Ensure existing auth routes, form actions, and session cookies continue to work unchanged +- Document in code comments how to migrate fully to WorkOS AuthKit later + +## Step 3: Install SDK + +Detect package manager, install SDK package from README. + +``` +pnpm-lock.yaml? → pnpm add @workos/authkit-sveltekit +yarn.lock? → yarn add @workos/authkit-sveltekit +bun.lockb? → bun add @workos/authkit-sveltekit +else → npm install @workos/authkit-sveltekit +``` + +Verify: SDK package exists in node_modules before continuing. + +## Step 4: Configure Server Hooks + +SvelteKit uses `src/hooks.server.ts` for server-side middleware. This is where the AuthKit handler is registered. + +Create or update `src/hooks.server.ts` with the authkit handle function from the README. + +### Existing Hooks (IMPORTANT) + +If `src/hooks.server.ts` already exists with custom logic, use SvelteKit's `sequence()` helper to compose hooks: + +```typescript +import { sequence } from '@sveltejs/kit/hooks'; +import { authkitHandle } from '@workos/authkit-sveltekit'; // Check README for exact export + +export const handle = sequence(authkitHandle, yourExistingHandle); +``` + +Check README for the exact export name and usage pattern. + +## Step 5: Create Callback Route + +Parse `WORKOS_REDIRECT_URI` to determine route path: + +``` +URI path --> Route location +/callback --> src/routes/callback/+server.ts +/auth/callback --> src/routes/auth/callback/+server.ts +``` + +Use the SDK's callback handler from the README. Do not write custom OAuth logic. + +Critical: SvelteKit uses `+server.ts` for API routes, not `+page.server.ts`. + +## Step 6: Layout Setup + +Update `src/routes/+layout.server.ts` to load the auth session and pass it to all pages. + +Check README for the exact pattern -- typically a `load` function that returns the user session from locals. + +```typescript +// src/routes/+layout.server.ts +import type { LayoutServerLoad } from './$types'; + +export const load: LayoutServerLoad = async (event) => { + // Check README for exact API -- session is typically on event.locals + return { + user: event.locals.user, // or similar from README + }; +}; +``` + +## Step 7: UI Integration + +Add auth UI to `src/routes/+page.svelte` using the session data from the layout. + +- Show user info when authenticated +- Show sign-in link/button when not authenticated +- Add sign-out functionality + +Check README for sign-in URL generation and sign-out patterns. + +## Verification Checklist (ALL MUST PASS) + +Run these commands to confirm integration. Do not mark complete until all pass: + +```bash +# 1. Check hooks.server.ts exists and has authkit +grep -i "workos\|authkit" src/hooks.server.ts || echo "FAIL: authkit missing from hooks.server.ts" + +# 2. Check callback route exists +find src/routes -name "+server.ts" -path "*/callback/*" + +# 3. Check layout loads auth session +grep -i "user\|auth\|session" src/routes/+layout.server.ts || echo "FAIL: auth session missing from layout" + +# 4. Build succeeds +pnpm build || npm run build +``` + +## Error Recovery + +### "Cannot find module '@workos/authkit-sveltekit'" + +- Check: SDK installed before writing imports +- Check: SDK package directory exists in node_modules +- Re-run install if missing + +### hooks.server.ts not taking effect + +- Check: File is at `src/hooks.server.ts`, not `src/hooks.ts` or elsewhere +- Check: Named export is `handle` (SvelteKit requirement) +- Check: If using `sequence()`, all handles are properly composed + +### Callback route not found (404) + +- Check: File uses `+server.ts` (not `+page.server.ts`) +- Check: Route path matches `WORKOS_REDIRECT_URI` path exactly +- Check: Exports `GET` handler (SvelteKit convention) + +### "locals" type errors + +- Check: App.Locals interface is augmented in `src/app.d.ts` +- Check README for TypeScript setup instructions + +### Cookie password error + +- Verify `WORKOS_COOKIE_PASSWORD` is 32+ characters +- Generate new: `openssl rand -base64 32` + +### Auth state not available in pages + +- Check: `+layout.server.ts` load function returns user data +- Check: Pages access data via `export let data` (Svelte 4) or `$page.data` (Svelte 5) diff --git a/plugins/workos/skills/workos/references/workos-authkit-tanstack-start.md b/plugins/workos/skills/workos/references/workos-authkit-tanstack-start.md new file mode 100644 index 00000000..c5d3b9f4 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-authkit-tanstack-start.md @@ -0,0 +1,309 @@ +# WorkOS AuthKit for TanStack Start + +## Decision Tree + +``` +1. Fetch README (BLOCKING) + ├── Extract package name from install command + └── README is source of truth for ALL code patterns + +2. Detect directory structure + ├── src/ (TanStack Start v1.132+, default) + └── app/ (legacy vinxi-based projects) + +3. Follow README install/setup exactly + └── Do not invent commands or patterns +``` + +## Fetch SDK Documentation (BLOCKING) + +STOP - Do not proceed until complete. + +WebFetch: `https://raw.githubusercontent.com/workos/authkit-tanstack-start/main/README.md` + +From README, extract: + +1. Package name: `@workos/authkit-tanstack-react-start` +2. Use that exact name for all imports + +README overrides this skill if conflict. + +## Pre-Flight Checklist + +- [ ] README fetched and package name extracted +- [ ] `@tanstack/start` or `@tanstack/react-start` in package.json +- [ ] Identify directory structure: `src/` (modern) or `app/` (legacy) +- [ ] Environment variables set (see below) + +## Directory Structure Detection + +Modern TanStack Start (v1.132+) uses `src/`: + +``` +src/ +├── start.ts # Middleware config (CRITICAL) +├── router.tsx # Router setup +├── routes/ +│ ├── __root.tsx # Root layout +│ ├── api.auth.callback.tsx # OAuth callback (flat route) +│ └── ... +``` + +Legacy (vinxi-based) uses `app/`: + +``` +app/ +├── start.ts or router.tsx +├── routes/ +│ └── api/auth/callback.tsx # OAuth callback (nested route) +``` + +Detection: + +```bash +ls src/routes 2>/dev/null && echo "Modern (src/)" || echo "Legacy (app/)" +``` + +## Environment Variables + +| Variable | Format | Required | +| ------------------------ | ------------ | -------- | +| `WORKOS_API_KEY` | `sk_...` | Yes | +| `WORKOS_CLIENT_ID` | `client_...` | Yes | +| `WORKOS_REDIRECT_URI` | Full URL | Yes | +| `WORKOS_COOKIE_PASSWORD` | 32+ chars | Yes | + +Generate password if missing: `openssl rand -base64 32` + +Default redirect URI: `http://localhost:3000/api/auth/callback` + +## Middleware Configuration (CRITICAL) + +authkitMiddleware MUST be configured or auth will fail silently. + +WARNING: Do NOT add middleware to `createRouter()` in `router.tsx` or `app.tsx`. That is TanStack Router (client-side only). Server middleware belongs in `start.ts` using `requestMiddleware`. + +### If `start.ts` already exists + +Read the existing file first. Add `authkitMiddleware` to the existing `requestMiddleware` array (or create the array if missing). Preserve the existing export style. Do not rewrite the file from scratch. + +### If `start.ts` does not exist + +Create `src/start.ts` (or `app/start.ts` for legacy) using `createStart`: + +```typescript +import { createStart } from '@tanstack/react-start'; +import { authkitMiddleware } from '@workos/authkit-tanstack-react-start'; + +export const startInstance = createStart(() => ({ + requestMiddleware: [authkitMiddleware()], +})); +``` + +Two things matter here: + +1. Named export `startInstance` -- the build plugin generates `import type { startInstance }` from this file. A `default` export will cause a build error. +2. `createStart` takes a function returning the options object, not the options directly. `createStart({ ... })` will fail. + +WARNING: Do NOT add middleware to `createRouter()` in `router.tsx` or `app.tsx`. That is TanStack Router (client-side only). Server middleware belongs in `start.ts` using `requestMiddleware`. + +## Callback Route (CRITICAL) + +Path must match `WORKOS_REDIRECT_URI`. For `/api/auth/callback`: + +Modern (flat routes): `src/routes/api.auth.callback.tsx` +Legacy (nested routes): `app/routes/api/auth/callback.tsx` + +```typescript +import { createFileRoute } from '@tanstack/react-router'; +import { handleCallbackRoute } from '@workos/authkit-tanstack-react-start'; + +export const Route = createFileRoute('/api/auth/callback')({ + server: { + handlers: { + GET: handleCallbackRoute(), + }, + }, +}); +``` + +Key points: + +- Use `handleCallbackRoute()` - do not write custom OAuth logic +- Route path string must match the URI path exactly +- This is a server-only route (no component needed) + +## Protected Routes + +Use `getAuth()` in route loaders to check authentication: + +```typescript +import { createFileRoute, redirect } from '@tanstack/react-router'; +import { getAuth, getSignInUrl } from '@workos/authkit-tanstack-react-start'; + +export const Route = createFileRoute('/dashboard')({ + loader: async () => { + const { user } = await getAuth(); + if (!user) { + const signInUrl = await getSignInUrl(); + throw redirect({ href: signInUrl }); + } + return { user }; + }, + component: Dashboard, +}); +``` + +## Sign Out Route + +```typescript +import { createFileRoute, redirect } from '@tanstack/react-router'; +import { signOut } from '@workos/authkit-tanstack-react-start'; + +export const Route = createFileRoute('/signout')({ + loader: async () => { + await signOut(); + throw redirect({ href: '/' }); + }, +}); +``` + +## Client-Side Hooks (Optional) + +Only needed if you want reactive auth state in components. + +1. Add AuthKitProvider to root: + +```typescript +// src/routes/__root.tsx +import { AuthKitProvider } from '@workos/authkit-tanstack-react-start/client'; + +function RootComponent() { + return ( + + + + ); +} +``` + +2. Use hooks in components: + +```typescript +import { useAuth } from '@workos/authkit-tanstack-react-start/client'; + +function Profile() { + const { user, isLoading } = useAuth(); + // ... +} +``` + +Note: Server-side `getAuth()` is preferred for most use cases. + +## Finalize (REQUIRED before declaring success) + +After creating/editing all files, run these steps in order. Skipping them is the most common cause of build failures. + +### 1. Regenerate the route tree + +Adding new route files (callback, signout, etc.) makes the existing `routeTree.gen.ts` stale. The build will fail with type errors about missing routes until it is regenerated. + +```bash +pnpm build 2>/dev/null || npx tsr generate +``` + +The build itself triggers route tree regeneration. If it fails for other reasons, use `tsr generate` directly. + +### 2. Ensure Vite type declarations exist + +TanStack Start projects import CSS with `import styles from './styles.css?url'`. Without Vite's type declarations, TypeScript will error on these imports. Check if `src/vite-env.d.ts` (or `app/vite-env.d.ts`) exists -- if not, create it now (before attempting the build): + +```typescript +/// +``` + +### 3. Verify the build + +```bash +pnpm build +``` + +Do not skip this step. If the build fails, fix the errors before finishing. Common causes: + +- Stale route tree → re-run step 1 +- Missing Vite types → re-run step 2 +- Wrong import paths → check package name is `@workos/authkit-tanstack-react-start` + +## Verification Checklist (ALL MUST PASS) + +Run these commands to confirm integration. Do not mark complete until all pass: + +```bash +# 1. Check authkitMiddleware is configured +grep -r "authkitMiddleware" src/ app/ 2>/dev/null || echo "FAIL: Middleware not configured" + +# 2. Check callback route exists +find src/routes app/routes -name "*callback*" 2>/dev/null + +# 3. Check environment variables +grep -c "WORKOS_" .env 2>/dev/null || echo "FAIL: No env vars found" + +# 4. Build succeeds +pnpm build +``` + +If check #1 fails: authkitMiddleware must be in src/start.ts (or app/start.ts for legacy) requestMiddleware array. Auth will fail silently without it. + +## Error Recovery + +### "AuthKit middleware is not configured" + +Cause: `authkitMiddleware()` not in start.ts +Fix: Create/update `src/start.ts` with middleware config +Verify: `grep -r "authkitMiddleware" src/` + +### "Module not found" for SDK + +Cause: Wrong package name or not installed +Fix: `pnpm add @workos/authkit-tanstack-react-start` +Verify: `ls node_modules/@workos/authkit-tanstack-react-start` + +### Callback 404 + +Cause: Route file path doesn't match WORKOS_REDIRECT_URI +Fix: + +- URI `/api/auth/callback` → file `src/routes/api.auth.callback.tsx` (flat) or `app/routes/api/auth/callback.tsx` (nested) +- Route path string in `createFileRoute()` must match exactly + +### getAuth returns undefined user + +Cause: Middleware not configured or not running +Fix: Ensure `authkitMiddleware()` is in start.ts requestMiddleware array + +### "Cookie password too short" + +Cause: WORKOS_COOKIE_PASSWORD < 32 chars +Fix: `openssl rand -base64 32`, update .env + +### Build fails with route type errors + +Cause: Route tree not regenerated after adding routes +Fix: `pnpm dev` to regenerate `routeTree.gen.ts` + +## SDK Exports Reference + +Server (main export): + +- `authkitMiddleware()` - Request middleware +- `handleCallbackRoute()` - OAuth callback handler +- `getAuth()` - Get current session +- `signOut()` - Sign out user +- `getSignInUrl()` / `getSignUpUrl()` - Auth URLs +- `switchToOrganization()` - Change org context + +Client (`/client` subpath): + +- `AuthKitProvider` - Context provider +- `useAuth()` - Auth state hook +- `useAccessToken()` - Token management diff --git a/plugins/workos/skills/workos/references/workos-authkit-vanilla-js.md b/plugins/workos/skills/workos/references/workos-authkit-vanilla-js.md new file mode 100644 index 00000000..9e3bdfee --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-authkit-vanilla-js.md @@ -0,0 +1,87 @@ +# WorkOS AuthKit for Vanilla JavaScript + +## Decision Tree + +### Step 1: Fetch README (BLOCKING) + +WebFetch: `https://raw.githubusercontent.com/workos/authkit-js/main/README.md` + +README is source of truth. If this skill conflicts, follow README. + +### Step 2: Detect Project Type + +``` +Has package.json with build tool (Vite, webpack, Parcel)? + YES -> Bundled project (npm install) + NO -> CDN/Static project (script tag) +``` + +### Step 3: Follow README Installation + +- Bundled: Use package manager install from README +- CDN: Use unpkg script tag from README + +### Step 4: Implement Per README + +Follow README examples for: + +- Client initialization +- Sign in/out handlers +- User state management + +## Critical API Quirk + +`createClient()` is async - returns a Promise, not a client directly. + +```javascript +// CORRECT +const authkit = await createClient(clientId); +``` + +## Verification Checklist (ALL MUST PASS) + +Run these commands to confirm integration. Do not mark complete until all pass: + +```bash +# 1. Check SDK is available (bundled or CDN) +grep -r "createClient\|WorkOS" src/ *.html 2>/dev/null || echo "FAIL: SDK not found" + +# 2. Check createClient uses await +grep -rn "await createClient" src/ *.js *.html 2>/dev/null || echo "FAIL: createClient must be awaited" + +# 3. Check sign-in is on user gesture (click handler) +grep -rn "signIn\|sign_in" src/ *.js *.html 2>/dev/null + +# 4. Build succeeds (bundled projects only) +pnpm build 2>/dev/null || echo "CDN project -- verify manually in browser" +``` + +If check #2 fails: createClient() is async and must be awaited. Using it without await returns a Promise, not a client. + +## Environment Variables + +Bundled projects only: + +- Vite: `VITE_WORKOS_CLIENT_ID` +- Webpack: `REACT_APP_WORKOS_CLIENT_ID` or custom +- No `WORKOS_API_KEY` needed (client-side SDK) + +## Error Recovery + +| Error | Cause | Fix | +| -------------------------------- | ------------------- | ------------------------------------------------------ | +| `WorkOS is not defined` | CDN not loaded | Add script to `` before your code | +| `createClient is not a function` | Wrong import | npm: check import path; CDN: use `WorkOS.createClient` | +| `clientId is required` | Undefined env var | Check env prefix matches build tool | +| CORS errors | `file://` protocol | Use local dev server (`npx serve`) | +| Popup blocked | Not user gesture | Call `signIn()` only from click handler | +| Auth state lost | Token not persisted | Check localStorage in dev tools | + +## Task Flow + +1. preflight: Fetch README, detect project type, verify env vars +2. install: Add SDK per project type +3. callback: SDK handles internally (no server route needed) +4. provider: Initialize client with `await createClient()` +5. ui: Add auth buttons and state display +6. verify: Build (if bundled), check console diff --git a/plugins/workos/skills/workos/references/workos-cli-upgrade.md b/plugins/workos/skills/workos/references/workos-cli-upgrade.md new file mode 100644 index 00000000..ca458098 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-cli-upgrade.md @@ -0,0 +1,42 @@ +# WorkOS CLI Upgrades + +## Docs + +- npm package: https://www.npmjs.com/package/workos +- Releases / changelog: https://github.com/workos/cli/releases + +If this file conflicts with fetched docs, follow the docs. + +Use this when the user is running an outdated `workos` CLI and you need to recommend an upgrade. Symptoms include `unknown command`, missing flags shown in newer docs, or the user explicitly asking how to update the CLI. + +## Detecting an outdated CLI + +- Confirm the user's running version with `workos --version`. +- Confirm the latest published version with `npm view workos version` (or `npm view workos dist-tags`). Do NOT guess. The latest version moves frequently and any number you reproduce from memory is almost certainly stale. +- If running version < latest, recommend the upgrade command for the user's package manager (table below). +- If you cannot determine how the user installed the CLI, recommend `npx workos@latest ` as a no-install fallback so they can unblock immediately. + +## Upgrade commands by package manager + +| Package manager | One-shot upgrade | No-install alternative | +| --------------- | ------------------------------ | ---------------------- | +| npm | `npm install -g workos@latest` | `npx workos@latest` | +| pnpm | `pnpm add -g workos@latest` | `pnpm dlx workos` | + +The CLI is published to npm. If the user is on a different package manager, suggest the `npx workos@latest` no-install form so they can unblock immediately. + +After upgrading, have the user re-run `workos --version` to confirm the new version is on PATH (a stale shim from a different package manager can shadow the upgrade -- `workos doctor` flags this in newer CLI versions). + +## What NOT to do + +- Do NOT guess the "latest" version. Always tell the user to run `npm view workos version`. A fabricated version pin (`workos@0.13.0` when the real latest is `0.14.2`) leaves the user pinned to a stale install with no obvious symptom. +- Do NOT recommend `npm uninstall -g workos && npm install -g workos` as a default upgrade path. `workos@latest` reinstalls in place. Suggest uninstall-then-reinstall only if the user reports a corrupted install or a binary-shadow warning from `workos doctor`. +- Do NOT pin to a specific version unless the user explicitly asks (e.g. "I need to stay on 0.12.x for CI"). Default to `@latest`. +- Do NOT mix package managers. If the user installed via npm, recommend the npm upgrade command -- a `pnpm add -g` on top of a global npm install can leave two `workos` binaries on PATH. When unsure which one they used, prefer the no-install `npx workos@latest` form. +- Do NOT recommend Homebrew, asdf, or other version managers. The CLI is published to npm only. If the user mentions a non-npm install, treat it as out-of-scope and point them to the npm package URL above. + +## Gotchas + +- Global install on Node managed by `nvm` / `fnm` / `volta`: each Node version has its own global prefix. Switching Node versions can make `workos` "disappear" until the user reinstalls under the new Node. The fix is to reinstall, not to chase the missing binary. +- `npx` cache: `npx workos@latest` may serve a cached older version on the first invocation after a release. Re-running once usually picks up the new tarball. +- Corporate proxies / private registries: if `npm view workos version` errors, the user may be on a private registry that mirrors npm. Have them check `npm config get registry`; recommendations above assume the public npm registry. diff --git a/plugins/workos/skills/workos/references/workos-custom-domains.md b/plugins/workos/skills/workos/references/workos-custom-domains.md new file mode 100644 index 00000000..31746b72 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-custom-domains.md @@ -0,0 +1,21 @@ +# WorkOS Custom Domains + +## Docs + +- https://workos.com/docs/custom-domains/index +- https://workos.com/docs/custom-domains/email +- https://workos.com/docs/custom-domains/authkit +- https://workos.com/docs/custom-domains/auth-api +- https://workos.com/docs/custom-domains/admin-portal + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- Custom domains are production-environment only. Staging environments always use `workos.dev` and cannot be customized. +- AuthKit custom domains require updating ALL callback URLs in production code, environment variables (`WORKOS_REDIRECT_URI`), and OAuth app registration in the WorkOS Dashboard. Missing any one causes "Invalid redirect_uri" errors. +- DNS verification can take 24-48 hours. Do not proceed with code changes until Dashboard shows "Verified" status. +- SSL is auto-provisioned after DNS verification but is a separate status. Dashboard must show both "Verified" and "SSL Active" before the domain is usable. +- Email and Admin Portal custom domains require no code changes -- WorkOS handles them backend-only. Only AuthKit domains need code updates. +- When migrating AuthKit to a custom domain, add the new callback URI alongside the old one in Dashboard. Remove the old `workos.com` callback only after confirming the custom domain works. +- Cookie domain must be set to `.yourapp.com` (with leading dot) for subdomain compatibility. Incorrect cookie domain causes silent auth failures where login redirects back to `workos.com`. +- Emails still sent from `workos.dev` usually means the staging environment is selected or the email domain is not yet verified. Check the environment toggle (top-right in Dashboard). diff --git a/plugins/workos/skills/workos/references/workos-directory-sync.md b/plugins/workos/skills/workos/references/workos-directory-sync.md new file mode 100644 index 00000000..9f9f464f --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-directory-sync.md @@ -0,0 +1,51 @@ +# WorkOS Directory Sync + +## Docs + +- https://workos.com/docs/directory-sync/quick-start +- https://workos.com/docs/directory-sync/understanding-events +- https://workos.com/docs/directory-sync/handle-inactive-users +- https://workos.com/docs/directory-sync/attributes +- https://workos.com/docs/directory-sync/identity-provider-role-assignment +- https://workos.com/docs/rbac/integration + If this file conflicts with fetched docs, follow the docs. + +## Mapping directory groups to WorkOS roles + +This is one of the most common customer asks. Full recipe lives in `workos-rbac.md` under "IdP group → role mapping"; Directory Sync specifics: + +- SCIM/Google Workspace directory groups are the source of truth. Per docs: "SCIM is generally the preferred option due to its real-time synchronization capabilities." +- Roles propagate in real time: "Roles are granted to directory users in real-time, when we receive updates to their group memberships." +- IdP role assignment overrides API and Dashboard role assignments. Per docs: "IdP role assignment will always take precedence over roles assigned via API or the WorkOS Dashboard." Do not recommend `updateOrganizationMembership` as a workaround when a Directory Sync mapping exists -- it will silently revert on the next sync. +- Mappings are configured in the Admin Portal during directory setup, or on the directory page of the WorkOS Dashboard. Do not invent specific menu paths beyond that -- the docs do not commit to exact click-paths. +- Not configurable via the WorkOS CLI. If asked, say so explicitly and link to https://workos.com/docs/directory-sync/identity-provider-role-assignment. + +## Gotchas + +- dsync.deleted sends ONE event for the whole directory -- it does NOT send individual dsync.user.deleted or dsync.group.deleted events. You must cascade-delete all users and groups by directory_id yourself. +- Use email as stable user identity, NOT the WorkOS directory*user*\* ID -- the ID changes if a user is recreated in the IdP. Upsert by email. +- Return 200 from webhook handler IMMEDIATELY (WorkOS times out at 10s) -- process events asynchronously after acknowledging +- Webhooks are NOT mandatory -- the Events API (workos.events.listEvents) is a fully supported pull-based alternative for batch processing +- Webhook signature verification must use the RAW request body, not parsed JSON -- parsing first breaks the signature +- Use dsync.\* wildcard for Events API filter, not just "dsync" -- bare string returns nothing +- Events API after param must be within 30-day retention window +- User state "inactive" is far more common than "deleted" -- most IdPs deactivate users rather than deleting them. Handle dsync.user.updated with state=inactive as a deprovisioning event. +- Webhook handler pattern: call workos.webhooks.verifyEvent() with raw body + workos-signature header + secret, THEN return 200, THEN process event in async handler. Order matters. +- Ruby webhook trap: use request.raw_post for signature verification, NOT request.body -- Rails parses body into params which breaks the signature. Disable JSON parsing for the webhook endpoint (use ActionController::API or skip_before_action). +- Use upsert pattern (ON CONFLICT / upsert) for all webhook handlers -- events can be delivered more than once. dsync.user.created should upsert, not insert. + +## Endpoints + +| Endpoint | Description | +| ----------------------- | -------------------------- | +| `/directory-sync` | Directory Sync overview | +| `/directory` | Directory management | +| `/directory-group` | Directory group operations | +| `/directory-group/get` | Get a directory group | +| `/directory-group/list` | List directory groups | +| `/directory-user` | Directory user operations | +| `/directory-user/get` | Get a directory user | +| `/directory-user/list` | List directory users | +| `/directory/delete` | Delete a directory | +| `/directory/get` | Get a directory | +| `/directory/list` | List directories | diff --git a/plugins/workos/skills/workos/references/workos-dotnet.md b/plugins/workos/skills/workos/references/workos-dotnet.md new file mode 100644 index 00000000..ddf5b776 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-dotnet.md @@ -0,0 +1,158 @@ +# WorkOS AuthKit for .NET (ASP.NET Core) + +## Step 1: Fetch SDK Documentation (BLOCKING) + +STOP. Do not proceed until complete. + +WebFetch: `https://raw.githubusercontent.com/workos/workos-dotnet/main/README.md` + +The README is the source of truth for SDK API usage. If this skill conflicts with README, follow README. + +## Step 2: Pre-Flight Validation + +### Project Structure + +- Confirm a `*.csproj` file exists in the project root +- Detect project style: + - Minimal API (modern): `Program.cs` with `WebApplication.CreateBuilder()` -- .NET 6+ + - Startup pattern (older): `Startup.cs` with `ConfigureServices()` / `Configure()` -- .NET 5 and earlier + +This detection determines WHERE to register WorkOS services and middleware. + +### Environment Variables + +Check `appsettings.Development.json` for: + +- `WORKOS_API_KEY` -- starts with `sk_` +- `WORKOS_CLIENT_ID` -- starts with `client_` + +## Step 3: Install SDK + +```bash +dotnet add package WorkOS.net +``` + +Verify: Check the `*.csproj` file contains a `", + "ClientId": "", + "RedirectUri": "http://localhost:5000/auth/callback" + } +} +``` + +Use the actual credential values provided in the environment context. + +Important: Do NOT put secrets in `appsettings.json` (committed to git). Use `appsettings.Development.json` (gitignored) or `dotnet user-secrets`. + +## Step 7: Verification + +Run these checks -- do not mark complete until all pass: + +```bash +# 1. Check WorkOS.net is in csproj +grep -i "WorkOS" *.csproj + +# 2. Check auth endpoints exist +grep -r "auth/login\|auth/callback\|auth/logout" *.cs + +# 3. Build succeeds +dotnet build +``` + +If build fails: Read the error output carefully. Common issues: + +- Missing `using` statements for WorkOS namespaces +- Incorrect DI registration order +- Missing session/cookie middleware registration + +## Error Recovery + +### "dotnet: command not found" + +- .NET SDK is not installed. Inform the user to install from https://dotnet.microsoft.com/download + +### NuGet restore failures + +- Check internet connectivity +- Try `dotnet restore` explicitly before `dotnet build` + +### "No project file found" + +- Ensure you're in the correct directory with a `*.csproj` file + +### Build errors after integration + +- Check that all `using` statements are correct +- Verify DI registration order (services before middleware) +- Ensure `app.UseSession()` is called before mapping auth endpoints diff --git a/plugins/workos/skills/workos/references/workos-elixir.md b/plugins/workos/skills/workos/references/workos-elixir.md new file mode 100644 index 00000000..023546a8 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-elixir.md @@ -0,0 +1,226 @@ +# WorkOS AuthKit for Elixir (Phoenix) + +## Step 1: Fetch SDK Documentation (BLOCKING) + +STOP. Do not proceed until complete. + +WebFetch: `https://raw.githubusercontent.com/workos/workos-elixir/main/README.md` + +The README is the source of truth for SDK API usage. If this skill conflicts with README, follow README. + +## Step 2: Pre-Flight Validation + +### Project Structure + +- Confirm `mix.exs` exists +- Read `mix.exs` to extract the app name (look for `app: :my_app` in `project/0`) +- Confirm `lib/{app}_web/router.ex` exists (Phoenix project marker) +- Confirm `config/runtime.exs` exists + +### Determine App Name + +The app name from `mix.exs` determines all file paths. For example, if `app: :my_app`: + +- Web module: `lib/my_app_web/` +- Router: `lib/my_app_web/router.ex` +- Controllers: `lib/my_app_web/controllers/` + +### Environment Variables + +Check `.env.local` for: + +- `WORKOS_API_KEY` - starts with `sk_` +- `WORKOS_CLIENT_ID` - starts with `client_` + +## Step 2b: Partial Install Recovery + +Before creating new files, check if a previous AuthKit attempt exists: + +1. Check if `:workos` is already in `mix.exs` dependencies +2. Check for incomplete auth code -- AuthController exists but has TODO stubs, 501 responses, or missing callback/sign_out functions +3. If partial install detected: + - Do NOT re-add the SDK dependency (it's already there) + - Do NOT re-run `mix deps.get` if deps are already fetched + - Read existing auth files to understand what's done vs missing + - Complete the integration by filling gaps (controller methods, routes) + - Preserve any working code -- only fix what's broken + - Check for missing `/auth/callback` route (most common gap) + +## Step 2c: Existing Auth System Detection + +Check for existing authentication before integrating: + +``` +mix.exs has ':ueberauth'? → Ueberauth auth +mix.exs has ':pow'? → Pow auth +mix.exs has ':guardian'? → Guardian JWT auth +mix.exs has ':phx_gen_auth'? → Phoenix generated auth +config/*.exs has 'Ueberauth' config? → Ueberauth configured +router.ex has '/:provider' wildcard auth routes? → Ueberauth routes +``` + +If existing auth detected: + +- Do NOT remove or disable it +- Add WorkOS AuthKit alongside the existing system +- If Ueberauth is configured, be careful of `/:provider` wildcard routes -- use a specific scope like `/auth/workos` to avoid conflicts +- Reuse existing session infrastructure if compatible +- Create separate route paths for WorkOS auth +- Ensure existing auth routes continue to work unchanged +- Document in code comments how to migrate fully to WorkOS later + +## Step 3: Install SDK + +Add the `workos` package to `mix.exs` dependencies: + +```elixir +defp deps do + [ + # ... existing deps + {:workos, "~> 1.0"} + ] +end +``` + +Then run: + +```bash +mix deps.get +``` + +Verify: Check that `mix deps.get` completed successfully (exit code 0). + +## Step 4: Configure WorkOS + +Add WorkOS configuration to `config/runtime.exs`: + +```elixir +config :workos, + api_key: System.get_env("WORKOS_API_KEY"), + client_id: System.get_env("WORKOS_CLIENT_ID") +``` + +This ensures credentials are loaded from environment variables at runtime, not compiled into the release. + +## Step 5: Create Auth Controller + +### Prerequisite: Verify `{AppName}Web` module exists + +The controller uses `use {AppName}Web, :controller`. Confirm `lib/{app}_web.ex` exists and defines the `:controller` macro. If it doesn't exist (minimal Phoenix projects may lack it), create it: + +```elixir +defmodule {AppName}Web do + def controller do + quote do + use Phoenix.Controller, formats: [:html, :json] + import Plug.Conn + end + end + + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end +``` + +### Create controller + +Create `lib/{app}_web/controllers/auth_controller.ex`: + +```elixir +defmodule {AppName}Web.AuthController do + use {AppName}Web, :controller + + def sign_in(conn, _params) do + client_id = Application.get_env(:workos, :client_id) + redirect_uri = "http://localhost:4000/auth/callback" + + authorization_url = WorkOS.UserManagement.get_authorization_url(%{ + provider: "authkit", + client_id: client_id, + redirect_uri: redirect_uri + }) + + case authorization_url do + {:ok, url} -> redirect(conn, external: url) + {:error, reason} -> conn |> put_status(500) |> text("Auth error: #{inspect(reason)}") + end + end + + def callback(conn, %{"code" => code}) do + client_id = Application.get_env(:workos, :client_id) + + case WorkOS.UserManagement.authenticate_with_code(%{ + code: code, + client_id: client_id + }) do + {:ok, auth_response} -> + conn + |> put_session(:user, auth_response.user) + |> redirect(to: "/") + + {:error, reason} -> + conn |> put_status(401) |> text("Authentication failed: #{inspect(reason)}") + end + end + + def sign_out(conn, _params) do + conn + |> clear_session() + |> redirect(to: "/") + end +end +``` + +IMPORTANT: Adapt the module name and API calls based on the README. The WorkOS Elixir SDK API may differ from the pseudocode above. Always follow the README for exact function names, parameter shapes, and return types. + +## Step 6: Add Routes + +Add auth routes to `lib/{app}_web/router.ex`. Add these routes inside or outside the existing pipeline scope as appropriate: + +```elixir +scope "/auth", {AppName}Web do + pipe_through :browser + + get "/sign-in", AuthController, :sign_in + get "/callback", AuthController, :callback + post "/sign-out", AuthController, :sign_out +end +``` + +## Step 7: Verification + +Run the following to confirm the integration compiles: + +```bash +mix compile +``` + +If compilation fails: + +1. Read the error message carefully +2. Check that the WorkOS SDK module names match what's in the README +3. Verify the app name is consistent across all files +4. Fix the issue and re-run `mix compile` + +## Error Recovery + +### "could not compile dependency :workos" + +- Check Elixir version compatibility (1.15+ recommended) +- Try `mix deps.clean workos && mix deps.get` + +### "module WorkOS.UserManagement is not available" + +- The SDK API may use different module paths -- re-read the README +- Check if the SDK uses `WorkOS.SSO` or another module instead + +### "undefined function" in controller + +- Verify `use {AppName}Web, :controller` is correct +- Check that the SDK functions match the README exactly + +### Route conflicts + +- Check existing routes in router.ex for `/auth` prefix conflicts +- Adjust the scope path if needed (e.g., `/workos-auth`) diff --git a/plugins/workos/skills/workos/references/workos-email.md b/plugins/workos/skills/workos/references/workos-email.md new file mode 100644 index 00000000..324d51cc --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-email.md @@ -0,0 +1,16 @@ +# WorkOS Email Delivery + +## Docs + +- https://workos.com/docs/email + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- WorkOS sends auth emails automatically (Magic Auth, invitations, password resets). This feature is about configuring the sender domain, not writing email-sending code. +- Do NOT manually configure SPF/DKIM TXT records. WorkOS uses SendGrid's automated security via CNAMEs. Adding custom SPF/DKIM records will break authentication. +- You must set up actual inboxes for `welcome@` and `access@`. Email providers check if sender addresses are real -- no inbox means higher spam score. +- Spam trigger words in your team name or organization names (e.g., "FREE", "WINNER", "URGENT") damage deliverability even with perfect DNS config. +- Only send invitations when a user explicitly requests access. Bulk inviting from marketing lists violates anti-spam laws and destroys domain reputation. +- DNS propagation for CNAME records can take 24-48 hours. Do not assume failure before that window. +- "Domain already in use" error means the domain is configured in another WorkOS account -- must remove from old account first. diff --git a/plugins/workos/skills/workos/references/workos-events.md b/plugins/workos/skills/workos/references/workos-events.md new file mode 100644 index 00000000..30e7dc68 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-events.md @@ -0,0 +1,30 @@ +# WorkOS Events + +## Docs + +- https://workos.com/docs/events/index +- https://workos.com/docs/events/observability/datadog +- https://workos.com/docs/events/data-syncing/webhooks +- https://workos.com/docs/events/data-syncing/index +- https://workos.com/docs/events/data-syncing/events-api +- https://workos.com/docs/events/data-syncing/data-reconciliation +- https://workos.com/docs/reference/events +- https://workos.com/docs/reference/events/list + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- Do not implement both webhooks and Events API polling simultaneously -- this causes duplicate event processing. +- Webhook endpoints must return 200 OK within 5 seconds. Acknowledge immediately, process asynchronously. +- Verify webhook signature before processing using the raw request body. JSON-parsing the body before verification breaks the signature check. +- `WORKOS_WEBHOOK_SECRET` is shown only once when registering the endpoint. Save it immediately. +- WorkOS may retry webhook deliveries. All event processing must be idempotent -- deduplicate using `event.id`, not webhook delivery ID. +- For Events API polling, `last_event_id` must be stored persistently. If lost, you must backfill via date-range queries. +- Do NOT backfill by replaying webhooks. Webhook signatures expire. Always use the Events API for historical data. + +## Endpoints + +| Endpoint | Description | +| --------- | ------------- | +| `/events` | events | +| `/list` | events - list | diff --git a/plugins/workos/skills/workos/references/workos-feature-flags.md b/plugins/workos/skills/workos/references/workos-feature-flags.md new file mode 100644 index 00000000..f9f76097 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-feature-flags.md @@ -0,0 +1,30 @@ +# WorkOS Feature Flags + +## Docs + +- https://workos.com/docs/feature-flags +- https://workos.com/docs/feature-flags/sdk-integration +- https://workos.com/docs/feature-flags/slack-notifications +- https://workos.com/docs/reference/feature-flags +- https://workos.com/docs/reference/feature-flags/flag +- https://workos.com/docs/reference/feature-flags/targeting + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- Feature flags are delivered via the `feature_flags` claim in the access token -- NOT via a separate API call. You must read them from the session. +- Read the `feature_flags` claim from the session/access token. Some frameworks expose convenience helpers like `session.getFeatureFlag()`, but there is no standalone `workos.featureFlags.get()` API method. Claude tends to invent one. +- Flags have three targeting states: None (off for all), Some (targeted orgs/users), All (on for everyone). There is no percentage rollout -- it's discrete targeting. +- Flag evaluation requires a valid session with `feature_flags` claim. If using `loadSealedSession()`, the claim is included automatically. +- To refresh flag values mid-session, call `session.refresh()` -- stale tokens carry stale flag state. +- Flags are scoped per environment (sandbox vs production). A flag enabled in sandbox is NOT automatically enabled in production. +- Separate runtime evaluation from management. At runtime, flag values arrive in the `feature_flags` access-token claim -- do not call the API per request. For management, the API does expose write methods: `enableFeatureFlag(slug)`, `disableFeatureFlag(slug)`, `addFlagTarget({ slug, resourceId })`, `removeFlagTarget({ slug, resourceId })` (Node SDK). Flag definitions themselves (creation, slug, default state) are still Dashboard-only. +- Slack notifications for flag changes are opt-in and configured per flag in the Dashboard. +- In Next.js with `@workos-inc/authkit-nextjs`, server components access flags via `const { featureFlags } = await withAuth();` -- the `featureFlags` field is the deserialized form of the `feature_flags` JWT claim. Prefer this over reaching for `loadSealedSession()` from `@workos-inc/node`; the AuthKit helper is the documented path and handles session loading for you. + +## Endpoints + +| Endpoint | Description | +| ------------ | ---------------------------- | +| `/flag` | Feature flag management | +| `/targeting` | Flag targeting configuration | diff --git a/plugins/workos/skills/workos/references/workos-fga.md b/plugins/workos/skills/workos/references/workos-fga.md new file mode 100644 index 00000000..a1288ff3 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-fga.md @@ -0,0 +1,52 @@ +# WorkOS Fine-Grained Authorization (FGA) + +## Docs + +- https://workos.com/docs/fga +- https://workos.com/docs/fga/quick-start +- https://workos.com/docs/fga/resource-types +- https://workos.com/docs/fga/resources +- https://workos.com/docs/fga/roles-and-permissions +- https://workos.com/docs/fga/assignments +- https://workos.com/docs/fga/access-checks +- https://workos.com/docs/fga/resource-discovery +- https://workos.com/docs/fga/authkit-integration +- https://workos.com/docs/fga/standalone-integration +- https://workos.com/docs/fga/high-cardinality-entities +- https://workos.com/docs/reference/fga + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- FGA extends RBAC -- it is NOT a replacement. Org-level roles from RBAC still apply. FGA adds resource-scoped roles on top. +- SDK namespace is `workos.authorization.*`, NOT `workos.fga.*`. The product is called FGA but every SDK method lives under `authorization`: `check`, `assignRole`, `removeRoleAssignment`, `listResources`, `createResource`, etc. Claude consistently invents `workos.fga.check`, `workos.fga.assignRole`, `workos.warrants.check` (legacy Warrant API, removed). Always use `workos.authorization.*`. +- Resource types are configured ONLY in the Dashboard, not via API. Slugs are immutable after creation -- choose carefully. +- Access checks use `organizationMembershipId`, NOT `userId`. Fetch membership first via `listOrganizationMemberships()`. +- Permissions use `{resource_type}:{action}` format (e.g., `project:edit`). Claude tends to invent flat permission names like `editProject`. +- Hierarchy max depth is 5 levels, max 10 child types per resource type, max 50 resource types per environment. +- First org-level role creation on a resource type makes that org independent -- it stops inheriting environment-level roles. Same behavior as RBAC. +- `resourceExternalId` (your app's ID) and `resourceTypeSlug` can be used instead of WorkOS `resource_id` in all API calls -- prefer external IDs to avoid storing WorkOS IDs. +- Deleting a resource fails by default if it has children or role assignments. Pass cascade option to force. +- Access checks evaluate three levels automatically (direct assignment, inherited from parent, org-scoped role) -- do NOT manually check each level. +- For org-wide permissions, check JWT claims instead of calling the API -- saves a round trip. Reserve API calls for resource-specific checks. + +## Endpoints + +| Endpoint | Description | +| ------------------------------ | ---------------------------------------- | +| `/fga` | FGA overview | +| `/resource` | Resource management | +| `/resource/create` | Create a resource | +| `/resource/get` | Get resource by ID | +| `/resource/get-by-external-id` | Get resource by external ID | +| `/resource/list` | List resources | +| `/resource/update` | Update a resource | +| `/resource/delete` | Delete a resource | +| `/role-assignment` | Role assignment management | +| `/role-assignment/assign` | Assign role on a resource | +| `/role-assignment/list` | List role assignments for membership | +| `/role-assignment/remove` | Remove role assignment | +| `/access-check` | Access check | +| `/access-check/check` | Check if membership has permission | +| `/access-check/list-resources` | List resources membership can access | +| `/access-check/list-members` | List memberships with access to resource | diff --git a/plugins/workos/skills/workos/references/workos-go.md b/plugins/workos/skills/workos/references/workos-go.md new file mode 100644 index 00000000..04106741 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-go.md @@ -0,0 +1,221 @@ +# WorkOS AuthKit for Go + +## Step 1: Fetch SDK Documentation (BLOCKING) + +STOP. Do not proceed until complete. + +WebFetch: `https://raw.githubusercontent.com/workos/workos-go/main/README.md` + +The README is the source of truth for SDK API usage. If this skill conflicts with README, follow README. + +## Step 2: Pre-Flight Validation + +### Project Structure + +- Confirm `go.mod` exists in the project root +- Confirm Go module is initialized (module path declared in `go.mod`) + +### Environment Variables + +Check `.env` for: + +- `WORKOS_API_KEY` - starts with `sk_` +- `WORKOS_CLIENT_ID` - starts with `client_` +- `WORKOS_REDIRECT_URI` - valid callback URL (e.g., `http://localhost:8080/auth/callback`) + +### Framework Detection + +Read `go.mod` to detect web framework: + +``` +go.mod contains github.com/gin-gonic/gin? + | + +-- Yes --> Use Gin router patterns + | + +-- No --> Use stdlib net/http patterns +``` + +## Step 2b: Partial Install Recovery + +Before creating new files, check if a previous AuthKit attempt exists: + +1. Check if `github.com/workos/workos-go` is already in `go.mod` +2. Check for incomplete auth code -- files that import WorkOS packages but have non-functional handlers (TODO comments, 501 responses, empty handler bodies) +3. If partial install detected: + - Do NOT re-run `go get` (the module is already there) + - Read existing auth files to understand what's done vs missing + - Complete the integration by filling gaps rather than starting fresh + - Preserve any working code -- only fix what's broken + - If `usermanagement.SetAPIKey()` is already called in `init()`, don't call it again + - Always run `go mod tidy` after changes to keep `go.sum` consistent + +## Step 2c: Existing Auth System Detection + +Check for existing authentication before integrating: + +``` +*.go files have 'jwt' or 'JWT'? → Custom JWT auth +*.go files have 'oauth2'? → OAuth2 middleware +*.go files have 'authMiddleware'? → Custom auth middleware +go.mod has 'golang.org/x/oauth2'? → OAuth2 package +go.mod has 'github.com/coreos/go-oidc'? → OIDC auth +``` + +If existing auth detected: + +- Do NOT remove or disable it +- Add WorkOS AuthKit alongside the existing system +- Create separate route paths for WorkOS auth (e.g., `/auth/workos/login` if `/auth/login` is taken) +- Match handler signatures to the detected framework (Gin `*gin.Context` vs stdlib `http.ResponseWriter, *http.Request`) +- Ensure existing auth middleware continues to work on its routes +- Document in code comments how to migrate fully to WorkOS later + +## Step 3: Install SDK + +Run: + +```bash +go get github.com/workos/workos-go/v4 +``` + +Verify: Check that `go.mod` now contains `github.com/workos/workos-go/v4`. Both `go.mod` and `go.sum` will be modified -- this is expected. + +## Step 4: Configure Authentication + +### 4a: Create Auth Handler File + +Create an auth handler file. Respect existing project structure: + +- If `internal/` directory exists, create `internal/auth/handlers.go` +- If `handlers/` directory exists, create `handlers/auth.go` +- Otherwise, create `auth/handlers.go` + +The file must: + +- Declare a package matching the directory name +- Import `github.com/workos/workos-go/v4` packages as needed +- Read env vars with `os.Getenv("WORKOS_API_KEY")`, `os.Getenv("WORKOS_CLIENT_ID")`, `os.Getenv("WORKOS_REDIRECT_URI")` + +### 4b: Implement Handlers + +Implement these three handlers following the redirect-based auth flow from the README: + +Login handler (`/auth/login`): + +- Get the authorization URL from WorkOS using `usermanagement.GetAuthorizationURL()` +- Set `Provider` to the string `"authkit"` (it's a plain string, not a constant) +- Include `ClientID` and `RedirectURI` from env vars +- Redirect the user to the returned URL + +Callback handler (`/auth/callback`): + +- Extract the `code` query parameter from the redirect +- Call `usermanagement.AuthenticateWithCode()` with the code and `ClientID` +- Store user info in session/cookie (or return as JSON for API-first apps) +- Redirect to homepage or return user data + +Logout handler (`/auth/logout`): + +- Clear session data +- Redirect to homepage + +CRITICAL: Use idiomatic Go error handling throughout: + +```go +result, err := someFunction() +if err != nil { + http.Error(w, "Error message", http.StatusInternalServerError) + return +} +``` + +### 4c: Wire Handlers into Router + +#### If using Gin: + +```go +r := gin.Default() +r.GET("/auth/login", handleLogin) +r.GET("/auth/callback", handleCallback) +r.GET("/auth/logout", handleLogout) +``` + +#### If using stdlib net/http: + +```go +http.HandleFunc("/auth/login", handleLogin) +http.HandleFunc("/auth/callback", handleCallback) +http.HandleFunc("/auth/logout", handleLogout) +``` + +Wire these routes into the existing router setup in `main.go` or wherever routes are defined. Do NOT replace existing routes -- add alongside them. + +### 4d: Initialize WorkOS Client + +In the appropriate init location (package-level `init()` or `main()`), initialize the WorkOS client: + +```go +import "github.com/workos/workos-go/v4/pkg/usermanagement" + +func init() { + usermanagement.SetAPIKey(os.Getenv("WORKOS_API_KEY")) +} +``` + +Follow the README for the exact initialization pattern -- it may differ from above. + +## Step 5: Environment Setup + +The `.env` file should already contain the required variables (written by the installer). Verify it contains: + +``` +WORKOS_API_KEY=sk_... +WORKOS_CLIENT_ID=client_... +WORKOS_REDIRECT_URI=http://localhost:8080/auth/callback +``` + +Note for production: Go does not have a built-in .env convention. In production, set real OS environment variables. The `.env` file is for development only. If using a `.env` loader like `github.com/joho/godotenv`, the agent may install it and add `godotenv.Load()` to `main()`. + +## Step 6: Verification + +Run these commands. Do not mark complete until all pass: + +```bash +# 1. Go module is tidy +go mod tidy + +# 2. Build succeeds +go build ./... + +# 3. Vet passes (catches common mistakes) +go vet ./... +``` + +If build fails: + +- Check import paths match the SDK version in `go.mod` +- Ensure all new files have correct package declarations +- Run `go mod tidy` to resolve dependency issues + +## Error Recovery + +### "cannot find module providing package github.com/workos/workos-go/v4/..." + +- Run `go mod tidy` to sync dependencies +- Check that `go get` completed successfully +- Verify the import path matches exactly (v4 suffix required) + +### "undefined: usermanagement.SetAPIKey" or similar + +- SDK API may have changed -- refer to the fetched README +- Check the correct subpackage import path + +### Build fails with type errors + +- Ensure handler function signatures match the framework (Gin uses `*gin.Context`, stdlib uses `http.ResponseWriter, *http.Request`) +- Check that error return values are handled + +### "package X is not in std" + +- Run `go mod tidy` after adding new imports +- Ensure `go get` was run before writing import statements diff --git a/plugins/workos/skills/workos/references/workos-integrations.md b/plugins/workos/skills/workos/references/workos-integrations.md new file mode 100644 index 00000000..d8df8cfd --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-integrations.md @@ -0,0 +1,253 @@ +--- +name: workos-integrations +description: Set up identity provider integrations with WorkOS. Covers SSO, SCIM, and OAuth for 40+ providers. +--- + + + +# WorkOS Integrations + +## Step 1: Identify the Provider + +Ask the user which identity provider they need to integrate. Then find it in the table below. + +## Provider Lookup + +| Provider | Type | Doc URL | +| ---------------------- | ---------- | -------------------------------------------------------- | +| Access People HR | General | workos.com/docs/integrations/access-people-hr | +| ADP | OIDC | workos.com/docs/integrations/adp-oidc | +| Apple | General | workos.com/docs/integrations/apple | +| Auth0 | SAML | workos.com/docs/integrations/auth0-saml | +| Auth0 | Enterprise | workos.com/docs/integrations/auth0-enterprise-connection | +| Auth0 | Directory | workos.com/docs/integrations/auth0-directory-sync | +| AWS Cognito | General | workos.com/docs/integrations/aws-cognito | +| Bamboohr | General | workos.com/docs/integrations/bamboohr | +| Breathe HR | General | workos.com/docs/integrations/breathe-hr | +| Bubble | General | workos.com/docs/integrations/bubble | +| CAS | SAML | workos.com/docs/integrations/cas-saml | +| Cezanne HR | General | workos.com/docs/integrations/cezanne | +| Classlink | SAML | workos.com/docs/integrations/classlink-saml | +| Clever | OIDC | workos.com/docs/integrations/clever-oidc | +| Cloudflare | SAML | workos.com/docs/integrations/cloudflare-saml | +| Cyberark | SCIM | workos.com/docs/integrations/cyberark-scim | +| Cyberark | SAML | workos.com/docs/integrations/cyberark-saml | +| Duo | SAML | workos.com/docs/integrations/duo-saml | +| Entra ID (Azure AD) | SCIM | workos.com/docs/integrations/entra-id-scim | +| Entra ID (Azure AD) | SAML | workos.com/docs/integrations/entra-id-saml | +| Entra ID (Azure AD) | OIDC | workos.com/docs/integrations/entra-id-oidc | +| Firebase | General | workos.com/docs/integrations/firebase | +| Fourth | General | workos.com/docs/integrations/fourth | +| Github | OAuth | workos.com/docs/integrations/github-oauth | +| Gitlab | OAuth | workos.com/docs/integrations/gitlab-oauth | +| Google Workspace | SAML | workos.com/docs/integrations/google-saml | +| Google Workspace | OIDC | workos.com/docs/integrations/google-oidc | +| Google Workspace | OAuth | workos.com/docs/integrations/google-oauth | +| Google Workspace | Directory | workos.com/docs/integrations/google-directory-sync | +| Hibob | General | workos.com/docs/integrations/hibob | +| Intuit | OAuth | workos.com/docs/integrations/intuit-oauth | +| Jumpcloud | SCIM | workos.com/docs/integrations/jumpcloud-scim | +| Jumpcloud | SAML | workos.com/docs/integrations/jumpcloud-saml | +| Keycloak | SAML | workos.com/docs/integrations/keycloak-saml | +| Lastpass | SAML | workos.com/docs/integrations/lastpass-saml | +| Linkedin | OAuth | workos.com/docs/integrations/linkedin-oauth | +| Login.gov | OIDC | workos.com/docs/integrations/login-gov-oidc | +| Microsoft | OAuth | workos.com/docs/integrations/microsoft-oauth | +| Microsoft AD FS | SAML | workos.com/docs/integrations/microsoft-ad-fs-saml | +| Miniorange | SAML | workos.com/docs/integrations/miniorange-saml | +| NetIQ | SAML | workos.com/docs/integrations/net-iq-saml | +| NextAuth.js | General | workos.com/docs/integrations/next-auth | +| Oidc | General | workos.com/docs/integrations/oidc | +| Okta | SCIM | workos.com/docs/integrations/okta-scim | +| Okta | SAML | workos.com/docs/integrations/okta-saml | +| Okta | OIDC | workos.com/docs/integrations/okta-oidc | +| Onelogin | SCIM | workos.com/docs/integrations/onelogin-scim | +| Onelogin | SAML | workos.com/docs/integrations/onelogin-saml | +| Oracle | SAML | workos.com/docs/integrations/oracle-saml | +| Pingfederate | SCIM | workos.com/docs/integrations/pingfederate-scim | +| Pingfederate | SAML | workos.com/docs/integrations/pingfederate-saml | +| Pingone | SAML | workos.com/docs/integrations/pingone-saml | +| React Native Expo | General | workos.com/docs/integrations/react-native-expo | +| Rippling | SCIM | workos.com/docs/integrations/rippling-scim | +| Rippling | SAML | workos.com/docs/integrations/rippling-saml | +| Sailpoint | SCIM | workos.com/docs/integrations/sailpoint-scim | +| Salesforce | SAML | workos.com/docs/integrations/salesforce-saml | +| Salesforce | OAuth | workos.com/docs/integrations/salesforce-oauth | +| Saml | General | workos.com/docs/integrations/saml | +| Scim | General | workos.com/docs/integrations/scim | +| Sftp | General | workos.com/docs/integrations/sftp | +| Shibboleth Generic | SAML | workos.com/docs/integrations/shibboleth-generic-saml | +| Shibboleth Unsolicited | SAML | workos.com/docs/integrations/shibboleth-unsolicited-saml | +| SimpleSAMLphp | General | workos.com/docs/integrations/simple-saml-php | +| Slack | OAuth | workos.com/docs/integrations/slack-oauth | +| Supabase + AuthKit | General | workos.com/docs/integrations/supabase-authkit | +| Supabase + WorkOS SSO | General | workos.com/docs/integrations/supabase-sso | +| Vercel | OAuth | workos.com/docs/integrations/vercel-oauth | +| Vmware | SAML | workos.com/docs/integrations/vmware-saml | +| Workday | General | workos.com/docs/integrations/workday | +| Xero | OAuth | workos.com/docs/integrations/xero-oauth | + +## Step 2: Set Up the Connection + +WebFetch the provider-specific doc URL from the table above, then follow the protocol for the connection type. + +### SAML SSO Setup + +1. In WorkOS Dashboard, navigate to Organizations > [Org] > Authentication +2. Click Add Connection and select SAML +3. Copy these values from the connection detail page: + - ACS URL (Assertion Consumer Service) -- paste into IdP's SSO config + - SP Entity ID -- paste into IdP's audience/entity field +4. In the IdP admin console, create a new SAML application: + a. Set the ACS URL and SP Entity ID from step 3 + b. Configure attribute mapping: `id` → NameID, `email` → email, `firstName` → first name, `lastName` → last name + c. Download or copy the IdP Metadata URL (or download the metadata XML file) +5. Back in WorkOS Dashboard, upload the IdP metadata (URL or XML file) +6. Connection state transitions: Draft → Validating → Active + - If it stays in Draft, see Troubleshooting below + +### SCIM Directory Sync Setup + +1. In WorkOS Dashboard, navigate to Organizations > [Org] > Directory Sync +2. Click Add Directory and select the provider +3. Copy these values from the directory detail page: + - SCIM Endpoint URL (e.g., `https://api.workos.com/directories//scim/v2`) + - SCIM Bearer Token -- treat as a secret, never log or commit +4. In the IdP admin console, configure SCIM provisioning: + a. Set the SCIM Base URL to the endpoint from step 3 + b. Set Authentication to Bearer Token and paste the token + c. Enable provisioning actions: Create Users, Update User Attributes, Deactivate Users + d. Map user attributes: `userName` → email, `name.givenName` → first name, `name.familyName` → last name +5. Run a test push/sync from the IdP to verify users appear in WorkOS +6. Directory state transitions: Inactive → Validating → Linked + +### OAuth Social Login Setup + +1. In the OAuth provider's developer console, create a new OAuth application +2. Set the Redirect URI to the value from WorkOS Dashboard: + - Format: `https://auth.workos.com/sso/oauth/callback/` +3. Copy the Client ID and Client Secret from the provider +4. In WorkOS Dashboard, navigate to Authentication > Social Login +5. Select the provider and paste the Client ID and Client Secret +6. Configure scopes (typically: `openid`, `profile`, `email`) +7. Test by initiating a login flow through your application + +## Step 3: Verify the Integration + +```bash +# Check connection status via WorkOS API +curl -s -H "Authorization: Bearer $WORKOS_API_KEY" \ + https://api.workos.com/connections | jq '.data[] | {id, name, state}' + +# Verify SSO connection is active +curl -s -H "Authorization: Bearer $WORKOS_API_KEY" \ + https://api.workos.com/connections | jq '.data[] | select(.state == "active") | .name' + +# Check directory sync connections +curl -s -H "Authorization: Bearer $WORKOS_API_KEY" \ + https://api.workos.com/directories | jq '.data[] | {id, name, state}' +``` + +Checklist: + +- [ ] Connection appears in WorkOS Dashboard with "Active" (SSO) or "Linked" (directory) state +- [ ] Test SSO login succeeds with a test user +- [ ] User profile attributes map correctly (email, first name, last name, groups) +- [ ] (If SCIM) Directory sync shows users from provider +- [ ] (If OAuth) Social login redirects correctly and returns user profile + +## Integration Type Decision Tree + +``` +What type of integration? + | + +-- SSO (user login) + | | + | +-- Provider supports SAML? → Use SAML connection (preferred for enterprise) + | +-- Provider supports OIDC only? → Use OIDC connection + | +-- Provider supports both? → Prefer SAML (wider enterprise support, more attributes) + | + +-- Directory Sync (user provisioning) + | | + | +-- Provider supports SCIM? → Use SCIM connection + | +-- No SCIM but has API? → Check if WorkOS has a native directory for this provider + | +-- No directory support? → Consider SFTP-based import or manual sync + | + +-- OAuth (social login) + | + +-- Find provider in OAuth section of table above + +-- Follow OAuth setup steps in Step 2 +``` + +## Troubleshooting Decision Tree + +``` +Connection not working? + | + +-- Connection stuck in "Draft" + | | + | +-- Did you upload IdP metadata? → Upload XML or enter metadata URL in Dashboard + | +-- Metadata uploaded but still Draft? → Check metadata is valid XML, not HTML login page + | +-- Using metadata URL? → Verify URL is publicly reachable (not behind firewall) + | + +-- Connection stuck in "Validating" + | | + | +-- IdP metadata certificate expired? → Upload new cert or fresh metadata + | +-- ACS URL contains typo? → Re-copy from Dashboard, paste exactly + | +-- SP Entity ID mismatch? → Ensure IdP audience matches WorkOS SP Entity ID exactly + | + +-- SAML assertion errors + | | + | +-- "Recipient mismatch" → ACS URL in IdP config does not match WorkOS; re-copy it + | +-- "Audience mismatch" → SP Entity ID in IdP does not match; re-copy it + | +-- "Signature invalid" → IdP metadata/certificate is stale; re-upload current metadata + | +-- "Response expired" → Clock skew between IdP and SP; verify NTP sync on IdP server + | +-- "NameID missing" → IdP not sending NameID; add NameID mapping in IdP attribute config + | + +-- SCIM sync failing + | | + | +-- 401 Unauthorized → Bearer token is wrong or expired; regenerate in Dashboard + | +-- 404 Not Found → SCIM endpoint URL is wrong; re-copy from Dashboard + | +-- Users sync but no attributes → Check attribute mapping in IdP; must map userName, name.* + | +-- Users created but not updated → Ensure "Update User Attributes" is enabled in IdP provisioning + | +-- Deactivated users still active → Enable "Deactivate Users" in IdP provisioning settings + | + +-- OAuth redirect errors + | | + | +-- "redirect_uri_mismatch" → Redirect URI in provider console doesn't match WorkOS; copy exact URI + | +-- "invalid_client" → Client ID or Secret is wrong; re-copy from provider console + | +-- "access_denied" → User denied consent, or OAuth app not approved; check provider app status + | +-- Scopes error → Remove unsupported scopes; start with openid, profile, email + | + +-- "Organization not found" + | + +-- No org exists → Create organization in Dashboard first + +-- Org exists but connection not linked → Link connection to organization in Dashboard + +-- Domain not verified → Verify domain under Organizations > Domains (required for SSO) +``` + +## Error Recovery Reference + +| Problem | Root Cause | Fix | +| -------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------- | +| Connection stuck in "Draft" | IdP metadata not uploaded or invalid | Upload valid IdP metadata XML or enter a reachable metadata URL in Dashboard | +| Connection stuck in "Validating" | Certificate expired or ACS/Entity ID mismatch | Re-upload fresh metadata; verify ACS URL and SP Entity ID match exactly | +| SAML "Recipient mismatch" | ACS URL in IdP does not match WorkOS value | Copy the exact ACS URL from the connection's details in the WorkOS Dashboard | +| SAML "Audience mismatch" | SP Entity ID in IdP does not match WorkOS | Copy exact SP Entity ID from Dashboard; paste into IdP audience/entity field | +| SAML "Signature invalid" | IdP certificate rotated but WorkOS has old cert | Re-download IdP metadata and re-upload to WorkOS Dashboard | +| SAML "Response expired" | Clock skew between IdP server and WorkOS | Sync IdP server time via NTP; most assertions allow 5-minute skew | +| SCIM 401 Unauthorized | Bearer token is expired or was regenerated | Copy the current bearer token from the directory's SCIM settings in the Dashboard | +| SCIM 404 Not Found | Endpoint URL has wrong directory ID or path | Re-copy full SCIM endpoint URL from Dashboard | +| SCIM sync no attributes | Attribute mapping missing in IdP | Map userName, name.givenName, name.familyName in IdP SCIM config | +| SCIM users not deactivated | Deprovisioning not enabled | Enable "Deactivate Users" in IdP provisioning settings | +| OAuth "redirect_uri_mismatch" | Redirect URI in provider console is different | Paste exact redirect URI from WorkOS Dashboard into provider OAuth app | +| OAuth "invalid_client" | Client ID or Secret is wrong | Re-copy Client ID and Secret from provider developer console | +| "Organization not found" | Connection not linked to an organization | Create org in Dashboard, then link the connection to it | +| "Domain not verified" | SSO requires a verified domain | Add and verify the organization's domain in the Dashboard | + +## Related Skills + +- workos-sso: General SSO implementation and configuration +- workos-directory-sync: Directory Sync setup and management +- workos-domain-verification: Domain verification required for SSO +- workos-admin-portal: Let customers configure their own connections diff --git a/plugins/workos/skills/workos/references/workos-kotlin.md b/plugins/workos/skills/workos/references/workos-kotlin.md new file mode 100644 index 00000000..0cfae85c --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-kotlin.md @@ -0,0 +1,190 @@ +# WorkOS AuthKit for Kotlin (Spring Boot) + +## Step 1: Fetch SDK Documentation (BLOCKING) + +STOP. Do not proceed until complete. + +WebFetch: `https://raw.githubusercontent.com/workos/workos-kotlin/main/README.md` + +The README is the source of truth. If this skill conflicts with README, follow README. + +## Step 2: Pre-Flight Validation + +### Project Structure + +- Confirm `build.gradle.kts` exists (Kotlin DSL) or `build.gradle` (Groovy DSL) +- Confirm Spring Boot plugin is present (`org.springframework.boot`) +- Detect Gradle wrapper: check if `./gradlew` exists + +### Gradle Wrapper + +```bash +# If gradlew exists, ensure it's executable +if [ -f ./gradlew ]; then chmod +x ./gradlew; fi +``` + +Use `./gradlew` if wrapper exists, otherwise fall back to `gradle`. + +### Environment Variables + +Check `application.properties` or `application.yml` for: + +- `workos.api-key` or `WORKOS_API_KEY` +- `workos.client-id` or `WORKOS_CLIENT_ID` + +## Step 2b: Partial Install Recovery + +Before creating new files, check if a previous AuthKit attempt exists: + +1. Check if `workos-kotlin` is already in `build.gradle.kts` dependencies +2. Check for incomplete auth code -- WorkOS imported/instantiated but no controller with login/callback endpoints +3. If partial install detected: + - Do NOT re-add the SDK dependency (it's already there) + - Read existing source files to understand what's done vs missing + - Complete the integration by filling gaps (controller, config bean, routes) + - Preserve any working code -- only fix what's broken + - Check for a missing `/auth/callback` endpoint (most common gap) + +## Step 2c: Existing Auth System Detection + +Check for existing authentication before integrating: + +``` +build.gradle.kts has 'spring-boot-starter-security'? → Spring Security +*.kt files have 'SecurityFilterChain'? → Security filter config +*.kt files have 'formLogin'? → Form-based auth +*.kt files have 'oauth2Login'? → OAuth2 auth +*.kt files have 'httpBasic'? → Basic auth +``` + +If existing auth detected: + +- Do NOT remove or disable it +- Add WorkOS AuthKit alongside the existing system +- If Spring Security is configured, ensure WorkOS auth routes are permitted through the security filter chain (add `.requestMatchers("/auth/workos/").permitAll()`) +- Create separate route paths for WorkOS auth (e.g., `/auth/workos/login` if `/login` is taken) +- Ensure existing auth routes continue to work unchanged +- Document in code comments how to migrate fully to WorkOS later + +## Step 3: Install SDK + +Add the WorkOS Kotlin SDK dependency to `build.gradle.kts`: + +```kotlin +dependencies { + implementation("com.workos:workos-kotlin:4.18.1") + // ... existing dependencies +} +``` + +Check the README for the latest version number -- use the version from the README if it differs from above. + +JVM target: Ensure `jvmTarget` in `build.gradle.kts` matches the JDK on the system. Check with `java -version`. Common values: `"17"`, `"21"`. If `kotlin { jvmToolchain(...) }` is set, ensure it matches too. + +Verify: Run `./gradlew dependencies` or `gradle dependencies` to confirm the dependency resolves. + +## Step 4: Configure Authentication + +### 4a: Application Properties + +Add WorkOS configuration to `src/main/resources/application.properties`: + +```properties +workos.api-key=${WORKOS_API_KEY} +workos.client-id=${WORKOS_CLIENT_ID} +workos.redirect-uri=http://localhost:8080/auth/callback +``` + +Or if the project uses `application.yml`, add the equivalent YAML. + +### 4b: Create WorkOS Configuration Bean + +Create a configuration class that initializes the WorkOS client: + +```kotlin +@Configuration +class WorkOSConfig { + @Value("\${workos.api-key}") + lateinit var apiKey: String + + @Bean + fun workos(): WorkOS = WorkOS(apiKey) +} +``` + +Adapt based on the SDK README -- the exact client initialization may vary. + +### 4c: Create Auth Controller + +Create a Spring `@RestController` with these endpoints: + +1. GET /auth/login -- Redirect user to WorkOS AuthKit hosted login + - Use `workos.userManagement.getAuthorizationUrl()` -- this returns a URL string + - Parameters: `clientId`, `redirectUri`, `provider = "authkit"` + - The method uses a builder pattern: `.provider("authkit").redirectUri(uri).build()` + +2. GET /auth/callback -- Exchange authorization code for user profile + - Extract `code` query parameter + - Call `workos.userManagement.authenticateWithCode()` with the code and clientId + - Store user session (use Spring's `HttpSession`) + - Redirect to home page + +3. GET /auth/logout -- Clear session and redirect + - Invalidate `HttpSession` + - Redirect to home page or WorkOS logout URL + +Follow the README for exact API method names and parameters.** + +## Step 5: Session Management + +Use Spring's built-in `HttpSession` for session management: + +- Store user profile in session after callback +- Check session in protected routes +- Clear session on logout + +If Spring Security is already configured, integrate with the existing security filter chain rather than replacing it. + +## Step 6: Verification + +Run the build to verify everything compiles: + +```bash +./gradlew build +``` + +If build fails: + +- Check dependency resolution: `./gradlew dependencies | grep workos` +- Check for missing imports in the auth controller +- Verify application.properties syntax +- Gradle builds can be slow (30-60s) -- be patient + +### Checklist + +- [ ] WorkOS SDK dependency in build.gradle.kts +- [ ] Application properties configured +- [ ] Auth controller with login, callback, logout endpoints +- [ ] Build succeeds (`./gradlew build`) + +## Error Recovery + +### Dependency resolution failure + +- Check Maven Central is accessible +- Verify the artifact coordinates match README exactly +- Ensure `mavenCentral()` is in the `repositories` block of build.gradle.kts + +### "Could not resolve com.workos:workos-kotlin" + +- The package may use a different group ID -- check README +- Ensure repositories block includes `mavenCentral()` + +### Build fails with missing Spring Boot annotations + +- Verify `org.springframework.boot` plugin is applied +- Check Spring Boot starter dependencies are present + +### Gradle wrapper permission denied + +- Run `chmod +x ./gradlew` before building diff --git a/plugins/workos/skills/workos/references/workos-management.md b/plugins/workos/skills/workos/references/workos-management.md new file mode 100644 index 00000000..360cb7c6 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-management.md @@ -0,0 +1,290 @@ +# WorkOS Management Commands + +Use these commands to manage WorkOS resources directly from the terminal. The CLI must be authenticated via `workos auth login` or `WORKOS_API_KEY` env var. + +All commands support `--json` for structured output. Use `--json` when you need to parse output (e.g., extract an ID). + +## Checking what the CLI can do (READ THIS FIRST) + +If a user asks whether the CLI supports operation X, or if you're about to suggest a `workos ...` command, verify it first. The authoritative, machine-readable command tree is: + +```bash +workos --help --json +``` + +This emits a complete JSON tree of every registered command, subcommand, and flag. If a command or subcommand is not in that output, it does not exist -- do not invent it, do not suggest it, do not assume "it must be there." Common agent failure mode: seeing that `workos ` supports `list/get/delete` and assuming `create` / `update` also exist when they don't. + +Rule: Before suggesting any `workos` command not listed in the Quick Reference table below, either (a) run `workos --help --json` and confirm the command exists, or (b) tell the user "this doesn't appear to be in the CLI -- verify with `workos --help --json`." Never guess. + +The tables below are a snapshot and may lag the published CLI. The live `--help --json` output is the source of truth. + +## Detecting and recommending CLI upgrades + +If `workos --help --json` is missing a command you expected, or the user reports `unknown command: ` for a command that exists in the latest release, the user is likely on an outdated CLI rather than encountering a bug. Before suggesting a workaround: + +1. Ask the user to run `workos --version`. +2. Compare against the latest published version with `npm view workos version` (do NOT guess the latest version from memory -- it moves frequently). +3. If the user is behind, send them to `references/workos-cli-upgrade.md` for the upgrade command for their package manager (npm/pnpm) and the no-install `npx workos@latest` fallback. + +## Quick Reference + +| Task | Command | +| ---------------------- | ---------------------------------------------------------------------------- | +| List organizations | `workos organization list` | +| Create organization | `workos organization create "Acme Corp" acme.com:verified` | +| List users | `workos user list --email=alice@acme.com` | +| Create permission | `workos permission create --slug=read-users --name="Read Users"` | +| Create role | `workos role create --slug=admin --name=Admin` | +| Assign perms to role | `workos role set-permissions admin --permissions=read-users,write-users` | +| Create org-scoped role | `workos role create --slug=admin --name=Admin --org=org_xxx` | +| Add user to org | `workos membership create --org=org_xxx --user=user_xxx` | +| Send invitation | `workos invitation send --email=alice@acme.com --org=org_xxx` | +| Revoke session | `workos session revoke ` | +| Add redirect URI | `workos config redirect add http://localhost:3000/callback` | +| Add CORS origin | `workos config cors add http://localhost:3000` | +| Set homepage URL | `workos config homepage-url set http://localhost:3000` | +| Create webhook | `workos webhook create --url=https://example.com/hook --events=user.created` | +| List SSO connections | `workos connection list --org=org_xxx` | +| List directories | `workos directory list` | +| Toggle feature flag | `workos feature-flag enable my-flag` | +| Store a secret | `workos vault create --name=api-secret --value=sk_xxx --org=org_xxx` | +| Generate portal link | `workos portal generate-link --intent=sso --org=org_xxx` | +| Seed environment | `workos seed --file=workos-seed.yml` | +| Debug SSO | `workos debug-sso conn_xxx` | +| Debug directory sync | `workos debug-sync directory_xxx` | +| Set up an org | `workos setup-org "Acme Corp" --domain=acme.com --roles=admin,viewer` | +| Onboard a user | `workos onboard-user alice@acme.com --org=org_xxx --role=admin` | + +## Workflows + +### Setting up RBAC + +When you see permission checks in the codebase (e.g., `hasPermission('read-users')`), create the matching WorkOS resources: + +```bash +workos permission create --slug=read-users --name="Read Users" +workos permission create --slug=write-users --name="Write Users" +workos role create --slug=admin --name=Admin +workos role set-permissions admin --permissions=read-users,write-users +workos role create --slug=viewer --name=Viewer +workos role set-permissions viewer --permissions=read-users +``` + +For organization-scoped roles, add `--org=org_xxx` to role commands. + +### Organization Onboarding + +One-shot setup with the compound command: + +```bash +workos setup-org "Acme Corp" --domain=acme.com --roles=admin,viewer +``` + +Or step by step: + +```bash +ORG_ID=$(workos organization create "Acme Corp" --json | jq -r '.data.id') +workos org-domain create acme.com --org=$ORG_ID +workos role create --slug=admin --name=Admin --org=$ORG_ID +workos portal generate-link --intent=sso --org=$ORG_ID +``` + +### User Onboarding + +```bash +workos onboard-user alice@acme.com --org=org_xxx --role=admin +``` + +Or step by step: + +```bash +workos invitation send --email=alice@acme.com --org=org_xxx --role=admin +workos membership create --org=org_xxx --user=user_xxx --role=admin +``` + +### Local Development Setup + +Configure WorkOS for local development: + +```bash +workos config redirect add http://localhost:3000/callback +workos config cors add http://localhost:3000 +workos config homepage-url set http://localhost:3000 +``` + +### Environment Seeding + +Create a `workos-seed.yml` file in your repo: + +```yaml +permissions: + - name: 'Read Users' + slug: 'read-users' + - name: 'Write Users' + slug: 'write-users' + +roles: + - name: 'Admin' + slug: 'admin' + permissions: ['read-users', 'write-users'] + - name: 'Viewer' + slug: 'viewer' + permissions: ['read-users'] + +organizations: + - name: 'Test Org' + domains: ['test.com'] + +config: + redirect_uris: ['http://localhost:3000/callback'] + cors_origins: ['http://localhost:3000'] + homepage_url: 'http://localhost:3000' +``` + +Then run: + +```bash +workos seed --file=workos-seed.yml # Create resources +workos seed --clean # Tear down seeded resources +``` + +### Debugging SSO + +```bash +workos debug-sso conn_xxx +``` + +Shows: connection type/state, organization binding, recent auth events, and common issues (inactive connection, org mismatch). + +### Debugging Directory Sync + +```bash +workos debug-sync directory_xxx +``` + +Shows: directory type/state, user/group counts, recent sync events, and stall detection. + +### Webhook Management + +```bash +workos webhook list +workos webhook create --url=https://example.com/hook --events=user.created,dsync.user.created +workos webhook delete we_xxx +``` + +### Audit Logs + +```bash +workos audit-log create-event --org=org_xxx --action=user.login --actor-type=user --actor-id=user_xxx +workos audit-log list-actions +workos audit-log get-schema user.login +workos audit-log export --org=org_xxx --range-start=2024-01-01 --range-end=2024-02-01 +workos audit-log get-retention --org=org_xxx +``` + +## Using --json for Structured Output + +All commands support `--json` for machine-readable output. Use this when you need to extract values: + +```bash +# Get an organization ID +workos organization list --json | jq '.data[0].id' + +# Get a connection's state +workos connection get conn_xxx --json | jq '.state' + +# List all role slugs +workos role list --json | jq '.data[].slug' + +# Chain commands: create org then add domain +ORG_ID=$(workos organization create "Acme" --json | jq -r '.data.id') +workos org-domain create acme.com --org=$ORG_ID +``` + +JSON output format: + +- List commands: `{ "data": [...], "listMetadata": { "before": null, "after": "cursor" } }` +- Get commands: Raw object (no wrapper) +- Create/Update/Delete: `{ "status": "ok", "message": "...", "data": {...} }` +- Errors: `{ "error": { "code": "...", "message": "..." } }` on stderr + +## Command Reference + +### Resource Commands + +| Command | Subcommands | +| --------------------- | ----------------------------------------------------------------------------------------------------- | +| `workos organization` | `list`, `get`, `create`, `update`, `delete` | +| `workos user` | `list`, `get`, `update`, `delete` | +| `workos role` | `list`, `get`, `create`, `update`, `delete`, `set-permissions`, `add-permission`, `remove-permission` | +| `workos permission` | `list`, `get`, `create`, `update`, `delete` | +| `workos membership` | `list`, `get`, `create`, `update`, `delete`, `deactivate`, `reactivate` | +| `workos invitation` | `list`, `get`, `send`, `revoke`, `resend` | +| `workos session` | `list`, `revoke` | +| `workos connection` | `list`, `get`, `delete` | +| `workos directory` | `list`, `get`, `delete`, `list-users`, `list-groups` | +| `workos event` | `list` (requires `--events` flag) | +| `workos audit-log` | `create-event`, `export`, `list-actions`, `get-schema`, `create-schema`, `get-retention` | +| `workos feature-flag` | `list`, `get`, `enable`, `disable`, `add-target`, `remove-target` | +| `workos webhook` | `list`, `create`, `delete` | +| `workos config` | `redirect add`, `cors add`, `homepage-url set` | +| `workos portal` | `generate-link` | +| `workos vault` | `list`, `get`, `get-by-name`, `create`, `update`, `delete`, `describe`, `list-versions` | +| `workos api-key` | `list`, `create`, `validate`, `delete` | +| `workos org-domain` | `get`, `create`, `verify`, `delete` | + +### Workflow Commands + +| Command | Purpose | +| ----------------------------- | ------------------------------------------- | +| `workos seed --file=` | Declarative resource provisioning from YAML | +| `workos seed --clean` | Tear down seeded resources | +| `workos setup-org ` | One-shot org onboarding | +| `workos onboard-user ` | Send invitation + optional wait | +| `workos debug-sso ` | SSO connection diagnostics | +| `workos debug-sync ` | Directory sync diagnostics | + +### Common Flags + +| Flag | Purpose | Scope | +| ------------------------------------------- | ------------------------ | --------------------------------------------------- | +| `--json` | Structured JSON output | All commands | +| `--api-key` | Override API key | Resource commands | +| `--org` | Organization scope | role, membership, invitation, api-key, feature-flag | +| `--force` | Skip confirmation prompt | connection delete, directory delete | +| `--limit`, `--before`, `--after`, `--order` | Pagination | All list commands | + +## Not in the CLI (use Dashboard, Admin Portal, API, or a different workflow) + +These operations are commonly asked for but are not supported in the WorkOS CLI today. Do not invent commands for them. For each, the right answer is listed. + +### Dashboard / Admin Portal only + +| Operation | Where it lives | Docs | +| --------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| Create an SSO connection | Admin Portal (generate via `workos portal generate-link --intent=sso --org=`) | https://workos.com/docs/sso/guide | +| Create a Directory Sync connection | Admin Portal (generate via `workos portal generate-link --intent=dsync --org=`) | https://workos.com/docs/directory-sync/quick-start | +| Map IdP (Entra/AD/Okta/Google Workspace) groups to WorkOS roles | Admin Portal during directory setup, or directory page in Dashboard | https://workos.com/docs/directory-sync/identity-provider-role-assignment | +| Map SSO groups to WorkOS roles | Admin Portal during SSO setup, or connection page in Dashboard | https://workos.com/docs/rbac/idp-role-assignment | +| Enable/disable Admin Portal role-assignment step | Authorization page in the WorkOS Dashboard | https://workos.com/docs/directory-sync/identity-provider-role-assignment | +| Enable/disable authentication methods | Authentication settings in the WorkOS Dashboard | https://workos.com/docs/authkit | +| Configure session lifetime | Authentication settings in the WorkOS Dashboard | https://workos.com/docs/user-management/sessions | +| Set up social login providers (Google, GitHub, etc.) | Authentication settings in the WorkOS Dashboard | https://workos.com/docs/user-management/social-login | +| Create feature flags | Feature Flags page in the WorkOS Dashboard (toggle/target ops work via CLI) | https://workos.com/docs/feature-flags | +| Configure branding (logos, colors) | Branding settings in the WorkOS Dashboard | https://workos.com/docs/admin-portal/branding | +| Set up email templates | Email settings in the WorkOS Dashboard | https://workos.com/docs/emails | +| Manage billing / plan | Settings in the WorkOS Dashboard | -- | + +### API-only (not in CLI, but can be scripted via SDK / REST) + +| Operation | Where it lives | Notes | +| -------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | +| Assign a role to an individual user | `updateOrganizationMembership` via SDK/REST | Warning: IdP mapping silently overrides this on next sync/login when mapping exists. See `workos-rbac.md`. | +| Webhook signature verification | SDK (`workos.webhooks.verifyEvent`) | CLI can create/list/delete webhooks but does not verify events | +| Session introspection / JWT validation | SDK | CLI has `workos session list/revoke` only | + +Rule of thumb: if a user asks "is there a CLI command for X" and X is not in the Quick Reference table above and is not produced by `workos --help --json`, the answer is no. Do not speculate. Point the user at the right surface per this table. + +### Do not invent click-paths in the Dashboard + +The paths in the "Where it lives" column above are intentionally described in conceptual terms ("Authentication settings", "directory page") rather than as literal click-paths like "Dashboard > Organizations > X > Y". The docs don't commit to exact menu paths, and the Dashboard UI is re-organized periodically. Link the user to the docs URL and let them navigate. If you see yourself writing `Dashboard > A > B > C` or `dashboard.workos.com/some/path`, stop and link to docs instead. diff --git a/plugins/workos/skills/workos/references/workos-mfa.md b/plugins/workos/skills/workos/references/workos-mfa.md new file mode 100644 index 00000000..18d371a0 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-mfa.md @@ -0,0 +1,20 @@ +# WorkOS Multi-Factor Authentication + +## Docs + +- https://workos.com/docs/mfa/index +- https://workos.com/docs/mfa/example-apps +- https://workos.com/docs/mfa/ux/sign-in +- https://workos.com/docs/mfa/ux/enrollment + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- MFA API is NOT compatible with WorkOS SSO. For SSO users, use the IdP's built-in MFA features instead. +- You must persist `factor.id` in your user database. Without it, the enrolled factor cannot be used for future challenges. +- Challenges are single-use. Attempting to verify a challenge twice returns "challenge already verified." Create a new challenge for each sign-in attempt. +- SMS challenges expire after 10 minutes. TOTP has no such limit but is affected by device clock skew. +- `qr_code` from TOTP enrollment is a data URI, not a URL. Use it directly as an image `src` -- do not attempt to fetch it. +- SMS phone numbers must be E.164 format (`+1234567890`). Other formats (e.g., `(123) 456-7890`) cause a 400 error. +- TOTP enrollment requires both `totp_issuer` and `totp_user` parameters. Omitting either causes a 400 error. +- MFA verifies factor ownership, not user identity. It supplements primary authentication -- never use it as the sole auth method. diff --git a/plugins/workos/skills/workos/references/workos-migrate-auth0.md b/plugins/workos/skills/workos/references/workos-migrate-auth0.md new file mode 100644 index 00000000..aebc22db --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-migrate-auth0.md @@ -0,0 +1,14 @@ +# WorkOS Migration: Auth0 + +## Docs + +- https://workos.com/docs/migrate/auth0 + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- Auth0 does NOT export plaintext passwords. Only bcrypt hashes are available, and only via a support ticket (1-2 week turnaround). Decide upfront whether you need them -- requesting after starting the export adds weeks to the timeline. +- `password_hash_type` MUST be `'bcrypt'` for Auth0 exports. Other algorithm values will silently fail. +- Auth0 bcrypt hashes must start with `$2a$`, `$2b$`, or `$2y$`. If the prefix is missing, the export may be incomplete -- contact Auth0 support. +- If a user already exists in WorkOS (duplicate email), the Create User API will reject it. Use Update User API instead. +- WorkOS has an official migration tool at https://github.com/workos/migrate-auth0-users that handles rate limiting, retries, and duplicates automatically. diff --git a/plugins/workos/skills/workos/references/workos-migrate-aws-cognito.md b/plugins/workos/skills/workos/references/workos-migrate-aws-cognito.md new file mode 100644 index 00000000..c9a07f56 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-migrate-aws-cognito.md @@ -0,0 +1,15 @@ +# WorkOS Migration: AWS Cognito + +## Docs + +- https://workos.com/docs/migrate/aws-cognito + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- AWS Cognito does not export password hashes or MFA keys (Cognito platform limitation). WorkOS supports hash import for other providers (bcrypt, scrypt, argon2, pbkdf2, ssha, firebase-scrypt), but since Cognito won't export them, all migrated users must reset their password. +- There is NO JIT (just-in-time) migration path for Cognito. Cognito does not expose a password verification endpoint. The only path is bulk import of user attributes + forced password reset. +- OAuth users do NOT need password resets -- their provider tokens continue working after migration. +- WORKOS_COOKIE_PASSWORD applies to ALL server-side AuthKit framework SDKs (Next.js, Remix, React Router, TanStack Start, SvelteKit) -- not just Next.js. +- Bulk password reset emails can be flagged as spam. Slow send rate to 1 req/second max and verify SPF/DKIM records if using a custom email domain. +- Do NOT set the password field when importing users. Users imported without passwords are automatically flagged for password reset. diff --git a/plugins/workos/skills/workos/references/workos-migrate-better-auth.md b/plugins/workos/skills/workos/references/workos-migrate-better-auth.md new file mode 100644 index 00000000..128d8fc0 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-migrate-better-auth.md @@ -0,0 +1,14 @@ +# WorkOS Migration: Better Auth + +## Docs + +- https://workos.com/docs/migrate/better-auth + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- Password hashes live in the `account` table (not `user`), filtered by `providerId = 'credential'`. Do NOT skip this table. +- Better Auth uses scrypt by default. If you customized the hash algorithm, you must know which one it is before importing to WorkOS. +- The `password` column contains the full hash string including algorithm parameters. Do NOT strip prefixes or decode it. +- Import order matters when organizations are involved: create orgs first, then import users, then password hashes, then assign memberships. Out-of-order imports will fail with "Organization not found." +- Better Auth sessions are JWT-based and remain valid until expiry even after migration. This is expected -- not an error. To force immediate cutover, rotate the Better Auth secret key. diff --git a/plugins/workos/skills/workos/references/workos-migrate-clerk.md b/plugins/workos/skills/workos/references/workos-migrate-clerk.md new file mode 100644 index 00000000..3bf5b915 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-migrate-clerk.md @@ -0,0 +1,15 @@ +# WorkOS Migration: Clerk + +## Docs + +- https://workos.com/docs/migrate/clerk + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- Clerk exports multiple emails pipe-separated (e.g., `john@example.com|john.doe@example.com`) and does NOT indicate which is the primary email. If you can't call the Clerk API per user to resolve `primary_email_address_id`, you must pick the first email and document the choice. +- Clerk does NOT provide plaintext passwords. Password hashes are only available via the Clerk Backend API export, not the standard dashboard export. +- WorkOS users have a single primary email. You must pick ONE from Clerk's pipe-separated list. +- Clerk export may include deleted/suspended users. Filter these before import or you'll get count mismatches. +- Duplicate emails in the Clerk export will cause WorkOS rejections -- deduplicate before importing. +- WorkOS has an official migration tool at https://github.com/workos/migrate-clerk-users that handles rate limits and retries. diff --git a/plugins/workos/skills/workos/references/workos-migrate-descope.md b/plugins/workos/skills/workos/references/workos-migrate-descope.md new file mode 100644 index 00000000..a1e1a66b --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-migrate-descope.md @@ -0,0 +1,13 @@ +# WorkOS Migration: Descope + +## Docs + +- https://workos.com/docs/migrate/descope + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- Descope does NOT expose password hashes via API. A support ticket is required to get a CSV export with hashes -- this is not self-service. +- When requesting the export from Descope support, confirm which hashing algorithm was used (bcrypt, argon2, or pbkdf2). You need this value for the `password_hash_type` parameter during WorkOS import. +- The algorithm name must match exactly: `"bcrypt"` not `"bcrypt_sha256"` or `"bcrypt-sha256"`. Typos cause silent rejection. +- If Descope used a hashing algorithm not supported by WorkOS, you must fall back to the password reset flow. Contact WorkOS support before attempting import. diff --git a/plugins/workos/skills/workos/references/workos-migrate-firebase.md b/plugins/workos/skills/workos/references/workos-migrate-firebase.md new file mode 100644 index 00000000..37a46cf3 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-migrate-firebase.md @@ -0,0 +1,16 @@ +# WorkOS Migration: Firebase + +## Docs + +- https://workos.com/docs/migrate/firebase + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- Firebase uses a non-standard scrypt variant. Password hashes must be converted to PHC format with Firebase-specific parameters (`sk=` signer key, `ss=` salt separator). Missing either parameter causes import failure. +- Firebase exports use base64 encoding. WorkOS expects base64 in the PHC string -- do NOT decode the bytes before importing. +- Firebase users can have MULTIPLE auth methods (e.g., password + Google). A user with both needs both migration paths (password import AND OAuth reconnection). +- Firebase's OAuth redirect URI is `/__/auth/handler`. WorkOS uses your custom callback URL. The redirect URI must match exactly or OAuth will fail silently. +- WorkOS user IDs differ from Firebase UIDs. You must store the mapping (`firebase_uid` -> `workos_user_id`) in your database. +- For SAML/OIDC enterprise connections, the identity provider admin must update THEIR config with the new WorkOS callback URL. Coordinate timing -- this is a cross-org dependency. +- Firebase Email Link sends a link that completes auth in the same browser. WorkOS Magic Auth sends a code the user enters in-app. The UX is different -- plan for user communication. diff --git a/plugins/workos/skills/workos/references/workos-migrate-other-services.md b/plugins/workos/skills/workos/references/workos-migrate-other-services.md new file mode 100644 index 00000000..62828c62 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-migrate-other-services.md @@ -0,0 +1,15 @@ +# WorkOS Migration: Other Services + +## Docs + +- https://workos.com/docs/migrate/other-services + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- WorkOS only supports these hash algorithms for password import: bcrypt, scrypt, pbkdf2, argon2, ssha, firebase-scrypt. If your source uses md5, sha1, or a custom algorithm, you cannot import passwords -- use the password reset flow instead. +- OAuth tokens CANNOT be imported for security reasons. Social auth users must re-authenticate with their provider. WorkOS links accounts automatically by email match -- if emails differ between WorkOS and the social profile, the user sees a "create new account" flow instead of linking. +- WorkOS user IDs (`user_01...`) are new. You MUST persist the mapping from your old system's IDs. Failing to do so breaks all foreign key references. +- Email matching for social account linking is case-sensitive. If the WorkOS user email doesn't exactly match the social profile email, auto-linking fails silently. +- Migration scripts MUST be idempotent. Track migration status per user -- re-running a non-idempotent script creates duplicates. +- Bulk password reset emails can be throttled or spam-filtered. Batch at 10 resets/sec max and verify your sending domain in WorkOS Dashboard. diff --git a/plugins/workos/skills/workos/references/workos-migrate-stytch.md b/plugins/workos/skills/workos/references/workos-migrate-stytch.md new file mode 100644 index 00000000..d2cd25da --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-migrate-stytch.md @@ -0,0 +1,15 @@ +# WorkOS Migration: Stytch + +## Docs + +- https://workos.com/docs/migrate/stytch + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- Stytch password export requires a support ticket (support@stytch.com). Start this FIRST -- it's the bottleneck with variable turnaround time. Do not proceed with password import until hashes are received. +- Stytch uses scrypt for password hashing. Verify the hash format from the Stytch export matches WorkOS requirements before bulk import -- test with ONE user first. +- Stytch Search API has a 100 requests/minute rate limit. For large datasets, add delays between batches or you'll get throttled during export. +- Domain conflicts: if a domain is already claimed by another WorkOS organization, the org import fails with "Domain already exists." Import without `domainData` and resolve conflicts manually. +- Organizations must be imported BEFORE users. If the org mapping is lost or orgs aren't created yet, user import fails with "Missing organization ID." +- For consumer (non-B2B) users, use the Stytch export utility at https://github.com/stytchauth/stytch-node-export-users instead of the Search API. diff --git a/plugins/workos/skills/workos/references/workos-migrate-supabase-auth.md b/plugins/workos/skills/workos/references/workos-migrate-supabase-auth.md new file mode 100644 index 00000000..718d721f --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-migrate-supabase-auth.md @@ -0,0 +1,15 @@ +# WorkOS Migration: Supabase Auth + +## Docs + +- https://workos.com/docs/migrate/supabase + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- Supabase uses bcrypt. Verify exported hashes start with `$2a$`, `$2b$`, or `$2y$`. If verification fails, re-export -- corrupted hashes cannot be recovered. +- Do NOT use Supabase UUIDs as WorkOS user IDs. WorkOS generates its own IDs (`user_*`). Store the mapping (`supabase_user_id` -> `workos_user_id`) in your database. +- You CANNOT migrate active sessions from Supabase to WorkOS. Sessions are provider-specific. Users with valid Supabase sessions must be forced to re-authenticate with WorkOS. +- Supabase `phone` field cannot be migrated -- WorkOS AuthKit uses email-based auth. +- If password hash export is corrupted, use explicit SQL cast: `CAST(encrypted_password AS TEXT)` during re-export. +- For >10,000 users, contact WorkOS support for bulk import assistance rather than scripting it yourself. diff --git a/plugins/workos/skills/workos/references/workos-migrate-the-standalone-sso-api.md b/plugins/workos/skills/workos/references/workos-migrate-the-standalone-sso-api.md new file mode 100644 index 00000000..6818d606 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-migrate-the-standalone-sso-api.md @@ -0,0 +1,15 @@ +# WorkOS Migration: Standalone SSO API + +## Docs + +- https://workos.com/docs/migrate/standalone-sso + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- User IDs change when migrating from standalone SSO API to AuthKit. Old Profile IDs (`user_xxx` from `sso.getProfileAndToken`) are NOT the same as new AuthKit User IDs. If Profile IDs are foreign keys in your database, you need a migration script before cutover. +- `authenticateWithCode` requires a `clientId` parameter (`client_xxx`) that the standalone SSO API's `getProfileAndToken` did not require. Missing or wrong `clientId` causes "Invalid client_id" errors. +- AuthKit returns new error types that the standalone SSO API never produced: `email_verification_required`, `mfa_enrollment_required`, and `mfa_challenge_required`. If your callback doesn't handle these, logins will appear to fail silently. +- Using the hosted UI (`provider: "authkit"`) handles email verification and MFA challenges automatically. With the API directly, you must implement those flows yourself. +- Use exactly two terms: "standalone SSO API" for the old system, "AuthKit" for the new system. The docs use these terms consistently. Do NOT use bare "SSO" or "SSO API" -- it creates ambiguity. +- WORKOS_COOKIE_PASSWORD applies to ALL server-side AuthKit framework SDKs (Next.js, Remix, React Router, TanStack Start, SvelteKit) -- not just Next.js. diff --git a/plugins/workos/skills/workos/references/workos-node.md b/plugins/workos/skills/workos/references/workos-node.md new file mode 100644 index 00000000..7699d292 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-node.md @@ -0,0 +1,192 @@ +# WorkOS AuthKit for Node.js + +## Step 1: Fetch SDK Documentation (BLOCKING) + +STOP - Do not proceed until complete. + +WebFetch: `https://raw.githubusercontent.com/workos/workos-node/main/README.md` + +Also fetch the AuthKit quickstart for reference: +WebFetch: `https://workos.com/docs/authkit/vanilla/nodejs` + +README is the source of truth for all SDK patterns. README overrides this skill if conflict. + +## Step 2: Detect Framework & Project Structure + +``` +package.json has 'express'? → Express +package.json has 'fastify'? → Fastify +package.json has 'hono'? → Hono +package.json has 'koa'? → Koa +None of the above? → Vanilla Node.js http (use Express quickstart pattern) + +tsconfig.json exists? → TypeScript (.ts files) +"type": "module" in package.json? → ESM (import/export) +else → CJS (require/module.exports) +``` + +Detect entry point: `src/index.ts`, `src/app.ts`, `app.js`, `server.js`, `index.js` + +Detect package manager: `pnpm-lock.yaml` → `yarn.lock` → `bun.lockb` → npm + +Adapt all subsequent steps to the detected framework and module system. + +## Step 2b: Partial Install Recovery + +Before creating new files, check if a previous AuthKit attempt exists: + +1. Check if `@workos-inc/node` is already in `package.json` +2. Check for incomplete auth files -- routes/handlers that import `@workos-inc/node` but are non-functional (TODO comments, 501 responses, empty handlers) +3. If partial install detected: + - Do NOT reinstall the SDK (it's already there) + - Read existing auth files to understand what's done vs missing + - Complete the integration by filling gaps rather than starting fresh + - Preserve any working code -- only fix what's broken + - Check for a missing `/callback` route (most common gap) + +## Step 2c: Existing Auth System Detection + +Check for existing authentication before integrating: + +``` +package.json has 'passport'? → Passport.js auth +package.json has 'express-openid-connect'? → Auth0 / OIDC +package.json has 'express-session'? → Session-based auth may exist +*.js/*.ts files have 'jsonwebtoken'? → JWT-based auth +``` + +If existing auth detected: + +- Do NOT remove or disable it +- Add WorkOS AuthKit alongside the existing system +- If `express-session` is already configured, reuse it for WorkOS session storage (don't create a second session middleware) +- Create separate route paths for WorkOS auth (e.g., `/auth/workos/login` if `/login` is taken) +- Ensure existing auth routes continue to work unchanged +- Document in code comments how to migrate fully to WorkOS later + +## Step 3: Install SDK + +``` +pnpm-lock.yaml → pnpm add @workos-inc/node dotenv cookie-parser +yarn.lock → yarn add @workos-inc/node dotenv cookie-parser +bun.lockb → bun add @workos-inc/node dotenv cookie-parser +else → npm install @workos-inc/node dotenv cookie-parser +``` + +For TypeScript, also install types: `pnpm add -D @types/cookie-parser` + +Verify: `@workos-inc/node` in package.json dependencies + +## Step 4: Initialize WorkOS Client + +Adapt to detected module system (ESM vs CJS): + +ESM/TypeScript: + +```typescript +import { WorkOS } from '@workos-inc/node'; +const workos = new WorkOS(process.env.WORKOS_API_KEY, { + clientId: process.env.WORKOS_CLIENT_ID, +}); +``` + +CJS: + +```javascript +const { WorkOS } = require('@workos-inc/node'); +const workos = new WorkOS(process.env.WORKOS_API_KEY, { + clientId: process.env.WORKOS_CLIENT_ID, +}); +``` + +## Step 5: Integrate Authentication + +### If Express + +Follow the quickstart pattern: + +1. `/login` route -- call `workos.userManagement.getAuthorizationUrl({ provider: 'authkit', redirectUri: ..., clientId: ... })`, redirect +2. `/callback` route -- call `workos.userManagement.authenticateWithCode({ code, clientId })`, store session via sealed session or express-session +3. `/logout` route -- clear session cookie, redirect +4. Cookie middleware -- `app.use(cookieParser())` +5. Session-aware home route -- read session, display user info + +Session handling options (pick one): + +- Sealed sessions (recommended, from quickstart): use `sealSession: true` in authenticateWithCode, store sealed cookie, use `loadSealedSession` for verification +- express-session: install `express-session`, configure middleware before routes, store user in `req.session` + +### If Fastify + +1. Register `@fastify/cookie` plugin +2. Create `/login`, `/callback`, `/logout` routes using Fastify route syntax +3. Use `reply.redirect()` for redirects +4. Store session in signed cookie + +### If Hono + +1. Create `/login`, `/callback`, `/logout` routes using Hono router +2. Use `c.redirect()` for redirects +3. Use Hono's cookie helpers for session + +### If Koa + +1. Install `koa-router` if not present +2. Create auth routes on router +3. Use `ctx.redirect()` for redirects +4. Use `koa-session` for session management + +### If Vanilla Node.js (no framework detected) + +Install Express and follow the Express pattern above. This matches the official quickstart. + +## Step 6: Environment Setup + +Create `.env` if it doesn't exist. Do NOT overwrite existing values: + +``` +WORKOS_API_KEY=sk_... +WORKOS_CLIENT_ID=client_... +WORKOS_REDIRECT_URI=http://localhost:3000/callback +WORKOS_COOKIE_PASSWORD= +``` + +Ensure `.env` is in `.gitignore`. + +## Step 7: Verification + +TypeScript: `npx tsc --noEmit` +JavaScript: `node --check ` + +### Checklist + +- [ ] SDK installed (`@workos-inc/node` in package.json) +- [ ] WorkOS client initialized +- [ ] Login route redirects to AuthKit +- [ ] Callback route exchanges code for user +- [ ] Logout route clears session +- [ ] `.env` has required variables +- [ ] Build/syntax check passes + +## Error Recovery + +### Module not found: @workos-inc/node + +Re-run install for detected package manager. + +### Session not persisting + +If using express-session: ensure middleware registered BEFORE routes. +If using sealed sessions: ensure cookie is being set with correct options (httpOnly, secure in prod, sameSite: 'lax'). + +### Callback returns 404 + +Route path must match WORKOS_REDIRECT_URI exactly. + +### ESM/CJS mismatch + +Check `"type"` field in package.json -- `"module"` = ESM (import/export), absent = CJS (require). + +### TypeScript errors + +Install missing types: `@types/express`, `@types/cookie-parser`, `@types/express-session`. diff --git a/plugins/workos/skills/workos/references/workos-php-laravel.md b/plugins/workos/skills/workos/references/workos-php-laravel.md new file mode 100644 index 00000000..903aee75 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-php-laravel.md @@ -0,0 +1,177 @@ +# WorkOS AuthKit for Laravel + +## Step 1: Fetch SDK Documentation (BLOCKING) + +STOP. Do not proceed until complete. + +WebFetch: `https://raw.githubusercontent.com/workos/workos-php-laravel/main/README.md` + +The README is the source of truth. If this skill conflicts with README, follow README. + +## Step 2: Pre-Flight Validation + +### Project Structure + +- Confirm `artisan` file exists at project root +- Confirm `composer.json` contains `laravel/framework` dependency +- Confirm `app/` and `routes/` directories exist + +### Environment Variables + +Check `.env` for: + +- `WORKOS_API_KEY` - starts with `sk_` +- `WORKOS_CLIENT_ID` - starts with `client_` +- `WORKOS_REDIRECT_URI` - valid callback URL (e.g., `http://localhost:8000/auth/callback`) + +If `.env` exists but is missing these variables, append them. If `.env` doesn't exist, copy `.env.example` and add them. + +## Step 2b: Partial Install Recovery + +Before creating new files, check if a previous AuthKit attempt exists: + +1. Check if `workos/workos-php-laravel` is already in `composer.json` +2. Check if `config/workos.php` exists but no auth controller or routes are wired +3. If partial install detected: + - Do NOT reinstall the SDK (it's already there) + - Do NOT re-publish config if `config/workos.php` already exists + - Read existing files to understand what's done vs missing + - Complete the integration by filling gaps (controller, routes, middleware) + - Preserve any working code -- only fix what's broken + - Check for missing auth controller or routes (most common gap) + +## Step 2c: Existing Auth System Detection + +Check for existing authentication before integrating: + +``` +composer.json has 'laravel/breeze'? → Breeze auth scaffolding +composer.json has 'laravel/jetstream'? → Jetstream auth +composer.json has 'laravel/fortify'? → Fortify auth backend +app/Http/Controllers/Auth/ exists? → Auth controllers present +routes/auth.php exists? → Auth routes file +``` + +If existing auth detected: + +- Do NOT remove or disable it +- Add WorkOS AuthKit alongside the existing system +- If Laravel session is already configured, reuse it for WorkOS +- Create separate route paths for WorkOS auth (e.g., `/auth/workos/login` if `/login` is taken) +- Ensure existing auth routes continue to work unchanged +- Document in code comments how to migrate fully to WorkOS later + +## Step 3: Install SDK + +```bash +composer require workos/workos-php-laravel +``` + +Verify: Check `composer.json` contains `workos/workos-php-laravel` in require section before continuing. + +## Step 4: Publish Configuration + +```bash +php artisan vendor:publish --provider="WorkOS\Laravel\WorkOSServiceProvider" +``` + +This creates `config/workos.php`. Verify the file exists after publishing. + +If the artisan command fails, check README for the correct provider class name -- it may differ. + +## Step 5: Configure Environment + +Ensure `.env` contains: + +``` +WORKOS_API_KEY=sk_... +WORKOS_CLIENT_ID=client_... +WORKOS_REDIRECT_URI=http://localhost:8000/auth/callback +``` + +Also ensure `config/workos.php` reads these env vars correctly. Check README for exact config structure. + +## Step 6: Create Auth Controller + +Create `app/Http/Controllers/AuthController.php` with methods for: + +- `login()` -- Redirect to WorkOS AuthKit authorization URL +- `callback()` -- Handle OAuth callback, exchange code for user profile +- `logout()` -- Clear session and redirect + +Use SDK methods from README. Do NOT construct OAuth URLs manually. + +## Step 7: Add Routes + +Add to `routes/web.php`: + +```php +use App\Http\Controllers\AuthController; + +Route::get('/login', [AuthController::class, 'login'])->name('login'); +Route::get('/auth/callback', [AuthController::class, 'callback']); +Route::get('/logout', [AuthController::class, 'logout'])->name('logout'); +``` + +Ensure the callback route path matches `WORKOS_REDIRECT_URI`. + +## Step 8: Add Middleware (if applicable) + +Check README for any authentication middleware the SDK provides. If available: + +1. Register middleware in `app/Http/Kernel.php` or `bootstrap/app.php` (Laravel 11+) +2. Apply to routes that require authentication + +For Laravel 11+, middleware is registered in `bootstrap/app.php` instead of `Kernel.php`. + +## Step 9: Add UI Integration + +Update the home page or dashboard view to show: + +- Sign in link when user is not authenticated +- User info and sign out link when authenticated + +Use Blade directives or SDK helpers from README. + +## Verification Checklist (ALL MUST PASS) + +```bash +# 1. Config file exists +ls config/workos.php + +# 2. Controller exists +ls app/Http/Controllers/AuthController.php + +# 3. Routes registered +php artisan route:list | grep -E "login|callback|logout" + +# 4. SDK installed +composer show workos/workos-php-laravel + +# 5. Lint check +php -l app/Http/Controllers/AuthController.php +``` + +## Error Recovery + +### "Class WorkOS\Laravel\WorkOSServiceProvider not found" + +- Verify `composer require` completed successfully +- Run `composer dump-autoload` +- Check `vendor/workos/` directory exists + +### "Route not defined" + +- Verify routes are in `routes/web.php` +- Run `php artisan route:clear && php artisan route:cache` + +### Config not loading + +- Verify `config/workos.php` exists +- Run `php artisan config:clear` +- Check `.env` variables match config keys + +### Middleware issues (Laravel 11+) + +- Laravel 11 removed `Kernel.php` -- register middleware in `bootstrap/app.php` +- Check README for Laravel version-specific instructions diff --git a/plugins/workos/skills/workos/references/workos-php.md b/plugins/workos/skills/workos/references/workos-php.md new file mode 100644 index 00000000..ecaddef6 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-php.md @@ -0,0 +1,155 @@ +# WorkOS AuthKit for PHP + +## Step 1: Fetch SDK Documentation (BLOCKING) + +STOP. Do not proceed until complete. + +WebFetch: `https://raw.githubusercontent.com/workos/workos-php/main/README.md` + +The README is the source of truth. If this skill conflicts with README, follow README. + +## Step 2: Pre-Flight Validation + +### Project Structure + +- Confirm `composer.json` exists at project root +- If `composer.json` doesn't exist, create a minimal one with `composer init --no-interaction` + +### Environment Variables + +Check for `.env` file with: + +- `WORKOS_API_KEY` - starts with `sk_` +- `WORKOS_CLIENT_ID` - starts with `client_` +- `WORKOS_REDIRECT_URI` - valid callback URL (e.g., `http://localhost:8000/callback.php`) + +If `.env` doesn't exist, create it with the required variables. + +## Step 2b: Partial Install Recovery + +Before creating new files, check if a previous AuthKit attempt exists: + +1. Check if `workos/workos-php` is already in `composer.json` +2. Check for incomplete auth files -- `login.php` or `callback.php` that import WorkOS but are non-functional (TODO comments, 501 responses, empty handlers) +3. If partial install detected: + - Do NOT reinstall the SDK (it's already there) + - Read existing auth files to understand what's done vs missing + - Complete the integration by filling gaps rather than starting fresh + - Preserve any working code -- only fix what's broken + - Check for a missing `callback.php` (most common gap) + +## Step 2c: Existing Auth System Detection + +Check for existing authentication before integrating: + +``` +*.php files have 'session_start()' + '$_SESSION'? → Native PHP session auth +*.php files have 'password_verify'? → Password-based auth +*.php files have form POST to '/login'? → Form-based auth +composer.json has auth libraries? → Third-party auth +``` + +If existing auth detected: + +- Do NOT remove or disable it +- Add WorkOS AuthKit alongside the existing system +- If `session_start()` is already used, reuse the session for WorkOS (don't create a second session mechanism) +- Create separate file paths for WorkOS auth (e.g., `workos-login.php` if `login.php` is taken) +- Ensure existing auth routes continue to work unchanged +- Document in code comments how to migrate fully to WorkOS later + +## Step 3: Install SDK + +```bash +composer require workos/workos-php +``` + +Verify: Check `composer.json` contains `workos/workos-php` in require section. + +Also install a dotenv library if not present: + +```bash +composer require vlucas/phpdotenv +``` + +## Step 4: Create Bootstrap File + +Create a bootstrap or config file (e.g., `config.php` or `bootstrap.php`) that: + +1. Requires Composer autoloader: `require_once __DIR__ . '/vendor/autoload.php';` +2. Loads `.env` using phpdotenv +3. Initializes the WorkOS SDK client with API key + +Use SDK initialization from README. Do NOT hardcode credentials. + +## Step 5: Create Auth Endpoint Files + +### `login.php` + +- Initialize WorkOS client (include bootstrap) +- Generate authorization URL using SDK +- Redirect user to WorkOS AuthKit + +### `callback.php` + +- Initialize WorkOS client (include bootstrap) +- Exchange authorization code from `$_GET['code']` for user profile using SDK +- Start session, store user data +- Redirect to home/dashboard + +### `logout.php` + +- Destroy session +- Redirect to home page + +Use SDK methods from README for all WorkOS API calls. Do NOT construct OAuth URLs manually. + +## Step 6: Create Home Page + +Create or update `index.php` to show: + +- Sign in link (`login.php`) when no session +- User info and sign out link (`logout.php`) when session exists + +## Verification Checklist (ALL MUST PASS) + +```bash +# 1. SDK installed +composer show workos/workos-php + +# 2. Auth files exist +ls login.php callback.php logout.php + +# 3. No syntax errors +php -l login.php +php -l callback.php +php -l logout.php +php -l index.php + +# 4. Autoloader exists +ls vendor/autoload.php +``` + +## Error Recovery + +### "Class WorkOS\WorkOS not found" + +- Verify `composer require` completed successfully +- Check `vendor/autoload.php` is required in bootstrap +- Run `composer dump-autoload` + +### Session issues + +- Ensure `session_start()` is called before any session access +- Check PHP session configuration (`session.save_path`) + +### Redirect URI mismatch + +- Compare callback file path to `WORKOS_REDIRECT_URI` in `.env` +- URLs must match exactly (including trailing slash) + +### Environment variables not loading + +- Verify `.env` file exists in project root +- Verify phpdotenv is installed and loaded in bootstrap +- Check file permissions on `.env` diff --git a/plugins/workos/skills/workos/references/workos-pipes.md b/plugins/workos/skills/workos/references/workos-pipes.md new file mode 100644 index 00000000..81536405 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-pipes.md @@ -0,0 +1,36 @@ +# WorkOS Pipes + +## Docs + +- https://workos.com/docs/pipes +- https://workos.com/docs/pipes/providers +- https://workos.com/docs/reference/pipes +- https://workos.com/docs/reference/pipes/provider +- https://workos.com/docs/reference/pipes/connected-account +- https://workos.com/docs/reference/pipes/access-token +- https://workos.com/docs/widgets/pipes + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- Pipes manages the full OAuth lifecycle (authorization, token refresh, credential storage) -- do NOT implement your own token refresh logic. +- Use `workos.pipes.getAccessToken()` to get tokens -- WorkOS auto-refreshes expired tokens. Never cache access tokens client-side. +- Sandbox environments use "shared credentials" (WorkOS-managed OAuth apps). Production requires custom credentials configured per provider in the Dashboard. +- Response is a discriminated union. Node SDK (camelCase): `{ active: true, accessToken }` on success or `{ active: false, error: "needs_reauthorization" | "not_installed" }` on failure. Raw REST uses snake_case (`access_token`). Branch on `active` first -- `accessToken`/`error` only exist on their respective branches. +- Provider slugs are lowercase (e.g., `github`, `slack`, `salesforce`). Claude tends to capitalize or use display names. +- Connected account deletion removes stored tokens -- the user must re-authorize. This is not reversible. +- The Pipes Widget provides a pre-built UI for account connection -- use it instead of building custom OAuth flows. Load via `workos-widgets` skill. +- `getAccessToken()` requires `provider` and `userId` params. `organizationId` is optional but needed for org-scoped connections. +- The authorize endpoint (`/data-integrations/{slug}/authorize`) returns a URL -- redirect the user to it, do NOT fetch it server-side. + +## Endpoints + +| Endpoint | Description | +| --------------------------- | ---------------------------- | +| `/pipes` | Pipes overview | +| `/provider` | Provider details | +| `/connected-account` | Connected account management | +| `/connected-account/get` | Get a connected account | +| `/connected-account/delete` | Delete a connected account | +| `/access-token` | Get OAuth access token | +| `/authorize` | Generate authorization URL | diff --git a/plugins/workos/skills/workos/references/workos-python.md b/plugins/workos/skills/workos/references/workos-python.md new file mode 100644 index 00000000..c09a3269 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-python.md @@ -0,0 +1,188 @@ +# WorkOS AuthKit for Python + +## Step 1: Fetch SDK Documentation (BLOCKING) + +STOP. Do not proceed until complete. + +WebFetch: `https://raw.githubusercontent.com/workos/workos-python/main/README.md` + +Also fetch the AuthKit quickstart for reference: +WebFetch: `https://workos.com/docs/authkit/vanilla/python` + +The README is the source of truth for SDK API usage. If this skill conflicts with README, follow README. + +## Step 2: Detect Framework + +Examine the project to determine which Python web framework is in use: + +``` +manage.py exists? → Django + settings.py has django imports? → Confirmed Django + +Gemfile/requirements has 'fastapi'? → FastAPI + main.py has FastAPI() instance? → Confirmed FastAPI + +requirements has 'flask'? → Flask + server.py/app.py has Flask() instance? → Confirmed Flask + +None of the above? → Vanilla Python (use Flask quickstart pattern) +``` + +Adapt all subsequent steps to the detected framework. Do not force one framework onto another. + +## Step 2b: Partial Install Recovery + +Before creating new files, check if a previous AuthKit attempt exists: + +1. Check if `workos` is already in `requirements.txt` or `pyproject.toml` +2. Check for incomplete auth files -- files that import `workos` but have non-functional routes (TODO comments, commented-out code, empty handlers) +3. If partial install detected: + - Do NOT reinstall the SDK (it's already there) + - Read existing auth files to understand what's done vs missing + - Complete the integration by filling gaps rather than starting fresh + - Preserve any working code -- only fix what's broken + - Check for a missing `/callback` route (most common gap) + +## Step 2c: Existing Auth System Detection + +Check for existing authentication before integrating: + +``` +requirements.txt has 'flask-login'? → Flask-Login auth +requirements.txt has 'authlib'? → OAuth/OIDC auth (e.g., Auth0) +requirements.txt has 'django-allauth'? → Django allauth +manage.py exists + 'django.contrib.auth'? → Django built-in auth +*.py files have 'flask_login'? → Flask-Login in use +``` + +If existing auth detected: + +- Do NOT remove or disable it +- Add WorkOS AuthKit alongside the existing system +- If `flask-login` is present, use Flask-Login's session infrastructure (`login_user()`) for WorkOS auth too, rather than raw session management +- Create separate route paths for WorkOS auth (e.g., `/auth/workos/login` if `/login` is taken) +- Ensure existing auth routes continue to work unchanged +- Document in code comments how to migrate fully to WorkOS later + +## Step 3: Pre-Flight Validation + +### Package Manager Detection + +``` +uv.lock exists? → uv add +pyproject.toml has [tool.poetry]? → poetry add +Pipfile exists? → pipenv install +requirements.txt exists? → pip install (+ append to requirements.txt) +else → pip install +``` + +### Environment Variables + +Check `.env` for: + +- `WORKOS_API_KEY` - starts with `sk_` +- `WORKOS_CLIENT_ID` - starts with `client_` + +## Step 4: Install SDK + +Install using the detected package manager: + +```bash +# uv +uv add workos python-dotenv + +# poetry +poetry add workos python-dotenv + +# pip +pip install workos python-dotenv +``` + +If using `requirements.txt`, also append `workos` and `python-dotenv` to it. + +Verify: `python -c "import workos; print('OK')"` + +## Step 5: Integrate Authentication + +### If Django + +1. Configure settings.py -- add `import os` + `from dotenv import load_dotenv` + `load_dotenv()` at top. Add `WORKOS_API_KEY` and `WORKOS_CLIENT_ID` from `os.environ.get()`. +2. Create auth views -- create `auth_views.py` (or add to existing views): + - `login_view`: call SDK's `get_authorization_url()` with `provider='authkit'`, redirect + - `callback_view`: call `authenticate_with_code()` with the code param, store user in `request.session` + - `logout_view`: flush session, redirect +3. Add URL patterns -- add `auth/login/`, `auth/callback/`, `auth/logout/` to `urls.py` +4. Update templates -- add login/logout links using `{% url %}` tags + +### If Flask + +Follow the quickstart pattern exactly: + +1. Initialize WorkOS client in `server.py` / `app.py`: + ```python + from workos import WorkOSClient + workos = WorkOSClient(api_key=os.getenv("WORKOS_API_KEY"), client_id=os.getenv("WORKOS_CLIENT_ID")) + ``` +2. Create `/login` route -- call `workos.user_management.get_authorization_url(provider="authkit", redirect_uri="...")`, redirect +3. Create `/callback` route -- call `workos.user_management.authenticate_with_code(code=code)`, set session cookie +4. Create `/logout` route -- clear session, redirect +5. Update home route -- show user info if session exists + +### If FastAPI + +1. Initialize WorkOS client in main app file +2. Create `/login` endpoint -- generate auth URL, return `RedirectResponse` +3. Create `/callback` endpoint -- exchange code, store in session/cookie +4. Create `/logout` endpoint -- clear session +5. Use `Depends()` for auth middleware on protected routes + +### If Vanilla Python (no framework detected) + +Install Flask and follow the Flask pattern above. This matches the official quickstart. + +## Step 6: Environment Setup + +Create/update `.env` with WorkOS credentials. Do NOT overwrite existing values. + +``` +WORKOS_API_KEY=sk_... +WORKOS_CLIENT_ID=client_... +``` + +## Step 7: Verification Checklist + +```bash +# 1. SDK importable +python -c "import workos; print('OK')" + +# 2. Credentials configured +python -c " +from dotenv import load_dotenv; import os; load_dotenv() +assert os.environ.get('WORKOS_API_KEY','').startswith('sk_'), 'Missing WORKOS_API_KEY' +assert os.environ.get('WORKOS_CLIENT_ID','').startswith('client_'), 'Missing WORKOS_CLIENT_ID' +print('Credentials OK') +" + +# 3. Framework-specific check +# Django: python manage.py check +# Flask: python -m py_compile server.py +# FastAPI: python -m py_compile main.py +``` + +## Error Recovery + +### "ModuleNotFoundError: No module named 'workos'" + +Re-run the install command for the detected package manager. + +### Django: "CSRF verification failed" + +Auth callback receives GET requests from WorkOS. Ensure callback view uses GET, not POST. Or add `@csrf_exempt`. + +### Flask: Session not persisting + +Ensure `app.secret_key` is set (required for Flask sessions). + +### Virtual environment not active + +Check for `.venv/`, `venv/`, or poetry-managed environments. Activate before running install. diff --git a/plugins/workos/skills/workos/references/workos-radar.md b/plugins/workos/skills/workos/references/workos-radar.md new file mode 100644 index 00000000..84292fba --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-radar.md @@ -0,0 +1,37 @@ +# WorkOS Radar + +## Docs + +- https://workos.com/docs/radar +- https://workos.com/docs/radar/overview +- https://workos.com/docs/radar/standalone +- https://workos.com/docs/reference/radar +- https://workos.com/docs/reference/radar/attempts +- https://workos.com/docs/reference/radar/lists + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- Radar is built into AuthKit natively -- if using AuthKit, fraud detection works automatically. The standalone API is only needed for custom auth flows. +- There is no `workos.radar.*` namespace in the Node SDK. The Radar standalone API has no SDK wrapper methods at all -- for attempts, lists, or anything else. Use `workos.post('/radar/attempts', ...)`, `workos.put('/radar/attempts/:id', ...)`, `workos.post('/radar/lists/{type}/{action}', ...)` directly. Claude hallucinates `workos.radar.assessAttempt`, `workos.radar.updateAttempt`, `workos.userManagement.updateAuthenticationAttempt`, etc. -- none exist. +- The standalone API is in preview -- access requires contacting WorkOS support. +- `POST /radar/attempts` returns a `verdict`: `"allow"`, `"block"`, or `"challenge"`. Your app MUST act on the verdict -- Radar does not block requests itself. +- All attempt fields are required: `ip_address`, `user_agent`, `email`, `auth_method`, `action`. Missing fields cause a 422. +- `auth_method` must be one of: `Password`, `Passkey`, `Authenticator`, `SMS_OTP`, `Email_OTP`, `Social`, `SSO`, `Other`. Claude tends to use lowercase or invented values. +- `action` accepts: `login`, `signup` (and variants like `sign-in`, `sign_up`). Use the simplest form. +- After a successful authentication, call `PUT /radar/attempts/:id` with `attempt_status: "success"` to improve Radar's model (enables impossible travel detection). +- Block/allow lists use path-based routing: `POST /radar/lists/{type}/{action}` where type is `ip_address`, `domain`, `email`, `device`, `user_agent`, `device_fingerprint`, or `country`, and action is `block` or `allow`. +- `device_fingerprint` and `bot_score` are optional enrichment fields -- pass them if your client-side SDK collects them. +- There are NO SDK wrapper methods for block/allow list management. Use direct HTTP calls (`POST /radar/lists/{type}/{action}`). Claude hallucinates `workos.radar.blockIpAddress()` or `workos.userManagement.createBlocklistEntry()` -- neither exists. + +## Endpoints + +| Endpoint | Description | +| ------------------------------- | ---------------------------------- | +| `/radar` | Radar overview | +| `/attempts` | Attempt management | +| `/attempts/create` | Create an attempt (get verdict) | +| `/attempts/update` | Update attempt status | +| `/lists` | List management | +| `/lists/{type}/{action}/add` | Add entry to block/allow list | +| `/lists/{type}/{action}/remove` | Remove entry from block/allow list | diff --git a/plugins/workos/skills/workos/references/workos-rbac.md b/plugins/workos/skills/workos/references/workos-rbac.md new file mode 100644 index 00000000..c050bd65 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-rbac.md @@ -0,0 +1,100 @@ +# WorkOS Role-Based Access Control + +## Docs + +- https://workos.com/docs/rbac/quick-start +- https://workos.com/docs/rbac/organization-roles +- https://workos.com/docs/rbac/integration +- https://workos.com/docs/rbac/idp-role-assignment +- https://workos.com/docs/directory-sync/identity-provider-role-assignment + If this file conflicts with fetched docs, follow the docs. + +## IdP group → role mapping (the common customer ask) + +When a user asks how to map Entra / Azure AD / Okta / Google Workspace groups to WorkOS roles, this is the canonical flow. It is not a CLI operation. Do not fabricate `workos role-mappings ...` or similar commands. + +Which source to use: + +- If the org has a Directory Sync connection (SCIM or Google Workspace), use Directory Sync group role assignment. Roles propagate in real time. Docs: https://workos.com/docs/directory-sync/identity-provider-role-assignment +- If the org only has SSO (no Directory Sync), use SSO group role assignment. Roles only update when the user re-authenticates. Docs: https://workos.com/docs/rbac/idp-role-assignment +- When both exist, prefer Directory Sync. Per docs: "SCIM is generally the preferred option due to its real-time synchronization capabilities." + +Order of operations: + +1. Configure roles first in the WorkOS Dashboard. Per docs: "Once roles are configured for your application, enable directory group role assignment in Admin Portal to allow IT contacts to assign roles to groups during directory setup." +2. Enable directory group role assignment on the Authorization page of the WorkOS Dashboard (toggle controlling whether the Admin Portal surfaces the role-assignment step). +3. The IT contact / org admin maps groups → roles during directory setup via Admin Portal, or via the directory page on the WorkOS Dashboard (per docs: "Navigate to the directory page on the WorkOS dashboard"). +4. For Directory Sync: new users added to a mapped group get the mapped role in real time ("Roles are granted to directory users in real-time, when we receive updates to their group memberships"). +5. For SSO: the mapped role is applied when the user authenticates ("Roles are granted to SSO profiles when the user authenticates"). + +Precedence (read this before telling a user to call `updateOrganizationMembership`): + +Per docs: "IdP role assignment will always take precedence over roles assigned via API or the WorkOS Dashboard." If an IdP mapping exists for the user's group, any API-assigned role is silently overwritten on the next sync (Directory Sync) or next login (SSO). + +What NOT to do: + +- Do not invent specific dashboard menu paths (e.g. "Dashboard → Organizations → [name] → Roles → Map Groups"). The docs do not commit to a specific click-path. Link the user to the docs URLs above and let them navigate. +- Do not claim this is configurable via the WorkOS CLI. It is not. If the user asks for a CLI command, explicitly say it is not supported in the CLI and link to the docs above. + +## API role assignment recipe + +Use this when the user asks how to assign an already-configured role to a user in an organization. + +1. Confirm from the prompt or user context that the role slug already exists. If they are unsure, tell them to verify the role in the WorkOS Dashboard first; do not create the role as part of assignment. +2. List organization memberships filtered by `user_id` and `organization_id` to find the organization membership ID. +3. Update the organization membership with the role slug. +4. If the role reverts later, check for IdP group role mapping; IdP mapping overrides API/Dashboard role assignments. + +Python shape: + +```python +memberships = workos_client.user_management.list_organization_memberships( + user_id=user_id, + organization_id=organization_id, +) +membership_id = memberships.data[0].id + +workos_client.user_management.update_organization_membership( + organization_membership_id=membership_id, + role_slug="billing-admin", +) +``` + +## Gotchas + +- Always check permissions (role.permissions.includes('action')), NOT role slugs (role.slug === 'admin') -- slug checks break in multi-org with custom roles. Claude defaults to slug checks. +- Role assignment requires the MEMBERSHIP ID, not the user ID -- fetch via listOrganizationMemberships() first, then call updateOrganizationMembership(membershipId, { roleSlug }) +- IdP group mapping OVERRIDES API/Dashboard role assignments on every auth -- updateOrganizationMembership() changes silently revert on next login if IdP mapping exists +- IdP role mapping only works with environment-level roles, NOT org-level roles +- First org-level role creation isolates that org permanently -- it stops inheriting environment-level role changes. This is irreversible. +- Org-level role slugs are auto-prefixed with "org:" -- use the full slug "org:custom_admin", not just "custom_admin" +- Stale session after role change -- role assigned after login won't take effect until user re-authenticates. Force re-auth or refresh the session. +- Permission slug typos fail silently -- "video.create" vs "videos.create" won't error, just denies access + +## Endpoints + +| Endpoint | Description | +| -------------------------------------- | ------------------------------- | +| `/roles` | Roles overview | +| `/organization-role` | Organization role management | +| `/organization-role/add-permission` | Add permission to org role | +| `/organization-role/create` | Create org role | +| `/organization-role/delete` | Delete org role | +| `/organization-role/get` | Get org role | +| `/organization-role/list` | List org roles | +| `/organization-role/remove-permission` | Remove permission from org role | +| `/organization-role/set-permissions` | Set permissions on org role | +| `/organization-role/update` | Update org role | +| `/permission` | Permission management | +| `/permission/create` | Create permission | +| `/permission/delete` | Delete permission | +| `/permission/get` | Get permission | +| `/permission/list` | List permissions | +| `/permission/update` | Update permission | +| `/role` | Environment role management | +| `/role/add-permission` | Add permission to role | +| `/role/create` | Create role | +| `/role/get` | Get role | +| `/role/list` | List roles | +| `/role/set-permissions` | Set permissions on role | +| `/role/update` | Update role | diff --git a/plugins/workos/skills/workos/references/workos-ruby.md b/plugins/workos/skills/workos/references/workos-ruby.md new file mode 100644 index 00000000..7e1007de --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-ruby.md @@ -0,0 +1,193 @@ +# WorkOS AuthKit for Ruby + +## Step 1: Fetch SDK Documentation (BLOCKING) + +STOP -- Do not proceed until this fetch is complete. + +WebFetch: `https://raw.githubusercontent.com/workos/workos-ruby/main/README.md` + +Also fetch the AuthKit quickstart for reference: +WebFetch: `https://workos.com/docs/authkit/vanilla/ruby` + +The README is the source of truth for gem API usage. If this skill conflicts with the README, follow the README. + +## Step 2: Detect Framework + +Examine the project to determine which Ruby web framework is in use: + +``` +config/routes.rb exists? → Rails + Gemfile has 'rails' gem? → Confirmed Rails + +Gemfile has 'sinatra' gem? → Sinatra + server.rb/app.rb has Sinatra routes? → Confirmed Sinatra + +None of the above? → Vanilla Ruby (use Sinatra quickstart pattern) +``` + +Adapt all subsequent steps to the detected framework. Do not force Rails on a Sinatra project or vice versa. + +## Step 2b: Partial Install Recovery + +Before creating new files, check if a previous AuthKit attempt exists: + +1. Check if `workos` is already in `Gemfile` +2. Check for incomplete auth files -- files that `require "workos"` but have non-functional routes (TODO comments, 501 responses, empty handlers) +3. If partial install detected: + - Do NOT reinstall the gem (it's already there) + - Read existing auth files to understand what's done vs missing + - Complete the integration by filling gaps rather than starting fresh + - Preserve any working code -- only fix what's broken + - Run `bundle install` (not `bundle update`) to avoid unexpected gem upgrades + +## Step 2c: Existing Auth System Detection + +Check for existing authentication before integrating: + +``` +Gemfile has 'devise'? → Devise auth (uses Warden) +Gemfile has 'warden'? → Warden auth +Gemfile has 'omniauth'? → OmniAuth (OAuth/OIDC) +*.rb files have 'Warden'? → Warden middleware in use +config/initializers has devise.rb? → Devise configured +``` + +If existing auth detected: + +- Do NOT remove or disable it +- Add WorkOS AuthKit alongside the existing system +- If Devise is present, Devise uses Warden under the hood -- integrate WorkOS at the Warden strategy level if possible +- Create separate route paths for WorkOS auth (e.g., `/auth/workos/login` if `/login` is taken) +- Ensure Rack middleware ordering is correct (WorkOS session middleware must not conflict) +- Ensure existing auth routes continue to work unchanged +- Document in code comments how to migrate fully to WorkOS later + +## Step 3: Install WorkOS Gem + +```bash +bundle add workos +``` + +If `dotenv` is not in the Gemfile: + +```bash +# Rails +bundle add dotenv-rails --group development,test + +# Sinatra / other +bundle add dotenv +``` + +Verify: `bundle show workos` + +## Step 4: Integrate Authentication + +### If Rails + +1. Create initializer -- `config/initializers/workos.rb`: + + ```ruby + WorkOS.configure do |config| + config.api_key = ENV.fetch("WORKOS_API_KEY") + config.client_id = ENV.fetch("WORKOS_CLIENT_ID") + end + ``` + +2. Create AuthController -- `app/controllers/auth_controller.rb`: + - `login` action: call `WorkOS::UserManagement.get_authorization_url(provider: "authkit", redirect_uri: ...)`, redirect + - `callback` action: call `WorkOS::UserManagement.authenticate_with_code(code: params[:code])`, store user in session + - `logout` action: clear session, redirect + +3. Add routes to `config/routes.rb`: + + ```ruby + get "/auth/login", to: "auth#login" + get "/auth/callback", to: "auth#callback" + get "/auth/logout", to: "auth#logout" + ``` + +4. Add current_user helper to `ApplicationController` (optional): + + ```ruby + helper_method :current_user + def current_user + @current_user ||= session[:user] && JSON.parse(session[:user]) + end + ``` + +5. Verify: `bundle exec rails routes | grep auth` + +### If Sinatra + +Follow the quickstart pattern exactly: + +1. Configure WorkOS in `server.rb`: + + ```ruby + require "dotenv/load" + require "workos" + require "sinatra" + + WorkOS.configure do |config| + config.key = ENV["WORKOS_API_KEY"] + end + ``` + +2. Create `/login` route -- call `WorkOS::UserManagement.authorization_url(provider: "authkit", client_id: ..., redirect_uri: ...)`, redirect + +3. Create `/callback` route -- call `WorkOS::UserManagement.authenticate_with_code(client_id: ..., code: ...)`, store in session cookie + +4. Create `/logout` route -- clear session cookie, redirect + +5. Update home route -- read session, show user info if present + +6. Verify: `ruby -c server.rb` + +### If Vanilla Ruby (no framework detected) + +Install Sinatra and follow the Sinatra pattern above. This matches the official quickstart. + +## Step 5: Environment Setup + +Create/update `.env` with WorkOS credentials. Do NOT overwrite existing values. + +``` +WORKOS_API_KEY=sk_... +WORKOS_CLIENT_ID=client_... +``` + +## Step 6: Verification + +### Rails + +```bash +bundle show workos +bundle exec rails routes | grep auth +grep WORKOS .env +``` + +### Sinatra + +```bash +bundle show workos +ruby -c server.rb +grep WORKOS .env +``` + +## Error Recovery + +### "uninitialized constant WorkOS" + +Gem not loaded. Verify `bundle show workos` succeeds. For Rails, ensure initializer exists. For Sinatra, ensure `require "workos"` is at top of server file. + +### "NoMethodError" on WorkOS methods + +SDK API may differ from this skill. Re-read the README (Step 1) and use exact method names. + +### Routes not working (Rails) + +Run `bundle exec rails routes | grep auth`. Verify routes are inside `Rails.application.routes.draw` block. + +### Session not persisting (Sinatra) + +Enable sessions: `enable :sessions` in server.rb, or use `rack-session` gem. diff --git a/plugins/workos/skills/workos/references/workos-sso.md b/plugins/workos/skills/workos/references/workos-sso.md new file mode 100644 index 00000000..4a3c0ba2 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-sso.md @@ -0,0 +1,87 @@ +# WorkOS Single Sign-On + +## Docs + +- https://workos.com/docs/sso/guide +- https://workos.com/docs/sso/login-flows +- https://workos.com/docs/reference/sso/get-authorization-url +- https://workos.com/docs/sso/redirect-uris +- https://workos.com/docs/sso/test-sso +- https://workos.com/docs/sso/launch-checklist +- https://workos.com/docs/rbac/idp-role-assignment + If this file conflicts with fetched docs, follow the docs. + +## Mapping SSO groups to WorkOS roles + +Full recipe lives in `workos-rbac.md` under "IdP group → role mapping". SSO-specific caveats: + +- Re-auth required: SSO group role assignment does not propagate in real time. Per docs: "Roles are granted to SSO profiles when the user authenticates." A role change in the IdP only takes effect the next time the user re-authenticates. +- When Directory Sync is also available, prefer Directory Sync. Per docs: "SCIM is generally the preferred option due to its real-time synchronization capabilities." +- Same precedence rule applies: IdP mapping overrides API/Dashboard role assignment ("IdP role assignment will always take precedence over roles assigned via API or the WorkOS Dashboard"). +- Not configurable via the WorkOS CLI. If asked, say so explicitly and link to https://workos.com/docs/rbac/idp-role-assignment. + +## Canonical SSO flows + +Use only the relevant flow when the user asks for implementation help. Do not include email-domain routing or IdP-initiated callback handling unless the user asks for those topics. Keep the exact operation order clear; most bugs come from doing state validation or connection selection in the wrong place. + +Standalone SSO SDK methods: + +- Node: use `workos.sso.getAuthorizationUrl(...)` and `workos.sso.getProfileAndToken(...)`. +- Ruby: use `WorkOS::SSO.authorization_url(...)` and `WorkOS::SSO.profile_and_token(...)`. +- Do not use AuthKit/User Management methods such as `workos.userManagement.getAuthorizationUrl(...)` or `authenticateWithCode(...)` for standalone SSO prompts. + +### SP-initiated SSO + +1. Generate a cryptographically random `state` value and store it server-side in the user's session. +2. Generate the authorization URL with exactly one connection selector: `organization`, `connection`, or `provider`. +3. Redirect the user to the IdP using that authorization URL. +4. In the callback, check the `error` query parameter before exchanging the code. +5. Verify the returned `state` against the session value. +6. Exchange `code` for the profile and token with `getProfileAndToken` (Node) or `profile_and_token` (Ruby). +7. Create the app session from the returned profile, then clear the one-time state value. + +### IdP-initiated SSO callback + +1. Check if the `state` parameter is present on the callback. +2. If `state` has a non-empty value, verify it against the session value. +3. If `state` is `""` (empty string), treat it as IdP-initiated and skip CSRF state verification for that request. +4. Do not skip state verification for every callback; only the empty-string IdP-initiated case gets this exception. +5. Exchange `code` for the profile and token, then create the app session. + +### Email-domain routing + +1. Collect the user's email address before starting SSO. +2. Extract the domain from the email address. +3. Look up the matching WorkOS organization ID from your app database, tenant config, or the Organizations API. +4. Pass `organization` / `organization_id` to the authorization URL call. +5. Use exactly one connection selector per request. Do not combine `organization` with `connection` or `provider`. + +## Gotchas + +- Use exactly ONE connection selector (connection, organization, or provider) in getAuthorizationUrl -- never combine them, causes error +- domain_hint and login_hint are UX params, NOT connection selectors -- they pre-fill fields but don't route the request +- IdP-initiated flow sends state="" (empty string, not missing) -- skip CSRF verification for empty string, reject for null/missing +- Auth codes expire in 10 min and are single-use -- exchange immediately in callback, never store or retry +- signin_consent_denied means user clicked Cancel at IdP -- check req.query.error BEFORE attempting code exchange +- Email domain does NOT auto-resolve to organization -- YOUR app must map email domain → org_id via your DB or the Organizations API +- Redirect URI must match EXACTLY including trailing slash -- mismatch causes invalid_grant +- Use getProfileAndToken (not getProfile) to exchange code -- returns both profile and access token + +## Endpoints + +| Endpoint | Description | +| ------------------------------------- | --------------------------------- | +| `/sso` | SSO overview | +| `/connection` | SSO connection management | +| `/connection/delete` | Delete a connection | +| `/connection/get` | Get a connection | +| `/connection/list` | List connections | +| `/get-authorization-url` | Generate authorization URL | +| `/get-authorization-url/error-codes` | Authorization error codes | +| `/get-authorization-url/redirect-uri` | Redirect URI configuration | +| `/logout` | SSO logout | +| `/logout/authorize` | Authorize logout | +| `/logout/redirect` | Logout redirect | +| `/profile` | User profile | +| `/profile/get-profile-and-token` | Exchange code for profile + token | +| `/profile/get-user-profile` | Get user profile by ID | diff --git a/plugins/workos/skills/workos/references/workos-terms.md b/plugins/workos/skills/workos/references/workos-terms.md new file mode 100644 index 00000000..f6ac5a64 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-terms.md @@ -0,0 +1,37 @@ +# WorkOS Terminology → Canonical Docs URLs + +If this file conflicts with fetched docs, follow the docs. URLs here are canonical at time of writing; if a user reports a broken link, WebFetch to confirm and update the row. + +## How to Use + +User asked about a WorkOS term, dashboard field, environment variable, or configuration concept? Look it up here first. The table gives you: + +- Term -- exact name as it appears in the WorkOS Dashboard, SDK, or docs +- What it is -- one-line definition (enough to answer simple "what is X" questions without a fetch) +- Canonical URL -- WebFetch this if the user wants the full reference +- See also -- deeper reference file to Read when the task goes beyond terminology + +## Terms + +| Term | What it is | Canonical URL | See also | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | ------------------------ | +| Redirect URI | URL WorkOS redirects to after successful authentication; configured in Dashboard → Redirects. The `redirect_uri` request parameter must match one of the configured values EXACTLY (including trailing slash). | https://workos.com/docs/reference/authkit/authentication/get-authorization-url/redirect-uri | `workos-authkit-base.md` | +| Sign-in endpoint | URL on YOUR app that AuthKit redirects to when a sign-in request did not originate from your app (e.g., bookmark of hosted sign-in, password-reset email). Configured in Dashboard → Redirects. | https://workos.com/docs/authkit/vanilla/nodejs#configure-sign-in-endpoint | `workos-authkit-base.md` | +| `initiate_login_uri` | OIDC client metadata parameter an IdP reads to begin IdP-initiated login at the RP. For WorkOS configuration purposes this maps to the Sign-in endpoint in Dashboard → Redirects; no distinct WorkOS docs page exists under this name. | https://workos.com/docs/authkit/vanilla/nodejs#configure-sign-in-endpoint | `workos-authkit-base.md` | +| Sign-out redirect | URL users are redirected to after logging out of AuthKit. Configured in Dashboard → Redirects and/or passed as the `return_to` query parameter on the logout URL. | https://workos.com/docs/reference/authkit/logout/get-logout-url | `workos-authkit-base.md` | +| Organization ID | Identifier for a group of users (typically a tenant/customer org). Preferred parameter when initiating SAML/OIDC flows -- pass this instead of Connection ID so the org can pick its active connection. | https://workos.com/docs/sso/overview | `workos-sso.md` | +| Connection ID | Identifier for a specific SSO connection (auth method) belonging to an Organization. Use when you need to authenticate via a particular connection rather than letting the Org decide. | https://workos.com/docs/sso/overview | `workos-sso.md` | +| Admin Portal `intent` | Query parameter on `generateLink` that selects which Admin Portal flow to open. Valid values: `sso`, `dsync`, `audit_logs`, `log_streams`, `domain_verification`, `certificate_renewal`, `bring_your_own_key`. | https://workos.com/docs/reference/admin-portal/portal-link/generate | `workos-admin-portal.md` | +| JWKS endpoint | Public key set endpoint used to verify signatures on AuthKit-issued session access tokens. | https://workos.com/docs/reference/authkit/session-tokens/jwks | `workos-api-authkit.md` | +| Sealed session | AuthKit session data encrypted and stored in a cookie. "Sealing" = encrypting with the cookie password at sign-in; "unsealing" = decrypting via `loadSealedSession()` / `authenticateWithSessionCookie()` on each request. | https://workos.com/docs/reference/authkit/session-helpers/load-sealed-session | `workos-node.md` | +| `WORKOS_COOKIE_PASSWORD` | 32+ character password used to seal/unseal the AuthKit session cookie. Must be identical across all instances of your app. Generate with `openssl rand -base64 32`. | https://workos.com/docs/authkit/vanilla/nodejs | `workos-authkit-base.md` | + +## Still not here? + +1. Check the "See also" column for the closest feature reference and Read it -- the term may be covered in context there. +2. If still unclear, WebFetch https://workos.com/docs/llms.txt and search for the term. +3. If you find a canonical URL for a term that wasn't in this table, answer the user, then suggest they open a PR adding a row to this file. (This file is human-maintained; you can't reliably persist edits from a user session.) + +## Verification + +URLs drift when WorkOS reorganizes docs. Verify a URL by WebFetch only when (a) the user reports a broken link, or (b) you're about to write the URL into a file the user will commit. Routine chat answers don't require verification -- that wastes tokens. diff --git a/plugins/workos/skills/workos/references/workos-vault.md b/plugins/workos/skills/workos/references/workos-vault.md new file mode 100644 index 00000000..cc9bd585 --- /dev/null +++ b/plugins/workos/skills/workos/references/workos-vault.md @@ -0,0 +1,44 @@ +# WorkOS Vault + +## Docs + +- https://workos.com/docs/vault/quick-start +- https://workos.com/docs/vault/key-context +- https://workos.com/docs/vault/index +- https://workos.com/docs/vault/byok +- https://workos.com/docs/reference/vault +- https://workos.com/docs/reference/vault/key +- https://workos.com/docs/reference/vault/key/create-data-key +- https://workos.com/docs/reference/vault/key/decrypt-data +- https://workos.com/docs/reference/vault/key/decrypt-data-key + If this file conflicts with fetched docs, follow the docs. + +## Gotchas + +- BYOK requires customer-side IAM permissions granting WorkOS access to their KMS. Your app cannot do this programmatically -- provide customers with IAM policy templates from the BYOK docs. +- Vault encrypts data per WorkOS organization. Every operation requires an `organization_id` -- there is no global/unscoped access. +- Do NOT use internal customer IDs as `organization_id`. WorkOS organization IDs have format `org_*`. Always map through WorkOS APIs. +- Key context metadata is NOT encrypted separately. Do not store sensitive data or PII in metadata fields. +- Vault keys are case-sensitive. Mismatched casing between store and retrieve silently returns "key not found." +- BYOK KMS IAM changes can take 5-10 minutes to propagate. Customer must grant `kms:Decrypt` and `kms:Encrypt` on their key. + +## Endpoints + +| Endpoint | Description | +| ----------------------- | ------------------------------ | +| `/vault` | vault | +| `/key` | vault - key | +| `/key/create-data-key` | vault - key - create-data-key | +| `/key/decrypt-data` | vault - key - decrypt-data | +| `/key/decrypt-data-key` | vault - key - decrypt-data-key | +| `/key/encrypt-data` | vault - key - encrypt-data | +| `/object` | vault - object | +| `/object/create` | vault - object - create | +| `/object/delete` | vault - object - delete | +| `/object/get` | vault - object - get | +| `/object/get-by-name` | vault - object - get-by-name | +| `/object/list` | vault - object - list | +| `/object/metadata` | vault - object - metadata | +| `/object/update` | vault - object - update | +| `/object/version` | vault - object - version | +| `/object/versions` | vault - object - versions |