diff --git a/ee/apps/den-api/src/generated/app-version.ts b/ee/apps/den-api/src/generated/app-version.ts index 10716959..04fd0ca5 100644 --- a/ee/apps/den-api/src/generated/app-version.ts +++ b/ee/apps/den-api/src/generated/app-version.ts @@ -1 +1 @@ -export const BUILD_LATEST_APP_VERSION = "0.11.212" as const; +export const BUILD_LATEST_APP_VERSION = "0.11.212" as const diff --git a/ee/apps/den-api/src/routes/org/plugin-system/contracts.ts b/ee/apps/den-api/src/routes/org/plugin-system/contracts.ts index fd283dee..a3db498b 100644 --- a/ee/apps/den-api/src/routes/org/plugin-system/contracts.ts +++ b/ee/apps/den-api/src/routes/org/plugin-system/contracts.ts @@ -20,7 +20,11 @@ import { connectorAccountDisconnectSchema, connectorAccountListQuerySchema, connectorAccountListResponseSchema, + connectorAccountDisconnectResponseSchema, connectorAccountMutationResponseSchema, + connectorInstanceAutoImportSchema, + connectorInstanceConfigurationResponseSchema, + connectorInstanceRemoveResponseSchema, connectorAccountParamsSchema, connectorAccountRepositoryParamsSchema, connectorInstanceAccessGrantParamsSchema, @@ -50,6 +54,15 @@ import { connectorTargetParamsSchema, connectorTargetUpdateSchema, githubConnectorAccountCreateSchema, + githubConnectorDiscoveryResponseSchema, + githubDiscoveryApplyResponseSchema, + githubDiscoveryApplySchema, + githubDiscoveryTreeQuerySchema, + githubDiscoveryTreeResponseSchema, + githubInstallCompleteResponseSchema, + githubInstallCompleteSchema, + githubInstallStartResponseSchema, + githubInstallStartSchema, githubConnectorSetupSchema, githubRepositoryListQuerySchema, githubRepositoryListResponseSchema, @@ -69,6 +82,7 @@ import { marketplaceMutationResponseSchema, marketplaceParamsSchema, marketplacePluginListResponseSchema, + marketplaceResolvedResponseSchema, marketplacePluginMutationResponseSchema, marketplacePluginParamsSchema, marketplacePluginWriteSchema, @@ -152,6 +166,7 @@ export const pluginArchRoutePaths = { pluginAccessGrant: `${orgBasePath}/plugins/:pluginId/access/:grantId`, marketplaces: `${orgBasePath}/marketplaces`, marketplace: `${orgBasePath}/marketplaces/:marketplaceId`, + marketplaceResolved: `${orgBasePath}/marketplaces/:marketplaceId/resolved`, marketplaceArchive: `${orgBasePath}/marketplaces/:marketplaceId/archive`, marketplaceRestore: `${orgBasePath}/marketplaces/:marketplaceId/restore`, marketplacePlugins: `${orgBasePath}/marketplaces/:marketplaceId/plugins`, @@ -163,6 +178,9 @@ export const pluginArchRoutePaths = { connectorAccountDisconnect: `${orgBasePath}/connector-accounts/:connectorAccountId/disconnect`, connectorInstances: `${orgBasePath}/connector-instances`, connectorInstance: `${orgBasePath}/connector-instances/:connectorInstanceId`, + connectorInstanceConfiguration: `${orgBasePath}/connector-instances/:connectorInstanceId/configuration`, + connectorInstanceAutoImport: `${orgBasePath}/connector-instances/:connectorInstanceId/auto-import`, + connectorInstanceRemove: `${orgBasePath}/connector-instances/:connectorInstanceId/remove`, connectorInstanceArchive: `${orgBasePath}/connector-instances/:connectorInstanceId/archive`, connectorInstanceDisable: `${orgBasePath}/connector-instances/:connectorInstanceId/disable`, connectorInstanceEnable: `${orgBasePath}/connector-instances/:connectorInstanceId/enable`, @@ -177,11 +195,16 @@ export const pluginArchRoutePaths = { connectorSyncEvents: `${orgBasePath}/connector-sync-events`, connectorSyncEvent: `${orgBasePath}/connector-sync-events/:connectorSyncEventId`, connectorSyncEventRetry: `${orgBasePath}/connector-sync-events/:connectorSyncEventId/retry`, + connectorInstanceDiscovery: `${orgBasePath}/connector-instances/:connectorInstanceId/discovery`, + connectorInstanceDiscoveryApply: `${orgBasePath}/connector-instances/:connectorInstanceId/discovery/apply`, + connectorInstanceDiscoveryTree: `${orgBasePath}/connector-instances/:connectorInstanceId/discovery/tree`, + githubInstallStart: `${orgBasePath}/connectors/github/install/start`, + githubInstallComplete: `${orgBasePath}/connectors/github/install/complete`, githubSetup: `${orgBasePath}/connectors/github/setup`, githubAccounts: `${orgBasePath}/connectors/github/accounts`, githubAccountRepositories: `${orgBasePath}/connectors/github/accounts/:connectorAccountId/repositories`, githubValidateTarget: `${orgBasePath}/connectors/github/validate-target`, - githubWebhookIngress: "/api/webhooks/connectors/github", + githubWebhookIngress: "/v1/webhooks/connectors/github", } as const export const pluginArchEndpointContracts: Record = { @@ -509,6 +532,15 @@ export const pluginArchEndpointContracts: Record = { response: { description: "Marketplace plugin memberships.", schema: marketplacePluginListResponseSchema, status: 200 }, tag: "Marketplaces", }, + getMarketplaceResolved: { + audience: "admin", + description: "Return marketplace detail with plugins and derived source info.", + method: "GET", + path: pluginArchRoutePaths.marketplaceResolved, + request: { params: marketplaceParamsSchema }, + response: { description: "Marketplace resolved detail.", schema: marketplaceResolvedResponseSchema, status: 200 }, + tag: "Marketplaces", + }, addMarketplacePlugin: { audience: "admin", description: "Add a plugin to a marketplace using marketplace-scoped write access.", @@ -583,11 +615,11 @@ export const pluginArchEndpointContracts: Record = { }, disconnectConnectorAccount: { audience: "admin", - description: "Disconnect one connector account without deleting historical sync state.", + description: "Disconnect one connector account and delete its connector-managed imports (mappings, config objects, plugins created via discovery, and empty marketplaces).", method: "POST", path: pluginArchRoutePaths.connectorAccountDisconnect, request: { body: connectorAccountDisconnectSchema, params: connectorAccountParamsSchema }, - response: { description: "Connector account disconnected successfully.", schema: connectorAccountMutationResponseSchema, status: 200 }, + response: { description: "Connector account disconnected and cleaned up successfully.", schema: connectorAccountDisconnectResponseSchema, status: 200 }, tag: "Connectors", }, listConnectorInstances: { @@ -635,6 +667,33 @@ export const pluginArchEndpointContracts: Record = { response: { description: "Connector instance archived successfully.", schema: connectorInstanceMutationResponseSchema, status: 200 }, tag: "Connectors", }, + getConnectorInstanceConfiguration: { + audience: "admin", + description: "Return the configured plugins, mappings, and import state for a connector instance.", + method: "GET", + path: pluginArchRoutePaths.connectorInstanceConfiguration, + request: { params: connectorInstanceParamsSchema }, + response: { description: "Connector instance configuration returned successfully.", schema: connectorInstanceConfigurationResponseSchema, status: 200 }, + tag: "Connectors", + }, + setConnectorInstanceAutoImport: { + audience: "admin", + description: "Enable or disable auto-import of new plugins for a connector instance.", + method: "POST", + path: pluginArchRoutePaths.connectorInstanceAutoImport, + request: { body: connectorInstanceAutoImportSchema, params: connectorInstanceParamsSchema }, + response: { description: "Connector instance auto-import updated successfully.", schema: connectorInstanceConfigurationResponseSchema, status: 200 }, + tag: "Connectors", + }, + removeConnectorInstance: { + audience: "admin", + description: "Remove a connector instance and delete its associated plugins, mappings, config objects, and bindings.", + method: "POST", + path: pluginArchRoutePaths.connectorInstanceRemove, + request: { params: connectorInstanceParamsSchema }, + response: { description: "Connector instance removed and cleaned up successfully.", schema: connectorInstanceRemoveResponseSchema, status: 200 }, + tag: "Connectors", + }, disableConnectorInstance: { audience: "admin", description: "Disable sync execution for a connector instance.", @@ -788,6 +847,51 @@ export const pluginArchEndpointContracts: Record = { response: { description: "Connector sync retry queued successfully.", schema: connectorSyncAsyncResponseSchema, status: 202 }, tag: "Connectors", }, + getGithubConnectorDiscovery: { + audience: "admin", + description: "Analyze a GitHub connector instance and return the discovered repository shape and plugin candidates.", + method: "GET", + path: pluginArchRoutePaths.connectorInstanceDiscovery, + request: { params: connectorInstanceParamsSchema }, + response: { description: "GitHub connector discovery returned successfully.", schema: githubConnectorDiscoveryResponseSchema, status: 200 }, + tag: "GitHub", + }, + listGithubConnectorDiscoveryTree: { + audience: "admin", + description: "Page through the normalized GitHub repository tree for one connector instance.", + method: "GET", + path: pluginArchRoutePaths.connectorInstanceDiscoveryTree, + request: { params: connectorInstanceParamsSchema, query: githubDiscoveryTreeQuerySchema }, + response: { description: "GitHub discovery tree page returned successfully.", schema: githubDiscoveryTreeResponseSchema, status: 200 }, + tag: "GitHub", + }, + applyGithubConnectorDiscovery: { + audience: "admin", + description: "Create OpenWork plugins and connector mappings from selected GitHub discovery candidates.", + method: "POST", + path: pluginArchRoutePaths.connectorInstanceDiscoveryApply, + request: { body: githubDiscoveryApplySchema, params: connectorInstanceParamsSchema }, + response: { description: "GitHub discovery selection applied successfully.", schema: githubDiscoveryApplyResponseSchema, status: 200 }, + tag: "GitHub", + }, + githubInstallStart: { + audience: "admin", + description: "Start the GitHub App install flow and return a redirect URL.", + method: "POST", + path: pluginArchRoutePaths.githubInstallStart, + request: { body: githubInstallStartSchema }, + response: { description: "GitHub install redirect created successfully.", schema: githubInstallStartResponseSchema, status: 200 }, + tag: "GitHub", + }, + githubInstallComplete: { + audience: "admin", + description: "Complete one GitHub App installation and return repositories visible to it.", + method: "POST", + path: pluginArchRoutePaths.githubInstallComplete, + request: { body: githubInstallCompleteSchema }, + response: { description: "GitHub installation completed successfully.", schema: githubInstallCompleteResponseSchema, status: 200 }, + tag: "GitHub", + }, githubSetup: { audience: "admin", description: "Create the GitHub connector account, instance, target, and initial mappings in one setup flow.", diff --git a/ee/apps/den-api/src/routes/org/plugin-system/github-app.ts b/ee/apps/den-api/src/routes/org/plugin-system/github-app.ts new file mode 100644 index 00000000..ce1b73af --- /dev/null +++ b/ee/apps/den-api/src/routes/org/plugin-system/github-app.ts @@ -0,0 +1,639 @@ +import { createHmac, createSign, randomUUID, timingSafeEqual } from "node:crypto" + +export class GithubConnectorConfigError extends Error { + constructor(message: string) { + super(message) + this.name = "GithubConnectorConfigError" + } +} + +export class GithubConnectorRequestError extends Error { + constructor( + message: string, + readonly status: number, + readonly body?: unknown, + ) { + super(message) + this.name = "GithubConnectorRequestError" + } +} + +export type GithubConnectorAppConfig = { + appId: string + clientId?: string + clientSecret?: string + privateKey: string +} + +type GithubFetch = typeof fetch + +export type GithubManifestKind = "marketplace" | "plugin" | null + +type GithubRepositorySummary = { + defaultBranch: string | null + fullName: string + hasPluginManifest?: boolean + id: number + manifestKind?: GithubManifestKind + marketplacePluginCount?: number | null + private: boolean +} + +export type GithubRepositoryTreeEntry = { + id: string + kind: "blob" | "tree" + path: string + sha: string | null + size: number | null +} + +export type GithubRepositoryTreeSnapshot = { + headSha: string + truncated: boolean + treeEntries: GithubRepositoryTreeEntry[] + treeSha: string +} + +export type GithubAppSummary = { + htmlUrl: string + name: string + slug: string +} + +export type GithubInstallationSummary = { + accountLogin: string + accountType: "Organization" | "User" + displayName: string + installationId: number + repositorySelection: "all" | "selected" + settingsUrl: string | null +} + +export type GithubInstallStatePayload = { + exp: number + nonce: string + orgId: string + returnPath: string + userId: string +} + +const GITHUB_API_BASE = "https://api.github.com" +const GITHUB_API_VERSION = "2022-11-28" + +function base64UrlEncode(value: unknown) { + const buffer = typeof value === "string" + ? Buffer.from(value) + : Buffer.isBuffer(value) + ? value + : value instanceof Uint8Array + ? Buffer.from(value.buffer, value.byteOffset, value.byteLength) + : (() => { + throw new GithubConnectorConfigError("Unsupported value passed to base64UrlEncode.") + })() + + return buffer + .toString("base64") + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/g, "") +} + +export function normalizeGithubPrivateKey(privateKey: string) { + return privateKey.includes("\\n") ? privateKey.replace(/\\n/g, "\n") : privateKey +} + +export function getGithubConnectorAppConfig(input: { appId?: string; privateKey?: string }) { + const appId = input.appId?.trim() + const privateKey = input.privateKey?.trim() + + if (!appId) { + throw new GithubConnectorConfigError("GITHUB_CONNECTOR_APP_ID is required for live GitHub connector testing.") + } + + if (!privateKey) { + throw new GithubConnectorConfigError("GITHUB_CONNECTOR_APP_PRIVATE_KEY is required for live GitHub connector testing.") + } + + return { + appId, + privateKey: normalizeGithubPrivateKey(privateKey), + } satisfies GithubConnectorAppConfig +} + +function base64UrlDecode(value: string) { + return Buffer.from(value, "base64url") +} + +function isSafeRelativeReturnPath(value: string) { + return value.startsWith("/") && !value.startsWith("//") +} + +export function createGithubInstallStateToken(input: { + now?: Date | number + orgId: string + returnPath: string + secret: string + ttlSeconds?: number + userId: string +}) { + const nowMs = input.now instanceof Date ? input.now.getTime() : (typeof input.now === "number" ? input.now : Date.now()) + const returnPath = input.returnPath.trim() + if (!isSafeRelativeReturnPath(returnPath)) { + throw new GithubConnectorConfigError("GitHub install return path must be a safe relative path.") + } + + const payload: GithubInstallStatePayload = { + exp: Math.floor(nowMs / 1000) + (input.ttlSeconds ?? 10 * 60), + nonce: randomUUID(), + orgId: input.orgId, + returnPath, + userId: input.userId, + } + const encodedPayload = base64UrlEncode(JSON.stringify(payload)) + const signature = base64UrlEncode(createHmac("sha256", input.secret).update(encodedPayload).digest()) + return `${encodedPayload}.${signature}` +} + +export function verifyGithubInstallStateToken(input: { now?: Date | number; secret: string; token: string }) { + const [encodedPayload, encodedSignature] = input.token.split(".") + if (!encodedPayload || !encodedSignature) { + return null + } + + try { + const expectedSignature = createHmac("sha256", input.secret).update(encodedPayload).digest() + const providedSignature = base64UrlDecode(encodedSignature) + const expectedBytes = new Uint8Array(expectedSignature) + const providedBytes = new Uint8Array(providedSignature) + if (expectedBytes.length !== providedBytes.length || !timingSafeEqual(expectedBytes, providedBytes)) { + return null + } + + const payload = JSON.parse(base64UrlDecode(encodedPayload).toString("utf8")) as Partial + const nowSeconds = Math.floor((input.now instanceof Date ? input.now.getTime() : (typeof input.now === "number" ? input.now : Date.now())) / 1000) + if ( + typeof payload.exp !== "number" + || typeof payload.nonce !== "string" + || typeof payload.orgId !== "string" + || typeof payload.returnPath !== "string" + || typeof payload.userId !== "string" + || payload.exp < nowSeconds + || !isSafeRelativeReturnPath(payload.returnPath) + ) { + return null + } + return payload as GithubInstallStatePayload + } catch { + return null + } +} + +export function createGithubAppJwt(input: GithubConnectorAppConfig & { now?: Date | number }) { + const nowMs = input.now instanceof Date ? input.now.getTime() : (typeof input.now === "number" ? input.now : Date.now()) + const issuedAt = Math.floor(nowMs / 1000) - 60 + const expiresAt = issuedAt + (9 * 60) + const signingInput = [ + base64UrlEncode(JSON.stringify({ alg: "RS256", typ: "JWT" })), + base64UrlEncode(JSON.stringify({ exp: expiresAt, iat: issuedAt, iss: input.appId })), + ].join(".") + + const signer = createSign("RSA-SHA256") + signer.update(signingInput) + signer.end() + + return `${signingInput}.${base64UrlEncode(signer.sign(input.privateKey))}` +} + +async function requestGithubJson(input: { + fetchFn?: GithubFetch + headers?: Record + method?: "GET" | "POST" + path: string + allowStatuses?: number[] +}) { + const fetchFn = input.fetchFn ?? fetch + const response = await fetchFn(`${GITHUB_API_BASE}${input.path}`, { + headers: { + Accept: "application/vnd.github+json", + "User-Agent": "openwork-den-api", + "X-GitHub-Api-Version": GITHUB_API_VERSION, + ...input.headers, + }, + method: input.method ?? "GET", + }) + + const text = await response.text() + const body = text ? JSON.parse(text) as unknown : null + if (!response.ok && !(input.allowStatuses ?? []).includes(response.status)) { + const message = body && typeof body === "object" && typeof (body as Record).message === "string" + ? (body as Record).message as string + : `GitHub request failed with status ${response.status}.` + throw new GithubConnectorRequestError(message, response.status, body) + } + + return { + body: body as TResponse, + ok: response.ok, + status: response.status, + } +} + +export async function getGithubAppSummary(input: { config: GithubConnectorAppConfig; fetchFn?: GithubFetch }) { + const jwt = createGithubAppJwt(input.config) + const response = await requestGithubJson<{ html_url?: string; name?: string; slug?: string }>({ + fetchFn: input.fetchFn, + headers: { + Authorization: `Bearer ${jwt}`, + }, + path: "/app", + }) + + const htmlUrl = typeof response.body.html_url === "string" ? response.body.html_url.trim() : "" + const slug = typeof response.body.slug === "string" ? response.body.slug.trim() : "" + const name = typeof response.body.name === "string" ? response.body.name.trim() : "" + if (!htmlUrl || !slug || !name) { + throw new GithubConnectorRequestError("GitHub app metadata response was incomplete.", 502, response.body) + } + + return { + htmlUrl, + name, + slug, + } satisfies GithubAppSummary +} + +export function buildGithubAppInstallUrl(input: { app: GithubAppSummary; state: string }) { + const url = new URL(`${input.app.htmlUrl.replace(/\/+$/, "")}/installations/new`) + url.searchParams.set("state", input.state) + return url.toString() +} + +export async function getGithubInstallationSummary(input: { config: GithubConnectorAppConfig; fetchFn?: GithubFetch; installationId: number }) { + const jwt = createGithubAppJwt(input.config) + const response = await requestGithubJson<{ + account?: { + login?: string + type?: string + } + html_url?: string + id?: number + repository_selection?: string + }>({ + fetchFn: input.fetchFn, + headers: { + Authorization: `Bearer ${jwt}`, + }, + path: `/app/installations/${input.installationId}`, + }) + + const installationId = typeof response.body.id === "number" ? response.body.id : input.installationId + const accountLogin = typeof response.body.account?.login === "string" ? response.body.account.login.trim() : "" + const accountType = response.body.account?.type === "Organization" ? "Organization" : "User" + const repositorySelection = response.body.repository_selection === "selected" ? "selected" : "all" + if (!accountLogin) { + throw new GithubConnectorRequestError("GitHub installation response was missing the account login.", 502, response.body) + } + + return { + accountLogin, + accountType, + displayName: accountLogin, + installationId, + repositorySelection, + settingsUrl: typeof response.body.html_url === "string" ? response.body.html_url.trim() || null : null, + } satisfies GithubInstallationSummary +} + +async function createGithubInstallationAccessToken(input: { config: GithubConnectorAppConfig; fetchFn?: GithubFetch; installationId: number }) { + const jwt = createGithubAppJwt(input.config) + const response = await requestGithubJson<{ token?: string }>({ + fetchFn: input.fetchFn, + headers: { + Authorization: `Bearer ${jwt}`, + }, + method: "POST", + path: `/app/installations/${input.installationId}/access_tokens`, + }) + + const token = typeof response.body?.token === "string" ? response.body.token : null + if (!token) { + throw new GithubConnectorRequestError("GitHub did not return an installation access token.", 502, response.body) + } + + return token +} + +export async function getGithubInstallationAccessToken(input: { config: GithubConnectorAppConfig; fetchFn?: GithubFetch; installationId: number }) { + return createGithubInstallationAccessToken(input) +} + +function normalizeGithubRepository(entry: unknown): GithubRepositorySummary | null { + if (!entry || typeof entry !== "object") { + return null + } + + const candidate = entry as Record + const id = typeof candidate.id === "number" ? candidate.id : Number(candidate.id) + const fullName = typeof candidate.full_name === "string" + ? candidate.full_name + : typeof candidate.fullName === "string" + ? candidate.fullName + : null + + if (!Number.isFinite(id) || !fullName) { + return null + } + + return { + defaultBranch: typeof candidate.default_branch === "string" + ? candidate.default_branch + : typeof candidate.defaultBranch === "string" + ? candidate.defaultBranch + : null, + fullName, + id, + private: Boolean(candidate.private), + } +} + +export async function listGithubInstallationRepositories(input: { config: GithubConnectorAppConfig; fetchFn?: GithubFetch; installationId: number }) { + const token = await createGithubInstallationAccessToken(input) + const response = await requestGithubJson<{ repositories?: unknown[] }>({ + fetchFn: input.fetchFn, + headers: { + Authorization: `Bearer ${token}`, + }, + path: "/installation/repositories", + }) + + if (!Array.isArray(response.body.repositories)) { + return [] + } + + const repositories: GithubRepositorySummary[] = [] + for (const entry of response.body.repositories) { + const normalized = normalizeGithubRepository(entry) + if (!normalized) { + continue + } + + const manifest = await detectRepositoryManifest({ + fetchFn: input.fetchFn, + ownerAndRepo: normalized.fullName, + token, + }) + + repositories.push({ + ...normalized, + hasPluginManifest: manifest.manifestKind !== null, + manifestKind: manifest.manifestKind, + marketplacePluginCount: manifest.marketplacePluginCount, + }) + } + + return repositories +} + +async function detectRepositoryManifest(input: { fetchFn?: GithubFetch; ownerAndRepo: string; token: string }): Promise<{ + manifestKind: GithubManifestKind + marketplacePluginCount: number | null +}> { + const parts = splitRepositoryFullName(input.ownerAndRepo) + if (!parts) { + return { manifestKind: null, marketplacePluginCount: null } + } + + const marketplaceResponse = await requestGithubJson<{ content?: string; encoding?: string }>({ + allowStatuses: [404], + fetchFn: input.fetchFn, + headers: { + Authorization: `Bearer ${input.token}`, + }, + path: `/repos/${encodeURIComponent(parts.owner)}/${encodeURIComponent(parts.repo)}/contents/.claude-plugin/marketplace.json`, + }) + + if (marketplaceResponse.ok && typeof marketplaceResponse.body?.content === "string" && marketplaceResponse.body.encoding === "base64") { + let marketplacePluginCount: number | null = null + try { + const decoded = Buffer.from(marketplaceResponse.body.content.replace(/\n/g, ""), "base64").toString("utf8") + const parsed = JSON.parse(decoded) as unknown + if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && Array.isArray((parsed as Record).plugins)) { + marketplacePluginCount = ((parsed as Record).plugins as unknown[]).length + } + } catch { + marketplacePluginCount = null + } + return { manifestKind: "marketplace", marketplacePluginCount } + } + + const pluginResponse = await requestGithubJson({ + allowStatuses: [404], + fetchFn: input.fetchFn, + headers: { + Authorization: `Bearer ${input.token}`, + }, + path: `/repos/${encodeURIComponent(parts.owner)}/${encodeURIComponent(parts.repo)}/contents/.claude-plugin/plugin.json`, + }) + + if (pluginResponse.ok) { + return { manifestKind: "plugin", marketplacePluginCount: null } + } + + return { manifestKind: null, marketplacePluginCount: null } +} + +function splitRepositoryFullName(repositoryFullName: string) { + const [owner, repo, ...rest] = repositoryFullName.trim().split("/") + if (!owner || !repo || rest.length > 0) { + return null + } + + return { owner, repo } +} + +export async function getGithubRepositoryTextFile(input: { + config: GithubConnectorAppConfig + fetchFn?: GithubFetch + installationId: number + path: string + ref: string + repositoryFullName: string +}) { + const repositoryParts = splitRepositoryFullName(input.repositoryFullName) + if (!repositoryParts) { + throw new GithubConnectorRequestError("GitHub repository full name is invalid.", 400) + } + + const token = await createGithubInstallationAccessToken(input) + const response = await requestGithubJson<{ content?: string; encoding?: string }>({ + allowStatuses: [404], + fetchFn: input.fetchFn, + headers: { + Authorization: `Bearer ${token}`, + }, + path: `/repos/${encodeURIComponent(repositoryParts.owner)}/${encodeURIComponent(repositoryParts.repo)}/contents/${input.path.split("/").map(encodeURIComponent).join("/")}?ref=${encodeURIComponent(input.ref)}`, + }) + + if (!response.ok) { + return null + } + + if (response.body.encoding !== "base64" || typeof response.body.content !== "string") { + throw new GithubConnectorRequestError("GitHub file response was incomplete.", 502, response.body) + } + + return Buffer.from(response.body.content.replace(/\n/g, ""), "base64").toString("utf8") +} + +export async function getGithubRepositoryTree(input: { + branch: string + config: GithubConnectorAppConfig + fetchFn?: GithubFetch + installationId: number + repositoryFullName: string +}) { + const repositoryParts = splitRepositoryFullName(input.repositoryFullName) + if (!repositoryParts) { + throw new GithubConnectorRequestError("GitHub repository full name is invalid.", 400) + } + + const token = await createGithubInstallationAccessToken(input) + const authHeaders = { + Authorization: `Bearer ${token}`, + } + const commitResponse = await requestGithubJson<{ + commit?: { + tree?: { + sha?: string + } + } + sha?: string + }>({ + fetchFn: input.fetchFn, + headers: authHeaders, + path: `/repos/${encodeURIComponent(repositoryParts.owner)}/${encodeURIComponent(repositoryParts.repo)}/commits/${encodeURIComponent(input.branch.trim())}`, + }) + + const headSha = typeof commitResponse.body.sha === "string" ? commitResponse.body.sha : "" + const treeSha = typeof commitResponse.body.commit?.tree?.sha === "string" ? commitResponse.body.commit.tree.sha : "" + if (!headSha || !treeSha) { + throw new GithubConnectorRequestError("GitHub commit response was missing the head or tree sha.", 502, commitResponse.body) + } + + const treeResponse = await requestGithubJson<{ + truncated?: boolean + tree?: Array<{ + path?: string + sha?: string + size?: number + type?: string + }> + }>({ + fetchFn: input.fetchFn, + headers: authHeaders, + path: `/repos/${encodeURIComponent(repositoryParts.owner)}/${encodeURIComponent(repositoryParts.repo)}/git/trees/${encodeURIComponent(treeSha)}?recursive=1`, + }) + + const treeEntries = Array.isArray(treeResponse.body.tree) + ? treeResponse.body.tree.flatMap((entry) => { + const path = typeof entry.path === "string" ? entry.path.trim() : "" + const kind = entry.type === "blob" || entry.type === "tree" ? entry.type : null + if (!path || !kind) { + return [] + } + + return [{ + id: path, + kind, + path, + sha: typeof entry.sha === "string" ? entry.sha : null, + size: typeof entry.size === "number" ? entry.size : null, + } satisfies GithubRepositoryTreeEntry] + }) + : [] + + return { + headSha, + truncated: Boolean(treeResponse.body.truncated), + treeEntries, + treeSha, + } satisfies GithubRepositoryTreeSnapshot +} + +export async function validateGithubInstallationTarget(input: { + branch: string + config: GithubConnectorAppConfig + fetchFn?: GithubFetch + installationId: number + ref: string + repositoryFullName: string + repositoryId: number +}) { + const repositoryParts = splitRepositoryFullName(input.repositoryFullName) + if (!repositoryParts) { + return { + branchExists: false, + defaultBranch: null, + repositoryAccessible: false, + } + } + + const token = await createGithubInstallationAccessToken(input) + const authHeaders = { + Authorization: `Bearer ${token}`, + } + const repositoryResponse = await requestGithubJson<{ + default_branch?: string + full_name?: string + id?: number + }>({ + allowStatuses: [404], + fetchFn: input.fetchFn, + headers: authHeaders, + path: `/repos/${encodeURIComponent(repositoryParts.owner)}/${encodeURIComponent(repositoryParts.repo)}`, + }) + + if (!repositoryResponse.ok) { + return { + branchExists: false, + defaultBranch: null, + repositoryAccessible: false, + } + } + + const defaultBranch = typeof repositoryResponse.body.default_branch === "string" + ? repositoryResponse.body.default_branch + : null + const repositoryAccessible = repositoryResponse.body.id === input.repositoryId + && repositoryResponse.body.full_name === input.repositoryFullName + + if (!repositoryAccessible) { + return { + branchExists: false, + defaultBranch, + repositoryAccessible: false, + } + } + + const expectedRef = `refs/heads/${input.branch.trim()}` + if (input.ref.trim() !== expectedRef) { + return { + branchExists: false, + defaultBranch, + repositoryAccessible: true, + } + } + + const branchResponse = await requestGithubJson<{ name?: string }>({ + allowStatuses: [404], + fetchFn: input.fetchFn, + headers: authHeaders, + path: `/repos/${encodeURIComponent(repositoryParts.owner)}/${encodeURIComponent(repositoryParts.repo)}/branches/${encodeURIComponent(input.branch.trim())}`, + }) + + return { + branchExists: branchResponse.ok && branchResponse.body.name === input.branch.trim(), + defaultBranch, + repositoryAccessible: true, + } +} diff --git a/ee/apps/den-api/src/routes/org/plugin-system/github-discovery.ts b/ee/apps/den-api/src/routes/org/plugin-system/github-discovery.ts new file mode 100644 index 00000000..8f5fc102 --- /dev/null +++ b/ee/apps/den-api/src/routes/org/plugin-system/github-discovery.ts @@ -0,0 +1,494 @@ +type GithubDiscoveryTreeEntryKind = "blob" | "tree" + +export type GithubDiscoveryTreeEntry = { + id: string + kind: GithubDiscoveryTreeEntryKind + path: string + sha: string | null + size: number | null +} + +export type GithubDiscoveryClassification = + | "claude_marketplace_repo" + | "claude_multi_plugin_repo" + | "claude_single_plugin_repo" + | "folder_inferred_repo" + | "unsupported" + +export type GithubDiscoveredPluginSourceKind = + | "marketplace_entry" + | "plugin_manifest" + | "standalone_claude" + | "folder_inference" + +export type GithubDiscoveredPluginComponentKind = + | "skill" + | "command" + | "agent" + | "hook" + | "mcp_server" + | "lsp_server" + | "monitor" + | "settings" + +export type GithubDiscoveredPlugin = { + componentKinds: GithubDiscoveredPluginComponentKind[] + componentPaths: { + agents: string[] + commands: string[] + hooks: string[] + lspServers: string[] + mcpServers: string[] + monitors: string[] + settings: string[] + skills: string[] + } + description: string | null + displayName: string + key: string + manifestPath: string | null + metadata: Record + rootPath: string + selectedByDefault: boolean + sourceKind: GithubDiscoveredPluginSourceKind + supported: boolean + warnings: string[] +} + +export type GithubMarketplaceInfo = { + description: string | null + name: string | null + owner: string | null + version: string | null +} + +export type GithubRepoDiscoveryResult = { + classification: GithubDiscoveryClassification + discoveredPlugins: GithubDiscoveredPlugin[] + marketplace: GithubMarketplaceInfo | null + warnings: string[] +} + +type MarketplaceEntry = { + agents?: unknown + commands?: unknown + description?: unknown + hooks?: unknown + mcpServers?: unknown + name?: unknown + settings?: unknown + skills?: unknown + source?: unknown +} + +type PluginMetadata = { + description: string | null + metadata: Record + name: string | null +} + +const KNOWN_COMPONENT_SEGMENTS = ["skills", "commands", "agents"] as const + +function normalizePath(value: string) { + return value.trim().replace(/^\.\//, "").replace(/^\/+/, "").replace(/\/+$/, "") +} + +function joinPath(rootPath: string, childPath: string) { + const root = normalizePath(rootPath) + const child = normalizePath(childPath) + if (!root) return child + if (!child) return root + return `${root}/${child}` +} + +function basename(path: string) { + const normalized = normalizePath(path) + if (!normalized) return null + const parts = normalized.split("/") + return parts[parts.length - 1] ?? null +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null +} + +function asString(value: unknown) { + return typeof value === "string" && value.trim().length > 0 ? value.trim() : null +} + +function pathDirectoryPrefixes(path: string) { + const segments = normalizePath(path).split("/").filter(Boolean) + const prefixes: string[] = [] + for (let index = 1; index <= segments.length; index += 1) { + prefixes.push(segments.slice(0, index).join("/")) + } + return prefixes +} + +function buildPathSet(entries: GithubDiscoveryTreeEntry[]) { + const knownPaths = new Set() + for (const entry of entries) { + const normalizedPath = normalizePath(entry.path) + if (!normalizedPath) continue + knownPaths.add(normalizedPath) + for (const prefix of pathDirectoryPrefixes(normalizedPath)) { + knownPaths.add(prefix) + } + } + return knownPaths +} + +function hasPath(knownPaths: Set, path: string) { + const normalized = normalizePath(path) + return normalized.length > 0 && knownPaths.has(normalized) +} + +function hasDescendant(knownPaths: Set, path: string) { + const normalized = normalizePath(path) + if (!normalized) return false + for (const candidate of knownPaths) { + if (candidate === normalized || candidate.startsWith(`${normalized}/`)) { + return true + } + } + return false +} + +function readJsonMap(fileTextByPath: Record, path: string) { + const text = fileTextByPath[normalizePath(path)] + if (!text) return null + try { + return JSON.parse(text) as unknown + } catch { + return null + } +} + +function readPluginMetadata(fileTextByPath: Record, rootPath: string, manifestPath?: string | null): PluginMetadata { + const manifestCandidate = manifestPath ? normalizePath(manifestPath) : normalizePath(joinPath(rootPath, ".claude-plugin/plugin.json")) + const explicitManifest = manifestCandidate ? readJsonMap(fileTextByPath, manifestCandidate) : null + if (isRecord(explicitManifest)) { + return { + description: asString(explicitManifest.description), + metadata: explicitManifest, + name: asString(explicitManifest.name), + } + } + + const fallbackPluginJson = readJsonMap(fileTextByPath, joinPath(rootPath, "plugin.json")) + if (isRecord(fallbackPluginJson)) { + return { + description: asString(fallbackPluginJson.description), + metadata: fallbackPluginJson, + name: asString(fallbackPluginJson.name), + } + } + + return { + description: null, + metadata: {}, + name: null, + } +} + +function collectComponentPaths(knownPaths: Set, rootPath: string) { + const componentPaths = { + agents: [] as string[], + commands: [] as string[], + hooks: [] as string[], + lspServers: [] as string[], + mcpServers: [] as string[], + monitors: [] as string[], + settings: [] as string[], + skills: [] as string[], + } + + const candidates: Array<[keyof typeof componentPaths, string]> = [ + ["skills", joinPath(rootPath, "skills")], + ["skills", joinPath(rootPath, ".claude/skills")], + ["commands", joinPath(rootPath, "commands")], + ["commands", joinPath(rootPath, ".claude/commands")], + ["agents", joinPath(rootPath, "agents")], + ["agents", joinPath(rootPath, ".claude/agents")], + ["hooks", joinPath(rootPath, "hooks/hooks.json")], + ["mcpServers", joinPath(rootPath, ".mcp.json")], + ["lspServers", joinPath(rootPath, ".lsp.json")], + ["monitors", joinPath(rootPath, "monitors/monitors.json")], + ["settings", joinPath(rootPath, "settings.json")], + ] + + for (const [bucket, candidate] of candidates) { + if (!candidate) continue + if (bucket === "hooks" || bucket === "mcpServers" || bucket === "lspServers" || bucket === "monitors" || bucket === "settings") { + if (hasPath(knownPaths, candidate)) { + componentPaths[bucket].push(candidate) + } + continue + } + + if (hasDescendant(knownPaths, candidate)) { + componentPaths[bucket].push(candidate) + } + } + + return componentPaths +} + +function readStringArray(value: unknown) { + return Array.isArray(value) + ? value.flatMap((entry) => { + const normalized = asString(entry) + return normalized ? [normalized] : [] + }) + : [] +} + +function marketplaceComponentPaths(entry: MarketplaceEntry, knownPaths: Set, rootPath: string) { + const collect = (values: unknown, { file, directory }: { file?: boolean; directory?: boolean }) => { + const paths: string[] = [] + for (const value of readStringArray(values)) { + const candidate = joinPath(rootPath, value) + if (!candidate && !rootPath) { + continue + } + if ((directory && hasDescendant(knownPaths, candidate)) || (file && hasPath(knownPaths, candidate))) { + paths.push(candidate) + } + } + return paths + } + + return { + agents: collect(entry.agents, { directory: true }), + commands: collect(entry.commands, { directory: true }), + hooks: collect(entry.hooks, { file: true, directory: true }), + lspServers: [], + mcpServers: collect(entry.mcpServers, { file: true }), + monitors: [], + settings: collect(entry.settings, { file: true }), + skills: collect(entry.skills, { directory: true }), + } satisfies GithubDiscoveredPlugin["componentPaths"] +} + +function hasAnyComponentPaths(componentPaths: GithubDiscoveredPlugin["componentPaths"]) { + return Object.values(componentPaths).some((paths) => paths.length > 0) +} + +function componentKindsFromPaths(componentPaths: GithubDiscoveredPlugin["componentPaths"]): GithubDiscoveredPluginComponentKind[] { + const kinds: GithubDiscoveredPluginComponentKind[] = [] + if (componentPaths.skills.length > 0) kinds.push("skill") + if (componentPaths.commands.length > 0) kinds.push("command") + if (componentPaths.agents.length > 0) kinds.push("agent") + if (componentPaths.hooks.length > 0) kinds.push("hook") + if (componentPaths.mcpServers.length > 0) kinds.push("mcp_server") + if (componentPaths.lspServers.length > 0) kinds.push("lsp_server") + if (componentPaths.monitors.length > 0) kinds.push("monitor") + if (componentPaths.settings.length > 0) kinds.push("settings") + return kinds +} + +function buildDiscoveredPlugin(input: { + componentPathsOverride?: GithubDiscoveredPlugin["componentPaths"] | null + description?: string | null + displayName?: string | null + fileTextByPath: Record + key: string + knownPaths: Set + manifestPath?: string | null + rootPath: string + sourceKind: GithubDiscoveredPluginSourceKind + supported?: boolean + warnings?: string[] +}) { + const metadata = readPluginMetadata(input.fileTextByPath, input.rootPath, input.manifestPath) + const componentPaths = input.componentPathsOverride ?? collectComponentPaths(input.knownPaths, input.rootPath) + const displayName = input.displayName?.trim() + || metadata.name + || basename(input.rootPath) + || "Repository plugin" + + return { + componentKinds: componentKindsFromPaths(componentPaths), + componentPaths, + description: input.description ?? metadata.description, + displayName, + key: input.key, + manifestPath: input.manifestPath ? normalizePath(input.manifestPath) : (hasPath(input.knownPaths, joinPath(input.rootPath, ".claude-plugin/plugin.json")) ? joinPath(input.rootPath, ".claude-plugin/plugin.json") : null), + metadata: metadata.metadata, + rootPath: normalizePath(input.rootPath), + selectedByDefault: input.supported !== false, + sourceKind: input.sourceKind, + supported: input.supported !== false, + warnings: input.warnings ?? [], + } satisfies GithubDiscoveredPlugin +} + +function localMarketplaceRoot(entry: MarketplaceEntry) { + if (typeof entry.source === "string") { + return normalizePath(entry.source) + } + + if (!isRecord(entry.source)) { + return null + } + + if (typeof entry.source.url === "string") { + return null + } + + const localPath = asString(entry.source.path) + return localPath ? normalizePath(localPath) : null +} + +function pluginRootsFromManifests(entries: GithubDiscoveryTreeEntry[]) { + return entries + .map((entry) => normalizePath(entry.path)) + .filter((path) => path.endsWith(".claude-plugin/plugin.json")) + .map((path) => path.slice(0, -"/.claude-plugin/plugin.json".length)) +} + +function inferredRootsFromKnownFolders(entries: GithubDiscoveryTreeEntry[]) { + const inferred = new Set() + for (const entry of entries) { + const normalized = normalizePath(entry.path) + if (!normalized) continue + const segments = normalized.split("/") + for (let index = 0; index < segments.length; index += 1) { + const segment = segments[index] + if (!KNOWN_COMPONENT_SEGMENTS.includes(segment as (typeof KNOWN_COMPONENT_SEGMENTS)[number])) { + continue + } + const rootSegments = segments.slice(0, index) + if (rootSegments.length === 1 && rootSegments[0] === ".claude") { + inferred.add("") + continue + } + inferred.add(rootSegments.join("/")) + break + } + } + return [...inferred] +} + +export function buildGithubRepoDiscovery(input: { + entries: GithubDiscoveryTreeEntry[] + fileTextByPath: Record +}) { + const knownPaths = buildPathSet(input.entries) + const warnings: string[] = [] + + if (hasPath(knownPaths, ".claude-plugin/marketplace.json")) { + const marketplaceJson = readJsonMap(input.fileTextByPath, ".claude-plugin/marketplace.json") + const marketplaceEntries = isRecord(marketplaceJson) && Array.isArray(marketplaceJson.plugins) + ? marketplaceJson.plugins.filter(isRecord) as MarketplaceEntry[] + : [] + + const marketplaceInfo: GithubMarketplaceInfo = isRecord(marketplaceJson) + ? { + description: asString(marketplaceJson.description), + name: asString(marketplaceJson.name), + owner: isRecord(marketplaceJson.owner) + ? asString(marketplaceJson.owner.name) ?? asString(marketplaceJson.owner.login) ?? asString(marketplaceJson.owner) + : asString(marketplaceJson.owner), + version: asString(marketplaceJson.version), + } + : { description: null, name: null, owner: null, version: null } + + const discoveredPlugins = marketplaceEntries.map((entry, index) => { + const rootPath = localMarketplaceRoot(entry) + if (rootPath === null) { + const warning = "Marketplace entry points at an external source and cannot be auto-mapped from this connected repo yet." + warnings.push(warning) + return buildDiscoveredPlugin({ + description: asString(entry.description), + displayName: asString(entry.name) ?? `Marketplace plugin ${index + 1}`, + fileTextByPath: input.fileTextByPath, + key: `marketplace:${asString(entry.name) ?? index}`, + knownPaths, + manifestPath: null, + rootPath: "", + sourceKind: "marketplace_entry", + supported: false, + warnings: [warning], + }) + } + + return buildDiscoveredPlugin({ + componentPathsOverride: (() => { + const override = marketplaceComponentPaths(entry, knownPaths, rootPath) + return hasAnyComponentPaths(override) ? override : null + })(), + description: asString(entry.description), + displayName: asString(entry.name), + fileTextByPath: input.fileTextByPath, + key: `marketplace:${rootPath}`, + knownPaths, + manifestPath: joinPath(rootPath, ".claude-plugin/plugin.json"), + rootPath, + sourceKind: "marketplace_entry", + }) + }) + + return { + classification: "claude_marketplace_repo", + discoveredPlugins, + marketplace: marketplaceInfo, + warnings, + } satisfies GithubRepoDiscoveryResult + } + + const manifestRoots = [...new Set(pluginRootsFromManifests(input.entries))] + if (manifestRoots.length > 0) { + const discoveredPlugins = manifestRoots.map((rootPath) => buildDiscoveredPlugin({ + fileTextByPath: input.fileTextByPath, + key: `manifest:${rootPath || "root"}`, + knownPaths, + manifestPath: joinPath(rootPath, ".claude-plugin/plugin.json"), + rootPath, + sourceKind: "plugin_manifest", + })) + + return { + classification: manifestRoots.length === 1 && manifestRoots[0] === "" ? "claude_single_plugin_repo" : "claude_multi_plugin_repo", + discoveredPlugins, + marketplace: null, + warnings, + } satisfies GithubRepoDiscoveryResult + } + + // Intentionally disabled for now: directory-based inference can over-classify + // arbitrary repos as plugins. Until we support a broader compatibility model, + // discovery should only accept explicit Claude plugin markers. + // const inferredRoots = inferredRootsFromKnownFolders(input.entries) + // const standaloneRoot = inferredRoots.includes("") && ( + // hasDescendant(knownPaths, ".claude/skills") + // || hasDescendant(knownPaths, ".claude/commands") + // || hasDescendant(knownPaths, ".claude/agents") + // ) + // const folderRoots = standaloneRoot ? inferredRoots : inferredRoots.filter((root) => root !== "") + // + // if (folderRoots.length > 0) { + // const discoveredPlugins = folderRoots.map((rootPath) => buildDiscoveredPlugin({ + // fileTextByPath: input.fileTextByPath, + // key: `${standaloneRoot && rootPath === "" ? "standalone" : "folder"}:${rootPath || "root"}`, + // knownPaths, + // rootPath, + // sourceKind: standaloneRoot && rootPath === "" ? "standalone_claude" : "folder_inference", + // })) + // + // return { + // classification: "folder_inferred_repo", + // discoveredPlugins, + // warnings, + // } satisfies GithubRepoDiscoveryResult + // } + + warnings.push("OpenWork currently only supports Claude-compatible plugins and marketplaces. Add `.claude-plugin/marketplace.json` or `.claude-plugin/plugin.json` to this repository.") + + return { + classification: "unsupported", + discoveredPlugins: [], + marketplace: null, + warnings, + } satisfies GithubRepoDiscoveryResult +} diff --git a/ee/apps/den-api/src/routes/org/plugin-system/index.ts b/ee/apps/den-api/src/routes/org/plugin-system/index.ts index cbe3ecc2..326d8527 100644 --- a/ee/apps/den-api/src/routes/org/plugin-system/index.ts +++ b/ee/apps/den-api/src/routes/org/plugin-system/index.ts @@ -1,5 +1,7 @@ export * from "./contracts.js" export * from "./access.js" +export * from "./github-app.js" +export * from "./github-discovery.js" export * from "./routes.js" export * from "./schemas.js" export * from "./store.js" diff --git a/ee/apps/den-api/src/routes/org/plugin-system/routes.ts b/ee/apps/den-api/src/routes/org/plugin-system/routes.ts index 86db3d78..7660ffb3 100644 --- a/ee/apps/den-api/src/routes/org/plugin-system/routes.ts +++ b/ee/apps/den-api/src/routes/org/plugin-system/routes.ts @@ -24,11 +24,20 @@ import { connectorAccountDisconnectSchema, connectorAccountListQuerySchema, connectorAccountListResponseSchema, + connectorAccountDisconnectResponseSchema, connectorAccountMutationResponseSchema, + connectorInstanceAutoImportSchema, + connectorInstanceConfigurationResponseSchema, + connectorInstanceRemoveResponseSchema, connectorAccountParamsSchema, connectorAccountRepositoryParamsSchema, connectorInstanceAccessGrantParamsSchema, connectorInstanceCreateSchema, + githubConnectorDiscoveryResponseSchema, + githubDiscoveryApplyResponseSchema, + githubDiscoveryApplySchema, + githubDiscoveryTreeQuerySchema, + githubDiscoveryTreeResponseSchema, connectorInstanceDetailResponseSchema, connectorInstanceListQuerySchema, connectorInstanceListResponseSchema, @@ -54,6 +63,10 @@ import { connectorTargetParamsSchema, connectorTargetUpdateSchema, githubConnectorAccountCreateSchema, + githubInstallCompleteResponseSchema, + githubInstallCompleteSchema, + githubInstallStartResponseSchema, + githubInstallStartSchema, githubRepositoryListQuerySchema, githubRepositoryListResponseSchema, githubSetupResponseSchema, @@ -68,6 +81,7 @@ import { marketplaceMutationResponseSchema, marketplaceParamsSchema, marketplacePluginListResponseSchema, + marketplaceResolvedResponseSchema, marketplacePluginMutationResponseSchema, marketplacePluginParamsSchema, marketplacePluginWriteSchema, @@ -112,6 +126,7 @@ import { getConnectorTargetDetail, getLatestConfigObjectVersion, getMarketplaceDetail, + getMarketplaceResolved, getPluginDetail, githubSetup, listConfigObjectPlugins, @@ -129,6 +144,13 @@ import { listPlugins, listResourceAccess, attachPluginToMarketplace, + completeGithubConnectorInstall, + applyGithubConnectorDiscovery, + getConnectorInstanceConfiguration, + getGithubConnectorDiscovery, + getGithubConnectorDiscoveryTree, + removeConnectorInstance, + setConnectorInstanceAutoImport, queueConnectorTargetResync, removeConfigObjectFromPlugin, removePluginFromMarketplace, @@ -138,6 +160,7 @@ import { setConnectorInstanceLifecycle, setMarketplaceLifecycle, setPluginLifecycle, + startGithubConnectorInstall, updateConnectorInstance, updateConnectorMapping, updateConnectorTarget, @@ -196,6 +219,62 @@ function withPluginArchOrgContext(app: Hono, method: "delete" | "get" | "pa } export function registerPluginArchRoutes(app: Hono) { + withPluginArchOrgContext( + app, + "post", + pluginArchRoutePaths.githubInstallStart, + jsonValidator(githubInstallStartSchema), + describeRoute({ + tags: ["GitHub"], + summary: "Start GitHub install", + description: "Builds a GitHub App install redirect URL for the current organization.", + responses: { + 200: jsonResponse("GitHub install redirect returned successfully.", githubInstallStartResponseSchema), + 400: jsonResponse("The GitHub install request was invalid.", invalidRequestSchema), + 401: jsonResponse("The caller must be signed in to connect GitHub.", unauthorizedSchema), + 403: jsonResponse("The caller lacks permission to connect GitHub.", forbiddenSchema), + }, + }), + async (c: OrgContext) => { + try { + const context = actorContext(c) + await requirePluginArchCapability(context, "connector_account.create") + const body = validJson(c) + return c.json({ ok: true, item: await startGithubConnectorInstall({ context, returnPath: body.returnPath }) }) + } catch (error) { + return routeErrorResponse(c, error) + } + }, + ) + + withPluginArchOrgContext( + app, + "post", + pluginArchRoutePaths.githubInstallComplete, + jsonValidator(githubInstallCompleteSchema), + describeRoute({ + tags: ["GitHub"], + summary: "Complete GitHub install", + description: "Completes a GitHub App installation for the current organization and returns visible repositories.", + responses: { + 200: jsonResponse("GitHub installation completed successfully.", githubInstallCompleteResponseSchema), + 400: jsonResponse("The GitHub install completion request was invalid.", invalidRequestSchema), + 401: jsonResponse("The caller must be signed in to complete GitHub connection.", unauthorizedSchema), + 403: jsonResponse("The caller lacks permission to complete GitHub connection.", forbiddenSchema), + }, + }), + async (c: OrgContext) => { + try { + const context = actorContext(c) + await requirePluginArchCapability(context, "connector_account.create") + const body = validJson(c) + return c.json({ ok: true, item: await completeGithubConnectorInstall({ context, installationId: body.installationId, state: body.state }) }) + } catch (error) { + return routeErrorResponse(c, error) + } + }, + ) + withPluginArchOrgContext( app, "get", @@ -958,6 +1037,28 @@ export function registerPluginArchRoutes { + try { + const params = validParam(c) + return c.json({ ok: true, item: await getMarketplaceResolved({ context: actorContext(c), marketplaceId: params.marketplaceId }) }) + } catch (error) { + return routeErrorResponse(c, error) + } + }) + withPluginArchOrgContext(app, "post", pluginArchRoutePaths.marketplacePlugins, paramValidator(marketplaceParamsSchema), jsonValidator(marketplacePluginWriteSchema), @@ -1146,9 +1247,9 @@ export function registerPluginArchRoutes { + try { + return c.json({ ok: true, item: await getConnectorInstanceConfiguration({ connectorInstanceId: validParam(c).connectorInstanceId, context: actorContext(c) }) }) + } catch (error) { + return routeErrorResponse(c, error) + } + }) + + withPluginArchOrgContext(app, "post", pluginArchRoutePaths.connectorInstanceRemove, + paramValidator(connectorInstanceParamsSchema), + describeRoute({ + tags: ["Connectors"], + summary: "Remove connector instance", + description: "Removes a connector instance and deletes the plugins, mappings, config objects, and bindings associated with it.", + responses: { + 200: jsonResponse("Connector instance removed and cleaned up successfully.", connectorInstanceRemoveResponseSchema), + 400: jsonResponse("The connector instance path parameters were invalid.", invalidRequestSchema), + 401: jsonResponse("The caller must be signed in to remove connector instances.", unauthorizedSchema), + 403: jsonResponse("The caller lacks permission to remove this connector instance.", forbiddenSchema), + 404: jsonResponse("The connector instance could not be found.", notFoundSchema), + }, + }), + async (c: OrgContext) => { + try { + const context = actorContext(c) + return c.json({ ok: true, item: await removeConnectorInstance({ connectorInstanceId: validParam(c).connectorInstanceId, context }) }) + } catch (error) { + return routeErrorResponse(c, error) + } + }) + + withPluginArchOrgContext(app, "post", pluginArchRoutePaths.connectorInstanceAutoImport, + paramValidator(connectorInstanceParamsSchema), + jsonValidator(connectorInstanceAutoImportSchema), + describeRoute({ + tags: ["Connectors"], + summary: "Set connector instance auto-import", + description: "Enables or disables auto-import of new plugins on future push webhooks for a connector instance.", + responses: { + 200: jsonResponse("Connector instance auto-import updated successfully.", connectorInstanceConfigurationResponseSchema), + 400: jsonResponse("The auto-import request was invalid.", invalidRequestSchema), + 401: jsonResponse("The caller must be signed in to configure connector instances.", unauthorizedSchema), + 403: jsonResponse("The caller lacks permission to configure this connector instance.", forbiddenSchema), + 404: jsonResponse("The connector instance could not be found.", notFoundSchema), + }, + }), + async (c: OrgContext) => { + try { + const context = actorContext(c) + const params = validParam(c) + const body = validJson(c) + return c.json({ ok: true, item: await setConnectorInstanceAutoImport({ autoImportNewPlugins: Boolean(body.autoImportNewPlugins), connectorInstanceId: params.connectorInstanceId, context }) }) + } catch (error) { + return routeErrorResponse(c, error) + } + }) + + withPluginArchOrgContext(app, "get", pluginArchRoutePaths.connectorInstanceDiscovery, + paramValidator(connectorInstanceParamsSchema), + describeRoute({ + tags: ["GitHub"], + summary: "Get GitHub connector discovery", + description: "Analyzes a GitHub connector target and returns discovered plugin candidates.", + responses: { + 200: jsonResponse("GitHub connector discovery returned successfully.", githubConnectorDiscoveryResponseSchema), + 400: jsonResponse("The connector instance path parameters were invalid.", invalidRequestSchema), + 401: jsonResponse("The caller must be signed in to inspect GitHub discovery.", unauthorizedSchema), + 404: jsonResponse("The connector instance could not be found.", notFoundSchema), + }, + }), + async (c: OrgContext) => { + try { + return c.json({ ok: true, item: await getGithubConnectorDiscovery({ connectorInstanceId: validParam(c).connectorInstanceId, context: actorContext(c) }) }) + } catch (error) { + return routeErrorResponse(c, error) + } + }) + + withPluginArchOrgContext(app, "get", pluginArchRoutePaths.connectorInstanceDiscoveryTree, + paramValidator(connectorInstanceParamsSchema), + queryValidator(githubDiscoveryTreeQuerySchema), + describeRoute({ + tags: ["GitHub"], + summary: "List GitHub discovery tree entries", + description: "Pages through the normalized GitHub repository tree used during discovery.", + responses: { + 200: jsonResponse("GitHub discovery tree returned successfully.", githubDiscoveryTreeResponseSchema), + 400: jsonResponse("The discovery tree request was invalid.", invalidRequestSchema), + 401: jsonResponse("The caller must be signed in to inspect GitHub discovery tree entries.", unauthorizedSchema), + 404: jsonResponse("The connector instance could not be found.", notFoundSchema), + }, + }), + async (c: OrgContext) => { + try { + const params = validParam(c) + const query = validQuery(c) + return c.json(await getGithubConnectorDiscoveryTree({ connectorInstanceId: params.connectorInstanceId, context: actorContext(c), cursor: query.cursor, limit: query.limit, prefix: query.prefix })) + } catch (error) { + return routeErrorResponse(c, error) + } + }) + + withPluginArchOrgContext(app, "post", pluginArchRoutePaths.connectorInstanceDiscoveryApply, + paramValidator(connectorInstanceParamsSchema), + jsonValidator(githubDiscoveryApplySchema), + describeRoute({ + tags: ["GitHub"], + summary: "Apply GitHub discovery selection", + description: "Creates OpenWork plugins and connector mappings from selected discovery candidates.", + responses: { + 200: jsonResponse("GitHub discovery selection applied successfully.", githubDiscoveryApplyResponseSchema), + 400: jsonResponse("The discovery apply request was invalid.", invalidRequestSchema), + 401: jsonResponse("The caller must be signed in to apply discovery selections.", unauthorizedSchema), + 403: jsonResponse("The caller lacks permission to edit this connector instance.", forbiddenSchema), + 404: jsonResponse("The connector instance could not be found.", notFoundSchema), + }, + }), + async (c: OrgContext) => { + try { + const params = validParam(c) + const body = validJson(c) + const context = actorContext(c) + if (Array.isArray(body.selectedKeys) && body.selectedKeys.length > 0) { + await requirePluginArchCapability(context, "plugin.create") + } + return c.json({ ok: true, item: await applyGithubConnectorDiscovery({ autoImportNewPlugins: Boolean(body.autoImportNewPlugins), connectorInstanceId: params.connectorInstanceId, context, selectedKeys: body.selectedKeys }) }) + } catch (error) { + return routeErrorResponse(c, error) + } + }) + withPluginArchOrgContext(app, "get", pluginArchRoutePaths.connectorInstanceAccess, paramValidator(connectorInstanceParamsSchema), describeRoute({ @@ -1713,6 +1958,6 @@ export function registerPluginArchRoutes { const body = validJson(c) - return c.json({ ok: true, item: await validateGithubTarget({ branch: body.branch, ref: body.ref, repositoryFullName: body.repositoryFullName }) }) + return c.json({ ok: true, item: await validateGithubTarget({ branch: body.branch, installationId: body.installationId, ref: body.ref, repositoryFullName: body.repositoryFullName, repositoryId: body.repositoryId }) }) }) } diff --git a/ee/apps/den-api/src/routes/org/plugin-system/schemas.ts b/ee/apps/den-api/src/routes/org/plugin-system/schemas.ts index cb6f4cac..d87e7e5b 100644 --- a/ee/apps/den-api/src/routes/org/plugin-system/schemas.ts +++ b/ee/apps/den-api/src/routes/org/plugin-system/schemas.ts @@ -344,6 +344,26 @@ export const githubConnectorSetupSchema = z.object({ mappings: z.array(connectorMappingCreateSchema).max(100).default([]), }) +export const githubInstallStartSchema = z.object({ + returnPath: z.string().trim().min(1).max(1024), +}) + +export const githubInstallCompleteSchema = z.object({ + installationId: z.number().int().positive(), + state: z.string().trim().min(1).max(4096), +}) + +export const githubDiscoveryApplySchema = z.object({ + autoImportNewPlugins: z.boolean().default(false), + selectedKeys: z.array(z.string().trim().min(1).max(255)).max(200), +}) + +export const githubDiscoveryTreeQuerySchema = z.object({ + cursor: z.string().trim().min(1).max(255).optional(), + limit: z.coerce.number().int().positive().max(500).optional(), + prefix: z.string().trim().min(1).max(1024).optional(), +}) + export const githubConnectorAccountCreateSchema = z.object({ installationId: z.number().int().positive(), accountLogin: z.string().trim().min(1).max(255), @@ -427,6 +447,10 @@ export const pluginSchema = z.object({ updatedAt: z.string().datetime({ offset: true }), deletedAt: nullableTimestampSchema, memberCount: z.number().int().nonnegative().optional(), + marketplaces: z.array(z.object({ + id: marketplaceIdSchema, + name: z.string().trim().min(1).max(255), + })).optional(), }).meta({ ref: "PluginArchPlugin" }) export const marketplacePluginSchema = z.object({ @@ -461,6 +485,7 @@ export const connectorAccountSchema = z.object({ externalAccountRef: z.string().trim().min(1).max(255).nullable(), displayName: z.string().trim().min(1).max(255), status: connectorAccountStatusSchema, + createdByName: z.string().trim().min(1).max(255).nullable().optional(), createdByOrgMembershipId: memberIdSchema, createdAt: z.string().datetime({ offset: true }), updatedAt: z.string().datetime({ offset: true }), @@ -672,6 +697,23 @@ export const pluginMembershipMutationResponseSchema = pluginArchMutationResponse export const marketplaceListResponseSchema = pluginArchListResponseSchema("PluginArchMarketplaceListResponse", marketplaceSchema) export const marketplaceDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchMarketplaceDetailResponse", marketplaceSchema) export const marketplaceMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchMarketplaceMutationResponse", marketplaceSchema) + +export const marketplaceResolvedResponseSchema = pluginArchMutationResponseSchema( + "PluginArchMarketplaceResolvedResponse", + z.object({ + marketplace: marketplaceSchema, + plugins: z.array(pluginSchema.extend({ + componentCounts: z.record(z.string(), z.number().int().nonnegative()).default({}), + })), + source: z.object({ + connectorAccountId: connectorAccountIdSchema, + connectorInstanceId: connectorInstanceIdSchema, + accountLogin: z.string().trim().min(1).nullable(), + repositoryFullName: z.string().trim().min(1), + branch: z.string().trim().min(1).nullable(), + }).nullable(), + }), +) export const marketplacePluginListResponseSchema = pluginArchListResponseSchema("PluginArchMarketplacePluginListResponse", marketplacePluginSchema) export const marketplacePluginMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchMarketplacePluginMutationResponse", marketplacePluginSchema) export const accessGrantListResponseSchema = pluginArchListResponseSchema("PluginArchAccessGrantListResponse", accessGrantSchema) @@ -679,6 +721,42 @@ export const accessGrantMutationResponseSchema = pluginArchMutationResponseSchem export const connectorAccountListResponseSchema = pluginArchListResponseSchema("PluginArchConnectorAccountListResponse", connectorAccountSchema) export const connectorAccountDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchConnectorAccountDetailResponse", connectorAccountSchema) export const connectorAccountMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchConnectorAccountMutationResponse", connectorAccountSchema) +export const connectorAccountDisconnectResponseSchema = pluginArchMutationResponseSchema( + "PluginArchConnectorAccountDisconnectResponse", + z.object({ + deletedConfigObjectCount: z.number().int().nonnegative(), + deletedConnectorInstanceCount: z.number().int().nonnegative(), + deletedConnectorMappingCount: z.number().int().nonnegative(), + disconnectedAccountId: connectorAccountIdSchema, + reason: z.string().trim().nullable(), + }), +) +export const connectorInstanceConfiguredPluginSchema = pluginSchema.extend({ + componentCounts: z.record(z.string(), z.number().int().nonnegative()).default({}), + rootPath: z.string().nullable(), +}).meta({ ref: "PluginArchConnectorInstanceConfiguredPlugin" }) + +export const connectorInstanceConfigurationResponseSchema = pluginArchMutationResponseSchema( + "PluginArchConnectorInstanceConfigurationResponse", + z.object({ + autoImportNewPlugins: z.boolean(), + configuredPlugins: z.array(connectorInstanceConfiguredPluginSchema), + connectorInstance: connectorInstanceSchema, + importedConfigObjectCount: z.number().int().nonnegative(), + mappingCount: z.number().int().nonnegative(), + }), +) +export const connectorInstanceAutoImportSchema = z.object({ + autoImportNewPlugins: z.boolean(), +}) +export const connectorInstanceRemoveResponseSchema = pluginArchMutationResponseSchema( + "PluginArchConnectorInstanceRemoveResponse", + z.object({ + deletedConfigObjectCount: z.number().int().nonnegative(), + deletedConnectorMappingCount: z.number().int().nonnegative(), + removedConnectorInstanceId: connectorInstanceIdSchema, + }), +) export const connectorInstanceListResponseSchema = pluginArchListResponseSchema("PluginArchConnectorInstanceListResponse", connectorInstanceSchema) export const connectorInstanceDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchConnectorInstanceDetailResponse", connectorInstanceSchema) export const connectorInstanceMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchConnectorInstanceMutationResponse", connectorInstanceSchema) @@ -697,9 +775,95 @@ export const githubRepositorySchema = z.object({ id: z.number().int().positive(), fullName: z.string().trim().min(1), defaultBranch: z.string().trim().min(1).nullable(), + hasPluginManifest: z.boolean().optional(), + manifestKind: z.enum(["marketplace", "plugin"]).nullable().optional(), + marketplacePluginCount: z.number().int().nonnegative().nullable().optional(), private: z.boolean(), }).meta({ ref: "PluginArchGithubRepository" }) export const githubRepositoryListResponseSchema = pluginArchListResponseSchema("PluginArchGithubRepositoryListResponse", githubRepositorySchema) +export const githubDiscoveryStepSchema = z.object({ + id: z.enum(["read_repository_structure", "check_marketplace_manifest", "check_plugin_manifests", "prepare_discovered_plugins"]), + label: z.string().trim().min(1), + status: z.enum(["completed", "running", "warning"]), +}).meta({ ref: "PluginArchGithubDiscoveryStep" }) +export const githubDiscoveryTreeSummarySchema = z.object({ + scannedEntryCount: z.number().int().nonnegative(), + strategy: z.enum(["git-tree-recursive"]), + truncated: z.boolean(), +}).meta({ ref: "PluginArchGithubDiscoveryTreeSummary" }) +export const githubDiscoveredPluginSchema = z.object({ + key: z.string().trim().min(1), + sourceKind: z.enum(["marketplace_entry", "plugin_manifest", "standalone_claude", "folder_inference"]), + rootPath: z.string(), + displayName: z.string().trim().min(1), + description: nullableStringSchema, + selectedByDefault: z.boolean(), + supported: z.boolean(), + manifestPath: nullableStringSchema, + warnings: z.array(z.string().trim().min(1)), + componentKinds: z.array(z.enum(["skill", "command", "agent", "hook", "mcp_server", "lsp_server", "monitor", "settings"])), + componentPaths: z.object({ + agents: z.array(z.string().trim().min(1)), + commands: z.array(z.string().trim().min(1)), + hooks: z.array(z.string().trim().min(1)), + lspServers: z.array(z.string().trim().min(1)), + mcpServers: z.array(z.string().trim().min(1)), + monitors: z.array(z.string().trim().min(1)), + settings: z.array(z.string().trim().min(1)), + skills: z.array(z.string().trim().min(1)), + }), + metadata: jsonObjectSchema, +}).meta({ ref: "PluginArchGithubDiscoveredPlugin" }) +export const githubConnectorDiscoveryResponseSchema = pluginArchMutationResponseSchema( + "PluginArchGithubConnectorDiscoveryResponse", + z.object({ + autoImportNewPlugins: z.boolean(), + classification: z.enum(["claude_marketplace_repo", "claude_multi_plugin_repo", "claude_single_plugin_repo", "folder_inferred_repo", "unsupported"]), + connectorInstance: connectorInstanceSchema, + connectorTarget: connectorTargetSchema, + discoveredPlugins: z.array(githubDiscoveredPluginSchema), + repositoryFullName: z.string().trim().min(1), + sourceRevisionRef: z.string().trim().min(1), + steps: z.array(githubDiscoveryStepSchema), + treeSummary: githubDiscoveryTreeSummarySchema, + warnings: z.array(z.string().trim().min(1)), + }), +) +export const githubDiscoveryTreeEntrySchema = z.object({ + id: z.string().trim().min(1), + kind: z.enum(["blob", "tree"]), + path: z.string().trim().min(1), + sha: nullableStringSchema, + size: z.number().int().nonnegative().nullable(), +}).meta({ ref: "PluginArchGithubDiscoveryTreeEntry" }) +export const githubDiscoveryTreeResponseSchema = pluginArchListResponseSchema("PluginArchGithubDiscoveryTreeResponse", githubDiscoveryTreeEntrySchema) +export const githubDiscoveryApplyResponseSchema = pluginArchMutationResponseSchema( + "PluginArchGithubDiscoveryApplyResponse", + z.object({ + autoImportNewPlugins: z.boolean(), + createdMarketplace: marketplaceSchema.nullable().optional(), + connectorInstance: connectorInstanceSchema, + connectorTarget: connectorTargetSchema, + createdPlugins: z.array(pluginSchema), + createdMappings: z.array(connectorMappingSchema), + materializedConfigObjects: z.array(configObjectSchema), + sourceRevisionRef: z.string().trim().min(1), + }), +) +export const githubInstallStartResponseSchema = pluginArchMutationResponseSchema( + "PluginArchGithubInstallStartResponse", + z.object({ + redirectUrl: z.string().url(), + state: z.string().trim().min(1), + }), +) +export const githubInstallCompleteResponseSchema = pluginArchMutationResponseSchema( + "PluginArchGithubInstallCompleteResponse", + z.object({ + connectorAccount: connectorAccountSchema, + repositories: z.array(githubRepositorySchema), + }), +) export const githubSetupResponseSchema = pluginArchMutationResponseSchema( "PluginArchGithubSetupResponse", z.object({ diff --git a/ee/apps/den-api/src/routes/org/plugin-system/store.ts b/ee/apps/den-api/src/routes/org/plugin-system/store.ts index 6ddc1931..d6d26090 100644 --- a/ee/apps/den-api/src/routes/org/plugin-system/store.ts +++ b/ee/apps/den-api/src/routes/org/plugin-system/store.ts @@ -14,6 +14,8 @@ import { MarketplaceAccessGrantTable, MarketplacePluginTable, MarketplaceTable, + MemberTable, + OrganizationTable, PluginAccessGrantTable, PluginConfigObjectTable, PluginTable, @@ -21,7 +23,29 @@ import { import { createDenTypeId } from "@openwork-ee/utils/typeid" import type { PluginArchActorContext, PluginArchResourceKind, PluginArchRole } from "./access.js" import { requirePluginArchResourceRole, resolvePluginArchResourceRole } from "./access.js" +import { + buildGithubAppInstallUrl, + createGithubInstallStateToken, + GithubConnectorConfigError, + GithubConnectorRequestError, + getGithubAppSummary, + getGithubConnectorAppConfig, + getGithubRepositoryTextFile, + getGithubRepositoryTree, + getGithubInstallationSummary, + listGithubInstallationRepositories, + validateGithubInstallationTarget, + verifyGithubInstallStateToken, +} from "./github-app.js" +import { + buildGithubRepoDiscovery, + type GithubDiscoveredPlugin, + type GithubDiscoveryClassification, + type GithubDiscoveryTreeEntry, +} from "./github-discovery.js" import { db } from "../../../db.js" +import { env } from "../../../env.js" +import { roleIncludesOwner } from "../../../orgs.js" type OrganizationId = PluginArchActorContext["organizationContext"]["organization"]["id"] type MemberId = PluginArchActorContext["organizationContext"]["currentMember"]["id"] @@ -57,12 +81,26 @@ type ConnectorInstanceId = ConnectorInstanceRow["id"] type ConnectorTargetId = ConnectorTargetRow["id"] type ConnectorMappingId = ConnectorMappingRow["id"] type ConnectorSyncEventId = ConnectorSyncEventRow["id"] +type MemberRow = typeof MemberTable.$inferSelect +type OrganizationRow = typeof OrganizationTable.$inferSelect type CursorPage = { items: TItem[] nextCursor: string | null } +type GithubConnectorDiscoveryStep = { + id: "read_repository_structure" | "check_marketplace_manifest" | "check_plugin_manifests" | "prepare_discovered_plugins" + label: string + status: "completed" | "running" | "warning" +} + +type GithubConnectorDiscoveryTreeSummary = { + scannedEntryCount: number + strategy: "git-tree-recursive" + truncated: boolean +} + type ConfigObjectInput = { metadata?: Record normalizedPayloadJson?: Record @@ -81,7 +119,10 @@ type AccessGrantWrite = { type RepositorySummary = { defaultBranch: string | null fullName: string + hasPluginManifest?: boolean id: number + manifestKind?: "marketplace" | "plugin" | null + marketplacePluginCount?: number | null private: boolean } @@ -255,13 +296,19 @@ function serializeConfigObject(row: ConfigObjectRow, latestVersion: ConfigObject } } -function serializePlugin(row: PluginRow, memberCount?: number) { +type PluginMarketplaceSummary = { + id: string + name: string +} + +function serializePlugin(row: PluginRow, memberCount?: number, marketplaces: PluginMarketplaceSummary[] = []) { return { createdAt: row.createdAt.toISOString(), createdByOrgMembershipId: row.createdByOrgMembershipId, deletedAt: row.deletedAt ? row.deletedAt.toISOString() : null, description: row.description, id: row.id, + marketplaces, memberCount, name: row.name, organizationId: row.organizationId, @@ -325,10 +372,11 @@ function serializeAccessGrant(row: AccessGrantRow) { } } -function serializeConnectorAccount(row: ConnectorAccountRow) { +function serializeConnectorAccount(row: ConnectorAccountRow, creatorName: string | null = null) { return { connectorType: row.connectorType, createdAt: row.createdAt.toISOString(), + createdByName: creatorName, createdByOrgMembershipId: row.createdByOrgMembershipId, displayName: row.displayName, externalAccountRef: row.externalAccountRef, @@ -341,6 +389,12 @@ function serializeConnectorAccount(row: ConnectorAccountRow) { } } +function resolveCreatorName(context: PluginArchActorContext, memberId: string) { + const member = context.organizationContext.members.find((entry) => entry.id === memberId) + if (!member) return null + return member.user.name?.trim() || member.user.email || null +} + function serializeConnectorInstance(row: ConnectorInstanceRow) { return { connectorAccountId: row.connectorAccountId, @@ -1118,6 +1172,35 @@ export async function deleteResourceAccessGrant(input: { context: PluginArchActo return removeGrant(input) } +async function collectPluginMarketplaces(organizationId: PluginRow["organizationId"], pluginIds: PluginId[]): Promise> { + const byPlugin = new Map() + if (pluginIds.length === 0) { + return byPlugin + } + + const rows = await db + .select({ + marketplaceId: MarketplaceTable.id, + marketplaceName: MarketplaceTable.name, + pluginId: MarketplacePluginTable.pluginId, + }) + .from(MarketplacePluginTable) + .innerJoin(MarketplaceTable, eq(MarketplacePluginTable.marketplaceId, MarketplaceTable.id)) + .where(and( + eq(MarketplaceTable.organizationId, organizationId), + isNull(MarketplacePluginTable.removedAt), + isNull(MarketplaceTable.deletedAt), + inArray(MarketplacePluginTable.pluginId, pluginIds), + )) + + for (const row of rows) { + const existing = byPlugin.get(row.pluginId) ?? [] + existing.push({ id: row.marketplaceId, name: row.marketplaceName }) + byPlugin.set(row.pluginId, existing) + } + return byPlugin +} + export async function listPlugins(input: { context: PluginArchActorContext; cursor?: string; limit?: number; q?: string; status?: PluginRow["status"] }) { const rows = await db .select() @@ -1135,6 +1218,11 @@ export async function listPlugins(input: { context: PluginArchActorContext; curs return accumulator }, new Map()) + const marketplaceMembers = await collectPluginMarketplaces( + input.context.organizationContext.organization.id, + rows.map((row) => row.id), + ) + const visible: ReturnType[] = [] for (const row of rows) { const role = await resolvePluginArchResourceRole({ context: input.context, resourceId: row.id, resourceKind: "plugin" }) @@ -1144,7 +1232,7 @@ export async function listPlugins(input: { context: PluginArchActorContext; curs const haystack = `${row.name}\n${row.description ?? ""}`.toLowerCase() if (!haystack.includes(input.q.toLowerCase())) continue } - visible.push(serializePlugin(row, counts.get(row.id) ?? 0)) + visible.push(serializePlugin(row, counts.get(row.id) ?? 0, marketplaceMembers.get(row.id) ?? [])) } return pageItems(visible, input.cursor, input.limit) @@ -1153,7 +1241,8 @@ export async function listPlugins(input: { context: PluginArchActorContext; curs export async function getPluginDetail(context: PluginArchActorContext, pluginId: PluginId) { const row = await ensureVisiblePlugin(context, pluginId) const memberships = await db.select({ id: PluginConfigObjectTable.id }).from(PluginConfigObjectTable).where(and(eq(PluginConfigObjectTable.pluginId, row.id), isNull(PluginConfigObjectTable.removedAt))) - return serializePlugin(row, memberships.length) + const marketplaceMembers = await collectPluginMarketplaces(context.organizationContext.organization.id, [row.id]) + return serializePlugin(row, memberships.length, marketplaceMembers.get(row.id) ?? []) } export async function createPlugin(input: { context: PluginArchActorContext; description?: string | null; name: string }) { @@ -1352,6 +1441,121 @@ export async function listMarketplaceMemberships(input: { context: PluginArchAct return { items: memberships.map((membership) => serializeMarketplaceMembership(membership, byId.get(membership.pluginId))), nextCursor: null } } +export type MarketplaceResolvedSource = { + connectorAccountId: string + connectorInstanceId: string + accountLogin: string | null + repositoryFullName: string + branch: string | null +} | null + +export async function getMarketplaceResolved(input: { context: PluginArchActorContext; marketplaceId: MarketplaceId }) { + const marketplaceRow = await ensureVisibleMarketplace(input.context, input.marketplaceId) + const organizationId = input.context.organizationContext.organization.id + + const memberships = await db + .select() + .from(MarketplacePluginTable) + .where(and(eq(MarketplacePluginTable.marketplaceId, marketplaceRow.id), isNull(MarketplacePluginTable.removedAt))) + .orderBy(desc(MarketplacePluginTable.createdAt)) + + const pluginIds = memberships.map((membership) => membership.pluginId) + const pluginRows = pluginIds.length === 0 + ? [] + : await db.select().from(PluginTable).where(inArray(PluginTable.id, pluginIds)) + + const activePluginMemberships = pluginIds.length === 0 + ? [] + : await db + .select({ pluginId: PluginConfigObjectTable.pluginId, configObjectId: PluginConfigObjectTable.configObjectId }) + .from(PluginConfigObjectTable) + .where(and(inArray(PluginConfigObjectTable.pluginId, pluginIds), isNull(PluginConfigObjectTable.removedAt))) + const memberCounts = new Map() + for (const entry of activePluginMemberships) { + memberCounts.set(entry.pluginId, (memberCounts.get(entry.pluginId) ?? 0) + 1) + } + + const configObjectIds = [...new Set(activePluginMemberships.map((entry) => entry.configObjectId))] + const configObjectTypeById = new Map() + if (configObjectIds.length > 0) { + const rows = await db + .select({ id: ConfigObjectTable.id, objectType: ConfigObjectTable.objectType }) + .from(ConfigObjectTable) + .where(inArray(ConfigObjectTable.id, configObjectIds)) + for (const row of rows) { + configObjectTypeById.set(row.id, row.objectType) + } + } + + const componentCountsByPlugin = new Map>() + for (const entry of activePluginMemberships) { + const objectType = configObjectTypeById.get(entry.configObjectId) + if (!objectType) continue + let counts = componentCountsByPlugin.get(entry.pluginId) + if (!counts) { + counts = new Map() + componentCountsByPlugin.set(entry.pluginId, counts) + } + counts.set(objectType, (counts.get(objectType) ?? 0) + 1) + } + + const plugins = pluginRows.map((row) => ({ + ...serializePlugin(row, memberCounts.get(row.id) ?? 0), + componentCounts: Object.fromEntries(componentCountsByPlugin.get(row.id) ?? new Map()), + })) + + let source: MarketplaceResolvedSource = null + if (pluginIds.length > 0) { + const mappingRows = await db + .selectDistinct({ connectorInstanceId: ConnectorMappingTable.connectorInstanceId }) + .from(ConnectorMappingTable) + .where(and( + eq(ConnectorMappingTable.organizationId, organizationId), + inArray(ConnectorMappingTable.pluginId, pluginIds), + )) + const connectorInstanceIds = mappingRows.map((entry) => entry.connectorInstanceId) + if (connectorInstanceIds.length === 1) { + const [instance] = await db + .select() + .from(ConnectorInstanceTable) + .where(eq(ConnectorInstanceTable.id, connectorInstanceIds[0])) + .limit(1) + if (instance) { + const [account] = await db + .select() + .from(ConnectorAccountTable) + .where(eq(ConnectorAccountTable.id, instance.connectorAccountId)) + .limit(1) + const [target] = await db + .select() + .from(ConnectorTargetTable) + .where(eq(ConnectorTargetTable.connectorInstanceId, instance.id)) + .orderBy(asc(ConnectorTargetTable.createdAt), asc(ConnectorTargetTable.id)) + .limit(1) + const targetConfig = target?.targetConfigJson && typeof target.targetConfigJson === "object" + ? target.targetConfigJson as Record + : {} + const repositoryFullName = typeof targetConfig.repositoryFullName === "string" + ? targetConfig.repositoryFullName + : instance.remoteId ?? "" + source = { + connectorAccountId: instance.connectorAccountId, + connectorInstanceId: instance.id, + accountLogin: account?.externalAccountRef ?? (account?.metadataJson && typeof account.metadataJson === "object" ? (account.metadataJson as Record).accountLogin as string ?? null : null), + repositoryFullName, + branch: typeof targetConfig.branch === "string" ? targetConfig.branch : target?.externalTargetRef ?? null, + } + } + } + } + + return { + marketplace: serializeMarketplace(marketplaceRow, plugins.length), + plugins, + source, + } +} + export async function attachPluginToMarketplace(input: { context: PluginArchActorContext; marketplaceId: MarketplaceId; membershipSource?: MarketplaceMembershipRow["membershipSource"]; pluginId: PluginId }) { await ensureVisiblePlugin(input.context, input.pluginId) await ensureEditableMarketplace(input.context, input.marketplaceId) @@ -1408,7 +1612,7 @@ export async function listConnectorAccounts(input: { context: PluginArchActorCon .filter((row) => !input.connectorType || row.connectorType === input.connectorType) .filter((row) => !input.status || row.status === input.status) .filter((row) => !input.q || `${row.displayName}\n${row.remoteId}\n${row.externalAccountRef ?? ""}`.toLowerCase().includes(input.q.toLowerCase())) - .map((row) => serializeConnectorAccount(row)) + .map((row) => serializeConnectorAccount(row, resolveCreatorName(input.context, row.createdByOrgMembershipId))) return pageItems(filtered, input.cursor, input.limit) } @@ -1437,21 +1641,108 @@ export async function getConnectorAccountDetail(context: PluginArchActorContext, if (!row) { throw new PluginArchRouteFailure(404, "connector_account_not_found", "Connector account not found.") } - return serializeConnectorAccount(row) + return serializeConnectorAccount(row, resolveCreatorName(context, row.createdByOrgMembershipId)) } export async function disconnectConnectorAccount(input: { connectorAccountId: ConnectorAccountId; context: PluginArchActorContext; reason?: string }) { - const row = await getConnectorAccountRow(input.context.organizationContext.organization.id, input.connectorAccountId) + const organizationId = input.context.organizationContext.organization.id + const row = await getConnectorAccountRow(organizationId, input.connectorAccountId) if (!row) { throw new PluginArchRouteFailure(404, "connector_account_not_found", "Connector account not found.") } - const metadata = row.metadataJson ?? {} - await db.update(ConnectorAccountTable).set({ - metadataJson: input.reason ? { ...metadata, disconnectReason: input.reason } : metadata, - status: "disconnected", - updatedAt: new Date(), - }).where(eq(ConnectorAccountTable.id, row.id)) - return getConnectorAccountDetail(input.context, row.id) + + const instances = await db + .select({ id: ConnectorInstanceTable.id }) + .from(ConnectorInstanceTable) + .where(and( + eq(ConnectorInstanceTable.organizationId, organizationId), + eq(ConnectorInstanceTable.connectorAccountId, row.id), + )) + const instanceIds = instances.map((entry) => entry.id) + + const mappingRows = instanceIds.length === 0 + ? [] + : await db + .select({ id: ConnectorMappingTable.id, pluginId: ConnectorMappingTable.pluginId }) + .from(ConnectorMappingTable) + .where(inArray(ConnectorMappingTable.connectorInstanceId, instanceIds)) + const mappingIds = mappingRows.map((entry) => entry.id) + const connectorPluginIds = [...new Set(mappingRows.map((entry) => entry.pluginId).filter((value): value is PluginId => Boolean(value)))] + + const configObjectRows = instanceIds.length === 0 + ? [] + : await db + .select({ id: ConfigObjectTable.id }) + .from(ConfigObjectTable) + .where(inArray(ConfigObjectTable.connectorInstanceId, instanceIds)) + const configObjectIds = configObjectRows.map((entry) => entry.id) + + await db.transaction(async (tx) => { + if (instanceIds.length > 0) { + await tx.delete(ConnectorSourceTombstoneTable).where(inArray(ConnectorSourceTombstoneTable.connectorInstanceId, instanceIds)) + await tx.delete(ConnectorSourceBindingTable).where(inArray(ConnectorSourceBindingTable.connectorInstanceId, instanceIds)) + await tx.delete(ConnectorSyncEventTable).where(inArray(ConnectorSyncEventTable.connectorInstanceId, instanceIds)) + } + + if (configObjectIds.length > 0) { + await tx.delete(PluginConfigObjectTable).where(inArray(PluginConfigObjectTable.configObjectId, configObjectIds)) + await tx.delete(ConfigObjectAccessGrantTable).where(inArray(ConfigObjectAccessGrantTable.configObjectId, configObjectIds)) + await tx.delete(ConfigObjectVersionTable).where(inArray(ConfigObjectVersionTable.configObjectId, configObjectIds)) + await tx.delete(ConfigObjectTable).where(inArray(ConfigObjectTable.id, configObjectIds)) + } + + if (mappingIds.length > 0) { + await tx.delete(PluginConfigObjectTable).where(inArray(PluginConfigObjectTable.connectorMappingId, mappingIds)) + await tx.delete(ConnectorMappingTable).where(inArray(ConnectorMappingTable.id, mappingIds)) + } + + if (instanceIds.length > 0) { + await tx.delete(ConnectorTargetTable).where(inArray(ConnectorTargetTable.connectorInstanceId, instanceIds)) + await tx.delete(ConnectorInstanceAccessGrantTable).where(inArray(ConnectorInstanceAccessGrantTable.connectorInstanceId, instanceIds)) + await tx.delete(ConnectorInstanceTable).where(inArray(ConnectorInstanceTable.id, instanceIds)) + } + + if (connectorPluginIds.length > 0) { + const remainingMemberships = await tx + .select({ pluginId: PluginConfigObjectTable.pluginId }) + .from(PluginConfigObjectTable) + .where(inArray(PluginConfigObjectTable.pluginId, connectorPluginIds)) + const pluginsWithOtherContent = new Set(remainingMemberships.map((entry) => entry.pluginId)) + const pluginIdsToDelete = connectorPluginIds.filter((pluginId) => !pluginsWithOtherContent.has(pluginId)) + + if (pluginIdsToDelete.length > 0) { + await tx.delete(MarketplacePluginTable).where(inArray(MarketplacePluginTable.pluginId, pluginIdsToDelete)) + await tx.delete(PluginAccessGrantTable).where(inArray(PluginAccessGrantTable.pluginId, pluginIdsToDelete)) + await tx.delete(PluginTable).where(inArray(PluginTable.id, pluginIdsToDelete)) + } + + const marketplaceRows = await tx + .select({ marketplaceId: MarketplacePluginTable.marketplaceId }) + .from(MarketplacePluginTable) + const marketplacesWithMembers = new Set(marketplaceRows.map((entry) => entry.marketplaceId)) + const orphanMarketplaces = await tx + .select({ id: MarketplaceTable.id }) + .from(MarketplaceTable) + .where(eq(MarketplaceTable.organizationId, organizationId)) + const orphanIds = orphanMarketplaces + .map((entry) => entry.id) + .filter((marketplaceId) => !marketplacesWithMembers.has(marketplaceId)) + if (orphanIds.length > 0) { + await tx.delete(MarketplaceAccessGrantTable).where(inArray(MarketplaceAccessGrantTable.marketplaceId, orphanIds)) + await tx.delete(MarketplaceTable).where(inArray(MarketplaceTable.id, orphanIds)) + } + } + + await tx.delete(ConnectorAccountTable).where(eq(ConnectorAccountTable.id, row.id)) + }) + + return { + deletedConfigObjectCount: configObjectIds.length, + deletedConnectorInstanceCount: instanceIds.length, + deletedConnectorMappingCount: mappingIds.length, + disconnectedAccountId: row.id, + reason: input.reason ?? null, + } } export async function listConnectorInstances(input: { connectorAccountId?: ConnectorAccountId; context: PluginArchActorContext; cursor?: string; limit?: number; pluginId?: PluginId; q?: string; status?: ConnectorInstanceRow["status"] }) { @@ -1545,6 +1836,205 @@ export async function setConnectorInstanceLifecycle(input: { action: "archive" | return getConnectorInstanceDetail(input.context, row.id) } +function commonSelectorRootPath(selectors: string[]): string | null { + const normalized = selectors + .map((selector) => { + let path = selector.trim().replace(/^\/+/, "").replace(/\/+$/, "") + if (path.endsWith("/**")) { + path = path.slice(0, -3) + } + const knownLeafSegments = ["skills", "commands", "agents", "hooks", "monitors", "mcp", ".mcp.json", ".lsp.json", "settings.json", "hooks.json"] + for (const leaf of knownLeafSegments) { + if (path === leaf) return "" + if (path.endsWith(`/${leaf}`)) return path.slice(0, -(leaf.length + 1)) + } + return path + }) + .filter((path): path is string => path !== null) + + if (normalized.length === 0) return null + if (normalized.every((path) => path === normalized[0])) { + return normalized[0] + } + + const parts = normalized[0].split("/") + for (let index = parts.length; index > 0; index -= 1) { + const candidate = parts.slice(0, index).join("/") + if (normalized.every((path) => path === candidate || path.startsWith(`${candidate}/`))) { + return candidate + } + } + return "" +} + +export async function getConnectorInstanceConfiguration(input: { connectorInstanceId: ConnectorInstanceId; context: PluginArchActorContext }) { + const instance = await ensureVisibleConnectorInstance(input.context, input.connectorInstanceId) + const mappings = await db + .select() + .from(ConnectorMappingTable) + .where(eq(ConnectorMappingTable.connectorInstanceId, instance.id)) + .orderBy(desc(ConnectorMappingTable.createdAt), desc(ConnectorMappingTable.id)) + + const pluginIds = [...new Set(mappings.map((row) => row.pluginId).filter((value): value is PluginId => Boolean(value)))] + const pluginRows = pluginIds.length === 0 + ? [] + : await db.select().from(PluginTable).where(inArray(PluginTable.id, pluginIds)) + const memberships = pluginIds.length === 0 + ? [] + : await db + .select({ pluginId: PluginConfigObjectTable.pluginId, configObjectId: PluginConfigObjectTable.configObjectId }) + .from(PluginConfigObjectTable) + .where(and(inArray(PluginConfigObjectTable.pluginId, pluginIds), isNull(PluginConfigObjectTable.removedAt))) + const configObjectIds = [...new Set(memberships.map((entry) => entry.configObjectId))] + const configObjectTypeById = new Map() + if (configObjectIds.length > 0) { + const rows = await db + .select({ id: ConfigObjectTable.id, objectType: ConfigObjectTable.objectType }) + .from(ConfigObjectTable) + .where(inArray(ConfigObjectTable.id, configObjectIds)) + for (const row of rows) { + configObjectTypeById.set(row.id, row.objectType) + } + } + + const pluginComponentCounts = new Map>() + const membershipCounts = new Map() + for (const membership of memberships) { + membershipCounts.set(membership.pluginId, (membershipCounts.get(membership.pluginId) ?? 0) + 1) + const objectType = configObjectTypeById.get(membership.configObjectId) + if (!objectType) continue + let counts = pluginComponentCounts.get(membership.pluginId) + if (!counts) { + counts = new Map() + pluginComponentCounts.set(membership.pluginId, counts) + } + counts.set(objectType, (counts.get(objectType) ?? 0) + 1) + } + + const pluginRootPaths = new Map() + for (const pluginId of pluginIds) { + const selectors = mappings + .filter((mapping) => mapping.pluginId === pluginId) + .map((mapping) => mapping.selector) + pluginRootPaths.set(pluginId, commonSelectorRootPath(selectors)) + } + + const configObjectRows = await db + .select({ id: ConfigObjectTable.id }) + .from(ConfigObjectTable) + .where(eq(ConfigObjectTable.connectorInstanceId, instance.id)) + + const instanceConfig = instance.instanceConfigJson && typeof instance.instanceConfigJson === "object" + ? instance.instanceConfigJson as Record + : {} + const savedAutoImport = instanceConfig.autoImportNewPlugins + + return { + autoImportNewPlugins: typeof savedAutoImport === "boolean" ? savedAutoImport : true, + configuredPlugins: pluginRows.map((row) => ({ + ...serializePlugin(row, membershipCounts.get(row.id) ?? 0), + componentCounts: Object.fromEntries(pluginComponentCounts.get(row.id) ?? new Map()), + rootPath: pluginRootPaths.get(row.id) ?? null, + })), + connectorInstance: serializeConnectorInstance(instance), + importedConfigObjectCount: configObjectRows.length, + mappingCount: mappings.length, + } +} + +export async function setConnectorInstanceAutoImport(input: { autoImportNewPlugins: boolean; connectorInstanceId: ConnectorInstanceId; context: PluginArchActorContext }) { + const instance = await ensureEditableConnectorInstance(input.context, input.connectorInstanceId) + const currentConfig = instance.instanceConfigJson && typeof instance.instanceConfigJson === "object" + ? instance.instanceConfigJson as Record + : {} + await db.update(ConnectorInstanceTable).set({ + instanceConfigJson: { + ...currentConfig, + autoImportNewPlugins: input.autoImportNewPlugins, + }, + updatedAt: new Date(), + }).where(eq(ConnectorInstanceTable.id, instance.id)) + + return getConnectorInstanceConfiguration({ connectorInstanceId: instance.id, context: input.context }) +} + +export async function removeConnectorInstance(input: { connectorInstanceId: ConnectorInstanceId; context: PluginArchActorContext }) { + const organizationId = input.context.organizationContext.organization.id + const instance = await ensureEditableConnectorInstance(input.context, input.connectorInstanceId) + + const mappingRows = await db + .select({ id: ConnectorMappingTable.id, pluginId: ConnectorMappingTable.pluginId }) + .from(ConnectorMappingTable) + .where(eq(ConnectorMappingTable.connectorInstanceId, instance.id)) + const mappingIds = mappingRows.map((entry) => entry.id) + const pluginIds = [...new Set(mappingRows.map((entry) => entry.pluginId).filter((value): value is PluginId => Boolean(value)))] + + const configObjectRows = await db + .select({ id: ConfigObjectTable.id }) + .from(ConfigObjectTable) + .where(eq(ConfigObjectTable.connectorInstanceId, instance.id)) + const configObjectIds = configObjectRows.map((entry) => entry.id) + + await db.transaction(async (tx) => { + await tx.delete(ConnectorSourceTombstoneTable).where(eq(ConnectorSourceTombstoneTable.connectorInstanceId, instance.id)) + await tx.delete(ConnectorSourceBindingTable).where(eq(ConnectorSourceBindingTable.connectorInstanceId, instance.id)) + await tx.delete(ConnectorSyncEventTable).where(eq(ConnectorSyncEventTable.connectorInstanceId, instance.id)) + + if (configObjectIds.length > 0) { + await tx.delete(PluginConfigObjectTable).where(inArray(PluginConfigObjectTable.configObjectId, configObjectIds)) + await tx.delete(ConfigObjectAccessGrantTable).where(inArray(ConfigObjectAccessGrantTable.configObjectId, configObjectIds)) + await tx.delete(ConfigObjectVersionTable).where(inArray(ConfigObjectVersionTable.configObjectId, configObjectIds)) + await tx.delete(ConfigObjectTable).where(inArray(ConfigObjectTable.id, configObjectIds)) + } + + if (mappingIds.length > 0) { + await tx.delete(PluginConfigObjectTable).where(inArray(PluginConfigObjectTable.connectorMappingId, mappingIds)) + await tx.delete(ConnectorMappingTable).where(inArray(ConnectorMappingTable.id, mappingIds)) + } + + await tx.delete(ConnectorTargetTable).where(eq(ConnectorTargetTable.connectorInstanceId, instance.id)) + await tx.delete(ConnectorInstanceAccessGrantTable).where(eq(ConnectorInstanceAccessGrantTable.connectorInstanceId, instance.id)) + await tx.delete(ConnectorInstanceTable).where(eq(ConnectorInstanceTable.id, instance.id)) + + if (pluginIds.length > 0) { + const remainingMemberships = await tx + .select({ pluginId: PluginConfigObjectTable.pluginId }) + .from(PluginConfigObjectTable) + .where(inArray(PluginConfigObjectTable.pluginId, pluginIds)) + const pluginsWithOtherContent = new Set(remainingMemberships.map((entry) => entry.pluginId)) + const pluginIdsToDelete = pluginIds.filter((pluginId) => !pluginsWithOtherContent.has(pluginId)) + + if (pluginIdsToDelete.length > 0) { + await tx.delete(MarketplacePluginTable).where(inArray(MarketplacePluginTable.pluginId, pluginIdsToDelete)) + await tx.delete(PluginAccessGrantTable).where(inArray(PluginAccessGrantTable.pluginId, pluginIdsToDelete)) + await tx.delete(PluginTable).where(inArray(PluginTable.id, pluginIdsToDelete)) + } + + const marketplaceMembershipRows = await tx + .select({ marketplaceId: MarketplacePluginTable.marketplaceId }) + .from(MarketplacePluginTable) + const marketplacesWithMembers = new Set(marketplaceMembershipRows.map((entry) => entry.marketplaceId)) + const orphanMarketplaces = await tx + .select({ id: MarketplaceTable.id }) + .from(MarketplaceTable) + .where(eq(MarketplaceTable.organizationId, organizationId)) + const orphanIds = orphanMarketplaces + .map((entry) => entry.id) + .filter((marketplaceId) => !marketplacesWithMembers.has(marketplaceId)) + if (orphanIds.length > 0) { + await tx.delete(MarketplaceAccessGrantTable).where(inArray(MarketplaceAccessGrantTable.marketplaceId, orphanIds)) + await tx.delete(MarketplaceTable).where(inArray(MarketplaceTable.id, orphanIds)) + } + } + }) + + return { + deletedConfigObjectCount: configObjectIds.length, + deletedConnectorMappingCount: mappingIds.length, + removedConnectorInstanceId: instance.id, + } +} + export async function listConnectorTargets(input: { connectorInstanceId: ConnectorInstanceId; context: PluginArchActorContext; cursor?: string; limit?: number; q?: string; targetKind?: ConnectorTargetRow["targetKind"] }) { await ensureVisibleConnectorInstance(input.context, input.connectorInstanceId) const rows = await db @@ -1725,24 +2215,734 @@ export async function retryConnectorSyncEvent(input: { connectorSyncEventId: Con return { id: row.id } } -function normalizeRepositories(value: unknown): RepositorySummary[] { - if (!Array.isArray(value)) return [] - return value.flatMap((entry) => { - if (!entry || typeof entry !== "object") return [] - const candidate = entry as Record - const id = typeof candidate.id === "number" ? candidate.id : Number(candidate.id) - const fullName = typeof candidate.fullName === "string" - ? candidate.fullName - : typeof candidate.repositoryFullName === "string" - ? candidate.repositoryFullName - : null - if (!Number.isFinite(id) || !fullName) return [] - return [{ - defaultBranch: typeof candidate.defaultBranch === "string" ? candidate.defaultBranch : null, - fullName, - id, - private: Boolean(candidate.private), - }] +function githubConnectorAppConfig() { + try { + return getGithubConnectorAppConfig(env.githubConnectorApp) + } catch (error) { + if (error instanceof GithubConnectorConfigError) { + throw new PluginArchRouteFailure(409, "github_connector_app_not_configured", error.message) + } + throw error + } +} + +export function consumeGithubInstallState(state: string) { + const parsed = verifyGithubInstallStateToken({ secret: env.betterAuthSecret, token: state }) + if (!parsed) { + throw new PluginArchRouteFailure(400, "invalid_github_install_state", "GitHub install state is invalid or expired.") + } + return parsed +} + +function wrapGithubConnectorError(error: unknown): never { + if (error instanceof PluginArchRouteFailure) { + throw error + } + + if (error instanceof GithubConnectorConfigError) { + throw new PluginArchRouteFailure(409, "github_connector_app_not_configured", error.message) + } + + if (error instanceof GithubConnectorRequestError) { + throw new PluginArchRouteFailure(409, "github_connector_request_failed", error.message) + } + + throw error +} + +function normalizeDiscoveryCursor(value: string | undefined) { + return value?.trim() || undefined +} + +function discoveryStep(status: GithubConnectorDiscoveryStep["status"], id: GithubConnectorDiscoveryStep["id"], label: string): GithubConnectorDiscoveryStep { + return { id, label, status } +} + +async function getGithubDiscoveryContext(input: { connectorInstanceId: ConnectorInstanceId; context: PluginArchActorContext }) { + const connectorInstance = await ensureVisibleConnectorInstance(input.context, input.connectorInstanceId) + if (connectorInstance.connectorType !== "github") { + throw new PluginArchRouteFailure(409, "github_connector_instance_required", "Connector instance is not a GitHub connector.") + } + + const connectorAccount = await getConnectorAccountRow(input.context.organizationContext.organization.id, connectorInstance.connectorAccountId) + if (!connectorAccount || connectorAccount.connectorType !== "github") { + throw new PluginArchRouteFailure(404, "connector_account_not_found", "GitHub connector account not found.") + } + + const targetRows = await db + .select() + .from(ConnectorTargetTable) + .where(eq(ConnectorTargetTable.connectorInstanceId, connectorInstance.id)) + .orderBy(asc(ConnectorTargetTable.createdAt), asc(ConnectorTargetTable.id)) + .limit(1) + const connectorTarget = targetRows[0] ?? null + if (!connectorTarget) { + throw new PluginArchRouteFailure(404, "connector_target_not_found", "GitHub connector target not found.") + } + + const targetConfig = connectorTarget.targetConfigJson && typeof connectorTarget.targetConfigJson === "object" + ? connectorTarget.targetConfigJson as Record + : {} + const repositoryFullName = typeof targetConfig.repositoryFullName === "string" ? targetConfig.repositoryFullName.trim() : connectorTarget.remoteId.trim() + const branch = typeof targetConfig.branch === "string" ? targetConfig.branch.trim() : connectorTarget.externalTargetRef?.trim() ?? "" + const ref = typeof targetConfig.ref === "string" ? targetConfig.ref.trim() : branch ? `refs/heads/${branch}` : "" + const installationId = typeof connectorInstance.instanceConfigJson === "object" && connectorInstance.instanceConfigJson && typeof (connectorInstance.instanceConfigJson as Record).installationId === "number" + ? (connectorInstance.instanceConfigJson as Record).installationId as number + : Number(connectorAccount.remoteId) + + if (!repositoryFullName || !branch || !ref || !Number.isFinite(installationId) || installationId <= 0) { + throw new PluginArchRouteFailure(409, "invalid_github_connector_target", "GitHub connector target is missing repository, branch, or installation metadata.") + } + + const instanceConfigRecord = typeof connectorInstance.instanceConfigJson === "object" && connectorInstance.instanceConfigJson + ? connectorInstance.instanceConfigJson as Record + : null + const autoImportSaved = instanceConfigRecord ? instanceConfigRecord.autoImportNewPlugins : undefined + return { + autoImportNewPlugins: typeof autoImportSaved === "boolean" ? autoImportSaved : true, + branch, + connectorAccount, + connectorInstance, + connectorTarget, + installationId, + ref, + repositoryFullName, + } +} + +async function buildConnectorAutomationContext(input: { connectorInstance: ConnectorInstanceRow }) { + const organizationRows = await db + .select() + .from(OrganizationTable) + .where(eq(OrganizationTable.id, input.connectorInstance.organizationId)) + .limit(1) + const organization = organizationRows[0] as OrganizationRow | undefined + if (!organization) { + throw new PluginArchRouteFailure(404, "organization_not_found", "Organization not found for connector instance.") + } + + const memberRows = await db + .select() + .from(MemberTable) + .where(and( + eq(MemberTable.organizationId, input.connectorInstance.organizationId), + eq(MemberTable.id, input.connectorInstance.createdByOrgMembershipId), + )) + .limit(1) + const member = memberRows[0] as MemberRow | undefined + if (!member) { + throw new PluginArchRouteFailure(404, "member_not_found", "Connector creator member not found.") + } + + return { + memberTeams: [], + organizationContext: { + currentMember: { + createdAt: member.createdAt, + id: member.id, + isOwner: roleIncludesOwner(member.role), + role: member.role, + userId: member.userId, + }, + invitations: [], + members: [], + organization: { + allowedEmailDomains: organization.allowedEmailDomains ?? null, + createdAt: organization.createdAt, + desktopAppRestrictions: organization.desktopAppRestrictions, + id: organization.id, + logo: organization.logo ?? null, + metadata: organization.metadata ? JSON.stringify(organization.metadata) : null, + name: organization.name, + slug: organization.slug, + updatedAt: organization.updatedAt, + }, + roles: [], + teams: [], + }, + } satisfies PluginArchActorContext +} + +async function maybeAutoImportGithubConnectorInstance(input: { + connectorInstance: ConnectorInstanceRow + connectorTarget: ConnectorTargetRow +}) { + const instanceConfig = input.connectorInstance.instanceConfigJson && typeof input.connectorInstance.instanceConfigJson === "object" + ? input.connectorInstance.instanceConfigJson as Record + : {} + if (instanceConfig.autoImportNewPlugins !== true) { + return { autoImported: false as const, createdPluginCount: 0, materializedConfigObjectCount: 0 } + } + + const context = await buildConnectorAutomationContext({ connectorInstance: input.connectorInstance }) + const discovery = await computeGithubConnectorDiscovery({ + connectorInstanceId: input.connectorInstance.id, + context, + }) + const selectedKeys = discovery.discoveredPlugins + .filter((plugin) => plugin.supported) + .map((plugin) => plugin.key) + + const applied = await applyGithubConnectorDiscovery({ + autoImportNewPlugins: true, + connectorInstanceId: input.connectorInstance.id, + context, + selectedKeys, + }) + + return { + autoImported: true as const, + createdPluginCount: applied.createdPlugins.length, + materializedConfigObjectCount: applied.materializedConfigObjects.length, + } +} + +async function getGithubDiscoveryFileTexts(input: { + branch: string + config: ReturnType + installationId: number + repositoryFullName: string + treeEntries: GithubDiscoveryTreeEntry[] +}) { + const interestingPaths = new Set() + const knownPaths = new Set(input.treeEntries.map((entry) => entry.path)) + + if (knownPaths.has(".claude-plugin/marketplace.json")) { + interestingPaths.add(".claude-plugin/marketplace.json") + } + + for (const entry of input.treeEntries) { + if (entry.path.endsWith(".claude-plugin/plugin.json") || entry.path.endsWith("/plugin.json") || entry.path === "plugin.json") { + interestingPaths.add(entry.path) + } + } + + const fileTextByPath: Record = {} + for (const path of interestingPaths) { + try { + fileTextByPath[path] = await getGithubRepositoryTextFile({ + config: input.config, + installationId: input.installationId, + path, + ref: input.branch, + repositoryFullName: input.repositoryFullName, + }) + } catch (error) { + wrapGithubConnectorError(error) + } + } + + return fileTextByPath +} + +function pagedGithubDiscoveryTree(input: { cursor?: string; entries: GithubDiscoveryTreeEntry[]; limit?: number; prefix?: string }) { + const normalizedPrefix = input.prefix?.trim().replace(/^\/+/, "").replace(/\/+$/, "") + const filtered = input.entries + .filter((entry) => !normalizedPrefix || entry.path === normalizedPrefix || entry.path.startsWith(`${normalizedPrefix}/`)) + .sort((left, right) => left.path.localeCompare(right.path)) + return pageItems(filtered, normalizeDiscoveryCursor(input.cursor), input.limit) +} + +async function computeGithubConnectorDiscovery(input: { connectorInstanceId: ConnectorInstanceId; context: PluginArchActorContext }) { + const discoveryContext = await getGithubDiscoveryContext(input) + let treeSnapshot: Awaited> + try { + treeSnapshot = await getGithubRepositoryTree({ + branch: discoveryContext.branch, + config: githubConnectorAppConfig(), + installationId: discoveryContext.installationId, + repositoryFullName: discoveryContext.repositoryFullName, + }) + } catch (error) { + wrapGithubConnectorError(error) + } + + const fileTextByPath = await getGithubDiscoveryFileTexts({ + branch: discoveryContext.branch, + config: githubConnectorAppConfig(), + installationId: discoveryContext.installationId, + repositoryFullName: discoveryContext.repositoryFullName, + treeEntries: treeSnapshot.treeEntries, + }) + const discovery = buildGithubRepoDiscovery({ + entries: treeSnapshot.treeEntries, + fileTextByPath, + }) + + const steps: GithubConnectorDiscoveryStep[] = [ + discoveryStep("completed", "read_repository_structure", "Read repository structure"), + discoveryStep(treeSnapshot.treeEntries.some((entry) => entry.path === ".claude-plugin/marketplace.json") ? "completed" : "warning", "check_marketplace_manifest", "Check for Claude marketplace manifest"), + discoveryStep(discovery.classification === "claude_single_plugin_repo" || discovery.classification === "claude_multi_plugin_repo" ? "completed" : "warning", "check_plugin_manifests", "Check for plugin manifests"), + discoveryStep(discovery.discoveredPlugins.length > 0 ? "completed" : "warning", "prepare_discovered_plugins", "Prepare discovered plugins"), + ] + + return { + autoImportNewPlugins: discoveryContext.autoImportNewPlugins, + classification: discovery.classification, + connectorInstance: serializeConnectorInstance(discoveryContext.connectorInstance), + connectorTarget: serializeConnectorTarget(discoveryContext.connectorTarget), + discoveredPlugins: discovery.discoveredPlugins, + marketplace: discovery.marketplace, + repositoryFullName: discoveryContext.repositoryFullName, + sourceRevisionRef: treeSnapshot.headSha, + steps, + treeEntries: treeSnapshot.treeEntries, + treeSummary: { + scannedEntryCount: treeSnapshot.treeEntries.length, + strategy: "git-tree-recursive", + truncated: treeSnapshot.truncated, + } satisfies GithubConnectorDiscoveryTreeSummary, + warnings: discovery.warnings, + } +} + +function discoveryMappingsForPlugin(plugin: GithubDiscoveredPlugin) { + return [ + ...plugin.componentPaths.skills.map((selector) => ({ objectType: "skill" as const, selector: `${selector}/**` })), + ...plugin.componentPaths.commands.map((selector) => ({ objectType: "command" as const, selector: `${selector}/**` })), + ...plugin.componentPaths.agents.map((selector) => ({ objectType: "agent" as const, selector: `${selector}/**` })), + ...plugin.componentPaths.hooks.map((selector) => ({ objectType: "hook" as const, selector })), + ...plugin.componentPaths.mcpServers.map((selector) => ({ objectType: "mcp" as const, selector })), + ] +} + +function mappingSelectorMatchesPath(selector: string, path: string) { + const normalizedSelector = selector.trim().replace(/^\/+/, "") + const normalizedPath = path.trim().replace(/^\/+/, "") + if (normalizedSelector.endsWith("/**")) { + const prefix = normalizedSelector.slice(0, -3) + return normalizedPath.startsWith(`${prefix}/`) + } + return normalizedPath === normalizedSelector +} + +function importableGithubPathsForMapping(input: { mapping: ReturnType; treeEntries: GithubDiscoveryTreeEntry[] }) { + const matchingBlobs = input.treeEntries + .filter((entry) => entry.kind === "blob") + .filter((entry) => mappingSelectorMatchesPath(input.mapping.selector, entry.path)) + + if (input.mapping.objectType === "skill") { + const preferred = matchingBlobs.filter((entry) => entry.path.endsWith("/SKILL.md")) + return preferred.length > 0 ? preferred : matchingBlobs.filter((entry) => entry.path.endsWith(".md")) + } + if (input.mapping.objectType === "agent") { + const preferred = matchingBlobs.filter((entry) => entry.path.endsWith("/AGENT.md")) + return preferred.length > 0 ? preferred : matchingBlobs.filter((entry) => entry.path.endsWith(".md")) + } + if (input.mapping.objectType === "command") { + return matchingBlobs.filter((entry) => entry.path.endsWith(".md")) + } + return matchingBlobs +} + +function parseMarkdownFrontmatter(rawSourceText: string): { body: string; data: Record } { + const match = rawSourceText.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/) + if (!match) { + return { body: rawSourceText, data: {} } + } + + const [, yaml, body] = match + const data: Record = {} + for (const line of yaml.split(/\r?\n/)) { + const trimmed = line.trim() + if (!trimmed || trimmed.startsWith("#")) continue + const colonIndex = trimmed.indexOf(":") + if (colonIndex === -1) continue + const key = trimmed.slice(0, colonIndex).trim() + let value = trimmed.slice(colonIndex + 1).trim() + if (value.length > 1) { + const first = value[0] + const last = value[value.length - 1] + if ((first === '"' && last === '"') || (first === "'" && last === "'")) { + value = value.slice(1, -1) + } + } + if (!key || !value) continue + data[key] = value + } + return { body: body ?? "", data } +} + +function importedObjectMetadata(input: { objectType: ConnectorMappingRow["objectType"]; path: string; rawSourceText: string }) { + const pathSegments = input.path.split("/") + const fileName = pathSegments[pathSegments.length - 1] ?? input.path + const parentName = pathSegments[pathSegments.length - 2] ?? pathSegments[pathSegments.length - 1] ?? "Imported" + const nameFromFile = fileName.replace(/\.[^.]+$/, "") + const preferredName = input.objectType === "skill" || input.objectType === "agent" + ? (fileName.toUpperCase() === "SKILL.MD" || fileName.toUpperCase() === "AGENT.MD" ? parentName : nameFromFile) + : nameFromFile + + const isMarkdown = fileName.toLowerCase().endsWith(".md") || fileName.toLowerCase().endsWith(".mdx") + const frontmatter = isMarkdown ? parseMarkdownFrontmatter(input.rawSourceText) : null + const frontmatterName = frontmatter?.data.name ?? frontmatter?.data.title + const frontmatterDescription = frontmatter?.data.description ?? frontmatter?.data.summary + + const metadata: Record = { + name: frontmatterName?.trim() || preferredName, + relativePath: input.path, + } + if (frontmatterDescription?.trim()) { + metadata.description = frontmatterDescription.trim() + } + if (frontmatter && Object.keys(frontmatter.data).length > 0) { + metadata.frontmatter = frontmatter.data + } + + return { + metadata, + normalizedPayloadJson: (() => { + if (!fileName.endsWith(".json")) { + return undefined + } + try { + const parsed = JSON.parse(input.rawSourceText) as unknown + return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed as Record : undefined + } catch { + return undefined + } + })(), + } +} + +async function materializeGithubImportedObject(input: { + connectorInstance: ReturnType + connectorMapping: ReturnType + connectorTarget: ReturnType + context: PluginArchActorContext + externalLocator: string + rawSourceText: string + sourceRevisionRef: string +}) { + const organizationId = input.context.organizationContext.organization.id + const createdByOrgMembershipId = input.context.organizationContext.currentMember.id + const now = new Date() + const metadata = importedObjectMetadata({ + objectType: input.connectorMapping.objectType, + path: input.externalLocator, + rawSourceText: input.rawSourceText, + }) + const frontmatterRecord = metadata.metadata && typeof metadata.metadata.frontmatter === "object" + ? metadata.metadata.frontmatter as Record + : null + const hasFrontmatter = frontmatterRecord && Object.keys(frontmatterRecord).length > 0 + const projectionRawSource = hasFrontmatter + ? parseMarkdownFrontmatter(input.rawSourceText).body + : input.rawSourceText + const projection = deriveProjection({ + objectType: input.connectorMapping.objectType, + value: { + metadata: metadata.metadata, + normalizedPayloadJson: metadata.normalizedPayloadJson, + rawSourceText: projectionRawSource, + }, + }) + const fileName = input.externalLocator.split("/").filter(Boolean).at(-1) ?? input.externalLocator + const fileExtension = fileName.includes(".") ? fileName.split(".").at(-1) ?? null : null + + const existingBinding = await db + .select() + .from(ConnectorSourceBindingTable) + .where(and( + eq(ConnectorSourceBindingTable.organizationId, organizationId), + eq(ConnectorSourceBindingTable.connectorMappingId, input.connectorMapping.id), + eq(ConnectorSourceBindingTable.externalLocator, input.externalLocator), + isNull(ConnectorSourceBindingTable.deletedAt), + )) + .limit(1) + + if (!existingBinding[0]) { + const configObjectId = createDenTypeId("configObject") + const versionId = createDenTypeId("configObjectVersion") + await db.transaction(async (tx) => { + await tx.insert(ConfigObjectTable).values({ + connectorInstanceId: input.connectorInstance.id, + createdAt: now, + createdByOrgMembershipId, + currentFileExtension: normalizeOptionalString(fileExtension ?? undefined), + currentFileName: fileName, + currentRelativePath: input.externalLocator, + deletedAt: null, + description: projection.description, + id: configObjectId, + objectType: input.connectorMapping.objectType, + organizationId, + searchText: projection.searchText, + sourceMode: "connector", + status: "active", + title: projection.title, + updatedAt: now, + }) + + await tx.insert(ConfigObjectVersionTable).values({ + configObjectId, + connectorSyncEventId: null, + createdAt: now, + createdByOrgMembershipId, + createdVia: "connector", + id: versionId, + isDeletedVersion: false, + normalizedPayloadJson: metadata.normalizedPayloadJson ?? null, + organizationId, + rawSourceText: normalizeOptionalString(input.rawSourceText), + schemaVersion: null, + sourceRevisionRef: input.sourceRevisionRef, + }) + + await tx.insert(ConfigObjectAccessGrantTable).values({ + configObjectId, + createdAt: now, + createdByOrgMembershipId, + id: createDenTypeId("configObjectAccessGrant"), + organizationId, + orgMembershipId: createdByOrgMembershipId, + orgWide: false, + role: "manager", + teamId: null, + }) + + if (input.connectorMapping.pluginId) { + await tx.insert(PluginConfigObjectTable).values({ + configObjectId, + connectorMappingId: input.connectorMapping.id, + createdAt: now, + createdByOrgMembershipId, + id: createDenTypeId("pluginConfigObject"), + membershipSource: "connector", + organizationId, + pluginId: input.connectorMapping.pluginId, + removedAt: null, + }) + } + + await tx.insert(ConnectorSourceBindingTable).values({ + configObjectId, + connectorInstanceId: input.connectorInstance.id, + connectorMappingId: input.connectorMapping.id, + connectorTargetId: input.connectorTarget.id, + connectorType: input.connectorTarget.connectorType, + createdAt: now, + deletedAt: null, + externalLocator: input.externalLocator, + externalStableRef: input.externalLocator, + id: createDenTypeId("connectorSourceBinding"), + lastSeenSourceRevisionRef: input.sourceRevisionRef, + organizationId, + remoteId: input.connectorTarget.remoteId, + status: "active", + updatedAt: now, + }) + }) + + return getConfigObjectDetail(input.context, configObjectId) + } + + const binding = existingBinding[0] + if (binding.lastSeenSourceRevisionRef !== input.sourceRevisionRef) { + const versionId = createDenTypeId("configObjectVersion") + await db.transaction(async (tx) => { + await tx.update(ConfigObjectTable).set({ + currentFileExtension: normalizeOptionalString(fileExtension ?? undefined), + currentFileName: fileName, + currentRelativePath: input.externalLocator, + description: projection.description, + searchText: projection.searchText, + status: "active", + title: projection.title, + updatedAt: now, + }).where(eq(ConfigObjectTable.id, binding.configObjectId)) + + await tx.insert(ConfigObjectVersionTable).values({ + configObjectId: binding.configObjectId, + connectorSyncEventId: null, + createdAt: now, + createdByOrgMembershipId, + createdVia: "connector", + id: versionId, + isDeletedVersion: false, + normalizedPayloadJson: metadata.normalizedPayloadJson ?? null, + organizationId, + rawSourceText: normalizeOptionalString(input.rawSourceText), + schemaVersion: null, + sourceRevisionRef: input.sourceRevisionRef, + }) + + if (input.connectorMapping.pluginId) { + const membership = await tx + .select({ id: PluginConfigObjectTable.id }) + .from(PluginConfigObjectTable) + .where(and( + eq(PluginConfigObjectTable.pluginId, input.connectorMapping.pluginId), + eq(PluginConfigObjectTable.configObjectId, binding.configObjectId), + )) + .limit(1) + if (membership[0]) { + await tx.update(PluginConfigObjectTable).set({ + connectorMappingId: input.connectorMapping.id, + membershipSource: "connector", + removedAt: null, + }).where(eq(PluginConfigObjectTable.id, membership[0].id)) + } else { + await tx.insert(PluginConfigObjectTable).values({ + configObjectId: binding.configObjectId, + connectorMappingId: input.connectorMapping.id, + createdAt: now, + createdByOrgMembershipId, + id: createDenTypeId("pluginConfigObject"), + membershipSource: "connector", + organizationId, + pluginId: input.connectorMapping.pluginId, + removedAt: null, + }) + } + } + + await tx.update(ConnectorSourceBindingTable).set({ + deletedAt: null, + lastSeenSourceRevisionRef: input.sourceRevisionRef, + status: "active", + updatedAt: now, + }).where(eq(ConnectorSourceBindingTable.id, binding.id)) + }) + } + + return getConfigObjectDetail(input.context, binding.configObjectId) +} + +async function materializeGithubMappings(input: { + connectorInstance: ReturnType + connectorTarget: ReturnType + context: PluginArchActorContext + mappings: Array> + sourceRevisionRef: string + treeEntries: GithubDiscoveryTreeEntry[] +}) { + const config = githubConnectorAppConfig() + const targetConfig = input.connectorTarget.targetConfigJson && typeof input.connectorTarget.targetConfigJson === "object" + ? input.connectorTarget.targetConfigJson as Record + : {} + const branch = typeof targetConfig.branch === "string" ? targetConfig.branch : input.connectorTarget.externalTargetRef ?? "" + const installationId = typeof input.connectorInstance.instanceConfigJson === "object" && input.connectorInstance.instanceConfigJson && typeof (input.connectorInstance.instanceConfigJson as Record).installationId === "number" + ? (input.connectorInstance.instanceConfigJson as Record).installationId as number + : null + const repositoryFullName = typeof targetConfig.repositoryFullName === "string" ? targetConfig.repositoryFullName : input.connectorTarget.remoteId + if (!installationId || !branch || !repositoryFullName) { + throw new PluginArchRouteFailure(409, "invalid_github_materialization_context", "GitHub connector target is missing required materialization context.") + } + + const materializedConfigObjects: ReturnType[] = [] + for (const mapping of input.mappings) { + const importableFiles = importableGithubPathsForMapping({ mapping, treeEntries: input.treeEntries }) + for (const file of importableFiles) { + let rawSourceText: string | null + try { + rawSourceText = await getGithubRepositoryTextFile({ + config, + installationId, + path: file.path, + ref: branch, + repositoryFullName, + }) + } catch (error) { + wrapGithubConnectorError(error) + } + if (!rawSourceText) { + continue + } + materializedConfigObjects.push(await materializeGithubImportedObject({ + connectorInstance: input.connectorInstance, + connectorMapping: mapping, + connectorTarget: input.connectorTarget, + context: input.context, + externalLocator: file.path, + rawSourceText, + sourceRevisionRef: input.sourceRevisionRef, + })) + } + } + + return materializedConfigObjects +} + +async function ensureDiscoveryPlugin(input: { context: PluginArchActorContext; description: string | null; name: string }) { + const existing = await db + .select() + .from(PluginTable) + .where(and( + eq(PluginTable.organizationId, input.context.organizationContext.organization.id), + eq(PluginTable.name, input.name.trim()), + isNull(PluginTable.deletedAt), + )) + .orderBy(asc(PluginTable.createdAt), asc(PluginTable.id)) + .limit(1) + + if (existing[0]) { + return serializePlugin(existing[0], 0) + } + + return createPlugin({ + context: input.context, + description: input.description, + name: input.name, + }) +} + +async function ensureDiscoveryMarketplace(input: { context: PluginArchActorContext; description: string | null; name: string }) { + const existing = await db + .select() + .from(MarketplaceTable) + .where(and( + eq(MarketplaceTable.organizationId, input.context.organizationContext.organization.id), + eq(MarketplaceTable.name, input.name.trim()), + isNull(MarketplaceTable.deletedAt), + )) + .orderBy(asc(MarketplaceTable.createdAt), asc(MarketplaceTable.id)) + .limit(1) + + if (existing[0]) { + return serializeMarketplace(existing[0], 0) + } + + return createMarketplace({ + context: input.context, + description: input.description, + name: input.name, + }) +} + +async function ensureDiscoveryMapping(input: { + connectorTargetId: ConnectorTargetId + context: PluginArchActorContext + objectType: ConnectorMappingRow["objectType"] + pluginId: PluginId + selector: string +}) { + const existing = await db + .select() + .from(ConnectorMappingTable) + .where(and( + eq(ConnectorMappingTable.connectorTargetId, input.connectorTargetId), + eq(ConnectorMappingTable.mappingKind, "path"), + eq(ConnectorMappingTable.objectType, input.objectType), + eq(ConnectorMappingTable.pluginId, input.pluginId), + eq(ConnectorMappingTable.selector, input.selector), + )) + .limit(1) + + if (existing[0]) { + return serializeConnectorMapping(existing[0]) + } + + return createConnectorMapping({ + autoAddToPlugin: true, + config: { + discoverySourceKind: input.objectType, + }, + connectorTargetId: input.connectorTargetId, + context: input.context, + mappingKind: "path", + objectType: input.objectType, + pluginId: input.pluginId, + selector: input.selector, }) } @@ -1755,34 +2955,296 @@ export async function createGithubConnectorAccount(input: { accountLogin: string accountLogin: input.accountLogin, accountType: input.accountType, repositories: [], + repositorySelection: "all", + settingsUrl: null, }, remoteId: String(input.installationId), }) } +async function upsertGithubConnectorAccountFromInstallation(input: { context: PluginArchActorContext; installationId: number }) { + let installation: Awaited> + try { + installation = await getGithubInstallationSummary({ + config: githubConnectorAppConfig(), + installationId: input.installationId, + }) + } catch (error) { + wrapGithubConnectorError(error) + } + const organizationId = input.context.organizationContext.organization.id + const existingRows = await db + .select() + .from(ConnectorAccountTable) + .where(and( + eq(ConnectorAccountTable.organizationId, organizationId), + eq(ConnectorAccountTable.connectorType, "github"), + eq(ConnectorAccountTable.remoteId, String(input.installationId)), + )) + .limit(1) + + const metadata = { + accountLogin: installation.accountLogin, + accountType: installation.accountType, + repositories: [], + repositorySelection: installation.repositorySelection, + settingsUrl: installation.settingsUrl, + } + + if (!existingRows[0]) { + return createConnectorAccount({ + connectorType: "github", + context: input.context, + displayName: installation.displayName, + externalAccountRef: installation.accountLogin, + metadata, + remoteId: String(input.installationId), + }) + } + + await db.update(ConnectorAccountTable).set({ + displayName: installation.displayName, + externalAccountRef: installation.accountLogin, + metadataJson: { + ...(existingRows[0].metadataJson ?? {}), + ...metadata, + }, + status: "active", + updatedAt: new Date(), + }).where(eq(ConnectorAccountTable.id, existingRows[0].id)) + + return getConnectorAccountDetail(input.context, existingRows[0].id) +} + +export async function startGithubConnectorInstall(input: { context: PluginArchActorContext; returnPath: string }) { + const returnPath = input.returnPath.trim() + if (!returnPath.startsWith("/") || returnPath.startsWith("//")) { + throw new PluginArchRouteFailure(400, "invalid_return_path", "GitHub install return path must be a safe relative path.") + } + + let app: Awaited> + try { + app = await getGithubAppSummary({ config: githubConnectorAppConfig() }) + } catch (error) { + wrapGithubConnectorError(error) + } + const state = createGithubInstallStateToken({ + orgId: input.context.organizationContext.organization.id, + returnPath, + secret: env.betterAuthSecret, + userId: input.context.organizationContext.currentMember.userId, + }) + + return { + redirectUrl: buildGithubAppInstallUrl({ app, state }), + state, + } +} + +export async function completeGithubConnectorInstall(input: { context: PluginArchActorContext; installationId: number; state: string }) { + const parsedState = consumeGithubInstallState(input.state) + if (parsedState.orgId !== input.context.organizationContext.organization.id) { + throw new PluginArchRouteFailure(409, "github_install_org_mismatch", "GitHub install state does not match the current organization.") + } + if (parsedState.userId !== input.context.organizationContext.currentMember.userId) { + throw new PluginArchRouteFailure(409, "github_install_user_mismatch", "GitHub install state does not match the current user.") + } + + const connectorAccount = await upsertGithubConnectorAccountFromInstallation({ + context: input.context, + installationId: input.installationId, + }) + const repositories = await listGithubRepositories({ + connectorAccountId: connectorAccount.id, + context: input.context, + limit: 100, + }) + + return { + connectorAccount, + repositories: repositories.items, + } +} + +export async function getGithubConnectorDiscovery(input: { connectorInstanceId: ConnectorInstanceId; context: PluginArchActorContext }) { + const discovery = await computeGithubConnectorDiscovery(input) + return { + classification: discovery.classification, + connectorInstance: discovery.connectorInstance, + connectorTarget: discovery.connectorTarget, + discoveredPlugins: discovery.discoveredPlugins, + repositoryFullName: discovery.repositoryFullName, + sourceRevisionRef: discovery.sourceRevisionRef, + steps: discovery.steps, + treeSummary: discovery.treeSummary, + warnings: discovery.warnings, + } +} + +export async function getGithubConnectorDiscoveryTree(input: { connectorInstanceId: ConnectorInstanceId; context: PluginArchActorContext; cursor?: string; limit?: number; prefix?: string }) { + const discovery = await computeGithubConnectorDiscovery({ connectorInstanceId: input.connectorInstanceId, context: input.context }) + return pagedGithubDiscoveryTree({ + cursor: input.cursor, + entries: discovery.treeEntries, + limit: input.limit, + prefix: input.prefix, + }) +} + +export async function applyGithubConnectorDiscovery(input: { autoImportNewPlugins: boolean; connectorInstanceId: ConnectorInstanceId; context: PluginArchActorContext; selectedKeys: string[] }) { + const discovery = await computeGithubConnectorDiscovery({ connectorInstanceId: input.connectorInstanceId, context: input.context }) + const selectedKeySet = new Set(input.selectedKeys.map((key) => key.trim()).filter(Boolean)) + const selectedPlugins = discovery.discoveredPlugins.filter((plugin) => plugin.supported && selectedKeySet.has(plugin.key)) + await db.update(ConnectorInstanceTable).set({ + instanceConfigJson: { + ...((discovery.connectorInstance.instanceConfigJson && typeof discovery.connectorInstance.instanceConfigJson === "object") + ? discovery.connectorInstance.instanceConfigJson as Record + : {}), + autoImportNewPlugins: input.autoImportNewPlugins, + }, + updatedAt: new Date(), + }).where(eq(ConnectorInstanceTable.id, discovery.connectorInstance.id)) + + const marketplaceInfo = discovery.marketplace + const marketplaceName = marketplaceInfo?.name?.trim() || discovery.repositoryFullName + const marketplaceDescription = marketplaceInfo?.description?.trim() + ?? `Imported from GitHub marketplace repository ${discovery.repositoryFullName}.` + const createdMarketplace = discovery.classification === "claude_marketplace_repo" + ? await ensureDiscoveryMarketplace({ + context: input.context, + description: marketplaceDescription, + name: marketplaceName, + }) + : null + + const plugins = [] as Array> + const mappings = [] as Array> + for (const discoveredPlugin of selectedPlugins) { + const plugin = await ensureDiscoveryPlugin({ + context: input.context, + description: discoveredPlugin.description, + name: discoveredPlugin.displayName, + }) + plugins.push(plugin) + + if (createdMarketplace) { + await attachPluginToMarketplace({ + context: input.context, + marketplaceId: createdMarketplace.id, + membershipSource: "connector", + pluginId: plugin.id, + }) + } + + for (const mapping of discoveryMappingsForPlugin(discoveredPlugin)) { + mappings.push(await ensureDiscoveryMapping({ + connectorTargetId: discovery.connectorTarget.id, + context: input.context, + objectType: mapping.objectType, + pluginId: plugin.id, + selector: mapping.selector, + })) + } + } + + const materializedConfigObjects = await materializeGithubMappings({ + connectorInstance: discovery.connectorInstance, + connectorTarget: discovery.connectorTarget, + context: input.context, + mappings, + sourceRevisionRef: discovery.sourceRevisionRef, + treeEntries: discovery.treeEntries, + }) + + return { + autoImportNewPlugins: input.autoImportNewPlugins, + createdMarketplace, + connectorInstance: discovery.connectorInstance, + connectorTarget: discovery.connectorTarget, + createdPlugins: plugins, + createdMappings: mappings, + materializedConfigObjects, + sourceRevisionRef: discovery.sourceRevisionRef, + } +} + export async function listGithubRepositories(input: { connectorAccountId: ConnectorAccountId; context: PluginArchActorContext; cursor?: string; limit?: number; q?: string }) { const account = await getConnectorAccountRow(input.context.organizationContext.organization.id, input.connectorAccountId) if (!account) { throw new PluginArchRouteFailure(404, "connector_account_not_found", "Connector account not found.") } - const repositories = normalizeRepositories(account.metadataJson && typeof account.metadataJson === "object" ? (account.metadataJson as Record).repositories : []) + if (account.connectorType !== "github") { + throw new PluginArchRouteFailure(409, "github_connector_account_required", "Connector account is not a GitHub account.") + } + + const installationId = Number(account.remoteId) + if (!Number.isFinite(installationId) || installationId <= 0) { + throw new PluginArchRouteFailure(409, "invalid_github_installation_id", "Connector account does not have a valid GitHub installation id.") + } + + let repositories: RepositorySummary[] + let installationSummary: Awaited> + try { + repositories = await listGithubInstallationRepositories({ + config: githubConnectorAppConfig(), + installationId, + }) + installationSummary = await getGithubInstallationSummary({ + config: githubConnectorAppConfig(), + installationId, + }) + } catch (error) { + wrapGithubConnectorError(error) + } + + const existingMetadata = account.metadataJson && typeof account.metadataJson === "object" + ? account.metadataJson as Record + : {} + await db.update(ConnectorAccountTable).set({ + metadataJson: { + ...existingMetadata, + repositories: repositories.map((repository) => ({ + defaultBranch: repository.defaultBranch, + fullName: repository.fullName, + id: repository.id, + private: repository.private, + })), + repositorySelection: installationSummary.repositorySelection, + settingsUrl: installationSummary.settingsUrl, + }, + updatedAt: new Date(), + }).where(eq(ConnectorAccountTable.id, account.id)) + + const filtered = repositories .filter((repository) => !input.q || `${repository.fullName}\n${repository.defaultBranch ?? ""}`.toLowerCase().includes(input.q.toLowerCase())) .map((repository) => ({ ...repository, id: String(repository.id) })) - const page = pageItems(repositories, input.cursor, input.limit) + const page = pageItems(filtered, input.cursor, input.limit) return { - items: page.items.map((repository) => ({ defaultBranch: repository.defaultBranch, fullName: repository.fullName, id: Number(repository.id), private: repository.private })), + items: page.items.map((repository) => ({ + defaultBranch: repository.defaultBranch, + fullName: repository.fullName, + hasPluginManifest: Boolean(repository.hasPluginManifest), + id: Number(repository.id), + manifestKind: repository.manifestKind ?? null, + marketplacePluginCount: repository.marketplacePluginCount ?? null, + private: repository.private, + })), nextCursor: page.nextCursor, } } -export async function validateGithubTarget(input: { branch: string; ref: string; repositoryFullName: string }) { - const branch = input.branch.trim() - const ref = input.ref.trim() - const expectedRef = `refs/heads/${branch}` - return { - branchExists: ref === expectedRef, - defaultBranch: branch, - repositoryAccessible: Boolean(input.repositoryFullName.trim()), +export async function validateGithubTarget(input: { branch: string; installationId: number; ref: string; repositoryFullName: string; repositoryId: number }) { + try { + return await validateGithubInstallationTarget({ + branch: input.branch, + config: githubConnectorAppConfig(), + installationId: input.installationId, + ref: input.ref, + repositoryFullName: input.repositoryFullName, + repositoryId: input.repositoryId, + }) + } catch (error) { + wrapGithubConnectorError(error) } } @@ -1797,6 +3259,20 @@ export async function githubSetup(input: { repositoryFullName: string repositoryId: number }) { + const validation = await validateGithubTarget({ + branch: input.branch, + installationId: input.installationId, + ref: input.ref, + repositoryFullName: input.repositoryFullName, + repositoryId: input.repositoryId, + }) + if (!validation.repositoryAccessible) { + throw new PluginArchRouteFailure(409, "github_repository_not_accessible", "GitHub repository is not accessible for this installation.") + } + if (!validation.branchExists) { + throw new PluginArchRouteFailure(409, "github_branch_not_found", "GitHub branch/ref could not be validated for this repository.") + } + let connectorAccountId = input.connectorAccountId as ConnectorAccountId | undefined let connectorAccountDetail = connectorAccountId ? await getConnectorAccountDetail(input.context, connectorAccountId) : null if (!connectorAccountId || !connectorAccountDetail) { @@ -1814,6 +3290,7 @@ export async function githubSetup(input: { connectorAccountId, connectorType: "github", config: { + autoImportNewPlugins: true, installationId: input.installationId, }, context: input.context, @@ -1824,6 +3301,7 @@ export async function githubSetup(input: { const connectorTarget = await createConnectorTarget({ config: { branch: input.branch, + defaultBranch: validation.defaultBranch, ref: input.ref, repositoryFullName: input.repositoryFullName, repositoryId: input.repositoryId, @@ -1916,14 +3394,38 @@ export async function enqueueGithubWebhookSync(input: { )) .limit(1) + let autoImportSummary: { + autoImported: boolean + createdPluginCount: number + materializedConfigObjectCount: number + } + try { + autoImportSummary = await maybeAutoImportGithubConnectorInstance({ + connectorInstance: row.instance, + connectorTarget: row.target, + }) + } catch (error) { + autoImportSummary = { + autoImported: false, + createdPluginCount: 0, + materializedConfigObjectCount: 0, + } + } + + const eventStatus = autoImportSummary.autoImported ? "completed" as const : "queued" as const + const completedAt = autoImportSummary.autoImported ? new Date() : null + const id = existing[0]?.id ?? createDenTypeId("connectorSyncEvent") if (existing[0]) { await db.update(ConnectorSyncEventTable).set({ - completedAt: null, + completedAt, externalEventRef: input.deliveryId, startedAt: new Date(), - status: "queued", + status: eventStatus, summaryJson: { + autoImportApplied: autoImportSummary.autoImported, + autoImportCreatedPluginCount: autoImportSummary.createdPluginCount, + autoImportMaterializedConfigObjectCount: autoImportSummary.materializedConfigObjectCount, deliveryId: input.deliveryId, headSha: input.headSha, repositoryFullName: input.repositoryFullName, @@ -1934,7 +3436,7 @@ export async function enqueueGithubWebhookSync(input: { }).where(eq(ConnectorSyncEventTable.id, id)) } else { await db.insert(ConnectorSyncEventTable).values({ - completedAt: null, + completedAt, connectorInstanceId: row.instance.id, connectorTargetId: row.target.id, connectorType: "github", @@ -1945,8 +3447,11 @@ export async function enqueueGithubWebhookSync(input: { remoteId: input.repositoryFullName, sourceRevisionRef: input.headSha, startedAt: new Date(), - status: "queued", + status: eventStatus, summaryJson: { + autoImportApplied: autoImportSummary.autoImported, + autoImportCreatedPluginCount: autoImportSummary.createdPluginCount, + autoImportMaterializedConfigObjectCount: autoImportSummary.materializedConfigObjectCount, deliveryId: input.deliveryId, headSha: input.headSha, installationId: input.installationId, diff --git a/ee/apps/den-api/test/github-connector-app.test.ts b/ee/apps/den-api/test/github-connector-app.test.ts new file mode 100644 index 00000000..b488485e --- /dev/null +++ b/ee/apps/den-api/test/github-connector-app.test.ts @@ -0,0 +1,180 @@ +import { describe, expect, test } from "bun:test" +import { generateKeyPairSync } from "node:crypto" +import { + buildGithubAppInstallUrl, + createGithubInstallStateToken, + createGithubAppJwt, + getGithubAppSummary, + getGithubConnectorAppConfig, + getGithubInstallationSummary, + listGithubInstallationRepositories, + normalizeGithubPrivateKey, + validateGithubInstallationTarget, + verifyGithubInstallStateToken, +} from "../src/routes/org/plugin-system/github-app.js" + +const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 }) +const privateKeyPem = privateKey.export({ format: "pem", type: "pkcs8" }).toString() + +describe("github connector app helpers", () => { + test("normalizes escaped private keys and produces a signed app JWT", () => { + const escapedKey = privateKeyPem.replace(/\n/g, "\\n") + expect(normalizeGithubPrivateKey(escapedKey)).toBe(privateKeyPem) + + const config = getGithubConnectorAppConfig({ + appId: "123456", + privateKey: escapedKey, + }) + const jwt = createGithubAppJwt({ ...config, now: new Date("2026-04-21T19:00:00.000Z") }) + const [headerSegment, payloadSegment, signatureSegment] = jwt.split(".") + + expect(signatureSegment.length).toBeGreaterThan(0) + expect(JSON.parse(Buffer.from(headerSegment, "base64url").toString("utf8"))).toEqual({ alg: "RS256", typ: "JWT" }) + expect(JSON.parse(Buffer.from(payloadSegment, "base64url").toString("utf8"))).toMatchObject({ + iss: "123456", + }) + }) + + test("lists repositories through the GitHub installation token flow", async () => { + const requests: Array<{ method: string; url: string }> = [] + const repositories = await listGithubInstallationRepositories({ + config: { appId: "123456", privateKey: privateKeyPem }, + fetchFn: async (url, init) => { + requests.push({ + method: init?.method ?? "GET", + url: String(url), + }) + + if (String(url).endsWith("/access_tokens")) { + return new Response(JSON.stringify({ token: "installation-token" }), { status: 201 }) + } + + if (String(url).endsWith("/contents/.claude-plugin/marketplace.json")) { + if (String(url).includes("different-ai/openwork")) { + const content = Buffer.from(JSON.stringify({ plugins: [{ name: "a" }, { name: "b" }, { name: "c" }] })).toString("base64") + return new Response(JSON.stringify({ content, encoding: "base64" }), { status: 200 }) + } + return new Response(JSON.stringify({ message: "not found" }), { status: 404 }) + } + + if (String(url).endsWith("/contents/.claude-plugin/plugin.json")) { + if (String(url).includes("different-ai/opencode")) { + return new Response(JSON.stringify({ name: "plugin.json" }), { status: 200 }) + } + return new Response(JSON.stringify({ message: "not found" }), { status: 404 }) + } + + return new Response(JSON.stringify({ + repositories: [ + { default_branch: "main", full_name: "different-ai/openwork", id: 42, private: true }, + { default_branch: "dev", full_name: "different-ai/opencode", id: 99, private: false }, + ], + }), { status: 200 }) + }, + installationId: 777, + }) + + expect(requests.map((request) => request.url)).toEqual([ + "https://api.github.com/app/installations/777/access_tokens", + "https://api.github.com/installation/repositories", + "https://api.github.com/repos/different-ai/openwork/contents/.claude-plugin/marketplace.json", + "https://api.github.com/repos/different-ai/opencode/contents/.claude-plugin/marketplace.json", + "https://api.github.com/repos/different-ai/opencode/contents/.claude-plugin/plugin.json", + ]) + expect(repositories).toEqual([ + { defaultBranch: "main", fullName: "different-ai/openwork", hasPluginManifest: true, id: 42, manifestKind: "marketplace", marketplacePluginCount: 3, private: true }, + { defaultBranch: "dev", fullName: "different-ai/opencode", hasPluginManifest: true, id: 99, manifestKind: "plugin", marketplacePluginCount: null, private: false }, + ]) + }) + + test("builds install URLs and validates signed state tokens", async () => { + const app = await getGithubAppSummary({ + config: { appId: "123456", privateKey: privateKeyPem }, + fetchFn: async () => new Response(JSON.stringify({ + html_url: "https://github.com/apps/openwork-test", + name: "OpenWork Test", + slug: "openwork-test", + }), { status: 200 }), + }) + + const token = createGithubInstallStateToken({ + now: new Date("2026-04-21T19:00:00.000Z"), + orgId: "org_123", + returnPath: "/o/test-org/dashboard/integrations/github", + secret: "secret-123", + userId: "user_123", + }) + + expect(buildGithubAppInstallUrl({ app, state: token })).toBe(`https://github.com/apps/openwork-test/installations/new?state=${encodeURIComponent(token)}`) + expect(verifyGithubInstallStateToken({ now: new Date("2026-04-21T19:05:00.000Z"), secret: "secret-123", token })).toMatchObject({ + orgId: "org_123", + returnPath: "/o/test-org/dashboard/integrations/github", + userId: "user_123", + }) + expect(verifyGithubInstallStateToken({ now: new Date("2026-04-21T19:05:00.000Z"), secret: "wrong-secret", token })).toBeNull() + }) + + test("reads GitHub installation account details", async () => { + const installation = await getGithubInstallationSummary({ + config: { appId: "123456", privateKey: privateKeyPem }, + fetchFn: async (url) => { + if (String(url).endsWith("/app/installations/777")) { + return new Response(JSON.stringify({ + account: { + login: "different-ai", + type: "Organization", + }, + id: 777, + }), { status: 200 }) + } + return new Response(JSON.stringify({ message: "not found" }), { status: 404 }) + }, + installationId: 777, + }) + + expect(installation).toEqual({ + accountLogin: "different-ai", + accountType: "Organization", + displayName: "different-ai", + installationId: 777, + repositorySelection: "all", + settingsUrl: null, + }) + }) + + test("validates repository identity and branch existence against GitHub", async () => { + const result = await validateGithubInstallationTarget({ + branch: "main", + config: { appId: "123456", privateKey: privateKeyPem }, + fetchFn: async (url) => { + if (String(url).endsWith("/access_tokens")) { + return new Response(JSON.stringify({ token: "installation-token" }), { status: 201 }) + } + + if (String(url).endsWith("/repos/different-ai/openwork")) { + return new Response(JSON.stringify({ + default_branch: "main", + full_name: "different-ai/openwork", + id: 42, + }), { status: 200 }) + } + + if (String(url).endsWith("/repos/different-ai/openwork/branches/main")) { + return new Response(JSON.stringify({ name: "main" }), { status: 200 }) + } + + return new Response(JSON.stringify({ message: "not found" }), { status: 404 }) + }, + installationId: 777, + ref: "refs/heads/main", + repositoryFullName: "different-ai/openwork", + repositoryId: 42, + }) + + expect(result).toEqual({ + branchExists: true, + defaultBranch: "main", + repositoryAccessible: true, + }) + }) +}) diff --git a/ee/apps/den-api/test/github-discovery.test.ts b/ee/apps/den-api/test/github-discovery.test.ts new file mode 100644 index 00000000..45c17509 --- /dev/null +++ b/ee/apps/den-api/test/github-discovery.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, test } from "bun:test" +import { buildGithubRepoDiscovery, type GithubDiscoveryTreeEntry } from "../src/routes/org/plugin-system/github-discovery.js" + +function blob(path: string): GithubDiscoveryTreeEntry { + return { id: path, kind: "blob", path, sha: null, size: null } +} + +describe("github discovery", () => { + test("classifies marketplace repos and resolves local plugin roots", () => { + const result = buildGithubRepoDiscovery({ + entries: [ + blob(".claude-plugin/marketplace.json"), + blob("plugins/sales/.claude-plugin/plugin.json"), + blob("plugins/sales/skills/hello/SKILL.md"), + blob("plugins/sales/commands/deploy.md"), + ], + fileTextByPath: { + ".claude-plugin/marketplace.json": JSON.stringify({ + plugins: [ + { name: "sales", description: "Sales workflows", source: "./plugins/sales" }, + ], + }), + "plugins/sales/.claude-plugin/plugin.json": JSON.stringify({ + name: "sales", + description: "Sales plugin", + }), + }, + }) + + expect(result.classification).toBe("claude_marketplace_repo") + expect(result.discoveredPlugins).toHaveLength(1) + expect(result.discoveredPlugins[0]).toMatchObject({ + displayName: "sales", + rootPath: "plugins/sales", + sourceKind: "marketplace_entry", + }) + expect(result.discoveredPlugins[0]?.componentPaths.skills).toEqual(["plugins/sales/skills"]) + expect(result.discoveredPlugins[0]?.componentPaths.commands).toEqual(["plugins/sales/commands"]) + }) + + test("treats marketplace source './' as the current repo root", () => { + const result = buildGithubRepoDiscovery({ + entries: [ + blob(".claude-plugin/marketplace.json"), + blob("skills/agent-browser/SKILL.md"), + blob("skills/other-skill/SKILL.md"), + ], + fileTextByPath: { + ".claude-plugin/marketplace.json": JSON.stringify({ + plugins: [ + { + name: "agent-browser", + description: "Automates browser interactions for web testing, form filling, screenshots, and data extraction", + source: "./", + strict: false, + skills: ["./skills/agent-browser"], + category: "development", + }, + ], + }), + }, + }) + + expect(result.classification).toBe("claude_marketplace_repo") + expect(result.warnings).toEqual([]) + expect(result.discoveredPlugins).toHaveLength(1) + expect(result.discoveredPlugins[0]).toMatchObject({ + displayName: "agent-browser", + rootPath: "", + sourceKind: "marketplace_entry", + supported: true, + }) + expect(result.discoveredPlugins[0]?.componentPaths.skills).toEqual(["skills/agent-browser"]) + }) + + test("treats non-Claude folder-only repos as unsupported", () => { + const result = buildGithubRepoDiscovery({ + entries: [ + blob("Sales/skills/pitch/SKILL.md"), + blob("Sales/commands/release.md"), + blob("finance/agents/reviewer.md"), + blob("finance/commands/audit.md"), + ], + fileTextByPath: { + "Sales/plugin.json": JSON.stringify({ name: "Sales", description: "Sales tools" }), + }, + }) + + expect(result.classification).toBe("unsupported") + expect(result.discoveredPlugins).toEqual([]) + expect(result.warnings[0]).toContain("only supports Claude-compatible plugins and marketplaces") + }) + + test("treats standalone .claude directories as unsupported without plugin manifests", () => { + const result = buildGithubRepoDiscovery({ + entries: [ + blob(".claude/skills/research/SKILL.md"), + blob(".claude/commands/publish.md"), + ], + fileTextByPath: {}, + }) + + expect(result.classification).toBe("unsupported") + expect(result.discoveredPlugins).toEqual([]) + expect(result.warnings[0]).toContain("only supports Claude-compatible plugins and marketplaces") + }) +}) diff --git a/ee/apps/den-api/test/github-webhook.test.ts b/ee/apps/den-api/test/github-webhook.test.ts index 2c1969f3..d2cbce69 100644 --- a/ee/apps/den-api/test/github-webhook.test.ts +++ b/ee/apps/den-api/test/github-webhook.test.ts @@ -30,7 +30,7 @@ function createWebhookApp() { test("webhook route rejects invalid signatures before JSON parsing", async () => { envModule.env.githubConnectorApp.webhookSecret = "super-secret" const app = createWebhookApp() - const response = await app.request("http://den.local/api/webhooks/connectors/github", { + const response = await app.request("http://den.local/v1/webhooks/connectors/github", { body: "{", headers: { "x-github-delivery": "delivery-1", @@ -47,7 +47,7 @@ test("webhook route rejects invalid signatures before JSON parsing", async () => test("webhook route returns 503 when the GitHub webhook secret is unset", async () => { envModule.env.githubConnectorApp.webhookSecret = undefined const app = createWebhookApp() - const response = await app.request("http://den.local/api/webhooks/connectors/github", { + const response = await app.request("http://den.local/v1/webhooks/connectors/github", { body: "{}", headers: { "x-github-delivery": "delivery-2", @@ -72,7 +72,7 @@ test("webhook route accepts a valid signature and ignores unbound deliveries cle }, }) - const response = await app.request("http://den.local/api/webhooks/connectors/github", { + const response = await app.request("http://den.local/v1/webhooks/connectors/github", { body: payload, headers: { "x-github-delivery": "delivery-3", diff --git a/ee/apps/den-web/app/(den)/_lib/den-org.ts b/ee/apps/den-web/app/(den)/_lib/den-org.ts index a57b5842..0eae106b 100644 --- a/ee/apps/den-web/app/(den)/_lib/den-org.ts +++ b/ee/apps/den-web/app/(den)/_lib/den-org.ts @@ -349,10 +349,30 @@ export function getPluginRoute(orgSlug: string | null | undefined, pluginId: str return `${getPluginsRoute(orgSlug)}/${encodeURIComponent(pluginId)}`; } +export function getMarketplacesRoute(orgSlug?: string | null): string { + return `${getOrgDashboardRoute(orgSlug)}/marketplaces`; +} + +export function getMarketplaceRoute(orgSlug: string | null | undefined, marketplaceId: string): string { + return `${getMarketplacesRoute(orgSlug)}/${encodeURIComponent(marketplaceId)}`; +} + export function getIntegrationsRoute(orgSlug?: string | null): string { return `${getOrgDashboardRoute(orgSlug)}/integrations`; } +export function getGithubIntegrationRoute(orgSlug?: string | null): string { + return `${getIntegrationsRoute(orgSlug)}/github`; +} + +export function getGithubIntegrationSetupRoute(orgSlug: string | null | undefined, connectorInstanceId: string): string { + return `${getGithubIntegrationRoute(orgSlug)}?connectorInstanceId=${encodeURIComponent(connectorInstanceId)}`; +} + +export function getGithubIntegrationAccountRoute(orgSlug: string | null | undefined, connectorAccountId: string): string { + return `${getGithubIntegrationRoute(orgSlug)}?connectorAccountId=${encodeURIComponent(connectorAccountId)}`; +} + export function parseOrgListPayload(payload: unknown): { orgs: DenOrgSummary[]; activeOrgId: string | null; diff --git a/ee/apps/den-web/app/(den)/dashboard/integrations/github/page.tsx b/ee/apps/den-web/app/(den)/dashboard/integrations/github/page.tsx new file mode 100644 index 00000000..59ac9ec4 --- /dev/null +++ b/ee/apps/den-web/app/(den)/dashboard/integrations/github/page.tsx @@ -0,0 +1 @@ +export { default } from "../../../o/[orgSlug]/dashboard/integrations/github/page"; diff --git a/ee/apps/den-web/app/(den)/dashboard/marketplaces/[marketplaceId]/page.tsx b/ee/apps/den-web/app/(den)/dashboard/marketplaces/[marketplaceId]/page.tsx new file mode 100644 index 00000000..38433394 --- /dev/null +++ b/ee/apps/den-web/app/(den)/dashboard/marketplaces/[marketplaceId]/page.tsx @@ -0,0 +1 @@ +export { default } from "../../../o/[orgSlug]/dashboard/marketplaces/[marketplaceId]/page"; diff --git a/ee/apps/den-web/app/(den)/dashboard/marketplaces/page.tsx b/ee/apps/den-web/app/(den)/dashboard/marketplaces/page.tsx new file mode 100644 index 00000000..cf78dc2b --- /dev/null +++ b/ee/apps/den-web/app/(den)/dashboard/marketplaces/page.tsx @@ -0,0 +1 @@ +export { default } from "../../o/[orgSlug]/dashboard/marketplaces/page"; diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/github-integration-screen.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/github-integration-screen.tsx new file mode 100644 index 00000000..9f62868c --- /dev/null +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/github-integration-screen.tsx @@ -0,0 +1,1283 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; +import { + ArrowLeft, + CheckCircle2, + ExternalLink, + GitBranch, + Github, + LoaderCircle, + Puzzle, + RefreshCw, + Search, + Settings, + Sparkles, + Trash2, +} from "lucide-react"; +import { PaperMeshGradient } from "@openwork/ui/react"; +import { getGithubIntegrationRoute, getIntegrationsRoute } from "../../../../_lib/den-org"; +import { buttonVariants, DenButton } from "../../../../_components/ui/button"; +import { DashboardPageTemplate } from "../../../../_components/ui/dashboard-page-template"; +import { DenInput } from "../../../../_components/ui/input"; +import { + type IntegrationRepo, + useApplyGithubDiscovery, + useConnectorInstanceConfiguration, + useCreateGithubConnectorInstance, + useGithubAccountRepositories, + useGithubConnectorDiscovery, + useGithubInstallCompletion, + useIntegrations, + useRemoveConnectorInstance, + useSetConnectorInstanceAutoImport, +} from "./integration-data"; +import { useOrgDashboard } from "../_providers/org-dashboard-provider"; + +function parseInstallationId(value: string | null) { + if (!value) return null; + const numeric = Number(value); + return Number.isInteger(numeric) && numeric > 0 ? numeric : null; +} + +export function GithubIntegrationScreen() { + const router = useRouter(); + const searchParams = useSearchParams(); + const { orgSlug } = useOrgDashboard(); + const connectorInstanceId = searchParams.get("connectorInstanceId")?.trim() ?? null; + const connectorAccountId = searchParams.get("connectorAccountId")?.trim() ?? null; + const mode = searchParams.get("mode")?.trim() ?? null; + const installationId = parseInstallationId(searchParams.get("installation_id")); + const state = searchParams.get("state")?.trim() ?? null; + + if (connectorInstanceId) { + return ( + router.push(getIntegrationsRoute(orgSlug))} + /> + ); + } + + if (connectorAccountId) { + return ; + } + + if (installationId && state) { + return ; + } + + return ( + + + + ); +} + +function GithubInstallCompletionRedirect({ installationId, state }: { installationId: number; state: string }) { + const router = useRouter(); + const { orgSlug } = useOrgDashboard(); + const completionQuery = useGithubInstallCompletion({ installationId, state }); + + useEffect(() => { + if (!completionQuery.data) return; + const nextUrl = `${getGithubIntegrationRoute(orgSlug)}?connectorAccountId=${encodeURIComponent(completionQuery.data.connectorAccount.id)}`; + router.replace(nextUrl); + }, [completionQuery.data, orgSlug, router]); + + return ( + + {completionQuery.error ? ( + + ) : ( +
+
+ +
+

+ Finalizing your GitHub connection +

+

+ OpenWork is resolving the installation and loading accessible repositories. +

+
+ )} +
+ ); +} + +function GithubConnectorInstanceRouter({ + connectorInstanceId, + mode, + onBack, +}: { + connectorInstanceId: string; + mode: string | null; + onBack: () => void; +}) { + const configurationQuery = useConnectorInstanceConfiguration(connectorInstanceId); + + if (configurationQuery.isLoading) { + return ; + } + + const configuration = configurationQuery.data; + const hasConfigured = Boolean(configuration && configuration.configuredPlugins.length > 0); + const shouldRunDiscovery = !hasConfigured || mode === "rediscover"; + + if (shouldRunDiscovery) { + return ( + + ); + } + + if (!configuration) { + return ( + + ); + } + + return ( + + ); +} + +function ConfigurationLoadingState() { + return ( + +
+ +

Loading connector configuration…

+
+
+ ); +} + +function GithubConnectorInstanceManagePhase({ + configuration, + onBack, +}: { + configuration: { + autoImportNewPlugins: boolean; + configuredPlugins: Array<{ + id: string; + name: string; + description: string | null; + memberCount: number; + componentCounts: Record; + rootPath: string | null; + }>; + connectorInstanceId: string; + connectorInstanceName: string; + importedConfigObjectCount: number; + mappingCount: number; + repositoryFullName: string | null; + }; + onBack: () => void; +}) { + const router = useRouter(); + const searchParams = useSearchParams(); + const { orgSlug } = useOrgDashboard(); + const removeMutation = useRemoveConnectorInstance(); + const autoImportMutation = useSetConnectorInstanceAutoImport(); + const [confirmOpen, setConfirmOpen] = useState(false); + const [autoImportChecked, setAutoImportChecked] = useState(configuration.autoImportNewPlugins); + + useEffect(() => { + setAutoImportChecked(configuration.autoImportNewPlugins); + }, [configuration.autoImportNewPlugins]); + + const repoName = configuration.repositoryFullName ?? configuration.connectorInstanceName; + + async function handleRemove() { + await removeMutation.mutateAsync(configuration.connectorInstanceId); + onBack(); + } + + function handleRediscover() { + const params = new URLSearchParams(searchParams?.toString() ?? ""); + params.set("connectorInstanceId", configuration.connectorInstanceId); + params.set("mode", "rediscover"); + router.push(`${getGithubIntegrationRoute(orgSlug)}?${params.toString()}`); + } + + async function handleAutoImportToggle(nextValue: boolean) { + setAutoImportChecked(nextValue); + try { + await autoImportMutation.mutateAsync({ + autoImportNewPlugins: nextValue, + connectorInstanceId: configuration.connectorInstanceId, + }); + } catch { + setAutoImportChecked(configuration.autoImportNewPlugins); + } + } + + return ( + +
+ + + Re-run discovery + +
+ +
+
+
+

+ Imported plugins +

+

+ {configuration.configuredPlugins.length} plugin{configuration.configuredPlugins.length === 1 ? "" : "s"} +

+
+ +
+
+

+ Auto-import new plugins +

+

+ When new plugin structures appear in this repository on future pushes, OpenWork will discover and import them automatically. +

+ {autoImportMutation.error ? ( +

+ {autoImportMutation.error instanceof Error ? autoImportMutation.error.message : "Failed to update auto-import."} +

+ ) : null} +
+ void handleAutoImportToggle(next)} + /> +
+ + {configuration.configuredPlugins.length > 0 ? ( +
+ {configuration.configuredPlugins.map((plugin) => ( + + ))} +
+ ) : ( +
+

+ No plugins imported yet +

+

+ Re-run discovery to pick plugins from this repository. +

+
+ )} +
+ +
+

+ Danger zone +

+
+
+
+

+ Remove this repository +

+

+ Deletes everything OpenWork imported from this repository. The GitHub connection itself stays active. +

+
+ setConfirmOpen(true)} + loading={removeMutation.isPending} + > + Remove + +
+ {removeMutation.error ? ( +
+ {removeMutation.error instanceof Error ? removeMutation.error.message : "Failed to remove this repository."} +
+ ) : null} +
+
+
+ + { + if (!removeMutation.isPending) setConfirmOpen(false); + }} + onConfirm={() => { + void handleRemove(); + setConfirmOpen(false); + }} + /> +
+ ); +} + +const COMPONENT_TYPE_LABELS: Record = { + skill: { singular: "skill", plural: "skills" }, + agent: { singular: "agent", plural: "agents" }, + command: { singular: "command", plural: "commands" }, + hook: { singular: "hook", plural: "hooks" }, + mcp: { singular: "MCP server", plural: "MCP servers" }, + mcp_server: { singular: "MCP server", plural: "MCP servers" }, + lsp_server: { singular: "LSP server", plural: "LSP servers" }, + monitor: { singular: "monitor", plural: "monitors" }, + settings: { singular: "setting", plural: "settings" }, +}; + +function formatComponentCount(type: string, count: number) { + const label = COMPONENT_TYPE_LABELS[type] ?? { + singular: type.replace(/_/g, " "), + plural: `${type.replace(/_/g, " ")}s`, + }; + return `${count} ${count === 1 ? label.singular : label.plural}`; +} + +function PluginListItem({ plugin }: { + plugin: { + id: string; + name: string; + description: string | null; + componentCounts: Record; + rootPath: string | null; + }; +}) { + const orderedCountEntries = Object.entries(plugin.componentCounts) + .filter(([, count]) => count > 0) + .sort((a, b) => b[1] - a[1]); + const repoPath = plugin.rootPath === null + ? null + : plugin.rootPath === "" + ? "/" + : plugin.rootPath; + + return ( +
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+

+ {plugin.name} +

+ {plugin.description ? ( +

+ {plugin.description} +

+ ) : null} +
+ {repoPath ? ( + + {repoPath} + + ) : null} +
+ + {orderedCountEntries.length > 0 ? ( +
+ {orderedCountEntries.map(([type, count]) => ( + + {count} + + {formatComponentCount(type, count).replace(`${count} `, "")} + + + ))} +
+ ) : null} +
+
+
+ ); +} + +function Toggle({ + checked, + busy, + onChange, +}: { + checked: boolean; + busy: boolean; + onChange: (next: boolean) => void; +}) { + return ( + + ); +} + +function RemoveRepositoryConfirmDialog({ + open, + repoName, + pluginCount, + configObjectCount, + busy, + onClose, + onConfirm, +}: { + open: boolean; + repoName: string; + pluginCount: number; + configObjectCount: number; + busy: boolean; + onClose: () => void; + onConfirm: () => void; +}) { + if (!open) { + return null; + } + + return ( +
+
event.stopPropagation()} + > +
+
+ +
+
+

+ Remove {repoName}? +

+

+ This will delete everything OpenWork imported from this repository, including: +

+
    +
  • + + + {pluginCount} imported plugin{pluginCount === 1 ? "" : "s"} + +
  • +
  • + + + {configObjectCount} imported config object{configObjectCount === 1 ? "" : "s"} and all their versions + +
  • +
  • + + Connector mappings, source bindings, and sync history for this repository +
  • +
  • + + Any marketplace that was created solely from this repository and is now empty +
  • +
+

+ The GitHub connection itself stays active. You can re-add this repository later from the Integrations page. +

+
+
+ +
+ + Cancel + + + Remove repository + +
+
+
+ ); +} + +function GithubConnectedAccountSelectionPhase({ connectorAccountId }: { connectorAccountId: string }) { + const router = useRouter(); + const { orgSlug } = useOrgDashboard(); + const { data: connections = [] } = useIntegrations(); + const repositoriesQuery = useGithubAccountRepositories(connectorAccountId); + const connectMutation = useCreateGithubConnectorInstance(); + const [query, setQuery] = useState(""); + const [selectedRepoId, setSelectedRepoId] = useState(null); + + const connection = connections.find((entry) => entry.provider === "github" && entry.account.id === connectorAccountId) ?? null; + const configuredByFullName = new Map( + (connection?.repos ?? []) + .filter((entry) => entry.connectorInstanceId) + .map((entry) => [entry.fullName, entry.connectorInstanceId as string]), + ); + const allRepos = repositoriesQuery.data ?? []; + const unconfiguredRepos = allRepos.filter((repo) => !configuredByFullName.has(repo.fullName)); + const filteredRepos = useMemo(() => { + const normalized = query.trim().toLowerCase(); + const base = !normalized + ? allRepos + : allRepos.filter((repository) => + `${repository.fullName}\n${repository.description}`.toLowerCase().includes(normalized), + ); + + const priority = (repository: IntegrationRepo): number => { + if (configuredByFullName.has(repository.fullName)) return 0; + if (repository.manifestKind) return 1; + return 2; + }; + + return [...base].sort((left, right) => { + const diff = priority(left) - priority(right); + if (diff !== 0) return diff; + return left.fullName.localeCompare(right.fullName); + }); + }, [allRepos, configuredByFullName, query]); + const selectedRepo = unconfiguredRepos.find((repo) => repo.id === selectedRepoId) ?? null; + + async function handleConnectRepo() { + if (!connection?.account.installationId || !selectedRepo || !selectedRepo.defaultBranch) { + return; + } + + const repositoryId = Number(selectedRepo.id); + if (!Number.isInteger(repositoryId) || repositoryId <= 0) { + return; + } + + const result = await connectMutation.mutateAsync({ + branch: selectedRepo.defaultBranch, + connectorAccountId, + connectorInstanceName: selectedRepo.fullName, + installationId: connection.account.installationId, + repositoryFullName: selectedRepo.fullName, + repositoryId, + }); + router.replace(`${getGithubIntegrationRoute(orgSlug)}?connectorInstanceId=${encodeURIComponent(result.connectorInstanceId)}`); + } + + const accessLabel = connection?.account.repositorySelection === "selected" + ? "Only selected" + : "All repositories"; + const ownerLogin = connection?.account.ownerName ?? connection?.account.name ?? null; + const totalReadable = allRepos.length; + + return ( + +
+ + + Back + + {ownerLogin ? ( + + + @{ownerLogin} + + ) : null} +
+ + {repositoriesQuery.isLoading ? ( + + ) : repositoriesQuery.error ? ( + + ) : !connection ? ( + + ) : ( +
+
+

+ Repositories +

+
+ {unconfiguredRepos.length} unconfigured + · + {totalReadable} readable + · + {accessLabel} +
+
+ + {allRepos.length > 0 ? ( + <> + setQuery(event.currentTarget.value)} + placeholder="Search repositories" + /> + + {filteredRepos.length > 0 ? ( +
+ {filteredRepos.map((repository) => { + const configuredInstanceId = configuredByFullName.get(repository.fullName) ?? null; + return ( + { + if (!configuredInstanceId) setSelectedRepoId(repository.id); + }} + /> + ); + })} +
+ ) : ( +
+

+ No repositories matched your search +

+

+ Try a different search term, or pick from the list above. +

+
+ )} + + {selectedRepo ? ( +
+
+

+ {selectedRepo.fullName} +

+

+ + {selectedRepo.defaultBranch ?? "Default branch unavailable"} +

+
+ void handleConnectRepo()} + > + Start discovery + +
+ ) : unconfiguredRepos.length > 0 ? ( +
+ Select a repository above to start discovery. +
+ ) : null} + + ) : null} + + {unconfiguredRepos.length === 0 && allRepos.length > 0 ? ( +
+

+ Nothing left to configure here +

+

+ {connection.account.repositorySelection === "selected" + ? "This GitHub installation is limited to selected repositories, and OpenWork has already configured all of them." + : "This GitHub installation already has access to all repositories under this owner, and there are none unconfigured right now."} +

+ {connection.account.repositorySelection === "selected" && connection.account.manageUrl ? ( + + + Allow more repositories on GitHub + + ) : null} +
+ ) : null} + + {allRepos.length === 0 ? ( +
+

+ No repositories available +

+

+ This GitHub installation has no repositories OpenWork can read right now. +

+
+ ) : null} + + {connectMutation.error ? ( +
+ {connectMutation.error instanceof Error ? connectMutation.error.message : "Failed to create the connector instance."} +
+ ) : null} +
+ )} +
+ ); +} + +function manifestLabel(kind: "marketplace" | "plugin" | null, marketplacePluginCount: number | null): string | null { + if (kind === "marketplace") { + if (marketplacePluginCount && marketplacePluginCount > 1) { + return `Claude Marketplace · ${marketplacePluginCount} plugins`; + } + return "Claude Marketplace Detected"; + } + if (kind === "plugin") { + return "Claude Plugin Detected"; + } + return null; +} + +function RepositoryCard({ + fullName, + defaultBranch, + manifestKind, + marketplacePluginCount, + configuredInstanceId, + configuredHref, + selected, + onSelect, +}: { + fullName: string; + defaultBranch: string | null; + manifestKind: "marketplace" | "plugin" | null; + marketplacePluginCount: number | null; + configuredInstanceId: string | null; + configuredHref: string | null; + selected: boolean; + onSelect: () => void; +}) { + const badge = manifestLabel(manifestKind, marketplacePluginCount); + const isConfigured = Boolean(configuredInstanceId); + + const innerContent = ( +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+

+ {fullName} +

+ {isConfigured ? ( + + Configured + + ) : null} + {badge ? ( + + {badge} + + ) : null} +
+

+ {defaultBranch ? `${defaultBranch} branch` : "Default branch unavailable"} +

+
+ + {isConfigured ? ( + + + + ) : ( + + + + )} +
+
+ ); + + const baseClass = `group block w-full overflow-hidden rounded-2xl border text-left transition ${ + selected + ? "border-[#0f172a] bg-white shadow-[0_8px_24px_-12px_rgba(15,23,42,0.2)]" + : "border-gray-100 bg-white hover:-translate-y-0.5 hover:border-gray-200 hover:shadow-[0_8px_24px_-12px_rgba(15,23,42,0.12)]" + }`; + + if (isConfigured && configuredHref) { + return ( + + {innerContent} + + ); + } + + return ( + + ); +} + +function GithubDiscoveryPhase({ connectorInstanceId, onBack }: { connectorInstanceId: string; onBack: () => void }) { + const discoveryQuery = useGithubConnectorDiscovery(connectorInstanceId); + const applyMutation = useApplyGithubDiscovery(); + const [selectedKeys, setSelectedKeys] = useState([]); + const [autoImportNewPlugins, setAutoImportNewPlugins] = useState(true); + + useEffect(() => { + if (!discoveryQuery.data) { + return; + } + + setSelectedKeys( + discoveryQuery.data.discoveredPlugins + .filter((plugin) => plugin.supported && plugin.selectedByDefault) + .map((plugin) => plugin.key), + ); + // Discovery is a fresh-intent operation: default the toggle ON each time + // the user enters the flow, regardless of the previously saved value. + setAutoImportNewPlugins(true); + }, [discoveryQuery.data]); + + const selectedPlugins = (discoveryQuery.data?.discoveredPlugins ?? []).filter((plugin) => selectedKeys.includes(plugin.key)); + + async function handleApply() { + const result = await applyMutation.mutateAsync({ + autoImportNewPlugins, + connectorInstanceId, + selectedKeys, + }); + + if (result.createdPluginNames.length === 0 && result.createdMappingCount === 0) { + return; + } + } + + function toggleCandidate(key: string) { + setSelectedKeys((current) => current.includes(key) ? current.filter((value) => value !== key) : [...current, key]); + } + + const repoName = discoveryQuery.data?.repositoryFullName ?? null; + + return ( + +
+ + {discoveryQuery.data ? ( + + + {discoveryQuery.data.sourceRevisionRef.slice(0, 7)} + + ) : null} +
+ + {discoveryQuery.isLoading ? ( + + ) : discoveryQuery.error ? ( + + ) : applyMutation.isSuccess ? ( + + ) : discoveryQuery.data ? ( +
+ {discoveryQuery.data.warnings.length > 0 ? ( +
+ {discoveryQuery.data.warnings[0]} +
+ ) : null} + {discoveryQuery.data.treeSummary.truncated ? ( +
+ GitHub truncated the tree response. Discovery is based on the paths GitHub returned so far. +
+ ) : null} + +
+
+

+ Discovered plugins +

+

+ {selectedPlugins.length} of {discoveryQuery.data.discoveredPlugins.length} selected +

+
+ +
+
+

+ Auto-import new plugins +

+

+ When new plugin structures appear on future pushes, OpenWork will discover and import them automatically. +

+
+ setAutoImportNewPlugins(next)} + /> +
+ + {discoveryQuery.data.discoveredPlugins.length > 0 ? ( +
+ {discoveryQuery.data.discoveredPlugins.map((plugin) => ( + { + if (plugin.supported) toggleCandidate(plugin.key); + }} + /> + ))} +
+ ) : ( +
+

+ No Claude-compatible plugins detected +

+

+ OpenWork currently only supports Claude-compatible plugins and marketplaces. Add .claude-plugin/marketplace.json or .claude-plugin/plugin.json to this repository. +

+
+ )} +
+ + {discoveryQuery.data.discoveredPlugins.length > 0 ? ( +
+
+ {selectedPlugins.length === 0 + ? "Select at least one plugin to import." + : `This will create ${selectedPlugins.length} plugin${selectedPlugins.length === 1 ? "" : "s"} and their mappings in OpenWork.`} +
+ void handleApply()} + > + Create plugins and mappings + +
+ ) : null} + + {applyMutation.error ? ( +
+ {applyMutation.error instanceof Error ? applyMutation.error.message : "Failed to apply discovery results."} +
+ ) : null} +
+ ) : null} +
+ ); +} + +function DiscoveredPluginCard({ + plugin, + selected, + onToggle, +}: { + plugin: { + key: string; + displayName: string; + description: string | null; + rootPath: string; + sourceKind: string; + supported: boolean; + componentKinds: string[]; + warnings: string[]; + }; + selected: boolean; + onToggle: () => void; +}) { + const repoPath = plugin.rootPath === "" ? "/" : plugin.rootPath; + + const baseClass = `group block w-full overflow-hidden rounded-2xl border text-left transition ${ + !plugin.supported + ? "cursor-not-allowed border-gray-100 bg-white opacity-70" + : "border-gray-100 bg-white hover:-translate-y-0.5 hover:border-gray-200 hover:shadow-[0_8px_24px_-12px_rgba(15,23,42,0.12)]" + }`; + + return ( + + ); +} + +function DiscoveryAppliedState({ + createdPluginNames, + createdMappingCount, + materializedConfigObjectCount, + onDone, +}: { + createdPluginNames: string[]; + createdMappingCount: number; + materializedConfigObjectCount: number; + onDone: () => void; +}) { + return ( +
+
+
+ +
+
+

+ Discovery applied +

+

+ OpenWork created {createdPluginNames.length} plugin{createdPluginNames.length === 1 ? "" : "s"},{" "} + {createdMappingCount} mapping{createdMappingCount === 1 ? "" : "s"}, and{" "} + {materializedConfigObjectCount} imported config object{materializedConfigObjectCount === 1 ? "" : "s"}. +

+ + {createdPluginNames.length > 0 ? ( +
+ {createdPluginNames.map((name) => ( + + {name} + + ))} +
+ ) : null} +
+
+
+ Return to integrations +
+
+ ); +} + +function DiscoveryLoadingState() { + return ( +
+
+ +
+

+ Discovering marketplaces and plugins in your repository +

+

+ OpenWork is scanning the repo for Claude-compatible plugin and marketplace manifests. +

+
+ ); +} + +function StatePanel({ title, body }: { title: string; body: string }) { + return ( +
+

{title}

+

{body}

+
+ ); +} + + diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/integration-data.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/integration-data.tsx index 522fa28e..45f6875f 100644 --- a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/integration-data.tsx +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/integration-data.tsx @@ -1,39 +1,37 @@ "use client"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; - -/** - * Integrations model — the "connectors" layer that sits in front of Plugins. - * - * A plugin catalog is only populated once at least one integration is - * connected. Until then, plugins/skills/hooks/mcps all render empty. - * - * In this preview the OAuth flow is fully mocked: the UI walks through the - * same steps it would for a real integration (authorize → select account - * → select repositories → connecting → connected) but never leaves the app. - * State lives in the React Query cache, scoped to the dashboard subtree, so - * it is intentionally in-memory only. - */ - -// ── Types ────────────────────────────────────────────────────────────────── +import { getErrorMessage, requestJson } from "../../../../_lib/den-flow"; +import { useOrgDashboard } from "../_providers/org-dashboard-provider"; export type IntegrationProvider = "github" | "bitbucket"; export type IntegrationAccount = { id: string; + installationId?: number; + manageUrl?: string | null; name: string; - /** `user` or `org`/`workspace` */ kind: "user" | "org"; avatarInitial: string; + createdByName?: string | null; + ownerName?: string; + repositorySelection?: "all" | "selected"; }; +export type IntegrationRepoManifestKind = "marketplace" | "plugin" | null; + export type IntegrationRepo = { + connectorInstanceId?: string; id: string; name: string; fullName: string; description: string; - /** whether this repo contributes plugins when connected */ + hasPluginManifest?: boolean; + manifestKind?: IntegrationRepoManifestKind; + marketplacePluginCount?: number | null; hasPlugins: boolean; + defaultBranch?: string | null; + private?: boolean; }; export type ConnectedIntegration = { @@ -44,9 +42,80 @@ export type ConnectedIntegration = { connectedAt: string; }; -// ── Provider catalog (static UI metadata) ────────────────────────────────── +export type GithubInstallStartResult = { + redirectUrl: string; + state: string; +}; -export type IntegrationProviderMeta = { +export type GithubInstallCompleteResult = { + connectorAccount: { + id: string; + displayName: string; + metadata?: Record; + }; + repositories: IntegrationRepo[]; +}; + +export type GithubConnectorCreationResult = { + connectorInstanceId: string; + connectorTargetId: string; + repositoryFullName: string; +}; + +export type GithubDiscoveryStep = { + id: string; + label: string; + status: "completed" | "running" | "warning"; +}; + +export type GithubDiscoveredPlugin = { + componentKinds: string[]; + componentPaths: { + agents: string[]; + commands: string[]; + hooks: string[]; + lspServers: string[]; + mcpServers: string[]; + monitors: string[]; + settings: string[]; + skills: string[]; + }; + description: string | null; + displayName: string; + key: string; + manifestPath: string | null; + rootPath: string; + selectedByDefault: boolean; + sourceKind: string; + supported: boolean; + warnings: string[]; +}; + +export type GithubConnectorDiscoveryResult = { + autoImportNewPlugins: boolean; + classification: string; + connectorInstanceId: string; + connectorTargetId: string; + discoveredPlugins: GithubDiscoveredPlugin[]; + repositoryFullName: string; + sourceRevisionRef: string; + steps: GithubDiscoveryStep[]; + treeSummary: { + scannedEntryCount: number; + strategy: string; + truncated: boolean; + }; + warnings: string[]; +}; + +export type GithubDiscoveryApplyResult = { + autoImportNewPlugins: boolean; + createdMappingCount: number; + materializedConfigObjectCount: number; + createdPluginNames: string[]; +}; + +type IntegrationProviderMeta = { provider: IntegrationProvider; name: string; description: string; @@ -58,9 +127,9 @@ export const INTEGRATION_PROVIDERS: Record { + return typeof value === "object" && value !== null; +} + +function asString(value: unknown): string | null { + return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; +} + +function asNullableString(value: unknown): string | null { + return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; +} + +function parseGithubConnectorAccounts(payload: unknown) { + if (!isRecord(payload) || !Array.isArray(payload.items)) { + return []; + } + + return payload.items.flatMap((entry) => { + if (!isRecord(entry)) { + return []; + } + + const id = asString(entry.id); + const displayName = asString(entry.displayName); + const createdAt = asString(entry.createdAt); + const externalAccountRef = asString(entry.externalAccountRef); + const createdByName = asNullableString(entry.createdByName); + const metadata = isRecord(entry.metadata) ? entry.metadata : undefined; + if (!id || !displayName || !createdAt) { + return []; + } + + const remoteId = asString(entry.remoteId); + return [{ id, createdAt, createdByName, displayName, externalAccountRef, metadata, remoteId }]; + }); +} + +function parseGithubConnectorInstances(payload: unknown) { + if (!isRecord(payload) || !Array.isArray(payload.items)) { + return []; + } + + return payload.items.flatMap((entry) => { + if (!isRecord(entry)) { + return []; + } + + const id = asString(entry.id); + const connectorAccountId = asString(entry.connectorAccountId); + const remoteId = asNullableString(entry.remoteId); + const name = asString(entry.name); + if (!id || !connectorAccountId || !name) { + return []; + } + + return [{ connectorAccountId, id, name, remoteId }]; + }); +} + +function toAccountKind(metadata: Record | undefined): "org" | "user" { + return metadata?.accountType === "Organization" ? "org" : "user"; +} + +function toAvatarInitial(name: string) { + return name.trim().charAt(0).toUpperCase() || "?"; +} + +function toRepoName(fullName: string) { + const parts = fullName.split("/"); + return parts[parts.length - 1] ?? fullName; +} + +async function simulateLatency(ms = 450) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function fetchGithubConnections() { + const [accountsResult, instancesResult] = await Promise.all([ + requestJson("/v1/connector-accounts?connectorType=github&status=active&limit=100", { method: "GET" }, 15000), + requestJson("/v1/connector-instances?connectorType=github&status=active&limit=100", { method: "GET" }, 15000), + ]); + + if (!accountsResult.response.ok) { + throw new Error(getErrorMessage(accountsResult.payload, `Failed to load GitHub integrations (${accountsResult.response.status}).`)); + } + if (!instancesResult.response.ok) { + throw new Error(getErrorMessage(instancesResult.payload, `Failed to load GitHub connector instances (${instancesResult.response.status}).`)); + } + + const accounts = parseGithubConnectorAccounts(accountsResult.payload); + const instances = parseGithubConnectorInstances(instancesResult.payload); + + return accounts.map((account) => ({ + id: account.id, + provider: "github", + account: { + avatarInitial: toAvatarInitial(account.displayName), + createdByName: account.createdByName, + id: account.id, + installationId: account.remoteId ? Number(account.remoteId) : undefined, + kind: toAccountKind(account.metadata), + manageUrl: typeof account.metadata?.settingsUrl === "string" ? account.metadata.settingsUrl : null, + name: account.displayName, + ownerName: account.externalAccountRef ?? (typeof account.metadata?.accountLogin === "string" ? account.metadata.accountLogin : undefined), + repositorySelection: account.metadata?.repositorySelection === "selected" ? "selected" : "all", + }, + connectedAt: account.createdAt, + repos: instances + .filter((instance) => instance.connectorAccountId === account.id && instance.remoteId) + .map((instance) => ({ + connectorInstanceId: instance.id, + defaultBranch: null, + description: "Repository selected for connector sync.", + fullName: instance.remoteId ?? instance.name, + hasPlugins: true, + id: instance.id, + name: toRepoName(instance.remoteId ?? instance.name), + })), + })); +} + +async function fetchConnections(): Promise { + const githubConnections = await fetchGithubConnections(); + return [...githubConnections, ...mockConnections]; +} export function formatIntegrationTimestamp(value: string | null): string { if (!value) return "Recently connected"; @@ -163,30 +332,42 @@ export function getProviderMeta(provider: IntegrationProvider): IntegrationProvi return INTEGRATION_PROVIDERS[provider]; } -// ── Query keys ───────────────────────────────────────────────────────────── - export const integrationQueryKeys = { all: ["integrations"] as const, - list: () => [...integrationQueryKeys.all, "list"] as const, + list: (orgId?: string | null) => [...integrationQueryKeys.all, "list", orgId ?? "none"] as const, accounts: (provider: IntegrationProvider) => [...integrationQueryKeys.all, "accounts", provider] as const, repos: (provider: IntegrationProvider, accountId: string | null) => [...integrationQueryKeys.all, "repos", provider, accountId ?? "none"] as const, + githubInstall: (installationId: number | null) => [...integrationQueryKeys.all, "github-install", installationId ?? 0] as const, + githubDiscovery: (connectorInstanceId: string | null) => [...integrationQueryKeys.all, "github-discovery", connectorInstanceId ?? "none"] as const, + connectorInstanceConfiguration: (connectorInstanceId: string | null) => [...integrationQueryKeys.all, "connector-instance-config", connectorInstanceId ?? "none"] as const, }; -// ── Hooks ────────────────────────────────────────────────────────────────── +export type ConnectorInstanceConfiguredPlugin = { + id: string; + name: string; + description: string | null; + memberCount: number; + componentCounts: Record; + rootPath: string | null; +}; -async function simulateLatency(ms = 450) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -async function fetchConnections(): Promise { - await simulateLatency(150); - return [...mockConnections]; -} +export type ConnectorInstanceConfiguration = { + autoImportNewPlugins: boolean; + configuredPlugins: ConnectorInstanceConfiguredPlugin[]; + connectorInstanceId: string; + connectorInstanceName: string; + importedConfigObjectCount: number; + mappingCount: number; + repositoryFullName: string | null; +}; export function useIntegrations() { + const { orgId } = useOrgDashboard(); + return useQuery({ - queryKey: integrationQueryKeys.list(), + enabled: Boolean(orgId), + queryKey: integrationQueryKeys.list(orgId), queryFn: fetchConnections, }); } @@ -219,7 +400,395 @@ export function useIntegrationRepos(provider: IntegrationProvider, accountId: st }); } -// ── Mutations ────────────────────────────────────────────────────────────── +export function useStartGithubInstall() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (input: { returnPath: string }): Promise => { + const { response, payload } = await requestJson( + "/v1/connectors/github/install/start", + { + method: "POST", + body: JSON.stringify({ returnPath: input.returnPath }), + }, + 15000, + ); + + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to start GitHub install (${response.status}).`)); + } + + const item = isRecord(payload) && isRecord(payload.item) ? payload.item : null; + const redirectUrl = item ? asString(item.redirectUrl) : null; + const state = item ? asString(item.state) : null; + if (!redirectUrl || !state) { + throw new Error("GitHub install start response was incomplete."); + } + + return { redirectUrl, state }; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: integrationQueryKeys.all }); + }, + }); +} + +export function useGithubInstallCompletion(input: { installationId: number | null; state: string | null }) { + return useQuery({ + enabled: Number.isFinite(input.installationId ?? NaN) && (input.installationId ?? 0) > 0 && Boolean(input.state?.trim()), + queryKey: [...integrationQueryKeys.githubInstall(input.installationId), input.state ?? "no-state"] as const, + queryFn: async (): Promise => { + const { response, payload } = await requestJson( + "/v1/connectors/github/install/complete", + { + method: "POST", + body: JSON.stringify({ installationId: input.installationId, state: input.state }), + }, + 20000, + ); + + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to complete GitHub installation (${response.status}).`)); + } + + const item = isRecord(payload) && isRecord(payload.item) ? payload.item : null; + const connectorAccount = item && isRecord(item.connectorAccount) ? item.connectorAccount : null; + const repositories = item && Array.isArray(item.repositories) + ? item.repositories.flatMap((entry) => { + if (!isRecord(entry)) { + return []; + } + + const id = typeof entry.id === "number" ? String(entry.id) : asString(entry.id); + const fullName = asString(entry.fullName); + if (!id || !fullName) { + return []; + } + + const manifestKindValue = entry.manifestKind; + const manifestKind: IntegrationRepoManifestKind = manifestKindValue === "marketplace" || manifestKindValue === "plugin" + ? manifestKindValue + : null; + return [{ + defaultBranch: asNullableString(entry.defaultBranch), + description: manifestKind === "marketplace" + ? "Claude marketplace manifest detected." + : manifestKind === "plugin" + ? "Claude plugin manifest detected." + : "Repository available to connect.", + fullName, + hasPluginManifest: Boolean(entry.hasPluginManifest), + hasPlugins: Boolean(entry.hasPluginManifest), + id, + manifestKind, + marketplacePluginCount: typeof entry.marketplacePluginCount === "number" ? entry.marketplacePluginCount : null, + name: toRepoName(fullName), + private: Boolean(entry.private), + } satisfies IntegrationRepo]; + }) + : []; + + if (!connectorAccount || !asString(connectorAccount.id) || !asString(connectorAccount.displayName)) { + throw new Error("GitHub install completion response was incomplete."); + } + + return { + connectorAccount: { + displayName: asString(connectorAccount.displayName) ?? "GitHub", + id: asString(connectorAccount.id) ?? "", + metadata: isRecord(connectorAccount.metadata) ? connectorAccount.metadata : undefined, + }, + repositories, + }; + }, + }); +} + +export function useGithubAccountRepositories(connectorAccountId: string | null) { + return useQuery({ + enabled: Boolean(connectorAccountId), + queryKey: [...integrationQueryKeys.repos("github", connectorAccountId), "connected-account"] as const, + queryFn: async (): Promise => { + const { response, payload } = await requestJson( + `/v1/connectors/github/accounts/${encodeURIComponent(connectorAccountId ?? "")}/repositories?limit=100`, + { method: "GET" }, + 20000, + ); + + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to load GitHub repositories (${response.status}).`)); + } + + return isRecord(payload) && Array.isArray(payload.items) + ? payload.items.flatMap((entry) => { + if (!isRecord(entry)) { + return []; + } + + const id = typeof entry.id === "number" ? String(entry.id) : asString(entry.id); + const fullName = asString(entry.fullName); + if (!id || !fullName) { + return []; + } + + const manifestKindValue = entry.manifestKind; + const manifestKind: IntegrationRepoManifestKind = manifestKindValue === "marketplace" || manifestKindValue === "plugin" + ? manifestKindValue + : null; + return [{ + defaultBranch: asNullableString(entry.defaultBranch), + description: manifestKind === "marketplace" + ? "Claude marketplace manifest detected." + : manifestKind === "plugin" + ? "Claude plugin manifest detected." + : "Repository available to connect.", + fullName, + hasPluginManifest: Boolean(entry.hasPluginManifest), + hasPlugins: Boolean(entry.hasPluginManifest), + id, + manifestKind, + marketplacePluginCount: typeof entry.marketplacePluginCount === "number" ? entry.marketplacePluginCount : null, + name: toRepoName(fullName), + private: Boolean(entry.private), + } satisfies IntegrationRepo]; + }) + : []; + }, + }); +} + +export function useCreateGithubConnectorInstance() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (input: { + branch: string; + connectorAccountId: string; + connectorInstanceName: string; + installationId: number; + repositoryFullName: string; + repositoryId: number; + }): Promise => { + const { response, payload } = await requestJson( + "/v1/connectors/github/setup", + { + method: "POST", + body: JSON.stringify({ + branch: input.branch, + connectorAccountId: input.connectorAccountId, + connectorInstanceName: input.connectorInstanceName, + installationId: input.installationId, + mappings: [], + ref: `refs/heads/${input.branch}`, + repositoryFullName: input.repositoryFullName, + repositoryId: input.repositoryId, + }), + }, + 20000, + ); + + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to connect GitHub repository (${response.status}).`)); + } + + const item = isRecord(payload) && isRecord(payload.item) ? payload.item : null; + const connectorInstance = item && isRecord(item.connectorInstance) ? item.connectorInstance : null; + const connectorTarget = item && isRecord(item.connectorTarget) ? item.connectorTarget : null; + const connectorInstanceId = connectorInstance ? asString(connectorInstance.id) : null; + const connectorTargetId = connectorTarget ? asString(connectorTarget.id) : null; + const repositoryFullName = connectorTarget && isRecord(connectorTarget.targetConfigJson) + ? asString(connectorTarget.targetConfigJson.repositoryFullName) + : null; + + if (!connectorInstanceId || !connectorTargetId || !repositoryFullName) { + throw new Error("GitHub setup response was incomplete."); + } + + return { + connectorInstanceId, + connectorTargetId, + repositoryFullName, + }; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: integrationQueryKeys.all }); + queryClient.invalidateQueries({ queryKey: ["plugins"] }); + }, + }); +} + +export function useGithubConnectorDiscovery(connectorInstanceId: string | null) { + return useQuery({ + enabled: Boolean(connectorInstanceId), + queryKey: integrationQueryKeys.githubDiscovery(connectorInstanceId), + queryFn: async (): Promise => { + const { response, payload } = await requestJson( + `/v1/connector-instances/${encodeURIComponent(connectorInstanceId ?? "")}/discovery`, + { method: "GET" }, + 20000, + ); + + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to inspect GitHub repository (${response.status}).`)); + } + + const item = isRecord(payload) && isRecord(payload.item) ? payload.item : null; + const connectorInstance = item && isRecord(item.connectorInstance) ? item.connectorInstance : null; + const connectorTarget = item && isRecord(item.connectorTarget) ? item.connectorTarget : null; + const connectorInstanceIdValue = connectorInstance ? asString(connectorInstance.id) : null; + const connectorTargetId = connectorTarget ? asString(connectorTarget.id) : null; + const repositoryFullName = item ? asString(item.repositoryFullName) : null; + const sourceRevisionRef = item ? asString(item.sourceRevisionRef) : null; + const classification = item ? asString(item.classification) : null; + const discoveredPlugins = item && Array.isArray(item.discoveredPlugins) + ? item.discoveredPlugins.flatMap((entry) => { + if (!isRecord(entry)) { + return []; + } + + const key = asString(entry.key); + const displayName = asString(entry.displayName); + if (!key || !displayName) { + return []; + } + + const componentPaths = isRecord(entry.componentPaths) ? entry.componentPaths : {}; + const asStringArray = (value: unknown) => Array.isArray(value) + ? value.flatMap((candidate) => { + const normalized = asString(candidate); + return normalized ? [normalized] : []; + }) + : []; + + return [{ + componentKinds: Array.isArray(entry.componentKinds) + ? entry.componentKinds.flatMap((candidate) => { + const normalized = asString(candidate); + return normalized ? [normalized] : []; + }) + : [], + componentPaths: { + agents: asStringArray(componentPaths.agents), + commands: asStringArray(componentPaths.commands), + hooks: asStringArray(componentPaths.hooks), + lspServers: asStringArray(componentPaths.lspServers), + mcpServers: asStringArray(componentPaths.mcpServers), + monitors: asStringArray(componentPaths.monitors), + settings: asStringArray(componentPaths.settings), + skills: asStringArray(componentPaths.skills), + }, + description: asNullableString(entry.description), + displayName, + key, + manifestPath: asNullableString(entry.manifestPath), + rootPath: typeof entry.rootPath === "string" ? entry.rootPath : "", + selectedByDefault: Boolean(entry.selectedByDefault), + sourceKind: asString(entry.sourceKind) ?? "folder_inference", + supported: Boolean(entry.supported), + warnings: Array.isArray(entry.warnings) + ? entry.warnings.flatMap((candidate) => { + const normalized = asString(candidate); + return normalized ? [normalized] : []; + }) + : [], + } satisfies GithubDiscoveredPlugin]; + }) + : []; + const steps = item && Array.isArray(item.steps) + ? item.steps.flatMap((entry) => { + if (!isRecord(entry)) { + return []; + } + + const id = asString(entry.id); + const label = asString(entry.label); + const status = asString(entry.status); + if (!id || !label || (status !== "completed" && status !== "running" && status !== "warning")) { + return []; + } + + return [{ id, label, status } satisfies GithubDiscoveryStep]; + }) + : []; + const treeSummary = item && isRecord(item.treeSummary) + ? { + scannedEntryCount: typeof item.treeSummary.scannedEntryCount === "number" ? item.treeSummary.scannedEntryCount : 0, + strategy: asString(item.treeSummary.strategy) ?? "git-tree-recursive", + truncated: Boolean(item.treeSummary.truncated), + } + : { scannedEntryCount: 0, strategy: "git-tree-recursive", truncated: false }; + const warnings = item && Array.isArray(item.warnings) + ? item.warnings.flatMap((entry) => { + const normalized = asString(entry); + return normalized ? [normalized] : []; + }) + : []; + + const autoImportNewPlugins = item ? Boolean(item.autoImportNewPlugins) : false; + + if (!connectorInstanceIdValue || !connectorTargetId || !repositoryFullName || !sourceRevisionRef || !classification) { + throw new Error("GitHub discovery response was incomplete."); + } + + return { + autoImportNewPlugins, + classification, + connectorInstanceId: connectorInstanceIdValue, + connectorTargetId, + discoveredPlugins, + repositoryFullName, + sourceRevisionRef, + steps, + treeSummary, + warnings, + }; + }, + }); +} + +export function useApplyGithubDiscovery() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (input: { autoImportNewPlugins: boolean; connectorInstanceId: string; selectedKeys: string[] }): Promise => { + const { response, payload } = await requestJson( + `/v1/connector-instances/${encodeURIComponent(input.connectorInstanceId)}/discovery/apply`, + { + method: "POST", + body: JSON.stringify({ autoImportNewPlugins: input.autoImportNewPlugins, selectedKeys: input.selectedKeys }), + }, + 20000, + ); + + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to apply GitHub discovery (${response.status}).`)); + } + + const item = isRecord(payload) && isRecord(payload.item) ? payload.item : null; + const createdPlugins = item && Array.isArray(item.createdPlugins) + ? item.createdPlugins.flatMap((entry) => { + if (!isRecord(entry)) return []; + const name = asString(entry.name); + return name ? [name] : []; + }) + : []; + const createdMappingCount = item && Array.isArray(item.createdMappings) ? item.createdMappings.length : 0; + const materializedConfigObjectCount = item && Array.isArray(item.materializedConfigObjects) ? item.materializedConfigObjects.length : 0; + + return { + autoImportNewPlugins: item ? Boolean(item.autoImportNewPlugins) : input.autoImportNewPlugins, + createdMappingCount, + materializedConfigObjectCount, + createdPluginNames: createdPlugins, + }; + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: integrationQueryKeys.all }); + queryClient.invalidateQueries({ queryKey: ["plugins"] }); + queryClient.invalidateQueries({ queryKey: integrationQueryKeys.githubDiscovery(variables.connectorInstanceId) }); + }, + }); +} export type ConnectInput = { provider: IntegrationProvider; @@ -232,7 +801,6 @@ export function useConnectIntegration() { return useMutation({ mutationFn: async (input: ConnectInput): Promise => { - // Simulate the remote OAuth exchange + repo webhook install roundtrip. await simulateLatency(900); const connection: ConnectedIntegration = { @@ -243,7 +811,6 @@ export function useConnectIntegration() { connectedAt: new Date().toISOString(), }; - // Replace any prior connection on the same account (idempotent). mockConnections = [ ...mockConnections.filter( (entry) => !(entry.provider === input.provider && entry.account.id === input.account.id), @@ -254,7 +821,7 @@ export function useConnectIntegration() { return connection; }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: integrationQueryKeys.list() }); + queryClient.invalidateQueries({ queryKey: integrationQueryKeys.all }); queryClient.invalidateQueries({ queryKey: ["plugins"] }); }, }); @@ -265,12 +832,144 @@ export function useDisconnectIntegration() { return useMutation({ mutationFn: async (connectionId: string) => { + const isGithubConnection = connectionId.startsWith("cac_"); + if (isGithubConnection) { + const { response, payload } = await requestJson( + `/v1/connector-accounts/${encodeURIComponent(connectionId)}/disconnect`, + { + method: "POST", + body: JSON.stringify({ reason: "Disconnected from Den Web integrations." }), + }, + 20000, + ); + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to disconnect integration (${response.status}).`)); + } + return connectionId; + } + await simulateLatency(300); mockConnections = mockConnections.filter((entry) => entry.id !== connectionId); return connectionId; }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: integrationQueryKeys.list() }); + queryClient.invalidateQueries({ queryKey: integrationQueryKeys.all }); + queryClient.invalidateQueries({ queryKey: ["plugins"] }); + }, + }); +} + +export function useConnectorInstanceConfiguration(connectorInstanceId: string | null) { + return useQuery({ + enabled: Boolean(connectorInstanceId), + queryKey: integrationQueryKeys.connectorInstanceConfiguration(connectorInstanceId), + queryFn: async (): Promise => { + const { response, payload } = await requestJson( + `/v1/connector-instances/${encodeURIComponent(connectorInstanceId ?? "")}/configuration`, + { method: "GET" }, + 15000, + ); + + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to load connector instance (${response.status}).`)); + } + + const item = isRecord(payload) && isRecord(payload.item) ? payload.item : null; + const connectorInstance = item && isRecord(item.connectorInstance) ? item.connectorInstance : null; + const connectorInstanceIdValue = connectorInstance ? asString(connectorInstance.id) : null; + const connectorInstanceName = connectorInstance ? asString(connectorInstance.name) : null; + const remoteId = connectorInstance ? asString(connectorInstance.remoteId) : null; + if (!item || !connectorInstanceIdValue || !connectorInstanceName) { + throw new Error("Connector instance configuration response was incomplete."); + } + + const configuredPlugins = Array.isArray(item.configuredPlugins) + ? item.configuredPlugins.flatMap((entry) => { + if (!isRecord(entry)) return []; + const id = asString(entry.id); + const name = asString(entry.name); + if (!id || !name) return []; + const componentCounts: Record = {}; + if (isRecord(entry.componentCounts)) { + for (const [key, value] of Object.entries(entry.componentCounts)) { + if (typeof value === "number" && value > 0) { + componentCounts[key] = value; + } + } + } + const rootPathValue = entry.rootPath; + const rootPath = typeof rootPathValue === "string" ? rootPathValue : null; + return [{ + componentCounts, + description: asNullableString(entry.description), + id, + memberCount: typeof entry.memberCount === "number" ? entry.memberCount : 0, + name, + rootPath, + } satisfies ConnectorInstanceConfiguredPlugin]; + }) + : []; + + return { + autoImportNewPlugins: Boolean(item.autoImportNewPlugins), + configuredPlugins, + connectorInstanceId: connectorInstanceIdValue, + connectorInstanceName, + importedConfigObjectCount: typeof item.importedConfigObjectCount === "number" ? item.importedConfigObjectCount : 0, + mappingCount: typeof item.mappingCount === "number" ? item.mappingCount : 0, + repositoryFullName: remoteId, + }; + }, + }); +} + +export function useSetConnectorInstanceAutoImport() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (input: { autoImportNewPlugins: boolean; connectorInstanceId: string }) => { + const { response, payload } = await requestJson( + `/v1/connector-instances/${encodeURIComponent(input.connectorInstanceId)}/auto-import`, + { + method: "POST", + body: JSON.stringify({ autoImportNewPlugins: input.autoImportNewPlugins }), + }, + 15000, + ); + + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to update auto-import (${response.status}).`)); + } + + return input.autoImportNewPlugins; + }, + onSuccess: (_result, variables) => { + queryClient.invalidateQueries({ + queryKey: integrationQueryKeys.connectorInstanceConfiguration(variables.connectorInstanceId), + }); + }, + }); +} + +export function useRemoveConnectorInstance() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (connectorInstanceId: string) => { + const { response, payload } = await requestJson( + `/v1/connector-instances/${encodeURIComponent(connectorInstanceId)}/remove`, + { method: "POST" }, + 20000, + ); + + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to remove connector instance (${response.status}).`)); + } + + return connectorInstanceId; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: integrationQueryKeys.all }); queryClient.invalidateQueries({ queryKey: ["plugins"] }); }, }); diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/integrations-screen.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/integrations-screen.tsx index 9a87fba8..a984bfd0 100644 --- a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/integrations-screen.tsx +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/integrations-screen.tsx @@ -1,7 +1,9 @@ "use client"; +import Link from "next/link"; import { useState } from "react"; -import { Cable, Check, GitBranch, Unplug } from "lucide-react"; +import { Cable, Check, GitBranch, Github, Loader2, Plus, Settings, Trash2 } from "lucide-react"; +import { getGithubIntegrationAccountRoute, getGithubIntegrationRoute, getGithubIntegrationSetupRoute } from "../../../../_lib/den-org"; import { DenButton } from "../../../../_components/ui/button"; import { DashboardPageTemplate } from "../../../../_components/ui/dashboard-page-template"; import { IntegrationConnectDialog } from "./integration-connect-dialog"; @@ -12,13 +14,33 @@ import { formatIntegrationTimestamp, useDisconnectIntegration, useIntegrations, + useStartGithubInstall, } from "./integration-data"; +import { useOrgDashboard } from "../_providers/org-dashboard-provider"; export function IntegrationsScreen() { + const { orgSlug } = useOrgDashboard(); const { data: connections = [], isLoading, error } = useIntegrations(); const disconnect = useDisconnectIntegration(); + const startGithubInstall = useStartGithubInstall(); const [dialogProvider, setDialogProvider] = useState(null); + async function handleConnect(provider: IntegrationProvider) { + if (provider !== "github") { + setDialogProvider(provider); + return; + } + + try { + const result = await startGithubInstall.mutateAsync({ + returnPath: getGithubIntegrationRoute(orgSlug), + }); + window.location.assign(result.redirectUrl); + } catch { + return; + } + } + const connectedByProvider = connections.reduce< Partial> >((acc, connection) => { @@ -38,9 +60,13 @@ export function IntegrationsScreen() { description="Connect to GitHub or Bitbucket. Once an account is linked, plugins and skills from those repositories show up on the Plugins page." colors={["#E0F2FE", "#0C4A6E", "#0284C7", "#7DD3FC"]} > - {error ? ( + {error || startGithubInstall.error ? (
- {error instanceof Error ? error.message : "Failed to load integrations."} + {error instanceof Error + ? error.message + : startGithubInstall.error instanceof Error + ? startGithubInstall.error.message + : "Failed to load integrations."}
) : null} @@ -60,12 +86,18 @@ export function IntegrationsScreen() { className="overflow-hidden rounded-2xl border border-gray-100 bg-white" > {/* Header */} -
+

{meta.name}

- {isConnected ? ( + {meta.provider === "bitbucket" ? ( + + Coming soon + + ) : isConnected ? ( Connected @@ -76,44 +108,43 @@ export function IntegrationsScreen() { )}
-

{meta.description}

+ {!isConnected ? ( +

{meta.description}

+ ) : null}
setDialogProvider(meta.provider)} + loading={meta.provider === "github" && startGithubInstall.isPending} + disabled={meta.provider === "bitbucket"} + onClick={() => void handleConnect(meta.provider)} > - {isConnected ? "Connect another" : "Connect"} + {meta.provider === "bitbucket" + ? "Coming soon" + : isConnected + ? "Connect another" + : "Connect"}
{/* Body: connected accounts + repos */} {isConnected ? ( -
+
{providerConnections.map((connection) => ( window.location.assign(getGithubIntegrationAccountRoute(orgSlug, connection.account.id)) : undefined} onDisconnect={() => disconnect.mutate(connection.id)} busy={disconnect.isPending && disconnect.variables === connection.id} /> ))}
- ) : ( -
- Requires scopes: {meta.scopes.map((scope) => ( - - {scope} - - ))} -
- )} + ) : null}
); })} @@ -131,53 +162,205 @@ export function IntegrationsScreen() { function ConnectionRow({ connection, + orgSlug, + onConfigureNewRepo, onDisconnect, busy, }: { connection: ConnectedIntegration; + orgSlug: string | null; + onConfigureNewRepo?: () => void; onDisconnect: () => void; busy: boolean; }) { + const accountLogin = connection.account.ownerName ?? connection.account.name; + const connectedBy = connection.account.createdByName ?? null; + const avatarUrl = connection.provider === "github" && accountLogin + ? `https://github.com/${encodeURIComponent(accountLogin)}.png?size=80` + : null; + const [confirmOpen, setConfirmOpen] = useState(false); + return ( -
-
-
-
- {connection.account.avatarInitial} +
+ + +
+ +
+
+

@{accountLogin}

+ + {connection.account.kind === "user" ? "Personal" : "Organization"} +
-
-

{connection.account.name}

-

- {connection.account.kind === "user" ? "Personal" : "Organization"} · Connected{" "} - {formatIntegrationTimestamp(connection.connectedAt)} +

+ {connectedBy ? `Added by ${connectedBy}` : "Added recently"} + · {formatIntegrationTimestamp(connection.connectedAt)} +

+
+
+ +
+

+ Configured repositories +

+ + {connection.repos.length > 0 ? ( +
    + {connection.repos.map((repo) => ( +
  • + + + {repo.fullName} + + {connection.provider === "github" && repo.connectorInstanceId ? ( + + + + ) : null} +
  • + ))} +
+ ) : ( +

No repositories configured yet.

+ )} + + {onConfigureNewRepo ? ( + + ) : null} +
+ + { + if (!busy) setConfirmOpen(false); + }} + onConfirm={() => { + onDisconnect(); + setConfirmOpen(false); + }} + /> +
+ ); +} + +function Avatar({ url, fallback }: { url: string | null; fallback: string }) { + const [errored, setErrored] = useState(false); + + if (!url || errored) { + return ( +
+ {fallback} +
+ ); + } + + return ( + setErrored(true)} + className="h-11 w-11 shrink-0 rounded-full bg-gray-100 object-cover" + /> + ); +} + +function DisconnectConfirmDialog({ + open, + accountLogin, + repoCount, + busy, + onClose, + onConfirm, +}: { + open: boolean; + accountLogin: string; + repoCount: number; + busy: boolean; + onClose: () => void; + onConfirm: () => void; +}) { + if (!open) { + return null; + } + + return ( +
+
event.stopPropagation()} + > +
+
+ +
+
+

+ Remove @{accountLogin}? +

+

+ This will permanently delete everything OpenWork imported from this GitHub account, including: +

+
    +
  • + + + {repoCount} connected {repoCount === 1 ? "repository" : "repositories"} and their connector setup + +
  • +
  • + + All plugins and marketplaces created from those repos +
  • +
  • + + All imported config objects, versions and source bindings +
  • +
+

+ The GitHub App installation itself stays on GitHub. You can remove it from your GitHub account settings if you also want to revoke access.

- - Disconnect - -
- - {connection.repos.length > 0 ? ( -
- {connection.repos.map((repo) => ( - - - {repo.fullName} - - ))} +
+ + Cancel + + + Remove integration +
- ) : null} +
); } @@ -185,8 +368,8 @@ function ConnectionRow({ function ProviderLogo({ provider }: { provider: IntegrationProvider }) { if (provider === "github") { return ( -
- GH +
+
); } diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/marketplace-data.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/marketplace-data.tsx new file mode 100644 index 00000000..a369b3dd --- /dev/null +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/marketplace-data.tsx @@ -0,0 +1,265 @@ +"use client"; + +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { getErrorMessage, requestJson } from "../../../../_lib/den-flow"; + +export type DenMarketplace = { + id: string; + name: string; + description: string | null; + pluginCount: number; + createdAt: string; + updatedAt: string; +}; + +export const marketplaceQueryKeys = { + all: ["marketplaces"] as const, + list: () => [...marketplaceQueryKeys.all, "list"] as const, + detail: (id: string) => [...marketplaceQueryKeys.all, "detail", id] as const, + resolved: (id: string) => [...marketplaceQueryKeys.all, "resolved", id] as const, + access: (id: string) => [...marketplaceQueryKeys.all, "access", id] as const, +}; + +export type MarketplaceAccessRole = "viewer" | "editor" | "manager"; + +export type MarketplaceAccessGrant = { + id: string; + orgMembershipId: string | null; + teamId: string | null; + orgWide: boolean; + role: MarketplaceAccessRole; + createdAt: string; + removedAt: string | null; +}; + +export type MarketplaceResolvedSource = { + connectorAccountId: string; + connectorInstanceId: string; + accountLogin: string | null; + repositoryFullName: string; + branch: string | null; +}; + +export type MarketplacePluginSummary = { + id: string; + name: string; + description: string | null; + memberCount: number; + componentCounts: Record; +}; + +export type MarketplaceResolved = { + marketplace: DenMarketplace; + plugins: MarketplacePluginSummary[]; + source: MarketplaceResolvedSource | null; +}; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function asString(value: unknown): string | null { + return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; +} + +function parseMarketplace(entry: unknown): DenMarketplace | null { + if (!isRecord(entry)) return null; + const id = asString(entry.id); + const name = asString(entry.name); + const createdAt = asString(entry.createdAt); + const updatedAt = asString(entry.updatedAt); + if (!id || !name || !createdAt || !updatedAt) return null; + return { + id, + name, + description: asString(entry.description), + pluginCount: typeof entry.pluginCount === "number" ? entry.pluginCount : 0, + createdAt, + updatedAt, + }; +} + +export function useMarketplace(marketplaceId: string | null) { + return useQuery({ + enabled: Boolean(marketplaceId), + queryKey: marketplaceQueryKeys.resolved(marketplaceId ?? "none"), + queryFn: async (): Promise => { + const { response, payload } = await requestJson( + `/v1/marketplaces/${encodeURIComponent(marketplaceId ?? "")}/resolved`, + { method: "GET" }, + 15000, + ); + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to load marketplace (${response.status}).`)); + } + + const item = isRecord(payload) && isRecord(payload.item) ? payload.item : null; + const marketplace = item && isRecord(item.marketplace) ? parseMarketplace(item.marketplace) : null; + if (!item || !marketplace) { + throw new Error("Marketplace resolved response was incomplete."); + } + + const plugins = Array.isArray(item.plugins) + ? item.plugins.flatMap((entry) => { + if (!isRecord(entry)) return []; + const id = asString(entry.id); + const name = asString(entry.name); + if (!id || !name) return []; + const componentCounts: Record = {}; + if (isRecord(entry.componentCounts)) { + for (const [key, value] of Object.entries(entry.componentCounts)) { + if (typeof value === "number" && value > 0) { + componentCounts[key] = value; + } + } + } + return [{ + id, + name, + description: asString(entry.description), + memberCount: typeof entry.memberCount === "number" ? entry.memberCount : 0, + componentCounts, + } satisfies MarketplacePluginSummary]; + }) + : []; + + const sourceRecord = isRecord(item.source) ? item.source : null; + const source: MarketplaceResolvedSource | null = sourceRecord + ? { + connectorAccountId: asString(sourceRecord.connectorAccountId) ?? "", + connectorInstanceId: asString(sourceRecord.connectorInstanceId) ?? "", + accountLogin: asString(sourceRecord.accountLogin), + repositoryFullName: asString(sourceRecord.repositoryFullName) ?? "", + branch: asString(sourceRecord.branch), + } + : null; + + return { marketplace, plugins, source }; + }, + }); +} + +function parseAccessGrant(entry: unknown): MarketplaceAccessGrant | null { + if (!isRecord(entry)) return null; + const id = asString(entry.id); + const role = asString(entry.role); + if (!id || !role) return null; + if (role !== "viewer" && role !== "editor" && role !== "manager") return null; + return { + id, + orgMembershipId: asString(entry.orgMembershipId), + teamId: asString(entry.teamId), + orgWide: Boolean(entry.orgWide), + role, + createdAt: asString(entry.createdAt) ?? new Date().toISOString(), + removedAt: asString(entry.removedAt), + }; +} + +export function useMarketplaceAccess(marketplaceId: string | null) { + return useQuery({ + enabled: Boolean(marketplaceId), + queryKey: marketplaceQueryKeys.access(marketplaceId ?? "none"), + queryFn: async (): Promise => { + const { response, payload } = await requestJson( + `/v1/marketplaces/${encodeURIComponent(marketplaceId ?? "")}/access`, + { method: "GET" }, + 15000, + ); + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to load marketplace access (${response.status}).`)); + } + + const items = isRecord(payload) && Array.isArray(payload.items) ? payload.items : []; + return items + .map(parseAccessGrant) + .filter((value): value is MarketplaceAccessGrant => Boolean(value) && value?.removedAt === null); + }, + }); +} + +export function useGrantMarketplaceAccess() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (input: { + marketplaceId: string; + body: + | { orgWide: true; role?: MarketplaceAccessRole } + | { teamId: string; role?: MarketplaceAccessRole } + | { orgMembershipId: string; role?: MarketplaceAccessRole }; + }) => { + const body = { + role: input.body.role ?? "viewer", + ...("orgWide" in input.body ? { orgWide: true } : {}), + ...("teamId" in input.body ? { teamId: input.body.teamId } : {}), + ...("orgMembershipId" in input.body ? { orgMembershipId: input.body.orgMembershipId } : {}), + }; + const { response, payload } = await requestJson( + `/v1/marketplaces/${encodeURIComponent(input.marketplaceId)}/access`, + { method: "POST", body: JSON.stringify(body) }, + 15000, + ); + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to grant access (${response.status}).`)); + } + return input.marketplaceId; + }, + onSuccess: (marketplaceId) => { + queryClient.invalidateQueries({ queryKey: marketplaceQueryKeys.access(marketplaceId) }); + }, + }); +} + +export function useRevokeMarketplaceAccess() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (input: { marketplaceId: string; grantId: string }) => { + const { response, payload } = await requestJson( + `/v1/marketplaces/${encodeURIComponent(input.marketplaceId)}/access/${encodeURIComponent(input.grantId)}`, + { method: "DELETE" }, + 15000, + ); + if (response.status !== 204 && !response.ok) { + throw new Error(getErrorMessage(payload, `Failed to revoke access (${response.status}).`)); + } + return input.marketplaceId; + }, + onSuccess: (marketplaceId) => { + queryClient.invalidateQueries({ queryKey: marketplaceQueryKeys.access(marketplaceId) }); + }, + }); +} + +export function useMarketplaces() { + return useQuery({ + queryKey: marketplaceQueryKeys.list(), + queryFn: async () => { + const { response, payload } = await requestJson( + "/v1/marketplaces?status=active&limit=100", + { method: "GET" }, + 15000, + ); + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to load marketplaces (${response.status}).`)); + } + + const items = isRecord(payload) && Array.isArray(payload.items) ? payload.items : []; + return items + .map(parseMarketplace) + .filter((value): value is DenMarketplace => Boolean(value)); + }, + }); +} + +export function formatMarketplaceTimestamp(value: string | null): string { + if (!value) return "Recently added"; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return "Recently added"; + return new Intl.DateTimeFormat("en-US", { + month: "short", + day: "numeric", + year: "numeric", + }).format(date); +} diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/marketplace-detail-screen.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/marketplace-detail-screen.tsx new file mode 100644 index 00000000..5c4f27aa --- /dev/null +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/marketplace-detail-screen.tsx @@ -0,0 +1,568 @@ +"use client"; + +import Link from "next/link"; +import { useMemo, useRef, useState, useEffect } from "react"; +import { ArrowLeft, Check, GitBranch, Github, Globe, Loader2, Plus, Puzzle, Store, Users, X } from "lucide-react"; +import { PaperMeshGradient } from "@openwork/ui/react"; +import { + getGithubIntegrationSetupRoute, + getMarketplacesRoute, + getPluginRoute, +} from "../../../../_lib/den-org"; +import { useOrgDashboard } from "../_providers/org-dashboard-provider"; +import { + formatMarketplaceTimestamp, + type MarketplacePluginSummary, + useGrantMarketplaceAccess, + useMarketplace, + useMarketplaceAccess, + useRevokeMarketplaceAccess, +} from "./marketplace-data"; + +const COMPONENT_TYPE_LABELS: Record = { + skill: { singular: "skill", plural: "skills" }, + agent: { singular: "agent", plural: "agents" }, + command: { singular: "command", plural: "commands" }, + hook: { singular: "hook", plural: "hooks" }, + mcp: { singular: "MCP server", plural: "MCP servers" }, + mcp_server: { singular: "MCP server", plural: "MCP servers" }, + lsp_server: { singular: "LSP server", plural: "LSP servers" }, + monitor: { singular: "monitor", plural: "monitors" }, + settings: { singular: "setting", plural: "settings" }, +}; + +function componentTypeLabel(type: string, count: number) { + const label = COMPONENT_TYPE_LABELS[type] ?? { + singular: type.replace(/_/g, " "), + plural: `${type.replace(/_/g, " ")}s`, + }; + return count === 1 ? label.singular : label.plural; +} + +export function MarketplaceDetailScreen({ marketplaceId }: { marketplaceId: string }) { + const { orgSlug } = useOrgDashboard(); + const { data, isLoading, error } = useMarketplace(marketplaceId); + + if (isLoading && !data) { + return ( +
+
+ Loading marketplace… +
+
+ ); + } + + if (!data) { + return ( +
+
+ {error instanceof Error ? error.message : "That marketplace could not be found."} +
+
+ ); + } + + const { marketplace, plugins, source } = data; + + return ( +
+
+ + + Back + +
+ +
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+

+ {marketplace.name} +

+ + {plugins.length} plugin{plugins.length === 1 ? "" : "s"} + +
+ {marketplace.description ? ( +

{marketplace.description}

+ ) : null} +

+ Added {formatMarketplaceTimestamp(marketplace.createdAt)} +

+
+
+
+ +
+ {source ? ( +
+

+ Source +

+ +
+ +
+
+

+ {source.repositoryFullName} +

+

+ {source.accountLogin ? `@${source.accountLogin}` : "GitHub connector"} + {source.branch ? ( + <> + · + + {source.branch} + + ) : null} +

+
+ +
+ ) : null} + + + +
+
+

+ Plugins +

+

+ {plugins.length} plugin{plugins.length === 1 ? "" : "s"} +

+
+ + {plugins.length === 0 ? ( +
+

+ No plugins in this marketplace yet +

+

+ Plugins appear here as they're imported from the source repository. +

+
+ ) : ( +
+ {plugins.map((plugin) => ( + + ))} +
+ )} +
+
+
+ ); +} + +function MarketplaceAccessSection({ marketplaceId }: { marketplaceId: string }) { + const { orgContext } = useOrgDashboard(); + const accessQuery = useMarketplaceAccess(marketplaceId); + const grantMutation = useGrantMarketplaceAccess(); + const revokeMutation = useRevokeMarketplaceAccess(); + + const grants = accessQuery.data ?? []; + const orgWideGrant = grants.find((grant) => grant.orgWide) ?? null; + const teamGrants = grants.filter((grant) => Boolean(grant.teamId)); + const memberGrants = grants.filter((grant) => Boolean(grant.orgMembershipId)); + + const teamsById = useMemo( + () => new Map((orgContext?.teams ?? []).map((team) => [team.id, team])), + [orgContext?.teams], + ); + const membersById = useMemo( + () => new Map((orgContext?.members ?? []).map((member) => [member.id, member])), + [orgContext?.members], + ); + + const teamsAvailable = (orgContext?.teams ?? []).filter( + (team) => !teamGrants.some((grant) => grant.teamId === team.id), + ); + const membersAvailable = (orgContext?.members ?? []).filter( + (member) => !memberGrants.some((grant) => grant.orgMembershipId === member.id), + ); + + async function handleToggleOrgWide() { + if (orgWideGrant) { + await revokeMutation.mutateAsync({ marketplaceId, grantId: orgWideGrant.id }); + } else { + await grantMutation.mutateAsync({ + marketplaceId, + body: { orgWide: true, role: "viewer" }, + }); + } + } + + async function handleAddTeam(teamId: string) { + await grantMutation.mutateAsync({ + marketplaceId, + body: { teamId, role: "viewer" }, + }); + } + + async function handleAddMember(memberId: string) { + await grantMutation.mutateAsync({ + marketplaceId, + body: { orgMembershipId: memberId, role: "viewer" }, + }); + } + + async function handleRevoke(grantId: string) { + await revokeMutation.mutateAsync({ marketplaceId, grantId }); + } + + const busy = accessQuery.isLoading || grantMutation.isPending || revokeMutation.isPending; + + return ( +
+
+

+ Who can access this +

+ {busy ? : null} +
+ +
+ + + { + const team = grant.teamId ? teamsById.get(grant.teamId) : null; + return { + grantId: grant.id, + title: team?.name ?? "Removed team", + subtitle: team ? `${team.memberIds.length} member${team.memberIds.length === 1 ? "" : "s"}` : null, + }; + })} + availableOptions={teamsAvailable.map((team) => ({ + id: team.id, + label: team.name, + subtitle: `${team.memberIds.length} member${team.memberIds.length === 1 ? "" : "s"}`, + }))} + availableEmptyLabel="All teams already have access" + onAdd={(id) => void handleAddTeam(id)} + onRemove={(id) => void handleRevoke(id)} + disabled={grantMutation.isPending || revokeMutation.isPending} + /> + + { + const member = grant.orgMembershipId ? membersById.get(grant.orgMembershipId) : null; + return { + grantId: grant.id, + title: member?.user.name ?? "Removed member", + subtitle: member?.user.email ?? null, + }; + })} + availableOptions={membersAvailable.map((member) => ({ + id: member.id, + label: member.user.name, + subtitle: member.user.email, + }))} + availableEmptyLabel="Everyone already has access" + onAdd={(id) => void handleAddMember(id)} + onRemove={(id) => void handleRevoke(id)} + disabled={grantMutation.isPending || revokeMutation.isPending} + /> +
+ + {accessQuery.error ? ( +

+ {accessQuery.error instanceof Error ? accessQuery.error.message : "Failed to load access."} +

+ ) : null} + {grantMutation.error ? ( +

+ {grantMutation.error instanceof Error ? grantMutation.error.message : "Failed to grant access."} +

+ ) : null} + {revokeMutation.error ? ( +

+ {revokeMutation.error instanceof Error ? revokeMutation.error.message : "Failed to revoke access."} +

+ ) : null} +
+ ); +} + +function AccessRowGroup({ + label, + icon: Icon, + emptyLabel, + items, + availableOptions, + availableEmptyLabel, + onAdd, + onRemove, + disabled, +}: { + label: string; + icon: React.ComponentType<{ className?: string }>; + emptyLabel: string; + items: Array<{ grantId: string; title: string; subtitle: string | null }>; + availableOptions: Array<{ id: string; label: string; subtitle: string }>; + availableEmptyLabel: string; + onAdd: (id: string) => void; + onRemove: (grantId: string) => void; + disabled: boolean; +}) { + return ( +
+
+ +

{label}

+
+ + {items.length === 0 ? ( +

{emptyLabel}

+ ) : ( +
+ {items.map((entry) => ( + + {entry.title} + {entry.subtitle ? ( + · {entry.subtitle} + ) : null} + + + ))} +
+ )} + + +
+ ); +} + +function AccessAddPicker({ + label, + options, + emptyLabel, + disabled, + onAdd, +}: { + label: string; + options: Array<{ id: string; label: string; subtitle: string }>; + emptyLabel: string; + disabled: boolean; + onAdd: (id: string) => void; +}) { + const [open, setOpen] = useState(false); + const [query, setQuery] = useState(""); + const ref = useRef(null); + + useEffect(() => { + if (!open) return; + function handle(event: MouseEvent) { + if (ref.current && !ref.current.contains(event.target as Node)) { + setOpen(false); + } + } + document.addEventListener("mousedown", handle); + return () => document.removeEventListener("mousedown", handle); + }, [open]); + + const filtered = useMemo(() => { + const normalized = query.trim().toLowerCase(); + if (!normalized) return options; + return options.filter( + (option) => + option.label.toLowerCase().includes(normalized) || + option.subtitle.toLowerCase().includes(normalized), + ); + }, [options, query]); + + if (options.length === 0) { + return ( +

{emptyLabel}

+ ); + } + + return ( +
+ + + {open ? ( +
+
+ setQuery(event.target.value)} + placeholder={`Search ${label.toLowerCase()}...`} + className="w-full bg-transparent text-[12.5px] text-gray-900 placeholder:text-gray-400 focus:outline-none" + autoFocus + /> +
+
+ {filtered.length === 0 ? ( +

No matches

+ ) : ( + filtered.map((option) => ( + + )) + )} +
+
+ ) : null} +
+ ); +} + +function MarketplacePluginCard({ + orgSlug, + plugin, +}: { + orgSlug: string | null; + plugin: MarketplacePluginSummary; +}) { + const orderedCountEntries = Object.entries(plugin.componentCounts) + .filter(([, count]) => count > 0) + .sort((a, b) => b[1] - a[1]); + + return ( + +
+
+
+ +
+
+
+ +
+
+
+
+

+ {plugin.name} +

+ {plugin.description ? ( +

+ {plugin.description} +

+ ) : null} + + {orderedCountEntries.length > 0 ? ( +
+ {orderedCountEntries.map(([type, count]) => ( + + {count} + {componentTypeLabel(type, count)} + + ))} +
+ ) : ( +

+ {plugin.memberCount} imported object{plugin.memberCount === 1 ? "" : "s"} +

+ )} +
+
+ + ); +} diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/marketplaces-screen.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/marketplaces-screen.tsx new file mode 100644 index 00000000..53b15ee3 --- /dev/null +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/marketplaces-screen.tsx @@ -0,0 +1,154 @@ +"use client"; + +import Link from "next/link"; +import { useMemo, useState } from "react"; +import { Cable, Search, Store } from "lucide-react"; +import { PaperMeshGradient } from "@openwork/ui/react"; +import { DashboardPageTemplate } from "../../../../_components/ui/dashboard-page-template"; +import { DenInput } from "../../../../_components/ui/input"; +import { buttonVariants } from "../../../../_components/ui/button"; +import { getIntegrationsRoute, getMarketplaceRoute } from "../../../../_lib/den-org"; +import { useOrgDashboard } from "../_providers/org-dashboard-provider"; +import { useHasAnyIntegration } from "./integration-data"; +import { formatMarketplaceTimestamp, useMarketplaces } from "./marketplace-data"; + +export function MarketplacesScreen() { + const { orgSlug } = useOrgDashboard(); + const { data: marketplaces = [], isLoading, error } = useMarketplaces(); + const { hasAny: hasAnyIntegration, isLoading: integrationsLoading } = useHasAnyIntegration(); + const [query, setQuery] = useState(""); + + const normalizedQuery = query.trim().toLowerCase(); + const filtered = useMemo(() => { + if (!normalizedQuery) return marketplaces; + return marketplaces.filter((marketplace) => + `${marketplace.name}\n${marketplace.description ?? ""}`.toLowerCase().includes(normalizedQuery), + ); + }, [marketplaces, normalizedQuery]); + + return ( + +
+ setQuery(event.target.value)} + placeholder="Search marketplaces..." + /> +
+ + {error ? ( +
+ {error instanceof Error ? error.message : "Failed to load marketplaces."} +
+ ) : null} + + {isLoading || integrationsLoading ? ( +
+ Loading marketplaces… +
+ ) : !hasAnyIntegration ? ( + + ) : filtered.length === 0 ? ( + + ) : ( +
+ {filtered.map((marketplace) => ( + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+

+ {marketplace.name} +

+ + {marketplace.pluginCount} plugin{marketplace.pluginCount === 1 ? "" : "s"} + +
+ {marketplace.description ? ( +

+ {marketplace.description} +

+ ) : null} +

+ Added {formatMarketplaceTimestamp(marketplace.createdAt)} +

+
+
+ + ))} +
+ )} +
+ ); +} + +function EmptyState({ + title, + description, + action, +}: { + title: string; + description: string; + action?: { href: string; label: string; icon: React.ComponentType<{ className?: string }> }; +}) { + const ActionIcon = action?.icon; + return ( +
+

{title}

+

{description}

+ {action ? ( +
+ + {ActionIcon ?
+ ) : null} +
+ ); +} + +function ConnectIntegrationEmptyState({ integrationsHref }: { integrationsHref: string }) { + return ( + + ); +} diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/org-dashboard-shell.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/org-dashboard-shell.tsx index 127554cb..135e8cb6 100644 --- a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/org-dashboard-shell.tsx +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/org-dashboard-shell.tsx @@ -6,6 +6,7 @@ import { useMemo, useState } from "react"; import { BookOpen, Bot, + Cable, CreditCard, Cpu, FileText, @@ -13,8 +14,10 @@ import { KeyRound, LogOut, MessageSquare, + Puzzle, Share2, SlidersHorizontal, + Store, Users, } from "lucide-react"; import { useDenFlow } from "../../../../_providers/den-flow-provider"; @@ -29,6 +32,7 @@ import { getMembersRoute, getOrgDashboardRoute, getOrgSettingsRoute, + getMarketplacesRoute, getPluginsRoute, getSharedSetupsRoute, getSkillHubsRoute, @@ -117,6 +121,9 @@ function getDashboardPageTitle(pathname: string, orgSlug: string | null) { if (pathname.startsWith(getPluginsRoute(orgSlug))) { return "Plugins"; } + if (pathname.startsWith(getMarketplacesRoute(orgSlug))) { + return "Marketplaces"; + } if (pathname.startsWith(getIntegrationsRoute(orgSlug))) { return "Integrations"; } @@ -171,16 +178,32 @@ export function OrgDashboardShell({ children }: { children: React.ReactNode }) { icon: Bot, badge: "Alpha", }, - { - href: activeOrg ? getCustomLlmProvidersRoute(activeOrg.slug) : "#", - label: "LLM Providers", - icon: Cpu, - badge: "New", - }, + { + href: activeOrg ? getCustomLlmProvidersRoute(activeOrg.slug) : "#", + label: "LLM Providers", + icon: Cpu, + }, { href: activeOrg ? getSkillHubsRoute(activeOrg.slug) : "#", label: "Skill Hubs", icon: BookOpen, + }, + { + href: activeOrg ? getIntegrationsRoute(activeOrg.slug) : "#", + label: "Integrations", + icon: Cable, + badge: "New", + }, + { + href: activeOrg ? getMarketplacesRoute(activeOrg.slug) : "#", + label: "Marketplaces", + icon: Store, + badge: "New", + }, + { + href: activeOrg ? getPluginsRoute(activeOrg.slug) : "#", + label: "Plugins", + icon: Puzzle, badge: "New", }, { diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/plugin-data.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/plugin-data.tsx index d022b79c..862f7398 100644 --- a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/plugin-data.tsx +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/plugin-data.tsx @@ -1,6 +1,7 @@ "use client"; -import { useQuery, useQueryClient, type QueryClient } from "@tanstack/react-query"; +import { useQuery, type QueryClient } from "@tanstack/react-query"; +import { getErrorMessage, requestJson } from "../../../../_lib/den-flow"; import { type ConnectedIntegration, integrationQueryKeys, @@ -85,16 +86,22 @@ export type PluginSource = | { type: "github"; repo: string } | { type: "local"; path: string }; +export type PluginMarketplaceRef = { + id: string; + name: string; +}; + export type DenPlugin = { id: string; name: string; slug: string; description: string; - version: string; + version: string | null; author: string; category: PluginCategory; installed: boolean; source: PluginSource; + marketplaces?: PluginMarketplaceRef[]; skills: PluginSkill[]; hooks: PluginHook[]; mcps: PluginMcp[]; @@ -427,28 +434,190 @@ export const pluginQueryKeys = { detail: (id: string) => [...pluginQueryKeys.all, "detail", id] as const, }; +function slugifyPluginName(value: string) { + return value + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, "") || "plugin"; +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function asString(value: unknown): string | null { + return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; +} + +function parseMembershipConfigObject(entry: unknown) { + if (!isRecord(entry) || !isRecord(entry.configObject)) { + return null; + } + + const configObject = entry.configObject; + const id = asString(configObject.id); + const title = asString(configObject.title); + const description = asString(configObject.description) ?? "Imported from a connected repository."; + const objectType = asString(configObject.objectType); + const currentRelativePath = asString(configObject.currentRelativePath); + const latestVersion = isRecord(configObject.latestVersion) ? configObject.latestVersion : null; + const normalizedPayload = latestVersion && isRecord(latestVersion.normalizedPayloadJson) + ? latestVersion.normalizedPayloadJson + : null; + + if (!id || !title || !objectType) { + return null; + } + + return { + currentRelativePath, + description, + id, + normalizedPayload, + objectType, + title, + }; +} + +function derivePluginCategory(input: { agents: PluginAgent[]; commands: PluginCommand[]; hooks: PluginHook[]; mcps: PluginMcp[]; skills: PluginSkill[] }): PluginCategory { + if (input.mcps.length > 0 || input.hooks.length > 0) { + return "integrations"; + } + if (input.agents.length > 0 || input.commands.length > 0 || input.skills.length > 0) { + return "workflows"; + } + return "output-styles"; +} + +function parsePluginHookEvent(value: string | null): PluginHookEvent { + switch (value) { + case "PreToolUse": + case "PostToolUse": + case "SessionStart": + case "SessionEnd": + case "UserPromptSubmit": + case "Notification": + case "Stop": + return value; + default: + return "Notification"; + } +} + +async function fetchResolvedPlugin(id: string): Promise { + const [pluginResult, membershipsResult] = await Promise.all([ + requestJson(`/v1/plugins/${encodeURIComponent(id)}`, { method: "GET" }, 15000), + requestJson(`/v1/plugins/${encodeURIComponent(id)}/resolved`, { method: "GET" }, 15000), + ]); + + if (!pluginResult.response.ok) { + throw new Error(getErrorMessage(pluginResult.payload, `Failed to load plugin (${pluginResult.response.status}).`)); + } + if (!membershipsResult.response.ok) { + throw new Error(getErrorMessage(membershipsResult.payload, `Failed to load plugin contents (${membershipsResult.response.status}).`)); + } + + const pluginItem = isRecord(pluginResult.payload) && isRecord(pluginResult.payload.item) ? pluginResult.payload.item : null; + if (!pluginItem) { + return null; + } + + const pluginId = asString(pluginItem.id); + const name = asString(pluginItem.name); + if (!pluginId || !name) { + return null; + } + + const membershipItems = isRecord(membershipsResult.payload) && Array.isArray(membershipsResult.payload.items) + ? membershipsResult.payload.items.map(parseMembershipConfigObject).filter((value): value is NonNullable => Boolean(value)) + : []; + + const skills = membershipItems + .filter((item) => item.objectType === "skill") + .map((item) => ({ id: item.id, name: item.title, description: item.description } satisfies PluginSkill)); + const agents = membershipItems + .filter((item) => item.objectType === "agent") + .map((item) => ({ id: item.id, name: item.title, description: item.description } satisfies PluginAgent)); + const commands = membershipItems + .filter((item) => item.objectType === "command") + .map((item) => ({ id: item.id, name: item.currentRelativePath?.split("/").pop()?.replace(/\.md$/i, "") ?? item.title, description: item.description } satisfies PluginCommand)); + const hooks = membershipItems + .filter((item) => item.objectType === "hook") + .map((item) => ({ + description: item.description, + event: parsePluginHookEvent(asString(item.normalizedPayload?.event) ?? item.title), + id: item.id, + matcher: asString(item.normalizedPayload?.matcher), + } satisfies PluginHook)); + const mcps = membershipItems + .filter((item) => item.objectType === "mcp") + .map((item) => ({ + description: item.description, + id: item.id, + name: item.title, + toolCount: typeof item.normalizedPayload?.toolCount === "number" ? item.normalizedPayload.toolCount : 0, + transport: (asString(item.normalizedPayload?.transport) as PluginMcpTransport | null) ?? "stdio", + } satisfies PluginMcp)); + + const marketplaces = Array.isArray(pluginItem.marketplaces) + ? pluginItem.marketplaces.flatMap((entry) => { + if (!isRecord(entry)) return []; + const id = asString(entry.id); + const marketplaceName = asString(entry.name); + if (!id || !marketplaceName) return []; + return [{ id, name: marketplaceName } satisfies PluginMarketplaceRef]; + }) + : []; + + return { + agents, + author: "Connected repository", + category: derivePluginCategory({ agents, commands, hooks, mcps, skills }), + commands, + description: asString(pluginItem.description) ?? "Imported from a connected repository.", + hooks, + id: pluginId, + installed: true, + marketplaces, + mcps, + name, + requiresProvider: "github", + skills, + slug: slugifyPluginName(name), + source: marketplaces[0] + ? { type: "marketplace", marketplace: marketplaces[0].name } + : { type: "github", repo: "Connected repository" }, + updatedAt: asString(pluginItem.updatedAt) ?? new Date().toISOString(), + version: null, + } satisfies DenPlugin; +} + export function usePlugins() { - const queryClient = useQueryClient(); return useQuery({ queryKey: pluginQueryKeys.list(), queryFn: async () => { - await new Promise((resolve) => setTimeout(resolve, 180)); - const connectedProviders = readConnectedProviders(queryClient); - return filterByConnectedProviders(MOCK_PLUGINS, connectedProviders); + const { response, payload } = await requestJson("/v1/plugins?status=active&limit=100", { method: "GET" }, 20000); + if (!response.ok) { + throw new Error(getErrorMessage(payload, `Failed to load plugins (${response.status}).`)); + } + + const items = isRecord(payload) && Array.isArray(payload.items) ? payload.items : []; + const pluginIds = items.flatMap((entry) => { + const id = isRecord(entry) ? asString(entry.id) : null; + return id ? [id] : []; + }); + + const plugins = await Promise.all(pluginIds.map((id) => fetchResolvedPlugin(id))); + return plugins.filter((plugin): plugin is DenPlugin => Boolean(plugin)); }, }); } export function usePlugin(id: string) { - const queryClient = useQueryClient(); return useQuery({ queryKey: pluginQueryKeys.detail(id), - queryFn: async () => { - await new Promise((resolve) => setTimeout(resolve, 120)); - const connectedProviders = readConnectedProviders(queryClient); - const visible = filterByConnectedProviders(MOCK_PLUGINS, connectedProviders); - return visible.find((plugin) => plugin.id === id) ?? null; - }, + queryFn: async () => fetchResolvedPlugin(id), enabled: Boolean(id), }); } diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/plugin-detail-screen.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/plugin-detail-screen.tsx index d97bc4d7..4ddd88ab 100644 --- a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/plugin-detail-screen.tsx +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/plugin-detail-screen.tsx @@ -1,9 +1,9 @@ "use client"; import Link from "next/link"; -import { ArrowLeft, FileText, Puzzle, Server, Terminal, Users, Webhook } from "lucide-react"; +import { ArrowLeft, FileText, Puzzle, Server, Store, Terminal, Users, Webhook } from "lucide-react"; import { PaperMeshGradient } from "@openwork/ui/react"; -import { buttonVariants } from "../../../../_components/ui/button"; + import { getPluginsRoute } from "../../../../_lib/den-org"; import { useOrgDashboard } from "../_providers/org-dashboard-provider"; import { @@ -14,8 +14,6 @@ import { type PluginAgent, type PluginCommand, formatPluginTimestamp, - getPluginCategoryLabel, - getPluginPartsSummary, usePlugin, } from "./plugin-data"; @@ -25,9 +23,9 @@ export function PluginDetailScreen({ pluginId }: { pluginId: string }) { if (isLoading && !plugin) { return ( -
-
- Loading plugin details... +
+
+ Loading plugin details…
); @@ -35,17 +33,24 @@ export function PluginDetailScreen({ pluginId }: { pluginId: string }) { if (!plugin) { return ( -
-
+
+
{error instanceof Error ? error.message : "That plugin could not be found."}
); } + const marketplaces = plugin.marketplaces ?? []; + const missingLabels: string[] = []; + if (plugin.skills.length === 0) missingLabels.push("skills"); + if (plugin.agents.length === 0) missingLabels.push("agents"); + if (plugin.commands.length === 0) missingLabels.push("commands"); + if (plugin.hooks.length === 0) missingLabels.push("hooks"); + if (plugin.mcps.length === 0) missingLabels.push("MCP servers"); + return ( -
- {/* Nav */} +
Back - -
-
- {/* ── Main card ── */} -
- {/* Gradient header — seeded by plugin id to match the list card */} -
+
+
+
-
- +
+
+ +
-
- {/* Title + description + meta */} +
-

{plugin.name}

- - v{plugin.version} - - by {plugin.author} +

+ {plugin.name} +

+ {plugin.version ? ( + + v{plugin.version} + + ) : null}
-

{plugin.description}

-

- {getPluginPartsSummary(plugin)} · Updated {formatPluginTimestamp(plugin.updatedAt)} -

+ {plugin.description ? ( +

{plugin.description}

+ ) : null} - {/* Skills */} - renderSkillRow(skill)} - /> + {marketplaces.length > 0 ? ( +
+ {marketplaces.map((marketplace) => ( + + + {marketplace.name} + + ))} +
+ ) : null} - {/* Hooks */} - renderHookRow(hook)} - /> - - {/* MCP Servers */} - renderMcpRow(mcp)} - /> - - {/* Agents */} - renderAgentRow(agent)} - /> - - {/* Commands */} - renderCommandRow(command)} - /> -
-
- - {/* ── Sidebar ── */} -
+ - {/* Status */} -
-

Status

-

- {plugin.installed ? "Installed" : "Not installed"} -

-

- Install and enable management will land in a follow-up. -

-
- +
+ + + + +
+ + {missingLabels.length > 0 ? ( +

+ No {formatMissingList(missingLabels)} detected in this plugin. +

+ ) : null}
); } -// ── Section + row renderers ────────────────────────────────────────────────── +function formatMissingList(labels: string[]) { + if (labels.length === 0) return ""; + const lowered = labels.map((label) => label.toLowerCase()); + if (lowered.length === 1) return lowered[0]; + if (lowered.length === 2) return `${lowered[0]} or ${lowered[1]}`; + return `${lowered.slice(0, -1).join(", ")}, or ${lowered[lowered.length - 1]}`; +} function PrimitiveSection({ icon: Icon, label, items, - emptyLabel, render, }: { icon: React.ComponentType<{ className?: string }>; label: string; items: T[]; - emptyLabel: string; render: (item: T) => React.ReactNode; }) { - return ( -
-

- - {items.length === 0 ? label : `${items.length} ${label}`} -

+ if (items.length === 0) { + return null; + } - {items.length === 0 ? ( -
- {emptyLabel} -
- ) : ( -
{items.map((item) => render(item))}
- )} -
+ return ( +
+
+

+ + {label} +

+

+ {items.length} {items.length === 1 ? "item" : "items"} +

+
+
{items.map((item) => render(item))}
+
); } @@ -222,13 +170,12 @@ function renderSkillRow(skill: PluginSkill) { return (
-
-

{skill.name}

-

{skill.description}

-
- Skill +

{skill.name}

+ {skill.description ? ( +

{skill.description}

+ ) : null}
); } @@ -237,15 +184,19 @@ function renderHookRow(hook: PluginHook) { return (
-
-

{hook.event}

-

{hook.description}

+
+

{hook.event}

+ {hook.description ? ( +

{hook.description}

+ ) : null}
- - {hook.matcher ? `matcher: ${hook.matcher}` : "any"} - + {hook.matcher ? ( + + matcher: {hook.matcher} + + ) : null}
); } @@ -254,14 +205,16 @@ function renderMcpRow(mcp: PluginMcp) { return (
-
-

{mcp.name}

-

{mcp.description}

+
+

{mcp.name}

+ {mcp.description ? ( +

{mcp.description}

+ ) : null}
- - {mcp.transport} · {mcp.toolCount} tools + + {mcp.transport} · {mcp.toolCount} tool{mcp.toolCount === 1 ? "" : "s"}
); @@ -271,13 +224,12 @@ function renderAgentRow(agent: PluginAgent) { return (
-
-

{agent.name}

-

{agent.description}

-
- Agent +

{agent.name}

+ {agent.description ? ( +

{agent.description}

+ ) : null}
); } @@ -286,17 +238,14 @@ function renderCommandRow(command: PluginCommand) { return (
-
-

{command.name}

-

{command.description}

-
- Command +

{command.name}

+ {command.description ? ( +

{command.description}

+ ) : null}
); } -// Satisfy the type parameter of DenPlugin import even if unused at runtime. -// (Keeps the file importable when you wire in edit forms later.) export type { DenPlugin }; diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/plugins-screen.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/plugins-screen.tsx index dcf73c98..32d319ff 100644 --- a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/plugins-screen.tsx +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/_components/plugins-screen.tsx @@ -8,6 +8,9 @@ import { Puzzle, Search, Server, + Store, + Terminal, + Users, Webhook, } from "lucide-react"; import { PaperMeshGradient } from "@openwork/ui/react"; @@ -24,13 +27,15 @@ import { usePlugins, } from "./plugin-data"; -type PluginView = "plugins" | "skills" | "hooks" | "mcps"; +type PluginView = "plugins" | "skills" | "agents" | "commands" | "hooks" | "mcps"; const PLUGIN_TABS = [ { value: "plugins" as const, label: "Plugins", icon: Puzzle }, - { value: "skills" as const, label: "All Skills", icon: FileText }, - { value: "hooks" as const, label: "All Hooks", icon: Webhook }, - { value: "mcps" as const, label: "All MCPs", icon: Server }, + { value: "skills" as const, label: "Skills", icon: FileText }, + { value: "agents" as const, label: "Agents", icon: Users }, + { value: "commands" as const, label: "Commands", icon: Terminal }, + { value: "hooks" as const, label: "Hooks", icon: Webhook }, + { value: "mcps" as const, label: "MCPs", icon: Server }, ]; export function PluginsScreen() { @@ -81,6 +86,22 @@ export function PluginsScreen() { [plugins], ); + const allAgents = useMemo( + () => + plugins.flatMap((plugin) => + plugin.agents.map((agent) => ({ ...agent, pluginId: plugin.id, pluginName: plugin.name })), + ), + [plugins], + ); + + const allCommands = useMemo( + () => + plugins.flatMap((plugin) => + plugin.commands.map((command) => ({ ...command, pluginId: plugin.id, pluginName: plugin.name })), + ), + [plugins], + ); + const filteredSkills = useMemo(() => { if (!normalizedQuery) return allSkills; return allSkills.filter( @@ -111,14 +132,38 @@ export function PluginsScreen() { ); }, [normalizedQuery, allMcps]); + const filteredAgents = useMemo(() => { + if (!normalizedQuery) return allAgents; + return allAgents.filter( + (agent) => + agent.name.toLowerCase().includes(normalizedQuery) || + agent.description.toLowerCase().includes(normalizedQuery) || + agent.pluginName.toLowerCase().includes(normalizedQuery), + ); + }, [normalizedQuery, allAgents]); + + const filteredCommands = useMemo(() => { + if (!normalizedQuery) return allCommands; + return allCommands.filter( + (command) => + command.name.toLowerCase().includes(normalizedQuery) || + command.description.toLowerCase().includes(normalizedQuery) || + command.pluginName.toLowerCase().includes(normalizedQuery), + ); + }, [normalizedQuery, allCommands]); + const searchPlaceholder = activeView === "plugins" ? "Search plugins..." : activeView === "skills" ? "Search skills..." - : activeView === "hooks" - ? "Search hooks..." - : "Search MCPs..."; + : activeView === "agents" + ? "Search agents..." + : activeView === "commands" + ? "Search commands..." + : activeView === "hooks" + ? "Search hooks..." + : "Search MCPs..."; return ( ) : ( -
+
{filteredPlugins.map((plugin) => ( - {/* Gradient header */} -
-
- +
+
+
+ +
+
+
+ +
+
-
- -
- {plugin.installed ? ( - - Installed - - ) : null} -
- {/* Body */} -
-
-

{plugin.name}

- v{plugin.version} -
-

{plugin.description}

+
+
+

+ {plugin.name} +

+
+ {plugin.description ? ( +

+ {plugin.description} +

+ ) : null} -
- + {(plugin.marketplaces ?? []).length > 0 ? ( +
+ {(plugin.marketplaces ?? []).map((marketplace) => ( + + + {marketplace.name} + + ))} +
+ ) : null} + +

{getPluginPartsSummary(plugin)} - - View plugin +

@@ -209,6 +267,7 @@ export function PluginsScreen() { ) ) : activeView === "skills" ? ( + ) : activeView === "agents" ? ( + ({ + id: agent.id, + title: agent.name, + description: agent.description, + pluginName: agent.pluginName, + href: getPluginRoute(orgSlug, agent.pluginId), + }))} + /> + ) : activeView === "commands" ? ( + ({ + id: command.id, + title: command.name, + description: command.description, + pluginName: command.pluginName, + monospacedTitle: true, + href: getPluginRoute(orgSlug, command.pluginId), + }))} + /> ) : activeView === "hooks" ? ( ) : ( @@ -293,17 +388,21 @@ type PrimitiveRow = { id: string; title: string; description: string; - tag: string; + pluginName: string; + meta?: string; + monospacedTitle?: boolean; href: string; }; function PrimitiveList({ + icon: Icon, rows, unfilteredCount, emptyLabel, emptyDescriptionEmpty, emptyDescriptionFiltered, }: { + icon: React.ComponentType<{ className?: string }>; rows: PrimitiveRow[]; unfilteredCount: number; emptyLabel: string; @@ -320,22 +419,43 @@ function PrimitiveList({ } return ( -
+
{rows.map((row) => ( -
-

{row.title}

- {row.description ? ( -

{row.description}

+
+
+ +
+
+

+ {row.title} +

+ {row.description ? ( +

+ {row.description} +

+ ) : null} +
+
+
+ + + {row.pluginName} + + {row.meta ? ( + + {row.meta} + ) : null}
- - {row.tag} - ))}
diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/integrations/github/page.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/integrations/github/page.tsx new file mode 100644 index 00000000..afc70021 --- /dev/null +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/integrations/github/page.tsx @@ -0,0 +1,12 @@ +import { Suspense } from "react"; +import { GithubIntegrationScreen } from "../../_components/github-integration-screen"; + +export const dynamic = "force-dynamic"; + +export default function GithubIntegrationPage() { + return ( + + + + ); +} diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/marketplaces/[marketplaceId]/page.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/marketplaces/[marketplaceId]/page.tsx new file mode 100644 index 00000000..3e6e6fbf --- /dev/null +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/marketplaces/[marketplaceId]/page.tsx @@ -0,0 +1,10 @@ +import { MarketplaceDetailScreen } from "../../_components/marketplace-detail-screen"; + +export default async function MarketplaceDetailPage({ + params, +}: { + params: Promise<{ marketplaceId: string }>; +}) { + const { marketplaceId } = await params; + return ; +} diff --git a/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/marketplaces/page.tsx b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/marketplaces/page.tsx new file mode 100644 index 00000000..3154efe4 --- /dev/null +++ b/ee/apps/den-web/app/(den)/o/[orgSlug]/dashboard/marketplaces/page.tsx @@ -0,0 +1,5 @@ +import { MarketplacesScreen } from "../_components/marketplaces-screen"; + +export default function MarketplacesPage() { + return ; +} diff --git a/ee/apps/den-web/next-env.d.ts b/ee/apps/den-web/next-env.d.ts index c4b7818f..9edff1c7 100644 --- a/ee/apps/den-web/next-env.d.ts +++ b/ee/apps/den-web/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/ee/apps/den-web/tsconfig.tsbuildinfo b/ee/apps/den-web/tsconfig.tsbuildinfo index 4a166ed3..489f3f73 100644 --- a/ee/apps/den-web/tsconfig.tsbuildinfo +++ b/ee/apps/den-web/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"program":{"fileNames":["../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es5.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2016.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2018.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2019.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2021.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.dom.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.core.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.date.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.object.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.string.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2019.array.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2019.object.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2019.string.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.date.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.string.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.number.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2021.string.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.array.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.error.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.object.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.sharedmemory.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.string.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.esnext.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.decorators.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../../node_modules/.pnpm/@types+react@18.2.79/node_modules/@types/react/global.d.ts","../../../node_modules/.pnpm/csstype@3.2.3/node_modules/csstype/index.d.ts","../../../node_modules/.pnpm/@types+prop-types@15.7.15/node_modules/@types/prop-types/index.d.ts","../../../node_modules/.pnpm/@types+react@18.2.79/node_modules/@types/react/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/css.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/macro.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/style.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/global.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/get-page-files.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/assert.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/assert/strict.d.ts","../../../node_modules/.pnpm/buffer@5.6.0/node_modules/buffer/index.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/header.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/readable.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/file.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/fetch.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/formdata.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/connector.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/client.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/errors.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/dispatcher.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/global-dispatcher.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/global-origin.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/pool-stats.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/pool.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/handlers.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/balanced-pool.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/agent.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-interceptor.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-agent.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-client.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-pool.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-errors.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/proxy-agent.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/api.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/cookies.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/patch.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/filereader.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/diagnostics-channel.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/websocket.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/content-type.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/cache.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/interceptors.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/index.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/globals.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/async_hooks.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/buffer.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/child_process.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/cluster.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/console.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/constants.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/crypto.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/dgram.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/diagnostics_channel.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/dns.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/dns/promises.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/domain.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/dom-events.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/events.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/fs.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/fs/promises.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/http.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/http2.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/https.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/inspector.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/module.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/net.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/os.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/path.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/perf_hooks.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/process.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/punycode.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/querystring.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/readline.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/readline/promises.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/repl.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/sea.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/stream.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/stream/promises.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/stream/consumers.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/stream/web.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/string_decoder.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/test.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/timers.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/timers/promises.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/tls.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/trace_events.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/tty.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/url.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/util.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/v8.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/vm.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/wasi.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/worker_threads.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/zlib.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/globals.global.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/index.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/global.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/index.d.ts","../../../node_modules/.pnpm/@types+react@18.2.79/node_modules/@types/react/canary.d.ts","../../../node_modules/.pnpm/@types+react@18.2.79/node_modules/@types/react/experimental.d.ts","../../../node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/index.d.ts","../../../node_modules/.pnpm/@types+react-dom@18.2.25/node_modules/@types/react-dom/index.d.ts","../../../node_modules/.pnpm/@types+react-dom@18.2.25/node_modules/@types/react-dom/canary.d.ts","../../../node_modules/.pnpm/@types+react-dom@18.2.25/node_modules/@types/react-dom/experimental.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/fallback.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/webpack/webpack.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/entry-constants.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/constants.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/bundler.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/config.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/load-custom-routes.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/image-config.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/body-streams.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/search-params.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/segment-cache/vary-params-decoding.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/vary-params.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/params.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-kind.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/route-definition.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matches/route-match.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/app-router-headers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/cache-control.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/app-router-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/cache-handlers/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/use-cache/use-cache-wrapper.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/resume-data-cache/cache-store.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/resume-data-cache/resume-data-cache.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/constants.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/render-result.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/response-cache/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/response-cache/index.d.ts","../../../node_modules/.pnpm/@types+react@18.2.79/node_modules/@types/react/jsx-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/userspace/pages/pages-dev-overlay-setup.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/static-paths/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/app-page-route-definition.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/adapter/setup-node-env.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/instrumentation/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/setup-exception-listeners.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/worker.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/experimental/ppr.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/page-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/app/app-segment-config.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/pages/pages-segment-config.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/analysis/get-page-static-info.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/require-hook.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-polyfill-crypto.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-baseline.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/error-inspect.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/console-file.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/console-exit.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/console-dim.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/unhandled-rejection.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/random.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/date.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/web-crypto.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/node-crypto.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/fast-set-immediate.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/page-extensions-type.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/module.compiled.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/app-route-route-definition.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/i18n-provider.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/next-url.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/cookies.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/request.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/deep-readonly.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/incremental-cache/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/locale-route-definition.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/pages-route-definition.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/mitt.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/with-router.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/router.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/route-loader.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/page-loader.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/bloom-filter.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/router.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/readonly-url-search-params.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/flight-data-helpers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/cache-key.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/segment-cache/segment-value-encoding.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/scheduler.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/cache-map.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/vary-path.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/cache.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/router-reducer/ppr-navigations.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/navigation.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/vendored/contexts/entrypoints.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/module.compiled.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/templates/pages.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/module.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/render.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/pages-api-route-definition.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matches/pages-api-route-match.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matchers/route-matcher.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matcher-providers/route-matcher-provider.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matcher-managers/route-matcher-manager.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/normalizer.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/locale-route-normalizer.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/pathname-normalizer.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/suffix.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/rsc.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/next-data.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/builtin-request-context.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/segment-prefix-rsc.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/builtin/_error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/load-default-error-components.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/after.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/after-context.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/use-cache/cache-life.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-async-storage-instance.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/lazy-result.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/create-error-handler.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/action-revalidation-kind.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-async-storage.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/async-storage/work-store.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/http.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/hooks-server-context.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-route/shared-modules.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/redirect-status-code.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/redirect-error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/cache-signal.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/boundary-tracking.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/instant-validation-error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/parse-relative-url.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/instant-samples.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/dynamic-rendering.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-unit-async-storage-instance.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/implicit-tags.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/staged-rendering.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-unit-async-storage.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/templates/app-route.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/action-async-storage-instance.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/action-async-storage.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-route/module.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-route/module.compiled.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/app/app-segments.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/get-supported-browsers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/utils.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/rendering-mode.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/build-prefetch-segment-data-route.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/cpu-profile.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/result.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/helpers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/export/routes/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/export/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/export/worker.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/worker.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/coalesced-function.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/trace.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/shared.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/load-jsconfig.d.ts","../../../node_modules/.pnpm/@next+env@16.2.1/node_modules/@next/env/dist/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/telemetry-plugin/use-cache-tracker-utils.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/telemetry-plugin/telemetry-plugin.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/telemetry/storage.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/build-context.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack-config.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/swc/generated-native.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/define-env.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/swc/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/swc/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/parse-version-info.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/shared/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/dev-indicator-server-state.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/cache-indicator.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/parse-stack.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/server/shared.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/shared/stack-frame.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/utils/get-error-by-type.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/container/runtime-error/render-error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/shared.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/debug-channel.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/hot-reloader-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/response.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/middleware/middleware-config.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-http/node.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/async-callback-set.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts","../../../node_modules/.pnpm/sharp@0.34.5/node_modules/sharp/lib/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/image-optimizer.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/next-server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/lru-cache.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/dev-bundler-service.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/static-paths-worker.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/next-dev-server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/next.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/render-server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/path-match.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/filesystem.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/router-server-context.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/route-module.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/load-components.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/adapter.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/loaders/next-app-loader/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/app-dir-module.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/app-render.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/vendored/contexts/entrypoints.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/error-boundary.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/layout-router.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/render-from-template-context.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/client-page.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/client-segment.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/http-access-fallback/error-boundary.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/extra-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/metadata-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/manifest-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/twitter-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/resolvers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/icons.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/resolve-metadata.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/metadata.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/framework/boundary-components.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/rsc/preloads.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/rsc/postpone.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/rsc/taint.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/collect-segment-data.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/instant-validation.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/userspace/app/segment-explorer-node.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/entry-base.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/templates/app-page.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/helpers/prerender-manifest-matcher.d.ts","../../../node_modules/.pnpm/@types+react@18.2.79/node_modules/@types/react/jsx-dev-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/vendored/rsc/entrypoints.d.ts","../../../node_modules/.pnpm/@types+react-dom@18.2.25/node_modules/@types/react-dom/client.d.ts","../../../node_modules/.pnpm/@types+react-dom@18.2.25/node_modules/@types/react-dom/server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/vendored/ssr/entrypoints.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/module.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/fallback-params.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/image-response.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/user-agent.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/url-pattern.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/connection.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/exports/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request-meta.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/cli/next-test.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/size-limit.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/config-shared.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-http/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/api-utils/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/adapter/build-complete.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/utils.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/pages/_app.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/app.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/revalidate.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/use-cache/cache-tag.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/cache.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/pages/_document.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/document.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/dynamic.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dynamic.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/pages/_error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/catch-error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/api/error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/head.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/head.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/cookies.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/headers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/draft-mode.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/headers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/get-img-props.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/image-component.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/image-external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/image.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/link.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/link.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unrecognized-action-error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/redirect.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/not-found.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/forbidden.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unauthorized.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unstable-rethrow.server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unstable-rethrow.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/navigation.react-server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/navigation.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/navigation.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/router.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/script.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/script.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@vercel/og/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/types/global.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/types/compiled.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/image-types/global.d.ts","./.next/dev/types/routes.d.ts","./next-env.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/jsx-runtime.d.ts","./proxy.ts","./app/(den)/_lib/consts.ts","./app/(den)/_lib/client-route.ts","./app/(den)/_lib/den-flow.ts","./app/(den)/_lib/den-org.ts","./app/(den)/o/[orgslug]/dashboard/_components/shared-setup-data.ts","./app/api/_lib/upstream-proxy.ts","./app/api/auth/[...path]/route.ts","./app/api/den/[...path]/route.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@next/font/dist/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@next/font/dist/google/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/font/google/index.d.ts","./app/layout.tsx","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@vercel/og/index.node.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/og/image-response.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/og.d.ts","./app/opengraph-image.tsx","./app/(den)/_components/den-shell.tsx","./app/(den)/_providers/den-flow-provider.tsx","./app/(den)/layout.tsx","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shader-mount.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shader-sizing.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/types.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/mesh-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/smoke-ring.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/neuro-noise.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/dot-orbit.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/dot-grid.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/simplex-noise.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/metaballs.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/perlin-noise.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/voronoi.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/waves.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/warp.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/god-rays.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/spiral.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/swirl.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/dithering.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/grain-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/pulsing-border.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/color-panels.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/static-mesh-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/static-radial-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/paper-texture.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/water.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/fluted-glass.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/image-dithering.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/heatmap.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/liquid-metal.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/halftone-dots.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/halftone-cmyk.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/get-shader-color-from-string.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/get-shader-noise-texture.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/empty-pixel.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/index.d.ts","../../../packages/ui/src/common/paper.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shader-mount.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/mesh-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/smoke-ring.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/neuro-noise.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/dot-orbit.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/dot-grid.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/simplex-noise.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/metaballs.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/waves.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/perlin-noise.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/voronoi.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/warp.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/god-rays.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/spiral.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/swirl.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/dithering.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/grain-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/pulsing-border.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/color-panels.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/static-mesh-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/static-radial-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/paper-texture.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/fluted-glass.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/water.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/image-dithering.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/heatmap.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/liquid-metal.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/halftone-dots.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/halftone-cmyk.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/index.d.ts","../../../packages/ui/src/react/paper/grain-gradient.tsx","../../../packages/ui/src/react/paper/mesh-gradient.tsx","../../../packages/ui/src/react/index.ts","../../../node_modules/.pnpm/lucide-react@0.577.0_react@19.2.4/node_modules/lucide-react/dist/lucide-react.d.ts","./app/(den)/_components/auth-panel.tsx","./app/(den)/_components/auth-screen.tsx","./app/(den)/page.tsx","./app/(den)/_components/checkout-screen.tsx","./app/(den)/_components/dashboard-redirect-screen.tsx","./app/(den)/_components/dashboard-screen.tsx","./app/(den)/_components/join-org-screen.tsx","./app/(den)/_components/organization-screen.tsx","./app/(den)/_components/ui/button.tsx","./app/(den)/_components/ui/dashboard-page-template.tsx","./app/(den)/_components/ui/input.tsx","./app/(den)/_components/ui/tabs.tsx","./app/(den)/_components/ui/textarea.tsx","./app/(den)/checkout/page.tsx","./app/(den)/dashboard/page.tsx","./app/(den)/join-org/page.tsx","./app/(den)/o/[orgslug]/dashboard/_providers/org-dashboard-provider.tsx","./app/(den)/o/[orgslug]/dashboard/_components/org-dashboard-shell.tsx","./app/(den)/o/[orgslug]/dashboard/layout.tsx","./app/(den)/o/[orgslug]/dashboard/_components/dashboard-overview-screen.tsx","./app/(den)/o/[orgslug]/dashboard/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/background-agents-screen.tsx","./app/(den)/o/[orgslug]/dashboard/_components/billing-dashboard-screen.tsx","./app/(den)/o/[orgslug]/dashboard/_components/custom-llm-providers-screen.tsx","./app/(den)/o/[orgslug]/dashboard/_components/manage-members-screen.tsx","./app/(den)/o/[orgslug]/dashboard/_components/skill-hub-data.tsx","./app/(den)/o/[orgslug]/dashboard/_components/skill-detail-screen.tsx","./app/(den)/o/[orgslug]/dashboard/_components/skill-editor-screen.tsx","./app/(den)/o/[orgslug]/dashboard/_components/skill-hub-detail-screen.tsx","./app/(den)/o/[orgslug]/dashboard/_components/skill-hub-editor-screen.tsx","./app/(den)/o/[orgslug]/dashboard/_components/skill-hubs-screen.tsx","./app/(den)/o/[orgslug]/dashboard/_components/templates-dashboard-screen.tsx","./app/(den)/o/[orgslug]/dashboard/background-agents/page.tsx","./app/(den)/o/[orgslug]/dashboard/billing/page.tsx","./app/(den)/o/[orgslug]/dashboard/custom-llm-providers/page.tsx","./app/(den)/o/[orgslug]/dashboard/manage-members/page.tsx","./app/(den)/o/[orgslug]/dashboard/members/page.tsx","./app/(den)/o/[orgslug]/dashboard/shared-setups/page.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/page.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/[skillhubid]/page.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/[skillhubid]/edit/page.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/new/page.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/skills/[skillid]/page.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/skills/[skillid]/edit/page.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/skills/new/page.tsx","./app/(den)/organization/page.tsx","./components/den-admin-panel.tsx","./app/admin/page.tsx","./components/den-marketing-rail.tsx","./.next/dev/types/cache-life.d.ts","./.next/dev/types/validator.ts","../../../node_modules/.pnpm/next@14.2.5_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@18.2.0_react@18.2.0__react@18.2.0/node_modules/next/server.d.ts","../../../node_modules/.pnpm/next@14.2.5_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@18.2.0_react@18.2.0__react@18.2.0/node_modules/next/og.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/canary.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/experimental.d.ts","../../../node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/canary.d.ts","../../../node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/experimental.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/jsx-dev-runtime.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/compiler-runtime.d.ts","../../../node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/server.d.ts"],"fileInfos":[{"version":"824cb491a40f7e8fdeb56f1df5edf91b23f3e3ee6b4cde84d4a99be32338faee","affectsGlobalScope":true},"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","9a68c0c07ae2fa71b44384a839b7b8d81662a236d4b9ac30916718f7510b1b2d","5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","5514e54f17d6d74ecefedc73c504eadffdeda79c7ea205cf9febead32d45c4bc",{"version":"87d693a4920d794a73384b3c779cadcb8548ac6945aa7a925832fe2418c9527a","affectsGlobalScope":true},{"version":"76f838d5d49b65de83bc345c04aa54c62a3cfdb72a477dc0c0fce89a30596c30","affectsGlobalScope":true},{"version":"138fb588d26538783b78d1e3b2c2cc12d55840b97bf5e08bca7f7a174fbe2f17","affectsGlobalScope":true},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true},{"version":"4443e68b35f3332f753eacc66a04ac1d2053b8b035a0e0ac1d455392b5e243b3","affectsGlobalScope":true},{"version":"bc47685641087c015972a3f072480889f0d6c65515f12bd85222f49a98952ed7","affectsGlobalScope":true},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true},{"version":"93495ff27b8746f55d19fcbcdbaccc99fd95f19d057aed1bd2c0cafe1335fbf0","affectsGlobalScope":true},{"version":"6fc23bb8c3965964be8c597310a2878b53a0306edb71d4b5a4dfe760186bcc01","affectsGlobalScope":true},{"version":"ea011c76963fb15ef1cdd7ce6a6808b46322c527de2077b6cfdf23ae6f5f9ec7","affectsGlobalScope":true},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true},{"version":"bb42a7797d996412ecdc5b2787720de477103a0b2e53058569069a0e2bae6c7e","affectsGlobalScope":true},{"version":"4738f2420687fd85629c9efb470793bb753709c2379e5f85bc1815d875ceadcd","affectsGlobalScope":true},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true},{"version":"9fc46429fbe091ac5ad2608c657201eb68b6f1b8341bd6d670047d32ed0a88fa","affectsGlobalScope":true},{"version":"61c37c1de663cf4171e1192466e52c7a382afa58da01b1dc75058f032ddf0839","affectsGlobalScope":true},{"version":"b541a838a13f9234aba650a825393ffc2292dc0fc87681a5d81ef0c96d281e7a","affectsGlobalScope":true},{"version":"b20fe0eca9a4e405f1a5ae24a2b3290b37cf7f21eba6cbe4fc3fab979237d4f3","affectsGlobalScope":true},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true},{"version":"49ed889be54031e1044af0ad2c603d627b8bda8b50c1a68435fe85583901d072","affectsGlobalScope":true},{"version":"e93d098658ce4f0c8a0779e6cab91d0259efb88a318137f686ad76f8410ca270","affectsGlobalScope":true},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true},{"version":"bf14a426dbbf1022d11bd08d6b8e709a2e9d246f0c6c1032f3b2edb9a902adbe","affectsGlobalScope":true},{"version":"5e07ed3809d48205d5b985642a59f2eba47c402374a7cf8006b686f79efadcbd","affectsGlobalScope":true},{"version":"2b72d528b2e2fe3c57889ca7baef5e13a56c957b946906d03767c642f386bbc3","affectsGlobalScope":true},{"version":"8073890e29d2f46fdbc19b8d6d2eb9ea58db9a2052f8640af20baff9afbc8640","affectsGlobalScope":true},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true},{"version":"51e547984877a62227042850456de71a5c45e7fe86b7c975c6e68896c86fa23b","affectsGlobalScope":true},{"version":"956d27abdea9652e8368ce029bb1e0b9174e9678a273529f426df4b3d90abd60","affectsGlobalScope":true},{"version":"4fa6ed14e98aa80b91f61b9805c653ee82af3502dc21c9da5268d3857772ca05","affectsGlobalScope":true},{"version":"e6633e05da3ff36e6da2ec170d0d03ccf33de50ca4dc6f5aeecb572cedd162fb","affectsGlobalScope":true},{"version":"d8670852241d4c6e03f2b89d67497a4bbefe29ecaa5a444e2c11a9b05e6fccc6","affectsGlobalScope":true},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true},{"version":"caccc56c72713969e1cfe5c3d44e5bab151544d9d2b373d7dbe5a1e4166652be","affectsGlobalScope":true},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true},{"version":"50d53ccd31f6667aff66e3d62adf948879a3a16f05d89882d1188084ee415bbc","affectsGlobalScope":true},{"version":"13f6e6380c78e15e140243dc4be2fa546c287c6d61f4729bc2dd7cf449605471","affectsGlobalScope":true},{"version":"33358442698bb565130f52ba79bfd3d4d484ac85fe33f3cb1759c54d18201393","affectsGlobalScope":true},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true},{"version":"55461596dc873b866911ef4e640fae4c39da7ac1fbc7ef5e649cb2f2fb42c349","affectsGlobalScope":true},"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","87d9d29dbc745f182683f63187bf3d53fd8673e5fca38ad5eaab69798ed29fbc",{"version":"8117d2726c78497306ceef07b4ccd08a863a9bd6e1fd7ff9ed6332f0e49bbb1a","affectsGlobalScope":true},"acd8fd5090ac73902278889c38336ff3f48af6ba03aa665eb34a75e7ba1dccc4","d6258883868fb2680d2ca96bc8b1352cab69874581493e6d52680c5ffecdb6cc","1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153","f258e3960f324a956fc76a3d3d9e964fff2244ff5859dcc6ce5951e5413ca826","643f7232d07bf75e15bd8f658f664d6183a0efaca5eb84b48201c7671a266979","21da358700a3893281ce0c517a7a30cbd46be020d9f0c3f2834d0a8ad1f5fc75","d78c698fa755ef94e3af591883bfee3a330ffec36392e00aaacdff3541cf5382","7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","ef18cbf1d8374576e3db03ff33c2c7499845972eb0c4adf87392949709c5e160","5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","7180c03fd3cb6e22f911ce9ba0f8a7008b1a6ddbe88ccf16a9c8140ef9ac1686","25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","54cb85a47d760da1c13c00add10d26b5118280d44d58e6908d8e89abbd9d7725","3e4825171442666d31c845aeb47fcd34b62e14041bb353ae2b874285d78482aa","c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","a967bfe3ad4e62243eb604bf956101e4c740f5921277c60debaf325c1320bf88","e9775e97ac4877aebf963a0289c81abe76d1ec9a2a7778dbe637e5151f25c5f3","471e1da5a78350bc55ef8cef24eb3aca6174143c281b8b214ca2beda51f5e04a","cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","db3435f3525cd785bf21ec6769bf8da7e8a776be1a99e2e7efb5f244a2ef5fee","c3b170c45fc031db31f782e612adf7314b167e60439d304b49e704010e7bafe5","40383ebef22b943d503c6ce2cb2e060282936b952a01bea5f9f493d5fb487cc7","4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","3a84b7cb891141824bd00ef8a50b6a44596aded4075da937f180c90e362fe5f6","13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","33203609eba548914dc83ddf6cadbc0bcb6e8ef89f6d648ca0908ae887f9fcc5","0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","e53a3c2a9f624d90f24bf4588aacd223e7bec1b9d0d479b68d2f4a9e6011147f","339dc5265ee5ed92e536a93a04c4ebbc2128f45eeec6ed29f379e0085283542c","9f0a92164925aa37d4a5d9dd3e0134cff8177208dba55fd2310cd74beea40ee2","8bfdb79bf1a9d435ec48d9372dc93291161f152c0865b81fc0b2694aedb4578d","2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","d32275be3546f252e3ad33976caf8c5e842c09cb87d468cb40d5f4cf092d1acc","4a0c3504813a3289f7fb1115db13967c8e004aa8e4f8a9021b95285502221bd1",{"version":"a14ed46fa3f5ffc7a8336b497cd07b45c2084213aaca933a22443fcb2eef0d07","affectsGlobalScope":true},"6968359c8dbc693224fd1ea0b1f96b135f14d8eee3d6e23296d68c3a9da3ea00",{"version":"79d75a353f29d9f7fc63e879ccebe213baaaea26676fb3e47cc96cf221b27b4f","affectsGlobalScope":true},"dfdc7699360a0d512d7e31c69f75cb6a419cf415c98673e24499793170db5d6b","dcf46daa1e04481b1c2f360c7a77bf019885bd70353a92aa698b9c22b7fe3d6b",{"version":"033350619c2cfcbeab2a483f4b221e0866e17cc4ac514240d285d35c35eecf7c","affectsGlobalScope":true},"a40826e8476694e90da94aa008283a7de50d1dafd37beada623863f1901cb7fb",{"version":"b197fb2d5fa71cebc66e5d10e15c7d02f15fcd3194fbdaafeb964262582f2a82","affectsGlobalScope":true},"1a7f593d587f49ca97710c021c453ab1b95db5e39e58567f4af644f97a5fb0e0","dd4705d1d78af32c407e93e5df009962bed324599d6a5b2a9d661ba44dd99e43","3a02975d4a7034567425e529a0770f7f895ed605d2b576f7831668b7beea9fea","7525257b4aa35efc7a1bbc00f205a9a96c4e4ab791da90db41b77938c4e0c18e","cf87b355c4f531e98a9bba2b0e62d413b49b58b26bf8a9865e60a22d3af1fcd3",{"version":"ee1ee365d88c4c6c0c0a5a5701d66ebc27ccd0bcfcfaa482c6e2e7fe7b98edf7","affectsGlobalScope":true},{"version":"1a08fe5930473dcae34b831b3440cd51ff2c682cf03bd70e28812751dd1644dd","affectsGlobalScope":true},"6f3e00b838cf23f7837ffca5da88ae25f0a81742af9ccadce5cb85ac72050929","304f66274aa8119e8d65a49b1cff84cbf803def6afe1b2cc987386e9a9890e22","cbcb993f1fa22b7769074eb09c1307756e6380659a2990d6f50cfd8943bd8333","55a93997681797056da069cfac92878bff4d2a35e61c1c16280ee0cba38702f2","ea25afcaf96904668f7eebc1b834f89b5b5e5acafd430c29990028a1aaa0bcbe","df981b2ce32930887db27eeae29e48b9b841e4ba0bbba1162ebed04c778cd7e1",{"version":"ea455cc68871b049bcecd9f56d4cf27b852d6dafd5e3b54468ca87cc11604e4d","affectsGlobalScope":true},"3be96458790a77cb357856dab45d1cc8383ac63ba4e085f620b202fb62a6e1db","02d85d03fd4a4f63cba0b133f0e0192368dfeb4338bd33f87788a4f6302de873","bb3a0ce56babb71d7c208ed848b4aafe545e7a7e06304fc0c8cfe3ad328cab7a",{"version":"43bb766c0dc5f1150021f161aa6831eb2cc75dab278172408515cb6e47f697a9","affectsGlobalScope":true},{"version":"8bcf09ba67bd0ec12a9f1efc1e58e1ba2cb1ff78920ce6cf67ebfe6003c54b82","affectsGlobalScope":true},"13ce7518e39051544dd1e3124c185665adda05a5021676f2606c2c74ad2c964f","4ac5899be65d5e2cabe3aaf3dfc2cf7641e54dde23db198d9f683dfabe228145","124dacf89c97915479ed6ad81b09ba42fd40962d069c0642fed42e2d9719f2ba","139ad1dc93a503da85b7a0d5f615bddbae61ad796bc68fedd049150db67a1e26","ad06959073c066bb9543ef9c1dee37fc3140d2ecaae42b97bf4e27f2f03d6511","9eece5e586312581ccd106d4853e861aaaa1a39f8e3ea672b8c3847eedd12f6e","782abaae13e868dee4ea9c16d44499af251d112fba535c558d10ff5279b34678","269929a24b2816343a178008ac9ae9248304d92a8ba8e233055e0ed6dbe6ef71","93452d394fdd1dc551ec62f5042366f011a00d342d36d50793b3529bfc9bd633","3c1f19c7abcda6b3a4cf9438a15c7307a080bd3b51dfd56b198d9f86baf19447","98e7b7220dad76c509d584c9b7b1ec4dcbd7df5e3a2d37d28c54f74461ec0975",{"version":"c61b5fad633f25bb0de0f95612191c1df9a6671cd66f451507b5223bff41b50d","affectsGlobalScope":true},{"version":"d21966ba3284ade60cb94eb2c533ab5b2af7fd0b4b28462043f6ebcb8400bd21","affectsGlobalScope":true},"98e00f3613402504bc2a2c9a621800ab48e0a463d1eed062208a4ae98ad8f84c","b8e9e44ce8eba70af569523ff31d669cc239a93f548899a259f3224392a75e6c","005d1caa2a5d9bc096f75b598d0fd184bc848dd2665b050a17a17d5dc1ef652d","619735e4e221e1bf137ae3efa5330beee4a06039dccb876c822f9d8913a392da",{"version":"3560d0809b0677d77e39d0459ae6129c0e045cb3d43d1f345df06cf7ab7d6029","affectsGlobalScope":true},{"version":"5ab086d9457abbc69cca270e5475073f2e8eb35b2fb810c516400de7b7c7d575","affectsGlobalScope":true},"2a2fd53f2d963624b596fb720b390cbfe8d744e92cb55b48a8090a8fd42a302d","1f01c8fde66abc4ff6aed1db050a928b3bcb6f29bc89630a0d748a0649e14074","60223439b7ee9b26a08d527cacc8b34ea6c6741589ef4949f4669c9aeb97978e",{"version":"48fffe7824c2e8cf8c812f528c33d4c4f502767582083df35920a7f56fe794b3","affectsGlobalScope":true},"561bf7d1d3163db272980f9167b4b98f6a9ee8698c5955e9d9584e84088aad51",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"a42be67ed1ddaec743582f41fc219db96a1b69719fccac6d1464321178d610fc",{"version":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","affectsGlobalScope":true},"dc0a7f107690ee5cd8afc8dbf05c4df78085471ce16bdd9881642ec738bc81fe","54f6cab7c6ca8402a6d32b9f1dd03e7fae142fed1a594c22313f2f23431f73f0","97aeb764d7abf52656d5dab4dcb084862fd4bd4405b16e1dc194a2fe8bbaa5dc","be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","e077886219cf64381045928ab7d90a1b0d42c6236f73b76e37b4b2c546ab1850","5dbf2a502a7fcd85bfe753b585cfc6c9f60294570ee6a18084e574cf93be3fa0","bb7a61dd55dc4b9422d13da3a6bb9cc5e89be888ef23bbcf6558aa9726b89a1c","413df52d4ea14472c2fa5bee62f7a40abd1eb49be0b9722ee01ee4e52e63beb2","db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96","446a50749b24d14deac6f8843e057a6355dd6437d1fac4f9e5ce4a5071f34bff","182e9fcbe08ac7c012e0a6e2b5798b4352470be29a64fdc114d23c2bab7d5106","2f4e6b4d39426a1b85ecf4bdeb9dddbf4d9b3397d95d8555d46f925c9519ec7d","78a2869ad0cbf3f9045dda08c0d4562b7e1b2bfe07b19e0db072f5c3c56e9584","89d5d28d4f57e000b836ac273079be1b75710e28ce14750d081fb420d37e2ca5","fd4e24ccff3966390600d7f5d6aa1fed5a512e92ada735ea5fbc933d313ad3d3","b7cddfe1aa6b86b5fad3c9ccb30d05b3ccb165aebbf112f48d2d8a5f69dd98b1","a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f","ad0d1d75d129b1c80f911be438d6b61bfa8703930a8ff2be2f0e1f8a91841c64","bd2c7ada3dee03653d3f601011d30072194bc3970cd93208f9588fbdc0c69347","e480da45d32313e7174b265674da504f075f59ef326852f0c5a5d863b438ae85","ad54850f61fcf5d014e11be80d2f46fea9265cfa7e77456da876f7833ef81769","6f7c9e8bd2b5b6a080b07080065f94900bd3c7e5ebbd3047bc33fcce2fab1dd8","3e7efde639c6a6c3edb9847b3f61e308bf7a69685b92f665048c45132f51c218","df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5","8a0e762ceb20c7e72504feef83d709468a70af4abccb304f32d6b9bac1129b2c","da5950ee2a90721df6f3fba45f5d05308f7e4c35835392215dd2cd404505e2de","ce75b1aebb33d510ff28af960a9221410a3eaf7f18fc5f21f9404075fba77256","f42d5fed19610d485c646a0c430e768115567d078c7fc855c57b0c578b3d6cd3","ee8df1cb8d0faaca4013a1b442e99130769ce06f438d18d510fed95890067563","d5630f2ad9b4541e5ce891648121022f9412ecdca1820baa1f0104f70fd7eff7","4d15375ab13497104bc8fe56fdef2b5fd6853f29255737d23a33fa306ff7fd69","2cd3fc1d0d6a1e85baffd2d4f50f5efb192b5446eef567e97c94765402f0aad4","e4cbf2f1e89ecccaddd2c045e600ae41b732295953fb06247c7dcbc2d281ed30","27bbdb7509a5bb564020321fc5485764d0db3230a10d2336ae5ce2c1d401b0e7","8c1697d90c394a6fd955b98eae01238eff628e129b987a68aea10f898a48e7da","7580e62139cb2b44a0270c8d01abcbfcba2819a02514a527342447fa69b34ef1","42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3","f374cb24e93e7798c4d9e83ff872fa52d2cdb36306392b840a6ddf46cb925cb6","d10d63718e1646c2279e3b33831f82c60e31f622b2b7020f1196409ca4c09242","106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","148679c6d0f449210a96e7d2e562d589e56fcde87f843a92808b3ff103f1a774","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","02436d7e9ead85e09a2f8e27d5f47d9464bced31738dec138ca735390815c9f0","f8d5ff8eafd37499f2b6a98659dd9b45a321de186b8db6b6142faed0fea3de77","c86fe861cf1b4c46a0fb7d74dffe596cf679a2e5e8b1456881313170f092e3fa","a22dd55aa4d39906252000ab8e8a1b83b195eef7f4274eb51e457c1f11cf6580","540cc83ab772a2c6bc509fe1354f314825b5dba3669efdfbe4693ecd3048e34f","121b0696021ab885c570bbeb331be8ad82c6efe2f3b93a6e63874901bebc13e3","612d9da66bb046a9c1e2e8d026245ded881fc4b9f98cbfae714415d57ee0ae0b","32c2ad9494dad5d11b0564a619fee18f388db6c1e9e2cd3c360b3122549691eb","6c301d40aec56a74ec7bd7324e31a728dadf9bfba3e96def02938d3d973534ec","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","aa14cee20aa0db79f8df101fc027d929aec10feb5b8a8da3b9af3895d05b7ba2","493c700ac3bd317177b2eb913805c87fe60d4e8af4fb39c41f04ba81fae7e170","aeb554d876c6b8c818da2e118d8b11e1e559adbe6bf606cc9a611c1b6c09f670","acf5a2ac47b59ca07afa9abbd2b31d001bf7448b041927befae2ea5b1951d9f9","8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","d71291eff1e19d8762a908ba947e891af44749f3a2cbc5bd2ec4b72f72ea795f","c0480e03db4b816dff2682b347c95f2177699525c54e7e6f6aa8ded890b76be7","25a5f6fd3a2243c859eddc99ab5fba11d970af2fe7a5df9c32b7668f76f97b01","8d207e1f9d2c30d6f77dfa693f3827c3fbf0d89240297e10bdfe1041d433df68","b620391fe8060cf9bedc176a4d01366e6574d7a71e0ac0ab344a4e76576fcbb8","6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","2652448ac55a2010a1f71dd141f828b682298d39728f9871e1cdf8696ef443fd","d682336018141807fb602709e2d95a192828fcb8d5ba06dda3833a8ea98f69e3","6124e973eab8c52cabf3c07575204efc1784aca6b0a30c79eb85fe240a857efa","0d891735a21edc75df51f3eb995e18149e119d1ce22fd40db2b260c5960b914e","3b414b99a73171e1c4b7b7714e26b87d6c5cb03d200352da5342ab4088a54c85","4fbd3116e00ed3a6410499924b6403cc9367fdca303e34838129b328058ede40","9c82171d836c47486074e4ca8e059735bf97b205e70b196535b5efd40cbe1bc5","8c70ddc0c22d85e56011d49fddfaae3405eb53d47b59327b9dd589e82df672e7","2f9c89cbb29d362290531b48880a4024f258c6033aaeb7e59fbc62db26819650","a365c4d3bed3be4e4e20793c999c51f5cd7e6792322f14650949d827fbcd170f","c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5","65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9","9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801","de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d","c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5","1ee45496b5f8bdee6f7abc233355898e5bf9bd51255db65f5ff7ede617ca0027",{"version":"273782b8454e78f6a8b30d2cfbf6860499c930595095fcc1689637115f0eddda","affectsGlobalScope":true},{"version":"3fbdd025f9d4d820414417eeb4107ffa0078d454a033b506e22d3a23bc3d9c41","affectsGlobalScope":true},"dba114fb6a32b355a9cfc26ca2276834d72fe0e94cd2c3494005547025015369",{"version":"a8f8e6ab2fa07b45251f403548b78eaf2022f3c2254df3dc186cb2671fe4996d","affectsGlobalScope":true},"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e","f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b",{"version":"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa","affectsGlobalScope":true},"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6","9f9bb6755a8ce32d656ffa4763a8144aa4f274d6b69b59d7c32811031467216e","5c32bdfbd2d65e8fffbb9fbda04d7165e9181b08dad61154961852366deb7540","ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441","0c05e9842ec4f8b7bfebfd3ca61604bb8c914ba8da9b5337c4f25da427a005f2","faed7a5153215dbd6ebe76dfdcc0af0cfe760f7362bed43284be544308b114cf","7029e566b8df176f703fb59fd437a38670c7a0e02c58b2d66dfb5b2e2b2defdb","7f2aa4d4989a82530aaac3f72b3dceca90e9c25bee0b1a327e8a08a1262435ad","d96b39301d0ded3f1a27b47759676a33a02f6f5049bfcbde81e533fd10f50dcb","e9f147ecca73d9346a4c073432843c159ccbe50bdcb678a78f6da10eae2cecf4","de061f7d72bd65c06fc1419f841dfdcb29a8e22fe6fa527d1e6eb20b897d4de0","663beafc2446079574570cba86e9b15f986f908ddb1b01274509970126fee945","a3102887d5058bf4cb5b37fa6964c09e9527c42053b3b5c642b89878620748de","0aaaa1727edd29673d85c9b26d7ca4d54e5407a48586903c51b48b7f7d196f61","d35bca0b261bff02635758c48e8ab99c61c420d0dfabbcf467e847171d876b7d","3bc12c40d90c342ff88a3d876996c555ed5cbee5fe8c3308a240b321f401ee46","ba130768aae855a5477e9e148e5c879548e6e7ccbcc56fd1934c8a18ea5b7569","2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9","d38530db0601215d6d767f280e3a3c54b2a83b709e8d9001acb6f61c67e965fc","6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","b499af2054a037a162b3b72cd886f48bbf32a3502c865c6e29fac7d2ab3ce0b5","b83cb14474fa60c5f3ec660146b97d122f0735627f80d82dd03e8caa39b4388c","d87f90d2df7b638204d81d6c57e1f2a8cc9317c45ca331c691c375649aa9255c","7274fbffbd7c9589d8d0ffba68157237afd5cecff1e99881ea3399127e60572f","b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647","bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23","20865ac316b8893c1a0cc383ccfc1801443fbcc2a7255be166cf90d03fac88c9","c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23","461d0ad8ae5f2ff981778af912ba71b37a8426a33301daa00f21c6ccb27f8156","e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577","fcafff163ca5e66d3b87126e756e1b6dfa8c526aa9cd2a2b0a9da837d81bbd72","70246ad95ad8a22bdfe806cb5d383a26c0c6e58e7207ab9c431f1cb175aca657","f00f3aa5d64ff46e600648b55a79dcd1333458f7a10da2ed594d9f0a44b76d0b","772d8d5eb158b6c92412c03228bd9902ccb1457d7a705b8129814a5d1a6308fc","802e797bcab5663b2c9f63f51bdf67eff7c41bc64c0fd65e6da3e7941359e2f7","b01bd582a6e41457bc56e6f0f9de4cb17f33f5f3843a7cf8210ac9c18472fb0f","8b4327413e5af38cd8cb97c59f48c3c866015d5d642f28518e3a891c469f240e","4cceef18d7f088e797a463e90b7a9dad10c6bc667724b7686e3e740ae00122be","7ee86fbb3754388e004de0ef9e6505485ddfb3be7640783d6d015711c03d302d","cc1954b539604b1e562319119ac7e888172208b32ca873f9a357a92c826bd046","a67b87d0281c97dfc1197ef28dfe397fc2c865ccd41f7e32b53f647184cc7307","771ffb773f1ddd562492a6b9aaca648192ac3f056f0e1d997678ff97dbb6bf9b","43e96a3d5d1411ab40ba2f61d6a3192e58177bcf3b133a80ad2a16591611726d","232f70c0cf2b432f3a6e56a8dc3417103eb162292a9fd376d51a3a9ea5fbbf6f","bb8f2dbc03533abca2066ce4655c119bff353dd4514375beb93c08590c03e023",{"version":"706dd95827e7ebaabda91d5db2b755233e0952d98570e9c032b0f066a15c1177","affectsGlobalScope":true},"0b103e9abfe82d14c0ad06a55d9f91d6747154ef7cacc73cf27ecad2bfb3afcf","990b8fad2327b77e6920cc792af320e8867e68f02ce849b12c0a6ab9a1aebb09","5eb8cd1cb0c9143d74a8190b577c522720878c31aef67d866fcd29973f83e955","120599fd965257b1f4d0ff794bc696162832d9d8467224f4665f713a3119078b","43ba4f2fa8c698f5c304d21a3ef596741e8e85a810b7c1f9b692653791d8d97a","5433f33b0a20300cca35d2f229a7fc20b0e8477c44be2affeb21cb464af60c76","db036c56f79186da50af66511d37d9fe77fa6793381927292d17f81f787bb195","a6805fcafed712aea7759f8bc731014f9d22738c1d6ef9d43b8091d1d48346d5","c49469a5349b3cc1965710b5b0f98ed6c028686aa8450bcb3796728873eb923e","4a889f2c763edb4d55cb624257272ac10d04a1cad2ed2948b10ed4a7fda2a428","7bb79aa2fead87d9d56294ef71e056487e848d7b550c9a367523ee5416c44cfa","d88ea80a6447d7391f52352ec97e56b52ebec934a4a4af6e2464cfd8b39c3ba8","142617b3cdf902b69c6464c9fbd942b60ab3e733ca18c032b19e0f7e2adbefe8","0b603555f1881f87256ffd6344d3e3ed6d466c2e701eabf381f28be8c2125892","897e4f7662488e3ecc79e743bdd3b78f13bdb69a97851afa5b440c4211e32ea9","e2e1c6d3b2d93add5200bd7bc1a8cccb4e446836b2111ece45db8683a2c765de","251b03d5cd243854ce870d9a9a39f491faf69898c5d6b5eee28cc7649c57417b","27ff4196654e6373c9af16b6165120e2dd2169f9ad6abb5c935af5abd8c7938c","2c4de79f406d137390608e8c0a44fba2ff8e00bacfcae7c9d1781fef10e9440d","07ba23a10465791be5d22deaf5ef7de7658774ddff53721e5ea17fedea1bc721","dca8c645c5afeb03b1ecedbf16323f33e7d0afaa6256c8e047e6e38087a97f53","775f181bd4a533d6f8b5e55ec1d9f1624559720ae8a70e9432258da26b38d27c","796273b2edc72e78a04e86d7c58ae94d370ab93a0ddf40b1aa85a37a1c29ecd7","5df15a69187d737d6d8d066e189ae4f97e41f4d53712a46b2710ff9f8563ec9f","0659e6650e6c528420733abc2cdc36474ef14cc8d64ef3c6fee794d71c69cc2e","6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","622694a8522b46f6310c2a9b5d2530dde1e2854cb5829354e6d1ff8f371cf469","cd8ce8d68567f62dd580b3c3c37777ac3f5b81944c7417f5ea83030eab533385","e374d1eaa05b7dc38580062942ac8351ce79cbe11f6dbce4946a582a5680582d","9e2739b32f741859263fdba0244c194ca8e96da49b430377930b8f721d77c000","a9e6c0ff3f8186fccd05752cf75fc94e147c02645087ac6de5cc16403323d870","49af4b52f0d4d2304c5f2c6fe5fab3e153e0acc38830d0202821b877c097dd02","49c346823ba6d4b12278c12c977fb3a31c06b9ca719015978cb145eb86da1c61","bfac6e50eaa7e73bb66b7e052c38fdc8ccfc8dbde2777648642af33cf349f7f1","92f7c1a4da7fbfd67a2228d1687d5c2e1faa0ba865a94d3550a3941d7527a45d","f53b120213a9289d9a26f5af90c4c686dd71d91487a0aa5451a38366c70dc64b","e68b8e5a1df7c1be2bc105141456ecba70215806e1c28bfbc5c12bfce4be6e68","511c8f02329808d47d00b859c532ae9115590048b17325a946c74dac48428650","57d67b72e06059adc5e9454de26bbfe567d412b962a501d263c75c2db430f40e","b5f9e66625783eefcbe3d2da074b2e7ba2066d61ce3fc6ef4f22805ad946cab4","e37115962d284b9f7a37c2bdd2add50f88365dde41f5e0ff591ffc48a8ec7575","6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f","bb37588926aba35c9283fe8d46ebf4e79ffe976343105f5c6d45f282793352b2","f89488602bec98a142072fae7ea5ba99431a569ff580c64b7be39896474799d8","bbbc47961f39a57df103cf4ca3bb8f8732b4b6678a18225a0aa76d59c466956c","2e6114a7dd6feeef85b2c80120fdbfb59a5529c0dcc5bfa8447b6996c97a69f5","2ffb043dc5163458e473b7010859f86e01dc4edffcae0a93d885d028b426a546","c8f004e6036aa1c764ad4ec543cf89a5c1893a9535c80ef3f2b653e370de45e6","dd80b1e600d00f5c6a6ba23f455b84a7db121219e68f89f10552c54ba46e4dc9","b064c36f35de7387d71c599bfcf28875849a1dbc733e82bd26cae3d1cd060521","05c7280d72f3ed26f346cbe7cbbbb002fb7f15739197cbbee6ab3fd1a6cb9347","8de9fe97fa9e00ec00666fa77ab6e91b35d25af8ca75dabcb01e14ad3299b150","04b7b2e0832dfd3c31e81df3975e8d8fda28e7ff999b0aa2932608a8f6661d5c","ca2d34c6ed5cbd3070b8b6f32f42ae54adcc6499c1e4b99f0a5798b3f27cc653","9ec68995e66dd6b9dac834bf5ae85fde802714ea2e82151a5d1d53ef01b463ef","5c4d626b4902f2ef8a1cc146d761d276cef988016dc674e3b98fbad70e64bc9f","fdfaa0aad899524962e2955287b5b991ffe3be50f64e02eb60c933ca44644a94","53c972a0f9bc3a4ec70fff7314123ea8cfcf75b3703046f767d2dc1eea87b2fb","f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072","50256e9c31318487f3752b7ac12ff365c8949953e04568009c8705db802776fb","7d73b24e7bf31dfb8a931ca6c4245f6bb0814dfae17e4b60c9e194a631fe5f7b","d130c5f73768de51402351d5dc7d1b36eaec980ca697846e53156e4ea9911476","413586add0cfe7369b64979d4ec2ed56c3f771c0667fbde1bf1f10063ede0b08","06472528e998d152375ad3bd8ebcb69ff4694fd8d2effaf60a9d9f25a37a097a","7303b45138d2511035056a5901a1490ebdcbf055cbb1276f8629c5121cbe733e","27f874cd5327507eeff699a74567f60c1215b94509f4308633a7b01922471ed2","a401617604fa1f6ce437b81689563dfdc377069e4c58465dbd8d16069aede0a5","2c6cf04bc525caf6546e859e8ef10bfb9573837ec0bc5ec7b53a7b1b8ca72781","8695dec09ad439b0ceef3776ea68a232e381135b516878f0901ed2ea114fd0fe","304b44b1e97dd4c94697c3313df89a578dca4930a104454c99863f1784a54357","0a437ae178f999b46b6153d79095b60c42c996bc0458c04955f1c996dc68b971","74b2a5e5197bd0f2e0077a1ea7c07455bbea67b87b0869d9786d55104006784f","4a7baeb6325920044f66c0f8e5e6f1f52e06e6d87588d837bdf44feb6f35c664","87cc05fe13108f02e12da7e3efd8e360fef78d96a0c9e11408ea1b1b9fb3e03d","1abbf67c218d23c2ce76887caac2df6c7dab3d97ba2b65348432b876f510002a","1a82deef4c1d39f6882f28d275cad4c01f907b9b39be9cbc472fcf2cf051e05b","4b20fcf10a5413680e39f5666464859fc56b1003e7dfe2405ced82371ebd49b6","c06ef3b2569b1c1ad99fcd7fe5fba8d466e2619da5375dfa940a94e0feea899b","f7d628893c9fa52ba3ab01bcb5e79191636c4331ee5667ecc6373cbccff8ae12","1d879125d1ec570bf04bc1f362fdbe0cb538315c7ac4bcfcdf0c1e9670846aa6","8bd496cf710d4873d15e4891a5dbf945673e3321ca74cf75187e347fd5ed295e","a6dba407fc287f1e25454e75028c91bbc00675f2d1c4e8b3edcc36c08611a486","d663134457d8d669ae0df34eabd57028bddc04fc444c4bc04bc5215afc91e1f4","e91f7b1344577a02f051b9b471f33044fef8334a76dc9e1de003d17595a5219b","c0723195c85e19656d6b5b9fdb81d3f3403c1ae4679e722c6ea058c516b38d12","186eea74805194f04e41038fc5eca653788b9dedbab7c2d7d17e10139622dd92","71d9eb4c4e99456b78ae182fb20a5dfc20eb1667f091dbb9335b3c017dd1c783","cfa846a7b7847a1d973605fbb8c91f47f3a0f0643c18ac05c47077ebc72e71c7","1594da19968752a22b2ac48c2d0e60575700e745c577a8a4a676b841238ad5bb","e0cee12109e0a10a4c3d6769fcc7644b7c1ea7f52365bea51728f5af29f8a137","7d4254b4c6c67a29d5e7f65e67d72540480ac2cfb041ca484847f5ae70480b62","3536968defef8a75514f547ead5e2e9c1e984820290ec9b00c5fdfb6ef786535","d83773870080c30a230e322ce13a9c6f3398e8dacea4ea8a83e26370f3bac23e","dcfeaf98d66314fec29a9076c4290e45d0b196a65827becc19138e9c7b855f37","6849fe9210fe4946d5f085bfed36758f33dc6ae15a751338d178dd4daa017c46","888cda0fa66d7f74e985a3f7b1af1f64b8ff03eb3d5e80d051c3cbdeb7f32ab7","60681e13f3545be5e9477acb752b741eae6eaf4cc01658a25ec05bff8b82a2ef","ffae4e1e06aa848a1e4bcef162cd1c48e5909b26223515981310af9c036bdfc7","a57b1802794433adec9ff3fed12aa79d671faed86c49b09e02e1ac41b4f1d33a","34e16eb7c31768a11a08aebcfb3d70d7b8f0b016197e98d8419e566ceae6d6c8","f94ec1f7e4b709d26960306c9082a7a1b728a6e13089346aa48ba57c74cbf47e","9a11cb4033405e96c247cd5aa29790212aaffdd127869e8a5219103f0b389fd5","01479d9d5a5dda16d529b91811375187f61a06e74be294a35ecce77e0b9e8d6c","aff5213585cb72e94054dfe17250ff315f3569b3919d1ef1ad235f37c4ee894e","fb2ea35e1be6388d722d7725e2b49c697d34d9c890c3b96758faaeb86d35cef8","ce0df82a9ae6f914ba08409d4d883983cc08e6d59eb2df02d8e4d68309e7848b","1a4dc28334a926d90ba6a2d811ba0ff6c22775fcc13679521f034c124269fd40","f05315ff85714f0b87cc0b54bcd3dde2716e5a6b99aedcc19cad02bf2403e08c","5fad3b31fc17a5bc58095118a8b160f5260964787c52e7eb51e3d4fcf5d4a6f0","72105519d0390262cf0abe84cf41c926ade0ff475d35eb21307b2f94de985778","456006a6975b26c0a1785feddae165f6d307e2d601ffde27e21fc4a790e448a4","c857e0aae3f5f444abd791ec81206020fbcc1223e187316677e026d1c1d6fe08","ccf6dd45b708fb74ba9ed0f2478d4eb9195c9dfef0ff83a6092fa3cf2ff53b4f","1fe0d18b111e1145a7e7601855bccd4ca20f24e3b9a5aba6bb1fa9d1a7059170","5632c3c26d420c063eebe64c45b1248b9492a67bf44f1d0c57e9dc8f6cf449bb","0df5aa619ab12993a39ea6dae062ee46eadbb4d738916460e636ada52bced75b","8fca3039857709484e5893c05c1f9126ab7451fa6c29e19bb8c2411a2e937345","35069c2c417bd7443ae7c7cafd1de02f665bf015479fec998985ffbbf500628c","10ab7be91f87ebe8916b62cf28af2e45b5601fc7b0e311adf838f912c6b31dd8","bc636fbc08e0979ceb7eb0731a33000283d77a33b62e1f71ee65be50394e40ba","7e0b7f91c5ab6e33f511efc640d36e6f933510b11be24f98836a20a2dc914c2d","045b752f44bf9bbdcaffd882424ab0e15cb8d11fa94e1448942e338c8ef19fba","2894c56cad581928bb37607810af011764a2f511f575d28c9f4af0f2ef02d1ab","0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a","75bbd3be047d539988a0ff0b56384ef7a6a25f3b676ad96bee547d44c31622a7","42960001a776b089ade681ab5cfddc936e0afb0615133ec1841f3dee89d3e1bf","0aedb02516baf3e66b2c1db9fef50666d6ed257edac0f866ea32f1aa05aa474f",{"version":"da47712b394d944328245482603bc6f416d3949b67c9392279caab595076b510","affectsGlobalScope":true},"37d0071d8f0a06dc55c2c5e0ec3391affd4fd107c53410bf358196ec0bf3923f","b213dad76ca37fd552274c9499056e1c0d9c1bd38a55bb7f68b22ba6b84c3ad7","56ccb49443bfb72e5952f7012f0de1a8679f9f75fc93a5c1ac0bafb28725fc5f","d90b9f1520366d713a73bd30c5a9eb0040d0fb6076aff370796bc776fd705943","05321b823dd3781d0b6aac8700bfdc0c9181d56479fe52ba6a40c9196fd661a8",{"version":"26bdbbdc1de7ab3dc8330d3c64046ff8cc3a051d95296789a909c29fe38c1c25","affectsGlobalScope":true},"bef86adb77316505c6b471da1d9b8c9e428867c2566270e8894d4d773a1c4dc2","5a49adaef698b7ad7e6127949fa1b0bbd3d46b7cbd11c54e392a4dcdd51f5190","96171c03c2e7f314d66d38acd581f9667439845865b7f85da8df598ff9617476","27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7","5c634644d45a1b6bc7b05e71e05e52ec04f3d73d9ac85d5927f647a5f965181a","2489bf04d77dc025ba67f49f1a56eb24b9db477d5ff88123d887e163ed1776aa","63a7595a5015e65262557f883463f934904959da563b4f788306f699411e9bac","4ba137d6553965703b6b55fd2000b4e07ba365f8caeb0359162ad7247f9707a6","0b77b819b5417775fccb20c678293cf614c054a5b1a65421a5b933a9124ba998","e1f6076688a95bd82deaac740fccbe3cdea0d8a22057cccc9c5bce4398bdd33b","9252d498a77517aab5d8d4b5eb9d71e4b225bbc7123df9713e08181de63180f6","b1f1d57fde8247599731b24a733395c880a6561ec0c882efaaf20d7df968c5af","c8dadeff90ccc638d88a989c1139fd6a1329a5b39c2a7cbef1811c83ffe40903","35e6379c3f7cb27b111ad4c1aa69538fd8e788ab737b8ff7596a1b40e96f4f90","1fffe726740f9787f15b532e1dc870af3cd964dbe29e191e76121aa3dd8693f2","5a3ea721d03a361ccbdd7390ccd75f6e84cbca3a3f01f4b331ecc9af31890c49",{"version":"e7dfaee4af38d45b1cab8a1ee0b3bc1f85ddcf64545ed391d675d78ae6526274","affectsGlobalScope":true},"98e2b197bf7fe7800f89c87825e2556d66474869845e97ad9c2b36f347c43539","af48e58339188d5737b608d41411a9c054685413d8ae88b8c1d0d9bfabdf6e7e","616775f16134fa9d01fc677ad3f76e68c051a056c22ab552c64cc281a9686790","65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0","f9fe6af238339a0e5f7563acee3178f51db37f32a2e7c09f85273098cee7ec49","1de8c302fd35220d8f29dea378a4ae45199dc8ff83ca9923aca1400f2b28848a","77e71242e71ebf8528c5802993697878f0533db8f2299b4d36aa015bae08a79c","98a787be42bd92f8c2a37d7df5f13e5992da0d967fab794adbb7ee18370f9849","332248ee37cca52903572e66c11bef755ccc6e235835e63d3c3e60ddda3e9b93","94e8cc88ae2ef3d920bb3bdc369f48436db123aa2dc07f683309ad8c9968a1e1","4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35","320f4091e33548b554d2214ce5fc31c96631b513dffa806e2e3a60766c8c49d9","a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff","d90d5f524de38889d1e1dbc2aeef00060d779f8688c02766ddb9ca195e4a713d","07ed3ddab975995eea41b22f3010506fb9f5fb301d04820b07d7a1aee5477d7c","969d8b0965849f4bae7cab0ba90bd1e1220e95999c2c6f01117fa7500901c017","6ec840ee5e2bc103f557fe38b1d585ee250540468713d7634ee066de372bf332","b0309e1eda99a9e76f87c18992d9c3689b0938266242835dd4611f2b69efe456","47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e","6ceb10ca57943be87ff9debe978f4ab73593c0c85ee802c051a93fc96aaf7a20","1de3ffe0cc28a9fe2ac761ece075826836b5a02f340b412510a59ba1d41a505a","e46d6cc08d243d8d0d83986f609d830991f00450fb234f5b2f861648c42dc0d8","1c0a98de1323051010ce5b958ad47bc1c007f7921973123c999300e2b7b0ecc0","ff863d17c6c659440f7c5c536e4db7762d8c2565547b2608f36b798a743606ca","5412ad0043cd60d1f1406fc12cb4fb987e9a734decbdd4db6f6acf71791e36fe","ad036a85efcd9e5b4f7dd5c1a7362c8478f9a3b6c3554654ca24a29aa850a9c5","fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7","e297c0a524edee7677939122f90027bfbe5f2698939d9a85728e5044b39c7124","cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e","bc9ee0192f056b3d5527bcd78dc3f9e527a9ba2bdc0a2c296fbc9027147df4b2","b62381cae176db34f003cc6172ee8f3e0122014889d66391aa73698105cf4934","1d9c0a9a6df4e8f29dc84c25c5aa0bb1da5456ebede7a03e03df08bb8b27bae6","84380af21da938a567c65ef95aefb5354f676368ee1a1cbb4cae81604a4c7d17","1af3e1f2a5d1332e136f8b0b95c0e6c0a02aaabd5092b36b64f3042a03debf28","30d8da250766efa99490fc02801047c2c6d72dd0da1bba6581c7e80d1d8842a4","03566202f5553bd2d9de22dfab0c61aa163cabb64f0223c08431fb3fc8f70280","41eb514d9ce0a6e87957f08a4b7af70d93f87637f37dee706e2d92a6601c25a9","e7765aa8bcb74a38b3230d212b4547686eb9796621ffb4367a104451c3f9614f","1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450","1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d","5bf5c7a44e779790d1eb54c234b668b15e34affa95e78eada73e5757f61ed76a","5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70","4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872","7bd01f0f28cd3aeb2046274d85208e245965f6f2948edf4f7b2057bcf9f22ccc","d2f2cf2b8cc92bea913cda4a076e0f790b23a21e84f989d12f0116a7fe3906e0",{"version":"6de125ea94866c736c6d58d68eb15272cf7d1020a5b459fea1c660027eca9a90","affectsGlobalScope":true},{"version":"f5b20bc288ee49989c95b20847fc93b96bf61cc0845598897a6a53a967dd7d07","affectsGlobalScope":true},"064ac1c2ac4b2867c2ceaa74bbdce0cb6a4c16e7c31a6497097159c18f74aa7c","3dc14e1ab45e497e5d5e4295271d54ff689aeae00b4277979fdd10fa563540ae","d3b315763d91265d6b0e7e7fa93cfdb8a80ce7cdd2d9f55ba0f37a22db00bdb8","b789bf89eb19c777ed1e956dbad0925ca795701552d22e68fd130a032008b9f9",{"version":"d165a31bf162123de98d79a503f1dbdf319324cc517787afa384d1ce30df1dd2","affectsGlobalScope":true},"7ad303e40d4fddf44f156129e397511953a71481c5cfd86b1862649aaaf240cc","42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3",{"version":"19057deb2bccf5a9c84dc204f16eca621755a2780c41a0b5609e45c04955a256","signature":"1d52dd10a60604480c2df73b6f2ed688c4fb54521f6e89a7053fade549bbd305"},{"version":"8d508bdeb528b59f183d32ff2befa3e48d3fc19510ced2ab0c4e7b8983dd005c","signature":"96b83cb91c297d92b47dd178ab3b112dabe0131789c4eeadb9d9bfdfeccda54a"},{"version":"d40c2770ae085a4cd33f51fcda608eb21e65c4250eac66314bf34b4e0c483c2f","signature":"435d200a2397f25002f9eef130d0bf16aa85418ef89e59a0cb4460bd28853482"},{"version":"68aa019157d5c0685e9ee7bcfd950d1d5b15d7010d502798579f9eb2036e9fc2","signature":"4a62d9502c7a4cbd46f473e1f2ceb2dfed91689c7b78e59b2003663493f11b1c","affectsGlobalScope":true},{"version":"cd04505787578c14a5021938477de206312888d2783f3dd1675dddae02751463","signature":"265962ad253966cf3790d2132f4e23357649e408ac6835ca030d19d6c124cba2"},{"version":"a4c8347ceffe48e123208416e235a09a927df0c0f57e95df0c1da0781781d71a","signature":"174c55c30944cb2b1c14c23090b16fa4a2477af00b80734e0ee65d09bbe0cec1"},{"version":"a24a735966da9a193677d41f71589d36d4846e132287f51af63091fa1f16bedf","signature":"a0f44e6da310e13692195d272290b5f92d117549a660373907321b4f61b5cf0d"},{"version":"6af2eca05a7efac5490d85f83a62ca12d64e0a02626b48c7fb2035475373e5eb","signature":"c307ca4230db68b58cc3f3c2a4fd560ea4e7e140ae1551894f48180a17b103d6"},{"version":"272575aa71443e9d7732b9c140d839884d890bf35fd8ce065a720b5525582262","signature":"c307ca4230db68b58cc3f3c2a4fd560ea4e7e140ae1551894f48180a17b103d6"},"fe93c474ab38ac02e30e3af073412b4f92b740152cf3a751fdaee8cbea982341","3255b97f3f24af29c79cc1aa88004efb13b6285ebdde0a567bf32e19bb65250d","1e00b8bf9e3766c958218cd6144ffe08418286f89ff44ba5a2cc830c03dd22c7",{"version":"b21a240acb3f8b6f685a0e008c2d771a73614933d58851adca6963ce4dd7b6f1","signature":"060f2a669a8db2872d754ede467a81de2d3864e76780ae6dc152ca6ff2fbc408"},"1d5f3f3a6b781909db1f435b239f36f013b555e88cc63a534b7ed7a179410ae8","7348c35ce10f8323a515ea6a6c237c0dc7c3fd5e491c4dacec2a7b93f0dfcd26","258e67b2638408b67fbf5fe6c953b8f2dd2a69f9171d31c9a976cd2329716e4c",{"version":"4c8c17155232527fec337e87da680b5318714d10df203c785ab0c18e2c79f078","signature":"f88aab57fbb88cd12345f6107f25a2b91b36475471e7a5f69c55a738808e6152"},{"version":"08f9991f5185766214666421a45dcee4b4540dc6374324b079c15d12fb61dcfd","signature":"4970c172dc20f25ef7178ea077d63bab9c93821a8a8611e3ba149682fa7923c0"},{"version":"1d9f8f25b57605e96714c9868e87048dabca1a66eb58ef0dc06f9480fb24c60f","signature":"c6b757bec05519b4d08c80777afcb631a9d47f1dabfebea2b6a2f691b8767582"},{"version":"0699d5a2174ec2bb1cfdc81270042c8aa966864202fdb4d25150d5055ed2e590","signature":"961b814887af8c862239749c7a6eb09ef947b9b5cea4bf16405a48453800383d"},"8f95b4b53d4522f8d1010a073c07aebdda64f44d56b0a22a89400242aa3f4723","7e0dd7745de5cfc3c45f808d9f8553f65ab5e46137c1ee44ab0c6952e2293d7c","7d726479d24cdccd0a77a47b7203a4dd231ae8c97035c12c568dc6966ddb6a64","c335ba6a64c20a5feaff899e6491ec4c4e5dd69e70066b3a4d7e2bc964cd3057","123e805b51c37983b549cbbcc52f644b9714b6595e40145f79472e8a84b9ed86","5d8b31143c299a58ee75de8d08640b76d02b14ed21dd0d2f4f4ab2474f5a7d90","50cca1953dae077b33f08a532c408045e877a7b8c078ee5513231b005664cfd0","5e3dbd3b43e6ec52958b141af2b4bef1b2de8f602d9bea636f37eec83d4440b2","e92837bb84d6e09cf2a41029ca445effec2eb26c22317118439e546fdd33835a","7ed9b4563cbc9f9baac3be14c05ec6236314b9352606ee36758e088a6ec2eeec","7d13c1f2e06b846642d1843412df218725c7b87976e83129f62e8f2d4c58fa52","94a8c1c289ef27f42280c1d730100a28540f709492fff746cff2286b0e406a26","8c26acc596067d21f15f5dc136b4d3daf5658854461c29f4dc6ec66a480718e3","3bec811f5aeba45b7ff3adc9abbfb8aff256984bf44b26a5d1ba276aa79d261d","3737225833b121f3fcb4402070c095d87c29bf453796707a26da829f4228c1fc","7cac781e1c51f3eabbbdd6a2e17f83ad04ed016267ae0ce45d57d4fd14f563c4","c9d927a8c00c15ae154d85cfdc3830e18d3d0972eb549311473468ffd682826d","3c2179e56922a2e287e466534c6fb20691f83853b870d99f609ceb9bc7526a55","c1247c0820d5d9645163386bdb52947ecb9fbc45c436213c0c2c91e1dc8449ca","ed5e4d71f85fa8c7b70eac885fa1da580c7d5632cbbacf6478b675c67d3ec9df","0e24b3fb18105ee4e4536de9d15f885d22a8119633550ff52c6d5aa27cfc4cf5","3ce2e95dba28838c21a981faff028630d7e874c8c429e6d515cdc448cb868e42","e03e31f8cd337c7fd31b106d463960d35344860bc7a02f8f1da9a872eeb686f4","c4b966e6c010c501a32ac2bc8dc9420a87e5c4a3e0685f700262f02c045b6fb4","8d98489d008026dd1a38cb9d70b31558c0164ec488b9815b8ae0ed466d215df9","32c5af30582fc4d9cd817aab004de53994027c64a2931dbd4229935fa677dc31","11b1d083ec7b7de157f7bdad5a11a4b53913d1caa56007773b9a32b203e86781","18d99cee5c26fd8e9fbf386f055ac2b269fa7d645c1e4fd3e7bb5b3476ad6aec","2dde80ffcef0c366a1ae22a6eea8dbc6821d518092b6a0bbdceb37da0d53cfbf","0eab6314ceccba630cc6cca396d80e8415efe03903df730d57517ef1bb30bc4f","bab2e3c1a519be231dafb3d9b0463fc7415e356f89c08b785c0eb8b86d7556c4","ffa44f47e95902b40d02b5a2a2395a2825937df7ca08542716b015dfc61862ca","2b6ec928ac39e9e63c9406c427c0fb7d1e84d80d7b3c8dd78be121c8e5b32ee9","eff6fa1bcb36c62e15a4ab37e4717aa804986cb095c6c0cf676a596d614126eb","9d39ae81494ec822f28812de06f4441ff839940aa06b7e3cb0f9e02623cc310c","203180a5afdf73f2ccea45c493c8016cb3a76a1443696a488be326a5eda43cc3","d438883ea02e1c9e045f47b8a18ae014e4011d4c7ab8798b7b9f1be8b55bab20","babae7f05d084027561e1b7812edb6a660687946bd2c7729d5e186b9e494f9ab","e9a9f2962a374bd2d2b53059357d6ce9f0416f49e2c53d38f5c514ce00692c30","24bd9f28cb058c7bef45cc6f9babaa3af9694c6d68f8037cb6fe88f2419b1c1c","cc4f3e808c9e0251a3e4ac160a5836832eff53194dcb39e3cb2f3e70eaeb511c","7450441efd97dc54f68fe785a6a7f1eae875c11338134889bc88eaf052b70df0","426a1be9f47a8db226a21250158562e1837ad36862671cd4764d4d360f0c9eea","5fc1a3db11db206c64d5038b44276e94dcd402b262776b9a27c19e9176f2e8e6","c73e8393670ba80643a3bc0078a7f3de8193acfe1fd7c84e76132312562a419a","28a7f3ca9fcca7caca89b6bf74c2aefe982f7ff57f36bcb5f29c91833d39b302","d036b54cb57366f779bb8b998d92778ad1453e3e1483013cad43df76dd7c4eb1","325c9eda18d6e766fa14c006d4c1c329f3ad8cbdc2ed7c117af113b71f7aaeed","1105bd9fa22a9508097685f15ea765554eb65d55b455d273150dac014246f787","7092715152a127a874c1453831de3bda4e6527200b5fb79af83c420d4a445b7f","83562fedbc941e235a3be1c99b337cc69d6f9aae4999ac836adef2f920b9e3dc","26fbcd7cd9416ce4438e56417ae7b8b91eafb1cc647bac002778d57583dfecf4","95eb8982a57d9933d65e2afdd5c866bc715e40c30a0dbaceb59892501ad659e8","21a53758e5f95a22f56995233bfe18bd3f39c23ad64a57936cf793f5fb01fdbb","8cbf0fbcb7706d0a2394eae1a564825d50a0d0c1ee9336f8bc8c7da41711ed44","a31738774f95b193a777d9531f9c3609e0294cefe7f0971acce955e8ef52e8d5","ea88363b3147f77e19ea1b55f0cb038626364d476863e8e2f91cf60cf286e7e7","913ad4fa2f9e0b444c264605fce1605cbeb47d5be25137f9bdd25f3ccd7be556","d2a7cc6e7044a81891d07d0be88dd12f7559fbbb35227a3ad42514c9fb96b83a","914260e95660c84a6e283ad91dd832d9f3119fe9a7cb7ffcd0764716dc4f705a","0eb6cee7bcf4262afd1a083f9fef4ec12de5022e29e9fa0c86d0acd635eb37ac","a6b39f1d2ceaf4f5bb3dab98483fff79e4ede4786928c93307a482d0e888d12d","3cd99ec82de92fff68a2768cab73739911766bb7c6e7d10e29706d656107a2b5","c21314a38f4337202999e6473579aeaaef6371b6d001973692715433f4e7fc89","899bc20734112d2094d89e4f55644c6f3115d2440c250822ffbd4fa212b158a5","d39fd2772f9bb4dacc97319c5ed760ea9e2d83458b522eb5894ab09b0674a704","3f635b32106636b9c54c7cce0c73372f99859f9ae90c0de7d83d01f48af3064f","d114c65d009de428f5c9ae22fab8ab7a602d22822fb8eb77ba3f09ca7aaef895","fdc9ee7bc24198eb4ef75bff9c3233bab7b7586bad8ea11bf18cd6faacbc5c65","db7da89b083e353471f3911adb59288c2d4bda401b25433943e8128d654e0afc",{"version":"bf8a203c1089863aed23e7809aa227249eb2a6ffd6c4a3d1ed379937998660b9","signature":"4c71dae66cfa46abb4940cfecd76879ac342f070b02f1646e002aeaf70471a6c"},{"version":"ce892909eba31bf839570743ab5c08c35b501c847729e001922110052a972640","signature":"8594bd59ac178f59f2d84bf8688d27ad0a3becc7ba33fd81f04f8d824ebf4401"},{"version":"acc106b92fba6ddbb441372feefa1d5590714b547f02b50dd745d7737a6efa0c","signature":"7b87720aae5dbfb749d597c437531239e4019502dc0e78633561735db617b6f3"},{"version":"b4319293b3352cbb120669fb1f12b43fe008509da7092509d3a21d40f8cde762","signature":"af43d990de0a7ca77c517828e04eef576a41eecffd40f4969b7204730d7ab4ff"},{"version":"58161f996133c22693eb7ec1de37906396614f5f4496994114134a90170154c2","signature":"ee9ab354680e54334978cd515a52ab5827b0aa354a219a0090d6a3c2a5200607"},{"version":"d89f3462199b790d57723554a63e1427fe6b45d96fd3402e3257aeb65c1565ef","signature":"b600573c0debf674ee5c2f87d41a32a0ad05fbe876b4424de64e682e79aed0ef"},{"version":"9db30500f44eec4dfd5f2b05354871bd3553d92cf7f43c8cee4eff29b7d0c3fe","signature":"f0ea57024528a15b4b205f6b0730596c6b0fe6c05e5eec104a16feaaf3800a07"},{"version":"0174c67715efb46411ea3c1123de56364610e038127ac26b0c12a0e40e640963","signature":"11e21c32bb2568d07cbbe5fc074cb99b6904e76676f7cec14fea5b0b4f1b8079"},{"version":"aa4b87c53ed1aff38eb9199e5e47757ddf50fa17909ebeb84f8282825407748d","signature":"ed2a76a9709b7048cda9306c4923395ed1ed27f256aea26f91d3766cc4c819fb"},{"version":"0d4c12fbf58f91044d49cd2c5a4c0ca5e3e3e60ff885bd71a037a2e4eafaa96d","signature":"2e3e6fb88ce4f31e2be2058eb3a7c598ca20090b3b627c5fb6115cc43b8f8c4a"},{"version":"8405ddbc38a2fbac6ca04a336fc729a80b0c7860b7d729c35586be3d07005ea4","signature":"61b4247bf8ba6cc79cc47a073bea9c1df7a1e0b980a938f4838a7b0b995fa4fe"},{"version":"217661fc5bbc27004adbc4f2bc2be52c69385b2d623c7a8307236156b7c90413","signature":"eb0a47da0b0e3ffd59cf4ad71a6bf0ebf9d93680d53bb3c5dfbd2c01405e60e1"},{"version":"5dd20c5d4abeda697efdfaec0660a7be908d2ca9721591007dc0c87165d5ba2c","signature":"282b58c1a7eaa775343f717ce14a836038ef998c9ef306aba618b46ac328bbe1"},{"version":"a7ae6acae0232bd62325d5e35bc1ac57eff67516737f68285a2d124c176fea15","signature":"c2ad7311e0fbd63784d9762b56a4b75f6d11ca83d953d8d8e4582b28bb68b508"},{"version":"3be4eca7d70606b22f8b33e9fc6c5070c61438d2c79844752f214f41e3098418","signature":"7a4fe07bffc8b6ec1785aec2b011f454fc93828d90b9269ccdda995af85bf073"},{"version":"84b7b73cc56d8a8f66956740692053f280289ae6fa0fe880db00a0b0d4a7912e","signature":"fc0ae6558234c70ac37e299e68ab60c71602026067d156c59385a99b4175fb35"},{"version":"57b1aee4090386337584eb539c7aa8f0270c326dffe5c73621e6bb659d124bfc","signature":"a9106848710437acb35e74a35a0aaeea7b284f394b67dfe260365a26e4755eb6"},{"version":"ed6a6a0d5284b822dacd810206ecfe72084222d26e081e449dbf12945204fffe","signature":"fadfd32ec7a359064907a6416edcf6299f686c781e85246191af6eb82ab60474"},{"version":"5454f8e010bde885c7f2e432ed42e19c732902a50565b779e65b40f6f9866dad","signature":"fff30c11253d7dd225adf01b25018fcf08094057b0bc7ef6e882a23b7c328770"},{"version":"5d744607440816b2933e86bb4a3155fd0d225a8da3d13e8a9a43a3adbb69ca01","signature":"e9d4bb56f013adcd8a0b33211e0870040704ece52fc5d7f303482c1ea7b7775e"},{"version":"a5654321577784cf14f48954695365f6cd3c2922abe9c2d3084cbd88e7271d52","signature":"f17bd7ed36a4a9810bd5fd92dfd490b6835a6d537f8b4dc6c48fa3e3fcf28af5"},{"version":"0f79bf2e7c159343578dec5a9015e46387f30def460c518bbbbe35fdff0eed2f","signature":"c357f418fbf9e83e1eda2f76b9c0a69adab3cc16131895c20a2c37665421398e"},{"version":"cf9c524d9e89e1c91612193c85161793993b06cfa2a8d33541c75bca938ef82f","signature":"b62f8aeb315a6874b400426f33a71eda94b7086c73e07ace88f3a2bf39779f20"},"6b93e2a4435adceb71df8be7c9593d144d3c44bdf21c6399c5ba48c19d8d5250",{"version":"d4926c8f4a2dbddff6902851c92b410c808973f3287b32210e2bc0f5f8939320","signature":"38a79980404c2230335f84dbf00b715475d5a0c1b79de6d5645a0f4334a57c62"},{"version":"d2f08a6733d2b86858b627436f9ca44be1be10b256694dbb6dda7eff11d899d0","signature":"ebde7c386b80a28c65a11973982ed4414e82bba204ec868149244c23afe6638b"},{"version":"3b12535bcc01640c88da8e8462bbf67515a65a49db3b426b2deb3f960946c9e5","signature":"1d0150fa7b967ca930bf4d81f97968e81fbb12df39f7ff2dea819fe239a08a7e"},{"version":"d2babc5b87e4249402e30c1a2c64a6507fb89720e3f0d715077ac1e1cc85c876","signature":"9d48ba96f7a03f32aaaebc42d2093f08e05ff9845365325d607c574115981b3e"},{"version":"9d1f687f92714b82d0a55d822c181281f03b047a173a01c78a4124abaacbbf27","signature":"b3ff69f48488da5978db457570b0745f10ac01095b10460cc4323addd16b1e51"},{"version":"3557c9dadc3a7dcad9433e9f97cab42e1441611eaecf95ca2cc1e4c0970873d1","signature":"b57ffd2dee78f4d7e24877bab745458a15e7d4c2a9a715aa310e7d9776769ca1"},{"version":"609cdb013788cd63c927c074d933b00926a85dcefad189e1a4038a659703b779","signature":"44a30773fd48463f72a0ee166e80d71ea6a8ce2ea020dfd58ef85889ed56ce4e"},{"version":"d2bffec1bbab2fda3cb0e541c32bb6213f3b223c05ca5a2252cfd428ede8299f","signature":"3fdb55f8db6b78df59effe7150172b72a31f2a4ac7168115eeb2b6a1c98fb16f"},"194575ba989d1b6d14d4f4f7818a3132e46c0c038014d95bca9ec487a232845c","d38ce747917eeb6ae6d6e25c5c1704465e20d586fb4b685e056a151107bcfb93","267d6c33f6eb2acc8287b9dcc44ecd47dca5d002139f2dbd615420a83ac9c794",{"version":"c4054b9fe7c39e3bd7469ecc231152f46770789260585e915b13f6369e41e83e","signature":"ee2be24b03f0c81f478494b26979c41c1a46f0d61bde13b2a261df6f403c7886"},"c09d9f9e2665f2edfc7436577ab7de14333d92e6b3482df1b8d24126de1af5fa","9014a7fb5ee6092e72f54abc4c2bcf67941b7793dd8f04d2cbb0b4398f3a259f","c90bc2f4cc839efafb5afa09f7c43c5b9c8721e2c18c19de611df9cb8fdee0f7","f2dfcfffec92fb67f40ea55a99bf8481b98618e019e5937269aaf17fd35bed8c","25ca145f51bdd2277d91297fc7a976c9794a961808723a0481d940db73128870","127d649065fdb75c6659afdde9431b7cbb56250fa5048b013e2f3f81975bde1c","033eaedbfebd0c6e07a4cfffc59ec3c62872a4daed6ba391a2d02aff8dd2dd4f","3733123c9552464e981e3c48a3f3be87e30a0b87c57f39dc445e4a1a9b74f958","67a9057796ca7c7c04005297532358ad6e231223a8c405011f2f6752f79c5818",{"version":"1228e16822f5edea8f5221e1dbd091efd52daa5a438e6a2a5e92c0f3cb816835","signature":"11897cccf9e074fe6001b66576a2ac97e23f29a54991239800a309cdf08ae592"},{"version":"b7eb2b3d8e59e11c623616c54a9b9f594fcb0b18ba36675a028c53c33bf8fdb1","signature":"462223fc5c3f164d8818f43ab372137795270cc7dbdf537fd8f8db5ccfc074e7"},{"version":"05741e2c3b958508424ff867afc2573912e413ddbd4177fa691045eba32af703","signature":"8ee403d597589bc5bc99839d5199596b16f459bc2f3a78ecf6cecdbec6843341"},{"version":"11c2408c325a68fe9143136fd1f27d19d97cfec7b8d8e6e7772144a49c4ff40c","signature":"6ebe765592b2e9f9cfcc835e1eb80320ae2a60feb59e51ee0d036e5d6e812ec0"},"d1986184a09a52db8228cb2bb2a61a8c05c9354e5b93cec8e2628d8579c892d7","a09cf0eb099e890acd7b2d49371982c1e9212e345c74aa1848a27da14b34e4d1"],"root":[498,499,[501,509],513,[517,520],[591,641]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":4,"module":99,"skipLibCheck":true,"strict":true,"target":9},"fileIdsList":[[451,452,453,454],[492,495,498,500,508,509,513,520,593,604,605,606,609,611,623,624,625,626,627,628,629,630,631,632,633,634,635,636,638],[160,485,500,503,504,519,590],[160,485,500,503,519,586,589,591],[160,485,500,503,504,519],[160,485,500,503,519],[160,475,485,500,504,519],[500],[160,475,485,500,504,505,519,591],[160,485,500,504,505,519,590],[160,500],[160,500,586,589],[500,502],[160,500,504,505],[500,594],[500,595],[500,597],[500,518,519],[160,475,485,500,504,505,519,590,599,600,601,607],[160,500,504,519,590,599,600],[500,590,600],[475,500,505,506,519,590,607],[160,500,505,590,599,600,601,602,607],[160,475,485,500,505,506,519,590,607],[160,500,504],[160,475,500,505,589,590,599,607,616],[160,475,485,500,504,505,590,599,601,603,607,616],[160,475,500,505,589,590,599,600,601,602,607,616],[160,475,500,504,505,506,519,590,599,600,601,607],[160,485,500,504,505,519],[500,612],[500,613],[500,614],[500,607,608],[485,500],[500,615],[500,610],[500,622],[500,620],[500,619],[500,621],[500,618],[500,617],[500,598],[500,592],[475,500,637],[492,500],[492,500,507],[488,493,496,500,512],[500,516],[496,497,498],[555,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585],[555],[555,557],[160,555,557],[521,522,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554],[521,522,523],[521,522],[522],[71],[107],[108,113,142],[109,120,121,128,139,150],[109,110,120,128],[111,151],[112,113,121,129],[113,139,147],[114,116,120,128],[107,115],[116,117],[120],[118,120],[107,120],[120,121,122,139,150],[120,121,122,135,139,142],[105,108,155],[116,120,123,128,139,150],[120,121,123,124,128,139,147,150],[123,125,139,147,150],[71,72,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157],[120,126],[127,150,155],[116,120,128,139],[129],[130],[107,131],[128,129,132,149,155],[133],[134],[120,135,136],[135,137,151,153],[108,120,139,140,141,142],[108,139,141],[139,140],[142],[143],[107,139],[120,145,146],[145,146],[113,128,139,147],[148],[128,149],[108,123,134,150],[113,151],[139,152],[127,153],[154],[108,113,120,122,131,139,150,153,155],[139,156],[64,164,165,166,428],[64],[64,164,165],[64,165,428],[160],[64,68,162,446,491],[64,68,161,446,491],[61,62,63],[62,159],[449],[399,460,461],[171,172,174,186,210,325,336,442],[174,205,206,207,209,442],[174,342,344,346,347,349,442,444],[174,208,245,442],[172,174,185,186,192,198,203,324,325,326,335,442,444],[442],[181,187,206,226,321],[174],[167,181,187],[353],[350,351,353],[350,352,442],[123,226,423,439],[123,297,300,316,321,439],[123,269,439],[329],[328,329,330],[328],[70,123,167,174,186,192,198,204,206,210,211,224,225,292,322,323,336,442,446],[171,174,208,245,342,343,348,442,494],[208,494],[171,225,394,442,494],[494],[174,208,209,494],[345,494],[211,324,327,334],[64,399],[134,181,196],[181,196],[64,266],[64,196],[64,187,196,399],[181,252,266,267,476,483],[251,477,478,479,480,482],[302],[302,303],[185,187,254,255],[187,261,262],[187,256,264],[261],[179,187,254,255,256,257,258,259,260,261,264],[187,254,261,262,263,265],[187,255,257,258],[255,257,260,262],[481],[187],[64,175,470],[64,150],[64,208,243],[64,208,336],[241,246],[64,242,448],[510],[64,123,139,490,491],[64,68,123,161,162,446,490],[123,187],[123,186,191,272,289,331,332,336,391,393,442,443],[224,333],[446],[173],[64,178,181,396,412,414],[134,181,396,411,412,413,493],[405,406,407,408,409,410],[407],[411],[196,360,361,363],[64,187,354,355,356,357,362],[360,362],[358],[359],[64,196,242,448],[64,196,447,448],[64,196,448],[289,290],[290],[123,443,448],[319],[107,318],[181,187,193,195,297,310,314,316,393,396,431,432,439,443],[187,236,258],[297,308,311,316],[64,178,181,297,300,316,319,353,400,401,402,403,404,415,416,417,418,419,420,421,422,494],[178,181,206,297,304,305,306,309,310],[139,187,206,308,315,396,397,439],[312],[123,134,175,187,191,201,233,234,237,289,292,357,391,392,431,442,443,444,446,494],[178,179,181],[297],[107,206,233,234,291,292,293,294,295,296,443],[316],[107,180,181,191,195,231,297,304,305,306,307,308,311,312,313,314,315,432],[123,231,232,304,443,444],[206,234,289,292,297,393,443],[123,442,444],[123,139,439,443,444],[123,134,167,181,186,193,195,198,201,208,228,233,234,235,236,237,272,273,275,278,280,283,284,285,286,288,336,391,393,439,442,443,444],[123,139],[174,175,176,204,439,440,441,446,448,494],[171,172,442],[365],[123,139,150,183,349,353,354,355,356,357,363,364,494],[134,150,167,181,183,195,198,234,273,278,288,289,342,369,370,371,377,380,381,391,393,439,442],[198,204,211,224,234,292,442],[123,150,175,186,195,234,375,439,442],[395],[123,365,378,379,388],[439,442],[294,432],[195,233,336,448],[123,134,173,278,338,342,371,377,380,383,439],[123,211,224,342,384],[174,235,336,386,442,444],[123,150,357,442],[123,208,235,336,337,338,347,365,385,387,442],[70,123,233,390,446,448],[287,391],[123,134,181,184,186,187,193,195,201,210,211,224,234,237,273,275,285,288,289,336,369,370,371,372,374,376,391,393,439,448],[123,139,211,377,382,388,439],[214,215,216,217,218,219,220,221,222,223],[228,279],[281],[279],[281,282],[514],[123,185,186,187,191,192,443],[123,134,173,175,193,197,233,236,237,271,391,439,444,446,448],[123,134,150,177,184,185,195,197,234,389,432,438,443],[304],[305],[187,198,431],[306],[180],[182,194],[123,182,186,193],[189,194],[190],[182,183],[182,238],[182],[184,228,277],[276],[181,183,184],[184,274],[181,183],[233,336],[431],[123,150,193,195,199,233,336,390,393,396,397,398,424,425,427,430,432,439,443],[247,250,252,253,266,267],[64,164,165,166,196,426],[64,164,165,166,196,426,429],[320],[206,227,232,233,297,298,299,300,301,303,316,317,319,322,390,393,442,444],[266],[123,271,439],[271],[123,193,239,268,270,272,390,439,446,448],[247,248,249,250,252,253,266,267,447],[70,123,134,150,182,183,195,201,233,234,237,336,388,389,391,439,442,443,446],[178,181,188],[232,234,366,369],[232,367,433,434,435,436,437],[123,228,442],[123],[231,316],[230],[232,285],[229,231,442],[123,177,232,366,367,368,439,442,443],[64,181,187,265],[64,179],[169,170],[64,175],[64,181,251],[64,70,233,237,446,448],[175,470,471],[64,246],[64,134,150,173,240,242,244,245,448],[181,208,443],[181,373],[64,121,123,134,171,173,246,344,446,447],[64,161,162,446,491],[64,65,66,67,68],[113],[339,340,341],[339],[64,68,123,125,134,158,160,161,162,163,166,167,173,201,206,383,411,444,445,448,491],[456],[458],[462],[511],[464],[466,467,468],[472],[69,450,455,457,459,463,465,469,473,475,485,486,488,492,493,494,495],[474],[484],[515],[242],[487],[107,232,366,367,369,433,434,436,437,489,491],[158],[139,158],[82,86,150],[82,139,150],[77],[79,82,147,150],[128,147],[77,158],[79,82,128,150],[74,75,78,81,108,120,139,150],[74,80],[78,82,108,142,150,158],[108,158],[98,108,158],[76,77,158],[82],[76,77,78,79,80,81,82,83,84,86,87,88,89,90,91,92,93,94,95,96,97,99,100,101,102,103,104],[82,89,90],[80,82,90,91],[81],[74,77,82],[82,86,90,91],[86],[80,82,85,150],[74,79,80,82,86,89],[108,139],[77,82,98,108,155,158],[500,555],[500,556,587,588],[500,555,556,586],[500,556,586],[500,504],[160,504],[500,505],[642],[493,496,500],[643],[492],[68,160,446,491,644,645,646,647],[160,399],[134,181,500],[181,500],[160,266],[160,187,399,500],[160,175,470],[150,160],[160,208,243],[160,208,336],[160,242,448],[123,139,160,490,491],[68,123,160,163,446,490,644,645,646,647],[160,178,181,396,412,414],[360,361,363,500],[160,187,354,355,356,357,362],[160,242,448,500],[160,447,448,500],[160,448,500],[160,178,181,297,300,316,319,353,400,401,402,403,404,415,416,417,418,419,420,421,422,494],[160,163,500,647,648,649],[160,163,500,647,648,649,650],[160,181,187,265],[160,179],[160,175],[160,181,251],[70,160,233,237,446,448],[160,246],[134,150,160,173,240,242,244,245,448],[121,123,134,160,171,173,246,344,446,447],[160,163,446,491,644,645,646,647],[65,66,67,68,160],[68,123,125,134,158,160,163,167,173,201,206,383,411,444,445,448,491,644,645,646,647]],"referencedMap":[[640,1],[641,2],[591,3],[592,4],[594,5],[595,6],[596,7],[518,8],[597,9],[598,10],[599,11],[600,12],[601,11],[602,11],[603,11],[503,8],[502,8],[504,13],[505,8],[519,14],[604,15],[605,16],[606,17],[520,18],[612,19],[613,20],[614,21],[610,22],[615,23],[608,24],[506,25],[617,26],[618,27],[616,25],[619,26],[620,27],[621,28],[622,29],[607,30],[623,31],[624,32],[625,33],[609,34],[626,35],[627,36],[611,37],[628,38],[631,39],[630,40],[632,39],[629,41],[634,42],[633,43],[635,42],[636,44],[593,45],[638,46],[507,47],[508,48],[509,48],[513,49],[517,50],[637,11],[639,8],[499,51],[501,47],[586,52],[557,53],[575,54],[572,54],[562,54],[561,54],[579,54],[569,54],[573,54],[585,54],[584,54],[582,55],[581,54],[583,54],[558,54],[564,54],[560,54],[578,54],[566,54],[574,54],[563,54],[559,54],[570,54],[576,54],[577,54],[571,54],[567,54],[568,54],[580,54],[565,54],[555,56],[541,57],[538,58],[528,59],[527,57],[546,58],[535,57],[539,57],[551,58],[550,58],[548,57],[547,58],[549,58],[524,57],[530,57],[526,58],[544,58],[531,58],[540,57],[529,57],[525,57],[536,58],[542,57],[543,57],[537,57],[532,57],[534,57],[545,58],[533,59],[71,60],[72,60],[107,61],[108,62],[109,63],[110,64],[111,65],[112,66],[113,67],[114,68],[115,69],[116,70],[117,70],[119,71],[118,72],[120,73],[121,74],[122,75],[106,76],[123,77],[124,78],[125,79],[158,80],[126,81],[127,82],[128,83],[129,84],[130,85],[131,86],[132,87],[133,88],[134,89],[135,90],[136,90],[137,91],[139,92],[141,93],[140,94],[142,95],[143,96],[144,97],[145,98],[146,99],[147,100],[148,101],[149,102],[150,103],[151,104],[152,105],[153,106],[154,107],[155,108],[156,109],[165,110],[428,111],[166,112],[164,111],[429,113],[163,114],[161,115],[162,116],[64,117],[426,111],[196,111],[160,118],[500,114],[590,111],[450,119],[455,1],[462,120],[445,121],[208,122],[348,123],[351,124],[336,125],[343,126],[322,127],[368,128],[198,129],[350,130],[352,131],[353,132],[424,133],[317,134],[270,135],[330,136],[331,137],[329,138],[324,139],[349,140],[209,141],[395,142],[236,143],[210,144],[237,143],[273,143],[176,143],[346,145],[335,146],[461,147],[402,148],[403,149],[399,150],[404,151],[400,152],[484,153],[483,154],[303,155],[477,156],[401,111],[256,157],[263,158],[265,159],[260,160],[262,161],[264,162],[259,163],[261,164],[482,165],[254,166],[471,167],[474,168],[244,169],[243,170],[242,171],[487,111],[241,172],[511,173],[514,174],[490,111],[491,175],[332,176],[333,177],[334,178],[192,179],[416,111],[174,180],[415,181],[414,182],[411,183],[409,184],[412,185],[410,184],[203,143],[362,186],[363,187],[361,188],[359,189],[360,190],[422,151],[197,151],[449,191],[456,192],[460,193],[291,194],[436,195],[444,196],[318,197],[319,198],[397,199],[420,200],[295,111],[312,201],[423,202],[311,203],[421,204],[418,205],[393,206],[180,207],[293,208],[297,209],[313,210],[316,211],[305,212],[298,213],[443,214],[371,215],[289,216],[177,217],[442,218],[173,219],[364,220],[365,221],[382,222],[381,223],[376,224],[396,225],[380,226],[228,227],[314,228],[234,229],[384,230],[385,231],[387,232],[389,233],[388,234],[378,217],[391,235],[288,236],[377,237],[383,238],[224,239],[280,240],[284,241],[281,242],[283,243],[286,241],[282,242],[515,244],[193,245],[272,246],[439,247],[466,248],[468,249],[432,250],[467,251],[181,252],[178,252],[195,253],[194,254],[190,255],[191,256],[199,257],[227,257],[238,257],[274,258],[239,258],[183,259],[278,260],[277,261],[276,262],[275,263],[184,264],[425,265],[226,266],[431,267],[398,268],[427,269],[430,270],[321,271],[320,272],[301,273],[287,274],[269,275],[271,276],[268,277],[390,278],[189,279],[392,280],[438,281],[229,282],[306,283],[304,284],[231,285],[366,286],[232,287],[367,287],[369,288],[266,289],[187,290],[171,291],[458,111],[470,292],[253,111],[464,151],[252,293],[447,294],[250,292],[472,295],[248,111],[249,111],[247,296],[246,297],[235,298],[310,89],[370,89],[374,299],[258,166],[267,111],[441,179],[448,300],[65,111],[68,301],[69,302],[66,111],[347,303],[342,304],[340,305],[446,306],[457,307],[459,308],[463,309],[512,310],[465,311],[469,312],[497,313],[473,313],[496,314],[475,315],[485,316],[516,317],[486,318],[488,319],[492,320],[495,179],[493,321],[375,322],[89,323],[96,324],[88,323],[103,325],[80,326],[79,327],[102,321],[97,328],[100,329],[82,330],[81,331],[77,332],[76,333],[99,334],[78,335],[83,336],[87,336],[105,337],[104,336],[91,338],[92,339],[94,340],[90,341],[93,342],[98,321],[85,343],[86,344],[95,345],[75,346],[101,347],[556,348],[589,349],[587,350],[588,351]],"exportedModulesMap":[[640,1],[641,2],[591,352],[592,8],[594,8],[595,8],[596,8],[518,8],[597,8],[598,8],[599,11],[600,11],[601,11],[602,11],[603,11],[519,353],[604,8],[605,8],[606,8],[520,8],[612,8],[613,8],[614,21],[610,8],[615,8],[608,8],[617,8],[618,8],[619,8],[620,8],[621,8],[622,8],[607,354],[623,31],[624,32],[625,33],[609,8],[627,36],[611,8],[628,38],[631,39],[630,40],[632,39],[629,41],[634,42],[633,43],[635,42],[636,8],[593,8],[638,8],[507,355],[508,355],[509,355],[513,356],[517,357],[637,8],[639,8],[499,51],[501,358],[586,52],[557,53],[575,54],[572,54],[562,54],[561,54],[579,54],[569,54],[573,54],[585,54],[584,54],[582,55],[581,54],[583,54],[558,54],[564,54],[560,54],[578,54],[566,54],[574,54],[563,54],[559,54],[570,54],[576,54],[577,54],[571,54],[567,54],[568,54],[580,54],[565,54],[555,56],[541,57],[538,58],[528,59],[527,57],[546,58],[535,57],[539,57],[551,58],[550,58],[548,57],[547,58],[549,58],[524,57],[530,57],[526,58],[544,58],[531,58],[540,57],[529,57],[525,57],[536,58],[542,57],[543,57],[537,57],[532,57],[534,57],[545,58],[533,59],[71,60],[72,60],[107,61],[108,62],[109,63],[110,64],[111,65],[112,66],[113,67],[114,68],[115,69],[116,70],[117,70],[119,71],[118,72],[120,73],[121,74],[122,75],[106,76],[123,77],[124,78],[125,79],[158,80],[126,81],[127,82],[128,83],[129,84],[130,85],[131,86],[132,87],[133,88],[134,89],[135,90],[136,90],[137,91],[139,92],[141,93],[140,94],[142,95],[143,96],[144,97],[145,98],[146,99],[147,100],[148,101],[149,102],[150,103],[151,104],[152,105],[153,106],[154,107],[155,108],[156,109],[165,110],[428,111],[166,112],[164,111],[429,113],[163,359],[161,115],[162,116],[64,117],[426,111],[196,111],[160,118],[500,114],[590,114],[450,119],[455,1],[462,120],[445,121],[208,122],[348,123],[351,124],[336,125],[343,126],[322,127],[368,128],[198,129],[350,130],[352,131],[353,132],[424,133],[317,134],[270,135],[330,136],[331,137],[329,138],[324,139],[349,140],[209,141],[395,142],[236,143],[210,144],[237,143],[273,143],[176,143],[346,145],[335,146],[461,360],[402,361],[403,362],[399,363],[404,11],[400,364],[484,153],[483,154],[303,155],[477,156],[401,114],[256,157],[263,158],[265,159],[260,160],[262,161],[264,162],[259,163],[261,164],[482,165],[254,166],[471,365],[474,366],[244,367],[243,368],[242,171],[487,114],[241,369],[511,173],[514,370],[490,114],[491,371],[332,176],[333,177],[334,178],[192,179],[416,114],[174,180],[415,372],[414,182],[411,183],[409,184],[412,185],[410,184],[203,143],[362,373],[363,374],[361,188],[359,189],[360,190],[422,11],[197,11],[449,375],[456,376],[460,377],[291,194],[436,195],[444,196],[318,197],[319,198],[397,199],[420,200],[295,114],[312,201],[423,378],[311,203],[421,204],[418,205],[393,206],[180,207],[293,208],[297,209],[313,210],[316,211],[305,212],[298,213],[443,214],[371,215],[289,216],[177,217],[442,218],[173,219],[364,220],[365,221],[382,222],[381,223],[376,224],[396,225],[380,226],[228,227],[314,228],[234,229],[384,230],[385,231],[387,232],[389,233],[388,234],[378,217],[391,235],[288,236],[377,237],[383,238],[224,239],[280,240],[284,241],[281,242],[283,243],[286,241],[282,242],[515,244],[193,245],[272,246],[439,247],[466,248],[468,249],[432,250],[467,251],[181,252],[178,252],[195,253],[194,254],[190,255],[191,256],[199,257],[227,257],[238,257],[274,258],[239,258],[183,259],[278,260],[277,261],[276,262],[275,263],[184,264],[425,265],[226,266],[431,267],[398,268],[427,379],[430,380],[321,271],[320,272],[301,273],[287,274],[269,275],[271,276],[268,277],[390,278],[189,279],[392,280],[438,281],[229,282],[306,283],[304,284],[231,285],[366,286],[232,287],[367,287],[369,288],[266,381],[187,382],[171,291],[458,114],[470,383],[253,114],[464,11],[252,384],[447,385],[250,383],[472,295],[248,114],[249,114],[247,386],[246,387],[235,298],[310,89],[370,89],[374,299],[258,166],[267,114],[441,179],[448,388],[65,114],[68,389],[69,390],[66,114],[347,303],[342,304],[340,305],[446,391],[457,307],[459,308],[463,309],[512,310],[465,311],[469,312],[497,313],[473,313],[496,314],[475,315],[485,316],[516,317],[486,318],[488,319],[492,320],[495,179],[493,321],[375,322],[89,323],[96,324],[88,323],[103,325],[80,326],[79,327],[102,321],[97,328],[100,329],[82,330],[81,331],[77,332],[76,333],[99,334],[78,335],[83,336],[87,336],[105,337],[104,336],[91,338],[92,339],[94,340],[90,341],[93,342],[98,321],[85,343],[86,344],[95,345],[75,346],[101,347],[556,348],[589,349],[587,350],[588,351]],"semanticDiagnosticsPerFile":[640,498,641,591,592,594,595,596,518,597,598,599,600,601,602,603,503,502,504,505,519,604,605,606,520,612,613,614,610,615,608,506,617,618,616,619,620,621,622,607,623,624,625,609,626,627,611,628,631,630,632,629,634,633,635,636,593,638,507,508,509,513,517,637,639,499,501,344,586,557,575,572,562,561,579,569,573,585,584,582,581,583,558,564,560,578,566,574,563,559,570,576,577,571,567,568,580,565,554,552,553,555,521,522,541,538,528,527,546,535,539,551,550,548,547,549,524,530,526,544,531,540,529,525,536,542,543,537,532,534,545,533,523,71,72,107,108,109,110,111,112,113,114,115,116,117,119,118,120,121,122,106,157,123,124,125,158,126,127,128,129,130,131,132,133,134,135,136,137,138,139,141,140,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,63,165,428,166,164,429,163,161,162,61,64,426,196,159,160,500,73,62,590,450,455,462,445,200,208,348,351,323,336,343,225,325,206,322,368,207,198,350,352,353,424,317,270,330,331,329,328,324,349,209,394,395,236,210,237,273,176,346,345,335,440,185,461,402,403,399,479,300,404,400,484,483,478,251,303,302,477,401,256,263,265,255,260,262,264,259,257,261,480,476,482,481,254,471,474,244,243,242,487,241,230,489,511,510,514,490,491,168,332,333,334,172,337,192,167,416,174,415,414,405,406,413,408,411,407,409,412,410,205,202,203,357,362,363,361,359,360,355,422,197,449,456,460,291,290,285,436,444,318,319,397,307,420,295,312,423,308,311,309,421,418,417,419,315,393,180,293,297,313,316,305,298,443,371,289,177,442,173,364,356,365,382,354,381,70,376,201,396,372,186,188,327,380,204,228,314,234,294,379,358,384,385,326,387,389,388,338,378,391,288,377,383,213,217,216,215,220,214,223,222,219,218,221,224,212,280,279,284,281,283,286,282,515,193,272,439,437,466,468,432,467,181,178,211,195,194,190,191,199,227,238,274,239,183,182,278,277,276,275,184,425,226,431,398,427,430,321,320,301,287,269,271,268,390,292,454,189,392,438,299,229,306,304,231,366,433,232,367,452,451,453,435,434,369,296,266,187,245,171,233,458,170,470,253,464,252,447,250,175,472,248,249,240,169,247,246,235,310,370,386,374,373,258,179,267,441,448,65,68,69,66,67,347,342,341,340,339,446,457,459,463,512,465,469,497,473,496,475,485,516,486,488,492,495,494,493,375,59,60,10,11,13,12,2,14,15,16,17,18,19,20,21,3,22,4,23,27,24,25,26,28,29,30,5,31,32,33,34,6,38,35,36,37,39,7,40,45,46,41,42,43,44,8,50,47,48,49,51,9,52,53,54,57,55,56,1,58,89,96,88,103,80,79,102,97,100,82,81,77,76,99,78,83,84,87,74,105,104,91,92,94,90,93,98,85,86,95,75,101,556,589,587,588],"affectedFilesPendingEmit":[641,591,592,594,595,596,518,597,598,599,600,601,602,603,503,502,504,505,519,604,605,606,520,612,613,614,610,615,608,506,617,618,616,619,620,621,622,607,623,624,625,609,626,627,611,628,631,630,632,629,634,633,635,636,593,638,507,508,509,513,517,637,639,501]},"version":"5.4.5"} \ No newline at end of file +{"program":{"fileNames":["../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es5.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2016.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2018.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2019.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2021.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.dom.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.core.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.date.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.object.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.string.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2019.array.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2019.object.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2019.string.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.date.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.string.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2020.number.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2021.string.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.array.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.error.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.object.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.sharedmemory.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.string.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.esnext.intl.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.decorators.d.ts","../../../node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/global.d.ts","../../../node_modules/.pnpm/csstype@3.2.3/node_modules/csstype/index.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/css.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/macro.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/style.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/global.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/get-page-files.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/assert.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/assert/strict.d.ts","../../../node_modules/.pnpm/buffer@5.6.0/node_modules/buffer/index.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/header.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/readable.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/file.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/fetch.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/formdata.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/connector.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/client.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/errors.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/dispatcher.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/global-dispatcher.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/global-origin.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/pool-stats.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/pool.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/handlers.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/balanced-pool.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/agent.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-interceptor.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-agent.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-client.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-pool.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-errors.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/proxy-agent.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/api.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/cookies.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/patch.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/filereader.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/diagnostics-channel.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/websocket.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/content-type.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/cache.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/interceptors.d.ts","../../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/index.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/globals.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/async_hooks.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/buffer.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/child_process.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/cluster.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/console.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/constants.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/crypto.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/dgram.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/diagnostics_channel.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/dns.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/dns/promises.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/domain.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/dom-events.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/events.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/fs.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/fs/promises.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/http.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/http2.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/https.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/inspector.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/module.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/net.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/os.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/path.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/perf_hooks.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/process.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/punycode.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/querystring.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/readline.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/readline/promises.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/repl.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/sea.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/stream.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/stream/promises.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/stream/consumers.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/stream/web.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/string_decoder.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/test.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/timers.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/timers/promises.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/tls.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/trace_events.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/tty.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/url.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/util.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/v8.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/vm.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/wasi.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/worker_threads.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/zlib.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/globals.global.d.ts","../../../node_modules/.pnpm/@types+node@20.12.12/node_modules/@types/node/index.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/canary.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/experimental.d.ts","../../../node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/index.d.ts","../../../node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/canary.d.ts","../../../node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/experimental.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/fallback.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/webpack/webpack.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/entry-constants.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/constants.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/bundler.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/config.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/load-custom-routes.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/image-config.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/body-streams.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/search-params.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/segment-cache/vary-params-decoding.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/vary-params.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/params.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-kind.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/route-definition.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matches/route-match.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/app-router-headers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/cache-control.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/app-router-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/cache-handlers/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/use-cache/use-cache-wrapper.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/resume-data-cache/cache-store.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/resume-data-cache/resume-data-cache.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/constants.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/render-result.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/response-cache/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/response-cache/index.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/jsx-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/userspace/pages/pages-dev-overlay-setup.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/static-paths/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/app-page-route-definition.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/adapter/setup-node-env.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/instrumentation/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/setup-exception-listeners.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/worker.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/experimental/ppr.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/page-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/app/app-segment-config.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/pages/pages-segment-config.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/analysis/get-page-static-info.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/require-hook.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-polyfill-crypto.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-baseline.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/error-inspect.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/console-file.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/console-exit.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/console-dim.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/unhandled-rejection.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/random.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/date.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/web-crypto.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/node-crypto.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/fast-set-immediate.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/page-extensions-type.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/module.compiled.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/app-route-route-definition.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/i18n-provider.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/next-url.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/cookies.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/request.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/deep-readonly.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/incremental-cache/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/locale-route-definition.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/pages-route-definition.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/mitt.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/with-router.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/router.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/route-loader.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/page-loader.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/bloom-filter.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/router.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/readonly-url-search-params.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/flight-data-helpers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/cache-key.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/segment-cache/segment-value-encoding.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/scheduler.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/cache-map.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/vary-path.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/cache.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/router-reducer/ppr-navigations.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/navigation.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/vendored/contexts/entrypoints.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/module.compiled.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/templates/pages.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/module.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/render.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/pages-api-route-definition.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matches/pages-api-route-match.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matchers/route-matcher.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matcher-providers/route-matcher-provider.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matcher-managers/route-matcher-manager.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/normalizer.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/locale-route-normalizer.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/pathname-normalizer.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/suffix.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/rsc.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/next-data.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/builtin-request-context.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/segment-prefix-rsc.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/builtin/_error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/load-default-error-components.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/after.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/after-context.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/use-cache/cache-life.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-async-storage-instance.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/lazy-result.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/create-error-handler.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/action-revalidation-kind.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-async-storage.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/async-storage/work-store.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/http.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/hooks-server-context.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-route/shared-modules.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/redirect-status-code.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/redirect-error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/cache-signal.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/boundary-tracking.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/instant-validation-error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/parse-relative-url.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/instant-samples.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/dynamic-rendering.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-unit-async-storage-instance.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/implicit-tags.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/staged-rendering.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-unit-async-storage.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/templates/app-route.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/action-async-storage-instance.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/action-async-storage.external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-route/module.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-route/module.compiled.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/app/app-segments.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/get-supported-browsers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/utils.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/rendering-mode.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/build-prefetch-segment-data-route.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/cpu-profile.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/result.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/helpers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/export/routes/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/export/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/export/worker.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/worker.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/coalesced-function.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/trace.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/shared.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/load-jsconfig.d.ts","../../../node_modules/.pnpm/@next+env@16.2.1/node_modules/@next/env/dist/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/telemetry-plugin/use-cache-tracker-utils.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/telemetry-plugin/telemetry-plugin.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/telemetry/storage.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/build-context.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack-config.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/swc/generated-native.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/define-env.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/swc/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/swc/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/parse-version-info.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/shared/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/dev-indicator-server-state.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/cache-indicator.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/parse-stack.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/server/shared.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/shared/stack-frame.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/utils/get-error-by-type.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/container/runtime-error/render-error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/shared.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/debug-channel.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/hot-reloader-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/response.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/middleware/middleware-config.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-http/node.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/async-callback-set.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts","../../../node_modules/.pnpm/sharp@0.34.5/node_modules/sharp/lib/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/image-optimizer.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/next-server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/lru-cache.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/dev-bundler-service.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/static-paths-worker.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/next-dev-server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/next.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/render-server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/path-match.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/filesystem.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/router-server-context.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/route-module.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/load-components.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/adapter.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/loaders/next-app-loader/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/app-dir-module.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/app-render.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/vendored/contexts/entrypoints.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/error-boundary.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/layout-router.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/render-from-template-context.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/client-page.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/client-segment.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/http-access-fallback/error-boundary.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/extra-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/metadata-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/manifest-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/twitter-types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/resolvers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/icons.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/resolve-metadata.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/metadata.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/framework/boundary-components.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/rsc/preloads.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/rsc/postpone.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/rsc/taint.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/collect-segment-data.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/instant-validation.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/userspace/app/segment-explorer-node.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/entry-base.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/templates/app-page.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/helpers/prerender-manifest-matcher.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/jsx-dev-runtime.d.ts","../../../node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/compiler-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/vendored/rsc/entrypoints.d.ts","../../../node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/client.d.ts","../../../node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/static.d.ts","../../../node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/vendored/ssr/entrypoints.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/module.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/fallback-params.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/image-response.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/user-agent.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/url-pattern.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/connection.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/exports/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request-meta.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/cli/next-test.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/size-limit.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/config-shared.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-http/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/api-utils/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/adapter/build-complete.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/utils.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/pages/_app.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/app.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/revalidate.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/use-cache/cache-tag.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/cache.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/pages/_document.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/document.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/dynamic.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dynamic.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/pages/_error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/catch-error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/api/error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/head.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/head.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/cookies.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/headers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/draft-mode.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/headers.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/get-img-props.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/image-component.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/image-external.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/image.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/link.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/link.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unrecognized-action-error.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/redirect.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/not-found.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/forbidden.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unauthorized.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unstable-rethrow.server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unstable-rethrow.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/navigation.react-server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/navigation.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/navigation.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/router.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/script.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/script.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@vercel/og/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/server.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/types/global.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/types/compiled.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/image-types/global.d.ts","./.next/dev/types/routes.d.ts","./next-env.d.ts","./proxy.ts","./app/(den)/_components/ui/dropdown-styles.ts","./app/(den)/_lib/consts.ts","./app/(den)/_lib/client-route.ts","./app/(den)/_lib/den-flow.ts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/json-schema.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/standard-schema.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/registries.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/to-json-schema.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/util.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/versions.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/schemas.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/checks.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/errors.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/core.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/parse.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/regexes.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/ar.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/az.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/be.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/bg.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/ca.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/cs.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/da.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/de.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/en.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/eo.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/es.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/fa.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/fi.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/fr.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/fr-ca.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/he.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/hu.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/hy.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/id.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/is.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/it.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/ja.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/ka.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/kh.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/km.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/ko.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/lt.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/mk.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/ms.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/nl.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/no.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/ota.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/ps.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/pl.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/pt.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/ru.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/sl.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/sv.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/ta.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/th.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/tr.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/ua.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/uk.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/ur.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/uz.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/vi.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/zh-cn.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/zh-tw.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/yo.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/locales/index.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/doc.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/api.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/json-schema-processors.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/json-schema-generator.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/index.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/errors.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/parse.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/schemas.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/checks.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/compat.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/from-json-schema.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/iso.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/coerce.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/external.d.cts","../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/index.d.cts","../../../packages/types/src/den/desktop-app-restrictions.ts","./app/(den)/_lib/den-org.ts","./app/(den)/_lib/feedback.ts","./app/(den)/o/[orgslug]/dashboard/_components/shared-setup-data.ts","./app/api/_lib/upstream-proxy.ts","./app/api/auth/[...path]/route.ts","./app/api/den/[...path]/route.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@next/font/dist/types.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@next/font/dist/google/index.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/font/google/index.d.ts","./app/layout.tsx","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@vercel/og/index.node.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/og/image-response.d.ts","../../../node_modules/.pnpm/next@16.2.1_@opentelemetry+api@1.9.0_@playwright+test@1.58.2_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/og.d.ts","./app/opengraph-image.tsx","./app/(den)/_components/den-shell.tsx","./app/(den)/_providers/den-flow-provider.tsx","./app/(den)/layout.tsx","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shader-mount.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shader-sizing.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/types.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/mesh-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/smoke-ring.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/neuro-noise.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/dot-orbit.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/dot-grid.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/simplex-noise.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/metaballs.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/perlin-noise.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/voronoi.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/waves.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/warp.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/god-rays.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/spiral.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/swirl.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/dithering.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/grain-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/pulsing-border.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/color-panels.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/static-mesh-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/static-radial-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/paper-texture.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/water.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/fluted-glass.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/image-dithering.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/heatmap.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/liquid-metal.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/halftone-dots.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/shaders/halftone-cmyk.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/get-shader-color-from-string.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/get-shader-noise-texture.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/empty-pixel.d.ts","../../../node_modules/.pnpm/@paper-design+shaders@0.0.72/node_modules/@paper-design/shaders/dist/index.d.ts","../../../packages/ui/src/common/paper.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shader-mount.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/mesh-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/smoke-ring.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/neuro-noise.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/dot-orbit.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/dot-grid.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/simplex-noise.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/metaballs.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/waves.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/perlin-noise.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/voronoi.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/warp.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/god-rays.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/spiral.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/swirl.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/dithering.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/grain-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/pulsing-border.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/color-panels.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/static-mesh-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/static-radial-gradient.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/paper-texture.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/fluted-glass.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/water.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/image-dithering.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/heatmap.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/liquid-metal.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/halftone-dots.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/shaders/halftone-cmyk.d.ts","../../../node_modules/.pnpm/@paper-design+shaders-react@0.0.72_@types+react@19.2.14_react@19.2.4/node_modules/@paper-design/shaders-react/dist/index.d.ts","../../../packages/ui/src/react/paper/grain-gradient.tsx","../../../packages/ui/src/react/paper/mesh-gradient.tsx","../../../packages/ui/src/react/index.ts","../../../node_modules/.pnpm/lucide-react@0.577.0_react@19.2.4/node_modules/lucide-react/dist/lucide-react.d.ts","./app/(den)/_components/auth-panel.tsx","./app/(den)/_components/auth-screen.tsx","./app/(den)/page.tsx","./app/(den)/_components/checkout-screen.tsx","./app/(den)/_components/dashboard-redirect-screen.tsx","./app/(den)/_components/dashboard-screen.tsx","./app/(den)/_components/join-org-screen.tsx","./app/(den)/_components/ui/button.tsx","./app/(den)/_components/org-limit-dialog.tsx","./app/(den)/_components/organization-screen.tsx","./app/(den)/_components/ui/card.tsx","./app/(den)/_components/ui/combobox.tsx","./app/(den)/_components/ui/dashboard-page-template.tsx","./app/(den)/_components/ui/input.tsx","./app/(den)/_components/ui/select.tsx","./app/(den)/_components/ui/selectable-row.tsx","./app/(den)/_components/ui/tabs.tsx","./app/(den)/_components/ui/textarea.tsx","./app/(den)/checkout/page.tsx","./app/(den)/o/[orgslug]/dashboard/_providers/org-dashboard-provider.tsx","./app/(den)/o/[orgslug]/dashboard/_components/org-dashboard-shell.tsx","../../../node_modules/.pnpm/@tanstack+query-core@5.96.2/node_modules/@tanstack/query-core/build/modern/_tsup-dts-rollup.d.ts","../../../node_modules/.pnpm/@tanstack+query-core@5.96.2/node_modules/@tanstack/query-core/build/modern/index.d.ts","../../../node_modules/.pnpm/@tanstack+react-query@5.96.2_react@19.2.4/node_modules/@tanstack/react-query/build/modern/_tsup-dts-rollup.d.ts","../../../node_modules/.pnpm/@tanstack+react-query@5.96.2_react@19.2.4/node_modules/@tanstack/react-query/build/modern/index.d.ts","./app/(den)/o/[orgslug]/dashboard/_providers/query-client-provider.tsx","./app/(den)/dashboard/layout.tsx","./app/(den)/o/[orgslug]/dashboard/_components/dashboard-overview-screen.tsx","./app/(den)/o/[orgslug]/dashboard/page.tsx","./app/(den)/dashboard/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/api-keys-screen.tsx","./app/(den)/o/[orgslug]/dashboard/api-keys/page.tsx","./app/(den)/dashboard/api-keys/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/background-agents-screen.tsx","./app/(den)/o/[orgslug]/dashboard/background-agents/page.tsx","./app/(den)/dashboard/background-agents/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/billing-dashboard-screen.tsx","./app/(den)/o/[orgslug]/dashboard/billing/page.tsx","./app/(den)/dashboard/billing/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/llm-provider-data.tsx","./app/(den)/o/[orgslug]/dashboard/_components/llm-providers-screen.tsx","./app/(den)/o/[orgslug]/dashboard/custom-llm-providers/page.tsx","./app/(den)/dashboard/custom-llm-providers/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/llm-provider-detail-screen.tsx","./app/(den)/o/[orgslug]/dashboard/custom-llm-providers/[llmproviderid]/page.tsx","./app/(den)/dashboard/custom-llm-providers/[llmproviderid]/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/llm-provider-editor-screen.tsx","./app/(den)/o/[orgslug]/dashboard/custom-llm-providers/[llmproviderid]/edit/page.tsx","./app/(den)/dashboard/custom-llm-providers/[llmproviderid]/edit/page.tsx","./app/(den)/o/[orgslug]/dashboard/custom-llm-providers/new/page.tsx","./app/(den)/dashboard/custom-llm-providers/new/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/integration-data.tsx","./app/(den)/o/[orgslug]/dashboard/_components/integration-connect-dialog.tsx","./app/(den)/o/[orgslug]/dashboard/_components/integrations-screen.tsx","./app/(den)/o/[orgslug]/dashboard/integrations/page.tsx","./app/(den)/dashboard/integrations/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/github-integration-screen.tsx","./app/(den)/o/[orgslug]/dashboard/integrations/github/page.tsx","./app/(den)/dashboard/integrations/github/page.tsx","./app/(den)/o/[orgslug]/dashboard/manage-members/page.tsx","./app/(den)/dashboard/manage-members/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/marketplace-data.tsx","./app/(den)/o/[orgslug]/dashboard/_components/marketplaces-screen.tsx","./app/(den)/o/[orgslug]/dashboard/marketplaces/page.tsx","./app/(den)/dashboard/marketplaces/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/marketplace-detail-screen.tsx","./app/(den)/o/[orgslug]/dashboard/marketplaces/[marketplaceid]/page.tsx","./app/(den)/dashboard/marketplaces/[marketplaceid]/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/manage-members-screen.tsx","./app/(den)/o/[orgslug]/dashboard/members/page.tsx","./app/(den)/dashboard/members/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/org-settings-screen.tsx","./app/(den)/o/[orgslug]/dashboard/org-settings/page.tsx","./app/(den)/dashboard/org-settings/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/plugin-data.tsx","./app/(den)/o/[orgslug]/dashboard/_components/plugins-screen.tsx","./app/(den)/o/[orgslug]/dashboard/plugins/page.tsx","./app/(den)/dashboard/plugins/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/plugin-detail-screen.tsx","./app/(den)/o/[orgslug]/dashboard/plugins/[pluginid]/page.tsx","./app/(den)/dashboard/plugins/[pluginid]/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/templates-dashboard-screen.tsx","./app/(den)/o/[orgslug]/dashboard/shared-setups/page.tsx","./app/(den)/dashboard/shared-setups/page.tsx","../../../node_modules/.pnpm/@types+luxon@3.7.1/node_modules/@types/luxon/src/zone.d.ts","../../../node_modules/.pnpm/@types+luxon@3.7.1/node_modules/@types/luxon/src/settings.d.ts","../../../node_modules/.pnpm/@types+luxon@3.7.1/node_modules/@types/luxon/src/_util.d.ts","../../../node_modules/.pnpm/@types+luxon@3.7.1/node_modules/@types/luxon/src/misc.d.ts","../../../node_modules/.pnpm/@types+luxon@3.7.1/node_modules/@types/luxon/src/duration.d.ts","../../../node_modules/.pnpm/@types+luxon@3.7.1/node_modules/@types/luxon/src/interval.d.ts","../../../node_modules/.pnpm/@types+luxon@3.7.1/node_modules/@types/luxon/src/datetime.d.ts","../../../node_modules/.pnpm/@types+luxon@3.7.1/node_modules/@types/luxon/src/info.d.ts","../../../node_modules/.pnpm/@types+luxon@3.7.1/node_modules/@types/luxon/src/luxon.d.ts","../../../node_modules/.pnpm/@types+luxon@3.7.1/node_modules/@types/luxon/index.d.ts","../../../node_modules/.pnpm/@types+luxon@3.7.1/node_modules/@types/luxon/index.d.mts","../../../node_modules/.pnpm/typeid-js@1.2.0/node_modules/typeid-js/dist/index.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/types.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/max.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/nil.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/parse.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/stringify.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/v1.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/v1tov6.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/v35.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/v3.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/v4.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/v5.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/v6.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/v6tov1.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/v7.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/validate.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/version.d.ts","../../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/index.d.ts","../../packages/utils/src/typeid.ts","../../packages/utils/src/skill-markdown.ts","../../packages/utils/src/index.ts","./app/(den)/o/[orgslug]/dashboard/_components/skill-hub-data.tsx","./app/(den)/o/[orgslug]/dashboard/_components/skill-hubs-screen.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/page.tsx","./app/(den)/dashboard/skill-hubs/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/skill-hub-detail-screen.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/[skillhubid]/page.tsx","./app/(den)/dashboard/skill-hubs/[skillhubid]/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/skill-hub-editor-screen.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/[skillhubid]/edit/page.tsx","./app/(den)/dashboard/skill-hubs/[skillhubid]/edit/page.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/new/page.tsx","./app/(den)/dashboard/skill-hubs/new/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/skill-detail-screen.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/skills/[skillid]/page.tsx","./app/(den)/dashboard/skill-hubs/skills/[skillid]/page.tsx","./app/(den)/o/[orgslug]/dashboard/_components/skill-editor-screen.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/skills/[skillid]/edit/page.tsx","./app/(den)/dashboard/skill-hubs/skills/[skillid]/edit/page.tsx","./app/(den)/o/[orgslug]/dashboard/skill-hubs/skills/new/page.tsx","./app/(den)/dashboard/skill-hubs/skills/new/page.tsx","./app/(den)/join-org/page.tsx","./app/(den)/o/[orgslug]/dashboard/layout.tsx","./app/(den)/organization/page.tsx","./components/den-admin-panel.tsx","./app/admin/page.tsx","./components/den-marketing-rail.tsx","./.next/dev/types/cache-life.d.ts","./.next/dev/types/validator.ts"],"fileInfos":[{"version":"824cb491a40f7e8fdeb56f1df5edf91b23f3e3ee6b4cde84d4a99be32338faee","affectsGlobalScope":true},"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","9a68c0c07ae2fa71b44384a839b7b8d81662a236d4b9ac30916718f7510b1b2d","5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","5514e54f17d6d74ecefedc73c504eadffdeda79c7ea205cf9febead32d45c4bc",{"version":"87d693a4920d794a73384b3c779cadcb8548ac6945aa7a925832fe2418c9527a","affectsGlobalScope":true},{"version":"76f838d5d49b65de83bc345c04aa54c62a3cfdb72a477dc0c0fce89a30596c30","affectsGlobalScope":true},{"version":"138fb588d26538783b78d1e3b2c2cc12d55840b97bf5e08bca7f7a174fbe2f17","affectsGlobalScope":true},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true},{"version":"4443e68b35f3332f753eacc66a04ac1d2053b8b035a0e0ac1d455392b5e243b3","affectsGlobalScope":true},{"version":"bc47685641087c015972a3f072480889f0d6c65515f12bd85222f49a98952ed7","affectsGlobalScope":true},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true},{"version":"93495ff27b8746f55d19fcbcdbaccc99fd95f19d057aed1bd2c0cafe1335fbf0","affectsGlobalScope":true},{"version":"6fc23bb8c3965964be8c597310a2878b53a0306edb71d4b5a4dfe760186bcc01","affectsGlobalScope":true},{"version":"ea011c76963fb15ef1cdd7ce6a6808b46322c527de2077b6cfdf23ae6f5f9ec7","affectsGlobalScope":true},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true},{"version":"bb42a7797d996412ecdc5b2787720de477103a0b2e53058569069a0e2bae6c7e","affectsGlobalScope":true},{"version":"4738f2420687fd85629c9efb470793bb753709c2379e5f85bc1815d875ceadcd","affectsGlobalScope":true},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true},{"version":"9fc46429fbe091ac5ad2608c657201eb68b6f1b8341bd6d670047d32ed0a88fa","affectsGlobalScope":true},{"version":"61c37c1de663cf4171e1192466e52c7a382afa58da01b1dc75058f032ddf0839","affectsGlobalScope":true},{"version":"b541a838a13f9234aba650a825393ffc2292dc0fc87681a5d81ef0c96d281e7a","affectsGlobalScope":true},{"version":"b20fe0eca9a4e405f1a5ae24a2b3290b37cf7f21eba6cbe4fc3fab979237d4f3","affectsGlobalScope":true},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true},{"version":"49ed889be54031e1044af0ad2c603d627b8bda8b50c1a68435fe85583901d072","affectsGlobalScope":true},{"version":"e93d098658ce4f0c8a0779e6cab91d0259efb88a318137f686ad76f8410ca270","affectsGlobalScope":true},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true},{"version":"bf14a426dbbf1022d11bd08d6b8e709a2e9d246f0c6c1032f3b2edb9a902adbe","affectsGlobalScope":true},{"version":"5e07ed3809d48205d5b985642a59f2eba47c402374a7cf8006b686f79efadcbd","affectsGlobalScope":true},{"version":"2b72d528b2e2fe3c57889ca7baef5e13a56c957b946906d03767c642f386bbc3","affectsGlobalScope":true},{"version":"8073890e29d2f46fdbc19b8d6d2eb9ea58db9a2052f8640af20baff9afbc8640","affectsGlobalScope":true},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true},{"version":"51e547984877a62227042850456de71a5c45e7fe86b7c975c6e68896c86fa23b","affectsGlobalScope":true},{"version":"956d27abdea9652e8368ce029bb1e0b9174e9678a273529f426df4b3d90abd60","affectsGlobalScope":true},{"version":"4fa6ed14e98aa80b91f61b9805c653ee82af3502dc21c9da5268d3857772ca05","affectsGlobalScope":true},{"version":"e6633e05da3ff36e6da2ec170d0d03ccf33de50ca4dc6f5aeecb572cedd162fb","affectsGlobalScope":true},{"version":"d8670852241d4c6e03f2b89d67497a4bbefe29ecaa5a444e2c11a9b05e6fccc6","affectsGlobalScope":true},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true},{"version":"caccc56c72713969e1cfe5c3d44e5bab151544d9d2b373d7dbe5a1e4166652be","affectsGlobalScope":true},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true},{"version":"50d53ccd31f6667aff66e3d62adf948879a3a16f05d89882d1188084ee415bbc","affectsGlobalScope":true},{"version":"13f6e6380c78e15e140243dc4be2fa546c287c6d61f4729bc2dd7cf449605471","affectsGlobalScope":true},{"version":"33358442698bb565130f52ba79bfd3d4d484ac85fe33f3cb1759c54d18201393","affectsGlobalScope":true},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true},{"version":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","affectsGlobalScope":true},"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","dc0a7f107690ee5cd8afc8dbf05c4df78085471ce16bdd9881642ec738bc81fe","acd8fd5090ac73902278889c38336ff3f48af6ba03aa665eb34a75e7ba1dccc4","d6258883868fb2680d2ca96bc8b1352cab69874581493e6d52680c5ffecdb6cc","1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153","f258e3960f324a956fc76a3d3d9e964fff2244ff5859dcc6ce5951e5413ca826","643f7232d07bf75e15bd8f658f664d6183a0efaca5eb84b48201c7671a266979","21da358700a3893281ce0c517a7a30cbd46be020d9f0c3f2834d0a8ad1f5fc75","d78c698fa755ef94e3af591883bfee3a330ffec36392e00aaacdff3541cf5382","7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","ef18cbf1d8374576e3db03ff33c2c7499845972eb0c4adf87392949709c5e160","5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","7180c03fd3cb6e22f911ce9ba0f8a7008b1a6ddbe88ccf16a9c8140ef9ac1686","25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","54cb85a47d760da1c13c00add10d26b5118280d44d58e6908d8e89abbd9d7725","3e4825171442666d31c845aeb47fcd34b62e14041bb353ae2b874285d78482aa","c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","a967bfe3ad4e62243eb604bf956101e4c740f5921277c60debaf325c1320bf88","e9775e97ac4877aebf963a0289c81abe76d1ec9a2a7778dbe637e5151f25c5f3","471e1da5a78350bc55ef8cef24eb3aca6174143c281b8b214ca2beda51f5e04a","cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","db3435f3525cd785bf21ec6769bf8da7e8a776be1a99e2e7efb5f244a2ef5fee","c3b170c45fc031db31f782e612adf7314b167e60439d304b49e704010e7bafe5","40383ebef22b943d503c6ce2cb2e060282936b952a01bea5f9f493d5fb487cc7","4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","3a84b7cb891141824bd00ef8a50b6a44596aded4075da937f180c90e362fe5f6","13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","33203609eba548914dc83ddf6cadbc0bcb6e8ef89f6d648ca0908ae887f9fcc5","0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","e53a3c2a9f624d90f24bf4588aacd223e7bec1b9d0d479b68d2f4a9e6011147f","339dc5265ee5ed92e536a93a04c4ebbc2128f45eeec6ed29f379e0085283542c","9f0a92164925aa37d4a5d9dd3e0134cff8177208dba55fd2310cd74beea40ee2","8bfdb79bf1a9d435ec48d9372dc93291161f152c0865b81fc0b2694aedb4578d","2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","d32275be3546f252e3ad33976caf8c5e842c09cb87d468cb40d5f4cf092d1acc","4a0c3504813a3289f7fb1115db13967c8e004aa8e4f8a9021b95285502221bd1",{"version":"a14ed46fa3f5ffc7a8336b497cd07b45c2084213aaca933a22443fcb2eef0d07","affectsGlobalScope":true},"6968359c8dbc693224fd1ea0b1f96b135f14d8eee3d6e23296d68c3a9da3ea00",{"version":"79d75a353f29d9f7fc63e879ccebe213baaaea26676fb3e47cc96cf221b27b4f","affectsGlobalScope":true},"dfdc7699360a0d512d7e31c69f75cb6a419cf415c98673e24499793170db5d6b","dcf46daa1e04481b1c2f360c7a77bf019885bd70353a92aa698b9c22b7fe3d6b",{"version":"033350619c2cfcbeab2a483f4b221e0866e17cc4ac514240d285d35c35eecf7c","affectsGlobalScope":true},"a40826e8476694e90da94aa008283a7de50d1dafd37beada623863f1901cb7fb",{"version":"b197fb2d5fa71cebc66e5d10e15c7d02f15fcd3194fbdaafeb964262582f2a82","affectsGlobalScope":true},"1a7f593d587f49ca97710c021c453ab1b95db5e39e58567f4af644f97a5fb0e0","dd4705d1d78af32c407e93e5df009962bed324599d6a5b2a9d661ba44dd99e43","3a02975d4a7034567425e529a0770f7f895ed605d2b576f7831668b7beea9fea","7525257b4aa35efc7a1bbc00f205a9a96c4e4ab791da90db41b77938c4e0c18e","cf87b355c4f531e98a9bba2b0e62d413b49b58b26bf8a9865e60a22d3af1fcd3",{"version":"ee1ee365d88c4c6c0c0a5a5701d66ebc27ccd0bcfcfaa482c6e2e7fe7b98edf7","affectsGlobalScope":true},{"version":"1a08fe5930473dcae34b831b3440cd51ff2c682cf03bd70e28812751dd1644dd","affectsGlobalScope":true},"6f3e00b838cf23f7837ffca5da88ae25f0a81742af9ccadce5cb85ac72050929","304f66274aa8119e8d65a49b1cff84cbf803def6afe1b2cc987386e9a9890e22","cbcb993f1fa22b7769074eb09c1307756e6380659a2990d6f50cfd8943bd8333","55a93997681797056da069cfac92878bff4d2a35e61c1c16280ee0cba38702f2","ea25afcaf96904668f7eebc1b834f89b5b5e5acafd430c29990028a1aaa0bcbe","df981b2ce32930887db27eeae29e48b9b841e4ba0bbba1162ebed04c778cd7e1",{"version":"ea455cc68871b049bcecd9f56d4cf27b852d6dafd5e3b54468ca87cc11604e4d","affectsGlobalScope":true},"3be96458790a77cb357856dab45d1cc8383ac63ba4e085f620b202fb62a6e1db","02d85d03fd4a4f63cba0b133f0e0192368dfeb4338bd33f87788a4f6302de873","bb3a0ce56babb71d7c208ed848b4aafe545e7a7e06304fc0c8cfe3ad328cab7a",{"version":"43bb766c0dc5f1150021f161aa6831eb2cc75dab278172408515cb6e47f697a9","affectsGlobalScope":true},{"version":"8bcf09ba67bd0ec12a9f1efc1e58e1ba2cb1ff78920ce6cf67ebfe6003c54b82","affectsGlobalScope":true},"13ce7518e39051544dd1e3124c185665adda05a5021676f2606c2c74ad2c964f","4ac5899be65d5e2cabe3aaf3dfc2cf7641e54dde23db198d9f683dfabe228145","124dacf89c97915479ed6ad81b09ba42fd40962d069c0642fed42e2d9719f2ba","139ad1dc93a503da85b7a0d5f615bddbae61ad796bc68fedd049150db67a1e26","ad06959073c066bb9543ef9c1dee37fc3140d2ecaae42b97bf4e27f2f03d6511","9eece5e586312581ccd106d4853e861aaaa1a39f8e3ea672b8c3847eedd12f6e","782abaae13e868dee4ea9c16d44499af251d112fba535c558d10ff5279b34678","269929a24b2816343a178008ac9ae9248304d92a8ba8e233055e0ed6dbe6ef71","93452d394fdd1dc551ec62f5042366f011a00d342d36d50793b3529bfc9bd633","3c1f19c7abcda6b3a4cf9438a15c7307a080bd3b51dfd56b198d9f86baf19447","98e7b7220dad76c509d584c9b7b1ec4dcbd7df5e3a2d37d28c54f74461ec0975",{"version":"c61b5fad633f25bb0de0f95612191c1df9a6671cd66f451507b5223bff41b50d","affectsGlobalScope":true},{"version":"d21966ba3284ade60cb94eb2c533ab5b2af7fd0b4b28462043f6ebcb8400bd21","affectsGlobalScope":true},"98e00f3613402504bc2a2c9a621800ab48e0a463d1eed062208a4ae98ad8f84c","b8e9e44ce8eba70af569523ff31d669cc239a93f548899a259f3224392a75e6c","005d1caa2a5d9bc096f75b598d0fd184bc848dd2665b050a17a17d5dc1ef652d","619735e4e221e1bf137ae3efa5330beee4a06039dccb876c822f9d8913a392da",{"version":"3560d0809b0677d77e39d0459ae6129c0e045cb3d43d1f345df06cf7ab7d6029","affectsGlobalScope":true},{"version":"5ab086d9457abbc69cca270e5475073f2e8eb35b2fb810c516400de7b7c7d575","affectsGlobalScope":true},"2a2fd53f2d963624b596fb720b390cbfe8d744e92cb55b48a8090a8fd42a302d","1f01c8fde66abc4ff6aed1db050a928b3bcb6f29bc89630a0d748a0649e14074","60223439b7ee9b26a08d527cacc8b34ea6c6741589ef4949f4669c9aeb97978e",{"version":"48fffe7824c2e8cf8c812f528c33d4c4f502767582083df35920a7f56fe794b3","affectsGlobalScope":true},"561bf7d1d3163db272980f9167b4b98f6a9ee8698c5955e9d9584e84088aad51",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"a42be67ed1ddaec743582f41fc219db96a1b69719fccac6d1464321178d610fc","2beff543f6e9a9701df88daeee3cdd70a34b4a1c11cb4c734472195a5cb2af54","2e07abf27aa06353d46f4448c0bbac73431f6065eef7113128a5cd804d0c384d","be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","42bc0e1a903408137c3df2b06dfd7e402cdab5bbfa5fcfb871b22ebfdb30bd0b","9894dafe342b976d251aac58e616ac6df8db91fb9d98934ff9dd103e9e82578f","413df52d4ea14472c2fa5bee62f7a40abd1eb49be0b9722ee01ee4e52e63beb2","db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96","446a50749b24d14deac6f8843e057a6355dd6437d1fac4f9e5ce4a5071f34bff","182e9fcbe08ac7c012e0a6e2b5798b4352470be29a64fdc114d23c2bab7d5106","2f4e6b4d39426a1b85ecf4bdeb9dddbf4d9b3397d95d8555d46f925c9519ec7d","78a2869ad0cbf3f9045dda08c0d4562b7e1b2bfe07b19e0db072f5c3c56e9584","89d5d28d4f57e000b836ac273079be1b75710e28ce14750d081fb420d37e2ca5","fd4e24ccff3966390600d7f5d6aa1fed5a512e92ada735ea5fbc933d313ad3d3","b7cddfe1aa6b86b5fad3c9ccb30d05b3ccb165aebbf112f48d2d8a5f69dd98b1","a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f","ad0d1d75d129b1c80f911be438d6b61bfa8703930a8ff2be2f0e1f8a91841c64","bd2c7ada3dee03653d3f601011d30072194bc3970cd93208f9588fbdc0c69347","e480da45d32313e7174b265674da504f075f59ef326852f0c5a5d863b438ae85","ad54850f61fcf5d014e11be80d2f46fea9265cfa7e77456da876f7833ef81769","6f7c9e8bd2b5b6a080b07080065f94900bd3c7e5ebbd3047bc33fcce2fab1dd8","3e7efde639c6a6c3edb9847b3f61e308bf7a69685b92f665048c45132f51c218","df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5","8a0e762ceb20c7e72504feef83d709468a70af4abccb304f32d6b9bac1129b2c","da5950ee2a90721df6f3fba45f5d05308f7e4c35835392215dd2cd404505e2de","ce75b1aebb33d510ff28af960a9221410a3eaf7f18fc5f21f9404075fba77256","f42d5fed19610d485c646a0c430e768115567d078c7fc855c57b0c578b3d6cd3","ee8df1cb8d0faaca4013a1b442e99130769ce06f438d18d510fed95890067563","d5630f2ad9b4541e5ce891648121022f9412ecdca1820baa1f0104f70fd7eff7","4d15375ab13497104bc8fe56fdef2b5fd6853f29255737d23a33fa306ff7fd69","2cd3fc1d0d6a1e85baffd2d4f50f5efb192b5446eef567e97c94765402f0aad4","e4cbf2f1e89ecccaddd2c045e600ae41b732295953fb06247c7dcbc2d281ed30","27bbdb7509a5bb564020321fc5485764d0db3230a10d2336ae5ce2c1d401b0e7","8c1697d90c394a6fd955b98eae01238eff628e129b987a68aea10f898a48e7da","7580e62139cb2b44a0270c8d01abcbfcba2819a02514a527342447fa69b34ef1","42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3","f374cb24e93e7798c4d9e83ff872fa52d2cdb36306392b840a6ddf46cb925cb6","d10d63718e1646c2279e3b33831f82c60e31f622b2b7020f1196409ca4c09242","106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","148679c6d0f449210a96e7d2e562d589e56fcde87f843a92808b3ff103f1a774","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","02436d7e9ead85e09a2f8e27d5f47d9464bced31738dec138ca735390815c9f0","f8d5ff8eafd37499f2b6a98659dd9b45a321de186b8db6b6142faed0fea3de77","c86fe861cf1b4c46a0fb7d74dffe596cf679a2e5e8b1456881313170f092e3fa","a22dd55aa4d39906252000ab8e8a1b83b195eef7f4274eb51e457c1f11cf6580","540cc83ab772a2c6bc509fe1354f314825b5dba3669efdfbe4693ecd3048e34f","121b0696021ab885c570bbeb331be8ad82c6efe2f3b93a6e63874901bebc13e3","612d9da66bb046a9c1e2e8d026245ded881fc4b9f98cbfae714415d57ee0ae0b","32c2ad9494dad5d11b0564a619fee18f388db6c1e9e2cd3c360b3122549691eb","6c301d40aec56a74ec7bd7324e31a728dadf9bfba3e96def02938d3d973534ec","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","aa14cee20aa0db79f8df101fc027d929aec10feb5b8a8da3b9af3895d05b7ba2","493c700ac3bd317177b2eb913805c87fe60d4e8af4fb39c41f04ba81fae7e170","aeb554d876c6b8c818da2e118d8b11e1e559adbe6bf606cc9a611c1b6c09f670","acf5a2ac47b59ca07afa9abbd2b31d001bf7448b041927befae2ea5b1951d9f9","8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","d71291eff1e19d8762a908ba947e891af44749f3a2cbc5bd2ec4b72f72ea795f","c0480e03db4b816dff2682b347c95f2177699525c54e7e6f6aa8ded890b76be7","25a5f6fd3a2243c859eddc99ab5fba11d970af2fe7a5df9c32b7668f76f97b01","8d207e1f9d2c30d6f77dfa693f3827c3fbf0d89240297e10bdfe1041d433df68","b620391fe8060cf9bedc176a4d01366e6574d7a71e0ac0ab344a4e76576fcbb8","6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","2652448ac55a2010a1f71dd141f828b682298d39728f9871e1cdf8696ef443fd","d682336018141807fb602709e2d95a192828fcb8d5ba06dda3833a8ea98f69e3","6124e973eab8c52cabf3c07575204efc1784aca6b0a30c79eb85fe240a857efa","0d891735a21edc75df51f3eb995e18149e119d1ce22fd40db2b260c5960b914e","3b414b99a73171e1c4b7b7714e26b87d6c5cb03d200352da5342ab4088a54c85","4fbd3116e00ed3a6410499924b6403cc9367fdca303e34838129b328058ede40","9c82171d836c47486074e4ca8e059735bf97b205e70b196535b5efd40cbe1bc5","8c70ddc0c22d85e56011d49fddfaae3405eb53d47b59327b9dd589e82df672e7","2f9c89cbb29d362290531b48880a4024f258c6033aaeb7e59fbc62db26819650","a365c4d3bed3be4e4e20793c999c51f5cd7e6792322f14650949d827fbcd170f","c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5","65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9","9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801","de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d","c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5","1ee45496b5f8bdee6f7abc233355898e5bf9bd51255db65f5ff7ede617ca0027",{"version":"273782b8454e78f6a8b30d2cfbf6860499c930595095fcc1689637115f0eddda","affectsGlobalScope":true},{"version":"3fbdd025f9d4d820414417eeb4107ffa0078d454a033b506e22d3a23bc3d9c41","affectsGlobalScope":true},"dba114fb6a32b355a9cfc26ca2276834d72fe0e94cd2c3494005547025015369",{"version":"a8f8e6ab2fa07b45251f403548b78eaf2022f3c2254df3dc186cb2671fe4996d","affectsGlobalScope":true},"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e","f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b",{"version":"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa","affectsGlobalScope":true},"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6","9f9bb6755a8ce32d656ffa4763a8144aa4f274d6b69b59d7c32811031467216e","5c32bdfbd2d65e8fffbb9fbda04d7165e9181b08dad61154961852366deb7540","ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441","0c05e9842ec4f8b7bfebfd3ca61604bb8c914ba8da9b5337c4f25da427a005f2","faed7a5153215dbd6ebe76dfdcc0af0cfe760f7362bed43284be544308b114cf","7029e566b8df176f703fb59fd437a38670c7a0e02c58b2d66dfb5b2e2b2defdb","7f2aa4d4989a82530aaac3f72b3dceca90e9c25bee0b1a327e8a08a1262435ad","d96b39301d0ded3f1a27b47759676a33a02f6f5049bfcbde81e533fd10f50dcb","e9f147ecca73d9346a4c073432843c159ccbe50bdcb678a78f6da10eae2cecf4","de061f7d72bd65c06fc1419f841dfdcb29a8e22fe6fa527d1e6eb20b897d4de0","663beafc2446079574570cba86e9b15f986f908ddb1b01274509970126fee945","a3102887d5058bf4cb5b37fa6964c09e9527c42053b3b5c642b89878620748de","0aaaa1727edd29673d85c9b26d7ca4d54e5407a48586903c51b48b7f7d196f61","d35bca0b261bff02635758c48e8ab99c61c420d0dfabbcf467e847171d876b7d","3bc12c40d90c342ff88a3d876996c555ed5cbee5fe8c3308a240b321f401ee46","ba130768aae855a5477e9e148e5c879548e6e7ccbcc56fd1934c8a18ea5b7569","2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9","d38530db0601215d6d767f280e3a3c54b2a83b709e8d9001acb6f61c67e965fc","6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","b499af2054a037a162b3b72cd886f48bbf32a3502c865c6e29fac7d2ab3ce0b5","b83cb14474fa60c5f3ec660146b97d122f0735627f80d82dd03e8caa39b4388c","d87f90d2df7b638204d81d6c57e1f2a8cc9317c45ca331c691c375649aa9255c","7274fbffbd7c9589d8d0ffba68157237afd5cecff1e99881ea3399127e60572f","b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647","bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23","20865ac316b8893c1a0cc383ccfc1801443fbcc2a7255be166cf90d03fac88c9","c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23","461d0ad8ae5f2ff981778af912ba71b37a8426a33301daa00f21c6ccb27f8156","e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577","fcafff163ca5e66d3b87126e756e1b6dfa8c526aa9cd2a2b0a9da837d81bbd72","70246ad95ad8a22bdfe806cb5d383a26c0c6e58e7207ab9c431f1cb175aca657","f00f3aa5d64ff46e600648b55a79dcd1333458f7a10da2ed594d9f0a44b76d0b","772d8d5eb158b6c92412c03228bd9902ccb1457d7a705b8129814a5d1a6308fc","802e797bcab5663b2c9f63f51bdf67eff7c41bc64c0fd65e6da3e7941359e2f7","b01bd582a6e41457bc56e6f0f9de4cb17f33f5f3843a7cf8210ac9c18472fb0f","8b4327413e5af38cd8cb97c59f48c3c866015d5d642f28518e3a891c469f240e","4cceef18d7f088e797a463e90b7a9dad10c6bc667724b7686e3e740ae00122be","7ee86fbb3754388e004de0ef9e6505485ddfb3be7640783d6d015711c03d302d","cc1954b539604b1e562319119ac7e888172208b32ca873f9a357a92c826bd046","a67b87d0281c97dfc1197ef28dfe397fc2c865ccd41f7e32b53f647184cc7307","771ffb773f1ddd562492a6b9aaca648192ac3f056f0e1d997678ff97dbb6bf9b","43e96a3d5d1411ab40ba2f61d6a3192e58177bcf3b133a80ad2a16591611726d","232f70c0cf2b432f3a6e56a8dc3417103eb162292a9fd376d51a3a9ea5fbbf6f","bb8f2dbc03533abca2066ce4655c119bff353dd4514375beb93c08590c03e023",{"version":"706dd95827e7ebaabda91d5db2b755233e0952d98570e9c032b0f066a15c1177","affectsGlobalScope":true},"0b103e9abfe82d14c0ad06a55d9f91d6747154ef7cacc73cf27ecad2bfb3afcf","990b8fad2327b77e6920cc792af320e8867e68f02ce849b12c0a6ab9a1aebb09","5eb8cd1cb0c9143d74a8190b577c522720878c31aef67d866fcd29973f83e955","120599fd965257b1f4d0ff794bc696162832d9d8467224f4665f713a3119078b","43ba4f2fa8c698f5c304d21a3ef596741e8e85a810b7c1f9b692653791d8d97a","5433f33b0a20300cca35d2f229a7fc20b0e8477c44be2affeb21cb464af60c76","db036c56f79186da50af66511d37d9fe77fa6793381927292d17f81f787bb195","a6805fcafed712aea7759f8bc731014f9d22738c1d6ef9d43b8091d1d48346d5","c49469a5349b3cc1965710b5b0f98ed6c028686aa8450bcb3796728873eb923e","4a889f2c763edb4d55cb624257272ac10d04a1cad2ed2948b10ed4a7fda2a428","7bb79aa2fead87d9d56294ef71e056487e848d7b550c9a367523ee5416c44cfa","d88ea80a6447d7391f52352ec97e56b52ebec934a4a4af6e2464cfd8b39c3ba8","142617b3cdf902b69c6464c9fbd942b60ab3e733ca18c032b19e0f7e2adbefe8","0b603555f1881f87256ffd6344d3e3ed6d466c2e701eabf381f28be8c2125892","897e4f7662488e3ecc79e743bdd3b78f13bdb69a97851afa5b440c4211e32ea9","e2e1c6d3b2d93add5200bd7bc1a8cccb4e446836b2111ece45db8683a2c765de","251b03d5cd243854ce870d9a9a39f491faf69898c5d6b5eee28cc7649c57417b","27ff4196654e6373c9af16b6165120e2dd2169f9ad6abb5c935af5abd8c7938c","2c4de79f406d137390608e8c0a44fba2ff8e00bacfcae7c9d1781fef10e9440d","07ba23a10465791be5d22deaf5ef7de7658774ddff53721e5ea17fedea1bc721","dca8c645c5afeb03b1ecedbf16323f33e7d0afaa6256c8e047e6e38087a97f53","775f181bd4a533d6f8b5e55ec1d9f1624559720ae8a70e9432258da26b38d27c","796273b2edc72e78a04e86d7c58ae94d370ab93a0ddf40b1aa85a37a1c29ecd7","5df15a69187d737d6d8d066e189ae4f97e41f4d53712a46b2710ff9f8563ec9f","0659e6650e6c528420733abc2cdc36474ef14cc8d64ef3c6fee794d71c69cc2e","6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","622694a8522b46f6310c2a9b5d2530dde1e2854cb5829354e6d1ff8f371cf469","cd8ce8d68567f62dd580b3c3c37777ac3f5b81944c7417f5ea83030eab533385","e374d1eaa05b7dc38580062942ac8351ce79cbe11f6dbce4946a582a5680582d","9e2739b32f741859263fdba0244c194ca8e96da49b430377930b8f721d77c000","a9e6c0ff3f8186fccd05752cf75fc94e147c02645087ac6de5cc16403323d870","49af4b52f0d4d2304c5f2c6fe5fab3e153e0acc38830d0202821b877c097dd02","49c346823ba6d4b12278c12c977fb3a31c06b9ca719015978cb145eb86da1c61","bfac6e50eaa7e73bb66b7e052c38fdc8ccfc8dbde2777648642af33cf349f7f1","92f7c1a4da7fbfd67a2228d1687d5c2e1faa0ba865a94d3550a3941d7527a45d","f53b120213a9289d9a26f5af90c4c686dd71d91487a0aa5451a38366c70dc64b","e68b8e5a1df7c1be2bc105141456ecba70215806e1c28bfbc5c12bfce4be6e68","511c8f02329808d47d00b859c532ae9115590048b17325a946c74dac48428650","57d67b72e06059adc5e9454de26bbfe567d412b962a501d263c75c2db430f40e","b5f9e66625783eefcbe3d2da074b2e7ba2066d61ce3fc6ef4f22805ad946cab4","e37115962d284b9f7a37c2bdd2add50f88365dde41f5e0ff591ffc48a8ec7575","6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f","bb37588926aba35c9283fe8d46ebf4e79ffe976343105f5c6d45f282793352b2","f89488602bec98a142072fae7ea5ba99431a569ff580c64b7be39896474799d8","bbbc47961f39a57df103cf4ca3bb8f8732b4b6678a18225a0aa76d59c466956c","2e6114a7dd6feeef85b2c80120fdbfb59a5529c0dcc5bfa8447b6996c97a69f5","2ffb043dc5163458e473b7010859f86e01dc4edffcae0a93d885d028b426a546","c8f004e6036aa1c764ad4ec543cf89a5c1893a9535c80ef3f2b653e370de45e6","dd80b1e600d00f5c6a6ba23f455b84a7db121219e68f89f10552c54ba46e4dc9","b064c36f35de7387d71c599bfcf28875849a1dbc733e82bd26cae3d1cd060521","05c7280d72f3ed26f346cbe7cbbbb002fb7f15739197cbbee6ab3fd1a6cb9347","8de9fe97fa9e00ec00666fa77ab6e91b35d25af8ca75dabcb01e14ad3299b150","04b7b2e0832dfd3c31e81df3975e8d8fda28e7ff999b0aa2932608a8f6661d5c","ca2d34c6ed5cbd3070b8b6f32f42ae54adcc6499c1e4b99f0a5798b3f27cc653","9ec68995e66dd6b9dac834bf5ae85fde802714ea2e82151a5d1d53ef01b463ef","5c4d626b4902f2ef8a1cc146d761d276cef988016dc674e3b98fbad70e64bc9f","fdfaa0aad899524962e2955287b5b991ffe3be50f64e02eb60c933ca44644a94","53c972a0f9bc3a4ec70fff7314123ea8cfcf75b3703046f767d2dc1eea87b2fb","f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072","50256e9c31318487f3752b7ac12ff365c8949953e04568009c8705db802776fb","7d73b24e7bf31dfb8a931ca6c4245f6bb0814dfae17e4b60c9e194a631fe5f7b","d130c5f73768de51402351d5dc7d1b36eaec980ca697846e53156e4ea9911476","413586add0cfe7369b64979d4ec2ed56c3f771c0667fbde1bf1f10063ede0b08","06472528e998d152375ad3bd8ebcb69ff4694fd8d2effaf60a9d9f25a37a097a","7303b45138d2511035056a5901a1490ebdcbf055cbb1276f8629c5121cbe733e","27f874cd5327507eeff699a74567f60c1215b94509f4308633a7b01922471ed2","a401617604fa1f6ce437b81689563dfdc377069e4c58465dbd8d16069aede0a5","2c6cf04bc525caf6546e859e8ef10bfb9573837ec0bc5ec7b53a7b1b8ca72781","8695dec09ad439b0ceef3776ea68a232e381135b516878f0901ed2ea114fd0fe","304b44b1e97dd4c94697c3313df89a578dca4930a104454c99863f1784a54357","0a437ae178f999b46b6153d79095b60c42c996bc0458c04955f1c996dc68b971","74b2a5e5197bd0f2e0077a1ea7c07455bbea67b87b0869d9786d55104006784f","4a7baeb6325920044f66c0f8e5e6f1f52e06e6d87588d837bdf44feb6f35c664","87cc05fe13108f02e12da7e3efd8e360fef78d96a0c9e11408ea1b1b9fb3e03d","1abbf67c218d23c2ce76887caac2df6c7dab3d97ba2b65348432b876f510002a","1a82deef4c1d39f6882f28d275cad4c01f907b9b39be9cbc472fcf2cf051e05b","4b20fcf10a5413680e39f5666464859fc56b1003e7dfe2405ced82371ebd49b6","c06ef3b2569b1c1ad99fcd7fe5fba8d466e2619da5375dfa940a94e0feea899b","f7d628893c9fa52ba3ab01bcb5e79191636c4331ee5667ecc6373cbccff8ae12","1d879125d1ec570bf04bc1f362fdbe0cb538315c7ac4bcfcdf0c1e9670846aa6","8bd496cf710d4873d15e4891a5dbf945673e3321ca74cf75187e347fd5ed295e","a6dba407fc287f1e25454e75028c91bbc00675f2d1c4e8b3edcc36c08611a486","d663134457d8d669ae0df34eabd57028bddc04fc444c4bc04bc5215afc91e1f4","e91f7b1344577a02f051b9b471f33044fef8334a76dc9e1de003d17595a5219b","c0723195c85e19656d6b5b9fdb81d3f3403c1ae4679e722c6ea058c516b38d12","186eea74805194f04e41038fc5eca653788b9dedbab7c2d7d17e10139622dd92","71d9eb4c4e99456b78ae182fb20a5dfc20eb1667f091dbb9335b3c017dd1c783","cfa846a7b7847a1d973605fbb8c91f47f3a0f0643c18ac05c47077ebc72e71c7","1594da19968752a22b2ac48c2d0e60575700e745c577a8a4a676b841238ad5bb","e0cee12109e0a10a4c3d6769fcc7644b7c1ea7f52365bea51728f5af29f8a137","7d4254b4c6c67a29d5e7f65e67d72540480ac2cfb041ca484847f5ae70480b62","3536968defef8a75514f547ead5e2e9c1e984820290ec9b00c5fdfb6ef786535","d83773870080c30a230e322ce13a9c6f3398e8dacea4ea8a83e26370f3bac23e","dcfeaf98d66314fec29a9076c4290e45d0b196a65827becc19138e9c7b855f37","6849fe9210fe4946d5f085bfed36758f33dc6ae15a751338d178dd4daa017c46","888cda0fa66d7f74e985a3f7b1af1f64b8ff03eb3d5e80d051c3cbdeb7f32ab7","60681e13f3545be5e9477acb752b741eae6eaf4cc01658a25ec05bff8b82a2ef","ffae4e1e06aa848a1e4bcef162cd1c48e5909b26223515981310af9c036bdfc7","a57b1802794433adec9ff3fed12aa79d671faed86c49b09e02e1ac41b4f1d33a","34e16eb7c31768a11a08aebcfb3d70d7b8f0b016197e98d8419e566ceae6d6c8","f94ec1f7e4b709d26960306c9082a7a1b728a6e13089346aa48ba57c74cbf47e","9a11cb4033405e96c247cd5aa29790212aaffdd127869e8a5219103f0b389fd5","01479d9d5a5dda16d529b91811375187f61a06e74be294a35ecce77e0b9e8d6c","aff5213585cb72e94054dfe17250ff315f3569b3919d1ef1ad235f37c4ee894e","fb2ea35e1be6388d722d7725e2b49c697d34d9c890c3b96758faaeb86d35cef8","ce0df82a9ae6f914ba08409d4d883983cc08e6d59eb2df02d8e4d68309e7848b","1a4dc28334a926d90ba6a2d811ba0ff6c22775fcc13679521f034c124269fd40","f05315ff85714f0b87cc0b54bcd3dde2716e5a6b99aedcc19cad02bf2403e08c","5fad3b31fc17a5bc58095118a8b160f5260964787c52e7eb51e3d4fcf5d4a6f0","72105519d0390262cf0abe84cf41c926ade0ff475d35eb21307b2f94de985778","456006a6975b26c0a1785feddae165f6d307e2d601ffde27e21fc4a790e448a4","c857e0aae3f5f444abd791ec81206020fbcc1223e187316677e026d1c1d6fe08","ccf6dd45b708fb74ba9ed0f2478d4eb9195c9dfef0ff83a6092fa3cf2ff53b4f","1fe0d18b111e1145a7e7601855bccd4ca20f24e3b9a5aba6bb1fa9d1a7059170","5632c3c26d420c063eebe64c45b1248b9492a67bf44f1d0c57e9dc8f6cf449bb","0df5aa619ab12993a39ea6dae062ee46eadbb4d738916460e636ada52bced75b","8fca3039857709484e5893c05c1f9126ab7451fa6c29e19bb8c2411a2e937345","35069c2c417bd7443ae7c7cafd1de02f665bf015479fec998985ffbbf500628c","10ab7be91f87ebe8916b62cf28af2e45b5601fc7b0e311adf838f912c6b31dd8","bc636fbc08e0979ceb7eb0731a33000283d77a33b62e1f71ee65be50394e40ba","7e0b7f91c5ab6e33f511efc640d36e6f933510b11be24f98836a20a2dc914c2d","045b752f44bf9bbdcaffd882424ab0e15cb8d11fa94e1448942e338c8ef19fba","2894c56cad581928bb37607810af011764a2f511f575d28c9f4af0f2ef02d1ab","0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a","75bbd3be047d539988a0ff0b56384ef7a6a25f3b676ad96bee547d44c31622a7","42960001a776b089ade681ab5cfddc936e0afb0615133ec1841f3dee89d3e1bf","0aedb02516baf3e66b2c1db9fef50666d6ed257edac0f866ea32f1aa05aa474f",{"version":"da47712b394d944328245482603bc6f416d3949b67c9392279caab595076b510","affectsGlobalScope":true},"37d0071d8f0a06dc55c2c5e0ec3391affd4fd107c53410bf358196ec0bf3923f","b213dad76ca37fd552274c9499056e1c0d9c1bd38a55bb7f68b22ba6b84c3ad7","56ccb49443bfb72e5952f7012f0de1a8679f9f75fc93a5c1ac0bafb28725fc5f","20fa37b636fdcc1746ea0738f733d0aed17890d1cd7cb1b2f37010222c23f13e","d90b9f1520366d713a73bd30c5a9eb0040d0fb6076aff370796bc776fd705943","bc03c3c352f689e38c0ddd50c39b1e65d59273991bfc8858a9e3c0ebb79c023b",{"version":"19df3488557c2fc9b4d8f0bac0fd20fb59aa19dec67c81f93813951a81a867f8","affectsGlobalScope":true},{"version":"b25350193e103ae90423c5418ddb0ad1168dc9c393c9295ef34980b990030617","affectsGlobalScope":true},"bef86adb77316505c6b471da1d9b8c9e428867c2566270e8894d4d773a1c4dc2","5a49adaef698b7ad7e6127949fa1b0bbd3d46b7cbd11c54e392a4dcdd51f5190","96171c03c2e7f314d66d38acd581f9667439845865b7f85da8df598ff9617476","27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7","5c634644d45a1b6bc7b05e71e05e52ec04f3d73d9ac85d5927f647a5f965181a","2489bf04d77dc025ba67f49f1a56eb24b9db477d5ff88123d887e163ed1776aa","63a7595a5015e65262557f883463f934904959da563b4f788306f699411e9bac","4ba137d6553965703b6b55fd2000b4e07ba365f8caeb0359162ad7247f9707a6","0b77b819b5417775fccb20c678293cf614c054a5b1a65421a5b933a9124ba998","e1f6076688a95bd82deaac740fccbe3cdea0d8a22057cccc9c5bce4398bdd33b","9252d498a77517aab5d8d4b5eb9d71e4b225bbc7123df9713e08181de63180f6","b1f1d57fde8247599731b24a733395c880a6561ec0c882efaaf20d7df968c5af","c8dadeff90ccc638d88a989c1139fd6a1329a5b39c2a7cbef1811c83ffe40903","35e6379c3f7cb27b111ad4c1aa69538fd8e788ab737b8ff7596a1b40e96f4f90","1fffe726740f9787f15b532e1dc870af3cd964dbe29e191e76121aa3dd8693f2","5a3ea721d03a361ccbdd7390ccd75f6e84cbca3a3f01f4b331ecc9af31890c49",{"version":"e7dfaee4af38d45b1cab8a1ee0b3bc1f85ddcf64545ed391d675d78ae6526274","affectsGlobalScope":true},"98e2b197bf7fe7800f89c87825e2556d66474869845e97ad9c2b36f347c43539","af48e58339188d5737b608d41411a9c054685413d8ae88b8c1d0d9bfabdf6e7e","616775f16134fa9d01fc677ad3f76e68c051a056c22ab552c64cc281a9686790","65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0","f9fe6af238339a0e5f7563acee3178f51db37f32a2e7c09f85273098cee7ec49","1de8c302fd35220d8f29dea378a4ae45199dc8ff83ca9923aca1400f2b28848a","77e71242e71ebf8528c5802993697878f0533db8f2299b4d36aa015bae08a79c","98a787be42bd92f8c2a37d7df5f13e5992da0d967fab794adbb7ee18370f9849","332248ee37cca52903572e66c11bef755ccc6e235835e63d3c3e60ddda3e9b93","94e8cc88ae2ef3d920bb3bdc369f48436db123aa2dc07f683309ad8c9968a1e1","4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35","320f4091e33548b554d2214ce5fc31c96631b513dffa806e2e3a60766c8c49d9","a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff","d90d5f524de38889d1e1dbc2aeef00060d779f8688c02766ddb9ca195e4a713d","07ed3ddab975995eea41b22f3010506fb9f5fb301d04820b07d7a1aee5477d7c","969d8b0965849f4bae7cab0ba90bd1e1220e95999c2c6f01117fa7500901c017","6ec840ee5e2bc103f557fe38b1d585ee250540468713d7634ee066de372bf332","b0309e1eda99a9e76f87c18992d9c3689b0938266242835dd4611f2b69efe456","47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e","6ceb10ca57943be87ff9debe978f4ab73593c0c85ee802c051a93fc96aaf7a20","1de3ffe0cc28a9fe2ac761ece075826836b5a02f340b412510a59ba1d41a505a","e46d6cc08d243d8d0d83986f609d830991f00450fb234f5b2f861648c42dc0d8","1c0a98de1323051010ce5b958ad47bc1c007f7921973123c999300e2b7b0ecc0","ff863d17c6c659440f7c5c536e4db7762d8c2565547b2608f36b798a743606ca","5412ad0043cd60d1f1406fc12cb4fb987e9a734decbdd4db6f6acf71791e36fe","ad036a85efcd9e5b4f7dd5c1a7362c8478f9a3b6c3554654ca24a29aa850a9c5","fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7","e297c0a524edee7677939122f90027bfbe5f2698939d9a85728e5044b39c7124","cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e","bc9ee0192f056b3d5527bcd78dc3f9e527a9ba2bdc0a2c296fbc9027147df4b2","b62381cae176db34f003cc6172ee8f3e0122014889d66391aa73698105cf4934","1d9c0a9a6df4e8f29dc84c25c5aa0bb1da5456ebede7a03e03df08bb8b27bae6","84380af21da938a567c65ef95aefb5354f676368ee1a1cbb4cae81604a4c7d17","1af3e1f2a5d1332e136f8b0b95c0e6c0a02aaabd5092b36b64f3042a03debf28","30d8da250766efa99490fc02801047c2c6d72dd0da1bba6581c7e80d1d8842a4","03566202f5553bd2d9de22dfab0c61aa163cabb64f0223c08431fb3fc8f70280","41eb514d9ce0a6e87957f08a4b7af70d93f87637f37dee706e2d92a6601c25a9","e7765aa8bcb74a38b3230d212b4547686eb9796621ffb4367a104451c3f9614f","1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450","1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d","5bf5c7a44e779790d1eb54c234b668b15e34affa95e78eada73e5757f61ed76a","5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70","4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872","7bd01f0f28cd3aeb2046274d85208e245965f6f2948edf4f7b2057bcf9f22ccc","d2f2cf2b8cc92bea913cda4a076e0f790b23a21e84f989d12f0116a7fe3906e0",{"version":"6de125ea94866c736c6d58d68eb15272cf7d1020a5b459fea1c660027eca9a90","affectsGlobalScope":true},{"version":"f5b20bc288ee49989c95b20847fc93b96bf61cc0845598897a6a53a967dd7d07","affectsGlobalScope":true},"064ac1c2ac4b2867c2ceaa74bbdce0cb6a4c16e7c31a6497097159c18f74aa7c","3dc14e1ab45e497e5d5e4295271d54ff689aeae00b4277979fdd10fa563540ae","d3b315763d91265d6b0e7e7fa93cfdb8a80ce7cdd2d9f55ba0f37a22db00bdb8","b789bf89eb19c777ed1e956dbad0925ca795701552d22e68fd130a032008b9f9",{"version":"93ae4f0dfc37232949af02a71ef02651b9bba59c2ba5aadc1811158418a818f5","affectsGlobalScope":true},"7ad303e40d4fddf44f156129e397511953a71481c5cfd86b1862649aaaf240cc",{"version":"19057deb2bccf5a9c84dc204f16eca621755a2780c41a0b5609e45c04955a256","signature":"1d52dd10a60604480c2df73b6f2ed688c4fb54521f6e89a7053fade549bbd305"},{"version":"71ce4481a24791352a9d85c96464eb007398c5721cd4056f733272e18f997541","signature":"aa590225db421a6e653d25d07f3d5920ce1791ddf43ad9c1bff9d5b1656c3a57"},{"version":"8d508bdeb528b59f183d32ff2befa3e48d3fc19510ced2ab0c4e7b8983dd005c","signature":"96b83cb91c297d92b47dd178ab3b112dabe0131789c4eeadb9d9bfdfeccda54a"},{"version":"d40c2770ae085a4cd33f51fcda608eb21e65c4250eac66314bf34b4e0c483c2f","signature":"435d200a2397f25002f9eef130d0bf16aa85418ef89e59a0cb4460bd28853482"},{"version":"7ab265209ec68e2a156fe8226d32de96d3e2bdce280d662fc94a9d1721e1c76d","signature":"e32606cd5ffca79070c91e897105cd80afa446070a2bf5f3f69d5a9eabd29f0c","affectsGlobalScope":true},"c1a2e05eb6d7ca8d7e4a7f4c93ccf0c2857e842a64c98eaee4d85841ee9855e6","835fb2909ce458740fb4a49fc61709896c6864f5ce3db7f0a88f06c720d74d02","6e5857f38aa297a859cab4ec891408659218a5a2610cd317b6dcbef9979459cc","ead8e39c2e11891f286b06ae2aa71f208b1802661fcdb2425cffa4f494a68854","82919acbb38870fcf5786ec1292f0f5afe490f9b3060123e48675831bd947192","e222701788ec77bd57c28facbbd142eadf5c749a74d586bc2f317db7e33544b1","09154713fae0ed7befacdad783e5bd1970c06fc41a5f866f7f933b96312ce764","8d67b13da77316a8a2fabc21d340866ddf8a4b99e76a6c951cc45189142df652","a91c8d28d10fee7fe717ddf3743f287b68770c813c98f796b6e38d5d164bd459","68add36d9632bc096d7245d24d6b0b8ad5f125183016102a3dad4c9c2438ccb0","3a819c2928ee06bbcc84e2797fd3558ae2ebb7e0ed8d87f71732fb2e2acc87b4","f6f827cd43e92685f194002d6b52a9408309cda1cec46fb7ca8489a95cbd2fd4","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","a270a1a893d1aee5a3c1c8c276cd2778aa970a2741ee2ccf29cc3210d7da80f5","add0ce7b77ba5b308492fa68f77f24d1ed1d9148534bdf05ac17c30763fc1a79","8926594ee895917e90701d8cbb5fdf77fc238b266ac540f929c7253f8ad6233d","2f67911e4bf4e0717dc2ded248ce2d5e4398d945ee13889a6852c1233ea41508","d8430c275b0f59417ea8e173cfb888a4477b430ec35b595bf734f3ec7a7d729f","69364df1c776372d7df1fb46a6cb3a6bf7f55e700f533a104e3f9d70a32bec18","6042774c61ece4ba77b3bf375f15942eb054675b7957882a00c22c0e4fe5865c","5a3bd57ed7a9d9afef74c75f77fce79ba3c786401af9810cdf45907c4e93f30e","ed8763205f02fb65e84eff7432155258df7f93b7d938f01785cb447d043d53f3","30db853bb2e60170ba11e39ab48bacecb32d06d4def89eedf17e58ebab762a65","e27451b24234dfed45f6cf22112a04955183a99c42a2691fb4936d63cfe42761","2316301dd223d31962d917999acf8e543e0119c5d24ec984c9f22cb23247160c","58d65a2803c3b6629b0e18c8bf1bc883a686fcf0333230dd0151ab6e85b74307","e818471014c77c103330aee11f00a7a00b37b35500b53ea6f337aefacd6174c9","d4a5b1d2ff02c37643e18db302488cd64c342b00e2786e65caac4e12bda9219b","29f823cbe0166e10e7176a94afe609a24b9e5af3858628c541ff8ce1727023cd","2c848d85167df71bf58136eacb3909a93d5aeb445712cf225b68e23a58c94f9a",{"version":"0d42aad8c0912a3acd1885467766d230b59a9aaecf06cbe61db3ba8eb16ddb30","signature":"bcf58f57eadc764d0de6c302867e8d28a7af89716953e3966398e543530ed56a"},{"version":"a71180189528733fab93b635ba481d5735934537620e761dbf5a06f8c2d329cb","signature":"468bc51ab87258211e4098104ddbacfcb957b2bfef4181caabf42ff19daa7f5a"},{"version":"0c1543b200cacda3fdb390562916aa9261ca9494bcf44d61a9327e23e0e0ac13","signature":"c78ceb7105a4bb458318b2419e4280cea6cf6728801a4b56938dd67971248453"},{"version":"f11eb01b35d79a4b683e2367e62ebb21bab66a71543defa930ed4d9f4981b315","signature":"a0f44e6da310e13692195d272290b5f92d117549a660373907321b4f61b5cf0d"},{"version":"6af2eca05a7efac5490d85f83a62ca12d64e0a02626b48c7fb2035475373e5eb","signature":"c307ca4230db68b58cc3f3c2a4fd560ea4e7e140ae1551894f48180a17b103d6"},{"version":"272575aa71443e9d7732b9c140d839884d890bf35fd8ce065a720b5525582262","signature":"c307ca4230db68b58cc3f3c2a4fd560ea4e7e140ae1551894f48180a17b103d6"},"fe93c474ab38ac02e30e3af073412b4f92b740152cf3a751fdaee8cbea982341","3255b97f3f24af29c79cc1aa88004efb13b6285ebdde0a567bf32e19bb65250d","1e00b8bf9e3766c958218cd6144ffe08418286f89ff44ba5a2cc830c03dd22c7",{"version":"b21a240acb3f8b6f685a0e008c2d771a73614933d58851adca6963ce4dd7b6f1","signature":"caa1b198a8464669dafd523a15a3e1242818a28cfdf1e34c9bd0868de244d286"},"1d5f3f3a6b781909db1f435b239f36f013b555e88cc63a534b7ed7a179410ae8","7348c35ce10f8323a515ea6a6c237c0dc7c3fd5e491c4dacec2a7b93f0dfcd26","258e67b2638408b67fbf5fe6c953b8f2dd2a69f9171d31c9a976cd2329716e4c",{"version":"4c8c17155232527fec337e87da680b5318714d10df203c785ab0c18e2c79f078","signature":"f88aab57fbb88cd12345f6107f25a2b91b36475471e7a5f69c55a738808e6152"},{"version":"08f9991f5185766214666421a45dcee4b4540dc6374324b079c15d12fb61dcfd","signature":"d64c203591b2636598069519d3a6a0349872a23c280bcf7da0cfa40c956fa078"},{"version":"3e9d76bcf80e35422ce7bde4d17c48fb706cc231d4c3080a491836e7d2408773","signature":"55fb805b9021c06eafe78f95310a31cbdfdf8c3b1b3d6bb65258d147f1bee674"},{"version":"0699d5a2174ec2bb1cfdc81270042c8aa966864202fdb4d25150d5055ed2e590","signature":"35858f9c1818fe110880afb899b3de723aeaabbed636db4b751aedd937f457e0"},"8f95b4b53d4522f8d1010a073c07aebdda64f44d56b0a22a89400242aa3f4723","7e0dd7745de5cfc3c45f808d9f8553f65ab5e46137c1ee44ab0c6952e2293d7c","7d726479d24cdccd0a77a47b7203a4dd231ae8c97035c12c568dc6966ddb6a64","c335ba6a64c20a5feaff899e6491ec4c4e5dd69e70066b3a4d7e2bc964cd3057","123e805b51c37983b549cbbcc52f644b9714b6595e40145f79472e8a84b9ed86","5d8b31143c299a58ee75de8d08640b76d02b14ed21dd0d2f4f4ab2474f5a7d90","50cca1953dae077b33f08a532c408045e877a7b8c078ee5513231b005664cfd0","5e3dbd3b43e6ec52958b141af2b4bef1b2de8f602d9bea636f37eec83d4440b2","e92837bb84d6e09cf2a41029ca445effec2eb26c22317118439e546fdd33835a","7ed9b4563cbc9f9baac3be14c05ec6236314b9352606ee36758e088a6ec2eeec","7d13c1f2e06b846642d1843412df218725c7b87976e83129f62e8f2d4c58fa52","94a8c1c289ef27f42280c1d730100a28540f709492fff746cff2286b0e406a26","8c26acc596067d21f15f5dc136b4d3daf5658854461c29f4dc6ec66a480718e3","3bec811f5aeba45b7ff3adc9abbfb8aff256984bf44b26a5d1ba276aa79d261d","3737225833b121f3fcb4402070c095d87c29bf453796707a26da829f4228c1fc","7cac781e1c51f3eabbbdd6a2e17f83ad04ed016267ae0ce45d57d4fd14f563c4","c9d927a8c00c15ae154d85cfdc3830e18d3d0972eb549311473468ffd682826d","3c2179e56922a2e287e466534c6fb20691f83853b870d99f609ceb9bc7526a55","c1247c0820d5d9645163386bdb52947ecb9fbc45c436213c0c2c91e1dc8449ca","ed5e4d71f85fa8c7b70eac885fa1da580c7d5632cbbacf6478b675c67d3ec9df","0e24b3fb18105ee4e4536de9d15f885d22a8119633550ff52c6d5aa27cfc4cf5","3ce2e95dba28838c21a981faff028630d7e874c8c429e6d515cdc448cb868e42","e03e31f8cd337c7fd31b106d463960d35344860bc7a02f8f1da9a872eeb686f4","c4b966e6c010c501a32ac2bc8dc9420a87e5c4a3e0685f700262f02c045b6fb4","8d98489d008026dd1a38cb9d70b31558c0164ec488b9815b8ae0ed466d215df9","32c5af30582fc4d9cd817aab004de53994027c64a2931dbd4229935fa677dc31","11b1d083ec7b7de157f7bdad5a11a4b53913d1caa56007773b9a32b203e86781","18d99cee5c26fd8e9fbf386f055ac2b269fa7d645c1e4fd3e7bb5b3476ad6aec","2dde80ffcef0c366a1ae22a6eea8dbc6821d518092b6a0bbdceb37da0d53cfbf","0eab6314ceccba630cc6cca396d80e8415efe03903df730d57517ef1bb30bc4f","bab2e3c1a519be231dafb3d9b0463fc7415e356f89c08b785c0eb8b86d7556c4","ffa44f47e95902b40d02b5a2a2395a2825937df7ca08542716b015dfc61862ca","2b6ec928ac39e9e63c9406c427c0fb7d1e84d80d7b3c8dd78be121c8e5b32ee9","eff6fa1bcb36c62e15a4ab37e4717aa804986cb095c6c0cf676a596d614126eb","9d39ae81494ec822f28812de06f4441ff839940aa06b7e3cb0f9e02623cc310c","c869fea6f6781ff27332ff370d422ceae6f1c8ebda4ca79275d89d147786c1d4","d438883ea02e1c9e045f47b8a18ae014e4011d4c7ab8798b7b9f1be8b55bab20","babae7f05d084027561e1b7812edb6a660687946bd2c7729d5e186b9e494f9ab","e9a9f2962a374bd2d2b53059357d6ce9f0416f49e2c53d38f5c514ce00692c30","24bd9f28cb058c7bef45cc6f9babaa3af9694c6d68f8037cb6fe88f2419b1c1c","cc4f3e808c9e0251a3e4ac160a5836832eff53194dcb39e3cb2f3e70eaeb511c","7450441efd97dc54f68fe785a6a7f1eae875c11338134889bc88eaf052b70df0","426a1be9f47a8db226a21250158562e1837ad36862671cd4764d4d360f0c9eea","5fc1a3db11db206c64d5038b44276e94dcd402b262776b9a27c19e9176f2e8e6","c73e8393670ba80643a3bc0078a7f3de8193acfe1fd7c84e76132312562a419a","28a7f3ca9fcca7caca89b6bf74c2aefe982f7ff57f36bcb5f29c91833d39b302","d036b54cb57366f779bb8b998d92778ad1453e3e1483013cad43df76dd7c4eb1","325c9eda18d6e766fa14c006d4c1c329f3ad8cbdc2ed7c117af113b71f7aaeed","1105bd9fa22a9508097685f15ea765554eb65d55b455d273150dac014246f787","7092715152a127a874c1453831de3bda4e6527200b5fb79af83c420d4a445b7f","83562fedbc941e235a3be1c99b337cc69d6f9aae4999ac836adef2f920b9e3dc","26fbcd7cd9416ce4438e56417ae7b8b91eafb1cc647bac002778d57583dfecf4","95eb8982a57d9933d65e2afdd5c866bc715e40c30a0dbaceb59892501ad659e8","21a53758e5f95a22f56995233bfe18bd3f39c23ad64a57936cf793f5fb01fdbb","8cbf0fbcb7706d0a2394eae1a564825d50a0d0c1ee9336f8bc8c7da41711ed44","a31738774f95b193a777d9531f9c3609e0294cefe7f0971acce955e8ef52e8d5","ea88363b3147f77e19ea1b55f0cb038626364d476863e8e2f91cf60cf286e7e7","913ad4fa2f9e0b444c264605fce1605cbeb47d5be25137f9bdd25f3ccd7be556","d2a7cc6e7044a81891d07d0be88dd12f7559fbbb35227a3ad42514c9fb96b83a","914260e95660c84a6e283ad91dd832d9f3119fe9a7cb7ffcd0764716dc4f705a","0eb6cee7bcf4262afd1a083f9fef4ec12de5022e29e9fa0c86d0acd635eb37ac","a6b39f1d2ceaf4f5bb3dab98483fff79e4ede4786928c93307a482d0e888d12d","3cd99ec82de92fff68a2768cab73739911766bb7c6e7d10e29706d656107a2b5","c21314a38f4337202999e6473579aeaaef6371b6d001973692715433f4e7fc89","899bc20734112d2094d89e4f55644c6f3115d2440c250822ffbd4fa212b158a5","d39fd2772f9bb4dacc97319c5ed760ea9e2d83458b522eb5894ab09b0674a704","3f635b32106636b9c54c7cce0c73372f99859f9ae90c0de7d83d01f48af3064f","d114c65d009de428f5c9ae22fab8ab7a602d22822fb8eb77ba3f09ca7aaef895","fdc9ee7bc24198eb4ef75bff9c3233bab7b7586bad8ea11bf18cd6faacbc5c65","db7da89b083e353471f3911adb59288c2d4bda401b25433943e8128d654e0afc",{"version":"e02dba5bdeffc897f15d54bba5349552f6ad3ff78701c1f3daf48c1c833a338b","signature":"fb6f4fa19c31ec6d573314ab5554c1e502d060f1dd1bc13a8e33e13da82dac68"},{"version":"1d15b5ccdd194c50d98059e908abf83b86b07fef4ae775aa1f58ff2d8168c183","signature":"8594bd59ac178f59f2d84bf8688d27ad0a3becc7ba33fd81f04f8d824ebf4401"},{"version":"acc106b92fba6ddbb441372feefa1d5590714b547f02b50dd745d7737a6efa0c","signature":"7b87720aae5dbfb749d597c437531239e4019502dc0e78633561735db617b6f3"},{"version":"4696a36809af910477f1055f544b9fcbad274c61bef927a783cdebb4d7318fe5","signature":"af43d990de0a7ca77c517828e04eef576a41eecffd40f4969b7204730d7ab4ff"},{"version":"58161f996133c22693eb7ec1de37906396614f5f4496994114134a90170154c2","signature":"ee9ab354680e54334978cd515a52ab5827b0aa354a219a0090d6a3c2a5200607"},{"version":"d89f3462199b790d57723554a63e1427fe6b45d96fd3402e3257aeb65c1565ef","signature":"b600573c0debf674ee5c2f87d41a32a0ad05fbe876b4424de64e682e79aed0ef"},{"version":"a2b40c42a6955bf2d6ebe22c7c965f886cfeaac9264db564bdd9f0ffa52c42fe","signature":"f0ea57024528a15b4b205f6b0730596c6b0fe6c05e5eec104a16feaaf3800a07"},{"version":"8b0d9e2f3dcb64edcad3e6556e96ace0d36e8bfaeeb95a483ffd6c6f8ee24823","signature":"ed2a76a9709b7048cda9306c4923395ed1ed27f256aea26f91d3766cc4c819fb"},{"version":"9f12612081ecafd25a69e319b6daa3f79653940a11c2c4c7f92d154a257cd83c","signature":"e712c539f287227f7325b64c54f20a90b8fc7fe88ad6d5207b45384697c90153"},{"version":"e13870a91afa9586f9741f431478f9159c089edd8a71c2b15b460a66723d0de4","signature":"11e21c32bb2568d07cbbe5fc074cb99b6904e76676f7cec14fea5b0b4f1b8079"},{"version":"24406c9b1293aca6f377aa326931fb01adfa4dcd4b409c17b64bcc331ef5bf2b","signature":"ec9a875269954c1ba03a55c91da48022034ff4affc11c5f29d446350fd79827d"},{"version":"8fa86846b00fe5b75512d4837325dfd3922441a7576b10797ea3b0a2b35c4e02","signature":"9f4584be4f95f11ec5c89191b6b44240ba7a8da0c95742cfccea2592c3b5a831"},{"version":"0d4c12fbf58f91044d49cd2c5a4c0ca5e3e3e60ff885bd71a037a2e4eafaa96d","signature":"2e3e6fb88ce4f31e2be2058eb3a7c598ca20090b3b627c5fb6115cc43b8f8c4a"},{"version":"8dca17ff12fdb20402189f94db6fff6ccefb7bdba685b9630cb49164054f71fa","signature":"c794febe22f147a05771ef8271eeef3704ad5138bd1888085d890382b3e59161"},{"version":"df8e7b1a9a7b78dc3b4f86509adefcb299937ca48ba6131b3d13bdf1363581d9","signature":"9e802ec2285c19519c699128dd1d791b6843dd589fd9f2401fcb46a9a02aa43f"},{"version":"0863e3e9addb3f648dece90a3fa5444e6c38c972ec80924c66854456119bfeab","signature":"c00d28620b7f1f5a5514e497ec62252c3d081edc5baa049edd3f6b3255d55552"},{"version":"217661fc5bbc27004adbc4f2bc2be52c69385b2d623c7a8307236156b7c90413","signature":"eb0a47da0b0e3ffd59cf4ad71a6bf0ebf9d93680d53bb3c5dfbd2c01405e60e1"},{"version":"5dd20c5d4abeda697efdfaec0660a7be908d2ca9721591007dc0c87165d5ba2c","signature":"282b58c1a7eaa775343f717ce14a836038ef998c9ef306aba618b46ac328bbe1"},{"version":"a7ae6acae0232bd62325d5e35bc1ac57eff67516737f68285a2d124c176fea15","signature":"c2ad7311e0fbd63784d9762b56a4b75f6d11ca83d953d8d8e4582b28bb68b508"},{"version":"db6e59fb9001a0d370a1acd0b057dc774bf2fb5393e52665031f7e6809c12fc3","signature":"c3f1af455d3487b01feb03e93a6cfdbf55d62c16572a92c3d282bb1e00ca0ba3"},{"version":"36f055572026eff7586c54e4dc2dd3a1faa24dec59121d286fda602dbe8c433f","signature":"b883fe48c574a069b1bc61f4748c60d31da0f352515c3abf9b4e644ec9ab4252"},"50b5e0089cc8135750a422a661660de0b0efc8879363507c703e52fcde653d46","b7901072b4348af0c9c02ef413add51f3adbbd608a6f3155c40fd26b6a1ccc53","f887c9c3d273371c7fbfbb28128ece44ff88240e94226e2411b3800915ef1ec1","4a80acea776bb9bc1315176b7cbc8bd6afd7524ddede00936fab97a9c19e050d",{"version":"c32d64c16537dfdf5254f639606c632e4cdc97e445fbcd3659dda493592eb0ec","signature":"2ebf2908f5fd13e598b5fc6151c83b250bf6cfa3710f020dd2cf503b79d27577"},{"version":"d3b963d3ad36b1b3a39b1727c2d1cb3cf8f82a2f53d643cea4d2e8b787d7f867","signature":"aaaa457daaf26d32b47682eb61251436083f510fab6ebe4d0dc818bd0092edfd"},{"version":"ceb52e8ada32787ad839dff259cc4a85bcbf6448417dbed5d180584c65237ff5","signature":"e9d4bb56f013adcd8a0b33211e0870040704ece52fc5d7f303482c1ea7b7775e"},{"version":"a5654321577784cf14f48954695365f6cd3c2922abe9c2d3084cbd88e7271d52","signature":"f17bd7ed36a4a9810bd5fd92dfd490b6835a6d537f8b4dc6c48fa3e3fcf28af5"},"65bedb4a67d96f29840d820ef493c09d1347f49078a62d49ffbd09087cd07176",{"version":"8e9d2eba8e8f051f22dfde0d656af94b4856e974626b7c1eb56a7075fea9cca2","signature":"7b8d9ebd4c8e99e6e7e44fbb9a6ec72b22c8e45972bf75d8173101fd7b60e479"},{"version":"017609e44d98fea641804be490d75582139379bb9e70100b9d1102e7478fa62b","signature":"faf77a35be7f5648e12ab952b900ee69526103333b3d7b86498adf346a207d89"},"c47b3065f186a303ba2b6c43134d06f22bb55858edae0ef5a673af58b7224528",{"version":"6b2918103774a86c46c5b9977dfe8c1fb77714dfca2695f0b0e3022ced40e9be","signature":"c357f418fbf9e83e1eda2f76b9c0a69adab3cc16131895c20a2c37665421398e"},{"version":"194575ba989d1b6d14d4f4f7818a3132e46c0c038014d95bca9ec487a232845c","signature":"f0f984000a05f87208bb940b7ee0466035af819503044412fe4003569def7403"},"d8b13b3bc61d5c23a46d1ea65f29d4027afbdaa4b1e2c8245cd3bc1c995fccf4",{"version":"2ff079ea24d8658285ff80e69ddff92d99d1bd9514906ae17982bde909b4ab23","signature":"b62f8aeb315a6874b400426f33a71eda94b7086c73e07ace88f3a2bf39779f20"},{"version":"d38ce747917eeb6ae6d6e25c5c1704465e20d586fb4b685e056a151107bcfb93","signature":"fecf2db136256608e67de3a4d60c3e965bab210fa3e95c1be2bfe0b917b4f0e4"},"70f619f78c5dd1d7740302cf2ceb4f50a8250a089d245cbe3bdfd2d2b539267b",{"version":"cfc449516bcd4ae29ee4aa26b1db8b180b6b67a5b2ddb79c7f3648bfc71ad109","signature":"26b7bf752be650968a7a96e0d19667500ccb6f54da84c0dbec6beff2b4d6da84"},{"version":"14353fd744e863644b4129ee809916b5db701b408c63f0474011a3d51153392b","signature":"eb571f42631fd53b4c9759137984c071ed641bc1da3f59a348f2b960bb8e5ba6"},{"version":"ac7f2c6c0b472df4d59c70804538bef8a05f642aac71d3872cff54d30affc5df","signature":"a0a3fbad280d33c2deaec14a798270327710a06c48ea9deea1fc6c7e47a02a09"},"70459dac73b861b42361833fdc3332967f8467cd3a65b22963a5c4daf9925e7d",{"version":"45b21caebc3e5f217173ff0744bda6be16bae8f6d506875a90b713ef02c5a7f8","signature":"aaae4534085bc40d1208a414429ad36d1cb2f05701ed90ee61dcc76550208979"},{"version":"c60b83fd6ca9ee3cf065f5c659dcbb5f183deaae5409eeeb392ee069edabafce","signature":"b40d44b32456fd07c96d5fa3837db5f55d06eca8a1f93a352c15a19b9b6672ca"},"b6699805cb4ec267945d0070d77f2ae9a7315512ccb73fde71d883973beae399",{"version":"8c5afa88d6548f90cc80482de81a3cf91beae0fe0101dc68dfa86a48acc82384","signature":"9580f62d04ee36b80f9300af73217bd9d9b030de5b5286086950b95063b5e3f0"},{"version":"a783ec280de961ffddfc89a0cec42ef284a4fcddbc007642aacd878b79c4c75f","signature":"ad9e27de69f0a3ce140368e9701af3cee0164f9062a83c6f07229d77c86734d2"},"0a15e6c9f7f828d4e2a7e454c19436d5b9798515158ebb3b4c6c0b5d352d9743",{"version":"86ce7ad404f035e56c50dd95f2e6be119585d61c57d75a13602408eb14441000","signature":"445611e8f39aa6050a3484dc86cd1f0715f8677554a52e9ed9e793ab9a12fda1"},"59c3171e265479cfa3e3f39413f00afec7607e18ba6df3c1c737b3be04e954a6",{"version":"3fe0b23a1f93b7bf51384098ba3acf11d663ae43f32eabe6368312a048807c84","signature":"542d354def87ff5b99b066f1bcbb2d9a5f8fa7d84eb8b6ca10e4ebfb73639bec"},{"version":"5a5c6e7ed3446581d58bc21be415dd86fb7404e19e346a8159bde072dfde53f7","signature":"58fb8479412dfda44269d07f4e7305f26ad1bd77752cb5508630d7bc1e93900d"},{"version":"65eaf13c8a7f0c30272af9d193f27e4bf141cc2d8f213157f8f1099e4a69e617","signature":"77dbec1678fe6f3d5346b38a2e6cd3a42be62ae44c4755563944343877251987"},{"version":"d8e96f47ca5467d4c710ed7f7dd7def2e17ae2690924881952a3c71aeaa2bfd6","signature":"436759811402e264efb204dda538ba920dfa1ff2be85883a93757e29732637ba"},"19f112c9bab045b027eba6db1df6981092ba36c903d4f60093ee4f7aef12dc91",{"version":"9b445b17cf56b332b2240aff1079d7307d328dce7ebaa9f0242ad66fc18c73d8","signature":"cd32e623f24176a767082458bc617e122995c1cab290f9992ba93c71505296e5"},{"version":"a66e46a350be103b7f78a78edafa1fa93c0d619b723a18274c83f0e6323f2c47","signature":"0c9c77ad0910e37c70952568d9efeda325c83f68a6d8ff0056fa107189fd1945"},"3bddbab89355f51bd8b42587f27190e7b50a1882113b3a19ce886cdd422322da",{"version":"2b5457426efe2f305c2b5f8311cdf2ec1d3b4fa6155448f38836684313d45b0b","signature":"1db4be3384a337943065b5fa9ce41b6f19cb019c8e6a800b7bf12a31872133b1"},"3419307e5758ff5f87a99f6e0b57dfc3004fa0d0d4da5daba9d3440053ee3316",{"version":"13acc56e4dee51d558625d3ff9f9dda8ffa2ce15fe8a349f7fdce1e49dfd96ec","signature":"ba0e17bfbd876fd9995623e23a0288ba21175fe443cfc5998b31ceed87511194"},"7fef987076649aaa4b18f24b2b71420f7d33b4d5b39344e316e2c95edc9163ab","5828a7a366ce95e8c9d565afdcbab592dff75956b9c841cb6152a51ef8bec8e1","c5701d1aa21f437bb0d0fdfd6adf8c8ba2c9b8b150b80d7a309c8236ab9ce6bf",{"version":"84accfa3830ddd2fe6e88f319e3e72b2cba051424330b7e9a7d10fe82b2482c8","signature":"5f19f6528874c04d4c7ab60e0ccf83358df80fdde252f99ea73677ba9e465448"},"8876ebc218a576bbf2f71156d1699ce8d30b38c68a10977bf137935b5ddec3ba","bd162fd1db9c8175b2a9f8f8a8257bcaa71e7320272602e9ea130acf556fd6b4",{"version":"14df21adbe9baf4c8f8d13c680e96e5e93f3c05f67b49e01d58e62048e6a1607","signature":"38a79980404c2230335f84dbf00b715475d5a0c1b79de6d5645a0f4334a57c62"},{"version":"c09d9f9e2665f2edfc7436577ab7de14333d92e6b3482df1b8d24126de1af5fa","signature":"3b3553f30fd03aa1e3f9ff3f3e3dcf5397ac3cc1f792f77e231ce7ef172f063d"},"4f870e4eb9cdb42fecd2e0db6d2585239bbc476f9419973e32b4610bef5c976d",{"version":"4f3580808da24e36ee218b783c286b5179d592849982157c698e6a543c9be9f3","signature":"145bbd6d54e747345048c472c3997d1613da800d9eeb4b9449d0ed7bda8f0800"},{"version":"7285ba3a1ffbea7c7e678a8528117785b602c8f898ec045effb3cd2ca022dc43","signature":"7062acca0a96cd5b3bbb75021f3b1628493675d096415fcbf433e5e6f3fef2d3"},"96197ea53e0992242f1b8c3a91cee947fba45ee218d11aa6aa041041358d4c28",{"version":"0b6c490e5022cebaa1fb9b9efc92106024b1252a797520c7e9f7cec28e7149a7","signature":"eb24f9f3a6819d27e67d0e0dc70d8d729cfff07f3c5ab0a55159f95938c8d5dc"},{"version":"94431bb5a46bd051ae0f46b52b31a094fc89cba3ed8a7e74814f8087f3feefbc","signature":"d19936ae3a7cef953303df8ee5519f6cf79710b1476524afdc3aef618bbda299"},{"version":"ff32946dd6b9bff0ef89d720928f5638e5ae480b89cb714348162c2663063874","signature":"651e438b6de12883e5b66a63584263ea899685dac62f0162aa6d9e53419aa821"},"e88bc78dd8f694c29d6d001b122b1c1eab1776b04b47c2af7eb4b546b0658c5f",{"version":"e87affc96a499e5783c95a15aeaa52a449dcd6960fe7db126e908800424ba769","signature":"e29e2d48ec11f0c9c1c711a679b350a699afdabfb4ca68bf4d5c3631e8d86750"},{"version":"562a0ce230aed8e40a64a0fcbb9bc4e87f6e44b5a517207bfbf570708ce82b77","signature":"291219ea9e5e3d25456801a877f2ebaf564c3445d2b5377613c222424cce90e3"},"8d2dc1900eb4e0f58e9fae8bec2aa24cbb272e333b75c185f7152ebff4258073",{"version":"21a1da0e8f3cb3da1a8093f9be02a1d31928c3526bb69032b936c8185c82ecfd","signature":"3fdb55f8db6b78df59effe7150172b72a31f2a4ac7168115eeb2b6a1c98fb16f"},{"version":"9014a7fb5ee6092e72f54abc4c2bcf67941b7793dd8f04d2cbb0b4398f3a259f","signature":"1925cf8c19f0ebc9231348b1bb61ef17662cdaa98be5cce83c7555f37d0d3454"},"b789d50343e536848d186f23715c223599f62f2e562180a32803bc7524629759","5339f84dfcb7b04aa1c2b4d7713d6128039381447f07abc2e48d36685e2eef44","fb35a61a39c933d31b5b2549d906b2c932a1486622958586f662dbd4b2fe72e6","24e2728268be1ad2407bab004549d2753a49b2acb0f117a04c4e28ffb3ecdd4f","aff159b14eba59afe98a88fe6f57881ba02895fb9763512dda9083497bdcd0e6","1f2bddea07543ccda708134cca0600b4d9ac9bd774ec1ede0a69935b04df1496","6e8997d08f6798d0a9416df24312cafd084e6184a205d9283eba95ef56f8ef8b","ac6968717607889d24d6e407effb48dd5af82005925b4725b1d9eb52a8a047e2","26080058b725ac0b480241751255b4391f722263778e84e66a62068705aafd3c","46afbf46c3d62eac2afead3a2011d506637bf4f2c05e1fd64bbf7e2bb2947b7c","84d02daa32c7a8bff4946bbc7d878ffb7114c19879f7bfceeeb39bef48e93c42","97e1818573679b5d3d697406abd3e5f1e9cd00da1f2783ab236912180462f5be","be79291850df988e29e6a72fcec3703dbf85caec078d9631311f0b97d451094a","cff399d99c68e4fafdd5835d443a980622267a39ac6f3f59b9e3d60d60c4f133","6ada175c0c585e89569e8feb8ff6fc9fc443d7f9ca6340b456e0f94cbef559bf","e56e4d95fad615c97eb0ae39c329a4cda9c0af178273a9173676cc9b14b58520","73e8dfd5e7d2abc18bdb5c5873e64dbdd1082408dd1921cad6ff7130d8339334","fc820b2f0c21501f51f79b58a21d3fa7ae5659fc1812784dbfbb72af147659ee","4f041ef66167b5f9c73101e5fd8468774b09429932067926f9b2960cc3e4f99d","31501b8fc4279e78f6a05ca35e365e73c0b0c57d06dbe8faecb10c7254ce7714","7bc76e7d4bbe3764abaf054aed3a622c5cdbac694e474050d71ce9d4ab93ea4b","ff4e9db3eb1e95d7ba4b5765e4dc7f512b90fb3b588adfd5ca9b0d9d7a56a1ae","f205fd03cd15ea054f7006b7ef8378ef29c315149da0726f4928d291e7dce7b9","d683908557d53abeb1b94747e764b3bd6b6226273514b96a942340e9ce4b7be7","7c6d5704e2f236fddaf8dbe9131d998a4f5132609ef795b78c3b63f46317f88a","d05bd4d28c12545827349b0ac3a79c50658d68147dad38d13e97e22353544496","b6436d90a5487d9b3c3916b939f68e43f7eaca4b0bb305d897d5124180a122b9","04ace6bedd6f59c30ea6df1f0f8d432c728c8bc5c5fd0c5c1c80242d3ab51977","57a8a7772769c35ba7b4b1ba125f0812deec5c7102a0d04d9e15b1d22880c9e8","badcc9d59770b91987e962f8e3ddfa1e06671b0e4c5e2738bbd002255cad3f38","9c05962e2db9c8258604edc9dcf538524fbb7c32adbeed7efa6cb2f85bc41953","820287fd1e5c94ef0ad9a0cbde5fb238287444962edb2343341ef653229435df","3d1408c4a396851667313cf4acf538714271abf06dac605023660659e6057d9a",{"version":"71d5e9c7a237d68bef5f253e41fa2b98001439971c2a0aeac2dcba020c798e18","signature":"ebde7c386b80a28c65a11973982ed4414e82bba204ec868149244c23afe6638b"},{"version":"609cdb013788cd63c927c074d933b00926a85dcefad189e1a4038a659703b779","signature":"44a30773fd48463f72a0ee166e80d71ea6a8ce2ea020dfd58ef85889ed56ce4e"},{"version":"c90bc2f4cc839efafb5afa09f7c43c5b9c8721e2c18c19de611df9cb8fdee0f7","signature":"a05403c14f0fcf7d9f368648ccd7fbe9635dea2d76b38616ce7978b2aa63889c"},"9f2b60a199443fcba445b103dc7a0d6d3c0901d0279571860fcc32a67982c0e7",{"version":"9d1f687f92714b82d0a55d822c181281f03b047a173a01c78a4124abaacbbf27","signature":"b3ff69f48488da5978db457570b0745f10ac01095b10460cc4323addd16b1e51"},{"version":"f2dfcfffec92fb67f40ea55a99bf8481b98618e019e5937269aaf17fd35bed8c","signature":"5eea39e2d0454c2439941d26c4cc4f809fb8bac7cda34a58e68c627fa7c2e300"},"ba89ea4d3d73ef69151d4d9676b3c0b795db98af78ec0899788a2a8184bf720d",{"version":"77a85e25cc82b657571aa93d2be263618aa034b97ee75e76e171e7f9bb598ca7","signature":"b57ffd2dee78f4d7e24877bab745458a15e7d4c2a9a715aa310e7d9776769ca1"},{"version":"25ca145f51bdd2277d91297fc7a976c9794a961808723a0481d940db73128870","signature":"6df3d6d17d7b9b451b5c315fef3e05e7facdf40002ac423ed7badb854f5bbe76"},"f82eda491532647a55ac1fa047d04c1698964934e06be0ea80e3b17995c5bc22",{"version":"127d649065fdb75c6659afdde9431b7cbb56250fa5048b013e2f3f81975bde1c","signature":"19384f53eba2a1209a3b7e9e2615f4b11a89e634e8c9bb7e52ee68a858d7d3f9"},"1ef9310a9c3e55ef4479a6ba39c2d5a84dcf89d86ba2f0f450d7efd80c2c4086",{"version":"3b12535bcc01640c88da8e8462bbf67515a65a49db3b426b2deb3f960946c9e5","signature":"1d0150fa7b967ca930bf4d81f97968e81fbb12df39f7ff2dea819fe239a08a7e"},{"version":"033eaedbfebd0c6e07a4cfffc59ec3c62872a4daed6ba391a2d02aff8dd2dd4f","signature":"306c065f877b8fd57edfe4b0ae9cbcfa9a0c200f9c165c7186ae97f260c8df17"},"fbfad858ba60f5a46916ded99252dcd4f3eee04deab5ada357ae404acb7ae92c",{"version":"2618cae715702bd817e1528337285ec704ee5e925ab638c48efd011918c2d7d9","signature":"9d48ba96f7a03f32aaaebc42d2093f08e05ff9845365325d607c574115981b3e"},{"version":"3733123c9552464e981e3c48a3f3be87e30a0b87c57f39dc445e4a1a9b74f958","signature":"684dd2c3e2352eee7e561d7fd4123d56d7e5903f39d76a5b674ffe738b9e4ea5"},"c0fcec422e7ebbbc148daa36f11b979f225c4bb8f8e56c294c94d7427b68c54c",{"version":"67a9057796ca7c7c04005297532358ad6e231223a8c405011f2f6752f79c5818","signature":"4d8f6b245380e62372145114bc782801bb6fe32e46268022eb8773c46c411dbd"},"10108c7cdc4ad660215d3ef4bb893244afd422c0b75ffaad9c2d14177278b769",{"version":"84b7b73cc56d8a8f66956740692053f280289ae6fa0fe880db00a0b0d4a7912e","signature":"fc0ae6558234c70ac37e299e68ab60c71602026067d156c59385a99b4175fb35"},{"version":"c7cd53e6fa8cb5a1d88268abec30d74e868822b4816b9d4d4ef9d2c10747da51","signature":"83377ebcff939d6614a818c9df2b568894943d1de6b47486ebd6bafe9070cf90"},{"version":"1228e16822f5edea8f5221e1dbd091efd52daa5a438e6a2a5e92c0f3cb816835","signature":"11897cccf9e074fe6001b66576a2ac97e23f29a54991239800a309cdf08ae592"},{"version":"b7eb2b3d8e59e11c623616c54a9b9f594fcb0b18ba36675a028c53c33bf8fdb1","signature":"462223fc5c3f164d8818f43ab372137795270cc7dbdf537fd8f8db5ccfc074e7"},{"version":"05741e2c3b958508424ff867afc2573912e413ddbd4177fa691045eba32af703","signature":"8ee403d597589bc5bc99839d5199596b16f459bc2f3a78ecf6cecdbec6843341"},{"version":"11c2408c325a68fe9143136fd1f27d19d97cfec7b8d8e6e7772144a49c4ff40c","signature":"6ebe765592b2e9f9cfcc835e1eb80320ae2a60feb59e51ee0d036e5d6e812ec0"},"d1986184a09a52db8228cb2bb2a61a8c05c9354e5b93cec8e2628d8579c892d7","3dd7a9ae023eebbfbce7faab47f25dbacc126234d43d88888700b176f97fd2c9"],"root":[[496,502],[581,586],590,[594,597],[668,688],[693,751],[784,811]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":4,"module":99,"skipLibCheck":true,"strict":true,"target":9},"fileIdsList":[[449,450,451,452],[192,490,493,496,585,586,590,597,670,686,694,696,697,699,700,702,703,705,706,709,710,712,713,715,716,717,718,722,723,725,726,727,728,731,732,734,735,737,738,740,741,744,745,747,748,750,751,786,787,789,790,792,793,794,795,797,798,800,801,802,803,804,805,806,808],[63,192,483,501,502,596,667],[63,192,483,501,596,663,666,668],[63,192,483,501,502,596],[63,192,483,501,596],[63,192,473,483,502,596],[192],[63,192,473,483,502,581,596,668],[192,675],[63,192,483,502,581,596,667],[63,192],[63,192,499,667],[63,192,663,666],[63,192,667],[192,500],[192,580],[63,192,502,581],[192,671],[192,699],[192,702],[192,705],[192,715],[192,712],[192,717],[192,709],[192,725],[192,722],[192,687,688,693],[192,727],[192,734],[192,731],[192,737],[192,740],[192,696],[192,747],[192,744],[192,750],[192,792],[192,789],[192,794],[192,786],[192,800],[192,797],[192,802],[192,674],[192,595,596],[63,192,502,581,667,675,678,680,681,687],[63,192,473,483,502,581,582,596,667,675,676,680,681,687],[63,192,502,596,667,675,680],[192,473,581,583,596,667,687],[63,192,473,483,581,666,667,675,680,681,683,687,719],[63,192,667,675,681,683,719],[192,502,687,692],[63,192,473,581,667,675,680,687,719,720],[63,192,502],[63,192,473,483,502,581,667,675,687,707],[63,192,473,483,502,581,667,675,679,681,683,684,685,687,707],[63,192,473,581,667,675,680,681,687,707],[63,192,502,581,582,667,675,676,678,680,681,682,684,687],[192,502,692],[63,192,473,581,666,667,687,729],[63,192,473,581,666,667,675,680,681,687,719,729],[63,192,473,483,581,583,596,667,687],[63,192,502,581,667,675,678,680,681,685,687],[192,502,692,719],[192,473,581,666,667,687,742],[63,192,473,581,666,667,675,680,681,684,687,719,742],[63,192,502,582],[63,192,473,581,666,667,675,687,784],[63,192,473,483,502,581,667,675,681,682,685,687,784],[63,192,502,783],[63,192,473,483,502,581,667,675,681,685,687,784],[63,192,473,581,666,667,675,680,681,684,687,784],[63,192,473,502,581,583,596,667,675,680,681,687],[63,192,483,502,581,596],[63,192,692],[192,698],[192,701],[192,704],[192,714],[192,711],[192,708],[192,724],[192,721],[192,483],[192,733],[192,730],[192,736],[192,739],[192,695],[192,746],[192,743],[192,749],[192,791],[192,788],[192,785],[192,799],[192,796],[192,677],[192,669],[192,473,807],[192,490],[192,490,584],[192,486,491,494,589],[192,593],[494,495,496],[781,782],[579,762,763,780],[632,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662],[632],[632,634],[63,632,634],[598,599,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631],[598,599,600],[598,599],[599],[689],[63,192,690],[691],[761],[760],[753],[752,754,756,757,761],[754,755,758],[752,755,758],[754,756,758],[752,753,755,756,757,758,759],[752,758],[754],[70],[106],[107,112,141],[108,119,120,127,138,149],[108,109,119,127],[110,150],[111,112,120,128],[112,138,146],[113,115,119,127],[106,114],[115,116],[119],[117,119],[106,119],[119,120,121,138,149],[119,120,121,134,138,141],[104,107,154],[115,119,122,127,138,149],[119,120,122,123,127,138,146,149],[122,124,138,146,149],[70,71,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156],[119,125],[126,149,154],[115,119,127,138],[128],[129],[106,130],[127,128,131,148,154],[132],[133],[119,134,135],[134,136,150,152],[107,119,138,139,140,141],[107,138,140],[138,139],[141],[142],[106,138],[119,144,145],[144,145],[112,127,138,146],[147],[127,148],[107,122,133,149],[112,150],[138,151],[126,152],[153],[107,112,119,121,130,138,149,152,154],[138,155],[63,67,158,159,160,162,444,489],[63],[63,67,158,159,160,161,425,444,489],[63,67,158,159,161,162,444,489],[63,162,425,426],[63,162,425],[63,67,159,160,161,162,444,489],[63,67,158,160,161,162,444,489],[61,62],[447],[395,458,459],[167,168,170,182,206,321,332,440],[170,201,202,203,205,440],[170,338,340,342,343,345,440,442],[170,204,241,440],[168,170,181,182,188,194,199,320,321,322,331,440,442],[440],[177,183,202,222,317],[170],[163,177,183],[349],[346,347,349],[346,348,440],[122,222,419,437],[122,293,296,312,317,437],[122,265,437],[325],[324,325,326],[324],[69,122,163,170,182,188,194,200,202,206,207,220,221,288,318,319,332,440,444],[167,170,204,241,338,339,344,440,492],[204,492],[167,221,390,440,492],[492],[170,204,205,492],[341,492],[207,320,323,330],[63,395],[133,177,192],[177,192],[63,262],[63,183,192,395],[177,248,262,263,474,481],[247,475,476,477,478,480],[298],[298,299],[181,183,250,251],[183,257,258],[183,252,260],[257],[175,183,250,251,252,253,254,255,256,257,260],[183,250,257,258,259,261],[183,251,253,254],[251,253,256,258],[479],[183],[63,171,468],[63,149],[63,204,239],[63,204,332],[237,242],[63,238,446],[587],[63,122,138,488,489],[63,67,122,158,159,160,161,162,444,488],[122,183],[122,182,187,268,285,327,328,332,387,389,440,441],[220,329],[444],[169],[63,174,177,392,408,410],[133,177,392,407,408,409,491],[401,402,403,404,405,406],[403],[407],[192,356,357,359],[63,183,350,351,352,353,358],[356,358],[354],[355],[63,192,238,446],[63,192,445,446],[63,192,446],[285,286],[286],[122,441,446],[315],[106,314],[177,183,189,191,293,306,310,312,389,392,429,430,437,441],[183,232,254],[293,304,307,312],[63,174,177,293,296,312,315,349,396,397,398,399,400,411,412,413,414,415,416,417,418,492],[174,177,202,293,300,301,302,305,306],[138,183,202,304,311,392,393,437],[308],[122,133,171,183,187,197,229,230,233,285,288,353,387,388,429,440,441,442,444,492],[174,175,177],[293],[106,202,229,230,287,288,289,290,291,292,441],[312],[106,176,177,187,191,227,293,300,301,302,303,304,307,308,309,310,311,430],[122,227,228,300,441,442],[202,230,285,288,293,389,441],[122,440,442],[122,138,437,441,442],[122,133,163,177,182,189,191,194,197,204,224,229,230,231,232,233,268,269,271,274,276,279,280,281,282,284,332,387,389,437,440,441,442],[122,138],[170,171,172,200,437,438,439,444,446,492],[167,168,440],[361],[122,138,149,179,345,349,350,351,352,353,359,360,492],[133,149,163,177,179,191,194,230,269,274,284,285,338,365,366,367,373,376,377,387,389,437,440],[194,200,207,220,230,288,440],[122,149,171,182,191,230,371,437,440],[391],[122,361,374,375,384],[437,440],[290,430],[191,229,332,446],[122,133,169,274,334,338,367,373,376,379,437],[122,207,220,338,380],[170,231,332,382,440,442],[122,149,353,440],[122,204,231,332,333,334,343,361,381,383,440],[69,122,229,386,444,446],[283,387],[122,133,177,180,182,183,189,191,197,206,207,220,230,233,269,271,281,284,285,332,365,366,367,368,370,372,387,389,437,446],[122,138,207,373,378,384,437],[210,211,212,213,214,215,216,217,218,219],[224,275],[277],[275],[277,278],[591],[122,181,182,183,187,188,441],[122,133,169,171,189,193,229,232,233,267,387,437,442,444,446],[122,133,149,173,180,181,191,193,230,385,430,436,441],[300],[301],[183,194,429],[302],[176],[178,190],[122,178,182,189],[185,190],[186],[178,179],[178,234],[178],[180,224,273],[272],[177,179,180],[180,270],[177,179],[229,332],[429],[122,149,189,191,195,229,332,386,389,392,393,394,420,421,424,428,430,437,441],[243,246,248,249,262,263],[63,160,162,192,422,423],[63,160,162,192,422,423,427],[316],[202,223,228,229,293,294,295,296,297,299,312,313,315,318,386,389,440,442],[262],[122,267,437],[267],[122,189,235,264,266,268,386,437,444,446],[243,244,245,246,248,249,262,263,445],[69,122,133,149,178,179,191,197,229,230,233,332,384,385,387,437,440,441,444],[174,177,184],[228,230,362,365],[228,363,431,432,433,434,435],[122,224,440],[122],[227,312],[226],[228,281],[225,227,440],[122,173,228,362,363,364,437,440,441],[63,177,183,261],[63,175],[165,166],[63,171],[63,177,247],[63,69,229,233,444,446],[171,468,469],[63,242],[63,133,149,169,236,238,240,241,446],[177,204,441],[177,369],[63,120,122,133,167,169,242,340,444,445],[63,158,159,160,161,162,444,489],[63,64,65,66,67],[112],[335,336,337],[335],[63,67,122,124,133,157,158,159,160,161,162,163,169,197,202,379,407,442,443,446,489],[454],[456],[460],[588],[462],[464,465,466],[470],[68,448,453,455,457,461,463,467,471,473,483,484,486,490,491,492,493],[472],[482],[592],[238],[485],[106,228,362,363,365,431,432,434,435,487,489],[157],[138,157],[81,85,149],[81,138,149],[76],[78,81,146,149],[127,146],[76,157],[78,81,127,149],[73,74,77,80,107,119,138,149],[73,79],[77,81,107,141,149,157],[107,157],[97,107,157],[75,76,157],[81],[75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,98,99,100,101,102,103],[81,88,89],[79,81,89,90],[80],[73,76,81],[81,85,89,90],[85],[79,81,84,149],[73,78,79,81,85,88],[107,138],[76,81,97,107,154,157],[764,765,766,767,768,769,770,772,773,774,775,776,777,778,779],[764],[764,771],[578],[569],[569,572],[564,567,569,570,571,572,573,574,575,576,577],[503,505,572],[569,570],[504,569,571],[505,507,509,510,511,512],[507,509,511,512],[507,509,511],[504,507,509,510,512],[503,505,506,507,508,509,510,511,512,513,514,564,565,566,567,568],[503,505,506,509],[505,506,509],[509,512],[503,504,506,507,508,510,511,512],[503,504,505,509,569],[509,510,511,512],[511],[515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563],[579],[192,632],[192,633,664,665],[192,632,633,663],[192,633,663],[192,502],[580],[63,502],[725],[734],[731],[192,719],[692],[192,742],[582],[192,581],[490],[192,491,494],[593],[494,495]],"referencedMap":[[810,1],[811,2],[668,3],[669,4],[671,5],[672,6],[673,7],[595,8],[674,9],[676,10],[677,11],[675,12],[678,12],[679,13],[680,14],[499,8],[681,12],[682,13],[683,15],[684,12],[685,12],[501,8],[500,8],[502,16],[581,17],[582,8],[596,18],[686,19],[700,20],[703,21],[706,22],[716,23],[713,24],[718,25],[710,26],[726,27],[723,28],[694,29],[728,30],[735,31],[732,32],[738,33],[741,34],[697,35],[748,36],[745,37],[751,38],[793,39],[790,40],[795,41],[787,42],[801,43],[798,44],[803,45],[804,46],[597,47],[698,48],[701,49],[704,50],[695,51],[724,52],[720,53],[719,54],[721,55],[707,56],[711,57],[714,58],[708,59],[736,60],[729,61],[733,62],[730,63],[688,64],[739,65],[742,66],[746,67],[743,68],[583,69],[796,70],[799,71],[784,72],[788,70],[791,73],[785,74],[749,75],[687,76],[693,77],[699,78],[702,79],[705,80],[715,81],[712,82],[717,81],[709,83],[725,84],[722,85],[805,29],[727,86],[734,87],[731,88],[737,89],[740,90],[696,91],[747,92],[744,93],[750,94],[792,95],[789,96],[794,95],[786,97],[800,98],[797,99],[802,98],[806,100],[670,101],[808,102],[584,103],[585,104],[586,104],[590,105],[594,106],[807,12],[809,8],[497,107],[498,103],[783,108],[781,109],[663,110],[634,111],[652,112],[649,112],[639,112],[638,112],[656,112],[646,112],[650,112],[662,112],[661,112],[659,113],[658,112],[660,112],[635,112],[641,112],[637,112],[655,112],[643,112],[651,112],[640,112],[636,112],[647,112],[653,112],[654,112],[648,112],[644,112],[645,112],[657,112],[642,112],[632,114],[618,115],[615,116],[605,117],[604,115],[623,116],[612,115],[616,115],[628,116],[627,116],[625,115],[624,116],[626,116],[601,115],[607,115],[603,116],[621,116],[608,116],[617,115],[606,115],[602,115],[613,116],[619,115],[620,115],[614,115],[609,115],[611,115],[622,116],[610,117],[690,118],[691,119],[692,120],[762,121],[761,122],[754,123],[758,124],[756,125],[759,126],[757,127],[760,128],[753,129],[752,130],[70,131],[71,131],[106,132],[107,133],[108,134],[109,135],[110,136],[111,137],[112,138],[113,139],[114,140],[115,141],[116,141],[118,142],[117,143],[119,144],[120,145],[121,146],[105,147],[122,148],[123,149],[124,150],[157,151],[125,152],[126,153],[127,154],[128,155],[129,156],[130,157],[131,158],[132,159],[133,160],[134,161],[135,161],[136,162],[138,163],[140,164],[139,165],[141,166],[142,167],[143,168],[144,169],[145,170],[146,171],[147,172],[148,173],[149,174],[150,175],[151,176],[152,177],[153,178],[154,179],[155,180],[161,181],[425,182],[162,183],[160,184],[427,185],[426,186],[158,187],[159,188],[63,189],[422,182],[192,182],[667,182],[448,190],[453,1],[460,191],[443,192],[204,193],[344,194],[347,195],[332,196],[339,197],[318,198],[364,199],[194,200],[346,201],[348,202],[349,203],[420,204],[313,205],[266,206],[326,207],[327,208],[325,209],[320,210],[345,211],[205,212],[391,213],[232,214],[206,215],[233,214],[269,214],[172,214],[342,216],[331,217],[459,218],[398,219],[399,220],[395,221],[400,12],[396,222],[482,223],[481,224],[299,225],[475,226],[397,182],[252,227],[259,228],[261,229],[256,230],[258,231],[260,232],[255,233],[257,234],[480,235],[250,236],[469,237],[472,238],[240,239],[239,240],[238,241],[485,182],[237,242],[588,243],[591,244],[488,182],[489,245],[328,246],[329,247],[330,248],[188,249],[412,182],[170,250],[411,251],[410,252],[407,253],[405,254],[408,255],[406,254],[199,214],[358,256],[359,257],[357,258],[355,259],[356,260],[418,12],[193,12],[447,261],[454,262],[458,263],[287,264],[434,265],[442,266],[314,267],[315,268],[393,269],[416,270],[291,182],[308,271],[419,272],[307,273],[417,274],[414,275],[389,276],[176,277],[289,278],[293,279],[309,280],[312,281],[301,282],[294,283],[441,284],[367,285],[285,286],[173,287],[440,288],[169,289],[360,290],[361,291],[378,292],[377,293],[372,294],[392,295],[376,296],[224,297],[310,298],[230,299],[380,300],[381,301],[383,302],[385,303],[384,304],[374,287],[387,305],[284,306],[373,307],[379,308],[220,309],[276,310],[280,311],[277,312],[279,313],[282,311],[278,312],[592,314],[189,315],[268,316],[437,317],[464,318],[466,319],[430,320],[465,321],[177,322],[174,322],[191,323],[190,324],[186,325],[187,326],[195,327],[223,327],[234,327],[270,328],[235,328],[179,329],[274,330],[273,331],[272,332],[271,333],[180,334],[421,335],[222,336],[429,337],[394,338],[424,339],[428,340],[317,341],[316,342],[297,343],[283,344],[265,345],[267,346],[264,347],[386,348],[185,349],[388,350],[436,351],[225,352],[302,353],[300,354],[227,355],[362,356],[228,357],[363,357],[365,358],[262,359],[183,360],[167,361],[456,182],[468,362],[249,182],[462,12],[248,363],[445,364],[246,362],[470,365],[244,182],[245,182],[243,366],[242,367],[231,368],[306,160],[366,160],[370,369],[254,236],[263,182],[439,249],[446,370],[64,182],[67,371],[68,372],[65,182],[343,373],[338,374],[336,375],[444,376],[455,377],[457,378],[461,379],[589,380],[463,381],[467,382],[495,383],[471,383],[494,384],[473,385],[483,386],[593,387],[484,388],[486,389],[490,390],[493,249],[491,391],[371,392],[88,393],[95,394],[87,393],[102,395],[79,396],[78,397],[101,391],[96,398],[99,399],[81,400],[80,401],[76,402],[75,403],[98,404],[77,405],[82,406],[86,406],[104,407],[103,406],[90,408],[91,409],[93,410],[89,411],[92,412],[97,391],[84,413],[85,414],[94,415],[74,416],[100,417],[780,418],[769,419],[772,420],[771,419],[773,419],[774,420],[775,419],[777,419],[579,421],[573,422],[577,423],[574,423],[570,422],[578,424],[575,425],[576,423],[571,426],[572,427],[566,428],[510,429],[512,430],[511,431],[569,432],[568,433],[567,434],[513,429],[505,435],[509,436],[506,437],[507,438],[515,439],[516,439],[517,439],[518,439],[519,439],[520,439],[521,439],[522,439],[523,439],[524,439],[525,439],[526,439],[527,439],[529,439],[528,439],[530,439],[531,439],[532,439],[533,439],[564,440],[534,439],[535,439],[536,439],[537,439],[538,439],[539,439],[540,439],[541,439],[542,439],[543,439],[544,439],[545,439],[546,439],[548,439],[547,439],[549,439],[550,439],[551,439],[552,439],[553,439],[554,439],[555,439],[556,439],[557,439],[558,439],[559,439],[560,439],[563,439],[561,439],[562,439],[580,441],[633,442],[666,443],[664,444],[665,445]],"exportedModulesMap":[[810,1],[811,2],[668,446],[669,8],[671,8],[672,8],[673,8],[595,8],[674,8],[676,8],[677,8],[675,12],[678,182],[679,8],[680,12],[681,12],[682,12],[683,12],[684,12],[685,12],[581,447],[596,448],[686,8],[700,20],[703,21],[706,22],[716,23],[713,24],[718,25],[710,26],[726,449],[723,28],[694,8],[728,30],[735,450],[732,451],[738,33],[741,34],[697,35],[748,36],[745,37],[751,38],[793,39],[790,40],[795,41],[787,42],[801,43],[798,44],[803,45],[804,8],[597,8],[698,8],[701,8],[704,8],[695,8],[724,8],[720,452],[719,453],[721,8],[711,8],[714,8],[708,8],[736,8],[729,453],[733,8],[730,63],[688,8],[739,8],[742,453],[746,454],[743,8],[583,455],[796,8],[799,8],[788,8],[791,8],[785,8],[749,8],[687,456],[693,8],[699,8],[702,8],[705,8],[715,8],[712,8],[717,8],[709,8],[725,8],[722,8],[805,8],[734,87],[731,88],[737,8],[740,8],[696,8],[747,8],[744,8],[750,8],[792,8],[789,8],[794,8],[786,8],[800,8],[797,8],[802,8],[806,8],[670,8],[808,8],[584,457],[585,457],[586,457],[590,458],[594,459],[807,8],[809,8],[497,460],[498,457],[783,108],[781,109],[663,110],[634,111],[652,112],[649,112],[639,112],[638,112],[656,112],[646,112],[650,112],[662,112],[661,112],[659,113],[658,112],[660,112],[635,112],[641,112],[637,112],[655,112],[643,112],[651,112],[640,112],[636,112],[647,112],[653,112],[654,112],[648,112],[644,112],[645,112],[657,112],[642,112],[632,114],[618,115],[615,116],[605,117],[604,115],[623,116],[612,115],[616,115],[628,116],[627,116],[625,115],[624,116],[626,116],[601,115],[607,115],[603,116],[621,116],[608,116],[617,115],[606,115],[602,115],[613,116],[619,115],[620,115],[614,115],[609,115],[611,115],[622,116],[610,117],[690,118],[691,119],[692,120],[762,121],[761,122],[754,123],[758,124],[756,125],[759,126],[757,127],[760,128],[753,129],[752,130],[70,131],[71,131],[106,132],[107,133],[108,134],[109,135],[110,136],[111,137],[112,138],[113,139],[114,140],[115,141],[116,141],[118,142],[117,143],[119,144],[120,145],[121,146],[105,147],[122,148],[123,149],[124,150],[157,151],[125,152],[126,153],[127,154],[128,155],[129,156],[130,157],[131,158],[132,159],[133,160],[134,161],[135,161],[136,162],[138,163],[140,164],[139,165],[141,166],[142,167],[143,168],[144,169],[145,170],[146,171],[147,172],[148,173],[149,174],[150,175],[151,176],[152,177],[153,178],[154,179],[155,180],[161,181],[425,182],[162,183],[160,184],[427,185],[426,186],[158,187],[159,188],[63,189],[422,182],[192,182],[667,182],[448,190],[453,1],[460,191],[443,192],[204,193],[344,194],[347,195],[332,196],[339,197],[318,198],[364,199],[194,200],[346,201],[348,202],[349,203],[420,204],[313,205],[266,206],[326,207],[327,208],[325,209],[320,210],[345,211],[205,212],[391,213],[232,214],[206,215],[233,214],[269,214],[172,214],[342,216],[331,217],[459,218],[398,219],[399,220],[395,221],[400,12],[396,222],[482,223],[481,224],[299,225],[475,226],[397,182],[252,227],[259,228],[261,229],[256,230],[258,231],[260,232],[255,233],[257,234],[480,235],[250,236],[469,237],[472,238],[240,239],[239,240],[238,241],[485,182],[237,242],[588,243],[591,244],[488,182],[489,245],[328,246],[329,247],[330,248],[188,249],[412,182],[170,250],[411,251],[410,252],[407,253],[405,254],[408,255],[406,254],[199,214],[358,256],[359,257],[357,258],[355,259],[356,260],[418,12],[193,12],[447,261],[454,262],[458,263],[287,264],[434,265],[442,266],[314,267],[315,268],[393,269],[416,270],[291,182],[308,271],[419,272],[307,273],[417,274],[414,275],[389,276],[176,277],[289,278],[293,279],[309,280],[312,281],[301,282],[294,283],[441,284],[367,285],[285,286],[173,287],[440,288],[169,289],[360,290],[361,291],[378,292],[377,293],[372,294],[392,295],[376,296],[224,297],[310,298],[230,299],[380,300],[381,301],[383,302],[385,303],[384,304],[374,287],[387,305],[284,306],[373,307],[379,308],[220,309],[276,310],[280,311],[277,312],[279,313],[282,311],[278,312],[592,314],[189,315],[268,316],[437,317],[464,318],[466,319],[430,320],[465,321],[177,322],[174,322],[191,323],[190,324],[186,325],[187,326],[195,327],[223,327],[234,327],[270,328],[235,328],[179,329],[274,330],[273,331],[272,332],[271,333],[180,334],[421,335],[222,336],[429,337],[394,338],[424,339],[428,340],[317,341],[316,342],[297,343],[283,344],[265,345],[267,346],[264,347],[386,348],[185,349],[388,350],[436,351],[225,352],[302,353],[300,354],[227,355],[362,356],[228,357],[363,357],[365,358],[262,359],[183,360],[167,361],[456,182],[468,362],[249,182],[462,12],[248,363],[445,364],[246,362],[470,365],[244,182],[245,182],[243,366],[242,367],[231,368],[306,160],[366,160],[370,369],[254,236],[263,182],[439,249],[446,370],[64,182],[67,371],[68,372],[65,182],[343,373],[338,374],[336,375],[444,376],[455,377],[457,378],[461,379],[589,380],[463,381],[467,382],[495,383],[471,383],[494,384],[473,385],[483,386],[593,387],[484,388],[486,389],[490,390],[493,249],[491,391],[371,392],[88,393],[95,394],[87,393],[102,395],[79,396],[78,397],[101,391],[96,398],[99,399],[81,400],[80,401],[76,402],[75,403],[98,404],[77,405],[82,406],[86,406],[104,407],[103,406],[90,408],[91,409],[93,410],[89,411],[92,412],[97,391],[84,413],[85,414],[94,415],[74,416],[100,417],[780,418],[769,419],[772,420],[771,419],[773,419],[774,420],[775,419],[777,419],[579,421],[573,422],[577,423],[574,423],[570,422],[578,424],[575,425],[576,423],[571,426],[572,427],[566,428],[510,429],[512,430],[511,431],[569,432],[568,433],[567,434],[513,429],[505,435],[509,436],[506,437],[507,438],[515,439],[516,439],[517,439],[518,439],[519,439],[520,439],[521,439],[522,439],[523,439],[524,439],[525,439],[526,439],[527,439],[529,439],[528,439],[530,439],[531,439],[532,439],[533,439],[564,440],[534,439],[535,439],[536,439],[537,439],[538,439],[539,439],[540,439],[541,439],[542,439],[543,439],[544,439],[545,439],[546,439],[548,439],[547,439],[549,439],[550,439],[551,439],[552,439],[553,439],[554,439],[555,439],[556,439],[557,439],[558,439],[559,439],[560,439],[563,439],[561,439],[562,439],[580,441],[633,442],[666,443],[664,444],[665,445]],"semanticDiagnosticsPerFile":[810,496,811,668,669,671,672,673,595,674,676,677,675,678,679,680,499,681,682,683,684,685,501,500,502,581,582,596,686,700,703,706,716,713,718,710,726,723,694,728,735,732,738,741,697,748,745,751,793,790,795,787,801,798,803,804,597,698,701,704,695,724,720,719,721,707,711,714,708,736,729,733,730,688,739,742,746,743,583,796,799,784,788,791,785,749,687,693,699,702,705,715,712,717,709,725,722,805,727,734,731,737,740,696,747,744,750,792,789,794,786,800,797,802,806,670,808,584,585,586,590,594,807,809,497,498,783,782,781,340,663,634,652,649,639,638,656,646,650,662,661,659,658,660,635,641,637,655,643,651,640,636,647,653,654,648,644,645,657,642,631,629,630,632,598,599,618,615,605,604,623,612,616,628,627,625,624,626,601,607,603,621,608,617,606,602,613,619,620,614,609,611,622,610,600,689,690,691,692,762,761,754,758,756,759,757,760,755,753,752,70,71,106,107,108,109,110,111,112,113,114,115,116,118,117,119,120,121,105,156,122,123,124,157,125,126,127,128,129,130,131,132,133,134,135,136,137,138,140,139,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,161,425,162,160,427,426,158,423,159,61,63,422,192,72,62,667,448,453,460,443,196,204,344,347,319,332,339,221,321,202,318,364,203,194,346,348,349,420,313,266,326,327,325,324,320,345,205,390,391,232,206,233,269,172,342,341,331,438,181,459,398,399,395,477,296,400,396,482,481,476,247,299,298,475,397,252,259,261,251,256,258,260,255,253,257,478,474,480,479,250,469,472,240,239,238,485,237,226,487,588,587,591,488,489,164,328,329,330,168,333,188,163,412,170,411,410,401,402,409,404,407,403,405,408,406,201,198,199,353,358,359,357,355,356,351,418,193,447,454,458,287,286,281,434,442,314,315,393,303,416,291,308,419,304,307,305,417,414,413,415,311,389,176,289,293,309,312,301,294,441,367,285,173,440,169,360,352,361,378,350,377,69,372,197,392,368,182,184,323,376,200,224,310,230,290,375,354,380,381,322,383,385,384,334,374,387,284,373,379,209,213,212,211,216,210,219,218,215,214,217,220,208,276,275,280,277,279,282,278,592,189,268,437,435,464,466,430,465,177,174,207,191,190,186,187,195,223,234,270,235,179,178,274,273,272,271,180,421,222,429,394,424,428,317,316,297,283,265,267,264,386,288,452,185,388,436,295,225,302,300,227,362,431,228,363,450,449,451,433,432,365,292,262,183,241,167,229,456,166,468,249,462,248,445,246,171,470,244,245,236,165,243,242,231,306,366,382,370,369,254,175,263,439,446,64,67,68,65,66,343,338,337,336,335,444,455,457,461,589,463,467,495,471,494,473,483,593,484,486,490,493,492,491,371,763,59,60,10,11,13,12,2,14,15,16,17,18,19,20,21,3,22,4,23,27,24,25,26,28,29,30,5,31,32,33,34,6,38,35,36,37,39,7,40,45,46,41,42,43,44,8,50,47,48,49,51,9,52,53,54,57,55,56,1,58,88,95,87,102,79,78,101,96,99,81,80,76,75,98,77,82,83,86,73,104,103,90,91,93,89,92,97,84,85,94,74,100,780,765,766,767,768,764,769,770,772,771,773,774,775,776,777,778,779,579,573,577,574,570,578,575,576,571,572,566,510,512,565,511,569,568,567,503,513,514,505,509,504,506,507,508,515,516,517,518,519,520,521,522,523,524,525,526,527,529,528,530,531,532,533,564,534,535,536,537,538,539,540,541,542,543,544,545,546,548,547,549,550,551,552,553,554,555,556,557,558,559,560,563,561,562,580,633,666,664,665],"affectedFilesPendingEmit":[811,668,669,671,672,673,595,674,676,677,675,678,679,680,499,681,682,683,684,685,501,500,502,581,582,596,686,700,703,706,716,713,718,710,726,723,694,728,735,732,738,741,697,748,745,751,793,790,795,787,801,798,803,804,597,698,701,704,695,724,720,719,721,707,711,714,708,736,729,733,730,688,739,742,746,743,583,796,799,784,788,791,785,749,687,693,699,702,705,715,712,717,709,725,722,805,727,734,731,737,740,696,747,744,750,792,789,794,786,800,797,802,806,670,808,584,585,586,590,594,807,809,498]},"version":"5.4.5"} \ No newline at end of file diff --git a/prds/new-plugin-arch/GitHub-connector.md b/prds/new-plugin-arch/GitHub-connector.md index 087c452b..429312c9 100644 --- a/prds/new-plugin-arch/GitHub-connector.md +++ b/prds/new-plugin-arch/GitHub-connector.md @@ -204,7 +204,7 @@ So the recommended shape is: Recommended public endpoint: -- `POST /api/webhooks/connectors/github` +- `POST /v1/webhooks/connectors/github` Recommended internal handler split: @@ -222,7 +222,7 @@ If we want subpath-style organization inside the app, we can still do that after Example internal structure: -- public ingress: `POST /api/webhooks/connectors/github` +- public ingress: `POST /v1/webhooks/connectors/github` - internal modules: - `webhooks/connectors/github/push` - `webhooks/connectors/github/installation` @@ -401,7 +401,7 @@ This is the recommended API contract shape around the webhook ingress and async Endpoint: -- `POST /api/webhooks/connectors/github` +- `POST /v1/webhooks/connectors/github` Input: diff --git a/prds/new-plugin-arch/github-connection/GitHub-instructions.md b/prds/new-plugin-arch/github-connection/GitHub-instructions.md new file mode 100644 index 00000000..a375d898 --- /dev/null +++ b/prds/new-plugin-arch/github-connection/GitHub-instructions.md @@ -0,0 +1,320 @@ +# GitHub Instructions + +This document lists exactly what you need to configure for the GitHub App connection flow and where each value should go. + +## Goal + +After this setup: + +1. You open `Integrations` in Den Web. +2. You click `Connect` on GitHub. +3. GitHub shows the GitHub App install flow. +4. GitHub redirects back to OpenWork. +5. OpenWork shows the repositories visible to that installation. +6. You select one repo. + +## Where to put the local server values + +Fill these values in: + +`ee/apps/den-api/.env.local` + +That file is loaded by Den API in this order: + +1. `ee/apps/den-api/.env.local` +2. `ee/apps/den-api/.env` +3. existing shell environment + +## Values you need from GitHub + +You need to create or update a GitHub App and collect these values: + +- GitHub App ID +- GitHub App Client ID +- GitHub App Client Secret +- GitHub App Private Key +- GitHub App Webhook Secret +- GitHub Installation ID +- Test repository ID +- Test repository full name (`owner/repo`) +- Test branch +- Test ref (`refs/heads/`) + +## Exactly where each value goes + +Put these in `ee/apps/den-api/.env.local`: + +```env +# Required Den API basics +PORT=8790 +OPENWORK_DEV_MODE=1 +CORS_ORIGINS=http://localhost:3000,http://localhost:3001,http://localhost:3005 +BETTER_AUTH_URL=http://localhost:8790 +BETTER_AUTH_SECRET= +DEN_DB_ENCRYPTION_KEY= +DATABASE_URL=mysql://root:password@127.0.0.1:3306/den + +# Existing user auth GitHub values. These are separate from the connector app. +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= + +# GitHub connector app values +GITHUB_CONNECTOR_APP_ID= +GITHUB_CONNECTOR_APP_CLIENT_ID= +GITHUB_CONNECTOR_APP_CLIENT_SECRET= +GITHUB_CONNECTOR_APP_PRIVATE_KEY= +GITHUB_CONNECTOR_APP_WEBHOOK_SECRET= + +# Handy local test values +GITHUB_TEST_INSTALLATION_ID= +GITHUB_TEST_REPOSITORY_ID= +GITHUB_TEST_REPOSITORY_FULL_NAME= +GITHUB_TEST_BRANCH=main +GITHUB_TEST_REF=refs/heads/main +``` + +## Important private key formatting + +For `GITHUB_CONNECTOR_APP_PRIVATE_KEY`, paste the private key as one line with `\n` escapes. + +Example: + +```env +GITHUB_CONNECTOR_APP_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nMIIEv...\n-----END PRIVATE KEY----- +``` + +Do not paste raw multi-line PEM text directly unless you know the env loader path is handling it the way you expect. + +## GitHub App setup + +Go to: + +`GitHub -> Settings -> Developer settings -> GitHub Apps -> New GitHub App` + +Use these settings. + +### Basic info + +- App name: choose any unique name, for example `OpenWork Den Local` +- Homepage URL: use your local/public Den Web URL + - local example: `http://localhost:3005` + - public example: your deployed Den Web URL +- Description: optional + +### Webhooks + +- Webhooks: enabled +- Webhook URL: + - for webhook deliveries themselves, use: + - `https:///api/den/v1/webhooks/connectors/github` + - or the public Den API URL if you are not proxying through Den Web +- Webhook secret: + - set this to the same value you put in `GITHUB_CONNECTOR_APP_WEBHOOK_SECRET` + +## Important: Setup URL vs Webhook URL + +GitHub App has two different relevant URLs: + +1. `Setup URL` +2. `Webhook URL` + +### Setup URL + +This is where GitHub sends the user's browser back after installation. + +This should be an actual Den Web page, not a den-api callback route. + +Set it to: + +`https:///dashboard/integrations/github` + +GitHub will append values like: + +- `installation_id` +- `setup_action` +- `state` + +Den Web reads those query params and then calls Den API to validate the signed state and load the repositories for that installation. + +Do not point the Setup URL at Den API for this flow. + +### Webhook URL + +This is where GitHub sends push/install webhook events. + +Set it to: + +`https:///api/den/v1/webhooks/connectors/github` + +If your public entrypoint is Den API directly, use: + +`https:///v1/webhooks/connectors/github` + +## Repository permissions + +Set these GitHub App repository permissions: + +- `Metadata`: `Read-only` +- `Contents`: `Read-only` + +That is the minimum needed for the current repo-listing and validation flow. + +## Organization permissions + +None are strictly required for the current slice. + +## Subscribe to these webhook events + +Enable these events: + +- `Push` +- `Installation` +- `Installation target` +- `Repository` + +## Install the app + +After creating the app: + +1. Generate a client secret. +2. Generate a private key. +3. Install the app on the user or org that owns the repo you want to test. +4. Grant access to the repo you want to test. + +## How to collect the values after setup + +### App ID + +From the GitHub App settings page. + +Put in: + +`GITHUB_CONNECTOR_APP_ID` + +### Client ID + +From the GitHub App settings page. + +Put in: + +`GITHUB_CONNECTOR_APP_CLIENT_ID` + +### Client Secret + +Generate from the GitHub App settings page. + +Put in: + +`GITHUB_CONNECTOR_APP_CLIENT_SECRET` + +### Private Key + +Generate from the GitHub App settings page. + +Put in: + +`GITHUB_CONNECTOR_APP_PRIVATE_KEY` + +### Webhook Secret + +From the GitHub App webhook configuration. + +Put in: + +`GITHUB_CONNECTOR_APP_WEBHOOK_SECRET` + +### Installation ID + +You can get it from the GitHub install redirect/callback, or via `gh`: + +```bash +gh api repos///installation --jq '.id' +``` + +Put in: + +`GITHUB_TEST_INSTALLATION_ID` + +### Repository ID + +```bash +gh api repos// --jq '.id' +``` + +Put in: + +`GITHUB_TEST_REPOSITORY_ID` + +### Repository full name + +Format: + +`owner/repo` + +Put in: + +`GITHUB_TEST_REPOSITORY_FULL_NAME` + +### Branch and ref + +Examples: + +- branch: `main` +- ref: `refs/heads/main` + +Put in: + +- `GITHUB_TEST_BRANCH` +- `GITHUB_TEST_REF` + +## Local run commands + +From the repo root: + +```bash +pnpm --filter @openwork-ee/den-api dev +pnpm --filter @openwork-ee/den-web dev +``` + +Den Web default local URL in this repo is: + +`http://localhost:3005` + +Den API default local URL in this repo is: + +`http://localhost:8790` + +## Public URL requirement + +GitHub must be able to reach your callback and webhook endpoints. + +That means for real testing you need a public URL, usually via a tunnel or deployed environment. + +Examples: + +- `ngrok` +- `cloudflared` +- deployed Den Web / Den API host + +## What to do after env is filled + +1. Start Den API. +2. Start Den Web. +3. Confirm the GitHub App `Setup URL` points to the Den Web GitHub setup page. +4. Confirm the GitHub App `Webhook URL` points to the webhook endpoint. +5. Go to Den Web `Integrations`. +6. Click `Connect` on GitHub. +7. Finish the GitHub App install flow. +8. GitHub should return to `/dashboard/integrations/github` in Den Web. +9. Den Web should show the repository selection screen. + +## Current scope note + +This phase currently gets you to: + +- GitHub App install redirect +- return to OpenWork +- repository list +- selecting one repo to create a connector instance + +It does not yet complete full content ingestion from the selected repository. diff --git a/prds/new-plugin-arch/github-connection/connectors.md b/prds/new-plugin-arch/github-connection/connectors.md new file mode 100644 index 00000000..a2a67220 --- /dev/null +++ b/prds/new-plugin-arch/github-connection/connectors.md @@ -0,0 +1,1105 @@ +# Connectors — API surface, FS convention, and lifecycle + +Status: draft +Owner: src-opn +Scope: OpenWork server + Den web + OpenCode integration +Related: `/o/:slug/dashboard/integrations` (Den), `/o/:slug/dashboard/plugins` (Den) + +--- + +## TL;DR + +- A **connector** is an OAuth-backed transport that reads a filesystem tree from an external system (GitHub, Bitbucket) and hands it to a shared **ingester**. +- The ingester decomposes the tree into **Primitives** (skill, agent, command, MCP server, OpenCode plugin code). +- **Bundles** (what the product calls "plugins") are DB-level groupings of primitives — they are **virtual** as far as OpenCode is concerned. OpenCode never sees a `plugin.json`. +- **Installing a bundle into a workspace** means writing each member primitive to its **native `.opencode/` path** via the OpenWork server's existing per-workspace endpoints. The `reload-watcher` then propagates to running sessions. +- `.opencode/` is the **source of truth** on disk. Our remote DB is an **index** that bundles + surfaces primitives across the org. A future "local DB triggered via a skill" will invert the index relationship locally; the server schema is designed to accommodate that. + +### Reader map + +| Looking for… | Jump to | +|---|---| +| What does OpenCode actually load from disk? | [OpenCode interpretation](#opencode-interpretation--what-opencode-actually-sees) | +| End-to-end flow (create → index → denominate → install) | [Four-phase lifecycle](#four-phase-lifecycle) | +| Exact types to implement | [Typed schemas](#typed-schemas) | +| Every API endpoint | [API endpoints](#api-endpoints) | +| How each step actually runs over the wire | [Sequence flows](#sequence-flows) | +| How a user authors a plugin without a repo | [Authoring flow](#authoring-flow-creating-primitives--bundles-in-app) | +| How we close the loop back to the ecosystem | [Publish / export](#publish--export-sharing-authored-bundles) | +| From inside an agent / shell | [CLI surface](#cli-surface) | +| What's shipped when | [Rollout plan](#rollout-plan) | +| How we know it works | [Test strategy](#test-strategy) | +| What's still undecided | [Open questions](#open-questions) | + +## Mental model + +Three nouns, kept strictly distinct: + +| Noun | What it is | Who creates it | Shape | +|---|---|---|---| +| **ConnectorType** | The adapter class itself (e.g., "github", "bitbucket", "npm", "local"). Code in our server. | Us — v1 is in-house. v2 could accept plugin-authored adapters. | Code | +| **Integration** | An org's authorized grant to one ConnectorType + their selected sources (e.g., "GitHub account `different-ai`, 3 repos"). Persisted, scoped to an org. | End user via OAuth flow. | DB row | +| **Bundle** ("plugin" in UI copy) | A curated collection of primitives (skills, agents, commands, MCPs, code hooks) that can be installed as a unit. | Either: imported from a connector source (e.g., `.claude-plugin/marketplace.json` in a repo), or authored directly in the app. | DB row + BundleMembers | + +The UI already shipped on `/integrations` (PRs #1472, #1475) drives ConnectorType + Integration. The `/plugins` UI (PR #1472) will drive Bundle browsing/detail. **A new page is needed for workspace installation** (Phase 4 below). + +## OpenCode interpretation — what OpenCode actually sees + +OpenCode is the execution layer. It **only** reads a workspace directory containing `opencode.json{c}` and an optional `.opencode/`. Any product concept that OpenCode does not see on disk is invisible to the runtime. + +### OpenCode vs Claude Code — compatibility table + +| Primitive | Claude Code convention | OpenCode convention | Auto-portable? | +|---|---|---|---| +| Plugin manifest | `.claude-plugin/plugin.json` | **None** (no manifest file) | ❌ — OpenWork-only DB concept | +| Marketplace catalog | `.claude-plugin/marketplace.json` | **None** | ❌ — OpenWork-only DB concept | +| Skills | `.claude/skills//SKILL.md` (YAML + md) | `.opencode/skill[s]//SKILL.md` **and** `.claude/skills/**/SKILL.md` **and** `.agents/skills/**/SKILL.md` (all three scanned natively unless `OPENCODE_DISABLE_EXTERNAL_SKILLS` is set) | ✅ **drop-in** | +| Agents | `.claude/agents/.md` | `.opencode/agent[s]/.md` — file shape identical, path differs | ⚠️ re-home path, content unchanged | +| Commands | `.claude/commands/.md` | `.opencode/command[s]/.md` — file shape identical (same `$ARGUMENTS` templating), path differs | ⚠️ re-home path, content unchanged | +| MCP servers | `.mcp.json` (project root) | `opencode.json{c}` → `mcp{}` key | ⚠️ expand into JSONC | +| Hooks | `.claude/hooks/` + `hooks.json` (declarative events) | **Code-only**: JS/TS module in `.opencode/plugin[s]/` exporting a `Hooks` interface | ❌ Claude JSON hooks **cannot** be auto-ported | +| Plugins | Source-distributed bundle | `opencode.json` → `plugin[]` (npm spec) **or** `.opencode/plugin[s]/.{ts,js}` (file URL, auto-installed via `bun`) | Different semantic — OpenCode plugins are code; Claude "plugins" are catalog entries | + +### What this forces on our design + +1. **"Plugin" in Claude terminology ≠ "Plugin" in OpenCode terminology.** Our internal name for the Claude concept is **Bundle**. The UI can still say "Plugins" for user familiarity, but the codebase must not conflate them. +2. **The ingester is also a rehoming step.** A Claude-style `.claude-plugin/*` tree becomes OpenCode-native paths at materialization time. This is where the `.claude/agents/ → .opencode/agents/` move happens. +3. **Skill compatibility is a freebie.** SKILL.md is portable; we parse it once and write it back unchanged on install. +4. **Hooks need a policy.** See [Hooks strategy](#hooks-strategy). + +### Real examples we target + +Skill (frontmatter + body, OpenCode reads both `.opencode/skills/` and `.claude/skills/`): + +```markdown +--- +name: skill-creator +description: Create new OpenCode skills with the standard scaffold. +--- + +Skill creator helps create other skills that are self-buildable. +``` + +Command (`.opencode/commands/release.md`): + +```markdown +--- +description: Run the OpenWork release flow +--- + +You are running the OpenWork release flow in this repo. +Arguments: `$ARGUMENTS` +``` + +Agent (`.opencode/agent/triage.md`): + +```markdown +--- +mode: primary +model: opencode/claude-haiku-4-5 +tools: + "*": false + "github-triage": true +--- + +You are a triage agent responsible for triaging github issues. +``` + +MCP (inside `opencode.jsonc`): + +```jsonc +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "control-chrome": { + "type": "local", + "command": ["chrome-devtools-mcp"] + } + }, + "plugin": ["opencode-scheduler"] +} +``` + +## Four-phase lifecycle + +Split the problem into four phases. Each phase has its own API surface, UI, and failure modes. The earlier phases can **assume** later ones exist; later phases **depend on** earlier ones having produced data. + +### Phase 1 — Ingest (connectors) + +> Assume: primitives already exist somewhere (a GitHub repo maintained by a human, or a `.claude-plugin/marketplace.json` tree, or an npm package). We are not authoring them here; we are **bringing them in**. + +Actors: connector adapter + ingester. + +Flow: + +1. User on `/integrations` authorizes GitHub/Bitbucket (OAuth). +2. User selects repos; we create `PluginSource` rows. +3. Ingester fetches the filesystem for each source. Supported shapes: + - `.claude-plugin/marketplace.json` → catalog of bundles in this repo + - `.claude-plugin/plugin.json` → single bundle at repo root + - `opencode.json{c}` + `.opencode/*` → native OpenCode workspace exported as a single "workspace bundle" + - Bare `skills//SKILL.md` tree with no manifest → one inferred bundle of skills +4. Ingester walks the tree and upserts **Primitives** into the DB (Phase 2). +5. Ingester optionally upserts **Bundles** and **BundleMembers** (Phase 3) if a manifest was present. If not, a user can create bundles manually in Phase 3. + +Failure modes: invalid YAML frontmatter, forbidden `../` paths, hooks written in Claude JSON (flagged, see Hooks strategy), missing required fields. All surface as non-fatal `SyncEvent`s on the source. + +### Phase 2 — Primitive index (DB) + +> Assume: Phase 1 has produced raw content. Phase 2 stores each primitive as a first-class row so it can be searched, bundled, mutated, and projected to a workspace. + +Every primitive gets a row with the same envelope, differing only by `kind`: + +``` +Primitive + id + org_id + kind # "skill" | "agent" | "command" | "mcp_server" | "plugin_code" + name # e.g. "skill-creator" (unique within (org_id, kind, origin)) + content # raw source: SKILL.md body, agent .md, command .md, TS code, or JSON for mcp + content_hash # sha256 for change detection + metadata # parsed frontmatter or decoded JSON + origin # tagged union — see below + validation_status # "ok" | "warn" | "error" + validation_messages[] + created_at + updated_at +``` + +`origin` tagged union: + +``` + | { type: "connector", plugin_source_id, path_in_repo, commit_sha } + | { type: "authored", author_org_membership_id } + | { type: "local_mirror", workspace_id, local_revision } # future: local-DB SoT +``` + +Primitives are **org-scoped**, not workspace-scoped. The same `Primitive` row can be installed into many workspaces. + +### Phase 3 — Denomination (Bundle) + +> Assume: Phase 2 has given us primitives. Phase 3 groups them into shippable units. + +``` +Bundle + id + org_id + slug # "openwork-release-kit" + name # "OpenWork Release Kit" + description + version # "2.3.1" + icon # emoji or URL + category # display-only + origin # same tagged union as Primitive + published_at + +BundleMember + bundle_id + primitive_id + ordinal # for list display +``` + +Bundle creation paths: + +1. **Imported**: a `.claude-plugin/marketplace.json` or `plugin.json` produces one Bundle row per plugin in the catalog, with BundleMembers derived from the plugin's skill/agent/command/mcp/hook entries. +2. **Authored in the app**: user picks existing Primitives and drags them into a new Bundle. No connector required. +3. **Generated by another tool** (future): an agent creates a Bundle representing its own capabilities. + +The word "Bundle" lives in the schema; the UI can continue to say "Plugin" for user familiarity. + +### Phase 4 — Install (materialize into a workspace) + +> Assume: Phases 1–3 have produced a Bundle. Phase 4 projects its primitives onto a specific OpenCode workspace so the OpenCode runtime loads them. + +``` +WorkspaceInstallation + id + org_id + bundle_id + workspace_id # the OpenWork workspace (worker + path) + scope # "org" | "user" | "workspace" — affects conflict resolution + status # "pending" | "materializing" | "applied" | "error" | "uninstalled" + bundle_version_at_install + applied_primitive_digests # [{ primitive_id, target_path, content_hash }] + installed_at + updated_at + error # nullable; populated on failure +``` + +Materialization steps (all executed server-side against the OpenWork server API — not direct FS access): + +| Primitive kind | Target path in workspace | Mutation endpoint | +|---|---|---| +| `skill` | `.opencode/skills//SKILL.md` | `POST /workspace/:id/skills` | +| `agent` | `.opencode/agents/.md` | `POST /workspace/:id/files/content` *(no dedicated agents endpoint today — flagged in Open Questions)* | +| `command` | `.opencode/commands/.md` | `POST /workspace/:id/commands` | +| `mcp_server` | merged into `opencode.jsonc` → `mcp[name]` | `POST /workspace/:id/mcp` | +| `plugin_code` | `.opencode/plugins/.ts` **and/or** `opencode.jsonc` → `plugin[]` | `POST /workspace/:id/plugins` | + +The `reload-watcher` on the server picks up each file write and emits `ReloadEvent`s keyed by `workspaceId`. OpenCode-running sessions pick up skills/commands/agents hot; plugin code and MCP changes require a new session (reload-watcher already handles this via `openwork.json` → `reload.auto`). + +**Uninstall**: the server looks up `applied_primitive_digests` and deletes each target. Content-hash check prevents stomping on user-edited files — if the current on-disk hash differs, the file is left alone and a `drift` warning is stored on the installation row. + +**Conflict resolution** (two bundles declare the same skill name): + +- Scope precedence: `workspace > user > org`. A lower-scope install overwrites a higher-scope one and restores on uninstall. +- Same-scope collision: installation fails with a clear error; the user picks which Bundle owns the name. + +## Data model (updated) + +``` +Organization + └─ Integration 1 row per (org × connector_type × provider_account) + ├─ connector_type "github" | "bitbucket" | … + ├─ account { id, name, kind: "user" | "org" } + ├─ credentials_encrypted + └─ PluginSource[] 1 row per attached repo / subdir / ref + ├─ locator { repo, ref?, path?, sha? } + ├─ last_sync_at / _status + └─ discovered: Primitive[], Bundle[] + +Primitive the atoms — org-scoped + kind, name, content, hash, metadata, origin, validation_status + +Bundle the grouping — org-scoped (Phase 3 output) + ├─ BundleMember[] → Primitive + +WorkspaceInstallation Phase 4: projection onto a workspace + ├─ bundle_id + ├─ workspace_id + └─ applied_primitive_digests[] +``` + +**Why this shape:** + +- Primitives and Bundles are cleanly separated — a primitive can live outside a bundle (useful for org-authored skills that aren't shipped), and the same primitive can belong to many bundles. +- The `origin` tagged union lets the same table model both connector-imported and app-authored primitives with no special cases downstream. +- `WorkspaceInstallation.applied_primitive_digests` gives clean uninstall + drift detection. +- The future "local DB triggered via a skill" (§ [Local-DB future](#local-db-future)) slots in by adding `{ type: "local_mirror", … }` as a third `origin` variant without touching the rest of the schema. + +## Typed schemas + +TypeScript-style definitions for the core entities. These are the canonical shapes shared across the DB ORM, the API layer, and (eventually) the Den web SDK. Field names are camelCase in the API and snake_case in the DB per existing Den conventions — listed here in camelCase. + +### Shared primitives + +```ts +type Iso8601 = string; // "2026-04-17T11:22:33Z" +type Sha256 = string; // lowercased hex +type UUID = string; // v7 +type KebabCase = string; // ^[a-z][a-z0-9-]*$ + +type PrimitiveKind = + | "skill" + | "agent" + | "command" + | "mcp_server" + | "plugin_code" + | "hook"; // claude-json hooks; warn-only in v1 + +type ConnectorType = "github" | "bitbucket"; // extensible + +type InstallScope = "org" | "user" | "workspace"; + +type ValidationStatus = "ok" | "warn" | "error"; +type ValidationMessage = { code: string; message: string; path?: string }; + +type Origin = + | { type: "connector"; pluginSourceId: UUID; pathInRepo: string; commitSha: string } + | { type: "authored"; authorOrgMembershipId: UUID } + | { type: "local_mirror"; workspaceId: UUID; localRevision: number }; // future +``` + +### Integration + PluginSource + +```ts +type Integration = { + id: UUID; + orgId: UUID; + connectorType: ConnectorType; + account: { id: string; name: string; kind: "user" | "org"; avatarUrl?: string }; + credentialsEncrypted: string; // opaque; decrypted server-side only + tokenExpiresAt: Iso8601 | null; + createdAt: Iso8601; + updatedAt: Iso8601; +}; + +type PluginSourceLocator = + | { kind: "repo"; repo: string; ref?: string; sha?: string } // "owner/repo" + | { kind: "subdir"; repo: string; path: string; ref?: string; sha?: string } + | { kind: "npm"; pkg: string; version?: string; registry?: string }; // future + +type SyncStatus = "pending" | "ok" | "error"; + +type PluginSource = { + id: UUID; + orgId: UUID; + integrationId: UUID; + locator: PluginSourceLocator; + webhookId: string | null; // provider-side webhook identifier + lastSyncAt: Iso8601 | null; + lastSyncStatus: SyncStatus; + lastSyncError: string | null; + createdAt: Iso8601; + updatedAt: Iso8601; +}; +``` + +### Primitive (kind-discriminated) + +```ts +type Primitive = { + id: UUID; + orgId: UUID; + kind: PrimitiveKind; + name: KebabCase; // unique within (orgId, kind, origin-key) + content: string; // raw md/ts/json + contentHash: Sha256; + metadata: PrimitiveMetadata; // discriminated on kind + origin: Origin; + validationStatus: ValidationStatus; + validationMessages: ValidationMessage[]; + deletedAt: Iso8601 | null; // soft-delete so re-ingest can restore + createdAt: Iso8601; + updatedAt: Iso8601; +}; + +type PrimitiveMetadata = + | { kind: "skill"; description: string; license?: string; tags?: string[] } + | { kind: "agent"; description?: string; model?: string; mode?: "primary" | "subagent" | "all"; tools?: Record; color?: string } + | { kind: "command"; description?: string; agent?: string; model?: string; argumentsHint?: string[] } + | { kind: "mcp_server"; transport: "local" | "remote"; command?: string[]; url?: string; env?: Record } + | { kind: "plugin_code"; language: "ts" | "js"; entryFile: string } + | { kind: "hook"; event: string; matcher?: string; command: string }; // claude-json shape +``` + +### Bundle + BundleMember + +```ts +type Bundle = { + id: UUID; + orgId: UUID; + slug: KebabCase; // unique within org + name: string; + description: string; + version: string; // semver + icon?: string; // emoji or https URL + category?: string; + origin: Origin; + publishedAt: Iso8601 | null; + deletedAt: Iso8601 | null; + createdAt: Iso8601; + updatedAt: Iso8601; +}; + +type BundleMember = { + bundleId: UUID; + primitiveId: UUID; + ordinal: number; // display order +}; +``` + +### WorkspaceInstallation + +```ts +type InstallationStatus = + | "pending" | "materializing" | "applied" | "error" | "uninstalled"; + +type AppliedPrimitiveDigest = { + primitiveId: UUID; + kind: PrimitiveKind; + targetPath: string; // e.g. ".opencode/skills/release-prep/SKILL.md" + contentHashAtWrite: Sha256; // for drift detection on read-back +}; + +type WorkspaceInstallation = { + id: UUID; + orgId: UUID; + bundleId: UUID; + workspaceId: UUID; + scope: InstallScope; + status: InstallationStatus; + bundleVersionAtInstall: string; + appliedPrimitiveDigests: AppliedPrimitiveDigest[]; + installedAt: Iso8601; + updatedAt: Iso8601; + error: { code: string; message: string } | null; +}; +``` + +### API request/response shapes (examples) + +```ts +// POST /v1/orgs/:orgId/integrations/authorize +type AuthorizeRequest = { connectorType: ConnectorType; redirectAfter?: string }; +type AuthorizeResponse = { redirectUrl: string; state: string }; + +// POST /v1/orgs/:orgId/integrations/:id/plugin-sources +type AttachSourcesRequest = { sources: PluginSourceLocator[] }; +type AttachSourcesResponse = { sources: PluginSource[] }; + +// POST /v1/orgs/:orgId/bundles +type CreateBundleRequest = { + slug: KebabCase; + name: string; + description: string; + version: string; + icon?: string; + category?: string; + memberPrimitiveIds: UUID[]; // primitives must already exist in the org +}; +type CreateBundleResponse = Bundle & { members: BundleMember[] }; + +// POST /v1/workspaces/:workspaceId/installations +type CreateInstallationRequest = { bundleId: UUID; scope: InstallScope }; +type CreateInstallationResponse = WorkspaceInstallation; + +// GET /v1/workspaces/:workspaceId/installations/:id/status +type InstallationStatusResponse = { + status: InstallationStatus; + progress: Array<{ + primitiveId: UUID; + name: string; + targetPath: string; + state: "pending" | "writing" | "ok" | "error"; + error?: string; + }>; +}; +``` + +## Source-of-truth policy + +`.opencode/` on a worker's disk is the **canonical** state of what OpenCode actually loads. The remote DB is an **index** that: + +1. Knows which primitives exist across the org. +2. Knows which bundles compose which primitives. +3. Knows which workspaces have which bundles installed. +4. Records `applied_primitive_digests` so it can diff against disk and detect drift. + +When disk and DB disagree, disk wins for OpenCode loading. The DB updates its `validation_status` and drift markers but does not force a rewrite — user edits on disk are respected. + +### Local-DB future + +Long-term direction (informational — not in v1 scope): + +- A tiny **OpenWork skill** running inside the user's workspace maintains a **local SQLite** DB that mirrors the subset of the remote DB relevant to that workspace (primitives + installed bundles). +- This local DB becomes the operational source of truth for the workspace; the remote DB becomes a sync target and cross-workspace index. +- Connectors, installers, and authoring tools all read/write the local DB; a background sync skill reconciles with the remote. +- Benefits: works offline, no network trip for "what's installed in this workspace", enables per-workspace forks of a bundle without polluting org-wide state. +- Schema preparation: the `origin.local_mirror` variant on `Primitive` is already designed for this. + +For v1 we ship the remote DB only. The API contracts below do not change when the local DB lands — the local DB speaks the same schema and exposes the same endpoints over a UNIX socket or the existing server instance. + +## API endpoints + +Grouped by concern. All org-scoped routes live under `/v1/orgs/:orgId/...` consistent with the existing Den API (`/v1/orgs/:orgId/skills`, `/v1/orgs/:orgId/skill-hubs`). + +### 1. Connector-type registry (read-only catalog) + +| Method | Path | Purpose | +|---|---|---| +| `GET` | `/v1/connector-types` | List adapters the server supports, with display metadata, supported auth flow (`oauth2` / `token` / `local`), and required scopes. Powers the list of cards on `/integrations`. | +| `GET` | `/v1/connector-types/:type` | Detail for one adapter. | + +### 2. Integrations (the OAuth dance) + +| Method | Path | Purpose | +|---|---|---| +| `GET` | `/v1/orgs/:orgId/integrations` | List this org's integrations, including account + repo counts. Replaces the mock `useIntegrations()`. | +| `POST` | `/v1/orgs/:orgId/integrations/authorize` | Body: `{ connectorType }`. Returns `{ redirectUrl, state }`. Client navigates to the provider. | +| `GET` | `/v1/oauth/:type/callback` | Provider hits this with `?code&state`. Server exchanges for tokens, resolves the account, creates the Integration row, redirects to the app (`/o/:slug/dashboard/integrations?success=...`). | +| `GET` | `/v1/orgs/:orgId/integrations/:id` | Detail for one integration. | +| `POST` | `/v1/orgs/:orgId/integrations/:id/refresh-token` | Explicit refresh (mostly internal). | +| `DELETE` | `/v1/orgs/:orgId/integrations/:id` | Disconnect; revoke at provider if possible; cascade-delete `PluginSource`s and their derived Primitives/Bundles. | + +### 3. Account + repo enumeration (populates the wizard steps 2 & 3) + +| Method | Path | Purpose | +|---|---|---| +| `GET` | `/v1/orgs/:orgId/integrations/:id/accounts` | Proxy call to the provider for user + orgs/workspaces the grant can see. | +| `GET` | `/v1/orgs/:orgId/integrations/:id/accounts/:accountId/repos?q=&cursor=` | Paginated repo list, optionally filtered. Each repo flagged `hasPluginManifest: boolean` (server peeks for `.claude-plugin/plugin.json` or `.claude-plugin/marketplace.json`). | + +### 4. Plugin sources (attaching a repo to the Integration) + +| Method | Path | Purpose | +|---|---|---| +| `GET` | `/v1/orgs/:orgId/plugin-sources` | All sources attached across integrations. | +| `POST` | `/v1/orgs/:orgId/integrations/:id/plugin-sources` | Body: `[{ repo, ref?, path?, sha? }, …]`. Server registers webhook, triggers initial sync. | +| `DELETE` | `/v1/orgs/:orgId/plugin-sources/:sourceId` | Detach; cascade-delete the Primitives/Bundles it produced. | +| `POST` | `/v1/orgs/:orgId/plugin-sources/:sourceId/sync` | Force a refresh. Re-reads the tree, upserts Primitives + Bundles. | +| `GET` | `/v1/orgs/:orgId/plugin-sources/:sourceId/events` | Sync history (SSE or paginated log). | + +### 5. Primitives (Phase 2 — org-scoped atoms) + +Each primitive kind gets a resource. Shared shape, different payload. Internally backed by one `primitives` table with a `kind` discriminator. + +| Method | Path | Purpose | +|---|---|---| +| `GET` | `/v1/orgs/:orgId/primitives?kind=&originType=&q=&cursor=` | Combined index across all kinds. Powers the "All Skills / All Hooks / All MCPs" tabs on `/plugins`. | +| `GET` | `/v1/orgs/:orgId/primitives/:id` | Detail for any primitive kind. | +| `POST` | `/v1/orgs/:orgId/primitives` | Author a new primitive in the app. Body: `{ kind, name, content, metadata? }`. `origin` is set server-side to `{ type: "authored", author_org_membership_id }`. | +| `PATCH` | `/v1/orgs/:orgId/primitives/:id` | Edit content/metadata of an authored primitive. Connector-sourced primitives are read-only (return 409). | +| `DELETE` | `/v1/orgs/:orgId/primitives/:id` | Delete an authored primitive. Connector-sourced primitives get hidden (`deleted_at` set) so re-ingest can restore them. | + +Convenience kind-scoped views (optional; all read from the same table): + +- `GET /v1/orgs/:orgId/skills` +- `GET /v1/orgs/:orgId/agents` +- `GET /v1/orgs/:orgId/commands` +- `GET /v1/orgs/:orgId/mcp-servers` +- `GET /v1/orgs/:orgId/plugin-code` *(OpenCode code plugins, distinct from our Bundle concept)* + +The existing `GET /v1/orgs/:orgId/skills` endpoint in Den API stays and grows `originType` / `pluginSourceId` / `bundleId` filter params. + +### 6. Bundles (Phase 3 — denomination) + +| Method | Path | Purpose | +|---|---|---| +| `GET` | `/v1/orgs/:orgId/bundles?q=&category=&originType=` | All bundles in the org. Replaces mock `usePlugins()`. | +| `GET` | `/v1/orgs/:orgId/bundles/:id` | Bundle detail with embedded BundleMembers → resolved Primitives. Replaces mock `usePlugin(id)`. | +| `POST` | `/v1/orgs/:orgId/bundles` | Author a new bundle. Body: `{ slug, name, description, version, icon?, category?, memberPrimitiveIds: [...] }`. | +| `PATCH` | `/v1/orgs/:orgId/bundles/:id` | Edit authored bundle metadata. Imported bundles are read-only. | +| `DELETE` | `/v1/orgs/:orgId/bundles/:id` | Delete an authored bundle. Imported bundles hidden (re-ingest restores). | +| `POST` | `/v1/orgs/:orgId/bundles/:id/members` | Body: `{ primitiveId, ordinal? }`. Add a primitive to the bundle. | +| `DELETE` | `/v1/orgs/:orgId/bundles/:id/members/:primitiveId` | Remove a primitive from the bundle. | +| `POST` | `/v1/orgs/:orgId/bundles/:id/members/reorder` | Body: `[primitiveId, …]`. Reorders the bundle's member list. | + +### 7. Workspace installations (Phase 4 — projection) + +Workspace scope lives under `/v1/workspaces/:workspaceId/…` — mirrors the existing OpenWork server shape (`/workspace/:id/skills`, `/workspace/:id/commands`, etc.). The installation endpoints below are **orchestrators** that internally fan out to those existing workspace-level endpoints. No new filesystem primitives required. + +| Method | Path | Purpose | +|---|---|---| +| `GET` | `/v1/workspaces/:workspaceId/installations` | What bundles are installed in this workspace, with status + drift info. | +| `GET` | `/v1/workspaces/:workspaceId/installations/:id` | Detail: list of applied primitives with their target paths and content hashes. | +| `POST` | `/v1/workspaces/:workspaceId/installations` | Body: `{ bundleId, scope }`. Creates the row, begins materializing. Returns `{ id, status: "materializing" }`. | +| `GET` | `/v1/workspaces/:workspaceId/installations/:id/status` | Poll endpoint (or SSE). Reports per-primitive progress: `pending` → `writing` → `ok`/`error`. | +| `POST` | `/v1/workspaces/:workspaceId/installations/:id/reapply` | Re-run materialization against the current Bundle version. Useful after drift. | +| `DELETE` | `/v1/workspaces/:workspaceId/installations/:id` | Uninstall — reverses writes using `applied_primitive_digests`. Files that no longer match their recorded digest are left alone (drift safe) and reported. | + +Materialization engine (server-side only — not a public endpoint) maps each primitive kind to the existing per-workspace endpoint. See [materialization table](#four-phase-lifecycle) above. + +### 8. Webhooks (provider → us) + +| Method | Path | Purpose | +|---|---|---| +| `POST` | `/v1/webhooks/github` | Signed by `X-Hub-Signature-256`. On `push` to a tracked ref, reindex affected `PluginSource`s. Triggers re-materialization for every WorkspaceInstallation whose Bundle contains a changed Primitive. | +| `POST` | `/v1/webhooks/bitbucket` | Equivalent. | + +### 9. Admin / health (optional v1.1) + +| Method | Path | Purpose | +|---|---|---| +| `GET` | `/v1/orgs/:orgId/integrations/:id/diagnostics` | Token expiry, last webhook time, error stream — powers a "something is wrong" banner. | +| `GET` | `/v1/workspaces/:workspaceId/installations/:id/drift` | Compares `applied_primitive_digests` to current on-disk hashes via the existing `/workspace/:id/skills` etc. Returns per-primitive drift status. | + +## Sequence flows + +### A. Connect a source (Phase 1) + +``` + User Den web Den API Provider OpenWork server + │ │ │ │ │ + │─ click Connect ─▶ │ │ │ + │ │─ POST /integrations/authorize ───────▶│ │ + │ │◀── { redirectUrl, state } ──────────│ │ + │◀─ window.location = redirectUrl ───│ │ │ + │────────────── OAuth consent ──────────────────▶ │ │ + │ ◀─ 302 w/ code ─────│ │ + │─ GET /v1/oauth/:type/callback?code&state ──▶│ │ │ + │ │ exchange code │ │ + │ │───── POST token ─▶│ │ + │ │◀─ access_token ───│ │ + │ │ upsert Integration │ + │◀─ 302 /integrations?success&id=…────│ │ │ + │ │ │ │ │ + │─ select repos ──▶ │ │ │ + │ │─ POST /integrations/:id/plugin-sources ▶│ │ + │ │ server: attach sources, register webhook │ + │ │ enqueue initial sync job │ │ + │ │◀── { sources[], jobId }────────────────│ │ + │ │ │ │ │ + │ │ (job runs) │ │ + │ │ │ fetchPluginFS ─────▶│ │ + │ │ │◀── FileTree ────────│ │ + │ │ │ ingest → upsert Primitives + Bundles │ +``` + +### B. Webhook-driven re-sync + +``` + Provider Den API DB + │ │ │ + │─ POST /v1/webhooks/github (push event) ────▶ │ + │ │ verify HMAC │ + │ │ find PluginSources w/ matching ref │ + │ │ for each: enqueue sync job │ + │ │ ├─ fetchPluginFS │ + │ │ ├─ ingest → diff │ + │ │ ├─ upsert changed Primitives │ + │ │ └─ bump Bundle version if plugin.json version changed + │ │ fan out to WorkspaceInstallation(bundleId) + │ │ for each installation: │ + │ │ mark "update available" (not auto-apply) +``` + +### C. Install a bundle into a workspace (Phase 4) + +``` + User Den web Den API OpenWork server Disk + │ │ │ │ │ + │─ click Install ▶│ │ │ │ + │ │─ POST /workspaces/:wid/installations ───────────▶│ │ + │ │ body: { bundleId, scope } │ │ + │ │ │ create WorkspaceInstallation (status=pending) │ + │ │ │ begin materialize job │ │ + │ │◀─ { installationId, status: "materializing" } ───│ │ + │ │ │ │ │ + │ │ │ for each BundleMember: │ │ + │ │ │ switch (primitive.kind): │ │ + │ │ │ skill → POST /workspace/:wid/skills ─────────▶ │ + │ │ │ │ write .opencode/skills//SKILL.md│ + │ │ │ command → POST /workspace/:wid/commands ────────▶ │ + │ │ │ mcp → POST /workspace/:wid/mcp ─────────────▶ │ + │ │ │ plugin_code → POST /workspace/:wid/plugins ─────▶ │ + │ │ │ agent → POST /workspace/:wid/files/content ───▶ │ + │ │ │ record AppliedPrimitiveDigest (targetPath, contentHashAtWrite) │ + │ │ │ │ │ + │ │ │ reload-watcher emits ReloadEvents per affected subdir │ + │ │ │ WorkspaceInstallation.status = "applied" │ + │ │◀─ SSE/poll: status="applied", progress[] ────────│ │ + │ │ │ │ │ + │ │ OpenCode session: │ + │ │ auto-reload if openwork.json.reload.auto, else toast "reload available" │ +``` + +### D. Uninstall (drift-safe) + +``` + Den API Disk + │ │ + │ load WorkspaceInstallation.appliedPrimitiveDigests │ + │ for each digest: │ + │ GET current file via /workspace/:wid/{skills|commands|…} ──────▶ │ + │ ◀───── │ + │ if (currentHash === digest.contentHashAtWrite): │ + │ DELETE /workspace/:wid/{…}/:name │ + │ else: │ + │ leave file; record drift.skipped[primitiveId] │ + │ status = "uninstalled" │ + │ return { removed: [...], skipped: [...] } │ +``` + +## How each connector is structured (GitHub + Bitbucket) + +Both implement the same internal interface — only the provider-specific guts differ. Pseudocode: + +```ts +interface Connector { + type: "github" | "bitbucket"; + displayName: string; + scopes: string[]; + + // OAuth + buildAuthorizeUrl({ orgId, state, redirectUri }): string; + exchangeCode({ code }): { accessToken, refreshToken, expiresAt, account }; + refreshToken({ refreshToken }): { accessToken, refreshToken, expiresAt }; + revoke({ accessToken }): void; + + // Enumeration + listAccounts({ credentials }): Account[]; + listRepos({ credentials, accountId, cursor? }): Page; + peekManifest({ credentials, locator }): "plugin" | "marketplace" | "none"; + + // Ingestion — the heart of it + fetchPluginFS({ credentials, locator }): FileTree; + + // Change detection + registerWebhook({ credentials, locator, secret }): webhookId; + unregisterWebhook({ credentials, locator, webhookId }): void; + verifyWebhook(req): { ok: boolean; event?: RepoPushEvent }; +} +``` + +A separate **ingester** (provider-agnostic) takes `FileTree`, detects the shape, produces `Primitive` upserts and (optionally) `Bundle`+`BundleMember` upserts: + +``` +ingest(fileTree, pluginSourceId): + shape = detectShape(fileTree) + # possible shapes: + # "claude-marketplace" (.claude-plugin/marketplace.json) + # "claude-single" (.claude-plugin/plugin.json at root) + # "opencode-workspace" (opencode.json{c} + .opencode/) + # "bare-skills" (skills/*/SKILL.md with no manifest) + + for each plugin-root in shape.pluginRoots: + # 1. Parse primitives (always) + walk skills/*/SKILL.md → upsert Primitive(kind=skill, content=, metadata=) + walk agents/*.md → upsert Primitive(kind=agent, content=, metadata=) + walk commands/*.md → upsert Primitive(kind=command, content=, metadata=) + parse .mcp.json OR → upsert Primitive(kind=mcp_server, content=, metadata=) + opencode.json.mcp → upsert Primitive(kind=mcp_server, content=, metadata=) + walk .opencode/plugins/*.{ts,js} → upsert Primitive(kind=plugin_code, content=) + parse hooks/hooks.json → flag as Primitive(kind=hook, validation_status=warn, reason="claude-json-hooks") + # see "Hooks strategy" — not directly materializable on OpenCode + + # All primitives get origin = { type: "connector", plugin_source_id, path_in_repo, commit_sha } + # content_hash = sha256(content) + + # 2. Parse bundle metadata (manifest-dependent) + if shape == "claude-marketplace" or "claude-single": + parse .claude-plugin/plugin.json → upsert Bundle(name, description, version, …) + link parsed primitives as BundleMembers + if shape == "opencode-workspace": + synthesize Bundle from opencode.json name/package metadata + link all parsed primitives as members + if shape == "bare-skills": + synthesize Bundle(name = repo name, description = readme excerpt) + link all skill primitives as members +``` + +This means the GitHub and Bitbucket connectors share ~80% of their effort as the ingester — each connector is just "auth + fetch file tree + detect changes". Adding GitLab / npm / local later is a ~200 LOC adapter, no new parsing logic. + +### GitHub specifics + +- **OAuth app** credentials server-side. Scopes `repo`, `read:org`. +- `buildAuthorizeUrl` → `https://github.com/login/oauth/authorize?client_id=…&redirect_uri=…&scope=…&state=…` +- `exchangeCode` → `POST https://github.com/login/oauth/access_token` +- `listAccounts` → `GET /user` + `GET /user/orgs` +- `listRepos(accountId)` → `GET /orgs/:org/repos` or `GET /user/repos` +- `peekManifest` → `GET /repos/:owner/:repo/contents/.claude-plugin/marketplace.json` (404-tolerant) +- `fetchPluginFS` → tarball download (`GET /repos/:owner/:repo/tarball/:ref`) or git tree API for surgical reads +- `registerWebhook` → `POST /repos/:owner/:repo/hooks` filtered to `push` events +- `verifyWebhook` → HMAC-SHA256 against `X-Hub-Signature-256` using the per-source secret + +### Bitbucket specifics + +- **OAuth consumer** credentials server-side. Scopes `repository`, `account`. +- `buildAuthorizeUrl` → `https://bitbucket.org/site/oauth2/authorize?client_id=…&response_type=code&state=…` +- `exchangeCode` → `POST https://bitbucket.org/site/oauth2/access_token` +- `listAccounts` → `GET /2.0/user` + `GET /2.0/workspaces` +- `listRepos(workspace)` → `GET /2.0/repositories/:workspace` +- `peekManifest` → `GET /2.0/repositories/:workspace/:repo/src/:ref/.claude-plugin/marketplace.json` +- `fetchPluginFS` → recursive `/src/:ref/` walk or `/downloads/` tarball +- `registerWebhook` → `POST /2.0/repositories/:workspace/:repo/hooks` +- `verifyWebhook` → HMAC against `X-Hub-Signature` using the webhook UUID secret + +## OAuth flow mapped to endpoints + +What the UI currently simulates in `IntegrationConnectDialog` maps to real calls like this: + +| Dialog step | Mock behavior now | Real behavior | +|---|---|---| +| 1. Authorize | Click advances state | `POST /integrations/authorize` → navigate to `redirectUrl` → provider redirects to `/v1/oauth/:type/callback` → Den redirects back to `/integrations?success&integrationId=…` | +| 2. Select account | `useIntegrationAccounts(provider)` from mock | `GET /integrations/:id/accounts` | +| 3. Select repos | `useIntegrationRepos(provider, accountId)` from mock | `GET /integrations/:id/accounts/:accountId/repos?q=…` | +| 4. Connecting | `useConnectIntegration().mutateAsync` — local mock | `POST /integrations/:id/plugin-sources` body: the selected repos — server queues initial sync | +| 5. Connected | Show success | Poll `GET /plugin-sources/:id/status` or SSE until `last_sync_status === "ok"` | + +## Security + +- Credentials encrypted at rest (AES-GCM with a KMS-rotated key, per-org data key). +- OAuth `state` stored server-side for 10 min, single-use, bound to `orgId + userId`. +- Webhook secrets per `PluginSource`, not per integration — so revoking one source doesn't nuke the rest. +- `peekManifest` and `fetchPluginFS` must tolerate 404 / 403 / rate-limit and never throw into user flow — return typed results. +- Per-installation revocation should call provider revoke endpoints (`DELETE /applications/:client_id/grant` for GitHub, Bitbucket equivalent). +- Audit log row for every integration-level action (connect, disconnect, token refresh, source add, source remove, webhook verify failure). +- **Strict manifest validation** before ingestion — reject plugins that reference files outside their plugin root (`../shared-utils`), same rule Claude Code enforces. + +## Hooks strategy + +Claude Code's `hooks.json` is declarative (events + shell commands). OpenCode's hooks are **code** (JS/TS exporting a `Hooks` interface from a plugin). These are not mechanically equivalent. Three options: + +| Option | What it does | Cost | Verdict | +|---|---|---|---| +| A. Refuse import | At ingest, detect Claude-style `hooks/hooks.json`. Store the raw JSON on the Bundle but mark those hook primitives as `validation_status: "warn"` with a message: "Claude-style JSON hooks require an OpenCode plugin wrapper. [Docs]". Nothing materializes. | Zero | **v1 default** | +| B. Auto-wrap | On install, generate a `.opencode/plugins/-hooks.ts` that reads the JSON manifest and maps each Claude event to the equivalent OpenCode hook, spawning the declared shell command. | Medium — need a careful event mapping table and a stable wrapper runtime. | v1.1 | +| C. Ship a universal runner | Publish one npm package `@openwork/claude-hooks-runtime` that reads JSON hooks from a well-known path (`.opencode/openwork/hooks/*.json`) and registers them once. Installing any Bundle with JSON hooks just drops files in that path. | High upfront (one-time infrastructure) but zero per-Bundle cost thereafter. | v2 — best long-term | + +Recommendation: **A → C**. Ship A now so imports don't fail catastrophically, schedule C as a dedicated workstream. Skip B (per-Bundle codegen is a maintenance liability). + +Event mapping table we'd need for B/C (Claude → OpenCode): + +| Claude event | OpenCode equivalent | Notes | +|---|---|---| +| `PreToolUse` | `tool.execute.before` | matcher on tool name | +| `PostToolUse` | `tool.execute.after` | | +| `SessionStart` | `event` with `session.start` | via generic event hook | +| `SessionEnd` | `event` with `session.end` | | +| `UserPromptSubmit` | `chat.message` or `experimental.chat.messages.transform` | | +| `Notification` | no direct equivalent | punt | +| `Stop` | `experimental.session.compacting` (close) | approximate | + +## Authoring flow (creating primitives + bundles in-app) + +The reverse of ingest. A user authors Primitives + Bundles directly without a connector, e.g. to capture an ad-hoc skill they just wrote in chat. + +### A.1 Author a primitive + +1. User clicks **"Create skill"** (or agent/command/MCP) from `/plugins` → `All Skills` tab. +2. A composer drawer opens with the right form per `kind`: + - `skill` → YAML frontmatter (`name`, `description`) + body editor. + - `agent` → frontmatter (`mode`, `model`, `tools` etc.) + system-prompt body. + - `command` → frontmatter (`description`, `agent?`, `model?`) + template with `$ARGUMENTS`. + - `mcp_server` → form (transport, command/url, env). +3. `POST /v1/orgs/:orgId/primitives` creates the row with `origin = { type: "authored", authorOrgMembershipId }`. +4. `validation_status` is computed synchronously (frontmatter schema, name kebab-case, reserved names, forbidden paths). + +### A.2 Compose a bundle + +1. `/plugins` → **"Create plugin"** button opens the bundle composer. +2. User picks `name`, `description`, `version`, `icon`, `category`, and searches existing Primitives to add as `BundleMember`s (drag or click). +3. `POST /v1/orgs/:orgId/bundles` with `{ slug, name, …, memberPrimitiveIds }`. +4. The server enforces that every referenced primitive belongs to the same org. + +### A.3 Edit / delete + +- Authored primitives + bundles are mutable via `PATCH` and `DELETE`. +- Connector-sourced primitives/bundles are read-only; `PATCH` returns `409 conflict_readonly_connector_source` with a hint to fork. +- **Fork**: `POST /v1/orgs/:orgId/primitives/:id/fork` (and similar for bundles) creates an authored copy the user can edit. + +## Publish / export (sharing authored bundles) + +Closes the loop with the ecosystem: an authored Bundle can be pushed back to a Git repo as a Claude-compatible marketplace tree so other OpenWork (or Claude Code) users can ingest it. + +### Flow + +1. User on a Bundle detail page clicks **"Publish to repo"**. +2. Picks a target: a connected Integration + repo + branch. +3. `POST /v1/orgs/:orgId/bundles/:id/publish` with `{ targetRepo, targetRef, commitMessage }`. +4. Server materializes the Bundle into a `.claude-plugin/` tree in a scratch directory: + - `.claude-plugin/plugin.json` ← Bundle metadata + - `skills//SKILL.md` ← each `skill` Primitive + - `agents/.md` ← each `agent` Primitive + - `commands/.md` ← each `command` Primitive + - `.mcp.json` ← merged `mcp_server` Primitives + - OpenCode plugin code Primitives → warn (Claude target doesn't support native TS plugins) +5. Server commits + pushes via the Integration's credentials. Returns the commit SHA + a link. + +### Multi-bundle publishing + +Publishing multiple Bundles to the same repo synthesises a `.claude-plugin/marketplace.json` catalog listing each as a plugin entry under `plugins//`. + +### Round-trip guarantee + +Ingesting an exported tree back into OpenWork MUST produce semantically-identical Primitives (same `contentHash` per primitive, same Bundle membership). This is a tested invariant — see [Test strategy](#test-strategy). + +## CLI surface + +For agents running inside an OpenCode session to manage the catalog without leaving the shell. Mirrors Claude Code's `claude plugin …` commands. + +``` +openwork connector list +openwork connector add github --repo different-ai/openwork-plugins [--ref main] +openwork connector remove +openwork connector sync + +openwork bundle list [--installed] +openwork bundle show +openwork bundle install [--scope workspace|user|org] +openwork bundle uninstall +openwork bundle publish --to github:owner/repo [--ref main] + +openwork primitive list [--kind=skill|agent|…] +openwork primitive show --kind= +openwork primitive create --kind=skill --file=./my-skill.md +``` + +All commands hit the same API endpoints as the web UI — the CLI is a thin shell over `requestJson`. Output formats: `--json` for scripting, default human-friendly tables. + +An **OpenWork skill** wraps the CLI and surfaces it to agents: + +```yaml +--- +name: openwork-plugin-manager +description: | + Install, uninstall, and discover OpenWork plugins (bundles). + + Triggers when user mentions: + - "install plugin" + - "what plugins are available" + - "publish this as a plugin" +--- +``` + +This makes the full lifecycle reachable from inside any OpenCode conversation — living system behavior. + +## Observability + audit events + +Every state-changing operation emits an `AuditEvent` row with structured fields. Used for debugging, billing-relevant rate limits, and compliance. + +```ts +type AuditEvent = { + id: UUID; + orgId: UUID; + actor: { kind: "user" | "system" | "webhook"; id?: string }; + event: AuditEventType; + subject: { kind: string; id: string }; // e.g. { kind: "integration", id: "…" } + metadata: Record; // event-specific + createdAt: Iso8601; + requestId: string; // for correlating with logs +}; + +type AuditEventType = + // Integrations + | "integration.connected" + | "integration.disconnected" + | "integration.token_refreshed" + | "integration.webhook_verify_failed" + // Sources + | "plugin_source.attached" + | "plugin_source.detached" + | "plugin_source.sync_started" + | "plugin_source.sync_succeeded" + | "plugin_source.sync_failed" + // Primitives + | "primitive.created" + | "primitive.updated" + | "primitive.deleted" + | "primitive.forked" + // Bundles + | "bundle.created" + | "bundle.updated" + | "bundle.deleted" + | "bundle.published" + // Installations + | "installation.started" + | "installation.applied" + | "installation.failed" + | "installation.uninstalled" + | "installation.drift_detected"; +``` + +### Metrics (Prometheus-style labels) + +| Metric | Type | Labels | Purpose | +|---|---|---|---| +| `connectors_sync_duration_seconds` | histogram | `provider`, `status` | Ingest latency SLO | +| `connectors_ingested_primitives_total` | counter | `provider`, `kind` | Catalog growth | +| `installations_materialize_duration_seconds` | histogram | `status` | Install latency SLO | +| `installations_drift_detected_total` | counter | `kind` | Track how often users edit on disk | +| `webhooks_received_total` | counter | `provider`, `verified` | Traffic + security signal | + +### Alerts (proposed thresholds) + +- Sync failure rate > 5% over 5 min → page. +- Materialization p95 > 30s → warn. +- Webhook verification failures > 10/min → page (likely secret leak or attack). + +## Rollout plan + +Five slices, each independently shippable behind a feature flag. Previous slices stay on mocks until the backing slice lands. + +| Slice | Scope | Feature flag | Gates | +|---|---|---|---| +| 0 | **This PRD** (lands now) | n/a | Merged | +| 1 | Connector-types registry + Integrations CRUD + GitHub adapter + ingester + Primitive + Bundle tables + read-only `GET /v1/orgs/:orgId/{integrations,plugins,primitives,bundles}` | `ff.connectors.read` | Ingest a real repo; Den web Phase 1–3 reads live data | +| 2 | Authoring: `POST/PATCH/DELETE /v1/orgs/:orgId/{primitives,bundles}` + bundle members | `ff.connectors.author` | User can create + edit in-app | +| 3 | WorkspaceInstallation + materialization engine + agents endpoint (`POST /workspace/:id/agents`) | `ff.connectors.install` | Install Bundle from mock Den workspace; file appears on disk; OpenCode loads it | +| 4 | Bitbucket adapter + webhooks | `ff.connectors.bitbucket`, `ff.connectors.webhooks` | Multi-provider + live updates | +| 5 | Publish/export + CLI + universal hook runtime | `ff.connectors.publish`, `ff.connectors.cli`, `ff.connectors.hook_runtime` | Round-trip + ecosystem | + +### Mock-removal choreography + +For each slice that lands, the Den web side removes exactly one mock file's contents and swaps `queryFn`: + +- Slice 1 → `integration-data.tsx` queries hit real endpoints; `plugin-data.tsx` `usePlugins` reads real bundles, still filtered client-side by connected integrations. +- Slice 3 → add `useWorkspaceInstallations()` + new `/workspaces/:id/plugins` page. + +### Rollback + +Every slice's feature flag is independent. Rolling back slice N leaves slices 0..N-1 intact. Connector-written primitives are soft-deletable (`deletedAt`) so a bad sync can be reverted without data loss. + +### Migration + +No existing data to migrate — these are all new tables. The existing `/v1/orgs/:orgId/skills` endpoint (skill-hubs) keeps working; slice 1 adds a backfill that copies skill-hub skills into the `primitives` table as `origin = { type: "authored", … }` so they show up in the unified index. Skill-hubs remain as-is for deletion after slice 2. + +## Test strategy + +### Unit + +- **Ingester**: table-driven tests for each shape (`claude-marketplace`, `claude-single`, `opencode-workspace`, `bare-skills`). Golden-file fixtures under `test/fixtures/ingest/`. +- **Connector adapters**: mock GitHub/Bitbucket HTTP with `nock`; assert request construction (scopes, OAuth params, webhook payloads). +- **Materialization**: mock the OpenWork server API; assert correct endpoint + body per primitive kind; assert `appliedPrimitiveDigests` is recorded. +- **Drift detection**: given `{ primitive.contentHash, disk.hash }` permutations, assert correct classification. + +### Integration + +- **Real GitHub**: a test org + throwaway repo under `different-ai/openwork-test-plugins` with fixture plugins. CI authenticates with a PAT; runs a full ingest + install + uninstall against a temp worktree. Skipped in local unless `OPENWORK_TEST_GITHUB_PAT` is set. +- **Real OpenWork server**: spins up `packaging/docker/dev-up.sh`, provisions a workspace, runs materialization, diffs `.opencode/` against expectations. + +### End-to-end + +- **Den web** + Chrome MCP: the flow shipped in PRs #1472 / #1475 re-run against real endpoints. Authorize GitHub → select repo → install a bundle → check `/plugins` populates → check `.opencode/skills/` on the worker. +- **Round-trip**: author a Bundle in-app → publish to a repo → ingest that repo from another org → assert Primitives are identical (same `contentHash`) and Bundle membership matches. + +### Fuzz / property tests + +- **Path safety**: property-test the ingester against random inputs; assert no `../` path ever reaches a write. +- **Drift math**: for any sequence of `(write, user-edit?, uninstall)`, assert uninstall never deletes a file the installer didn't write. + +### Regression invariants (asserted in CI) + +- `contentHash(ingest(export(bundle)))` === `contentHash(bundle.members.content)` for every primitive kind. +- Installing the same Bundle twice is idempotent (same final disk state, same digests). +- Uninstall after a user edit leaves the file untouched and records drift. + +## UI surfaces (where each phase lives) + +| Phase | UI surface | Status | +|---|---|---| +| 1. Ingest | `/o/:slug/dashboard/integrations` | ✅ shipped (mock), wire to real API | +| 2. Primitive index | `/o/:slug/dashboard/plugins` — `All Skills` / `All Hooks` / `All MCPs` tabs | ✅ shipped (mock), wire to real API | +| 3. Bundle denomination | `/o/:slug/dashboard/plugins` — list + detail view | ✅ shipped (mock); add `/plugins/new` and `/plugins/:id/edit` for authoring | +| 4. Workspace install | **new**: `/o/:slug/dashboard/workspaces/:workspaceId/plugins` or a tab inside the existing workspace view | 🟡 not yet built | + +The new Phase-4 surface shows: + +- Currently installed bundles in this workspace (with scope badges: `Workspace` / `User` / `Org`). +- Per-primitive status rows: "Skill `release-prep` → `.opencode/skills/release-prep/SKILL.md` ✓". +- A "browse plugins" CTA that opens `/plugins` in install-mode with this workspace pre-selected. +- Drift indicators when on-disk content no longer matches `applied_primitive_digests`. + +Rough visual: same `DashboardPageTemplate` shell, same `DenSelectableRow` for per-primitive status, reuse the `PaperMeshGradient` card per installed bundle. + +## Open questions + +**Resolved since v1 of this PRD** (keeping for trace): + +- ✅ Canonical FS: `.opencode/` is source of truth on disk; DB indexes. Claude `.claude/skills/` paths work natively since OpenCode reads them. +- ✅ Plugin materialization: virtual bundles; primitives written individually to native `.opencode/` paths. +- ✅ Primitive storage: `.opencode/` is source of truth. Remote DB is the org-wide index today; local DB (triggered via a skill) is the future direction. + +**Still open:** + +1. **Agents mutation endpoint**: OpenWork server has `/workspace/:id/{skills,commands,plugins,mcp}` but **no dedicated agents endpoint**. Options: (a) add `POST /workspace/:id/agents`, (b) use the generic `POST /workspace/:id/files/content` for agents, (c) bundle agents under plugins. Recommend (a) for parity. +2. **Single-plugin repos vs marketplace repos**: both from day one? (Strongly yes.) +3. **Sync strategy**: webhooks-only, webhooks+daily poll fallback, or polling only for v1? Webhooks need a public ingress; for the desktop-hosted case that's harder. Start with polling + manual "sync now", add webhooks as a cloud-only feature. +4. **Installation scope semantics**: how does "org / user / workspace" map onto `orgMembership` / `workspace`? Specifically: can "org" scope auto-install into every newly-created workspace in that org (pre-populate from `extraKnownMarketplaces`-style config)? +5. **Hooks strategy rollout**: confirm A-now, C-later, skip B. +6. **Private package connector**: is npm a v1 target or punt to v1.1? npm adds tarball-fetch + auth. +7. **Client-authored connectors**: "clients can create connectors, or we can" — is that in-scope for v1 (user-registered connector definitions in DB)? Recommend punting to v2 and keeping v1 adapter-registry code-only. +8. **Drift policy defaults**: when a workspace's `.opencode/skills/foo/SKILL.md` differs from the installed Bundle's Primitive, do we (a) prefer disk silently, (b) prefer disk + warn on the Installations page, (c) force a reapply on next install action? Recommend (b). +9. **Bundle versioning semantics**: if a user installs Bundle v1.2 and we re-ingest and now see v1.3, do we auto-update or require an explicit "Update available" click? Recommend explicit click — matches Claude Code's `/plugin marketplace update` semantics. + +## What this buys us vs. building a bespoke schema + +1. **OpenCode-native materialization** — we don't fight the runtime. OpenCode reads exactly what it already expects; our system produces those files. +2. **Claude-ecosystem compatibility at the skill layer** — `.claude/skills/**/SKILL.md` trees work natively; Claude-style `.claude-plugin/marketplace.json` trees are importable (with agents/commands rehomed at ingest). +3. **Thin connector adapters** — parsing + materialization is shared; each provider is just "auth + fetch tree + webhook verify". +4. **Clean separation of concerns** — Primitives (atoms) ≠ Bundles (groups) ≠ WorkspaceInstallations (projection). Each layer testable in isolation. +5. **Drift-safe uninstall** — `applied_primitive_digests` gives exact reversal without stomping on user edits. +6. **Future-proofs the local-DB pivot** — the `origin.local_mirror` variant is already in the schema; switching SoT from remote-DB-index to local-DB-canonical is additive, not a rewrite. + +## Next steps + +Mapped directly to [Rollout plan](#rollout-plan) slices: + +1. **Land this PRD** (this PR — slice 0). +2. **Slice 1** — ingest → index. Ship behind `ff.connectors.read`. +3. **Slice 2** — authoring. Ship behind `ff.connectors.author`. Covers mock-removal for Phase 3 composer. +4. **Slice 3** — install → materialize. Ship behind `ff.connectors.install`. New `/workspaces/:id/plugins` page + `POST /workspace/:id/agents` endpoint. +5. **Slice 4** — Bitbucket + webhooks. +6. **Slice 5** — publish/export + CLI + universal hook runtime. + +Each slice owns its feature flag, mock-removal scope, and rollback plan (see Rollout plan for details). diff --git a/prds/new-plugin-arch/github-connection/plan.md b/prds/new-plugin-arch/github-connection/plan.md new file mode 100644 index 00000000..85bdadc1 --- /dev/null +++ b/prds/new-plugin-arch/github-connection/plan.md @@ -0,0 +1,123 @@ +# GitHub Connection UX Plan + +## Goal + +Define the desired user experience for connecting GitHub to OpenWork in den-web (cloud), independent of the current implementation state. + +This flow is for the den-api plugin connector system and GitHub App based connector onboarding. + +## Desired user flow + +1. User is in OpenWork den-web (cloud). +2. User sees an `Integrations` entry point. +3. User opens `Integrations`. +4. User sees a GitHub integration card. +5. User clicks `Connect` on GitHub. +6. OpenWork sends the user to the GitHub App install/authorize flow. +7. User completes the normal GitHub steps on GitHub. +8. GitHub returns the user to OpenWork. +9. OpenWork recognizes the completed GitHub App installation for the current org/user context. +10. OpenWork shows the user the list of repositories available through that installation. +11. User selects one repository. +12. OpenWork creates a new GitHub connector instance for that selected repository. +13. OpenWork configures webhook-driven sync for that repository. +14. Future pushes to the connected repository trigger OpenWork sync behavior through the connector pipeline. + +## Product expectations + +### Integrations surface + +- den-web should expose a clear `Integrations` UI in cloud mode. +- GitHub should appear as a first-class integration option. +- The user should not need to manually paste GitHub installation ids or repository ids. + +### Connect action + +- Clicking `Connect` should start a GitHub App flow, not a legacy OAuth-only flow. +- The flow should preserve enough OpenWork context to return the user to the correct org and screen after GitHub finishes. +- The GitHub-side step should feel like a normal GitHub App installation flow. + +### Return to OpenWork + +- After GitHub redirects back, OpenWork should detect the installation that was just created or updated. +- If installation state is incomplete or ambiguous, OpenWork should guide the user instead of silently failing. +- The user should land back in the GitHub integration flow, not on a generic page with no next step. + +### Repository selection + +- OpenWork should list repositories available to the installation. +- The user should be able to pick one repository as the first connected source. +- Selecting a repository should create a connector instance for that repo in the current OpenWork org. +- The UX may later support branch choice and mapping choice, but repository selection is the minimum required step. + +### Webhook + sync expectation + +- Once connected, OpenWork should be ready to receive GitHub App webhooks for the selected repository. +- Pushes on the tracked branch should enter the connector sync pipeline. +- The system should present this as a connected integration, not as a hidden backend-only setup. + +## User-facing behavior requirements + +- The user should not need to know what an installation id is. +- The user should not need to call admin APIs manually. +- The user should not need to configure webhooks manually in normal product usage. +- The user should be able to understand whether GitHub is: + - not connected + - connected but no repository selected + - connected and repository syncing + - connected but needs attention + +## Desired backend behavior + +To support the UX above, the backend flow should conceptually do the following: + +1. Generate or expose the GitHub App install URL. +2. Preserve OpenWork return context across the redirect. +3. Handle the GitHub return/callback. +4. Resolve the GitHub App installation id associated with the user action. +5. Create or update the corresponding `connector_account`. +6. List repositories accessible through that installation. +7. On repo selection, create: + - a `connector_instance` + - a `connector_target` for the repo/branch + - any initial mappings needed for ingestion +8. Ensure webhook events can resolve that connector target. +9. Queue sync work when relevant webhook events arrive. + +## UX principles + +- Prefer a short, guided flow over a configuration-heavy admin experience. +- Favor product language like `Connect GitHub` over backend nouns like `connector account`. +- Hide raw GitHub/App identifiers from the normal UX unless needed for support/debugging. +- Keep the first-run flow focused on success: install, return, pick repo, connected. +- Advanced settings can exist later, but should not block first connection. + +## Success criteria + +The experience is successful when: + +1. A cloud user can start from den-web without using terminal commands. +2. The user can complete GitHub App installation from the app. +3. The user returns to OpenWork automatically. +4. OpenWork shows repositories from that installation. +5. The user selects a repo. +6. OpenWork creates a connector instance for that repo. +7. GitHub webhooks for that repo can be accepted and associated to the instance. +8. The connection state is visible in the product UI. + +## Non-goals for this document + +- Exact API shapes for every route. +- Full ingestion/reconciliation design details. +- Delivery/install runtime behavior for connected content. +- Final UI layout or visual design. + +## Next planning step + +Translate this desired UX into an implementation plan that maps: + +- den-web screens and states +- den-api routes and callback behavior +- GitHub App configuration requirements +- connector-account / connector-instance creation behavior +- webhook readiness and initial sync behavior diff --git a/prds/new-plugin-arch/github-connection/repo-discovery-plan.md b/prds/new-plugin-arch/github-connection/repo-discovery-plan.md new file mode 100644 index 00000000..69d32a66 --- /dev/null +++ b/prds/new-plugin-arch/github-connection/repo-discovery-plan.md @@ -0,0 +1,773 @@ +# GitHub Repo Discovery Plan + +## Goal + +Define the discovery phase that happens after a user connects a GitHub repo and returns to Den Web. + +This phase should: + +1. inspect the connected repository structure; +2. determine whether the repo is a Claude-compatible marketplace repo, a Claude-compatible single-plugin repo, or a looser folder-based repo; +3. present the discovered plugins to the user in a setup flow; +4. let the user choose which discovered plugins should map into OpenWork; +5. translate the selected discovery result into OpenWork connector records and future ingestion work. + +This document covers: + +- the discovery UX; +- the GitHub-side reads we need; +- how we detect supported repo shapes; +- how we infer plugins when no manifest exists; +- how the result maps into OpenWork internal structures. + +Related: + +- `prds/new-plugin-arch/github-connection/plan.md` +- `prds/new-plugin-arch/github-connection/connectors.md` +- `prds/new-plugin-arch/GitHub-connector.md` + +## Why a discovery phase exists + +The current post-connect flow stops at repository selection. + +That is enough to create: + +- a `connector_account`; +- a `connector_instance`; +- a `connector_target`; +- webhook-triggered `connector_sync_event` rows. + +It is not enough to understand the shape of the repo and convert that shape into useful OpenWork mappings. + +The discovery phase fills that gap. + +Instead of immediately asking the user to author raw path mappings, OpenWork should first inspect the repo and propose a structured interpretation of what it found. + +## Desired user flow + +### Updated high-level flow + +1. User connects GitHub. +2. User selects a repository. +3. OpenWork creates the connector instance and target. +4. OpenWork routes the user into a dedicated `Setup` / `Discovery` page for that connector instance. +5. OpenWork reads the repository tree and shows progress steps in the UI. +6. OpenWork classifies the repo shape. +7. OpenWork shows discovered plugins, preselected by default. +8. User confirms or deselects discovered plugins. +9. OpenWork creates the initial connector mappings and plugin records from that discovery result. +10. OpenWork is ready for initial ingestion/sync. + +### User-facing setup steps + +The setup page should feel like a guided scan. + +Suggested steps: + +1. `Reading repository structure` +2. `Checking for Claude marketplace manifest` +3. `Checking for plugin manifests` +4. `Looking for known component folders` +5. `Preparing discovered plugins` + +The UI should show: + +- which step is currently running; +- success/failure state per step; +- the discovered plugins list when ready; +- clear empty-state or unsupported-shape messaging when nothing useful is found. + +## Reference conventions + +### Official Claude plugin conventions + +Based on the Claude plugin docs and reference repo: + +- plugin manifest lives at `.claude-plugin/plugin.json`; +- marketplace manifest lives at `.claude-plugin/marketplace.json`; +- plugin components live at the plugin root, not inside `.claude-plugin/`; +- common plugin root folders include: + - `skills/` + - `commands/` + - `agents/` + - `hooks/` + - `.mcp.json` + - `.lsp.json` + - `monitors/` + - `settings.json` +- standalone Claude configuration can also live under `.claude/`, especially: + - `.claude/skills/` + - `.claude/agents/` + - `.claude/commands/` + +### Reference repo + +Use `https://github.com/anthropics/claude-plugins-official` as a reference shape for marketplace repos. + +Important observations: + +- the repo has a root `.claude-plugin/marketplace.json`; +- it contains multiple plugin entries; +- many entries point at local paths inside the repo such as `./plugins/...` or `./external_plugins/...`; +- some entries point at external git URLs or subdirs. + +That means OpenWork discovery should treat marketplace repos as a first-class shape, but be explicit about what is in-scope for a connected single repo. + +## Discovery output model + +The discovery phase should produce an explicit, structured result. + +Suggested conceptual result: + +```ts +type RepoDiscoveryResult = { + connectorInstanceId: string + connectorTargetId: string + repositoryFullName: string + ref: string + treeSummary: { + scannedEntryCount: number + truncated: boolean + strategy: "git-tree-recursive" | "contents-bfs" + } + classification: + | "claude_marketplace_repo" + | "claude_multi_plugin_repo" + | "claude_single_plugin_repo" + | "folder_inferred_repo" + | "unsupported" + discoveredPlugins: DiscoveredPlugin[] + warnings: DiscoveryWarning[] +} + +type DiscoveredPlugin = { + key: string + sourceKind: + | "marketplace_entry" + | "plugin_manifest" + | "standalone_claude" + | "folder_inference" + rootPath: string + displayName: string + description: string | null + selectedByDefault: boolean + manifestPath: string | null + componentKinds: Array<"skill" | "command" | "agent" | "hook" | "mcp_server" | "lsp_server" | "monitor" | "settings"> + componentPaths: { + skills: string[] + commands: string[] + agents: string[] + hooks: string[] + mcpServers: string[] + lspServers: string[] + monitors: string[] + settings: string[] + } + metadata: Record +} +``` + +This result is intentionally separate from final ingestion. Discovery should be cheap to recompute and safe to show in the UI. + +## API surface + +## Requirements + +We need an API that can, given the selected connector instance/target, read GitHub and return a normalized view of the repository tree and discovery result. + +The tree can be large, so the API must not assume that the full repo listing is always tiny. + +### Recommended endpoints + +#### 1. Start or refresh discovery + +`POST /v1/connector-instances/:connectorInstanceId/discovery/refresh` + +Purpose: + +- read GitHub using the installation token; +- build or refresh the discovery snapshot; +- persist the result for the UI; +- return the current discovery state. + +Recommended response: + +- current step/state; +- summary counts; +- discovered plugins if already complete. + +#### 2. Get discovery state + +`GET /v1/connector-instances/:connectorInstanceId/discovery` + +Purpose: + +- return the last computed discovery result; +- support polling while the discovery scan runs; +- drive the setup page without recomputing every request. + +#### 3. Page through the normalized repo tree + +`GET /v1/connector-instances/:connectorInstanceId/discovery/tree?cursor=&limit=&prefix=` + +Purpose: + +- expose the discovered file list for debugging and future advanced UX; +- avoid forcing the UI to load every path at once; +- support drill-down into a directory prefix. + +### Why a persisted snapshot is better than live-only reads + +Discovery is more than a raw file listing. +It is a structured interpretation step. + +Persisting the latest snapshot gives us: + +- deterministic UI reload behavior; +- auditability of what the repo looked like when discovery ran; +- a clean handoff from discovery UI to mapping creation; +- a place to store warnings and unsupported cases. + +## GitHub reading strategy + +### Primary strategy + +Use the GitHub Git Trees API against the selected branch head commit. + +Preferred read path: + +1. fetch the tracked branch head SHA; +2. fetch the recursive tree for that commit; +3. normalize to a path list with type metadata. + +Advantages: + +- one request gives the full tree in the common case; +- easy to search for known files; +- easy to infer folder groupings; +- deterministic against a known commit SHA. + +### Fallback strategy for large repos + +GitHub recursive tree responses can be truncated. + +If the recursive tree response is truncated: + +1. store that truncated flag; +2. fall back to directory-by-directory `contents` traversal using BFS; +3. page the normalized result by `prefix + cursor`; +4. cap the total scan budget for one discovery run. + +### Suggested limits + +For v1: + +- default API page size: `200` normalized entries; +- default max discovery scan budget: `10,000` paths; +- stop scanning further when: + - we exceed budget; + - or we have enough evidence to classify the repo and build the discovered plugin list. + +### Practical optimization + +We do not need the full contents of every file during discovery. + +We mostly need: + +- the path list; +- whether certain files exist; +- the content of a small number of manifest files. + +So discovery should: + +- list tree entries first; +- only fetch file contents for: + - `.claude-plugin/marketplace.json` + - any `.claude-plugin/plugin.json` + - any root-level `plugin.json` used as a metadata hint + - `.mcp.json` + - `.lsp.json` + - `hooks/hooks.json` + - `monitors/monitors.json` + - `settings.json` + +Do not eagerly fetch SKILL/agent/command content during the discovery phase. + +## Classification algorithm + +Discovery should classify the repo in this priority order. + +### 1. Marketplace repo + +Check for root: + +- `.claude-plugin/marketplace.json` + +If present: + +- classify as `claude_marketplace_repo`; +- parse marketplace entries; +- attempt to resolve entries that point to local repo paths; +- present the listed plugins to the user, ticked by default. + +### 2. Explicit plugin manifests + +If no marketplace manifest exists, search for all instances of: + +- `.claude-plugin/plugin.json` + +If one or more are found: + +- classify as: + - `claude_single_plugin_repo` if exactly one plugin manifest exists and it is at repo root; + - `claude_multi_plugin_repo` if more than one plugin manifest exists or plugin roots live in subdirectories. +- create one `DiscoveredPlugin` per manifest. + +### 3. Standalone Claude folders + +If no marketplace manifest and no plugin manifest is found, check for standalone Claude paths: + +- `.claude/skills/**` +- `.claude/commands/**` +- `.claude/agents/**` + +If present: + +- classify as `standalone_claude` in the discovered plugin source kind; +- infer a single plugin rooted at repo root unless stronger folder grouping is present. + +### 4. Folder inference + +If none of the explicit Claude shapes exist, infer plugin candidates from known component folders. + +Known folders: + +- `skills/` +- `commands/` +- `agents/` + +Rule: + +- for each match, examine its parent folder; +- group sibling component folders by that parent; +- create one discovered plugin per parent folder. + +Example: + +```text +Sales/skills +Sales/commands +finance/agents +finance/commands +``` + +Discovery result: + +- plugin `Sales` +- plugin `finance` + +This becomes: + +- one plugin candidate rooted at `Sales/` +- one plugin candidate rooted at `finance/` + +If the repo itself has root-level `skills/`, `commands/`, or `agents/`, that should infer one root plugin using the repo name as the display name unless better metadata exists. + +## Plugin metadata resolution + +For each discovered plugin candidate, resolve metadata in this order. + +### 1. Official Claude plugin manifest + +Check: + +- `/.claude-plugin/plugin.json` + +If present, use: + +- `name` +- `description` +- `version` +- `author` +- other supported metadata as hints + +### 2. Loose metadata hint + +If no official manifest exists, optionally check: + +- `/plugin.json` + +This is not an official Claude plugin location. +Treat it as a metadata hint only. + +Use: + +- `name` +- `description` + +Do not treat it as proof that the repo is a Claude plugin. + +### 3. Folder-name fallback + +If no metadata file exists: + +- use the folder name as `displayName`; +- derive a human-friendly label from that folder name. + +For a root plugin with no folder name beyond the repo itself, use the repo name. + +## Marketplace repo handling + +Marketplace repos need special treatment. + +### What we should support in v1 + +Support marketplace entries whose source resolves inside the currently connected repo. + +Examples: + +- `./plugins/example-plugin` +- `./external_plugins/something` + +For these entries: + +- resolve the local plugin root; +- inspect that root for components; +- create one `DiscoveredPlugin` for each entry. + +### What we should not silently fake in v1 + +Marketplace entries that point to external URLs or other repos should not be treated as if they were fully present in the current repo. + +Examples: + +- `source.url = https://github.com/...` +- `source.source = git-subdir` + +For those entries, discovery should either: + +- mark them as `external source not yet supported in repo discovery`; or +- hide them unless we explicitly decide to support cross-repo expansion. + +Recommended v1 behavior: + +- show them in the discovery result but disable selection; +- explain that they require external source expansion, which is out of scope for the current single-repo connector flow. + +This keeps the behavior honest and still lets users understand what OpenWork detected. + +## Inferred plugin rules + +### Known component directories + +The discovery system should recognize these as plugin-like components: + +- `skills/` +- `commands/` +- `agents/` +- `.claude/skills/` +- `.claude/commands/` +- `.claude/agents/` + +Optional later additions: + +- `hooks/` +- `.mcp.json` +- `.lsp.json` +- `monitors/` +- `settings.json` + +### Grouping rules + +Group by the nearest plugin root candidate. + +Examples: + +#### Case A: explicit manifest + +```text +plugins/sales/.claude-plugin/plugin.json +plugins/sales/skills +plugins/sales/commands +``` + +Result: + +- one discovered plugin rooted at `plugins/sales` + +#### Case B: inferred sibling grouping + +```text +Sales/skills +Sales/commands +Finance/agents +Finance/commands +``` + +Result: + +- one discovered plugin rooted at `Sales` +- one discovered plugin rooted at `Finance` + +#### Case C: root standalone repo + +```text +.claude/skills +.claude/commands +``` + +Result: + +- one discovered plugin rooted at repo root + +## UI plan + +## Setup page states + +Suggested states: + +1. `loading` +2. `discovery_running` +3. `discovery_ready` +4. `discovery_empty` +5. `discovery_error` + +### discovery_running + +Show: + +- progress steps; +- current repo name/branch; +- a short explanation that OpenWork is figuring out how to map this repo. + +### discovery_ready + +Show: + +- discovered plugins list; +- each item ticked by default if supported; +- description/metadata when available; +- badges for detected component kinds: + - skills + - commands + - agents + - hooks + - MCP +- warnings for unsupported marketplace entries or ambiguous structure. + +Primary CTA: + +- `Continue with selected plugins` + +Secondary CTA: + +- `Review file structure` + +### discovery_empty + +Show: + +- no supported plugin structure found; +- what OpenWork looked for; +- option to create manual mappings. + +### discovery_error + +Show: + +- discovery failed; +- which step failed; +- retry action. + +## What the user selects + +The user should select plugin groups, not raw files. + +Each selected discovered plugin becomes a proposal for: + +- one OpenWork `plugin` row; +- a set of `connector_mapping` rows covering that plugin's component folders. + +This matches the product goal better than asking the user to map individual folders one by one on first run. + +## Mapping discovered plugins to OpenWork internal data + +## Internal objects we already have + +- `connector_account` +- `connector_instance` +- `connector_target` +- `connector_mapping` +- `connector_sync_event` +- `connector_source_binding` +- `connector_source_tombstone` +- `plugin` +- plugin membership tables +- `config_object` + +## Discovery-to-internal mapping + +### Discovery phase output + +Before the user confirms selection, discovery should exist as draft state. + +Recommended persistence model: + +- `connector_discovery_run` +- `connector_discovery_candidate` + +Conceptually: + +```text +connector_discovery_run +- id +- organization_id +- connector_instance_id +- connector_target_id +- source_revision_ref +- status +- classification +- tree_summary_json +- warnings_json +- created_at +- updated_at + +connector_discovery_candidate +- id +- discovery_run_id +- key +- source_kind +- root_path +- display_name +- description +- manifest_path +- component_summary_json +- selection_state +- supported +- warnings_json +``` + +Why add dedicated discovery tables instead of jumping straight to `connector_mapping`? + +- discovery is provisional; +- the user may deselect some plugin candidates; +- we want to store unsupported candidates and warnings; +- we want a clean boundary between `what we saw` and `what the user approved`. + +### After user confirms selection + +For each selected discovered plugin: + +1. create or upsert an OpenWork `plugin` row; +2. create one `connector_mapping` per detected component kind/path; +3. set `auto_add_to_plugin = true` for those mappings; +4. link the mapping to the selected OpenWork plugin id; +5. enqueue an initial discovery-approved ingestion sync. + +### Example mapping + +Repo: + +```text +Sales/skills +Sales/commands +finance/agents +finance/commands +``` + +Discovery result: + +- plugin candidate `Sales` +- plugin candidate `finance` + +Internal translation after user confirms: + +- create OpenWork plugin `Sales` +- create OpenWork plugin `finance` +- create mappings: + - `Sales/skills/**` -> `skill` -> plugin `Sales` + - `Sales/commands/**` -> `command` -> plugin `Sales` + - `finance/agents/**` -> `agent` -> plugin `finance` + - `finance/commands/**` -> `command` -> plugin `finance` + +### Marketplace mapping + +For a local marketplace entry rooted at `plugins/feature-dev`: + +- create one OpenWork plugin from the marketplace/plugin metadata; +- create mappings for each detected component path under that root; +- preserve the marketplace entry metadata as origin/discovery metadata. + +## Discovery does not ingest content yet + +Discovery should stop short of full content ingestion. + +It should: + +- inspect paths; +- read manifests and small metadata files; +- infer plugin groups; +- help the user approve a mapping shape. + +It should not yet: + +- parse every SKILL/agent/command file body; +- create `config_object` rows; +- create `connector_source_binding` rows; +- create tombstones. + +Those belong to the subsequent ingestion/reconciliation phase. + +## Relationship to initial sync + +The initial sync should happen after discovery is approved. + +Suggested flow: + +1. repo selected +2. connector instance created +3. discovery run computes candidates +4. user confirms selections +5. OpenWork creates plugin rows + connector mappings +6. OpenWork enqueues initial full sync +7. sync executor reads repo contents and materializes config objects + +This sequencing is important because ingestion needs the mapping decisions. + +## v1 scope + +### In scope + +- dedicated setup/discovery page after repo selection; +- repo tree listing API with pagination/limits; +- root marketplace detection; +- `.claude-plugin/plugin.json` discovery anywhere in the repo; +- `.claude/skills`, `.claude/commands`, `.claude/agents` support; +- folder-based inference from known component paths; +- user selection UI for discovered plugins; +- translation from selected candidates into plugin rows + connector mappings. + +### Explicitly out of scope for this phase + +- full content ingestion; +- recursive external marketplace source expansion across other repos; +- hooks-to-OpenWork runtime semantics beyond discovery; +- automatic parsing of every skill/agent/command file body during discovery. + +## Open questions + +1. Should discovery run synchronously for small repos and asynchronously for larger repos, or always be modeled as a background run? +2. Do we want to persist discovery results in dedicated tables immediately, or temporarily store the first version inside connector metadata while the shape is still changing? +3. For marketplace repos with external URL entries, should we show unsupported entries disabled, or hide them entirely in v1? +4. Should root-level `plugin.json` remain a metadata hint only, or do we want to formalize it as an OpenWork-specific compatibility rule? +5. When multiple discovered plugin candidates have the same normalized name, what is the preferred display/slug collision strategy? + +## Recommended next implementation order + +1. Add a discovery result model and API endpoints. +2. Implement GitHub tree listing with truncation-aware fallback. +3. Implement classification + candidate extraction. +4. Update the GitHub setup page to become the discovery page. +5. Add the discovered plugin selection UI. +6. Convert approved candidates into `plugin` + `connector_mapping` rows. +7. Then implement initial ingestion against those mappings. diff --git a/prds/new-plugin-arch/learnings.md b/prds/new-plugin-arch/learnings.md index e7d78c35..a21addd3 100644 --- a/prds/new-plugin-arch/learnings.md +++ b/prds/new-plugin-arch/learnings.md @@ -27,6 +27,19 @@ Use this shape for new entries: ## Current entries +## 2026-04-21 Step 2 - GitHub App connect + repo selection slice +- The existing Den `/integrations` UI already had the right shell, but the GitHub path was a pure client-side preview. The cleanest upgrade path is to keep Bitbucket on the mock dialog for now while sending GitHub through a real App install redirect and a dedicated post-return repo-selection screen. +- GitHub App install does not need the normal Better Auth GitHub social login flow. The updated working slice is: den-web calls `POST /v1/connectors/github/install/start`, GitHub redirects the browser to the Den Web setup page, then den-web calls `POST /v1/connectors/github/install/complete` with `installation_id + state` so den-api can validate the signed state and load repos. +- A signed state token based on `BETTER_AUTH_SECRET` is enough for the current redirect round-trip and is simpler than introducing a new persistence table for short-lived install state in this phase. +- The GitHub App `Setup URL` should point at a real web page in Den Web, e.g. `/dashboard/integrations/github`, not a backend callback route. +- Workspace dependency installation was the original gating build blocker, but after `pnpm install` the den-api build, den-web typecheck, and focused den-api tests all run in this worktree. + +## 2026-04-21 Step 1 - Live GitHub App admin validation +- The GitHub-specific admin path was more stubbed than it looked: `listGithubRepositories()` only echoed cached connector-account metadata and `validateGithubTarget()` only checked whether `ref === refs/heads/${branch}` without contacting GitHub. +- A small dedicated helper module at `ee/apps/den-api/src/routes/org/plugin-system/github-app.ts` keeps the real GitHub App mechanics isolated: normalize multiline private keys, mint an app JWT, exchange it for an installation token, then call GitHub APIs for repository listing and branch validation. +- For real connector setup testing, the minimally required live server secrets are `GITHUB_CONNECTOR_APP_ID`, `GITHUB_CONNECTOR_APP_PRIVATE_KEY`, and `GITHUB_CONNECTOR_APP_WEBHOOK_SECRET`; `GITHUB_CONNECTOR_APP_CLIENT_ID` / `CLIENT_SECRET` are still part of the app registration but are not yet consumed by the current den-api admin flow. +- Workspace dependency installation is still a gating factor for broader den-api tests in this worktree: pure helper tests can run with Bun, but route/store tests that import `hono` or `@openwork-ee/den-db/*` still fail until the workspace dependencies are installed. + ## 2026-04-17 Post-step cleanup - Type tightening and naming - The route directory is now `ee/apps/den-api/src/routes/org/plugin-system/`; `plugin-arch` was only the planning nickname and was too confusing as a long-lived API module name. - The plugin-system route wrapper can stay type-safe enough without `@ts-nocheck` by isolating Hono middleware registration behind a tiny `withPluginArchOrgContext()` helper and using explicit request-part adapters for `param`, `query`, and `json` reads. diff --git a/prds/new-plugin-arch/webhooks-api.md b/prds/new-plugin-arch/webhooks-api.md index b8a4e670..c5ffce8c 100644 --- a/prds/new-plugin-arch/webhooks-api.md +++ b/prds/new-plugin-arch/webhooks-api.md @@ -15,7 +15,7 @@ Normal authenticated admin APIs are documented in `prds/new-plugin-arch/admin-ap ### GitHub webhook ingress -- `POST /api/webhooks/connectors/github` +- `POST /v1/webhooks/connectors/github` Purpose: