From 2212f5f35ea96ada792d84c99e0cdf3f94b490eb Mon Sep 17 00:00:00 2001 From: kmbroai <272643392+kmbroai@users.noreply.github.com> Date: Thu, 27 Aug 2026 05:05:11 +0000 Subject: [PATCH 1/4] feat: persist resumable local findings workflows --- docker/fixtures/mock-reviews.mjs | 2 +- sdk/typescript/README.md | 40 ++ .../_bundled_plugin/scripts/workbench_cli.py | 1 + .../_bundled_plugin/scripts/workbench_db.py | 7 + .../scripts/workbench_finding_workflows.py | 93 ++++ .../scripts/workbench_schema.py | 12 + sdk/typescript/plugin-files.json | 1 + sdk/typescript/scripts/check-package.mjs | 1 + .../scripts/fixtures/package-consumer.ts | 3 + .../scripts/smoke-findings-service.ts | 49 ++- sdk/typescript/src/api.ts | 130 +++++- sdk/typescript/src/cli.ts | 61 ++- sdk/typescript/src/custom-publish.ts | 75 +++- sdk/typescript/src/deduplication/scan.ts | 110 +++-- sdk/typescript/src/finding-workflow.ts | 159 +++++++ sdk/typescript/src/saved-scan.ts | 24 +- sdk/typescript/tests-ts/api.test.ts | 107 +++++ sdk/typescript/tests-ts/cli-dedupe.test.ts | 50 +++ sdk/typescript/tests-ts/cli-publish.test.ts | 47 +++ sdk/typescript/tests-ts/cli.test.ts | 25 ++ .../tests-ts/finding-workflow.test.ts | 399 ++++++++++++++++++ 21 files changed, 1318 insertions(+), 78 deletions(-) create mode 100644 sdk/typescript/_bundled_plugin/scripts/workbench_finding_workflows.py create mode 100644 sdk/typescript/src/finding-workflow.ts create mode 100644 sdk/typescript/tests-ts/finding-workflow.test.ts diff --git a/docker/fixtures/mock-reviews.mjs b/docker/fixtures/mock-reviews.mjs index 020d12b22..37e0ab427 100644 --- a/docker/fixtures/mock-reviews.mjs +++ b/docker/fixtures/mock-reviews.mjs @@ -68,7 +68,7 @@ const server = createServer(async (request, response) => { const execute = functions.find((tool) => tool.name === "exec"); assert.match(execute.description, /### `exec_command`/); const same = findings.every( - (finding) => finding.extensions.smokeGroup === "duplicate", + (finding) => finding.extensions?.smokeGroup !== "distinct", ); const result = stage === "screen" diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index b6106d09c..b727d0939 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -1495,6 +1495,46 @@ embeddings, or external tickets. They do not require an embedding API key or trigger model calls. Review-generated merged findings remain review outputs; they do not replace stored documents. +### Resuming a local findings workflow + +Add `--workflow-id` to opt into durable state shared by `scan`, `publish scan +--to custom`, and `dedupe`. The SDK equivalents are the optional `workflowId` +fields on `ScanOptions`, `PublishScanToCustomOptions`, and `DeduplicateScanOptions`. +Without a workflow ID, existing command behavior and output shapes are unchanged. + +```bash +codex-security scan /path/to/repository --workflow-id run-001 +codex-security publish scan --workflow-id run-001 --to custom --findings-url http://localhost:3000 +codex-security dedupe --workflow-id run-001 --findings-url http://localhost:3000 --json +``` + +Repeat this sequence with the same ID after a process stops. Completed scans and +acknowledged publications are reused; unfinished stages run again. If the scan +completed before the workflow recorded its receipt, recovery verifies the saved +scan and its sealed artifacts instead of scanning again. Scan IDs and artifact +locations are recorded in the scan-registration transaction. This resumes between +completed steps; it does not resume individual model turns inside an unfinished +scan. Existing output-directory and archive safeguards still apply to scan retries. + +Publication and dedupe can use the workflow ID in place of `--scan`; an explicit +scan selector must identify that same scan. A workflow can also begin at custom +publication of a completed scan. Dedupe still requires local scan history to locate +the approved source checkout. For a workflow, dedupe first completes publication +if its receipt is missing. `--all-repositories` retains its existing default of +false. Changing a workflow's scan, destination, or bound scope is an error: choose +a different workflow ID. Use one coordinating process per workflow. + +Workflow metadata, stage statuses, errors, publication receipts, and results live +in the local workbench SQLite database under `CODEX_SECURITY_STATE_DIR`, outside +the sealed scan artifacts. Successful empty results are stored as completed +results, not treated as missing work. An empty scan can complete workflow +publication with an empty receipt. Dry-run never advances a workflow stage. + +A completed `dedupe --workflow-id` returns its saved result without repeating +reviews or group writes. An interrupted dedupe stage currently runs its reviews +again; per-review checkpoints are a separate follow-up. A publication whose +acknowledgement was lost is retried using the service's existing idempotent upsert. + ### Deduplication workflow 1. For each distinct finding ID in the scan, request diff --git a/sdk/typescript/_bundled_plugin/scripts/workbench_cli.py b/sdk/typescript/_bundled_plugin/scripts/workbench_cli.py index a46d88734..ebc967cf6 100644 --- a/sdk/typescript/_bundled_plugin/scripts/workbench_cli.py +++ b/sdk/typescript/_bundled_plugin/scripts/workbench_cli.py @@ -339,6 +339,7 @@ def parse_args(description: str) -> argparse.Namespace: publication.add_argument("--input-file", required=True) subparsers.add_parser("database-info") + subparsers.add_parser("finding-workflow") subparsers.add_parser("store-findings") subparsers.add_parser("store-dedupe-groups") dedupe_groups = subparsers.add_parser("list-dedupe-groups") diff --git a/sdk/typescript/_bundled_plugin/scripts/workbench_db.py b/sdk/typescript/_bundled_plugin/scripts/workbench_db.py index 02715d7f6..8ce991efe 100644 --- a/sdk/typescript/_bundled_plugin/scripts/workbench_db.py +++ b/sdk/typescript/_bundled_plugin/scripts/workbench_db.py @@ -82,6 +82,7 @@ ) from workbench_feedback import get_scan_feedback from workbench_finding_index import index_findings +from workbench_finding_workflows import finding_workflow, register_workflow_scan from workbench_findings import ( find_potential_duplicates, list_dedupe_groups, @@ -1658,10 +1659,12 @@ def register_cli_scan(connection: sqlite3.Connection, args: argparse.Namespace) raise SystemExit("The scan artifact directory must be empty before the scan starts.") user_context = None + workflow_id = None if args.registration_json_stdin: registration = json.load(sys.stdin) recipe_json = json.dumps(registration["recipe"], ensure_ascii=False, separators=(",", ":")) user_context = registration.get("userContext") + workflow_id = registration.get("workflowId") else: recipe_json = sys.stdin.read() if args.recipe_json_stdin else args.recipe_json recipe = parse_scan_recipe(recipe_json, repository) @@ -1757,6 +1760,8 @@ def register_cli_scan(connection: sqlite3.Connection, args: argparse.Namespace) scan_id, ), ) + if workflow_id is not None: + register_workflow_scan(connection, workflow_id, scan_id, str(scan_dir), timestamp) connection.commit() except BaseException: connection.rollback() @@ -4032,6 +4037,8 @@ def main() -> None: result = export_findings(connection, args) elif args.command == "database-info": result = {"databasePath": str(database_path())} + elif args.command == "finding-workflow": + result = finding_workflow(connection, json.load(sys.stdin), now()) elif args.command == "store-findings": payload = json.load(sys.stdin) result = store_findings(connection, payload["entries"], now(), payload.get("repositoryId")) diff --git a/sdk/typescript/_bundled_plugin/scripts/workbench_finding_workflows.py b/sdk/typescript/_bundled_plugin/scripts/workbench_finding_workflows.py new file mode 100644 index 000000000..34c533e5a --- /dev/null +++ b/sdk/typescript/_bundled_plugin/scripts/workbench_finding_workflows.py @@ -0,0 +1,93 @@ +"""Durable state for the opt-in local scan, publication, and dedupe workflow.""" + +from __future__ import annotations + +import json +import sqlite3 +from typing import Any + + +def read_workflow(connection: sqlite3.Connection, workflow_id: str) -> dict[str, Any] | None: + row = connection.execute( + "SELECT state_json FROM finding_workflows WHERE id = ?", (workflow_id,) + ).fetchone() + return json.loads(row["state_json"]) if row is not None else None + + +def save_workflow(connection: sqlite3.Connection, state: dict[str, Any], timestamp: str) -> None: + connection.execute( + """INSERT INTO finding_workflows (id, state_json, created_at, updated_at) + VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET + state_json = excluded.state_json, updated_at = excluded.updated_at""", + (state["id"], json.dumps(state, allow_nan=False), timestamp, timestamp), + ) + + +def bind_workflow(state: dict[str, Any], binding: dict[str, Any]) -> None: + for field, value in binding.items(): + if field not in { + "repositoryPath", "scanRequestDigest", "scanId", "scanDir", + "artifactDigest", "destination", "scope", + }: + raise SystemExit("Unknown workflow binding.") + if field in state and state[field] != value: + raise SystemExit( + f"Workflow {state['id']} is already bound to a different {field}. " + "Use another --workflow-id." + ) + state[field] = value + + +def register_workflow_scan( + connection: sqlite3.Connection, workflow_id: str, scan_id: str, scan_dir: str, timestamp: str +) -> None: + """Called inside the scan-registration transaction, before model execution.""" + state = read_workflow(connection, workflow_id) + if state is None: + raise SystemExit("The workflow must be started before registering its scan.") + if state["stages"]["scan"]["status"] == "completed": + raise SystemExit("The workflow scan is already complete.") + previous = state.get("scanId") + if previous is not None: + row = connection.execute("SELECT status FROM scans WHERE id = ?", (previous,)).fetchone() + if row is not None and row["status"] == "complete": + raise SystemExit("Reuse the workflow's completed scan instead of registering another.") + state["scanId"] = scan_id + state["scanDir"] = scan_dir + save_workflow(connection, state, timestamp) + + +def finding_workflow( + connection: sqlite3.Connection, payload: dict[str, Any], timestamp: str +) -> dict[str, Any]: + workflow_id = payload["id"] + if not isinstance(workflow_id, str) or not workflow_id.strip(): + raise SystemExit("workflowId must be a nonempty string.") + if payload["action"] == "get": + return {"workflow": read_workflow(connection, workflow_id)} + connection.execute("BEGIN IMMEDIATE") + with connection: + state = read_workflow(connection, workflow_id) + if state is None: + state = { + "id": workflow_id, + "stages": {stage: {"status": "pending"} for stage in ("scan", "publish", "dedupe")}, + } + bind_workflow(state, payload.get("binding", {})) + action = payload["action"] + if action != "bind": + stage = payload["stage"] + if stage not in state["stages"]: + raise SystemExit("Unknown workflow stage.") + current = state["stages"][stage] + if current["status"] != "completed": + if action == "begin": + current["status"] = "running" + elif action == "complete": + state["stages"][stage] = {"status": "completed", "result": payload["result"]} + elif action == "fail": + current.update(status="failed", error=payload["error"]) + else: + raise SystemExit("Unknown workflow action.") + save_workflow(connection, state, timestamp) + return {"workflow": state} diff --git a/sdk/typescript/_bundled_plugin/scripts/workbench_schema.py b/sdk/typescript/_bundled_plugin/scripts/workbench_schema.py index c0bc44efc..45fb30f78 100644 --- a/sdk/typescript/_bundled_plugin/scripts/workbench_schema.py +++ b/sdk/typescript/_bundled_plugin/scripts/workbench_schema.py @@ -758,6 +758,18 @@ ON finding_dedupe_group_members(finding_id, group_id); """, ), + ( + 36, + "persist local findings workflows", + """ + CREATE TABLE finding_workflows ( + id TEXT PRIMARY KEY, + state_json TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ); + """, + ), ) diff --git a/sdk/typescript/plugin-files.json b/sdk/typescript/plugin-files.json index 2b857a36e..12cffada8 100644 --- a/sdk/typescript/plugin-files.json +++ b/sdk/typescript/plugin-files.json @@ -64,6 +64,7 @@ "scripts/workbench_db.py", "scripts/workbench_feedback.py", "scripts/workbench_finding_index.py", + "scripts/workbench_finding_workflows.py", "scripts/workbench_findings.py", "scripts/workbench_native_indexes.py", "scripts/workbench_progress.py", diff --git a/sdk/typescript/scripts/check-package.mjs b/sdk/typescript/scripts/check-package.mjs index fcb123838..6cb0a1076 100644 --- a/sdk/typescript/scripts/check-package.mjs +++ b/sdk/typescript/scripts/check-package.mjs @@ -200,6 +200,7 @@ const distFiles = new Set( "deduplication/codex-review", "deduplication/deduplication", "finding-retrieval", + "finding-workflow", "findings-client", "finding-dedupe-groups", "deduplication/deduplication-prompts", diff --git a/sdk/typescript/scripts/fixtures/package-consumer.ts b/sdk/typescript/scripts/fixtures/package-consumer.ts index ea0bcd5f4..045ce3076 100644 --- a/sdk/typescript/scripts/fixtures/package-consumer.ts +++ b/sdk/typescript/scripts/fixtures/package-consumer.ts @@ -23,6 +23,7 @@ export async function publishCustom( signal: AbortSignal, ): Promise { return await publishScanToCustom(scanDir, { + workflowId: "example-workflow", findingsUrl: "http://127.0.0.1:3000", signal, }); @@ -33,6 +34,7 @@ export async function dedupe( signal: AbortSignal, ): Promise { return await deduplicateScan(scanId, { + workflowId: "example-workflow", findingsUrl: "http://127.0.0.1:3000", allRepositories: true, signal, @@ -40,6 +42,7 @@ export async function dedupe( } const options: ScanOptions = { + workflowId: "example-workflow", target: DiffTarget.refs({ base: "HEAD~1" }), onProgress(progress: ScanProgress) { progress.filesCompleted satisfies number; diff --git a/sdk/typescript/scripts/smoke-findings-service.ts b/sdk/typescript/scripts/smoke-findings-service.ts index a4c1df59d..f8b74552c 100644 --- a/sdk/typescript/scripts/smoke-findings-service.ts +++ b/sdk/typescript/scripts/smoke-findings-service.ts @@ -221,31 +221,44 @@ function checkCliDeduplication(): void { "--prepare-scan", ]); for (const allRepositories of [false, true]) { - const actual: unknown = JSON.parse( - docker([ - "exec", - container, - "node", - "--import", - "/test/mock-reviews.mjs", - "dist/cli.js", - "dedupe", - "--scan", - "scan_example_001", - "--findings-url", - "http://127.0.0.1:3000", - "--json", - ...(allRepositories ? ["--all-repositories"] : []), - ]), - ); + const command = [ + "exec", + container, + "node", + "--import", + "/test/mock-reviews.mjs", + "dist/cli.js", + "dedupe", + "--scan", + manifest.scan.id, + "--workflow-id", + allRepositories ? "smoke-all" : "smoke-repository", + "--findings-url", + "http://127.0.0.1:3000", + "--json", + ...(allRepositories ? ["--all-repositories"] : []), + ]; + const actual: unknown = JSON.parse(docker(command)); const expected: DeduplicateScanResult = { - scanId: "scan_example_001", + scanId: manifest.scan.id, uniqueFindingIds: [ids[0]!], duplicateGroups: [ids.slice(0, 3)], deduplicationStatus: "completed", }; assert.deepEqual(actual, expected); + const calls = docker([ + "exec", + container, + "cat", + "/state/review-calls.jsonl", + ]); + assert.deepEqual(JSON.parse(docker(command)), expected); + assert.equal( + docker(["exec", container, "cat", "/state/review-calls.jsonl"]), + calls, + ); } + findings[0] = example!; } async function checkPages(): Promise { diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index d0e2a3133..22d5bcd61 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -12,7 +12,15 @@ import { } from "node:fs/promises"; import { randomUUID } from "node:crypto"; import { homedir, tmpdir } from "node:os"; -import { basename, dirname, isAbsolute, join, relative, sep } from "node:path"; +import { + basename, + dirname, + isAbsolute, + join, + relative, + resolve, + sep, +} from "node:path"; import { Codex, type CodexOptions, @@ -83,10 +91,12 @@ import { prepareKnowledgeBase, type PreparedKnowledgeBase, } from "./knowledge-base.js"; +import { FindingWorkflow, workflowDigest } from "./finding-workflow.js"; import { ScanResult, type RepositoryFinding, type TurnResultMetadata, + type ScanResultOptions, } from "./result.js"; import type { SeverityLevel } from "./models.js"; import { scanActivitiesFromEvent, type ScanActivity } from "./scan-activity.js"; @@ -106,6 +116,7 @@ import { CODEX_EXECUTABLE_VERSION, CODEX_SDK_VERSION } from "./version.js"; import { acquireCodexSecurityCredentialHomeLock, bootstrapPlugin, + bundledPluginRoot, cleanupSdkDirectory, codexSecurityCredentialAllowsAmbientImport, codexSecurityCredentialHome, @@ -207,6 +218,8 @@ export interface DeepScanOptions { } export interface ScanOptions extends DeepScanOptions { + /** Opt into a durable scan -> custom publication -> dedupe workflow. */ + workflowId?: string; auth?: ScanAuthMode; /** Stable, privacy-preserving end-user ID for this scan's model requests. */ safetyIdentifier?: string; @@ -433,8 +446,113 @@ export class CodexSecurity { options: ScanOptions = {}, ): Promise { return await this.#trackOperation(() => - this.#run(repository, { ...options }), + options.workflowId === undefined + ? this.#run(repository, { ...options }) + : this.#runWorkflow(repository, { ...options }, options.workflowId), + ); + } + + async #runWorkflow( + repository: string, + options: ScanOptions, + workflowId: string, + ): Promise { + this.#requireOpen(); + const signal = AbortSignal.any([ + this.#abortController.signal, + ...(options.signal ? [options.signal] : []), + ]); + const local = await this.#validateLocalInputs( + repository, + { ...options, outputDir: undefined, archiveExisting: false }, + signal, + ); + const workflow = new FindingWorkflow( + workflowId, + this.#dependencies.environment, + this.#dependencies.runWorkbench, + this.config.pythonPath, ); + if (options.outputDir !== undefined) + await workflow.protectArtifacts(options.outputDir); + const state = await workflow.bind({ + repositoryPath: local.repository, + scanRequestDigest: workflowDigest({ + config: this.config, + options: { + ...options, + target: options.target ?? "repository", + mode: options.mode ?? "standard", + outputDir: + options.outputDir === undefined + ? undefined + : resolve(expandHome(options.outputDir)), + workflowId: undefined, + signal: undefined, + auth: undefined, + archiveExisting: undefined, + }, + }), + }); + type ScanMetadata = Pick< + ScanResultOptions, + "threadId" | "turnResult" | "sarifPath" | "repositoryFindings" + >; + if (state.scanId && state.scanDir) { + await workflow.protectArtifacts(state.scanDir); + let metadata = state.stages.scan.result as ScanMetadata | undefined; + let completed = state.stages.scan.status === "completed"; + if (!completed) { + const scan = await workflow.registeredScan(state.scanId); + completed = + (scan["progress"] as JsonObject | undefined)?.["status"] === + "complete"; + if (completed) + metadata = { + threadId: (scan["continuationThreadId"] as string) ?? "", + turnResult: { status: "completed" }, + }; + } + if (completed) { + const contract = await loadContract(state.scanDir, { + pluginRoot: await bundledPluginRoot(), + expectedScanId: state.scanId, + signal, + }); + await workflow.bind({ artifactDigest: workflowDigest(contract) }); + metadata ??= { threadId: "", turnResult: { status: "completed" } }; + await workflow.complete("scan", metadata); + return new ScanResult({ + ...contract, + scanDir: state.scanDir, + ...metadata, + }); + } + } + await workflow.begin("scan"); + try { + const result = await this.#run(repository, options); + await workflow.protectArtifacts(result.scanDir); + await workflow.bind({ + scanId: result.manifest.scan.id, + scanDir: result.scanDir, + artifactDigest: workflowDigest({ + manifest: result.manifest, + findings: result.findings, + coverage: result.coverage, + }), + }); + await workflow.complete("scan", { + threadId: result.threadId, + turnResult: result.turnResult, + sarifPath: result.sarifPath, + repositoryFindings: result.repositoryFindings, + } satisfies ScanMetadata); + return result; + } catch (error) { + await workflow.fail("scan", error); + throw error; + } } public async validate(options: ValidationOptions): Promise { @@ -943,7 +1061,13 @@ export class CodexSecurity { ? [] : ["--parent-scan-id", options.parentScanId]), ], - JSON.stringify({ recipe, userContext: options.scanPrompt }), + JSON.stringify({ + recipe, + userContext: options.scanPrompt, + ...(options.workflowId === undefined + ? {} + : { workflowId: options.workflowId }), + }), ); const scanId = registration["scanId"]; const targetId = registration["targetId"]; diff --git a/sdk/typescript/src/cli.ts b/sdk/typescript/src/cli.ts index 128804fed..d232b2c8b 100644 --- a/sdk/typescript/src/cli.ts +++ b/sdk/typescript/src/cli.ts @@ -57,7 +57,11 @@ import { import { accountStatus } from "./auth.js"; import { publishScanToCustom } from "./custom-publish.js"; import { deduplicateScanInternal } from "./deduplication/scan.js"; -import { resolveCompletedScan, type SavedScan } from "./saved-scan.js"; +import { + resolveCompletedScan, + resolveWorkflowScan, + type SavedScan, +} from "./saved-scan.js"; import { publishFindingsCsvToCloud, publishScanToCloud, @@ -228,6 +232,7 @@ const EXPORT_DEFAULT_OUTPUTS = { sarif: "results.sarif", } as const; const VALUE_OPTIONS = new Set([ + "--workflow-id", "--auth", "--safety-identifier", "--path", @@ -954,6 +959,7 @@ export function resolveCliPath(directory: string, value: string): string { } interface ScanArguments extends DeepScanOptions { + workflowId?: string; auth?: ScanAuthMode; safetyIdentifier?: string; verbose?: boolean; @@ -2038,6 +2044,11 @@ export async function main( .describe("Completed scan directory; omit to select a saved scan."), }), options: PUBLICATION_DESTINATION_OPTIONS.extend({ + workflowId: optionValue("--workflow-id") + .optional() + .describe( + "Resume the named local scan, custom publication, and dedupe workflow.", + ), scan: z .array(optionValue("--scan")) .default([]) @@ -2212,6 +2223,11 @@ export async function main( "--findings-url is only supported with --to custom.", ); } + if (options.workflowId !== undefined && options.to !== "custom") { + throw new CodexSecurityError( + "--workflow-id is only supported with --to custom.", + ); + } const destination = options.to === "linear" ? publicationDestination( @@ -2243,6 +2259,11 @@ export async function main( selectedScans.push(scan); } } + if (options.workflowId !== undefined && selectedScans.length === 0) { + selectedScans.push( + await resolveWorkflowScan(options.workflowId, dependencies), + ); + } let scanDir = selectedScans[0]?.scanDir; let publicationRepository = scanDir === undefined ? "scan" : basename(scanDir); @@ -2513,6 +2534,9 @@ export async function main( dependencies.publishScanToCustom ?? publishScanToCustom )(resolveCliPath(currentDirectory, scanDir), { findingsUrl: options.findingsUrl!, + ...(options.workflowId === undefined + ? {} + : { workflowId: options.workflowId }), dryRun: options.dryRun, signal: controller.signal, ...(selectedScans[0]?.scanId === undefined @@ -2735,6 +2759,11 @@ export async function main( }), options: z .object({ + workflowId: optionValue("--workflow-id") + .optional() + .describe( + "Reuse completed work in the named local findings workflow.", + ), auth: z .enum(SCAN_AUTH_MODES) .default("auto") @@ -2924,6 +2953,7 @@ export async function main( const outcome = await runScan( { auth: options.auth, + workflowId: options.workflowId, safetyIdentifier: options.safetyIdentifier, verbose: options.verbose, repository: args.repository, @@ -3061,9 +3091,14 @@ export async function main( destructive: true, mcp: false, options: z.object({ - scan: optionValue("--scan").describe( - "Saved scan ID, unique prefix, or latest.", - ), + workflowId: optionValue("--workflow-id") + .optional() + .describe( + "Resume the named local findings workflow; reuses its saved scan.", + ), + scan: optionValue("--scan") + .optional() + .describe("Saved scan ID, unique prefix, or latest."), allRepositories: z .boolean() .default(false) @@ -3092,12 +3127,25 @@ export async function main( dependencies.addSignalListener("SIGINT", onInterrupt); dependencies.addSignalListener("SIGTERM", onTerminate); try { + const scanId = + options.scan ?? + (options.workflowId === undefined + ? undefined + : (await resolveWorkflowScan(options.workflowId, dependencies)) + .scanId); + if (scanId === undefined) + throw new CodexSecurityError( + "Deduplication requires --scan or --workflow-id.", + ); return await ( dependencies.deduplicateScan ?? deduplicateScanInternal )( - options.scan, + scanId, { findingsUrl: options.findingsUrl, + ...(options.workflowId === undefined + ? {} + : { workflowId: options.workflowId }), allRepositories: options.allRepositories, signal: controller.signal, }, @@ -6069,6 +6117,9 @@ async function executeScan( } security = dependencies.createSecurity(config); const options: ScanOptions = { + ...(arguments_.workflowId === undefined + ? {} + : { workflowId: arguments_.workflowId }), auth, safetyIdentifier: arguments_.safetyIdentifier, target, diff --git a/sdk/typescript/src/custom-publish.ts b/sdk/typescript/src/custom-publish.ts index 2119611f4..ae2cf7697 100644 --- a/sdk/typescript/src/custom-publish.ts +++ b/sdk/typescript/src/custom-publish.ts @@ -1,10 +1,17 @@ -import { loadContract } from "./contract.js"; +import { loadContractWithScanDirectory } from "./contract.js"; import { CodexSecurityError } from "./errors.js"; import { FindingsClient, type FindingsRequest } from "./findings-client.js"; import type { Finding } from "./models.js"; -import { bundledPluginRoot } from "./runtime.js"; +import { bundledPluginRoot, type runWorkbench } from "./runtime.js"; +import { + FindingWorkflow, + workflowDestination, + workflowDigest, +} from "./finding-workflow.js"; export interface PublishScanToCustomOptions { + /** Resume publication within the named local findings workflow. */ + workflowId?: string; /** Findings API base URL, such as http://localhost:3000. */ findingsUrl: string; /** Validate and preview the upload without making an HTTP request. */ @@ -34,31 +41,55 @@ export async function publishScanToCustom( export async function publishScanToCustomInternal( scanDirectory: string, options: PublishScanToCustomOptions, - dependencies: { fetch?: FindingsRequest } = {}, + dependencies: { + fetch?: FindingsRequest; + environment?: NodeJS.ProcessEnv; + runWorkbench?: typeof runWorkbench; + } = {}, ): Promise { - const { manifest, findings } = await loadContract(scanDirectory, { - pluginRoot: await bundledPluginRoot(), - signal: options.signal, - expectedScanId: options.expectedScanId, - }); - if (findings.findings.length === 0) { + const { contract, scanDirectory: canonicalDirectory } = + await loadContractWithScanDirectory(scanDirectory, { + pluginRoot: await bundledPluginRoot(), + signal: options.signal, + expectedScanId: options.expectedScanId, + }); + const { manifest, findings } = contract; + if (findings.findings.length === 0 && options.workflowId === undefined) { throw new CodexSecurityError( "The completed scan has no findings to publish.", ); } const repositoryId = manifest.scan.target.targetId; - const findingIds = options.dryRun - ? findings.findings.map((finding) => finding.findingId) - : await new FindingsClient( - options.findingsUrl, - options.signal, - dependencies.fetch, - ).publish(findings.findings, repositoryId); - return { - scanId: manifest.scan.id, - repositoryId, - findingIds, - findingCount: findingIds.length, - ...(options.dryRun ? { dryRun: true, findings: findings.findings } : {}), + const publish = async (): Promise => { + const findingIds = options.dryRun + ? findings.findings.map((finding) => finding.findingId) + : await new FindingsClient( + options.findingsUrl, + options.signal, + dependencies.fetch, + ).publish(findings.findings, repositoryId); + return { + scanId: manifest.scan.id, + repositoryId, + findingIds, + findingCount: findingIds.length, + ...(options.dryRun ? { dryRun: true, findings: findings.findings } : {}), + }; }; + if (options.workflowId === undefined || options.dryRun) + return await publish(); + const workflow = new FindingWorkflow( + options.workflowId, + dependencies.environment, + dependencies.runWorkbench, + ); + await workflow.protectArtifacts(canonicalDirectory); + await workflow.bind({ + scanId: manifest.scan.id, + scanDir: canonicalDirectory, + artifactDigest: workflowDigest(contract), + destination: workflowDestination(options.findingsUrl), + }); + await workflow.complete("scan", null); + return await workflow.run("publish", publish); } diff --git a/sdk/typescript/src/deduplication/scan.ts b/sdk/typescript/src/deduplication/scan.ts index e53cb4841..3029c9017 100644 --- a/sdk/typescript/src/deduplication/scan.ts +++ b/sdk/typescript/src/deduplication/scan.ts @@ -1,4 +1,4 @@ -import { loadContract } from "../contract.js"; +import { loadContractWithScanDirectory } from "../contract.js"; import { bundledPluginRoot, codexSecurityStateDirectory, @@ -20,8 +20,16 @@ import { } from "./deduplication-reviewer.js"; import { FindingsClient, type FindingsRequest } from "../findings-client.js"; import type { FindingSearchScope } from "../finding-retrieval.js"; +import { + FindingWorkflow, + workflowDestination, + workflowDigest, +} from "../finding-workflow.js"; +import { publishScanToCustomInternal } from "../custom-publish.js"; export interface DeduplicateScanOptions { + /** Resume the named local findings workflow, including custom publication. */ + workflowId?: string; /** Findings API base URL. The scan's findings must already be indexed there. */ findingsUrl: string; /** Search all repositories instead of the saved scan's targetId. Defaults to false. */ @@ -77,11 +85,14 @@ export async function deduplicateScanInternal( ); }), }); - const contract = await loadContract(scan.scanDir, { - pluginRoot, - expectedScanId: scan.scanId, - signal: options.signal, - }); + const { contract, scanDirectory } = await loadContractWithScanDirectory( + scan.scanDir, + { + pluginRoot, + expectedScanId: scan.scanId, + signal: options.signal, + }, + ); const client = new FindingsClient( options.findingsUrl, options.signal, @@ -91,29 +102,72 @@ export async function deduplicateScanInternal( options.allRepositories === true ? { allRepositories: true } : { repositoryId: contract.manifest.scan.target.targetId }; - const deduplicator = new FindingDeduplicator( - { - potentialDuplicates: (findingId) => - client.potentialDuplicates(findingId, scope), - }, - dependencies.reviewer ?? - new CodexDeduplicationReviewer( - new CodexReviewRunner( + const workflow = + options.workflowId === undefined + ? undefined + : new FindingWorkflow( + options.workflowId, environment, - undefined, - options.signal, - scan["targetPath"] as string, + dependencies.runWorkbench === undefined + ? undefined + : (_options, args, input) => + dependencies.runWorkbench!(args, input), + ); + if (workflow) { + await workflow.protectArtifacts(scanDirectory); + await workflow.bind({ + scanId: scan.scanId, + scanDir: scanDirectory, + artifactDigest: workflowDigest(contract), + destination: workflowDestination(options.findingsUrl), + scope, + }); + await workflow.complete("scan", null); + await publishScanToCustomInternal( + scanDirectory, + { + findingsUrl: options.findingsUrl, + workflowId: options.workflowId, + expectedScanId: scan.scanId, + signal: options.signal, + }, + { + environment, + fetch: dependencies.fetch, + runWorkbench: + dependencies.runWorkbench === undefined + ? undefined + : (_options, args, input) => + dependencies.runWorkbench!(args, input), + }, + ); + } + const dedupe = async (): Promise => { + const deduplicator = new FindingDeduplicator( + { + potentialDuplicates: (findingId) => + client.potentialDuplicates(findingId, scope), + }, + dependencies.reviewer ?? + new CodexDeduplicationReviewer( + new CodexReviewRunner( + environment, + undefined, + options.signal, + scan["targetPath"] as string, + ), ), - ), - options.signal, - ); - const result = await deduplicator.run( - contract.findings.findings.map((finding) => finding.findingId), - ); - options.signal?.throwIfAborted(); - await client.storeDedupeGroups(result.duplicateGroups); - return { - scanId: scan.scanId, - ...result, + options.signal, + ); + const result = await deduplicator.run( + contract.findings.findings.map((finding) => finding.findingId), + ); + options.signal?.throwIfAborted(); + await client.storeDedupeGroups(result.duplicateGroups); + return { + scanId: scan.scanId, + ...result, + }; }; + return workflow ? await workflow.run("dedupe", dedupe) : await dedupe(); } diff --git a/sdk/typescript/src/finding-workflow.ts b/sdk/typescript/src/finding-workflow.ts new file mode 100644 index 000000000..3016654b0 --- /dev/null +++ b/sdk/typescript/src/finding-workflow.ts @@ -0,0 +1,159 @@ +import { createHash } from "node:crypto"; +import { isAbsolute, relative, sep } from "node:path"; +import type { JsonObject } from "./config.js"; +import { CodexSecurityError, safeErrorMessage } from "./errors.js"; +import type { FindingSearchScope } from "./finding-retrieval.js"; +import { + bundledPluginRoot, + canonicalizeModelSafePath, + codexSecurityStateDirectory, + resolvePluginPython, + runWorkbench, + type WorkbenchCommandOptions, +} from "./runtime.js"; + +export type WorkflowStage = "scan" | "publish" | "dedupe"; +export interface WorkflowBinding { + repositoryPath?: string; + scanRequestDigest?: string; + scanId?: string; + scanDir?: string; + artifactDigest?: string; + destination?: string; + scope?: FindingSearchScope; +} +export interface WorkflowState extends WorkflowBinding { + id: string; + stages: Record< + WorkflowStage, + { + status: "pending" | "running" | "failed" | "completed"; + result?: unknown; + error?: string; + } + >; +} + +export function workflowDigest(value: unknown): string { + const json = JSON.stringify(value, (_key, item: unknown) => + item !== null && typeof item === "object" && !Array.isArray(item) + ? Object.fromEntries( + Object.entries(item).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)), + ) + : item, + ); + return createHash("sha256").update(json).digest("hex"); +} + +export function workflowDestination(url: string): string { + const route = "v1/bulk/findings"; + const destination = new URL(route, url.endsWith("/") ? url : `${url}/`); + destination.username = ""; + destination.password = ""; + return destination.href.slice(0, -route.length); +} + +/** State lives in the workbench database, never in sealed scan artifacts. */ +export class FindingWorkflow { + private options?: Promise; + + constructor( + readonly id: string, + private readonly environment: NodeJS.ProcessEnv = process.env, + private readonly workbench: typeof runWorkbench = runWorkbench, + private readonly pythonPath?: string, + ) { + if (!id.trim()) + throw new CodexSecurityError("workflowId must be a nonempty string."); + } + + async protectArtifacts(scanDir: string): Promise { + const path = relative( + await canonicalizeModelSafePath(scanDir), + await canonicalizeModelSafePath( + codexSecurityStateDirectory(this.environment), + ), + ); + if ( + path === "" || + (!isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`)) + ) { + throw new CodexSecurityError( + "Workflow state must be outside the sealed scan artifacts.", + ); + } + } + + async get(): Promise { + return (await this.command({ action: "get" })) as WorkflowState | null; + } + + async bind(binding: WorkflowBinding): Promise { + return (await this.command({ action: "bind", binding }))!; + } + + async begin(stage: WorkflowStage): Promise { + return (await this.command({ action: "begin", stage }))!; + } + + async complete( + stage: WorkflowStage, + result: unknown, + ): Promise { + return (await this.command({ action: "complete", stage, result }))!; + } + + async fail(stage: WorkflowStage, error: unknown): Promise { + await this.command({ + action: "fail", + stage, + error: safeErrorMessage(error), + }).catch(() => undefined); + } + + async run(stage: WorkflowStage, operation: () => Promise): Promise { + const state = await this.begin(stage); + if (state.stages[stage].status === "completed") + return state.stages[stage].result as T; + try { + const result = await operation(); + await this.complete(stage, result); + return result; + } catch (error) { + await this.fail(stage, error); + throw error; + } + } + + async registeredScan(scanId: string): Promise { + const context = await this.call(["get-scan", "--scan-id", scanId]); + return context["scan"] as JsonObject; + } + + private async command(payload: object): Promise { + const result = await this.call( + ["finding-workflow"], + JSON.stringify({ id: this.id, ...payload }), + ); + return result["workflow"] as unknown as WorkflowState | null; + } + + private async call( + args: readonly string[], + input?: string, + ): Promise { + this.options ??= (async () => ({ + pluginRoot: await bundledPluginRoot(), + python: await resolvePluginPython({ + environment: this.environment, + configuredPath: this.pythonPath, + }), + environment: { + ...this.environment, + CODEX_SECURITY_STATE_DIR: codexSecurityStateDirectory(this.environment), + }, + failureMessage: "Could not save or resume the findings workflow", + }))(); + return await this.workbench(await this.options, args, input); + } +} diff --git a/sdk/typescript/src/saved-scan.ts b/sdk/typescript/src/saved-scan.ts index 8c2d04eb0..b73e50a44 100644 --- a/sdk/typescript/src/saved-scan.ts +++ b/sdk/typescript/src/saved-scan.ts @@ -8,7 +8,29 @@ export type SavedScan = JsonObject & { scanId: string; scanDir: string }; export interface SavedScanDependencies { currentDirectory(): string; - runWorkbench(args: readonly string[]): Promise; + runWorkbench(args: readonly string[], input?: string): Promise; +} + +export async function resolveWorkflowScan( + workflowId: string, + dependencies: SavedScanDependencies, +): Promise<{ scanId: string; scanDir: string }> { + const context = await dependencies.runWorkbench( + ["finding-workflow"], + JSON.stringify({ id: workflowId, action: "get" }), + ); + const workflow = context["workflow"]; + if ( + workflow === undefined || + !isJsonObject(workflow) || + typeof workflow["scanId"] !== "string" || + typeof workflow["scanDir"] !== "string" + ) { + throw new CodexSecurityError( + `Workflow ${workflowId} has no saved scan. Start it with scan --workflow-id ${workflowId}.`, + ); + } + return { scanId: workflow["scanId"], scanDir: workflow["scanDir"] }; } export async function resolveCompletedScan( diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index 74836e58d..15d02a5be 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -67,6 +67,7 @@ import { preparedRuntime, } from "./support/api-events.js"; import { runTestInSubprocess } from "./support/test-subprocess.js"; +import { FindingWorkflow } from "../src/finding-workflow.js"; type ScanObserverName = Parameters< NonNullable @@ -78,6 +79,112 @@ const { cleanup, copyCompletedScan, temporaryDirectory } = createApiTestFixtures(); afterEach(cleanup); +test.each(["completed", "receipt-lost", "scan-interrupted"])( + "durable scan workflow resumes after %s without rerunning completed work", + async (scenario) => { + const root = await temporaryDirectory(); + const repository = join(root, "repository"); + const scanDir = join(root, "scan"); + await mkdir(repository); + await mkdir(scanDir, { mode: 0o700 }); + const environment = { + PATH: process.env["PATH"], + SystemRoot: process.env["SystemRoot"], + CODEX_SECURITY_STATE_DIR: join(root, "state"), + }; + const workflowId = "durable-scan"; + let modelCalls = 0; + let completed = false; + let loseReceipt = scenario === "receipt-lost"; + const makeClient = async (attempt: number) => { + const codexHome = join(root, `codex-home-${attempt}`); + await mkdir(codexHome); + return new TestClient( + {}, + { + environment, + prepareRuntime: async () => preparedRuntime(codexHome), + resolvePluginPython: async () => "/managed/python", + prepareOutputDir: async () => scanDir, + repositoryRevision: async () => "deadbeef", + runWorkbench: async (options, args, input) => { + if (args[0] === "finding-workflow") { + const payload = JSON.parse(input!); + if ( + loseReceipt && + payload.action === "complete" && + payload.stage === "scan" + ) { + loseReceipt = false; + throw new Error("Synthetic receipt write failure"); + } + return await runWorkbench(options, args, input); + } + if (args[0] === "get-scan") + return { + scan: { + progress: { status: completed ? "complete" : "failed" }, + continuationThreadId: "thread-1", + }, + }; + if (args[0] === "register-cli-scan") { + expect(JSON.parse(input!).workflowId).toBe(workflowId); + const registration = mockScanRegistration(args, input); + await new FindingWorkflow(workflowId, environment).bind({ + scanId: registration["scanId"] as string, + scanDir, + }); + return registration; + } + if (args[0] === "complete-scan") completed = true; + return mockWorkbench(args, input); + }, + createCodex: () => ({ + startThread: () => ({ + id: "thread-1", + async runStreamed() { + modelCalls++; + if (scenario === "scan-interrupted" && modelCalls === 1) + throw new Error("Synthetic interrupted scan"); + await copyCompletedScan(root); + return { events: completedEvents() }; + }, + }), + }), + }, + ); + }; + const first = await makeClient(1); + let original: Record | undefined; + try { + if (scenario === "completed") + original = (await first.run(repository, { workflowId })).toJSON(); + else + await expect(first.run(repository, { workflowId })).rejects.toThrow( + "Synthetic", + ); + } finally { + await first.close(); + } + const resumed = await makeClient(2); + try { + const result = await resumed.run(repository, { workflowId }); + expect(result.manifest.scan.id).toBe("scan_example_001"); + if (original) expect(result.toJSON()).toEqual(original); + expect(modelCalls).toBe(scenario === "scan-interrupted" ? 2 : 1); + expect( + (await new FindingWorkflow(workflowId, environment).get())?.stages.scan + .status, + ).toBe("completed"); + await expect( + resumed.run(repository, { workflowId, mode: "deep" }), + ).rejects.toThrow("already bound to a different"); + } finally { + await resumed.close(); + } + }, +); + const EXTERNAL_PROVIDER_CASES = [ [ "OpenRouter", diff --git a/sdk/typescript/tests-ts/cli-dedupe.test.ts b/sdk/typescript/tests-ts/cli-dedupe.test.ts index 247ee98de..896162b97 100644 --- a/sdk/typescript/tests-ts/cli-dedupe.test.ts +++ b/sdk/typescript/tests-ts/cli-dedupe.test.ts @@ -11,6 +11,56 @@ const args = [ "--json", ]; +test("dedupe resolves a workflow's pinned scan and passes the workflow ID to the SDK", async () => { + const deps = dependencies(); + deps.runWorkbench = async (args, input) => { + expect(args).toEqual(["finding-workflow"]); + expect(JSON.parse(input!)).toEqual({ + id: "workflow-example", + action: "get", + }); + return { + workflow: { + id: "workflow-example", + scanId: "exact-scan", + scanDir: "/synthetic/artifacts", + }, + }; + }; + deps.deduplicateScan = async (scanId, options) => { + expect(scanId).toBe("exact-scan"); + expect(options.workflowId).toBe("workflow-example"); + return { + scanId, + uniqueFindingIds: [], + duplicateGroups: [], + deduplicationStatus: "completed", + }; + }; + const stdout = capture(); + expect( + await main( + [ + "dedupe", + "--workflow-id", + "workflow-example", + "--findings-url", + "http://localhost:3000", + "--json", + ], + stdout.stream, + capture().stream, + deps, + ), + ).toBe(0); + expect(JSON.parse(stdout.text())).toEqual({ + scanId: "exact-scan", + uniqueFindingIds: [], + duplicateGroups: [], + deduplicationStatus: "completed", + }); +}); + test.each([false, true])( "dedupe passes the scan selector, URL, and all-repository scope %s to the SDK", async (allRepositories) => { diff --git a/sdk/typescript/tests-ts/cli-publish.test.ts b/sdk/typescript/tests-ts/cli-publish.test.ts index 4e4bc043b..d65d6422c 100644 --- a/sdk/typescript/tests-ts/cli-publish.test.ts +++ b/sdk/typescript/tests-ts/cli-publish.test.ts @@ -75,6 +75,53 @@ function publicationResult( } describe("publish scan to custom", () => { + test("uses a workflow's exact scan without changing the publication receipt", async () => { + const [scanDir] = await publicationScanDirectories(1); + const deps = dependencies({ + onWorkbench: (args) => { + expect(args).toEqual(["finding-workflow"]); + return { + workflow: { + id: "publication-workflow", + scanId: "exact-scan", + scanDir: scanDir!, + }, + }; + }, + }); + const receipt = { + scanId: "exact-scan", + repositoryId: "repository", + findingIds: ["finding"], + findingCount: 1, + }; + deps.publishScanToCustom = async (directory, options) => { + expect(directory).toBe(scanDir!); + expect(options.workflowId).toBe("publication-workflow"); + expect(options.expectedScanId).toBe("exact-scan"); + return receipt; + }; + const stdout = capture(); + expect( + await main( + [ + "publish", + "scan", + "--workflow-id", + "publication-workflow", + "--to", + "custom", + "--findings-url", + "http://localhost:3000", + "--json", + ], + stdout.stream, + capture().stream, + deps, + ), + ).toBe(0); + expect(JSON.parse(stdout.text())).toEqual(receipt); + }); test.each([false, true])( "publishes a selected saved scan with dry-run=%s", async (dryRun) => { diff --git a/sdk/typescript/tests-ts/cli.test.ts b/sdk/typescript/tests-ts/cli.test.ts index f8d246400..7d591f614 100644 --- a/sdk/typescript/tests-ts/cli.test.ts +++ b/sdk/typescript/tests-ts/cli.test.ts @@ -1758,6 +1758,31 @@ describe("CLI", () => { }, ); + test("passes the workflow ID to scans without changing their JSON output", async () => { + const deps = dependencies(); + const result = fakeResult(); + deps.createSecurity = () => ({ + run: async (_repository, options) => { + expect(options?.workflowId).toBe("scan-workflow"); + return result; + }, + preflight: async () => fakePreflight(), + close: async () => {}, + }); + const stdout = capture(); + expect( + await main( + ["scan", ".", "--workflow-id", "scan-workflow", "--json"], + stdout.stream, + capture().stream, + deps, + ), + ).toBe(0); + expect(JSON.parse(stdout.text())).toMatchObject({ + scanDir: result.scanDir, + }); + }); + test("keeps verbose diagnostics separate from interactive progress", async () => { const stdout = capture(); const stderr = capture(true); diff --git a/sdk/typescript/tests-ts/finding-workflow.test.ts b/sdk/typescript/tests-ts/finding-workflow.test.ts new file mode 100644 index 000000000..ead0810e0 --- /dev/null +++ b/sdk/typescript/tests-ts/finding-workflow.test.ts @@ -0,0 +1,399 @@ +import { spawn } from "node:child_process"; +import { createHash } from "node:crypto"; +import { once } from "node:events"; +import { + chmod, + cp, + mkdir, + mkdtemp, + readFile, + rm, + writeFile, +} from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, expect, test } from "bun:test"; +import { + FindingWorkflow, + workflowDestination, +} from "../src/finding-workflow.js"; +import { publishScanToCustomInternal } from "../src/custom-publish.js"; +import { deduplicateScanInternal } from "../src/deduplication/scan.js"; +import { resolvePluginPython, runWorkbench } from "../src/runtime.js"; +import type { FindingsDocument, ScanManifest } from "../src/models.js"; +import { PLUGIN_ROOT } from "./plugin-root.js"; + +const directories: string[] = []; +afterEach(async () => { + await Promise.all( + directories + .splice(0) + .map((directory) => rm(directory, { recursive: true, force: true })), + ); +}); + +async function fixture() { + const root = await mkdtemp(join(tmpdir(), "findings-workflow-")); + directories.push(root); + const scanDir = join(root, "scan"); + const repository = join(root, "repository"); + await mkdir(repository); + await cp(join(PLUGIN_ROOT, "examples/completed-scan"), scanDir, { + recursive: true, + }); + if (process.platform !== "win32") await chmod(scanDir, 0o700); + const environment = { + PATH: process.env["PATH"], + SystemRoot: process.env["SystemRoot"], + TEMP: process.env["TEMP"], + TMP: process.env["TMP"], + CODEX_HOME: join(root, "codex"), + CODEX_SECURITY_STATE_DIR: join(root, "state"), + }; + const document = JSON.parse( + await readFile(join(scanDir, "findings.json"), "utf8"), + ) as FindingsDocument; + const workbenchOptions = { + environment, + pluginRoot: PLUGIN_ROOT, + python: await resolvePluginPython({ environment }), + }; + const history = async (args: readonly string[], input?: string) => + args[0] === "get-scan" + ? { + scan: { + scanId: document.scanId, + scanDir, + targetPath: repository, + progress: { status: "complete" }, + }, + } + : await runWorkbench(workbenchOptions, args, input); + return { + root, + repository, + scanDir, + environment, + document, + history, + workbenchOptions, + }; +} + +test("scan registration commits its workflow identity atomically and rolls back failed registration", async () => { + const { root, repository, environment, workbenchOptions } = await fixture(); + const workflow = new FindingWorkflow("registered-workflow", environment); + await workflow.bind({ repositoryPath: repository }); + await workflow.begin("scan"); + const register = async (workflowId: string, suffix: string) => { + const directory = join(root, suffix); + await mkdir(directory, { mode: 0o700 }); + return await runWorkbench( + workbenchOptions, + [ + "register-cli-scan", + "--repository", + repository, + "--scan-dir", + directory, + "--registration-json-stdin", + ], + JSON.stringify({ + workflowId, + recipe: { + repository, + mode: "standard", + target: { kind: "repository", paths: [] }, + config: {}, + }, + }), + ); + }; + const registered = await register("registered-workflow", "registered-scan"); + expect( + await new FindingWorkflow("registered-workflow", environment).get(), + ).toMatchObject({ + scanId: registered["scanId"], + scanDir: registered["scanDir"], + stages: { scan: { status: "running" } }, + }); + await expect( + register("unknown-workflow", "rolled-back-scan"), + ).rejects.toThrow("must be started"); + const history = await runWorkbench(workbenchOptions, [ + "list-scans", + "--repository", + repository, + ]); + expect(history["scans"]).toHaveLength(1); + const retried = await register("registered-workflow", "retried-scan"); + expect(retried["scanId"]).not.toBe(registered["scanId"]); + expect((await workflow.get())?.scanId).toBe(retried["scanId"] as string); +}); + +test.each(["running", "completed"])( + "survives termination with a %s stage and resumes in a fresh process", + async (status) => { + const { environment } = await fixture(); + const source = `import { FindingWorkflow } from ${JSON.stringify(new URL("../src/finding-workflow.ts", import.meta.url).href)}; +const workflow = new FindingWorkflow("interrupted", process.env); +const timer = setInterval(() => {}, 1000); +await workflow.run("publish", async () => { + if (${JSON.stringify(status)} === "running") { + process.stdout.write("ready\\n"); + await new Promise(() => {}); + } + return { findingIds: [] }; +}); +process.stdout.write("ready\\n");`; + const child = spawn(process.execPath, ["--eval", source], { + env: environment, + stdio: ["ignore", "pipe", "pipe"], + windowsHide: true, + }); + const closed = once(child, "close"); + let errors = ""; + child.stderr.on("data", (chunk) => { + errors += String(chunk); + }); + try { + const ready = await Promise.race([ + once(child.stdout, "data").then(([data]) => String(data)), + closed.then(() => { + throw new Error( + errors || "Workflow child exited before the checkpoint", + ); + }), + ]); + expect(ready).toContain("ready"); + child.kill(); + await closed; + const workflow = new FindingWorkflow("interrupted", environment); + expect((await workflow.get())?.stages.publish.status).toBe(status); + let attempts = 0; + expect( + await workflow.run("publish", async () => { + attempts++; + return { findingIds: [] }; + }), + ).toEqual({ findingIds: [] }); + expect(attempts).toBe(status === "running" ? 1 : 0); + expect( + (await new FindingWorkflow("interrupted", environment).get())?.stages + .publish, + ).toEqual({ status: "completed", result: { findingIds: [] } }); + } finally { + if (child.exitCode === null && child.signalCode === null) child.kill(); + await closed; + } + }, +); + +test("reuses publication after dedupe failure and persists a successful empty duplicate result", async () => { + const { scanDir, environment, document, history } = await fixture(); + const original = await readFile(join(scanDir, "findings.json"), "utf8"); + const workflowId = "resume-example"; + const options = { workflowId, findingsUrl: "http://synthetic.test/service" }; + let publications = 0; + let lookups = 0; + let unavailable = true; + const request = async (url: URL) => { + if (url.pathname.endsWith("/bulk/findings")) { + publications++; + return Response.json( + document.findings.map((finding) => finding.findingId), + ); + } + lookups++; + if (unavailable) throw new Error("Synthetic lookup failure"); + return Response.json({ + finding: document.findings[0], + potentialDuplicates: [], + }); + }; + const receipt = await publishScanToCustomInternal(scanDir, options, { + environment, + fetch: request, + }); + await expect( + deduplicateScanInternal(document.scanId, options, { + environment, + fetch: request, + runWorkbench: history, + }), + ).rejects.toThrow("Synthetic lookup failure"); + const workflow = new FindingWorkflow(workflowId, environment); + expect((await workflow.get())?.stages).toMatchObject({ + scan: { status: "completed" }, + publish: { status: "completed", result: receipt }, + dedupe: { status: "failed", error: "Synthetic lookup failure" }, + }); + expect(publications).toBe(1); + unavailable = false; + const result = await deduplicateScanInternal(document.scanId, options, { + environment, + fetch: request, + runWorkbench: history, + }); + expect(result).toEqual({ + scanId: document.scanId, + uniqueFindingIds: document.findings.map((finding) => finding.findingId), + duplicateGroups: [], + deduplicationStatus: "completed", + }); + expect((await workflow.get())?.stages.dedupe).toEqual({ + status: "completed", + result, + }); + expect( + await deduplicateScanInternal(document.scanId, options, { + environment, + runWorkbench: history, + fetch: async () => { + throw new Error("Completed stages must not make HTTP requests"); + }, + }), + ).toEqual(result); + expect(publications).toBe(1); + expect(lookups).toBe(2); + expect(await readFile(join(scanDir, "findings.json"), "utf8")).toBe(original); +}); + +test("an empty scan completes publication and dedupe and remains retrievable", async () => { + const { scanDir, environment, document, history } = await fixture(); + document.findings = []; + const content = JSON.stringify(document); + await writeFile(join(scanDir, "findings.json"), content); + const manifestPath = join(scanDir, "scan-manifest.json"); + const manifest = JSON.parse( + await readFile(manifestPath, "utf8"), + ) as ScanManifest; + manifest.scan.artifacts!.find( + (artifact) => artifact.path === "findings.json", + )!.sha256 = createHash("sha256").update(content).digest("hex"); + await writeFile(manifestPath, JSON.stringify(manifest)); + const options = { + workflowId: "empty-scan", + findingsUrl: "http://synthetic.test", + }; + let requests = 0; + const fetch = async (_url: URL, init: RequestInit) => { + requests++; + expect(JSON.parse(init.body as string).findings).toEqual([]); + return Response.json([]); + }; + const result = await deduplicateScanInternal(document.scanId, options, { + environment, + runWorkbench: history, + fetch, + }); + expect(result).toEqual({ + scanId: document.scanId, + uniqueFindingIds: [], + duplicateGroups: [], + deduplicationStatus: "completed", + }); + expect( + await deduplicateScanInternal(document.scanId, options, { + environment, + runWorkbench: history, + fetch, + }), + ).toEqual(result); + expect(requests).toBe(1); + expect( + (await new FindingWorkflow(options.workflowId, environment).get())?.stages + .publish, + ).toMatchObject({ + status: "completed", + result: { findingIds: [], findingCount: 0 }, + }); +}); + +test("failed publication stays unfinished, dry-run does not advance it, and retry records the receipt", async () => { + const { scanDir, environment, document } = await fixture(); + const options = { + workflowId: "publication-retry", + findingsUrl: "http://synthetic.test", + }; + await expect( + publishScanToCustomInternal(scanDir, options, { + environment, + fetch: async () => new Response("", { status: 503 }), + }), + ).rejects.toThrow("HTTP 503"); + const workflow = new FindingWorkflow(options.workflowId, environment); + const failed = await workflow.get(); + expect(failed?.stages.publish.status).toBe("failed"); + expect(failed?.stages.publish.result).toBeUndefined(); + await publishScanToCustomInternal( + scanDir, + { ...options, dryRun: true }, + { environment }, + ); + expect(await workflow.get()).toEqual(failed); + const result = await publishScanToCustomInternal(scanDir, options, { + environment, + fetch: async () => + Response.json(document.findings.map((finding) => finding.findingId)), + }); + expect((await workflow.get())?.stages.publish).toEqual({ + status: "completed", + result, + }); +}); + +test("separates workflow identities and rejects different scans, destinations, and scopes", async () => { + const { environment } = await fixture(); + const binding = { + scanId: "scan-a", + scanDir: "synthetic-artifacts", + destination: workflowDestination( + "http://synthetic:password@synthetic.test", + ), + scope: { repositoryId: "repository-a" }, + }; + const first = new FindingWorkflow("first", environment); + await first.bind(binding); + expect((await first.get())?.destination).toBe("http://synthetic.test/"); + await first.run("dedupe", async () => ({ duplicateGroups: [] })); + for (const changed of [ + { scanId: "scan-b" }, + { destination: "http://other.test/" }, + { scope: { allRepositories: true as const } }, + ]) { + await expect(first.bind({ ...binding, ...changed })).rejects.toThrow( + "already bound to a different", + ); + const separate = new FindingWorkflow( + `separate-${Object.keys(changed)[0]}`, + environment, + ); + expect( + (await separate.bind({ ...binding, ...changed })).stages.dedupe.status, + ).toBe("pending"); + } + expect((await first.get())?.stages.dedupe).toEqual({ + status: "completed", + result: { duplicateGroups: [] }, + }); +}); + +test("does not write workflow metadata into sealed artifacts", async () => { + const { scanDir, environment } = await fixture(); + await expect( + publishScanToCustomInternal( + scanDir, + { workflowId: "unsafe-location", findingsUrl: "http://synthetic.test" }, + { + environment: { + ...environment, + CODEX_SECURITY_STATE_DIR: join(scanDir, "state"), + }, + fetch: async () => { + throw new Error("Must not publish"); + }, + }, + ), + ).rejects.toThrow("outside the sealed scan artifacts"); +}); From 79c988151b130e8fa4b2b083c1314605185963c7 Mon Sep 17 00:00:00 2001 From: kmbroai <272643392+kmbroai@users.noreply.github.com> Date: Thu, 27 Aug 2026 06:09:23 +0000 Subject: [PATCH 2/4] fix: store workflow metadata in explicit SQLite columns --- sdk/typescript/README.md | 5 +- .../scripts/workbench_finding_workflows.py | 70 +++++++-- .../scripts/workbench_schema.py | 52 +++++++ .../tests-ts/finding-workflow.test.ts | 143 +++++++++++++++++- 4 files changed, 257 insertions(+), 13 deletions(-) diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index b727d0939..b081d7a0b 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -1526,7 +1526,10 @@ a different workflow ID. Use one coordinating process per workflow. Workflow metadata, stage statuses, errors, publication receipts, and results live in the local workbench SQLite database under `CODEX_SECURITY_STATE_DIR`, outside -the sealed scan artifacts. Successful empty results are stored as completed +the sealed scan artifacts. Identity, scan/artifact references, destination, scope, +hashes, stage statuses, and errors use explicit columns; only receipts and result +payloads use JSON. Existing workflow rows are migrated atomically in place. +Successful empty results are stored as completed results, not treated as missing work. An empty scan can complete workflow publication with an empty receipt. Dry-run never advances a workflow stage. diff --git a/sdk/typescript/_bundled_plugin/scripts/workbench_finding_workflows.py b/sdk/typescript/_bundled_plugin/scripts/workbench_finding_workflows.py index 34c533e5a..10ade322f 100644 --- a/sdk/typescript/_bundled_plugin/scripts/workbench_finding_workflows.py +++ b/sdk/typescript/_bundled_plugin/scripts/workbench_finding_workflows.py @@ -6,29 +6,77 @@ import sqlite3 from typing import Any +WORKFLOW_BINDINGS = { + "repositoryPath": "repository_path", + "scanRequestDigest": "scan_request_digest", + "scanId": "scan_id", + "scanDir": "scan_dir", + "artifactDigest": "artifact_digest", + "destination": "destination", +} +WORKFLOW_STAGES = ("scan", "publish", "dedupe") + def read_workflow(connection: sqlite3.Connection, workflow_id: str) -> dict[str, Any] | None: row = connection.execute( - "SELECT state_json FROM finding_workflows WHERE id = ?", (workflow_id,) + "SELECT * FROM finding_workflows WHERE id = ?", (workflow_id,) ).fetchone() - return json.loads(row["state_json"]) if row is not None else None + if row is None: + return None + state = {"id": row["id"], "stages": {}} + for field, column in WORKFLOW_BINDINGS.items(): + if row[column] is not None: + state[field] = row[column] + if row["scope_repository_id"] is not None: + state["scope"] = {"repositoryId": row["scope_repository_id"]} + elif row["scope_all_repositories"] is not None: + state["scope"] = {"allRepositories": bool(row["scope_all_repositories"])} + results = json.loads(row["results_json"]) + for stage in WORKFLOW_STAGES: + current = {"status": row[f"{stage}_status"]} + if row[f"{stage}_error"] is not None: + current["error"] = row[f"{stage}_error"] + if stage in results: + current["result"] = results[stage] + state["stages"][stage] = current + if "dedupePendingWrite" in results: + state["stages"]["dedupe"]["pendingWrite"] = results["dedupePendingWrite"] + return state def save_workflow(connection: sqlite3.Connection, state: dict[str, Any], timestamp: str) -> None: + values = {"id": state["id"]} + values.update({column: state.get(field) for field, column in WORKFLOW_BINDINGS.items()}) + scope = state.get("scope", {}) + values.update( + scope_repository_id=scope.get("repositoryId"), + scope_all_repositories=scope.get("allRepositories"), + ) + results = {} + for stage in WORKFLOW_STAGES: + current = state["stages"][stage] + values[f"{stage}_status"] = current["status"] + values[f"{stage}_error"] = current.get("error") + if "result" in current: + results[stage] = current["result"] + if "pendingWrite" in state["stages"]["dedupe"]: + results["dedupePendingWrite"] = state["stages"]["dedupe"]["pendingWrite"] + values.update( + results_json=json.dumps(results, allow_nan=False), created_at=timestamp, updated_at=timestamp + ) + updates = ", ".join( + f"{column} = excluded.{column}" for column in values if column not in {"id", "created_at"} + ) connection.execute( - """INSERT INTO finding_workflows (id, state_json, created_at, updated_at) - VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET - state_json = excluded.state_json, updated_at = excluded.updated_at""", - (state["id"], json.dumps(state, allow_nan=False), timestamp, timestamp), + f"INSERT INTO finding_workflows ({', '.join(values)}) " + f"VALUES ({', '.join('?' for _ in values)}) ON CONFLICT(id) DO UPDATE SET {updates}", + tuple(values.values()), ) def bind_workflow(state: dict[str, Any], binding: dict[str, Any]) -> None: for field, value in binding.items(): - if field not in { - "repositoryPath", "scanRequestDigest", "scanId", "scanDir", - "artifactDigest", "destination", "scope", - }: + if field not in WORKFLOW_BINDINGS and field != "scope": raise SystemExit("Unknown workflow binding.") if field in state and state[field] != value: raise SystemExit( @@ -71,7 +119,7 @@ def finding_workflow( if state is None: state = { "id": workflow_id, - "stages": {stage: {"status": "pending"} for stage in ("scan", "publish", "dedupe")}, + "stages": {stage: {"status": "pending"} for stage in WORKFLOW_STAGES}, } bind_workflow(state, payload.get("binding", {})) action = payload["action"] diff --git a/sdk/typescript/_bundled_plugin/scripts/workbench_schema.py b/sdk/typescript/_bundled_plugin/scripts/workbench_schema.py index 45fb30f78..3e9cf1be8 100644 --- a/sdk/typescript/_bundled_plugin/scripts/workbench_schema.py +++ b/sdk/typescript/_bundled_plugin/scripts/workbench_schema.py @@ -1,6 +1,7 @@ """SQLite schema history for the Codex Security workbench.""" import argparse +import json import sqlite3 from collections.abc import Callable @@ -770,9 +771,58 @@ ); """, ), + # Version 37 is the stacked dedupe-review checkpoint migration. + ( + 38, + "store findings workflow metadata in columns", + """ + ALTER TABLE finding_workflows RENAME COLUMN state_json TO results_json; + ALTER TABLE finding_workflows ADD COLUMN repository_path TEXT; + ALTER TABLE finding_workflows ADD COLUMN scan_request_digest TEXT; + ALTER TABLE finding_workflows ADD COLUMN scan_id TEXT; + ALTER TABLE finding_workflows ADD COLUMN scan_dir TEXT; + ALTER TABLE finding_workflows ADD COLUMN artifact_digest TEXT; + ALTER TABLE finding_workflows ADD COLUMN destination TEXT; + ALTER TABLE finding_workflows ADD COLUMN scope_repository_id TEXT; + ALTER TABLE finding_workflows ADD COLUMN scope_all_repositories INTEGER; + ALTER TABLE finding_workflows ADD COLUMN scan_status TEXT NOT NULL DEFAULT 'pending'; + ALTER TABLE finding_workflows ADD COLUMN scan_error TEXT; + ALTER TABLE finding_workflows ADD COLUMN publish_status TEXT NOT NULL DEFAULT 'pending'; + ALTER TABLE finding_workflows ADD COLUMN publish_error TEXT; + ALTER TABLE finding_workflows ADD COLUMN dedupe_status TEXT NOT NULL DEFAULT 'pending'; + ALTER TABLE finding_workflows ADD COLUMN dedupe_error TEXT; + """, + ), ) +def migrate_finding_workflow_columns(connection: sqlite3.Connection) -> None: + # Rename/backfill in place so existing checkpoint foreign keys and rows survive. + for row in connection.execute("SELECT id, results_json FROM finding_workflows").fetchall(): + state = json.loads(row["results_json"]) + scope = state.get("scope", {}) + stages = state["stages"] + results = {stage: value["result"] for stage, value in stages.items() if "result" in value} + if "pendingWrite" in stages["dedupe"]: + results["dedupePendingWrite"] = stages["dedupe"]["pendingWrite"] + connection.execute( + """UPDATE finding_workflows SET + repository_path = ?, scan_request_digest = ?, scan_id = ?, scan_dir = ?, + artifact_digest = ?, destination = ?, scope_repository_id = ?, scope_all_repositories = ?, + scan_status = ?, scan_error = ?, publish_status = ?, publish_error = ?, + dedupe_status = ?, dedupe_error = ?, results_json = ? WHERE id = ?""", + ( + state.get("repositoryPath"), state.get("scanRequestDigest"), + state.get("scanId"), state.get("scanDir"), state.get("artifactDigest"), + state.get("destination"), scope.get("repositoryId"), scope.get("allRepositories"), + stages["scan"]["status"], stages["scan"].get("error"), + stages["publish"]["status"], stages["publish"].get("error"), + stages["dedupe"]["status"], stages["dedupe"].get("error"), + json.dumps(results, allow_nan=False), row["id"], + ), + ) + + def apply_migrations( connection: sqlite3.Connection, migrations: tuple[tuple[int, str, str], ...], @@ -853,6 +903,8 @@ def apply_migrations( else: for statement in sql_statements(sql): connection.execute(statement) + if version == 38: + migrate_finding_workflow_columns(connection) connection.execute( "INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, ?)", (version, name, now()), diff --git a/sdk/typescript/tests-ts/finding-workflow.test.ts b/sdk/typescript/tests-ts/finding-workflow.test.ts index ead0810e0..b7b4eaee8 100644 --- a/sdk/typescript/tests-ts/finding-workflow.test.ts +++ b/sdk/typescript/tests-ts/finding-workflow.test.ts @@ -19,7 +19,11 @@ import { } from "../src/finding-workflow.js"; import { publishScanToCustomInternal } from "../src/custom-publish.js"; import { deduplicateScanInternal } from "../src/deduplication/scan.js"; -import { resolvePluginPython, runWorkbench } from "../src/runtime.js"; +import { + resolvePluginPython, + runCodexCommand, + runWorkbench, +} from "../src/runtime.js"; import type { FindingsDocument, ScanManifest } from "../src/models.js"; import { PLUGIN_ROOT } from "./plugin-root.js"; @@ -80,6 +84,143 @@ async function fixture() { }; } +test("migrates workflow columns atomically without losing receipts, pending writes, or resume state", async () => { + const { environment, repository, scanDir, workbenchOptions } = + await fixture(); + const completed = { + id: "migrated-completed", + repositoryPath: repository, + scanRequestDigest: "scan-request-hash", + scanId: "synthetic-scan", + scanDir, + artifactDigest: "artifact-hash", + destination: "http://synthetic.test/", + scope: { repositoryId: "synthetic-repository" }, + stages: { + scan: { status: "completed", result: null }, + publish: { status: "completed", result: { findingIds: [] } }, + dedupe: { status: "completed", result: { duplicateGroups: [] } }, + }, + } as const; + const unfinished = { + id: "migrated-unfinished", + scope: { allRepositories: true }, + stages: { + scan: { status: "failed", error: "Synthetic scan interruption" }, + publish: { status: "running", error: "Synthetic earlier failure" }, + dedupe: { + status: "failed", + error: "Synthetic lost acknowledgement", + result: { duplicateGroups: [["a", "b"]] }, + pendingWrite: { groups: [["a", "b"]] }, + }, + }, + } as const; + const pending = { + id: "migrated-pending", + stages: { + scan: { status: "pending" }, + publish: { status: "pending" }, + dedupe: { status: "pending" }, + }, + } as const; + await mkdir(environment.CODEX_SECURITY_STATE_DIR); + const probe = await runCodexCommand( + { command: workbenchOptions.python }, + [ + "-I", + "-B", + "-c", + `import json, sqlite3, sys +sys.path.insert(0, sys.argv[1]) +from workbench_schema import MIGRATIONS, apply_migrations +db = sqlite3.connect(sys.argv[2]) +db.row_factory = sqlite3.Row +db.execute("PRAGMA foreign_keys = ON") +timestamp = "2026-08-01T00:00:00Z" +apply = lambda migrations: apply_migrations(db, migrations, lambda: timestamp, lambda _: None) +apply(tuple(m for m in MIGRATIONS if m[0] <= 36)) +states = json.load(sys.stdin) +with db: + for state in states: + db.execute("INSERT INTO finding_workflows VALUES (?, ?, ?, ?)", + (state["id"], json.dumps(state), timestamp, "2026-08-02T00:00:00Z")) + db.execute("CREATE TABLE synthetic_workflow_references (workflow_id TEXT REFERENCES finding_workflows(id) ON DELETE CASCADE)") + db.execute("INSERT INTO synthetic_workflow_references VALUES (?)", (states[0]["id"],)) +before = list(db.iterdump()) +try: + apply((*MIGRATIONS, (999, "synthetic failure", "INSERT INTO synthetic_missing_table VALUES (1);"))) +except sqlite3.OperationalError: + pass +else: + raise AssertionError("Migration should fail") +assert not db.in_transaction +assert list(db.iterdump()) == before, "Migration must roll back schema and data together" +apply(MIGRATIONS) +after = list(db.iterdump()) +apply(MIGRATIONS) +assert list(db.iterdump()) == after +assert "state_json" not in {row["name"] for row in db.execute("PRAGMA table_info(finding_workflows)")} +assert db.execute("SELECT COUNT(*) FROM synthetic_workflow_references").fetchone()[0] == 1 +assert list(db.execute("PRAGMA foreign_key_check")) == [] +assert db.execute("PRAGMA integrity_check").fetchone()[0] == "ok" +print(json.dumps([dict(row) for row in db.execute("SELECT * FROM finding_workflows ORDER BY id")]))`, + join(PLUGIN_ROOT, "scripts"), + join(environment.CODEX_SECURITY_STATE_DIR, "workbench.sqlite3"), + ], + environment, + JSON.stringify([completed, unfinished, pending]), + ); + expect(probe.exitCode, probe.stderr).toBe(0); + const rows = JSON.parse(probe.stdout); + expect(rows[0]).toMatchObject({ + id: completed.id, + repository_path: repository, + scan_request_digest: completed.scanRequestDigest, + scan_id: completed.scanId, + scan_dir: scanDir, + artifact_digest: completed.artifactDigest, + destination: completed.destination, + scope_repository_id: completed.scope.repositoryId, + scope_all_repositories: null, + scan_status: "completed", + scan_error: null, + publish_status: "completed", + publish_error: null, + dedupe_status: "completed", + dedupe_error: null, + created_at: "2026-08-01T00:00:00Z", + updated_at: "2026-08-02T00:00:00Z", + }); + expect(JSON.parse(rows[0].results_json)).toEqual({ + scan: null, + publish: completed.stages.publish.result, + dedupe: completed.stages.dedupe.result, + }); + for (const state of [completed, unfinished, pending]) { + expect(await new FindingWorkflow(state.id, environment).get()).toEqual( + state, + ); + } + const workflow = new FindingWorkflow(completed.id, environment); + for (const stage of ["scan", "publish", "dedupe"] as const) { + expect( + await workflow.run(stage, async () => { + throw new Error("Completed work must not run again after migration"); + }), + ).toEqual(completed.stages[stage].result); + } + const resumed = new FindingWorkflow(unfinished.id, environment); + await resumed.run("scan", async () => ({ scanId: "resumed-scan" })); + expect((await resumed.get())?.stages.scan).toEqual({ + status: "completed", + result: { scanId: "resumed-scan" }, + }); + expect((await resumed.get())?.stages.dedupe).toEqual( + unfinished.stages.dedupe, + ); +}); + test("scan registration commits its workflow identity atomically and rolls back failed registration", async () => { const { root, repository, environment, workbenchOptions } = await fixture(); const workflow = new FindingWorkflow("registered-workflow", environment); From f6b60100451340aa132bc198387e8e3272805508 Mon Sep 17 00:00:00 2001 From: Kyle Brown Date: Thu, 27 Aug 2026 20:59:20 +0000 Subject: [PATCH 3/4] test(plugin): align workflow helper and schema contracts --- .../scripts/workbench_finding_workflows.py | 5 +++++ plugins/codex-security/tests/test_workbench_db.py | 3 ++- .../codex-security/tests/test_workbench_deep_scan.py | 2 +- .../tests/test_workbench_setup_and_migrations.py | 12 ++++++++++-- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/plugins/codex-security/scripts/workbench_finding_workflows.py b/plugins/codex-security/scripts/workbench_finding_workflows.py index 10ade322f..79f0742ff 100644 --- a/plugins/codex-security/scripts/workbench_finding_workflows.py +++ b/plugins/codex-security/scripts/workbench_finding_workflows.py @@ -2,6 +2,7 @@ from __future__ import annotations +import argparse import json import sqlite3 from typing import Any @@ -139,3 +140,7 @@ def finding_workflow( raise SystemExit("Unknown workflow action.") save_workflow(connection, state, timestamp) return {"workflow": state} + + +if __name__ == "__main__": + argparse.ArgumentParser(description=__doc__).parse_args() diff --git a/plugins/codex-security/tests/test_workbench_db.py b/plugins/codex-security/tests/test_workbench_db.py index 4e01be362..0d0aeab6a 100644 --- a/plugins/codex-security/tests/test_workbench_db.py +++ b/plugins/codex-security/tests/test_workbench_db.py @@ -67,6 +67,7 @@ "finding_remediation_attempts", "finding_repositories", "finding_triage", + "finding_workflows", "findings", "scan_artifacts", "scan_comparison_matches", @@ -994,7 +995,7 @@ def test_workbench_persists_progress_and_indexes_completed_findings(tmp_path: Pa ) } assert tables == EXPECTED_TABLES - assert connection.execute("SELECT COUNT(*) FROM schema_migrations").fetchone() == (35,) + assert connection.execute("SELECT COUNT(*) FROM schema_migrations").fetchone() == (37,) assert connection.execute("SELECT COUNT(*) FROM findings").fetchone() == (1,) assert connection.execute("SELECT COUNT(*) FROM finding_locations").fetchone() == (1,) diff --git a/plugins/codex-security/tests/test_workbench_deep_scan.py b/plugins/codex-security/tests/test_workbench_deep_scan.py index af89fa2b0..f0b38e700 100644 --- a/plugins/codex-security/tests/test_workbench_deep_scan.py +++ b/plugins/codex-security/tests/test_workbench_deep_scan.py @@ -278,7 +278,7 @@ def claim() -> dict[str, object]: return claim_deep_scan_coordinator(state_dir, codex_home, scan_id) with sqlite3.connect(state_dir / "workbench.sqlite3") as connection: - assert connection.execute("SELECT MAX(version) FROM schema_migrations").fetchone() == (35,) + assert connection.execute("SELECT MAX(version) FROM schema_migrations").fetchone() == (38,) assert claim()["deepScan"]["coordinatorGeneration"] == 2 assert claim()["coordinatorDisposition"] == "observing" expire_deep_scan_coordinator(state_dir, scan_id) diff --git a/plugins/codex-security/tests/test_workbench_setup_and_migrations.py b/plugins/codex-security/tests/test_workbench_setup_and_migrations.py index a5168ab91..38df34a8d 100644 --- a/plugins/codex-security/tests/test_workbench_setup_and_migrations.py +++ b/plugins/codex-security/tests/test_workbench_setup_and_migrations.py @@ -405,7 +405,7 @@ def test_workbench_serializes_concurrent_first_run_migrations(tmp_path: Path) -> {"databasePath": str(state_dir / "workbench.sqlite3")}, ] with sqlite3.connect(state_dir / "workbench.sqlite3") as connection: - assert connection.execute("SELECT COUNT(*) FROM schema_migrations").fetchone() == (35,) + assert connection.execute("SELECT COUNT(*) FROM schema_migrations").fetchone() == (37,) def test_workbench_backfills_repository_targets_only_during_migration() -> None: @@ -796,6 +796,8 @@ def test_workbench_creates_single_final_schema(tmp_path: Path) -> None: (33, "store complete findings and embeddings without a scan"), (34, "associate findings with repositories"), (35, "persist finding dedupe groups"), + (36, "persist local findings workflows"), + (38, "store findings workflow metadata in columns"), ] assert {row[1] for row in connection.execute("PRAGMA table_info(workspaces)")} >= { "diff_target_kind", @@ -898,7 +900,7 @@ def test_workbench_upgrades_preexisting_database(tmp_path: Path) -> None: connection.execute("ALTER TABLE scans DROP COLUMN handoff_claim_token") run_workbench(state_dir, "database-info") with sqlite3.connect(database) as connection: - assert connection.execute("SELECT MAX(version) FROM schema_migrations").fetchone() == (35,) + assert connection.execute("SELECT MAX(version) FROM schema_migrations").fetchone() == (38,) assert {row[1] for row in connection.execute("PRAGMA table_info(scans)")} >= { "handoff_claimed_at", "handoff_claim_token", @@ -1920,6 +1922,8 @@ def test_workbench_upgrades_released_database_schema(tmp_path: Path) -> None: (33, "store complete findings and embeddings without a scan"), (34, "associate findings with repositories"), (35, "persist finding dedupe groups"), + (36, "persist local findings workflows"), + (38, "store findings workflow metadata in columns"), ] assert "capability_preflight_json" in { row[1] for row in connection.execute("PRAGMA table_info(workspaces)") @@ -1997,6 +2001,8 @@ def test_workbench_upgrades_pre_release_phase_progress_migration(tmp_path: Path) (33, "store complete findings and embeddings without a scan"), (34, "associate findings with repositories"), (35, "persist finding dedupe groups"), + (36, "persist local findings workflows"), + (38, "store findings workflow metadata in columns"), ] assert "continuation_thread_id" in { row[1] for row in connection.execute("PRAGMA table_info(scans)") @@ -2082,6 +2088,8 @@ def test_workbench_upgrades_pre_release_preflight_progress_migration(tmp_path: P (33, "store complete findings and embeddings without a scan"), (34, "associate findings with repositories"), (35, "persist finding dedupe groups"), + (36, "persist local findings workflows"), + (38, "store findings workflow metadata in columns"), ] assert "continuation_thread_id" in { row[1] for row in connection.execute("PRAGMA table_info(scans)") From f78e295eb14641a130b72db001a3d57f89f3bc58 Mon Sep 17 00:00:00 2001 From: Kyle Brown Date: Thu, 27 Aug 2026 14:40:56 -0700 Subject: [PATCH 4/4] feat: checkpoint dedupe reviews and replay group writes (#674) * feat: checkpoint dedupe reviews and replay group writes * fix: keep workflow source snapshots within the checkout * fix: migrate dedupe checkpoint bindings to SQLite columns * test: retain required canonical ID in translated checkpoint checks * test: align checkpoint contracts with the updated findings stack * feat(server): add read-only findings dashboard (#679) * feat(server): add read-only findings dashboard * fix(dashboard): show workflows first * refactor(dashboard): simplify rendering and reuse workflow storage * fix(dashboard): preserve filters and completed result semantics * fix(dashboard): retain published repository identities in search * fix(dashboard): include deep scan activity in freshness * refactor(dashboard): use native selects and remove dropdown machinery * refactor(dashboard): show only stored findings and groups --------- Co-authored-by: kmbroai <272643392+kmbroai@users.noreply.github.com> --- Dockerfile | 2 +- README.md | 6 +- .../codex-security/scripts/workbench_cli.py | 1 + .../scripts/workbench_dashboard.py | 115 + .../codex-security/scripts/workbench_db.py | 3 + .../scripts/workbench_finding_workflows.py | 44 + .../scripts/workbench_schema.py | 59 +- .../scripts/workbench_target.py | 25 +- .../codex-security/tests/test_workbench_db.py | 3 +- .../tests/test_workbench_deep_scan.py | 2 +- .../test_workbench_setup_and_migrations.py | 12 +- sdk/typescript/README.md | 74 +- sdk/typescript/dashboard/App.tsx | 573 ++ sdk/typescript/dashboard/index.html | 18 + sdk/typescript/dashboard/index.tsx | 11 + sdk/typescript/dashboard/polling.ts | 30 + sdk/typescript/dashboard/styles.css | 433 ++ sdk/typescript/package.json | 9 +- sdk/typescript/plugin-files.json | 1 + sdk/typescript/pnpm-lock.yaml | 5675 ++++++++++++++--- sdk/typescript/pnpm-workspace.yaml | 2 + sdk/typescript/scripts/build-dashboard.mjs | 89 + sdk/typescript/scripts/check-package.mjs | 14 + .../fixtures/findings-service-sqlite.ts | 42 + .../scripts/smoke-findings-service.ts | 21 + sdk/typescript/scripts/smoke-package.mjs | 48 +- .../src/deduplication/checkpointed-review.ts | 93 + .../deduplication/deduplication-reviewer.ts | 31 + sdk/typescript/src/deduplication/scan.ts | 46 +- sdk/typescript/src/finding-workflow.ts | 39 +- sdk/typescript/src/server/dashboard-types.ts | 46 + sdk/typescript/src/server/dashboard.ts | 57 + sdk/typescript/src/server/findings-service.ts | 5 + sdk/typescript/src/server/routes.ts | 15 + sdk/typescript/src/server/sqlite-store.ts | 8 + sdk/typescript/src/server/storage.ts | 2 + .../tests-ts/finding-deduplication.test.ts | 8 + .../tests-ts/finding-workflow.test.ts | 635 +- .../tests-ts/findings-dashboard.test.ts | 122 + .../tests-ts/findings-server.test.ts | 168 + sdk/typescript/tests-ts/skeleton.test.ts | 2 +- sdk/typescript/tsconfig.json | 4 +- 42 files changed, 7515 insertions(+), 1078 deletions(-) create mode 100644 plugins/codex-security/scripts/workbench_dashboard.py create mode 100644 sdk/typescript/dashboard/App.tsx create mode 100644 sdk/typescript/dashboard/index.html create mode 100644 sdk/typescript/dashboard/index.tsx create mode 100644 sdk/typescript/dashboard/polling.ts create mode 100644 sdk/typescript/dashboard/styles.css create mode 100644 sdk/typescript/pnpm-workspace.yaml create mode 100644 sdk/typescript/scripts/build-dashboard.mjs create mode 100644 sdk/typescript/src/deduplication/checkpointed-review.ts create mode 100644 sdk/typescript/src/server/dashboard-types.ts create mode 100644 sdk/typescript/src/server/dashboard.ts create mode 100644 sdk/typescript/tests-ts/findings-dashboard.test.ts diff --git a/Dockerfile b/Dockerfile index c8825e2b0..b8cd633e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM node:22-bookworm-slim@sha256:6c74791e557ce11fc957704f6d4fe134a7bc8d6f5ca440 WORKDIR /build/sdk/typescript -COPY sdk/typescript/package.json sdk/typescript/pnpm-lock.yaml ./ +COPY sdk/typescript/package.json sdk/typescript/pnpm-lock.yaml sdk/typescript/pnpm-workspace.yaml ./ COPY plugins/codex-security/mcp-app/package.json plugins/codex-security/mcp-app/package-lock.json /build/plugins/codex-security/mcp-app/ RUN corepack enable \ diff --git a/README.md b/README.md index 07840493b..fd829cd9a 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,10 @@ Use the included Docker Compose configuration for scans of many repositories. Se The [findings service](sdk/typescript/README.md#findings-service-preview) runs from the SDK in Docker, stores findings and embeddings in SQLite, and lists -findings with pagination. It also returns potential duplicates by embedding -similarity within a repository or an explicit all-repository scope. The +findings with pagination. Its read-only dashboard at `/dashboard` refreshes every +five seconds and shows stored findings and duplicate groups from the service's +database. It also returns potential duplicates by embedding similarity within a +repository or an explicit all-repository scope. The `codex-security publish scan --to custom --findings-url http://localhost:3000` command uploads completed findings and their repository ID. The SDK and `codex-security dedupe` command retrieve candidates, run independent Codex diff --git a/plugins/codex-security/scripts/workbench_cli.py b/plugins/codex-security/scripts/workbench_cli.py index 84e963f1a..e4ceb29d8 100644 --- a/plugins/codex-security/scripts/workbench_cli.py +++ b/plugins/codex-security/scripts/workbench_cli.py @@ -339,6 +339,7 @@ def parse_args(description: str) -> argparse.Namespace: publication.add_argument("--input-file", required=True) subparsers.add_parser("database-info") + subparsers.add_parser("dashboard") subparsers.add_parser("finding-workflow") subparsers.add_parser("store-findings") subparsers.add_parser("store-dedupe-groups") diff --git a/plugins/codex-security/scripts/workbench_dashboard.py b/plugins/codex-security/scripts/workbench_dashboard.py new file mode 100644 index 000000000..0e7b510b6 --- /dev/null +++ b/plugins/codex-security/scripts/workbench_dashboard.py @@ -0,0 +1,115 @@ +"""Read-only dashboard projections for stored findings and duplicate groups.""" + +from __future__ import annotations + +import argparse +import json +import sqlite3 +import sys +from pathlib import Path +from typing import Any + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from workbench_findings import list_dedupe_groups + + +FINDING_RECORDS = """ +SELECT findings.id, json_extract(details_json, '$.title') AS title, + COALESCE(repositories.ids, '[]') AS repositoryIds, + json_extract(details_json, '$.severity.level') AS severity, + findings.created_at AS createdAt, findings.updated_at AS updatedAt +FROM findings LEFT JOIN ( + SELECT finding_id, json_group_array(repository_id) AS ids + FROM finding_repositories GROUP BY finding_id +) AS repositories ON repositories.finding_id = findings.id +WHERE details_json IS NOT NULL +""" + +GROUP_RECORDS = """ +SELECT groups.id, groups.id AS title, + (SELECT json_group_array(DISTINCT repository_id) + FROM finding_dedupe_group_members AS members + JOIN finding_repositories ON finding_repositories.finding_id = members.finding_id + WHERE members.group_id = groups.id) AS repositoryIds, + groups.created_at AS createdAt, groups.created_at AS updatedAt, + (SELECT COUNT(*) FROM finding_dedupe_group_members WHERE group_id = groups.id) AS memberCount +FROM finding_dedupe_groups AS groups +""" + +RECORDS = { + "findings": FINDING_RECORDS, + "groups": GROUP_RECORDS, +} + + +def item(row: sqlite3.Row) -> dict[str, Any]: + result = dict(row) + result["repositoryIds"] = json.loads(result["repositoryIds"]) + return result + + +def detail(connection: sqlite3.Connection, view: str, selected: dict[str, Any]) -> dict[str, Any]: + result: dict[str, Any] = {"item": selected} + selected_id = selected["id"] + if view == "findings": + result["finding"] = json.loads(connection.execute( + "SELECT details_json FROM findings WHERE id = ?", (selected_id,), + ).fetchone()[0]) + result["groups"] = list_dedupe_groups(connection, selected_id)["groups"] + else: + result["group"] = { + "groupId": selected_id, "createdAt": selected["createdAt"], + "findingIds": [r[0] for r in connection.execute( + "SELECT finding_id FROM finding_dedupe_group_members WHERE group_id = ? ORDER BY finding_id", + (selected_id,), + )], + } + return result + + +def dashboard(connection: sqlite3.Connection, query: dict[str, Any]) -> dict[str, Any]: + """One snapshot, no artifact reads, model calls, or writes.""" + view = query["view"] + records = RECORDS[view] + clauses: list[str] = [] + values: list[Any] = [] + if query.get("query"): + connection.create_function("casefold", 1, str.casefold, deterministic=True) + columns = ["id", "title", "repositoryIds"] + clauses.append("(" + " OR ".join(f"instr(casefold(COALESCE({c}, '')), casefold(?)) > 0" for c in columns) + ")") + values.extend([query["query"]] * len(columns)) + if query.get("repository"): + clauses.append("EXISTS (SELECT 1 FROM json_each(repositoryIds) WHERE value = ?)") + values.append(query["repository"]) + where = " WHERE " + " AND ".join(clauses) if clauses else "" + order = "createdAt DESC, id" if query["sort"] == "newest" else "updatedAt DESC, id" + connection.execute("BEGIN") + with connection: + repositories = connection.execute(""" + SELECT DISTINCT repository_id AS id, repository_id AS label + FROM finding_repositories ORDER BY repository_id + """).fetchall() + total = connection.execute(f"SELECT COUNT(*) FROM ({records}) {where}", values).fetchone()[0] + rows = connection.execute( + f"SELECT * FROM ({records}) {where} ORDER BY {order} LIMIT ? OFFSET ?", + (*values, query["limit"], query["offset"]), + ).fetchall() + selected = connection.execute( + f"SELECT * FROM ({records}) WHERE id = ?", (query["id"],), + ).fetchone() if query.get("id") else None + next_offset = query["offset"] + len(rows) + return { + "overview": { + "findings": connection.execute("SELECT COUNT(*) FROM findings WHERE details_json IS NOT NULL").fetchone()[0], + "groups": connection.execute("SELECT COUNT(*) FROM finding_dedupe_groups").fetchone()[0], + }, + "repositories": [dict(row) for row in repositories], + "items": [item(row) for row in rows], "total": total, + "limit": query["limit"], "offset": query["offset"], + "nextOffset": next_offset if next_offset < total else None, + "detail": detail(connection, view, item(selected)) if selected is not None else None, + } + + +if __name__ == "__main__": + argparse.ArgumentParser(description=__doc__).parse_args() diff --git a/plugins/codex-security/scripts/workbench_db.py b/plugins/codex-security/scripts/workbench_db.py index 159482aa7..69e58b0d5 100644 --- a/plugins/codex-security/scripts/workbench_db.py +++ b/plugins/codex-security/scripts/workbench_db.py @@ -81,6 +81,7 @@ SQLITE_RETRY_ATTEMPTS, ) from workbench_feedback import get_scan_feedback +from workbench_dashboard import dashboard from workbench_finding_index import index_findings from workbench_finding_workflows import finding_workflow, register_workflow_scan from workbench_findings import ( @@ -4038,6 +4039,8 @@ def main() -> None: result = {"databasePath": str(database_path())} elif args.command == "finding-workflow": result = finding_workflow(connection, json.load(sys.stdin), now()) + elif args.command == "dashboard": + result = dashboard(connection, json.load(sys.stdin)) elif args.command == "store-findings": payload = json.load(sys.stdin) result = store_findings(connection, payload["entries"], now(), payload.get("repositoryId")) diff --git a/plugins/codex-security/scripts/workbench_finding_workflows.py b/plugins/codex-security/scripts/workbench_finding_workflows.py index 79f0742ff..dee35cbf2 100644 --- a/plugins/codex-security/scripts/workbench_finding_workflows.py +++ b/plugins/codex-security/scripts/workbench_finding_workflows.py @@ -4,9 +4,15 @@ import argparse import json +import hashlib import sqlite3 +import sys +from pathlib import Path from typing import Any +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from workbench_target import directory_content_digest, git_output, git_revision + WORKFLOW_BINDINGS = { "repositoryPath": "repository_path", "scanRequestDigest": "scan_request_digest", @@ -114,6 +120,42 @@ def finding_workflow( raise SystemExit("workflowId must be a nonempty string.") if payload["action"] == "get": return {"workflow": read_workflow(connection, workflow_id)} + if payload["action"] == "source": + target = Path(payload["repository"]).resolve(strict=True) + return {"source": { + "repository": str(target), + "revision": git_revision(target), + "refsDigest": hashlib.sha256((git_output(target, "show-ref") or "").encode()).hexdigest(), + "content": directory_content_digest(target, include_ignored=True), + }} + if payload["action"] == "get-review": + row = connection.execute( + "SELECT result_json FROM finding_workflow_reviews WHERE workflow_id = ? AND review_key = ?", + (workflow_id, payload["key"]), + ).fetchone() + return {"review": json.loads(row["result_json"]) if row is not None else None} + if payload["action"] == "save-review": + binding = payload["binding"] + source = binding["source"] + scope = binding["scope"] + with connection: + connection.execute( + """INSERT INTO finding_workflow_reviews + (workflow_id, review_key, review_contract_version, codex_version, + source_repository_path, source_revision, source_refs_digest, source_content_digest, + scope_repository_id, scope_all_repositories, model, effort, settings_digest, + prompt_digest, contract_digest, result_json, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(workflow_id, review_key) DO NOTHING""", + ( + workflow_id, payload["key"], binding["version"], binding["codexVersion"], + source["repository"], source["revision"], source["refsDigest"], source["content"], + scope.get("repositoryId"), scope.get("allRepositories"), binding["model"], + binding["effort"], binding.get("settingsDigest"), binding["promptDigest"], + binding["contractDigest"], json.dumps(payload["result"], allow_nan=False), timestamp, + ), + ) + return {} connection.execute("BEGIN IMMEDIATE") with connection: state = read_workflow(connection, workflow_id) @@ -136,6 +178,8 @@ def finding_workflow( state["stages"][stage] = {"status": "completed", "result": payload["result"]} elif action == "fail": current.update(status="failed", error=payload["error"]) + elif action == "prepare-dedupe": + current.update(result=payload["result"], pendingWrite=payload["pendingWrite"]) else: raise SystemExit("Unknown workflow action.") save_workflow(connection, state, timestamp) diff --git a/plugins/codex-security/scripts/workbench_schema.py b/plugins/codex-security/scripts/workbench_schema.py index 4e08487f0..ff83eb326 100644 --- a/plugins/codex-security/scripts/workbench_schema.py +++ b/plugins/codex-security/scripts/workbench_schema.py @@ -771,7 +771,20 @@ ); """, ), - # Version 37 is the stacked dedupe-review checkpoint migration. + ( + 37, + "checkpoint validated dedupe reviews", + """ + CREATE TABLE finding_workflow_reviews ( + workflow_id TEXT NOT NULL REFERENCES finding_workflows(id) ON DELETE CASCADE, + review_key TEXT NOT NULL, + binding_json TEXT NOT NULL, + result_json TEXT NOT NULL, + created_at TEXT NOT NULL, + PRIMARY KEY (workflow_id, review_key) + ); + """, + ), ( 38, "store findings workflow metadata in columns", @@ -793,9 +806,51 @@ ALTER TABLE finding_workflows ADD COLUMN dedupe_error TEXT; """, ), + ( + 39, + "store dedupe checkpoint bindings in columns", + """ + ALTER TABLE finding_workflow_reviews RENAME COLUMN binding_json TO prompt_digest; + ALTER TABLE finding_workflow_reviews ADD COLUMN review_contract_version INTEGER; + ALTER TABLE finding_workflow_reviews ADD COLUMN codex_version TEXT; + ALTER TABLE finding_workflow_reviews ADD COLUMN source_repository_path TEXT; + ALTER TABLE finding_workflow_reviews ADD COLUMN source_revision TEXT; + ALTER TABLE finding_workflow_reviews ADD COLUMN source_refs_digest TEXT; + ALTER TABLE finding_workflow_reviews ADD COLUMN source_content_digest TEXT; + ALTER TABLE finding_workflow_reviews ADD COLUMN scope_repository_id TEXT; + ALTER TABLE finding_workflow_reviews ADD COLUMN scope_all_repositories INTEGER; + ALTER TABLE finding_workflow_reviews ADD COLUMN model TEXT; + ALTER TABLE finding_workflow_reviews ADD COLUMN effort TEXT; + ALTER TABLE finding_workflow_reviews ADD COLUMN settings_digest TEXT; + ALTER TABLE finding_workflow_reviews ADD COLUMN contract_digest TEXT; + """, + ), ) +def migrate_finding_workflow_review_columns(connection: sqlite3.Connection) -> None: + for row in connection.execute( + "SELECT workflow_id, review_key, prompt_digest FROM finding_workflow_reviews" + ).fetchall(): + binding = json.loads(row["prompt_digest"]) + source = binding["source"] + scope = binding["scope"] + connection.execute( + """UPDATE finding_workflow_reviews SET review_contract_version = ?, codex_version = ?, + source_repository_path = ?, source_revision = ?, source_refs_digest = ?, + source_content_digest = ?, scope_repository_id = ?, scope_all_repositories = ?, + model = ?, effort = ?, settings_digest = ?, prompt_digest = ?, contract_digest = ? + WHERE workflow_id = ? AND review_key = ?""", + ( + binding["version"], binding["codexVersion"], source["repository"], + source["revision"], source["refsDigest"], source["content"], + scope.get("repositoryId"), scope.get("allRepositories"), binding["model"], + binding["effort"], binding.get("settingsDigest"), binding["promptDigest"], + binding["contractDigest"], row["workflow_id"], row["review_key"], + ), + ) + + def migrate_finding_workflow_columns(connection: sqlite3.Connection) -> None: # Rename/backfill in place so existing checkpoint foreign keys and rows survive. for row in connection.execute("SELECT id, results_json FROM finding_workflows").fetchall(): @@ -905,6 +960,8 @@ def apply_migrations( connection.execute(statement) if version == 38: migrate_finding_workflow_columns(connection) + elif version == 39: + migrate_finding_workflow_review_columns(connection) connection.execute( "INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, ?)", (version, name, now()), diff --git a/plugins/codex-security/scripts/workbench_target.py b/plugins/codex-security/scripts/workbench_target.py index 3e26d5acd..1b1cb0500 100644 --- a/plugins/codex-security/scripts/workbench_target.py +++ b/plugins/codex-security/scripts/workbench_target.py @@ -414,14 +414,31 @@ def git_directory_snapshot_paths(target: Path) -> list[Path] | None: return sorted(set(paths)) -def directory_content_digest(target: Path, *, excluded: tuple[Path, ...] = ()) -> str: +def source_directory_snapshot_paths(target: Path) -> list[Path]: + paths: list[Path] = [] + pending = [target] + while pending: + for path in pending.pop().iterdir(): + if path.name == ".git": + continue + paths.append(path) + metadata = path.lstat() + # Name-surrogate reparse points include Windows directory junctions. + if stat.S_ISDIR(metadata.st_mode) and not getattr(metadata, "st_reparse_tag", 0) & 0x20000000: + pending.append(path) + return sorted(paths) + + +def directory_content_digest( + target: Path, *, excluded: tuple[Path, ...] = (), include_ignored: bool = False +) -> str: excluded_relative = [] for path in excluded: try: excluded_relative.append(path.relative_to(target)) except ValueError: continue - paths = git_directory_snapshot_paths(target) + paths = source_directory_snapshot_paths(target) if include_ignored else git_directory_snapshot_paths(target) if paths is None: paths = sorted(target.rglob("*")) digest = hashlib.sha256() @@ -440,7 +457,9 @@ def directory_content_digest(target: Path, *, excluded: tuple[Path, ...] = ()) - raw_path = os.fsencode(relative_path.as_posix()) update_digest_field(digest, b"path", raw_path) update_digest_field(digest, b"mode", str(stat.S_IMODE(metadata.st_mode)).encode()) - if stat.S_ISLNK(metadata.st_mode): + if stat.S_ISLNK(metadata.st_mode) or ( + include_ignored and getattr(metadata, "st_reparse_tag", 0) & 0x20000000 + ): update_digest_field(digest, b"kind", b"symlink") update_digest_field(digest, b"content", os.fsencode(os.readlink(path))) elif stat.S_ISDIR(metadata.st_mode): diff --git a/plugins/codex-security/tests/test_workbench_db.py b/plugins/codex-security/tests/test_workbench_db.py index 0d0aeab6a..136a038b8 100644 --- a/plugins/codex-security/tests/test_workbench_db.py +++ b/plugins/codex-security/tests/test_workbench_db.py @@ -67,6 +67,7 @@ "finding_remediation_attempts", "finding_repositories", "finding_triage", + "finding_workflow_reviews", "finding_workflows", "findings", "scan_artifacts", @@ -995,7 +996,7 @@ def test_workbench_persists_progress_and_indexes_completed_findings(tmp_path: Pa ) } assert tables == EXPECTED_TABLES - assert connection.execute("SELECT COUNT(*) FROM schema_migrations").fetchone() == (37,) + assert connection.execute("SELECT COUNT(*) FROM schema_migrations").fetchone() == (39,) assert connection.execute("SELECT COUNT(*) FROM findings").fetchone() == (1,) assert connection.execute("SELECT COUNT(*) FROM finding_locations").fetchone() == (1,) diff --git a/plugins/codex-security/tests/test_workbench_deep_scan.py b/plugins/codex-security/tests/test_workbench_deep_scan.py index f0b38e700..2e39cd074 100644 --- a/plugins/codex-security/tests/test_workbench_deep_scan.py +++ b/plugins/codex-security/tests/test_workbench_deep_scan.py @@ -278,7 +278,7 @@ def claim() -> dict[str, object]: return claim_deep_scan_coordinator(state_dir, codex_home, scan_id) with sqlite3.connect(state_dir / "workbench.sqlite3") as connection: - assert connection.execute("SELECT MAX(version) FROM schema_migrations").fetchone() == (38,) + assert connection.execute("SELECT MAX(version) FROM schema_migrations").fetchone() == (39,) assert claim()["deepScan"]["coordinatorGeneration"] == 2 assert claim()["coordinatorDisposition"] == "observing" expire_deep_scan_coordinator(state_dir, scan_id) diff --git a/plugins/codex-security/tests/test_workbench_setup_and_migrations.py b/plugins/codex-security/tests/test_workbench_setup_and_migrations.py index 38df34a8d..d6d184796 100644 --- a/plugins/codex-security/tests/test_workbench_setup_and_migrations.py +++ b/plugins/codex-security/tests/test_workbench_setup_and_migrations.py @@ -405,7 +405,7 @@ def test_workbench_serializes_concurrent_first_run_migrations(tmp_path: Path) -> {"databasePath": str(state_dir / "workbench.sqlite3")}, ] with sqlite3.connect(state_dir / "workbench.sqlite3") as connection: - assert connection.execute("SELECT COUNT(*) FROM schema_migrations").fetchone() == (37,) + assert connection.execute("SELECT COUNT(*) FROM schema_migrations").fetchone() == (39,) def test_workbench_backfills_repository_targets_only_during_migration() -> None: @@ -797,7 +797,9 @@ def test_workbench_creates_single_final_schema(tmp_path: Path) -> None: (34, "associate findings with repositories"), (35, "persist finding dedupe groups"), (36, "persist local findings workflows"), + (37, "checkpoint validated dedupe reviews"), (38, "store findings workflow metadata in columns"), + (39, "store dedupe checkpoint bindings in columns"), ] assert {row[1] for row in connection.execute("PRAGMA table_info(workspaces)")} >= { "diff_target_kind", @@ -900,7 +902,7 @@ def test_workbench_upgrades_preexisting_database(tmp_path: Path) -> None: connection.execute("ALTER TABLE scans DROP COLUMN handoff_claim_token") run_workbench(state_dir, "database-info") with sqlite3.connect(database) as connection: - assert connection.execute("SELECT MAX(version) FROM schema_migrations").fetchone() == (38,) + assert connection.execute("SELECT MAX(version) FROM schema_migrations").fetchone() == (39,) assert {row[1] for row in connection.execute("PRAGMA table_info(scans)")} >= { "handoff_claimed_at", "handoff_claim_token", @@ -1923,7 +1925,9 @@ def test_workbench_upgrades_released_database_schema(tmp_path: Path) -> None: (34, "associate findings with repositories"), (35, "persist finding dedupe groups"), (36, "persist local findings workflows"), + (37, "checkpoint validated dedupe reviews"), (38, "store findings workflow metadata in columns"), + (39, "store dedupe checkpoint bindings in columns"), ] assert "capability_preflight_json" in { row[1] for row in connection.execute("PRAGMA table_info(workspaces)") @@ -2002,7 +2006,9 @@ def test_workbench_upgrades_pre_release_phase_progress_migration(tmp_path: Path) (34, "associate findings with repositories"), (35, "persist finding dedupe groups"), (36, "persist local findings workflows"), + (37, "checkpoint validated dedupe reviews"), (38, "store findings workflow metadata in columns"), + (39, "store dedupe checkpoint bindings in columns"), ] assert "continuation_thread_id" in { row[1] for row in connection.execute("PRAGMA table_info(scans)") @@ -2089,7 +2095,9 @@ def test_workbench_upgrades_pre_release_preflight_progress_migration(tmp_path: P (34, "associate findings with repositories"), (35, "persist finding dedupe groups"), (36, "persist local findings workflows"), + (37, "checkpoint validated dedupe reviews"), (38, "store findings workflow metadata in columns"), + (39, "store dedupe checkpoint bindings in columns"), ] assert "continuation_thread_id" in { row[1] for row in connection.execute("PRAGMA table_info(scans)") diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index a967b0f43..681f17cc4 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -930,6 +930,47 @@ packaged `start:server` script, without invoking the CLI. Docker runs Node directly so stop signals reach the server. The existing default Docker target and bulk-scan Compose configuration are unchanged. +### Read-only dashboard + +Open `http://localhost:3000/dashboard` on the running findings service. The UI +uses the public OpenAI Apps SDK UI design system, follows the browser's light +or dark preference, and polls the service every five seconds. It never starts, +cancels, resumes, publishes, edits, or deduplicates anything. + +The dashboard opens on Findings, followed by Duplicate groups. Both views +support search, repository filtering, sorting, pagination, and record details. +Findings show stored content and links to their duplicate groups. Groups link +back to their member findings, preserving separate overlapping groups and the +original finding records. + +The dashboard reads only findings, repository associations, and duplicate groups +stored in the service's configured database. It does not display scans or +workflows: publication sends findings, not remote scan or workflow history. It +does not read report or source paths from stored records. The UI retains the last +successful data on a refresh failure and shows a connection warning until polling +succeeds. + +`GET /v1/dashboard` returns a consistent read snapshot with overview counts, +repository choices, a page of records, and optional selected-record details: + +- `view`: `findings` (default) or `groups`. +- `query`, `repository`: optional search text and exact repository ID. +- `sort`: `activity` (default; most recently updated first) or `newest`. +- `limit`, `offset`: existing pagination conventions, defaulting to 50 and 0. +- `id`: optional exact record ID to include in `detail`; unknown IDs return + `detail: null` without hiding the list. + +Overview counts are service-wide, not filtered page totals. Responses and UI +assets are served by the same Node process; no separate frontend server, CDN, +model credentials, or new CLI flags are needed to view the dashboard. Compiled +HTML, JavaScript, and CSS are included in the npm package and container. Frontend +source, build tools, and tests are not shipped as runtime dependencies. + +The existing preview access boundary is unchanged. The dashboard contains +sensitive finding content: keep the service on a trusted local endpoint or behind +an authenticated proxy. It does not add authentication or broaden the default +network binding. + ### API `POST /v1/bulk/findings` accepts `{"findings": [...]}`, using the existing SDK @@ -1156,15 +1197,40 @@ Workflow metadata, stage statuses, errors, publication receipts, and results liv in the local workbench SQLite database under `CODEX_SECURITY_STATE_DIR`, outside the sealed scan artifacts. Identity, scan/artifact references, destination, scope, hashes, stage statuses, and errors use explicit columns; only receipts and result -payloads use JSON. Existing workflow rows are migrated atomically in place. +payloads use JSON. Review source, scope, model settings, and contract/hash bindings +also use columns; review results remain JSON. Existing workflows and checkpoints +are migrated atomically in place without changing their review keys. Successful empty results are stored as completed results, not treated as missing work. An empty scan can complete workflow publication with an empty receipt. Dry-run never advances a workflow stage. A completed `dedupe --workflow-id` returns its saved result without repeating -reviews or group writes. An interrupted dedupe stage currently runs its reviews -again; per-review checkpoints are a separate follow-up. A publication whose -acknowledgement was lost is retried using the service's existing idempotent upsert. +reviews or group writes. A publication whose acknowledgement was lost is retried +using the service's existing idempotent upsert. + +Each validated screening and pair review is checkpointed locally, +including DISTINCT decisions. Every SAME checkpoint retains its required +`canonicalFindingId` and generated `mergedFinding`. The merged record must satisfy +the Finding schema and preserve the canonical finding ID. Validation still happens +through `review_validator.submit_decisions`; invalid submissions are corrected in +the same review conversation, and invalid or unfinished reviews are not cached. + +Checkpoints bind to the exact original records and ordering, approved source path, +Git revision and current file contents (including ignored files), repository scope, +model and reasoning settings, Codex configuration and version, and prompt/contract version. Changed +inputs cause a new review rather than reusing a decision. Source changes during +review stop that attempt before group writes; restart with the same ID to review +the changed source. Original findings, never prior rationales or merged findings, +are supplied to later independent reviews. Source snapshots do not follow directory +links outside the approved checkout. + +Before posting groups, the workflow saves the exact final result and write payload. +If posting fails or its acknowledgement is lost, rerunning dedupe replays that +payload without looking up candidates or running models. Group persistence reuses +the existing membership identity, so replay does not create another group. The +dedupe stage completes only after acknowledgement. Empty results are also retained. +A completed workflow or pending write represents its already reviewed snapshot; +use another workflow ID for a fresh review rather than changing that saved result. ### Deduplication workflow diff --git a/sdk/typescript/dashboard/App.tsx b/sdk/typescript/dashboard/App.tsx new file mode 100644 index 000000000..801bcbabb --- /dev/null +++ b/sdk/typescript/dashboard/App.tsx @@ -0,0 +1,573 @@ +import { useEffect, useRef, useState, type ReactNode } from "react"; +import { Badge } from "@openai/apps-sdk-ui/components/Badge"; +import { Button } from "@openai/apps-sdk-ui/components/Button"; +import { Input } from "@openai/apps-sdk-ui/components/Input"; +import type { + DashboardDetail, + DashboardItem, + DashboardSnapshot, + DashboardView, +} from "../src/server/dashboard-types.js"; +import { pollDashboard } from "./polling.js"; + +const views: { id: DashboardView; label: string; description: string }[] = [ + { + id: "findings", + label: "Findings", + description: "Findings stored in this service.", + }, + { + id: "groups", + label: "Duplicate groups", + description: + "Reviewed duplicate relationships. Overlapping groups stay separate.", + }, +]; +const count = (value: number | null | undefined) => + value == null ? "—" : value.toLocaleString(); +const timestamp = (value: string | null | undefined) => + value ? new Date(value).toLocaleString() : "—"; + +function age(value: string | null | undefined, now: number) { + if (!value) return "—"; + const seconds = Math.max(0, Math.floor((now - Date.parse(value)) / 1_000)); + if (seconds < 60) return `${seconds}s ago`; + if (seconds < 3_600) return `${Math.floor(seconds / 60)}m ago`; + if (seconds < 86_400) return `${Math.floor(seconds / 3_600)}h ago`; + return `${Math.floor(seconds / 86_400)}d ago`; +} + +function Field({ name, children }: { name: string; children: ReactNode }) { + return ( +
+
{name}
+
{children ?? "—"}
+
+ ); +} + +type Navigate = (view: DashboardView, id?: string) => void; + +function RecordLinks({ + ids, + view, + navigate, + empty = "None", +}: { + ids: string[]; + view: DashboardView; + navigate: Navigate; + empty?: string; +}) { + if (ids.length === 0) return

{empty}

; + return ( +
    + {ids.map((id) => ( +
  • + +
  • + ))} +
+ ); +} + +function Inspector({ + detail, + navigate, +}: { + detail: DashboardDetail; + navigate: Navigate; +}) { + const finding = detail.finding; + return ( + <> + {finding && ( + <> +
+

{finding.title}

+
+ + {finding.severity.level} + + + {finding.confidence.level} confidence + +
+

{finding.summary}

+
+ + {finding.findingId} + + + {detail.item.repositoryIds.join(", ") || "Not recorded"} + +
+
+
+

Affected locations

+
    + {finding.locations.map((location, i) => ( +
  • + + {location.path}:{location.startLine} + {location.endLine != null ? `–${location.endLine}` : ""} + +
  • + ))} +
+
+ {( + [ + ["writeup", "Writeup"], + ["codeEvidence", "Code evidence"], + ["remediation", "Remediation"], + ["validation", "Validation"], + ["attackPath", "Attack path"], + ] as const + ).map(([key, title]) => + finding[key] != null ? ( +
+

{title}

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

Duplicate groups

+ group.groupId)} + view="groups" + navigate={navigate} + empty="No stored duplicate groups." + /> +
+ + )} + {detail.group && ( +
+

Group members · {detail.group.findingIds.length}

+

+ Reviewed together. Membership does not replace the original findings + or merge overlapping groups. +

+ +
+ {timestamp(detail.group.createdAt)} +
+
+ )} + + ); +} + +function FindingContent({ value }: { value: unknown }) { + if (typeof value === "string") return

{value}

; + return
{JSON.stringify(value, null, 2)}
; +} + +function Results({ + view, + items, + selected, + navigate, + now, +}: { + view: DashboardView; + items: DashboardItem[]; + selected: string; + navigate: Navigate; + now: number; +}) { + return ( +
+ + + + + + + {view === "findings" && } + {view === "groups" && } + + + + + + {items.map((item) => ( + + + + {view === "findings" && ( + + )} + {view === "groups" && ( + + )} + + + + ))} + +
+ {views.find((v) => v.id === view)!.label} +
{view === "findings" ? "Finding" : "Group"}RepositorySeverityMembersCreatedLast update
+ + {item.id !== item.title && ( + {item.id} + )} + + {item.repositoryIds.join(", ") || "—"} + + + {item.severity} + + {count(item.memberCount)} + + + +
+
+ ); +} + +export function App() { + const [view, setView] = useState("findings"); + const [query, setQuery] = useState(""); + const [repository, setRepository] = useState(""); + const [sort, setSort] = useState("activity"); + const [offset, setOffset] = useState(0); + const [selected, setSelected] = useState(""); + const inspectorHeading = useRef(null); + const resultsElement = useRef(null); + useEffect(() => { + // On narrow screens details follow the table; bring a new selection into view. + if (selected) inspectorHeading.current?.focus(); + }, [selected, view]); + const [saved, setSaved] = useState<{ + key: string; + data: DashboardSnapshot; + refreshed: number; + }>(); + const [error, setError] = useState<{ key: string; message: string }>(); + const parameters = new URLSearchParams({ + view, + query, + repository, + sort, + offset: String(offset), + }); + if (selected) parameters.set("id", selected); + const key = parameters.toString(); + useEffect( + () => + pollDashboard( + async (signal) => { + const response = await fetch(`../v1/dashboard?${key}`, { + signal, + cache: "no-store", + }); + if (!response.ok) + throw new Error( + `Dashboard request failed (HTTP ${response.status}).`, + ); + return (await response.json()) as DashboardSnapshot; + }, + (data) => { + setSaved({ key, data, refreshed: Date.now() }); + setError(undefined); + }, + (error) => + setError({ + key, + message: error instanceof Error ? error.message : String(error), + }), + ), + [key], + ); + + const data = saved?.key === key ? saved.data : undefined; + const failure = error?.key === key ? error.message : undefined; + // Keep overview/filter choices stable while a new page or detail is loading. + const overview = saved?.data.overview; + const now = Date.now(); + const current = views.find((v) => v.id === view)!; + function navigate(next: DashboardView, id?: string) { + if (next !== view) { + setView(next); + setQuery(""); + setRepository(""); + setOffset(0); + } + setSelected(id ?? ""); + } + function filter(setter: (value: string) => void, value: string) { + setter(value); + setOffset(0); + } + const counts: Record = { + findings: overview?.findings, + groups: overview?.groups, + }; + return ( +
+
+
+ + Codex Security + + Findings service +
+ Read only +
+
+ +
+
+
+

{current.label}

+

{current.description}

+
+
+
+
+
+
+ Stored findings + {count(overview?.findings)} +
+
+ Duplicate groups + {count(overview?.groups)} +
+
+
+ + + +
+ {failure && ( +
+ Unable to refresh +

+ {failure}{" "} + {data + ? "Showing the last successful data." + : "No current data to show."}{" "} + Retrying every five seconds. +

+
+ )} +
+
+ {!data ? ( +
+

+ {failure + ? "Data unavailable" + : `Loading ${current.label.toLowerCase()}…`} +

+
+ ) : data.items.length ? ( + + ) : ( +
+

+ {query || repository + ? "No matching records" + : `No ${current.label.toLowerCase()} yet`} +

+

+ {query || repository + ? "Try another search or filter." + : view === "findings" + ? "Published or imported findings will appear here." + : "Accepted duplicate groups will appear here after they are saved."} +

+
+ )} + {data && ( +
+ + {data.total === 0 + ? "0 records" + : `${data.offset + (data.items.length ? 1 : 0)}–${data.offset + data.items.length} of ${data.total}`} + +
+ + +
+
+ )} +
+ {selected && ( + + )} +
+

+ This dashboard only reads findings and duplicate groups stored in + this service. +

+
+
+
+ ); +} diff --git a/sdk/typescript/dashboard/index.html b/sdk/typescript/dashboard/index.html new file mode 100644 index 000000000..7e305b1cd --- /dev/null +++ b/sdk/typescript/dashboard/index.html @@ -0,0 +1,18 @@ + + + + + + + Findings · Codex Security + + + + +
+ + + diff --git a/sdk/typescript/dashboard/index.tsx b/sdk/typescript/dashboard/index.tsx new file mode 100644 index 000000000..65d7626e3 --- /dev/null +++ b/sdk/typescript/dashboard/index.tsx @@ -0,0 +1,11 @@ +import "./styles.css"; +import { createRoot } from "react-dom/client"; +import { App } from "./App.js"; + +const theme = matchMedia("(prefers-color-scheme: dark)"); +const applyTheme = () => { + document.documentElement.dataset["theme"] = theme.matches ? "dark" : "light"; +}; +applyTheme(); +theme.addEventListener("change", applyTheme); +createRoot(document.getElementById("root")!).render(); diff --git a/sdk/typescript/dashboard/polling.ts b/sdk/typescript/dashboard/polling.ts new file mode 100644 index 000000000..a65aec6f7 --- /dev/null +++ b/sdk/typescript/dashboard/polling.ts @@ -0,0 +1,30 @@ +import type { DashboardSnapshot } from "../src/server/dashboard-types.js"; + +/** Poll immediately and every five seconds, without overlapping requests. */ +export function pollDashboard( + read: (signal: AbortSignal) => Promise, + onData: (snapshot: DashboardSnapshot) => void, + onError: (error: unknown) => void, + clock: Pick = globalThis, +): () => void { + const controller = new AbortController(); + let pending = false; + async function refresh() { + if (pending || controller.signal.aborted) return; + pending = true; + try { + const snapshot = await read(controller.signal); + if (!controller.signal.aborted) onData(snapshot); + } catch (error) { + if (!controller.signal.aborted) onError(error); + } finally { + pending = false; + } + } + void refresh(); + const timer = clock.setInterval(() => void refresh(), 5_000); + return () => { + controller.abort(); + clock.clearInterval(timer); + }; +} diff --git a/sdk/typescript/dashboard/styles.css b/sdk/typescript/dashboard/styles.css new file mode 100644 index 000000000..c0b42fa4a --- /dev/null +++ b/sdk/typescript/dashboard/styles.css @@ -0,0 +1,433 @@ +@import "tailwindcss"; +@import "@openai/apps-sdk-ui/css"; +@source "../node_modules/@openai/apps-sdk-ui"; +@source "./**/*.{ts,tsx}"; + +body { + margin: 0; + background: var(--color-surface); + font-family: var(--font-sans); +} +.dashboard-shell { + min-height: 100vh; +} +.app-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + padding: 18px 28px; + border-bottom: 1px solid var(--color-border); +} +.brand { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 12px; + font-size: 14px; +} +.brand-mark { + font-size: 24px; +} +.header-divider { + height: 18px; + width: 1px; + background: var(--color-border); +} +.workspace { + display: grid; + grid-template-columns: 200px minmax(0, 1fr); +} +.sidebar { + padding: 30px 16px; + border-right: 1px solid var(--color-border); + display: flex; + flex-direction: column; + gap: 16px; +} +.eyebrow { + padding: 0 12px; + font-size: 12px; + color: var(--color-text-secondary); +} +.sidebar nav { + display: grid; + gap: 4px; +} +.nav-count { + margin-left: auto; + font-variant-numeric: tabular-nums; + color: var(--color-text-secondary); +} +main { + min-width: 0; + padding: 36px 32px 24px; +} +h1 { + font-size: 28px; + font-weight: 600; + letter-spacing: -0.8px; + margin: 0 0 6px; +} +h2 { + font-size: 18px; + font-weight: 500; +} +h3 { + font-size: 14px; + font-weight: 600; + margin: 0 0 12px; +} +p { + font-size: 14px; + line-height: 1.6; +} +.page-heading { + display: flex; + align-items: flex-start; + justify-content: space-between; + flex-wrap: wrap; + gap: 20px; + margin-bottom: 28px; +} +.refresh-state { + display: flex; + align-items: flex-start; + gap: 8px; + color: var(--color-text-secondary); + font-size: 12px; + padding-top: 6px; +} +.refresh-state small { + display: block; + margin-top: 4px; + font-size: 11px; +} +.connection-dot { + display: block; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--color-text-success); + margin-top: 5px; +} +.connection-dot.disconnected { + background: var(--color-text-danger); +} +.overview { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + border-block: 1px solid var(--color-border); + padding: 22px 0; + gap: 18px; +} +.overview > div { + display: grid; + gap: 8px; +} +.overview span { + font-size: 12px; + color: var(--color-text-secondary); +} +.overview strong { + font-size: 28px; + font-weight: 500; + line-height: 1.2; + font-variant-numeric: tabular-nums; +} +.filters { + display: flex; + flex-wrap: wrap; + align-items: end; + gap: 12px; + padding: 24px 0; +} +.filters > label { + display: grid; + gap: 6px; + min-width: 130px; + max-width: 320px; +} +.filters > label > span:not(.sr-only) { + font-size: 12px; + color: var(--color-text-secondary); +} +.filters .search-field { + min-width: 190px; + flex: 1; + max-width: none; +} +.filters select { + appearance: auto; + min-width: 0; + width: 100%; + height: 40px; + padding: 0 12px; + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + background: var(--color-surface); + color: var(--color-text); + font-size: 14px; +} +.filters select:focus-visible { + outline: 2px solid var(--color-text); + outline-offset: 2px; +} +.content-layout { + min-width: 0; +} +.content-layout.with-inspector { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(320px, 400px); + gap: 24px; + align-items: start; +} +.results { + min-width: 0; + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); + overflow: hidden; +} +.table-scroll { + overflow-x: auto; +} +table { + width: 100%; + border-collapse: collapse; + text-align: left; + font-size: 13px; +} +thead { + background: var(--color-surface-secondary); +} +th, +td { + padding: 15px 16px; + vertical-align: top; + border-bottom: 1px solid var(--color-border); +} +thead th { + font-weight: 500; + font-size: 12px; + color: var(--color-text-secondary); + white-space: nowrap; +} +tbody th { + font-weight: 400; + min-width: 220px; + max-width: 360px; +} +tbody tr:last-child th, +tbody tr:last-child td { + border-bottom: 0; +} +tbody tr[data-selected] { + background: var(--color-background-primary-soft); +} +tbody tr:hover { + background: var(--color-surface-secondary); +} +.record-link { + color: var(--color-text); + text-align: left; + text-decoration: underline; + text-decoration-color: var(--color-border); + text-underline-offset: 3px; + cursor: pointer; + overflow-wrap: anywhere; +} +.record-link:hover { + text-decoration-color: currentColor; +} +.record-link:focus-visible { + outline: 2px solid var(--color-text); + outline-offset: 3px; +} +.row-title { + font-weight: 500; + text-decoration: none; +} +.row-subtitle { + display: block; + font-size: 11px; + color: var(--color-text-secondary); + margin-top: 5px; + overflow-wrap: anywhere; +} +.repository-cell { + min-width: 140px; + max-width: 240px; + overflow-wrap: anywhere; +} +.numeric { + font-variant-numeric: tabular-nums; +} +time { + font-size: 12px; +} +.pagination { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 12px; + border-top: 1px solid var(--color-border); + padding: 14px 16px; + font-size: 12px; + color: var(--color-text-secondary); +} +.inline-values { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; +} +.empty-state { + text-align: center; + padding: 64px 28px; +} +.empty-state p { + max-width: 470px; + margin: 12px auto 0; + color: var(--color-text-secondary); +} +.state-note { + font-size: 12px; + color: var(--color-text-secondary); + margin-top: 20px; +} +.inspector { + min-width: 0; + border-left: 1px solid var(--color-border); + padding-left: 24px; + overflow-wrap: anywhere; +} +.inspector-heading { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; +} +.detail-section { + padding: 20px 0; + border-bottom: 1px solid var(--color-border); +} +.detail-section:last-child { + border-bottom: 0; +} +.detail-section p { + font-size: 13px; +} +.detail-section dl { + margin-top: 12px; +} +.detail-field { + display: grid; + grid-template-columns: 112px minmax(0, 1fr); + gap: 12px; + margin: 10px 0; + font-size: 12px; +} +.detail-field dt { + color: var(--color-text-secondary); +} +.detail-field dd { + margin: 0; +} +code, +pre { + font-family: var(--font-mono); + font-size: 12px; +} +.record-links { + list-style: none; + padding: 0; + margin: 12px 0 0; + display: grid; + gap: 10px; + font-size: 12px; +} +.record-links li { + overflow-wrap: anywhere; +} +.error-message { + background: var(--color-background-danger-soft); + border-radius: var(--radius-md); + padding: 14px; + margin: 12px 0; + font-size: 13px; + overflow-wrap: anywhere; +} +.error-message strong { + color: var(--color-text-danger); +} +.error-message p { + margin-top: 4px; + white-space: pre-wrap; + font-size: 13px; +} +.finding-copy { + white-space: pre-wrap; + overflow-wrap: anywhere; + margin-top: 14px; +} +@media (max-width: 1200px) { + .content-layout.with-inspector { + grid-template-columns: minmax(0, 1fr); + } + .inspector { + border-left: 0; + padding: 0; + border-top: 1px solid var(--color-border); + } +} +@media (max-width: 800px) { + .workspace { + grid-template-columns: minmax(0, 1fr); + } + .sidebar { + padding: 12px 16px; + border-right: 0; + border-bottom: 1px solid var(--color-border); + } + .sidebar nav { + display: flex; + flex-wrap: wrap; + } + .sidebar nav > * { + width: auto; + flex: 1; + } + .eyebrow { + display: none; + } + main { + padding: 24px 16px; + } + .app-header { + padding: 16px; + } +} +@media (max-width: 480px) { + .brand { + gap: 8px; + } + .brand > span:last-child, + .header-divider { + display: none; + } + .filters > label { + flex: 1; + min-width: 125px; + max-width: 100%; + } + .filters .search-field { + flex-basis: 100%; + } + .sidebar nav > * { + flex-basis: 40%; + } + .detail-field { + grid-template-columns: 96px minmax(0, 1fr); + } +} diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json index 8846420aa..7d1ea9898 100644 --- a/sdk/typescript/package.json +++ b/sdk/typescript/package.json @@ -44,7 +44,7 @@ "scripts": { "audit:prod": "pnpm audit --prod --audit-level high", "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"", - "build": "node --run clean && tsc -p tsconfig.build.json", + "build": "node --run clean && tsc -p tsconfig.build.json && node scripts/build-dashboard.mjs", "build:plugin": "node scripts/build-plugin.mjs", "check:plugin-source": "node scripts/check-plugin-source.mjs", "check:package": "node scripts/check-package.mjs", @@ -81,16 +81,23 @@ "smol-toml": "1.6.1" }, "devDependencies": { + "@openai/apps-sdk-ui": "0.2.2", "@stryker-mutator/core": "9.6.1", + "@tailwindcss/postcss": "4.3.3", "@types/bun": "1.3.13", "@types/node": "22.19.17", "@types/papaparse": "5.3.15", "@types/react": "19.2.14", + "@types/react-dom": "19.2.3", "@types/semver": "7.8.0", + "esbuild": "0.28.2", "fast-check": "4.9.0", "ink-testing-library": "4.0.0", "json-schema-to-typescript": "15.0.4", + "postcss": "8.5.6", "prettier": "3.2.5", + "react-dom": "19.2.4", + "tailwindcss": "4.3.3", "typescript": "5.7.3" } } diff --git a/sdk/typescript/plugin-files.json b/sdk/typescript/plugin-files.json index c435b22fc..7d77201ce 100644 --- a/sdk/typescript/plugin-files.json +++ b/sdk/typescript/plugin-files.json @@ -66,6 +66,7 @@ "scripts/workbench_feedback.py", "scripts/workbench_finding_index.py", "scripts/workbench_finding_workflows.py", + "scripts/workbench_dashboard.py", "scripts/workbench_findings.py", "scripts/workbench_native_indexes.py", "scripts/workbench_progress.py", diff --git a/sdk/typescript/pnpm-lock.yaml b/sdk/typescript/pnpm-lock.yaml index 44d7ab5c9..5496e12fd 100644 --- a/sdk/typescript/pnpm-lock.yaml +++ b/sdk/typescript/pnpm-lock.yaml @@ -60,9 +60,15 @@ importers: specifier: 1.6.1 version: 1.6.1 devDependencies: + '@openai/apps-sdk-ui': + specifier: 0.2.2 + version: 0.2.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.3.3) '@stryker-mutator/core': specifier: 9.6.1 version: 9.6.1(@types/node@22.19.17) + '@tailwindcss/postcss': + specifier: 4.3.3 + version: 4.3.3 '@types/bun': specifier: 1.3.13 version: 1.3.13 @@ -75,9 +81,15 @@ importers: '@types/react': specifier: 19.2.14 version: 19.2.14 + '@types/react-dom': + specifier: 19.2.3 + version: 19.2.3(@types/react@19.2.14) '@types/semver': specifier: 7.8.0 version: 7.8.0 + esbuild: + specifier: 0.28.2 + version: 0.28.2 fast-check: specifier: 4.9.0 version: 4.9.0 @@ -87,9 +99,18 @@ importers: json-schema-to-typescript: specifier: 15.0.4 version: 15.0.4 + postcss: + specifier: 8.5.6 + version: 8.5.6 prettier: specifier: 3.2.5 version: 3.2.5 + react-dom: + specifier: 19.2.4 + version: 19.2.4(react@19.2.4) + tailwindcss: + specifier: 4.3.3 + version: 4.3.3 typescript: specifier: 5.7.3 version: 5.7.3 @@ -100,6 +121,10 @@ packages: resolution: {integrity: sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==} engines: {node: '>=18'} + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + '@apidevtools/json-schema-ref-parser@11.9.3': resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} engines: {node: '>= 16'} @@ -245,6 +270,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.29.7': resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} @@ -260,6 +289,177 @@ packages: '@cfworker/json-schema@4.1.1': resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/react-dom@2.1.9': + resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + '@graphql-typed-document-node/core@3.2.0': resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} peerDependencies: @@ -535,6 +735,12 @@ packages: '@octokit/types@16.0.0': resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + '@openai/apps-sdk-ui@0.2.2': + resolution: {integrity: sha512-KaG+6qcVCKVRe51wr2te68OKoHfjRQPDVVkugxiF+SaZlgLpNbxOfyrL/FyUBmHbjh1q4p7eWDYAd75jHseZ9A==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + tailwindcss: ^4.0.10 + '@openai/codex-sdk@0.149.1': resolution: {integrity: sha512-R00Rz5327LefZggAxl28r7vFQq1vxa91OxtjZJOsQfAM/MyH8InW5qwwRu6pzUmRGp1E29XrOzm7u1TeV5Yz2A==} engines: {node: '>=18'} @@ -580,1285 +786,3574 @@ packages: cpu: [x64] os: [win32] - '@scalar/openapi-types@0.8.0': - resolution: {integrity: sha512-WmaxVSfvY5K/TwcG2B2TU1WOe1As1uc2s7myswtP6dBlcjU3hM08SApxv/jmyGaCE8t4gO5BBhmHY4pDUfmr2g==} - engines: {node: '>=22'} + '@radix-ui/number@1.1.3': + resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==} - '@sec-ant/readable-stream@0.4.1': - resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@radix-ui/primitive@1.1.7': + resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==} - '@sindresorhus/merge-streams@4.0.0': - resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} - engines: {node: '>=18'} + '@radix-ui/react-accessible-icon@1.1.15': + resolution: {integrity: sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@stryker-mutator/api@9.6.1': - resolution: {integrity: sha512-g8VNoFWQWbx0pdal3Vt8jVCZW+v3sc3gi94iI0GVtVgUGTqphAjJF6EAruPTx0lqvtonsaAxn5TD36hcG1d6Wg==} - engines: {node: '>=20.0.0'} + '@radix-ui/react-accordion@1.2.20': + resolution: {integrity: sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@stryker-mutator/core@9.6.1': - resolution: {integrity: sha512-WMgnvf+Wyh/yiruhNZwc8w8DlzmmjXhPjSn5MR8RhAXzlnWji8TQrUYgBUkHk9bEgSaIlB3KZHm37iiU5Q2cLQ==} - engines: {node: '>=20.0.0'} - hasBin: true + '@radix-ui/react-alert-dialog@1.1.23': + resolution: {integrity: sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@stryker-mutator/instrumenter@9.6.1': - resolution: {integrity: sha512-5K8wH4Pthly25c2uKKik4Dfcoeou7sbJdFS6u3QIYHlulgFVDJwtEMWTZGkZfs7IiUEXIDNa0keRACq5jn5AvA==} - engines: {node: '>=20.0.0'} + '@radix-ui/react-arrow@1.1.15': + resolution: {integrity: sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@stryker-mutator/util@9.6.1': - resolution: {integrity: sha512-Lk/ALVctJjFv1vvwR+CFoKzDCWvsBlq7flDUnmnpuwTrGbm156EdZD1Jjq4o8KdOap0ezUZqQNE9OAI1m2+pUQ==} + '@radix-ui/react-aspect-ratio@1.1.15': + resolution: {integrity: sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@toon-format/toon@2.3.0': - resolution: {integrity: sha512-/Ew9etdRQKVMnm9fDaCG0JjyAOK/O7T0M97oum1aW4W+UR8ZhVVPBanIV7oWgHBiGlnVxV9M55PWQCHofDV07w==} + '@radix-ui/react-avatar@1.2.6': + resolution: {integrity: sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@types/bun@1.3.13': - resolution: {integrity: sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw==} + '@radix-ui/react-checkbox@1.3.11': + resolution: {integrity: sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@radix-ui/react-collapsible@1.1.20': + resolution: {integrity: sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@types/lodash@4.17.24': - resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + '@radix-ui/react-collection@1.1.15': + resolution: {integrity: sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@types/node@22.19.17': - resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} + '@radix-ui/react-compose-refs@1.1.5': + resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@types/papaparse@5.3.15': - resolution: {integrity: sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==} + '@radix-ui/react-context-menu@2.3.7': + resolution: {integrity: sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@radix-ui/react-context@1.2.2': + resolution: {integrity: sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@types/semver@7.8.0': - resolution: {integrity: sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ==} + '@radix-ui/react-dialog@1.1.23': + resolution: {integrity: sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@radix-ui/react-direction@1.1.4': + resolution: {integrity: sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + '@radix-ui/react-dismissable-layer@1.1.19': + resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - ajv@8.20.0: - resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + '@radix-ui/react-dropdown-menu@2.1.24': + resolution: {integrity: sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - angular-html-parser@10.4.0: - resolution: {integrity: sha512-++nLNyZwRfHqFh7akH5Gw/JYizoFlMRz0KRigfwfsLqV8ZqlcVRb1LkPEWdYvEKDnbktknM2J4BXaYUGrQZPww==} - engines: {node: '>= 14'} + '@radix-ui/react-focus-guards@1.1.6': + resolution: {integrity: sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - ansi-escapes@7.3.0: - resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} - engines: {node: '>=18'} + '@radix-ui/react-focus-scope@1.1.16': + resolution: {integrity: sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} + '@radix-ui/react-form@0.1.16': + resolution: {integrity: sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} + '@radix-ui/react-hover-card@1.1.23': + resolution: {integrity: sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + '@radix-ui/react-id@1.1.4': + resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - auto-bind@5.0.1: - resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} + '@radix-ui/react-label@2.1.15': + resolution: {integrity: sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + '@radix-ui/react-menu@2.1.24': + resolution: {integrity: sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - baseline-browser-mapping@2.11.13: - resolution: {integrity: sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==} - engines: {node: '>=6.0.0'} - hasBin: true + '@radix-ui/react-menubar@1.1.24': + resolution: {integrity: sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - before-after-hook@4.0.0: - resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + '@radix-ui/react-navigation-menu@1.2.22': + resolution: {integrity: sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - brace-expansion@5.0.9: - resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} - engines: {node: 20 || >=22} + '@radix-ui/react-one-time-password-field@0.1.16': + resolution: {integrity: sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - browserslist@4.28.8: - resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + '@radix-ui/react-password-toggle-field@0.1.11': + resolution: {integrity: sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + '@radix-ui/react-popover@1.1.23': + resolution: {integrity: sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - bun-types@1.3.13: - resolution: {integrity: sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA==} + '@radix-ui/react-popper@1.3.7': + resolution: {integrity: sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} + '@radix-ui/react-portal@1.1.17': + resolution: {integrity: sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} + '@radix-ui/react-presence@1.1.10': + resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - caniuse-lite@1.0.30001809: - resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + '@radix-ui/react-primitive@2.1.10': + resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + '@radix-ui/react-progress@1.1.16': + resolution: {integrity: sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - chardet@2.2.0: - resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + '@radix-ui/react-radio-group@1.4.7': + resolution: {integrity: sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - cli-boxes@3.0.0: - resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} - engines: {node: '>=10'} + '@radix-ui/react-roving-focus@1.1.19': + resolution: {integrity: sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - cli-cursor@4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + '@radix-ui/react-scroll-area@1.2.18': + resolution: {integrity: sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - cli-truncate@5.2.0: - resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} - engines: {node: '>=20'} + '@radix-ui/react-select@2.3.7': + resolution: {integrity: sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} + '@radix-ui/react-separator@1.1.15': + resolution: {integrity: sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - code-excerpt@4.0.0: - resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + '@radix-ui/react-slider@1.4.7': + resolution: {integrity: sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - commander@14.0.3: - resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} - engines: {node: '>=20'} + '@radix-ui/react-slot@1.3.3': + resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - content-type@2.0.0: - resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} - engines: {node: '>=18'} + '@radix-ui/react-switch@1.3.7': + resolution: {integrity: sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + '@radix-ui/react-tabs@1.1.21': + resolution: {integrity: sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - convert-to-spaces@2.0.1: - resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + '@radix-ui/react-toast@1.2.23': + resolution: {integrity: sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} + '@radix-ui/react-toggle-group@1.1.19': + resolution: {integrity: sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + '@radix-ui/react-toggle@1.1.18': + resolution: {integrity: sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} + '@radix-ui/react-toolbar@1.1.19': + resolution: {integrity: sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==} peerDependencies: - supports-color: '*' + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - supports-color: + '@types/react': + optional: true + '@types/react-dom': optional: true - des.js@1.1.0: - resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} + '@radix-ui/react-tooltip@1.2.16': + resolution: {integrity: sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - diff-match-patch@1.0.5: - resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + '@radix-ui/react-use-callback-ref@1.1.4': + resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} + '@radix-ui/react-use-controllable-state@1.2.6': + resolution: {integrity: sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - electron-to-chromium@1.5.404: - resolution: {integrity: sha512-3WJtd7/lVq2Jnuz6wed1l9+1ZD2u2Tet1/1NBc4Iedkmgbu+I7YuAqdAQ8T+VZtnwysMsAf3IqSq9D1gyZjA2g==} + '@radix-ui/react-use-effect-event@0.0.5': + resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + '@radix-ui/react-use-escape-keydown@1.1.5': + resolution: {integrity: sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + '@radix-ui/react-use-is-hydrated@0.1.3': + resolution: {integrity: sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - environment@1.1.0: - resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} - engines: {node: '>=18'} + '@radix-ui/react-use-layout-effect@1.1.4': + resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} + '@radix-ui/react-use-previous@1.1.4': + resolution: {integrity: sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} + '@radix-ui/react-use-rect@1.1.4': + resolution: {integrity: sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - es-object-atoms@1.1.2: - resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} - engines: {node: '>= 0.4'} + '@radix-ui/react-use-size@1.1.4': + resolution: {integrity: sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - es-toolkit@1.50.0: - resolution: {integrity: sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==} + '@radix-ui/react-visually-hidden@1.2.11': + resolution: {integrity: sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} + '@radix-ui/rect@1.1.3': + resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==} - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} + '@scalar/openapi-types@0.8.0': + resolution: {integrity: sha512-WmaxVSfvY5K/TwcG2B2TU1WOe1As1uc2s7myswtP6dBlcjU3hM08SApxv/jmyGaCE8t4gO5BBhmHY4pDUfmr2g==} + engines: {node: '>=22'} - execa@9.6.1: - resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} - engines: {node: ^18.19.0 || >=20.5.0} + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - extract-zip@2.0.1: - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} - fast-check@4.9.0: - resolution: {integrity: sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg==} - engines: {node: '>=12.17.0'} + '@stryker-mutator/api@9.6.1': + resolution: {integrity: sha512-g8VNoFWQWbx0pdal3Vt8jVCZW+v3sc3gi94iI0GVtVgUGTqphAjJF6EAruPTx0lqvtonsaAxn5TD36hcG1d6Wg==} + engines: {node: '>=20.0.0'} - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + '@stryker-mutator/core@9.6.1': + resolution: {integrity: sha512-WMgnvf+Wyh/yiruhNZwc8w8DlzmmjXhPjSn5MR8RhAXzlnWji8TQrUYgBUkHk9bEgSaIlB3KZHm37iiU5Q2cLQ==} + engines: {node: '>=20.0.0'} + hasBin: true - fast-string-truncated-width@3.0.3: - resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + '@stryker-mutator/instrumenter@9.6.1': + resolution: {integrity: sha512-5K8wH4Pthly25c2uKKik4Dfcoeou7sbJdFS6u3QIYHlulgFVDJwtEMWTZGkZfs7IiUEXIDNa0keRACq5jn5AvA==} + engines: {node: '>=20.0.0'} - fast-string-width@3.0.2: - resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + '@stryker-mutator/util@9.6.1': + resolution: {integrity: sha512-Lk/ALVctJjFv1vvwR+CFoKzDCWvsBlq7flDUnmnpuwTrGbm156EdZD1Jjq4o8KdOap0ezUZqQNE9OAI1m2+pUQ==} - fast-uri@3.1.5: - resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} + '@tailwindcss/node@4.3.3': + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} - fast-wrap-ansi@0.2.2: - resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + '@tailwindcss/oxide-android-arm64@4.3.3': + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] - fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + '@tailwindcss/oxide-darwin-arm64@4.3.3': + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true + '@tailwindcss/oxide-darwin-x64@4.3.3': + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + '@tailwindcss/oxide-freebsd-x64@4.3.3': + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] - figures@6.1.0: - resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} - engines: {node: '>=18'} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] - get-east-asian-width@1.6.0: - resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} - engines: {node: '>=18'} + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] - get-stream@9.0.1: - resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} - engines: {node: '>=18'} + '@tailwindcss/oxide@4.3.3': + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} + engines: {node: '>= 20'} - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} + '@tailwindcss/postcss@4.3.3': + resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==} - graphql@17.0.2: - resolution: {integrity: sha512-FRWbddMxfkjiB7z+aQDWIR+E34xo9I8c9mtK2RPv8PmMzKRvrdsreHL/Ui/TmwHJfhHChEtsFPyMHKI+xuarQQ==} - engines: {node: ^22.0.0 || ^24.0.0 || ^25.0.0 || >=26.0.0} + '@toon-format/toon@2.3.0': + resolution: {integrity: sha512-/Ew9etdRQKVMnm9fDaCG0JjyAOK/O7T0M97oum1aW4W+UR8ZhVVPBanIV7oWgHBiGlnVxV9M55PWQCHofDV07w==} - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} + '@types/bun@1.3.13': + resolution: {integrity: sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw==} - hasown@2.0.4: - resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} - engines: {node: '>= 0.4'} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} - human-signals@8.0.1: - resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} - engines: {node: '>=18.18.0'} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - iconv-lite@0.7.3: - resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} - engines: {node: '>=0.10.0'} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - incur@0.4.13: - resolution: {integrity: sha512-BeKlYFLIsRCgC8IxsUd3S2/4kPeZaNf1Rbz+Kz41jQII4jOgr7j5w4KYe00aAEQa3V0Ur57BVxE5JDTx8L2s5Q==} - engines: {node: '>=22'} - hasBin: true + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} - indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + '@types/katex@0.16.8': + resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} - ink-testing-library@4.0.0: - resolution: {integrity: sha512-yF92kj3pmBvk7oKbSq5vEALO//o7Z9Ck/OaLNlkzXNeYdwfpxMQkSowGTFUCS5MSu9bWfSZMewGpp7bFc66D7Q==} - engines: {node: '>=18'} - peerDependencies: - '@types/react': '>=18.0.0' - peerDependenciesMeta: - '@types/react': - optional: true + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} - ink@6.8.0: - resolution: {integrity: sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==} - engines: {node: '>=20'} - peerDependencies: - '@types/react': '>=19.0.0' - react: '>=19.0.0' - react-devtools-core: '>=6.1.2' - peerDependenciesMeta: - '@types/react': - optional: true - react-devtools-core: - optional: true + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - is-fullwidth-code-point@5.1.0: - resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} - engines: {node: '>=18'} + '@types/node@22.19.17': + resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + '@types/papaparse@5.3.15': + resolution: {integrity: sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==} - is-in-ci@2.0.0: - resolution: {integrity: sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==} - engines: {node: '>=20'} - hasBin: true + '@types/prismjs@1.26.6': + resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==} - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 - is-stream@4.0.1: - resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} - engines: {node: '>=18'} + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - is-unicode-supported@2.1.0: - resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} - engines: {node: '>=18'} + '@types/semver@7.8.0': + resolution: {integrity: sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ==} - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - js-md4@0.3.2: - resolution: {integrity: sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - js-tiktoken@1.0.21: - resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==} + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + '@ungap/structured-clone@1.3.3': + resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} - js-yaml@4.3.1: - resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} - hasBin: true + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} - json-rpc-2.0@1.7.1: - resolution: {integrity: sha512-JqZjhjAanbpkXIzFE7u8mE/iFblawwlXtONaCvRqI+pyABVz7B4M1EUNpyVW+dZjqgQ2L5HFmZCmOCgUKm00hg==} + angular-html-parser@10.4.0: + resolution: {integrity: sha512-++nLNyZwRfHqFh7akH5Gw/JYizoFlMRz0KRigfwfsLqV8ZqlcVRb1LkPEWdYvEKDnbktknM2J4BXaYUGrQZPww==} + engines: {node: '>= 14'} - json-schema-to-typescript@15.0.4: - resolution: {integrity: sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==} - engines: {node: '>=16.0.0'} - hasBin: true + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} - json-with-bigint@3.5.10: - resolution: {integrity: sha512-Vcx+JVNEBts/xfcoCS69sKrOhOk/3TVlvlT+XzUOefVKnnrbYSCKpDCm10pohsJFtsJVYnwa/cXRZ4eElzaM6w==} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + auto-bind@5.0.1: + resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.11.13: + resolution: {integrity: sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==} + engines: {node: '>=6.0.0'} hasBin: true - lodash.groupby@4.6.0: - resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + before-after-hook@4.0.0: + resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + bun-types@1.3.13: + resolution: {integrity: sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + code-excerpt@4.0.0: + resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - minimatch@10.2.6: - resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} - engines: {node: 18 || 20 || >=22} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} - mutation-server-protocol@0.4.1: - resolution: {integrity: sha512-SBGK0j8hLDne7bktgThKI8kGvGTx3rY3LAeQTmOKZ5bVnL/7TorLMvcVF7dIPJCu5RNUWhkkuF53kurygYVt3g==} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} engines: {node: '>=18'} - mutation-testing-elements@3.7.3: - resolution: {integrity: sha512-SMeIPxngJpfjfNYctFpYQQtlBlZaVO0aoB3FKdwrI8Ee/2bkyUuCZzAOCLv1U9fnmfA37dPFq0Owduoxs2XgGQ==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - mutation-testing-metrics@3.7.3: - resolution: {integrity: sha512-B8QrP0ZomErzTPNlhrzKWPNBln+3afwBZPHv0Q7N8wZZTYxMptzb/Gdm3ExXVmioVYrtZAtsDs7W/T/b2AixOQ==} + convert-to-spaces@2.0.1: + resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mutation-testing-report-schema@3.7.3: - resolution: {integrity: sha512-BHm3MYq+ckO+t5CtlG8zpqxc75rdJCkxVlE+fGuGJM3F7tNCQ/OW2N+TQVHN3BHsYa84+BFc6g3AwDYkUsw2MA==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} - mute-stream@3.0.0: - resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} - engines: {node: ^20.17.0 || >=22.9.0} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - node-releases@2.0.53: - resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} - engines: {node: '>=18'} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true - npm-run-path@6.0.0: - resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + des.js@1.1.0: + resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff-match-patch@1.0.5: + resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.404: + resolution: {integrity: sha512-3WJtd7/lVq2Jnuz6wed1l9+1ZD2u2Tet1/1NBc4Iedkmgbu+I7YuAqdAQ8T+VZtnwysMsAf3IqSq9D1gyZjA2g==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + enhanced-resolve@5.24.5: + resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} + engines: {node: '>=10.13.0'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} - papaparse@5.5.3: - resolution: {integrity: sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==} + es-toolkit@1.50.0: + resolution: {integrity: sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==} - parse-ms@4.0.0: - resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} engines: {node: '>=18'} + hasBin: true - patch-console@2.0.0: - resolution: {integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - pdfjs-dist@6.2.108: - resolution: {integrity: sha512-YxFb+SQcodN2rnX9Tn3dHYlqfb7NjlzzfONPpJd+AKoKtUjEdevTfbC07d5TcczzOK6261auRkP/M8OBHs9vFQ==} - engines: {node: '>=22.13.0 || >=24'} + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fast-check@4.9.0: + resolution: {integrity: sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg==} + engines: {node: '>=12.17.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-uri@3.1.5: + resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphql@17.0.2: + resolution: {integrity: sha512-FRWbddMxfkjiB7z+aQDWIR+E34xo9I8c9mtK2RPv8PmMzKRvrdsreHL/Ui/TmwHJfhHChEtsFPyMHKI+xuarQQ==} + engines: {node: ^22.0.0 || ^24.0.0 || ^25.0.0 || >=26.0.0} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hast-util-from-dom@5.0.1: + resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} + + hast-util-from-html-isomorphic@2.0.0: + resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + highlightjs-vue@1.0.0: + resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} + + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + incur@0.4.13: + resolution: {integrity: sha512-BeKlYFLIsRCgC8IxsUd3S2/4kPeZaNf1Rbz+Kz41jQII4jOgr7j5w4KYe00aAEQa3V0Ur57BVxE5JDTx8L2s5Q==} + engines: {node: '>=22'} + hasBin: true + + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ink-testing-library@4.0.0: + resolution: {integrity: sha512-yF92kj3pmBvk7oKbSq5vEALO//o7Z9Ck/OaLNlkzXNeYdwfpxMQkSowGTFUCS5MSu9bWfSZMewGpp7bFc66D7Q==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': '>=18.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + + ink@6.8.0: + resolution: {integrity: sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==} + engines: {node: '>=20'} + peerDependencies: + '@types/react': '>=19.0.0' + react: '>=19.0.0' + react-devtools-core: '>=6.1.2' + peerDependenciesMeta: + '@types/react': + optional: true + react-devtools-core: + optional: true + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-in-ci@2.0.0: + resolution: {integrity: sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==} + engines: {node: '>=20'} + hasBin: true + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + js-md4@0.3.2: + resolution: {integrity: sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==} + + js-tiktoken@1.0.21: + resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-rpc-2.0@1.7.1: + resolution: {integrity: sha512-JqZjhjAanbpkXIzFE7u8mE/iFblawwlXtONaCvRqI+pyABVz7B4M1EUNpyVW+dZjqgQ2L5HFmZCmOCgUKm00hg==} + + json-schema-to-typescript@15.0.4: + resolution: {integrity: sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==} + engines: {node: '>=16.0.0'} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-with-bigint@3.5.10: + resolution: {integrity: sha512-Vcx+JVNEBts/xfcoCS69sKrOhOk/3TVlvlT+XzUOefVKnnrbYSCKpDCm10pohsJFtsJVYnwa/cXRZ4eElzaM6w==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} + hasBin: true + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lowlight@1.20.0: + resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + luxon@3.7.1: + resolution: {integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==} + engines: {node: '>=12'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdast-util-directive@3.1.0: + resolution: {integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-math@3.0.0: + resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-newline-to-break@2.0.0: + resolution: {integrity: sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-directive@3.0.2: + resolution: {integrity: sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-math@3.1.0: + resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mutation-server-protocol@0.4.1: + resolution: {integrity: sha512-SBGK0j8hLDne7bktgThKI8kGvGTx3rY3LAeQTmOKZ5bVnL/7TorLMvcVF7dIPJCu5RNUWhkkuF53kurygYVt3g==} + engines: {node: '>=18'} + + mutation-testing-elements@3.7.3: + resolution: {integrity: sha512-SMeIPxngJpfjfNYctFpYQQtlBlZaVO0aoB3FKdwrI8Ee/2bkyUuCZzAOCLv1U9fnmfA37dPFq0Owduoxs2XgGQ==} + + mutation-testing-metrics@3.7.3: + resolution: {integrity: sha512-B8QrP0ZomErzTPNlhrzKWPNBln+3afwBZPHv0Q7N8wZZTYxMptzb/Gdm3ExXVmioVYrtZAtsDs7W/T/b2AixOQ==} + + mutation-testing-report-schema@3.7.3: + resolution: {integrity: sha512-BHm3MYq+ckO+t5CtlG8zpqxc75rdJCkxVlE+fGuGJM3F7tNCQ/OW2N+TQVHN3BHsYa84+BFc6g3AwDYkUsw2MA==} + + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + engines: {node: '>=18'} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + papaparse@5.5.3: + resolution: {integrity: sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + patch-console@2.0.0: + resolution: {integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + pdfjs-dist@6.2.108: + resolution: {integrity: sha512-YxFb+SQcodN2rnX9Tn3dHYlqfb7NjlzzfONPpJd+AKoKtUjEdevTfbC07d5TcczzOK6261auRkP/M8OBHs9vFQ==} + engines: {node: '>=22.13.0 || >=24'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} + hasBin: true + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + pure-rand@8.4.2: + resolution: {integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==} + + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + + radix-ui@1.6.7: + resolution: {integrity: sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-markdown@9.1.0: + resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + + react-merge-refs@2.1.1: + resolution: {integrity: sha512-jLQXJ/URln51zskhgppGJ2ub7b2WFKGq3cl3NYKtlHoTG+dN2q7EzWrn3hN3EgPsTMvpR9tpq5ijdp7YwFZkag==} + + react-reconciler@0.33.0: + resolution: {integrity: sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^19.2.0 + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-syntax-highlighter@16.1.1: + resolution: {integrity: sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==} + engines: {node: '>= 16.20.2'} + peerDependencies: + react: '>= 0.14.0' + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + refractor@5.0.0: + resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} + + rehype-katex@7.0.1: + resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} + + remark-breaks@4.0.0: + resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==} + + remark-directive@3.0.1: + resolution: {integrity: sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-math@6.0.0: + resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + + smol-toml@1.6.1: + resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} + engines: {node: '>= 18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.2: + resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} + engines: {node: '>=20'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + tailwindcss@4.3.3: + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + terminal-size@4.0.1: + resolution: {integrity: sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tokenx@1.3.0: + resolution: {integrity: sha512-NLdXTEZkKiO0gZuLtMoZKjCXTREXeZZt8nnnNeyoXtNZAfG/GKGSbQtLU5STspc0rMSwcA+UJfWZkbNU01iKmQ==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel@0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + + type-fest@5.8.0: + resolution: {integrity: sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==} + engines: {node: '>=20'} + + typed-inject@5.0.0: + resolution: {integrity: sha512-0Ql2ORqBORLMdAW89TQKZsb1PQkFGImFfVmncXWe7a+AA3+7dh7Se9exxZowH4kbnlvKEFkMxUYdHUpjYWFJaA==} + engines: {node: '>=18'} + + typed-rest-client@2.3.1: + resolution: {integrity: sha512-k4kX5Up6qA68D0Cby2AK+6+vM5k3qTxe+/3FqhnHRExjY5cfbOnzjQZbP/LXleF8hVoDvDqxlgk9KK83HoBZlQ==} + engines: {node: '>= 16.0.0'} + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + underscore@1.13.8: + resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + usehooks-ts@3.1.1: + resolution: {integrity: sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==} + engines: {node: '>=16.15.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + weapon-regex@1.3.6: + resolution: {integrity: sha512-wsf1m1jmMrso5nhwVFJJHSubEBf3+pereGd7+nBKtYJ18KoB/PWJOHS3WRkwS04VrOU0iJr2bZU+l1QaTJ+9nA==} + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + widest-line@6.0.0: + resolution: {integrity: sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==} + engines: {node: '>=20'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yoctocolors@2.2.0: + resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} + engines: {node: '>=18'} + + yoga-layout@3.2.1: + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@alcalzone/ansi-tokenize@0.2.5': + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + '@alloc/quick-lru@5.2.0': {} + + '@apidevtools/json-schema-ref-parser@11.9.3': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.3.1 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.8 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.8 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/plugin-proposal-decorators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-decorators': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-syntax-decorators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-explicit-resource-management@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.28.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@cfworker/json-schema@4.1.1': {} + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} - engines: {node: '>=12'} + '@esbuild/android-x64@0.28.2': + optional: true - prettier@3.2.5: - resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} - engines: {node: '>=14'} - hasBin: true + '@esbuild/darwin-arm64@0.28.2': + optional: true - pretty-ms@9.3.0: - resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} - engines: {node: '>=18'} + '@esbuild/darwin-x64@0.28.2': + optional: true - progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} + '@esbuild/freebsd-arm64@0.28.2': + optional: true - pump@3.0.4: - resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + '@esbuild/freebsd-x64@0.28.2': + optional: true - pure-rand@8.4.2: - resolution: {integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==} + '@esbuild/linux-arm64@0.28.2': + optional: true - qs@6.15.1: - resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} - engines: {node: '>=0.6'} + '@esbuild/linux-arm@0.28.2': + optional: true - react-reconciler@0.33.0: - resolution: {integrity: sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==} - engines: {node: '>=0.10.0'} - peerDependencies: - react: ^19.2.0 + '@esbuild/linux-ia32@0.28.2': + optional: true - react@19.2.4: - resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} - engines: {node: '>=0.10.0'} + '@esbuild/linux-loong64@0.28.2': + optional: true - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} + '@esbuild/linux-mips64el@0.28.2': + optional: true - restore-cursor@4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + '@esbuild/linux-ppc64@0.28.2': + optional: true - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + '@esbuild/linux-riscv64@0.28.2': + optional: true - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + '@esbuild/linux-s390x@0.28.2': + optional: true - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + '@esbuild/linux-x64@0.28.2': + optional: true - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true + '@esbuild/netbsd-arm64@0.28.2': + optional: true - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true + '@esbuild/netbsd-x64@0.28.2': + optional: true - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} - engines: {node: '>=10'} - hasBin: true + '@esbuild/openbsd-arm64@0.28.2': + optional: true - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + '@esbuild/openbsd-x64@0.28.2': + optional: true - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + '@esbuild/openharmony-arm64@0.28.2': + optional: true - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} + '@esbuild/sunos-x64@0.28.2': + optional: true - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} + '@esbuild/win32-arm64@0.28.2': + optional: true - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} + '@esbuild/win32-ia32@0.28.2': + optional: true - side-channel@1.1.1: - resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} - engines: {node: '>= 0.4'} + '@esbuild/win32-x64@0.28.2': + optional: true - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 - slice-ansi@8.0.0: - resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} - engines: {node: '>=20'} + '@floating-ui/react-dom@2.1.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@floating-ui/dom': 1.8.0 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) - smol-toml@1.6.1: - resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} - engines: {node: '>= 18'} + '@floating-ui/utils@0.2.12': {} - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} + '@graphql-typed-document-node/core@3.2.0(graphql@17.0.2)': + dependencies: + graphql: 17.0.2 - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} + '@inquirer/ansi@2.0.7': {} - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} + '@inquirer/checkbox@5.2.1(@types/node@22.19.17)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/core': 11.2.1(@types/node@22.19.17) + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 - string-width@8.2.2: - resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} - engines: {node: '>=20'} + '@inquirer/confirm@6.1.1(@types/node@22.19.17)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@22.19.17) + '@inquirer/type': 4.0.7(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} + '@inquirer/core@11.2.1(@types/node@22.19.17)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@22.19.17) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 22.19.17 - strip-final-newline@4.0.0: - resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} - engines: {node: '>=18'} + '@inquirer/editor@5.2.2(@types/node@22.19.17)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@22.19.17) + '@inquirer/external-editor': 3.0.3(@types/node@22.19.17) + '@inquirer/type': 4.0.7(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 - tagged-tag@1.0.0: - resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} - engines: {node: '>=20'} + '@inquirer/expand@5.1.1(@types/node@22.19.17)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@22.19.17) + '@inquirer/type': 4.0.7(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 - terminal-size@4.0.1: - resolution: {integrity: sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==} - engines: {node: '>=18'} + '@inquirer/external-editor@3.0.3(@types/node@22.19.17)': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.3 + optionalDependencies: + '@types/node': 22.19.17 - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} + '@inquirer/figures@2.0.7': {} - tokenx@1.3.0: - resolution: {integrity: sha512-NLdXTEZkKiO0gZuLtMoZKjCXTREXeZZt8nnnNeyoXtNZAfG/GKGSbQtLU5STspc0rMSwcA+UJfWZkbNU01iKmQ==} + '@inquirer/input@5.1.2(@types/node@22.19.17)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@22.19.17) + '@inquirer/type': 4.0.7(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true + '@inquirer/number@4.1.1(@types/node@22.19.17)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@22.19.17) + '@inquirer/type': 4.0.7(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + '@inquirer/password@5.1.1(@types/node@22.19.17)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/core': 11.2.1(@types/node@22.19.17) + '@inquirer/type': 4.0.7(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 - tunnel@0.0.6: - resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} - engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + '@inquirer/prompts@8.3.0(@types/node@22.19.17)': + dependencies: + '@inquirer/checkbox': 5.2.1(@types/node@22.19.17) + '@inquirer/confirm': 6.1.1(@types/node@22.19.17) + '@inquirer/editor': 5.2.2(@types/node@22.19.17) + '@inquirer/expand': 5.1.1(@types/node@22.19.17) + '@inquirer/input': 5.1.2(@types/node@22.19.17) + '@inquirer/number': 4.1.1(@types/node@22.19.17) + '@inquirer/password': 5.1.1(@types/node@22.19.17) + '@inquirer/rawlist': 5.3.1(@types/node@22.19.17) + '@inquirer/search': 4.2.1(@types/node@22.19.17) + '@inquirer/select': 5.2.1(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 - type-fest@5.8.0: - resolution: {integrity: sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==} - engines: {node: '>=20'} + '@inquirer/rawlist@5.3.1(@types/node@22.19.17)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@22.19.17) + '@inquirer/type': 4.0.7(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 - typed-inject@5.0.0: - resolution: {integrity: sha512-0Ql2ORqBORLMdAW89TQKZsb1PQkFGImFfVmncXWe7a+AA3+7dh7Se9exxZowH4kbnlvKEFkMxUYdHUpjYWFJaA==} - engines: {node: '>=18'} + '@inquirer/search@4.2.1(@types/node@22.19.17)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@22.19.17) + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 - typed-rest-client@2.3.1: - resolution: {integrity: sha512-k4kX5Up6qA68D0Cby2AK+6+vM5k3qTxe+/3FqhnHRExjY5cfbOnzjQZbP/LXleF8hVoDvDqxlgk9KK83HoBZlQ==} - engines: {node: '>= 16.0.0'} + '@inquirer/select@5.2.1(@types/node@22.19.17)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/core': 11.2.1(@types/node@22.19.17) + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 - typescript@5.7.3: - resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} - engines: {node: '>=14.17'} - hasBin: true + '@inquirer/type@4.0.7(@types/node@22.19.17)': + optionalDependencies: + '@types/node': 22.19.17 - underscore@1.13.8: - resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 - unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} - engines: {node: '>=18'} + '@jridgewell/resolve-uri@3.1.2': {} - universal-user-agent@7.0.3: - resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + '@jridgewell/sourcemap-codec@1.5.5': {} - update-browserslist-db@1.3.1: - resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 - weapon-regex@1.3.6: - resolution: {integrity: sha512-wsf1m1jmMrso5nhwVFJJHSubEBf3+pereGd7+nBKtYJ18KoB/PWJOHS3WRkwS04VrOU0iJr2bZU+l1QaTJ+9nA==} + '@jsdevtools/ono@7.1.3': {} - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + '@linear/sdk@89.0.0(graphql@17.0.2)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@17.0.2) + transitivePeerDependencies: + - graphql - widest-line@6.0.0: - resolution: {integrity: sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==} - engines: {node: '>=20'} + '@modelcontextprotocol/core@2.0.0-beta.4': + dependencies: + zod: 4.4.3 - wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} + '@modelcontextprotocol/server@2.0.0-beta.4': + dependencies: + '@modelcontextprotocol/core': 2.0.0-beta.4 + zod: 4.4.3 - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + '@napi-rs/canvas-android-arm64@1.0.3': + optional: true - ws@8.21.3: - resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true + '@napi-rs/canvas-darwin-arm64@1.0.3': + optional: true - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + '@napi-rs/canvas-darwin-x64@1.0.3': + optional: true - yaml@2.9.0: - resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} - engines: {node: '>= 14.6'} - hasBin: true + '@napi-rs/canvas-linux-arm-gnueabihf@1.0.3': + optional: true - yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + '@napi-rs/canvas-linux-arm64-gnu@1.0.3': + optional: true - yoctocolors@2.2.0: - resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} - engines: {node: '>=18'} + '@napi-rs/canvas-linux-arm64-musl@1.0.3': + optional: true - yoga-layout@3.2.1: - resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + '@napi-rs/canvas-linux-riscv64-gnu@1.0.3': + optional: true - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + '@napi-rs/canvas-linux-x64-gnu@1.0.3': + optional: true -snapshots: + '@napi-rs/canvas-linux-x64-musl@1.0.3': + optional: true - '@alcalzone/ansi-tokenize@0.2.5': - dependencies: - ansi-styles: 6.2.3 - is-fullwidth-code-point: 5.1.0 + '@napi-rs/canvas-win32-arm64-msvc@1.0.3': + optional: true - '@apidevtools/json-schema-ref-parser@11.9.3': - dependencies: - '@jsdevtools/ono': 7.1.3 - '@types/json-schema': 7.0.15 - js-yaml: 4.3.1 + '@napi-rs/canvas-win32-x64-msvc@1.0.3': + optional: true - '@babel/code-frame@7.29.7': - dependencies: - '@babel/helper-validator-identifier': 7.29.7 - js-tokens: 4.0.0 - picocolors: 1.1.1 + '@napi-rs/canvas@1.0.3': + optionalDependencies: + '@napi-rs/canvas-android-arm64': 1.0.3 + '@napi-rs/canvas-darwin-arm64': 1.0.3 + '@napi-rs/canvas-darwin-x64': 1.0.3 + '@napi-rs/canvas-linux-arm-gnueabihf': 1.0.3 + '@napi-rs/canvas-linux-arm64-gnu': 1.0.3 + '@napi-rs/canvas-linux-arm64-musl': 1.0.3 + '@napi-rs/canvas-linux-riscv64-gnu': 1.0.3 + '@napi-rs/canvas-linux-x64-gnu': 1.0.3 + '@napi-rs/canvas-linux-x64-musl': 1.0.3 + '@napi-rs/canvas-win32-arm64-msvc': 1.0.3 + '@napi-rs/canvas-win32-x64-msvc': 1.0.3 + optional: true - '@babel/compat-data@7.29.7': {} + '@octokit/auth-token@6.0.0': {} - '@babel/core@7.29.7': + '@octokit/core@7.0.6': dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.8 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.8 - '@babel/types': 7.29.8 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color + '@octokit/auth-token': 6.0.0 + '@octokit/graphql': 9.0.3 + '@octokit/request': 10.0.11 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 - '@babel/generator@7.29.8': + '@octokit/endpoint@11.0.3': dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 - '@babel/helper-annotate-as-pure@7.29.7': + '@octokit/graphql@9.0.3': dependencies: - '@babel/types': 7.29.8 + '@octokit/request': 10.0.11 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 - '@babel/helper-compilation-targets@7.29.7': - dependencies: - '@babel/compat-data': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.8 - lru-cache: 5.1.1 - semver: 6.3.1 + '@octokit/openapi-types@27.0.0': {} - '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + '@octokit/request-error@7.1.0': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 - '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/traverse': 7.29.8 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/helper-globals@7.29.7': {} + '@octokit/types': 16.0.0 - '@babel/helper-member-expression-to-functions@7.29.7': + '@octokit/request@10.0.11': dependencies: - '@babel/traverse': 7.29.8 - '@babel/types': 7.29.8 - transitivePeerDependencies: - - supports-color + '@octokit/endpoint': 11.0.3 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + content-type: 2.0.0 + json-with-bigint: 3.5.10 + universal-user-agent: 7.0.3 - '@babel/helper-module-imports@7.29.7': + '@octokit/types@16.0.0': dependencies: - '@babel/traverse': 7.29.8 - '@babel/types': 7.29.8 - transitivePeerDependencies: - - supports-color + '@octokit/openapi-types': 27.0.0 - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + '@openai/apps-sdk-ui@0.2.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.3.3)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.8 + clsx: 2.1.1 + lodash: 4.17.21 + luxon: 3.7.1 + radix-ui: 1.6.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-markdown: 9.1.0(@types/react@19.2.14)(react@19.2.4) + react-merge-refs: 2.1.1 + react-syntax-highlighter: 16.1.1(react@19.2.4) + rehype-katex: 7.0.1 + remark-breaks: 4.0.0 + remark-directive: 3.0.1 + remark-gfm: 4.0.1 + remark-math: 6.0.0 + tailwindcss: 4.3.3 + unist-util-visit: 5.1.0 + usehooks-ts: 3.1.1(react@19.2.4) transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - react-dom - supports-color - '@babel/helper-optimise-call-expression@7.29.7': - dependencies: - '@babel/types': 7.29.8 - - '@babel/helper-plugin-utils@7.29.7': {} - - '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + '@openai/codex-sdk@0.149.1': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 - '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.8 - transitivePeerDependencies: - - supports-color + '@openai/codex': 0.149.1 - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': - dependencies: - '@babel/traverse': 7.29.8 - '@babel/types': 7.29.8 - transitivePeerDependencies: - - supports-color + '@openai/codex@0.149.1': + optionalDependencies: + '@openai/codex-darwin-arm64': '@openai/codex@0.149.1-darwin-arm64' + '@openai/codex-darwin-x64': '@openai/codex@0.149.1-darwin-x64' + '@openai/codex-linux-arm64': '@openai/codex@0.149.1-linux-arm64' + '@openai/codex-linux-x64': '@openai/codex@0.149.1-linux-x64' + '@openai/codex-win32-arm64': '@openai/codex@0.149.1-win32-arm64' + '@openai/codex-win32-x64': '@openai/codex@0.149.1-win32-x64' - '@babel/helper-string-parser@7.29.7': {} + '@openai/codex@0.149.1-darwin-arm64': + optional: true - '@babel/helper-validator-identifier@7.29.7': {} + '@openai/codex@0.149.1-darwin-x64': + optional: true - '@babel/helper-validator-option@7.29.7': {} + '@openai/codex@0.149.1-linux-arm64': + optional: true - '@babel/helpers@7.29.7': - dependencies: - '@babel/template': 7.29.7 - '@babel/types': 7.29.8 + '@openai/codex@0.149.1-linux-x64': + optional: true - '@babel/parser@7.29.8': - dependencies: - '@babel/types': 7.29.8 + '@openai/codex@0.149.1-win32-arm64': + optional: true - '@babel/plugin-proposal-decorators@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-decorators': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color + '@openai/codex@0.149.1-win32-x64': + optional: true - '@babel/plugin-syntax-decorators@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@radix-ui/number@1.1.3': {} - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + '@radix-ui/primitive@1.1.7': {} + + '@radix-ui/react-accessible-icon@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-accordion@1.2.20(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collapsible': 1.1.20(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + '@radix-ui/react-alert-dialog@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.7)': + '@radix-ui/react-arrow@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.8 - transitivePeerDependencies: - - supports-color + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@babel/plugin-transform-explicit-resource-management@7.29.7(@babel/core@7.29.7)': + '@radix-ui/react-aspect-ratio@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + '@radix-ui/react-avatar@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + '@radix-ui/react-checkbox@1.3.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-collapsible@1.1.20(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@babel/preset-typescript@7.28.5(@babel/core@7.29.7)': + '@radix-ui/react-collection@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@babel/template@7.29.7': + '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 - '@babel/traverse@7.29.8': + '@radix-ui/react-context-menu@2.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.8 - '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/template': 7.29.7 - '@babel/types': 7.29.8 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@babel/types@7.29.8': + '@radix-ui/react-context@1.2.2(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 - '@cfworker/json-schema@4.1.1': {} + '@radix-ui/react-dialog@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + aria-hidden: 1.2.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@graphql-typed-document-node/core@3.2.0(graphql@17.0.2)': + '@radix-ui/react-direction@1.1.4(@types/react@19.2.14)(react@19.2.4)': dependencies: - graphql: 17.0.2 - - '@inquirer/ansi@2.0.7': {} + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 - '@inquirer/checkbox@5.2.1(@types/node@22.19.17)': + '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.19.17) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@22.19.17) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@inquirer/confirm@6.1.1(@types/node@22.19.17)': + '@radix-ui/react-dropdown-menu@2.1.24(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.19.17) - '@inquirer/type': 4.0.7(@types/node@22.19.17) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@inquirer/core@11.2.1(@types/node@22.19.17)': + '@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@22.19.17) - cli-width: 4.1.0 - fast-wrap-ansi: 0.2.2 - mute-stream: 3.0.0 - signal-exit: 4.1.0 + react: 19.2.4 optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 - '@inquirer/editor@5.2.2(@types/node@22.19.17)': + '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.19.17) - '@inquirer/external-editor': 3.0.3(@types/node@22.19.17) - '@inquirer/type': 4.0.7(@types/node@22.19.17) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@inquirer/expand@5.1.1(@types/node@22.19.17)': + '@radix-ui/react-form@0.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.19.17) - '@inquirer/type': 4.0.7(@types/node@22.19.17) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-hover-card@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@inquirer/external-editor@3.0.3(@types/node@22.19.17)': + '@radix-ui/react-id@1.1.4(@types/react@19.2.14)(react@19.2.4)': dependencies: - chardet: 2.2.0 - iconv-lite: 0.7.3 + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 - '@inquirer/figures@2.0.7': {} + '@radix-ui/react-label@2.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-menu@2.1.24(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + aria-hidden: 1.2.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-menubar@1.1.24(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-navigation-menu@1.2.22(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-one-time-password-field@0.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-password-toggle-field@0.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-popover@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + aria-hidden: 1.2.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-popper@1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@floating-ui/react-dom': 2.1.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-rect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/rect': 1.1.3 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@inquirer/input@5.1.2(@types/node@22.19.17)': + '@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.19.17) - '@inquirer/type': 4.0.7(@types/node@22.19.17) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@inquirer/number@4.1.1(@types/node@22.19.17)': + '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.19.17) - '@inquirer/type': 4.0.7(@types/node@22.19.17) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@inquirer/password@5.1.1(@types/node@22.19.17)': + '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.19.17) - '@inquirer/type': 4.0.7(@types/node@22.19.17) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@inquirer/prompts@8.3.0(@types/node@22.19.17)': + '@radix-ui/react-progress@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@inquirer/checkbox': 5.2.1(@types/node@22.19.17) - '@inquirer/confirm': 6.1.1(@types/node@22.19.17) - '@inquirer/editor': 5.2.2(@types/node@22.19.17) - '@inquirer/expand': 5.1.1(@types/node@22.19.17) - '@inquirer/input': 5.1.2(@types/node@22.19.17) - '@inquirer/number': 4.1.1(@types/node@22.19.17) - '@inquirer/password': 5.1.1(@types/node@22.19.17) - '@inquirer/rawlist': 5.3.1(@types/node@22.19.17) - '@inquirer/search': 4.2.1(@types/node@22.19.17) - '@inquirer/select': 5.2.1(@types/node@22.19.17) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-radio-group@1.4.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-roving-focus@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-scroll-area@1.2.18(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-select@2.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + aria-hidden: 1.2.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@inquirer/rawlist@5.3.1(@types/node@22.19.17)': + '@radix-ui/react-separator@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.19.17) - '@inquirer/type': 4.0.7(@types/node@22.19.17) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-slider@1.4.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@inquirer/search@4.2.1(@types/node@22.19.17)': + '@radix-ui/react-slot@1.3.3(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.19.17) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@22.19.17) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 - '@inquirer/select@5.2.1(@types/node@22.19.17)': + '@radix-ui/react-switch@1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.19.17) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@22.19.17) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/node': 22.19.17 - - '@inquirer/type@4.0.7(@types/node@22.19.17)': + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-tabs@1.1.21(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/node': 22.19.17 + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-toast@1.2.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@jridgewell/gen-mapping@0.3.13': + '@radix-ui/react-toggle-group@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-toggle': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@jridgewell/remapping@2.3.5': + '@radix-ui/react-toggle@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@jridgewell/trace-mapping@0.3.31': + '@radix-ui/react-toolbar@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@jsdevtools/ono@7.1.3': {} + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-separator': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-tooltip@1.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@linear/sdk@89.0.0(graphql@17.0.2)': + '@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@graphql-typed-document-node/core': 3.2.0(graphql@17.0.2) - transitivePeerDependencies: - - graphql + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 - '@modelcontextprotocol/core@2.0.0-beta.4': + '@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.14)(react@19.2.4)': dependencies: - zod: 4.4.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 - '@modelcontextprotocol/server@2.0.0-beta.4': + '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@modelcontextprotocol/core': 2.0.0-beta.4 - zod: 4.4.3 - - '@napi-rs/canvas-android-arm64@1.0.3': - optional: true - - '@napi-rs/canvas-darwin-arm64@1.0.3': - optional: true - - '@napi-rs/canvas-darwin-x64@1.0.3': - optional: true - - '@napi-rs/canvas-linux-arm-gnueabihf@1.0.3': - optional: true - - '@napi-rs/canvas-linux-arm64-gnu@1.0.3': - optional: true - - '@napi-rs/canvas-linux-arm64-musl@1.0.3': - optional: true - - '@napi-rs/canvas-linux-riscv64-gnu@1.0.3': - optional: true - - '@napi-rs/canvas-linux-x64-gnu@1.0.3': - optional: true - - '@napi-rs/canvas-linux-x64-musl@1.0.3': - optional: true - - '@napi-rs/canvas-win32-arm64-msvc@1.0.3': - optional: true - - '@napi-rs/canvas-win32-x64-msvc@1.0.3': - optional: true - - '@napi-rs/canvas@1.0.3': + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 optionalDependencies: - '@napi-rs/canvas-android-arm64': 1.0.3 - '@napi-rs/canvas-darwin-arm64': 1.0.3 - '@napi-rs/canvas-darwin-x64': 1.0.3 - '@napi-rs/canvas-linux-arm-gnueabihf': 1.0.3 - '@napi-rs/canvas-linux-arm64-gnu': 1.0.3 - '@napi-rs/canvas-linux-arm64-musl': 1.0.3 - '@napi-rs/canvas-linux-riscv64-gnu': 1.0.3 - '@napi-rs/canvas-linux-x64-gnu': 1.0.3 - '@napi-rs/canvas-linux-x64-musl': 1.0.3 - '@napi-rs/canvas-win32-arm64-msvc': 1.0.3 - '@napi-rs/canvas-win32-x64-msvc': 1.0.3 - optional: true - - '@octokit/auth-token@6.0.0': {} + '@types/react': 19.2.14 - '@octokit/core@7.0.6': + '@radix-ui/react-use-escape-keydown@1.1.5(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@octokit/auth-token': 6.0.0 - '@octokit/graphql': 9.0.3 - '@octokit/request': 10.0.11 - '@octokit/request-error': 7.1.0 - '@octokit/types': 16.0.0 - before-after-hook: 4.0.0 - universal-user-agent: 7.0.3 + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 - '@octokit/endpoint@11.0.3': + '@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@octokit/types': 16.0.0 - universal-user-agent: 7.0.3 + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 - '@octokit/graphql@9.0.3': + '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@octokit/request': 10.0.11 - '@octokit/types': 16.0.0 - universal-user-agent: 7.0.3 - - '@octokit/openapi-types@27.0.0': {} + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 - '@octokit/request-error@7.1.0': + '@radix-ui/react-use-previous@1.1.4(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@octokit/types': 16.0.0 + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 - '@octokit/request@10.0.11': + '@radix-ui/react-use-rect@1.1.4(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@octokit/endpoint': 11.0.3 - '@octokit/request-error': 7.1.0 - '@octokit/types': 16.0.0 - content-type: 2.0.0 - json-with-bigint: 3.5.10 - universal-user-agent: 7.0.3 + '@radix-ui/rect': 1.1.3 + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 - '@octokit/types@16.0.0': + '@radix-ui/react-use-size@1.1.4(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@octokit/openapi-types': 27.0.0 + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 - '@openai/codex-sdk@0.149.1': + '@radix-ui/react-visually-hidden@1.2.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@openai/codex': 0.149.1 - - '@openai/codex@0.149.1': + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@openai/codex-darwin-arm64': '@openai/codex@0.149.1-darwin-arm64' - '@openai/codex-darwin-x64': '@openai/codex@0.149.1-darwin-x64' - '@openai/codex-linux-arm64': '@openai/codex@0.149.1-linux-arm64' - '@openai/codex-linux-x64': '@openai/codex@0.149.1-linux-x64' - '@openai/codex-win32-arm64': '@openai/codex@0.149.1-win32-arm64' - '@openai/codex-win32-x64': '@openai/codex@0.149.1-win32-x64' - - '@openai/codex@0.149.1-darwin-arm64': - optional: true - - '@openai/codex@0.149.1-darwin-x64': - optional: true - - '@openai/codex@0.149.1-linux-arm64': - optional: true - - '@openai/codex@0.149.1-linux-x64': - optional: true - - '@openai/codex@0.149.1-win32-arm64': - optional: true + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@openai/codex@0.149.1-win32-x64': - optional: true + '@radix-ui/rect@1.1.3': {} '@scalar/openapi-types@0.8.0': {} @@ -1924,16 +4419,107 @@ snapshots: '@stryker-mutator/util@9.6.1': {} + '@tailwindcss/node@4.3.3': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.24.5 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.3 + + '@tailwindcss/oxide-android-arm64@4.3.3': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.3': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.3': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + optional: true + + '@tailwindcss/oxide@4.3.3': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-x64': 4.3.3 + '@tailwindcss/oxide-freebsd-x64': 4.3.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 + + '@tailwindcss/postcss@4.3.3': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + postcss: 8.5.26 + tailwindcss: 4.3.3 + '@toon-format/toon@2.3.0': {} '@types/bun@1.3.13': dependencies: bun-types: 1.3.13 + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.9 + + '@types/estree@1.0.9': {} + + '@types/hast@3.0.5': + dependencies: + '@types/unist': 3.0.3 + '@types/json-schema@7.0.15': {} + '@types/katex@0.16.8': {} + '@types/lodash@4.17.24': {} + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + '@types/node@22.19.17': dependencies: undici-types: 6.21.0 @@ -1942,17 +4528,29 @@ snapshots: dependencies: '@types/node': 22.19.17 + '@types/prismjs@1.26.6': {} + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + '@types/react@19.2.14': dependencies: csstype: 3.2.3 '@types/semver@7.8.0': {} + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + '@types/yauzl@2.10.3': dependencies: '@types/node': 22.19.17 optional: true + '@ungap/structured-clone@1.3.3': {} + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 @@ -1979,8 +4577,14 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + auto-bind@5.0.1: {} + bail@2.0.2: {} + balanced-match@4.0.4: {} base64-js@1.5.1: {} @@ -2019,8 +4623,18 @@ snapshots: caniuse-lite@1.0.30001809: {} + ccount@2.0.1: {} + chalk@5.6.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + chardet@2.2.0: {} cli-boxes@3.0.0: {} @@ -2036,12 +4650,18 @@ snapshots: cli-width@4.1.0: {} + clsx@2.1.1: {} + code-excerpt@4.0.0: dependencies: convert-to-spaces: 2.0.1 + comma-separated-tokens@2.0.3: {} + commander@14.0.3: {} + commander@8.3.0: {} + content-type@2.0.0: {} convert-source-map@2.0.0: {} @@ -2060,11 +4680,25 @@ snapshots: dependencies: ms: 2.1.3 + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + dequal@2.0.3: {} + des.js@1.1.0: dependencies: inherits: 2.0.4 minimalistic-assert: 1.0.1 + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + diff-match-patch@1.0.5: {} dunder-proto@1.0.1: @@ -2081,6 +4715,13 @@ snapshots: dependencies: once: 1.4.0 + enhanced-resolve@5.24.5: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + entities@6.0.1: {} + environment@1.1.0: {} es-define-property@1.0.1: {} @@ -2093,10 +4734,43 @@ snapshots: es-toolkit@1.50.0: {} + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + escalade@3.2.0: {} escape-string-regexp@2.0.0: {} + escape-string-regexp@5.0.0: {} + + estree-util-is-identifier-name@3.0.0: {} + execa@9.6.1: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -2112,6 +4786,8 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.2.0 + extend@3.0.2: {} + extract-zip@2.0.1: dependencies: debug: 4.4.3 @@ -2140,6 +4816,10 @@ snapshots: dependencies: fast-string-width: 3.0.2 + fault@1.0.4: + dependencies: + format: 0.2.2 + fd-slicer@1.1.0: dependencies: pend: 1.2.0 @@ -2154,6 +4834,8 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 + format@0.2.2: {} + function-bind@1.1.2: {} gensync@1.0.0-beta.2: {} @@ -2173,151 +4855,723 @@ snapshots: hasown: 2.0.4 math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 es-object-atoms: 1.1.2 - get-stream@5.2.0: + get-stream@5.2.0: + dependencies: + pump: 3.0.4 + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphql@17.0.2: {} + + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hast-util-from-dom@5.0.1: + dependencies: + '@types/hast': 3.0.5 + hastscript: 9.0.1 + web-namespaces: 2.0.1 + + hast-util-from-html-isomorphic@2.0.0: + dependencies: + '@types/hast': 3.0.5 + hast-util-from-dom: 5.0.1 + hast-util-from-html: 2.0.3 + unist-util-remove-position: 5.0.0 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.5 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.2.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.5 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + + highlight.js@10.7.3: {} + + highlightjs-vue@1.0.0: {} + + html-url-attributes@3.0.1: {} + + human-signals@8.0.1: {} + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + incur@0.4.13: + dependencies: + '@cfworker/json-schema': 4.1.1 + '@modelcontextprotocol/server': 2.0.0-beta.4 + '@scalar/openapi-types': 0.8.0 + '@toon-format/toon': 2.3.0 + tokenx: 1.3.0 + yaml: 2.9.0 + zod: 4.4.3 + + indent-string@5.0.0: {} + + inherits@2.0.4: {} + + ink-testing-library@4.0.0(@types/react@19.2.14): + optionalDependencies: + '@types/react': 19.2.14 + + ink@6.8.0(@types/react@19.2.14)(react@19.2.4): + dependencies: + '@alcalzone/ansi-tokenize': 0.2.5 + ansi-escapes: 7.3.0 + ansi-styles: 6.2.3 + auto-bind: 5.0.1 + chalk: 5.6.2 + cli-boxes: 3.0.0 + cli-cursor: 4.0.0 + cli-truncate: 5.2.0 + code-excerpt: 4.0.0 + es-toolkit: 1.50.0 + indent-string: 5.0.0 + is-in-ci: 2.0.0 + patch-console: 2.0.0 + react: 19.2.4 + react-reconciler: 0.33.0(react@19.2.4) + scheduler: 0.27.0 + signal-exit: 3.0.7 + slice-ansi: 8.0.0 + stack-utils: 2.0.6 + string-width: 8.2.2 + terminal-size: 4.0.1 + type-fest: 5.8.0 + widest-line: 6.0.0 + wrap-ansi: 9.0.2 + ws: 8.21.3 + yoga-layout: 3.2.1 + optionalDependencies: + '@types/react': 19.2.14 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + inline-style-parser@0.2.7: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.6.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hexadecimal@2.0.1: {} + + is-in-ci@2.0.0: {} + + is-plain-obj@4.1.0: {} + + is-stream@4.0.1: {} + + is-unicode-supported@2.1.0: {} + + isexe@2.0.0: {} + + jiti@2.7.0: {} + + js-md4@0.3.2: {} + + js-tiktoken@1.0.21: + dependencies: + base64-js: 1.5.1 + + js-tokens@4.0.0: {} + + js-yaml@4.3.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-rpc-2.0@1.7.1: {} + + json-schema-to-typescript@15.0.4: + dependencies: + '@apidevtools/json-schema-ref-parser': 11.9.3 + '@types/json-schema': 7.0.15 + '@types/lodash': 4.17.24 + is-glob: 4.0.3 + js-yaml: 4.3.1 + lodash: 4.18.1 + minimist: 1.2.8 + prettier: 3.2.5 + tinyglobby: 0.2.17 + + json-schema-traverse@1.0.0: {} + + json-with-bigint@3.5.10: {} + + json5@2.2.3: {} + + katex@0.16.47: + dependencies: + commander: 8.3.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lodash.debounce@4.0.8: {} + + lodash.groupby@4.6.0: {} + + lodash@4.17.21: {} + + lodash@4.18.1: {} + + longest-streak@3.1.0: {} + + lowlight@1.20.0: + dependencies: + fault: 1.0.4 + highlight.js: 10.7.3 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + luxon@3.7.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + markdown-table@3.0.4: {} + + math-intrinsics@1.1.0: {} + + mdast-util-directive@3.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-visit-parents: 6.0.2 + transitivePeerDependencies: + - supports-color + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: dependencies: - pump: 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color - get-stream@9.0.1: + mdast-util-gfm@3.1.0: dependencies: - '@sec-ant/readable-stream': 0.4.1 - is-stream: 4.0.1 + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color - gopd@1.2.0: {} + mdast-util-math@3.0.0: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + longest-streak: 3.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + unist-util-remove-position: 5.0.0 + transitivePeerDependencies: + - supports-color - graphql@17.0.2: {} + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color - has-symbols@1.1.0: {} + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color - hasown@2.0.4: + mdast-util-mdxjs-esm@2.0.1: dependencies: - function-bind: 1.1.2 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color - human-signals@8.0.1: {} + mdast-util-newline-to-break@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-find-and-replace: 3.0.2 + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.3 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-directive@3.0.2: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + parse-entities: 4.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - iconv-lite@0.7.3: + micromark-extension-gfm-footnote@2.1.0: dependencies: - safer-buffer: 2.1.2 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - incur@0.4.13: + micromark-extension-gfm-strikethrough@2.1.0: dependencies: - '@cfworker/json-schema': 4.1.1 - '@modelcontextprotocol/server': 2.0.0-beta.4 - '@scalar/openapi-types': 0.8.0 - '@toon-format/toon': 2.3.0 - tokenx: 1.3.0 - yaml: 2.9.0 - zod: 4.4.3 + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - indent-string@5.0.0: {} + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - inherits@2.0.4: {} + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 - ink-testing-library@4.0.0(@types/react@19.2.14): - optionalDependencies: - '@types/react': 19.2.14 + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - ink@6.8.0(@types/react@19.2.14)(react@19.2.4): + micromark-extension-gfm@3.0.0: dependencies: - '@alcalzone/ansi-tokenize': 0.2.5 - ansi-escapes: 7.3.0 - ansi-styles: 6.2.3 - auto-bind: 5.0.1 - chalk: 5.6.2 - cli-boxes: 3.0.0 - cli-cursor: 4.0.0 - cli-truncate: 5.2.0 - code-excerpt: 4.0.0 - es-toolkit: 1.50.0 - indent-string: 5.0.0 - is-in-ci: 2.0.0 - patch-console: 2.0.0 - react: 19.2.4 - react-reconciler: 0.33.0(react@19.2.4) - scheduler: 0.27.0 - signal-exit: 3.0.7 - slice-ansi: 8.0.0 - stack-utils: 2.0.6 - string-width: 8.2.2 - terminal-size: 4.0.1 - type-fest: 5.8.0 - widest-line: 6.0.0 - wrap-ansi: 9.0.2 - ws: 8.21.3 - yoga-layout: 3.2.1 - optionalDependencies: - '@types/react': 19.2.14 - transitivePeerDependencies: - - bufferutil - - utf-8-validate + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 - is-extglob@2.1.1: {} + micromark-extension-math@3.1.0: + dependencies: + '@types/katex': 0.16.8 + devlop: 1.1.0 + katex: 0.16.47 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - is-fullwidth-code-point@5.1.0: + micromark-factory-destination@2.0.1: dependencies: - get-east-asian-width: 1.6.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - is-glob@4.0.3: + micromark-factory-label@2.0.1: dependencies: - is-extglob: 2.1.1 + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - is-in-ci@2.0.0: {} + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 - is-plain-obj@4.1.0: {} + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - is-stream@4.0.1: {} + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - is-unicode-supported@2.1.0: {} + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - isexe@2.0.0: {} + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 - js-md4@0.3.2: {} + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - js-tiktoken@1.0.21: + micromark-util-combine-extensions@2.0.1: dependencies: - base64-js: 1.5.1 + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 - js-tokens@4.0.0: {} + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 - js-yaml@4.3.1: + micromark-util-decode-string@2.0.1: dependencies: - argparse: 2.0.1 + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 - jsesc@3.1.0: {} + micromark-util-encode@2.0.1: {} - json-rpc-2.0@1.7.1: {} + micromark-util-html-tag-name@2.0.1: {} - json-schema-to-typescript@15.0.4: + micromark-util-normalize-identifier@2.0.1: dependencies: - '@apidevtools/json-schema-ref-parser': 11.9.3 - '@types/json-schema': 7.0.15 - '@types/lodash': 4.17.24 - is-glob: 4.0.3 - js-yaml: 4.3.1 - lodash: 4.18.1 - minimist: 1.2.8 - prettier: 3.2.5 - tinyglobby: 0.2.17 + micromark-util-symbol: 2.0.1 - json-schema-traverse@1.0.0: {} + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 - json-with-bigint@3.5.10: {} + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 - json5@2.2.3: {} + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - lodash.groupby@4.6.0: {} + micromark-util-symbol@2.0.1: {} - lodash@4.18.1: {} + micromark-util-types@2.0.2: {} - lru-cache@5.1.1: + micromark@4.0.2: dependencies: - yallist: 3.1.1 - - math-intrinsics@1.1.0: {} + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color mimic-fn@2.1.0: {} @@ -2345,6 +5599,8 @@ snapshots: mute-stream@3.0.0: {} + nanoid@3.3.18: {} + node-releases@2.0.53: {} npm-run-path@6.0.0: @@ -2364,8 +5620,22 @@ snapshots: papaparse@5.5.3: {} + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse-ms@4.0.0: {} + parse5@7.3.0: + dependencies: + entities: 6.0.1 + patch-console@2.0.0: {} path-key@3.1.1: {} @@ -2382,14 +5652,30 @@ snapshots: picomatch@4.0.5: {} + postcss@8.5.26: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prettier@3.2.5: {} pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 + prismjs@1.30.0: {} + progress@2.0.3: {} + property-information@7.2.0: {} + pump@3.0.4: dependencies: end-of-stream: 1.4.5 @@ -2401,13 +5687,213 @@ snapshots: dependencies: side-channel: 1.1.1 + radix-ui@1.6.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-accessible-icon': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-accordion': 1.2.20(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-alert-dialog': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-aspect-ratio': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-avatar': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-checkbox': 1.3.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collapsible': 1.1.20(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context-menu': 2.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-dropdown-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-form': 0.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-hover-card': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-menubar': 1.1.24(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-navigation-menu': 1.2.22(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-one-time-password-field': 0.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-password-toggle-field': 0.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popover': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-progress': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-radio-group': 1.4.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-scroll-area': 1.2.18(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-select': 2.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-separator': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slider': 1.4.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-switch': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-tabs': 1.1.21(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-toast': 1.2.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-toggle': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-toolbar': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-tooltip': 1.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-escape-keydown': 1.1.5(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-markdown@9.1.0(@types/react@19.2.14)(react@19.2.4): + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@types/react': 19.2.14 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.4 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + react-merge-refs@2.1.1: {} + react-reconciler@0.33.0(react@19.2.4): dependencies: react: 19.2.4 scheduler: 0.27.0 + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.4): + dependencies: + react: 19.2.4 + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.4) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.14 + + react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.4): + dependencies: + react: 19.2.4 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.4) + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.4) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.4) + use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + + react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.4): + dependencies: + get-nonce: 1.0.1 + react: 19.2.4 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.14 + + react-syntax-highlighter@16.1.1(react@19.2.4): + dependencies: + '@babel/runtime': 7.29.7 + highlight.js: 10.7.3 + highlightjs-vue: 1.0.0 + lowlight: 1.20.0 + prismjs: 1.30.0 + react: 19.2.4 + refractor: 5.0.0 + react@19.2.4: {} + refractor@5.0.0: + dependencies: + '@types/hast': 3.0.5 + '@types/prismjs': 1.26.6 + hastscript: 9.0.1 + parse-entities: 4.0.2 + + rehype-katex@7.0.1: + dependencies: + '@types/hast': 3.0.5 + '@types/katex': 0.16.8 + hast-util-from-html-isomorphic: 2.0.0 + hast-util-to-text: 4.0.2 + katex: 0.16.47 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + + remark-breaks@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-newline-to-break: 2.0.0 + unified: 11.0.5 + + remark-directive@3.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-directive: 3.1.0 + micromark-extension-directive: 3.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-math@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-math: 3.0.0 + micromark-extension-math: 3.1.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + require-from-string@2.0.2: {} restore-cursor@4.0.0: @@ -2474,8 +5960,12 @@ snapshots: smol-toml@1.6.1: {} + source-map-js@1.2.1: {} + source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -2491,14 +5981,31 @@ snapshots: get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 strip-final-newline@4.0.0: {} + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + tagged-tag@1.0.0: {} + tailwindcss@4.3.3: {} + + tapable@2.3.3: {} + terminal-size@4.0.1: {} tinyglobby@0.2.17: @@ -2510,6 +6017,10 @@ snapshots: tree-kill@1.2.2: {} + trim-lines@3.0.1: {} + + trough@2.2.0: {} + tslib@2.8.1: {} tunnel@0.0.6: {} @@ -2536,6 +6047,49 @@ snapshots: unicorn-magic@0.3.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + universal-user-agent@7.0.3: {} update-browserslist-db@1.3.1(browserslist@4.28.8): @@ -2544,8 +6098,45 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.4): + dependencies: + react: 19.2.4 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.14 + + use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.4): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.4 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.14 + + usehooks-ts@3.1.1(react@19.2.4): + dependencies: + lodash.debounce: 4.0.8 + react: 19.2.4 + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + weapon-regex@1.3.6: {} + web-namespaces@2.0.1: {} + which@2.0.2: dependencies: isexe: 2.0.0 @@ -2578,3 +6169,5 @@ snapshots: yoga-layout@3.2.1: {} zod@4.4.3: {} + + zwitch@2.0.4: {} diff --git a/sdk/typescript/pnpm-workspace.yaml b/sdk/typescript/pnpm-workspace.yaml new file mode 100644 index 000000000..49c0ad742 --- /dev/null +++ b/sdk/typescript/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + esbuild: false diff --git a/sdk/typescript/scripts/build-dashboard.mjs b/sdk/typescript/scripts/build-dashboard.mjs new file mode 100644 index 000000000..17ef4d8e8 --- /dev/null +++ b/sdk/typescript/scripts/build-dashboard.mjs @@ -0,0 +1,89 @@ +import { + copyFile, + mkdir, + readFile, + readdir, + writeFile, +} from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; +import { build } from "esbuild"; +import postcss from "postcss"; +import tailwindcss from "@tailwindcss/postcss"; + +const root = fileURLToPath(new URL("../", import.meta.url)); +const destination = new URL("../dist/server/dashboard/", import.meta.url); +await mkdir(destination, { recursive: true }); +const built = await build({ + absWorkingDir: root, + entryPoints: ["dashboard/index.tsx"], + outfile: fileURLToPath(new URL("app.js", destination)), + bundle: true, + platform: "browser", + format: "esm", + target: ["es2022"], + minify: true, + metafile: true, + legalComments: "eof", + define: { "process.env.NODE_ENV": '"production"' }, + plugins: [ + { + name: "dashboard-styles", + setup(builder) { + builder.onLoad( + { filter: /dashboard[/\\]styles\.css$/ }, + async ({ path }) => { + const css = await postcss([tailwindcss({ base: root })]).process( + await readFile(path, "utf8"), + { from: path }, + ); + // Use system fonts instead of the design system's remote math fonts. + css.root.walkAtRules("font-face", (rule) => rule.remove()); + return { + contents: css.root.toString(), + loader: "css", + resolveDir: dirname(path), + }; + }, + ); + }, + }, + ], +}); +await copyFile( + new URL("../dashboard/index.html", import.meta.url), + new URL("index.html", destination), +); + +// Preserve the licenses of packages whose code or design tokens ship in the browser bundle. +const packages = new Set([ + "node_modules/@openai/apps-sdk-ui", + "node_modules/tailwindcss", +]); +for (const output of Object.values(built.metafile.outputs)) { + for (const [source, { bytesInOutput }] of Object.entries(output.inputs)) { + const directory = /^(.*node_modules\/(?:@[^/]+\/)?[^/]+)\//.exec( + source.replaceAll("\\", "/"), + )?.[1]; + if (directory && bytesInOutput > 0) packages.add(directory); + } +} +const notices = []; +for (const directory of [...packages].sort()) { + const path = join(root, directory); + const manifest = JSON.parse( + await readFile(join(path, "package.json"), "utf8"), + ); + const license = (await readdir(path)).find((name) => + /^licen[sc]e(?:\.[^.]+)?$/i.test(name), + ); + if (!license) + throw new Error(`Missing dashboard dependency license: ${manifest.name}`); + notices.push( + `${manifest.name}@${manifest.version}\n\n${await readFile(join(path, license), "utf8")}`, + ); +} +await writeFile( + new URL("THIRD_PARTY_NOTICES.txt", destination), + notices.join("\n\n---\n\n"), +); diff --git a/sdk/typescript/scripts/check-package.mjs b/sdk/typescript/scripts/check-package.mjs index 39a6712d5..33df7cef2 100644 --- a/sdk/typescript/scripts/check-package.mjs +++ b/sdk/typescript/scripts/check-package.mjs @@ -198,6 +198,7 @@ const distFiles = new Set( "scan-sessions", "server/index", "deduplication/codex-review", + "deduplication/checkpointed-review", "deduplication/deduplication", "finding-retrieval", "finding-workflow", @@ -208,6 +209,8 @@ const distFiles = new Set( "deduplication/scan", "saved-scan", "server/embeddings", + "server/dashboard", + "server/dashboard-types", "server/errors", "server/findings-service", "server/routes", @@ -227,6 +230,15 @@ const distFiles = new Set( ), ), ); +const dashboardFiles = new Set([ + "package/dist/server/dashboard/index.html", + "package/dist/server/dashboard/app.js", + "package/dist/server/dashboard/app.css", + "package/dist/server/dashboard/THIRD_PARTY_NOTICES.txt", +]); +for (const file of dashboardFiles) { + if (!files.has(file)) throw new Error(`npm tarball is missing ${file}.`); +} for (const file of distFiles) { if (!files.has(file)) throw new Error(`npm tarball is missing ${file}.`); } @@ -238,10 +250,12 @@ for (const file of files) { normalized === "package/bin" || normalized === "package/dist" || normalized === "package/dist/server" || + normalized === "package/dist/server/dashboard" || normalized === "package/dist/deduplication" || pluginDirectories.has(normalized) : allowedRoot.has(normalized) || distFiles.has(normalized) || + dashboardFiles.has(normalized) || pluginEntries.has(normalized); if (!allowed || unsafePath.test(file) || file.includes("\\")) { throw new Error(`npm tarball contains an unexpected file: ${file}.`); diff --git a/sdk/typescript/scripts/fixtures/findings-service-sqlite.ts b/sdk/typescript/scripts/fixtures/findings-service-sqlite.ts index 227482d56..326b2412a 100644 --- a/sdk/typescript/scripts/fixtures/findings-service-sqlite.ts +++ b/sdk/typescript/scripts/fixtures/findings-service-sqlite.ts @@ -75,6 +75,48 @@ try { members.map((row) => row["finding_id"]), importedIds.slice(0, 3).sort(), ); + const workflows = db + .prepare("SELECT dedupe_status, results_json FROM finding_workflows") + .all(); + assert.equal(workflows.length, 2); + for (const row of workflows) { + const results = JSON.parse(row["results_json"] as string); + assert.equal(row["dedupe_status"], "completed"); + assert.deepEqual(results.dedupe.duplicateGroups, [ + importedIds.slice(0, 3), + ]); + assert.ok(!("dedupePendingWrite" in results)); + } + const reviews = db + .prepare( + "SELECT model, source_content_digest, prompt_digest, contract_digest, result_json FROM finding_workflow_reviews", + ) + .all(); + const models = new Set(); + const decisions = new Set(); + for (const row of reviews) { + const result = JSON.parse(row["result_json"] as string); + models.add(row["model"] as string); + assert.ok(row["source_content_digest"]); + assert.ok(row["prompt_digest"] && row["contract_digest"]); + for (const decision of "decisions" in result + ? result.decisions + : [result]) { + decisions.add(decision.decision); + if (decision.decision === "SAME") { + assert.equal(typeof decision.canonicalFindingId, "string"); + assert.equal( + decision.mergedFinding.findingId, + decision.canonicalFindingId, + ); + assert.ok( + decision.mergedFinding.extensions.mergedOriginals.length > 0, + ); + } + } + } + assert.deepEqual(models, new Set(["gpt-5.6-luna", "gpt-5.6-sol"])); + assert.deepEqual(decisions, new Set(["SAME", "DISTINCT"])); } if (process.argv.includes("--prepare-scan")) { diff --git a/sdk/typescript/scripts/smoke-findings-service.ts b/sdk/typescript/scripts/smoke-findings-service.ts index 5e892f381..c565c217d 100644 --- a/sdk/typescript/scripts/smoke-findings-service.ts +++ b/sdk/typescript/scripts/smoke-findings-service.ts @@ -9,6 +9,7 @@ import type { Finding, FindingsDocument, ScanManifest } from "../src/models.js"; import type { DeduplicateScanResult } from "../src/deduplication/scan.js"; import type { FindingsPage } from "../src/server/storage.js"; import type { FindingDedupeGroup } from "../src/finding-dedupe-groups.js"; +import type { DashboardSnapshot } from "../src/server/dashboard-types.js"; const repositoryRoot = fileURLToPath(new URL("../../../", import.meta.url)); const container = "findings-ci"; @@ -168,6 +169,25 @@ async function checkHostPublication(): Promise { }); } +async function checkDashboard(): Promise { + for (const [path, type] of [ + ["/dashboard", "text/html"], + ["/dashboard/app.js", "text/javascript"], + ["/dashboard/app.css", "text/css"], + ]) { + const response = await fetch(`${base}${path}`); + assert.equal(response.status, 200); + assert.ok(response.headers.get("content-type")?.startsWith(type!)); + assert.ok((await response.text()).length > 0); + } + const response = await fetch(`${base}/v1/dashboard?view=findings`); + assert.equal(response.status, 200); + const snapshot = (await response.json()) as DashboardSnapshot; + assert.equal(snapshot.total, findings.length); + assert.equal(snapshot.overview.findings, findings.length); + assert.equal(snapshot.items.length, findings.length); +} + async function checkInsertions(): Promise { for (const [repository, batch] of [ [repositoryId, findings.slice(0, 3)], @@ -350,6 +370,7 @@ try { await startService(); await checkHostPublication(); await checkInsertions(); + await checkDashboard(); await checkCandidates(); await checkPages(); checkStorage(); diff --git a/sdk/typescript/scripts/smoke-package.mjs b/sdk/typescript/scripts/smoke-package.mjs index 549d6cfe4..f9d50b242 100644 --- a/sdk/typescript/scripts/smoke-package.mjs +++ b/sdk/typescript/scripts/smoke-package.mjs @@ -601,6 +601,52 @@ try { /lin_api_|security@example\.test/u, ); + const { startFindingsServer } = await import( + pathToFileURL(join(installedRoot, "dist/server/server.js")).href + ); + const dashboardServer = await startFindingsServer({ + // Package builders need only Node. Native and runtime-container tests cover SQLite. + store: { + async initialize() {}, + }, + embeddings: { + async embed() { + throw new Error("Dashboard reads must not call a model"); + }, + }, + host: "127.0.0.1", + port: 0, + }); + try { + const base = `http://127.0.0.1:${dashboardServer.address().port}`; + for (const [path, contentType] of [ + ["/dashboard", "text/html"], + ["/dashboard/app.js", "text/javascript"], + ["/dashboard/app.css", "text/css"], + ]) { + const response = await fetch(`${base}${path}`); + assert.equal(response.status, 200); + assert.ok(response.headers.get("content-type").startsWith(contentType)); + const body = await response.text(); + assert.ok(body.length > 0); + if (contentType === "text/html") { + const mounted = new URL("/service/dashboard/", base); + const assets = [...body.matchAll(/(?:href|src)="([^"]+)"/g)].map( + (match) => new URL(match[1], mounted).pathname, + ); + assert.deepEqual(assets, [ + "/service/dashboard/app.css", + "/service/dashboard/app.js", + ]); + } + } + assert.equal((await fetch(`${base}/dashboard/package.json`)).status, 404); + } finally { + await new Promise((resolve, reject) => + dashboardServer.close((error) => (error ? reject(error) : resolve())), + ); + } + run( process.execPath, [ @@ -614,7 +660,7 @@ try { await smokeNestedDeepScanWorker(installedRoot, consumer); console.log( - `Validated installed ${packageManifest.name}@${packageManifest.version}: public import, NodeNext types, CLI, credential locking, ${expectedPluginFiles.length} bundled plugin files, MCP initialization, bundled Codex version, and a nested worker without global codex.`, + `Validated installed ${packageManifest.name}@${packageManifest.version}: public import, NodeNext types, CLI, credential locking, ${expectedPluginFiles.length} bundled plugin files, MCP initialization, bundled Codex version, dashboard assets, and a nested worker without global codex.`, ); } finally { await rm(consumer, { diff --git a/sdk/typescript/src/deduplication/checkpointed-review.ts b/sdk/typescript/src/deduplication/checkpointed-review.ts new file mode 100644 index 000000000..6fa28c670 --- /dev/null +++ b/sdk/typescript/src/deduplication/checkpointed-review.ts @@ -0,0 +1,93 @@ +import type { JsonObject } from "../config.js"; +import { readFile } from "node:fs/promises"; +import { homedir } from "node:os"; +import { join } from "node:path"; +import { CodexSecurityError } from "../errors.js"; +import type { FindingSearchScope } from "../finding-retrieval.js"; +import { FindingWorkflow, workflowDigest } from "../finding-workflow.js"; +import { CODEX_EXECUTABLE_VERSION } from "../version.js"; +import { + codexSecurityCredentialHome, + expandHome, + resolveCodexCommand, +} from "../runtime.js"; +import type { CodexReview, CodexReviewRunner } from "./codex-review.js"; +import { + reviewSubmissionInstructions, + sourceReviewInstructions, +} from "./deduplication-prompts.js"; + +// Increment when validation or review execution changes without a prompt/schema change. +const REVIEW_CONTRACT_VERSION = 1; + +export async function reviewSettingsDigest( + environment: NodeJS.ProcessEnv, +): Promise { + const homes = new Set([ + expandHome( + environment["CODEX_HOME"] ?? join(homedir(), ".codex"), + environment, + ), + codexSecurityCredentialHome(environment), + ]); + const configs = await Promise.all( + [...homes].map(async (home) => { + try { + return await readFile(join(home, "config.toml"), "utf8"); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") return null; + throw error; + } + }), + ); + return workflowDigest({ + configs, + command: resolveCodexCommand(environment), + baseUrl: environment["OPENAI_BASE_URL"], + }); +} + +export class CheckpointedReviewRunner { + constructor( + private readonly workflow: FindingWorkflow, + private readonly runner: Pick, + private readonly source: JsonObject, + private readonly scope: FindingSearchScope, + private readonly settingsDigest?: string, + ) {} + + async assertSourceUnchanged(): Promise { + const current = await this.workflow.sourceSnapshot( + this.source["repository"] as string, + ); + if (workflowDigest(current) !== workflowDigest(this.source)) + throw new CodexSecurityError( + "Source changed during deduplication. Restart the workflow to review the changed inputs.", + ); + } + + async run(review: CodexReview): Promise { + const binding = { + version: REVIEW_CONTRACT_VERSION, + codexVersion: CODEX_EXECUTABLE_VERSION, + source: this.source, + scope: this.scope, + model: review.model, + effort: review.effort, + settingsDigest: this.settingsDigest, + promptDigest: workflowDigest([ + reviewSubmissionInstructions, + sourceReviewInstructions, + review.prompt, + ]), + contractDigest: workflowDigest(review.schema), + }; + const key = workflowDigest(binding); + const saved = await this.workflow.getReview(key); + if (saved !== null) return review.validate(saved); + const result = review.validate(await this.runner.run(review)); + await this.assertSourceUnchanged(); + await this.workflow.saveReview(key, binding, result); + return result; + } +} diff --git a/sdk/typescript/src/deduplication/deduplication-reviewer.ts b/sdk/typescript/src/deduplication/deduplication-reviewer.ts index a703d6652..129dc6899 100644 --- a/sdk/typescript/src/deduplication/deduplication-reviewer.ts +++ b/sdk/typescript/src/deduplication/deduplication-reviewer.ts @@ -1,4 +1,6 @@ import { z } from "incur"; +import { readFileSync } from "node:fs"; +import Ajv2020, { type ValidateFunction } from "ajv/dist/2020.js"; import type { Finding } from "../models.js"; import type { CodexReviewRunner } from "./codex-review.js"; import { pairReviewPrompt, screeningPrompt } from "./deduplication-prompts.js"; @@ -32,6 +34,33 @@ const screeningSchema = z }) .strict(); +let validateMergedFinding: ValidateFunction | undefined; + +function requireMergedFinding(result: DuplicateDecision): void { + if (result.decision !== "SAME") return; + if (validateMergedFinding === undefined) { + const schema = JSON.parse( + readFileSync( + new URL( + "../../_bundled_plugin/schemas/findings.schema.json", + import.meta.url, + ), + "utf8", + ), + ); + validateMergedFinding = new Ajv2020({ strict: false }).compile( + schema.properties.findings.items, + ); + } + if ( + !validateMergedFinding(result.mergedFinding) || + result.mergedFinding["findingId"] !== result.canonicalFindingId + ) + throw new Error( + "Every SAME decision requires a generated mergedFinding in the Finding schema with the canonical finding's identity.", + ); +} + export type ScreeningResult = z.infer; export type DuplicateDecision = z.infer; @@ -49,6 +78,7 @@ export function validateReview( findings: readonly Finding[], ): DuplicateDecision { const result = reviewSchema.parse(value); + requireMergedFinding(result); if ( result.decision === "SAME" && !findings.some((finding) => finding.findingId === result.canonicalFindingId) @@ -71,6 +101,7 @@ export function validateScreening( ); const seen = new Set(); for (const recommendation of result.decisions) { + requireMergedFinding(recommendation); const pair = recommendation.findingIds; const key = pairKey(pair); if (pair[0] === pair[1] || !required.has(key) || seen.has(key)) { diff --git a/sdk/typescript/src/deduplication/scan.ts b/sdk/typescript/src/deduplication/scan.ts index 3029c9017..2cb58652b 100644 --- a/sdk/typescript/src/deduplication/scan.ts +++ b/sdk/typescript/src/deduplication/scan.ts @@ -26,6 +26,10 @@ import { workflowDigest, } from "../finding-workflow.js"; import { publishScanToCustomInternal } from "../custom-publish.js"; +import { + CheckpointedReviewRunner, + reviewSettingsDigest, +} from "./checkpointed-review.js"; export interface DeduplicateScanOptions { /** Resume the named local findings workflow, including custom publication. */ @@ -56,6 +60,7 @@ export async function deduplicateScanInternal( dependencies: Partial & { environment?: NodeJS.ProcessEnv; reviewer?: DeduplicationReviewer; + reviewRunner?: Pick; fetch?: FindingsRequest; } = {}, ): Promise { @@ -143,31 +148,46 @@ export async function deduplicateScanInternal( ); } const dedupe = async (): Promise => { + const saved = (await workflow?.get())?.stages.dedupe; + if (saved?.pendingWrite) { + await client.storeDedupeGroups(saved.pendingWrite.groups); + return saved.result as DeduplicateScanResult; + } + const runner = + dependencies.reviewRunner ?? + new CodexReviewRunner( + environment, + undefined, + options.signal, + scan["targetPath"] as string, + ); + const checkpoints = workflow + ? new CheckpointedReviewRunner( + workflow, + runner, + await workflow.sourceSnapshot(scan["targetPath"] as string), + scope, + await reviewSettingsDigest(environment), + ) + : undefined; const deduplicator = new FindingDeduplicator( { potentialDuplicates: (findingId) => client.potentialDuplicates(findingId, scope), }, dependencies.reviewer ?? - new CodexDeduplicationReviewer( - new CodexReviewRunner( - environment, - undefined, - options.signal, - scan["targetPath"] as string, - ), - ), + new CodexDeduplicationReviewer(checkpoints ?? runner), options.signal, ); - const result = await deduplicator.run( + const reviewed = await deduplicator.run( contract.findings.findings.map((finding) => finding.findingId), ); + await checkpoints?.assertSourceUnchanged(); options.signal?.throwIfAborted(); + const result: DeduplicateScanResult = { scanId: scan.scanId, ...reviewed }; + await workflow?.prepareDedupe(result, { groups: result.duplicateGroups }); await client.storeDedupeGroups(result.duplicateGroups); - return { - scanId: scan.scanId, - ...result, - }; + return result; }; return workflow ? await workflow.run("dedupe", dedupe) : await dedupe(); } diff --git a/sdk/typescript/src/finding-workflow.ts b/sdk/typescript/src/finding-workflow.ts index 3016654b0..07a3df6e9 100644 --- a/sdk/typescript/src/finding-workflow.ts +++ b/sdk/typescript/src/finding-workflow.ts @@ -30,6 +30,7 @@ export interface WorkflowState extends WorkflowBinding { status: "pending" | "running" | "failed" | "completed"; result?: unknown; error?: string; + pendingWrite?: { groups: string[][] }; } >; } @@ -130,12 +131,46 @@ export class FindingWorkflow { return context["scan"] as JsonObject; } + async sourceSnapshot(repository: string): Promise { + return (await this.request({ action: "source", repository }))[ + "source" + ] as JsonObject; + } + + async getReview(key: string): Promise { + return (await this.request({ action: "get-review", key }))["review"]; + } + + async saveReview( + key: string, + binding: object, + result: unknown, + ): Promise { + await this.request({ action: "save-review", key, binding, result }); + } + + async prepareDedupe( + result: unknown, + pendingWrite: { groups: string[][] }, + ): Promise { + await this.command({ + action: "prepare-dedupe", + stage: "dedupe", + result, + pendingWrite, + }); + } + private async command(payload: object): Promise { - const result = await this.call( + const result = await this.request(payload); + return result["workflow"] as unknown as WorkflowState | null; + } + + private async request(payload: object): Promise { + return await this.call( ["finding-workflow"], JSON.stringify({ id: this.id, ...payload }), ); - return result["workflow"] as unknown as WorkflowState | null; } private async call( diff --git a/sdk/typescript/src/server/dashboard-types.ts b/sdk/typescript/src/server/dashboard-types.ts new file mode 100644 index 000000000..4a5206046 --- /dev/null +++ b/sdk/typescript/src/server/dashboard-types.ts @@ -0,0 +1,46 @@ +import type { Finding } from "../models.js"; +import type { FindingDedupeGroup } from "../finding-dedupe-groups.js"; + +export type DashboardView = "findings" | "groups"; + +export interface DashboardQuery { + view: DashboardView; + limit: number; + offset: number; + query: string; + repository: string; + sort: "activity" | "newest"; + id?: string; +} + +/** Stored finding or duplicate group list projection. */ +export interface DashboardItem { + id: string; + title: string; + repositoryIds: string[]; + createdAt: string; + updatedAt: string; + memberCount?: number; + severity?: Finding["severity"]["level"]; +} + +export interface DashboardDetail { + item: DashboardItem; + finding?: Finding; + group?: FindingDedupeGroup; + groups?: FindingDedupeGroup[]; +} + +export interface DashboardSnapshot { + overview: { + findings: number; + groups: number; + }; + repositories: { id: string; label: string }[]; + items: DashboardItem[]; + total: number; + limit: number; + offset: number; + nextOffset: number | null; + detail: DashboardDetail | null; +} diff --git a/sdk/typescript/src/server/dashboard.ts b/sdk/typescript/src/server/dashboard.ts new file mode 100644 index 000000000..e0dc28804 --- /dev/null +++ b/sdk/typescript/src/server/dashboard.ts @@ -0,0 +1,57 @@ +import { readFile } from "node:fs/promises"; +import type { ServerResponse } from "node:http"; +import type { DashboardQuery, DashboardView } from "./dashboard-types.js"; +import { FindingsError } from "./errors.js"; +import { pagination } from "./validation.js"; + +const assets = new Map([ + ["/dashboard/", ["index.html", "text/html; charset=utf-8"]], + ["/dashboard/app.js", ["app.js", "text/javascript; charset=utf-8"]], + ["/dashboard/app.css", ["app.css", "text/css; charset=utf-8"]], +]); + +/** Serve only bundled UI assets, never a path supplied by a request. */ +export async function serveDashboard( + path: string, + response: ServerResponse, +): Promise { + if (path === "/dashboard") { + response.writeHead(308, { Location: "dashboard/" }); + response.end(); + return true; + } + const asset = assets.get(path); + if (!asset) return false; + const body = await readFile( + new URL(`./dashboard/${asset[0]}`, import.meta.url), + ); + response.writeHead(200, { + "Content-Type": asset[1]!, + "Cache-Control": "no-cache", + "X-Content-Type-Options": "nosniff", + "Content-Security-Policy": + "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; font-src 'self'; base-uri 'none'; frame-ancestors 'none'", + }); + response.end(body); + return true; +} + +export function dashboardQuery(parameters: URLSearchParams): DashboardQuery { + const view = parameters.get("view") ?? "findings"; + const sort = parameters.get("sort") ?? "activity"; + if (!["findings", "groups"].includes(view)) + throw new FindingsError("invalid_request", "Unknown dashboard view."); + if (sort !== "activity" && sort !== "newest") + throw new FindingsError( + "invalid_request", + "sort must be activity or newest.", + ); + return { + view: view as DashboardView, + ...pagination(parameters), + query: parameters.get("query") ?? "", + repository: parameters.get("repository") ?? "", + sort, + ...(parameters.has("id") ? { id: parameters.get("id")! } : {}), + }; +} diff --git a/sdk/typescript/src/server/findings-service.ts b/sdk/typescript/src/server/findings-service.ts index 9aaabc158..a271acd18 100644 --- a/sdk/typescript/src/server/findings-service.ts +++ b/sdk/typescript/src/server/findings-service.ts @@ -1,4 +1,5 @@ import type { Finding } from "../models.js"; +import type { DashboardQuery } from "./dashboard-types.js"; import type { FindingSearchScope } from "../finding-retrieval.js"; import type { FindingEmbedder } from "./embeddings.js"; import type { FindingsPage, FindingsStore } from "./storage.js"; @@ -38,4 +39,8 @@ export class FindingsService { async list(page: { limit: number; offset: number }): Promise { return await this.store.list(page); } + + async dashboard(query: DashboardQuery) { + return await this.store.dashboard(query); + } } diff --git a/sdk/typescript/src/server/routes.ts b/sdk/typescript/src/server/routes.ts index effcba354..d94e11142 100644 --- a/sdk/typescript/src/server/routes.ts +++ b/sdk/typescript/src/server/routes.ts @@ -1,6 +1,7 @@ import type { IncomingMessage, ServerResponse } from "node:http"; import type { ValidateFunction } from "ajv"; import { FindingsError } from "./errors.js"; +import { dashboardQuery, serveDashboard } from "./dashboard.js"; import type { FindingsService } from "./findings-service.js"; import { findingSearchScope, @@ -18,6 +19,20 @@ export async function handleFindingsRequest( try { const url = new URL(request.url ?? "/", "http://localhost"); const route = `${request.method} ${url.pathname}`; + if ( + request.method === "GET" && + (await serveDashboard(url.pathname, response)) + ) + return; + if (route === "GET /v1/dashboard") { + response.setHeader("Cache-Control", "no-store"); + json( + response, + 200, + await service.dashboard(dashboardQuery(url.searchParams)), + ); + return; + } if (route === "GET /v1/findings") { console.log(route); json(response, 200, await service.list(pagination(url.searchParams))); diff --git a/sdk/typescript/src/server/sqlite-store.ts b/sdk/typescript/src/server/sqlite-store.ts index 84a9ad665..4794fa928 100644 --- a/sdk/typescript/src/server/sqlite-store.ts +++ b/sdk/typescript/src/server/sqlite-store.ts @@ -6,6 +6,7 @@ import { type WorkbenchCommandOptions, } from "../runtime.js"; import { FindingsError } from "./errors.js"; +import type { DashboardQuery, DashboardSnapshot } from "./dashboard-types.js"; import type { FindingDedupeGroup } from "../finding-dedupe-groups.js"; import type { FindingNeighborhood, @@ -26,6 +27,13 @@ export class SqliteFindingsStore implements FindingsStore { await this.run(["database-info"]); } + async dashboard(query: DashboardQuery): Promise { + return (await this.run( + ["dashboard"], + JSON.stringify(query), + )) as unknown as DashboardSnapshot; + } + async insert( entries: readonly EmbeddedFinding[], repositoryId?: string, diff --git a/sdk/typescript/src/server/storage.ts b/sdk/typescript/src/server/storage.ts index ba663763d..ef804176b 100644 --- a/sdk/typescript/src/server/storage.ts +++ b/sdk/typescript/src/server/storage.ts @@ -1,4 +1,5 @@ import type { Finding } from "../models.js"; +import type { DashboardQuery, DashboardSnapshot } from "./dashboard-types.js"; import type { FindingDedupeGroup } from "../finding-dedupe-groups.js"; import type { FindingNeighborhood, @@ -25,6 +26,7 @@ export interface FindingsPage { export interface FindingsStore { initialize(): Promise; + dashboard(query: DashboardQuery): Promise; insert( entries: readonly EmbeddedFinding[], repositoryId?: string, diff --git a/sdk/typescript/tests-ts/finding-deduplication.test.ts b/sdk/typescript/tests-ts/finding-deduplication.test.ts index 60647aa45..78ea87488 100644 --- a/sdk/typescript/tests-ts/finding-deduplication.test.ts +++ b/sdk/typescript/tests-ts/finding-deduplication.test.ts @@ -361,6 +361,14 @@ test("accepts complete canonical and merged reviews and rejects invalid assignme { ...result, canonicalFindingId: null }, { ...result, mergedFinding: undefined }, { ...result, mergedFinding: null }, + { ...result, mergedFinding: {} }, + { + ...result, + mergedFinding: { + ...result.mergedFinding, + findingId: findings[1]!.findingId, + }, + }, { ...result, canonicalFindingId: "outside" }, { ...result, diff --git a/sdk/typescript/tests-ts/finding-workflow.test.ts b/sdk/typescript/tests-ts/finding-workflow.test.ts index b7b4eaee8..1ab5967f8 100644 --- a/sdk/typescript/tests-ts/finding-workflow.test.ts +++ b/sdk/typescript/tests-ts/finding-workflow.test.ts @@ -1,4 +1,4 @@ -import { spawn } from "node:child_process"; +import { execFileSync, spawn } from "node:child_process"; import { createHash } from "node:crypto"; import { once } from "node:events"; import { @@ -13,9 +13,11 @@ import { import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, expect, test } from "bun:test"; +import type { JsonObject } from "../src/config.js"; import { FindingWorkflow, workflowDestination, + type WorkflowState, } from "../src/finding-workflow.js"; import { publishScanToCustomInternal } from "../src/custom-publish.js"; import { deduplicateScanInternal } from "../src/deduplication/scan.js"; @@ -24,7 +26,16 @@ import { runCodexCommand, runWorkbench, } from "../src/runtime.js"; -import type { FindingsDocument, ScanManifest } from "../src/models.js"; +import type { Finding, FindingsDocument, ScanManifest } from "../src/models.js"; +import type { CodexReview } from "../src/deduplication/codex-review.js"; +import { + CheckpointedReviewRunner, + reviewSettingsDigest, +} from "../src/deduplication/checkpointed-review.js"; +import { + CodexDeduplicationReviewer, + type DuplicateDecision, +} from "../src/deduplication/deduplication-reviewer.js"; import { PLUGIN_ROOT } from "./plugin-root.js"; const directories: string[] = []; @@ -84,6 +95,58 @@ async function fixture() { }; } +async function restoreLegacyWorkflow( + environment: NodeJS.ProcessEnv, + state: object, + reviews: object[], +) { + const probe = await runCodexCommand( + { command: await resolvePluginPython({ environment }) }, + [ + "-I", + "-B", + "-c", + `import json, sqlite3, sys +sys.path.insert(0, sys.argv[1]) +from workbench_schema import MIGRATIONS, apply_migrations, sql_statements +db = sqlite3.connect(sys.argv[2]) +db.row_factory = sqlite3.Row +db.execute("PRAGMA foreign_keys = ON") +payload = json.load(sys.stdin) +state = payload["state"] +timestamp = "2026-08-01T00:00:00Z" +with db: + db.execute("DROP TABLE finding_workflow_reviews") + db.execute("DROP TABLE finding_workflows") + db.execute("DELETE FROM schema_migrations WHERE version >= 38") + for version, _, sql in MIGRATIONS: + if version in (36, 37): + for statement in sql_statements(sql): + db.execute(statement) + db.execute("INSERT INTO finding_workflows VALUES (?, ?, ?, ?)", + (state["id"], json.dumps(state), timestamp, timestamp)) + for review in payload["reviews"]: + db.execute("INSERT INTO finding_workflow_reviews VALUES (?, ?, ?, ?, ?)", + (state["id"], review["key"], json.dumps(review["binding"]), json.dumps(review["result"]), timestamp)) +before = list(db.iterdump()) +try: + apply_migrations(db, (*MIGRATIONS, (999, "synthetic failure", "INSERT INTO synthetic_missing_table VALUES (1);")), lambda: timestamp, lambda _: None) +except sqlite3.OperationalError: + pass +else: + raise AssertionError("Migration should fail") +assert not db.in_transaction +assert list(db.iterdump()) == before, "Failed migration must preserve workflow and review rows together" +db.close()`, + join(PLUGIN_ROOT, "scripts"), + join(environment["CODEX_SECURITY_STATE_DIR"]!, "workbench.sqlite3"), + ], + environment, + JSON.stringify({ state, reviews }), + ); + expect(probe.exitCode, probe.stderr).toBe(0); +} + test("migrates workflow columns atomically without losing receipts, pending writes, or resume state", async () => { const { environment, repository, scanDir, workbenchOptions } = await fixture(); @@ -115,7 +178,7 @@ test("migrates workflow columns atomically without losing receipts, pending writ pendingWrite: { groups: [["a", "b"]] }, }, }, - } as const; + } satisfies WorkflowState; const pending = { id: "migrated-pending", stages: { @@ -538,3 +601,569 @@ test("does not write workflow metadata into sealed artifacts", async () => { ), ).rejects.toThrow("outside the sealed scan artifacts"); }); + +function merged(findings: readonly Finding[]) { + return { + decision: "SAME" as const, + rationale: "REVIEW_OUTPUT_ONLY: one correction covers the supplied paths.", + canonicalFindingId: findings[0]!.findingId, + mergedFinding: { + ...findings[0]!, + title: "MERGED_OUTPUT_ONLY", + extensions: { originals: findings }, + }, + }; +} +const distinct: DuplicateDecision = { + decision: "DISTINCT", + rationale: "REVIEW_OUTPUT_ONLY: independent corrections are required.", +}; + +test.each(["screen", "pair"])( + "resumes unfinished %s reviews using validated checkpoints and original inputs", + async (interruptAt) => { + const { scanDir, environment, document, history } = await fixture(); + const findings = [ + document.findings[0]!, + ...[1, 2, 3].map((index) => ({ + ...structuredClone(document.findings[0]!), + findingId: `csf_${"f".repeat(23)}${index}`, + title: `Synthetic original ${index}`, + })), + ]; + const options = { + workflowId: `reviews-${interruptAt}`, + findingsUrl: "http://synthetic.test", + }; + const calls: string[] = []; + let interrupted = false; + const reviewRunner = { + async run(review: CodexReview): Promise { + expect(review.prompt).not.toContain("REVIEW_OUTPUT_ONLY"); + expect(review.prompt).not.toContain("MERGED_OUTPUT_ONLY"); + const originals = JSON.parse( + review.prompt.slice(review.prompt.lastIndexOf("\n\n") + 2), + ).findings as Finding[]; + for (const finding of originals) + expect(finding).toEqual( + findings.find( + (original) => original.findingId === finding.findingId, + )!, + ); + const stage = review.model === "gpt-5.6-luna" ? "screen" : "pair"; + calls.push(stage); + if ( + !interrupted && + stage === interruptAt && + (stage !== "pair" || + calls.filter((call) => call === "pair").length === 2) + ) { + interrupted = true; + throw new Error("Synthetic interrupted review"); + } + return review.validate( + stage === "screen" + ? { + decisions: originals.slice(1).map((finding) => ({ + findingIds: [originals[0]!.findingId, finding.findingId], + ...merged([originals[0]!, finding]), + })), + } + : originals.some( + (finding) => finding.findingId === findings[3]!.findingId, + ) + ? distinct + : merged(originals), + ); + }, + }; + const fetch = async (url: URL) => + url.pathname.endsWith("/bulk/findings") + ? Response.json([findings[0]!.findingId]) + : url.pathname.endsWith("/dedupe-groups") + ? Response.json([]) + : Response.json({ + finding: findings[0], + potentialDuplicates: findings.slice(1), + }); + await publishScanToCustomInternal(scanDir, options, { environment, fetch }); + await expect( + deduplicateScanInternal(document.scanId, options, { + environment, + runWorkbench: history, + reviewRunner, + fetch, + }), + ).rejects.toThrow("Synthetic interrupted review"); + const result = await deduplicateScanInternal(document.scanId, options, { + environment, + runWorkbench: history, + reviewRunner, + fetch, + }); + expect(result.duplicateGroups).toEqual([ + findings.slice(0, 3).map((finding) => finding.findingId), + ]); + expect(calls.filter((stage) => stage === "screen")).toHaveLength( + interruptAt === "screen" ? 2 : 1, + ); + expect(calls.filter((stage) => stage === "pair")).toHaveLength( + interruptAt === "pair" ? 4 : 3, + ); + const count = calls.length; + expect( + await deduplicateScanInternal(document.scanId, options, { + environment, + runWorkbench: history, + reviewRunner, + fetch: async () => { + throw new Error("Completed workflow must use its saved result"); + }, + }), + ).toEqual(result); + expect(calls).toHaveLength(count); + }, +); + +test.each([ + "before-post", + "before-write", + "lost-ack", + "lost-completion", + "migrated-lost-ack", +])( + "replays the exact saved group payload after %s without models", + async (failure) => { + const { environment, document, history } = await fixture(); + const originals = [ + document.findings[0]!, + { ...document.findings[0]!, findingId: `csf_${"f".repeat(24)}` }, + ]; + const options = { + workflowId: `write-${failure}`, + findingsUrl: "http://synthetic.test", + }; + const bodies: string[] = []; + const checkpoints: object[] = []; + let modelCalls = 0; + let failed = false; + const reviewRunner = { + async run(review: CodexReview): Promise { + modelCalls++; + return review.validate( + review.model === "gpt-5.6-luna" + ? { + decisions: [ + { + findingIds: originals.map((finding) => finding.findingId), + ...merged(originals), + }, + ], + } + : merged(originals), + ); + }, + }; + const fetch = async (url: URL, init: RequestInit) => { + if (url.pathname.endsWith("/bulk/findings")) + return Response.json([originals[0]!.findingId]); + if (!url.pathname.endsWith("/dedupe-groups")) + return Response.json({ + finding: originals[0], + potentialDuplicates: [originals[1]], + }); + const saved = (await new FindingWorkflow( + options.workflowId, + environment, + ).get())!.stages.dedupe; + expect(saved.status).toBe("running"); + expect(saved.result).toMatchObject({ + duplicateGroups: [originals.map((finding) => finding.findingId)], + }); + expect(saved.pendingWrite).toEqual(JSON.parse(init.body as string)); + bodies.push(init.body as string); + if (!failed && failure !== "lost-completion") { + failed = true; + if (failure === "before-write") + return new Response("", { status: 503 }); + return new Response("incomplete acknowledgement", { status: 201 }); + } + return Response.json([]); + }; + const runWorkbench = async (args: readonly string[], input?: string) => { + const payload = input ? JSON.parse(input) : {}; + if ( + !failed && + failure === "before-post" && + payload.action === "prepare-dedupe" + ) { + await history(args, input); + failed = true; + throw new Error("Synthetic stop before posting"); + } + if ( + !failed && + failure === "lost-completion" && + payload.action === "complete" && + payload.stage === "dedupe" + ) { + failed = true; + throw new Error("Synthetic completion receipt failure"); + } + const result = await history(args, input); + if (payload.action === "save-review") checkpoints.push(payload); + return result; + }; + await expect( + deduplicateScanInternal(document.scanId, options, { + environment, + runWorkbench, + reviewRunner, + fetch, + }), + ).rejects.toThrow(); + if (failure === "migrated-lost-ack") { + await restoreLegacyWorkflow( + environment, + (await new FindingWorkflow(options.workflowId, environment).get())!, + checkpoints, + ); + } + expect( + (await new FindingWorkflow(options.workflowId, environment).get())?.stages + .dedupe.status, + ).toBe("failed"); + const result = await deduplicateScanInternal(document.scanId, options, { + environment, + runWorkbench: history, + reviewRunner, + fetch, + }); + expect(bodies).toHaveLength(failure === "before-post" ? 1 : 2); + expect(new Set(bodies).size).toBe(1); + expect(modelCalls).toBe(2); + expect( + (await new FindingWorkflow(options.workflowId, environment).get())?.stages + .dedupe, + ).toEqual({ status: "completed", result }); + }, +); + +test.each(["current", "legacy", "workflow-columns"])( + "persists DISTINCT and complete SAME checkpoints across %s databases", + async (version) => { + const { environment, repository, document, workbenchOptions } = + await fixture(); + const workflow = new FindingWorkflow("all-decisions", environment); + await workflow.bind({}); + if (version === "workflow-columns") { + const probe = await runCodexCommand( + { command: workbenchOptions.python }, + [ + "-I", + "-B", + "-c", + `import sqlite3, sys +with sqlite3.connect(sys.argv[1]) as db: + db.execute("DROP TABLE finding_workflow_reviews") + db.execute("DELETE FROM schema_migrations WHERE version IN (37, 39)")`, + join(environment.CODEX_SECURITY_STATE_DIR, "workbench.sqlite3"), + ], + environment, + ); + expect(probe.exitCode, probe.stderr).toBe(0); + } + const originals = [ + document.findings[0]!, + { ...document.findings[0]!, findingId: `csf_${"f".repeat(24)}` }, + ]; + let calls = 0; + const checkpoints: Array<{ + key: string; + binding: JsonObject; + result: unknown; + }> = []; + const recordCheckpoint: typeof runWorkbench = async ( + options, + args, + input, + ) => { + const result = await runWorkbench(options, args, input); + const payload = input ? JSON.parse(input) : {}; + if (payload.action === "save-review") checkpoints.push(payload); + return result; + }; + const runner = { + async run(review: CodexReview): Promise { + calls++; + return review.validate( + review.model === "gpt-5.6-luna" + ? { + decisions: [ + { + findingIds: originals.map((finding) => finding.findingId), + ...distinct, + }, + ], + } + : merged(originals), + ); + }, + }; + const makeReviewer = async () => + new CodexDeduplicationReviewer( + new CheckpointedReviewRunner( + new FindingWorkflow(workflow.id, environment, recordCheckpoint), + runner, + await workflow.sourceSnapshot(repository), + { allRepositories: true }, + "synthetic-settings-hash", + ), + ); + const first = await makeReviewer(); + const screening = await first.screen(originals); + const pair = await first.reviewPair(originals); + if (version === "legacy") + await restoreLegacyWorkflow( + environment, + (await workflow.get())!, + checkpoints, + ); + const resumed = await makeReviewer(); + expect(await resumed.screen(originals)).toEqual(screening); + expect(await resumed.reviewPair(originals)).toEqual(pair); + expect(pair).toEqual(merged(originals)); + expect(calls).toBe(2); + expect(checkpoints).toHaveLength(2); + const probe = await runCodexCommand( + { command: workbenchOptions.python }, + [ + "-I", + "-B", + "-c", + `import json, sqlite3, sys +db = sqlite3.connect(sys.argv[1]) +db.row_factory = sqlite3.Row +assert "binding_json" not in {row["name"] for row in db.execute("PRAGMA table_info(finding_workflow_reviews)")} +assert list(db.execute("PRAGMA foreign_key_check")) == [] +assert db.execute("PRAGMA integrity_check").fetchone()[0] == "ok" +print(json.dumps([dict(row) for row in db.execute("SELECT * FROM finding_workflow_reviews ORDER BY review_key")]))`, + join(environment.CODEX_SECURITY_STATE_DIR, "workbench.sqlite3"), + ], + environment, + ); + expect(probe.exitCode, probe.stderr).toBe(0); + const rows = JSON.parse(probe.stdout); + for (const { key, binding, result } of checkpoints) { + const source = binding["source"] as JsonObject; + const row = rows.find( + (row: { review_key: string }) => row.review_key === key, + ); + expect(row).toMatchObject({ + workflow_id: workflow.id, + review_contract_version: binding["version"], + codex_version: binding["codexVersion"], + source_repository_path: source["repository"], + source_revision: source["revision"], + source_refs_digest: source["refsDigest"], + source_content_digest: source["content"], + scope_repository_id: null, + scope_all_repositories: 1, + model: binding["model"], + effort: binding["effort"], + settings_digest: binding["settingsDigest"], + prompt_digest: binding["promptDigest"], + contract_digest: binding["contractDigest"], + }); + if (version === "legacy") + expect(row.created_at).toBe("2026-08-01T00:00:00Z"); + expect(JSON.parse(row.result_json)).toEqual(result); + } + }, +); + +test.each([ + "finding", + "source", + "scope", + "model", + "effort", + "prompt", + "contract", + "configuration", +])("does not reuse a checkpoint for changed %s inputs", async (changed) => { + const { environment, repository, document } = await fixture(); + const workflow = new FindingWorkflow(`changed-${changed}`, environment); + await workflow.bind({}); + let calls = 0; + const runner = { + async run(review: CodexReview): Promise { + calls++; + return review.validate(distinct); + }, + }; + const review: CodexReview = { + model: "gpt-5.6-sol", + effort: "ultra", + prompt: JSON.stringify(document.findings), + schema: { type: "object" }, + validate: () => distinct, + }; + const source = await workflow.sourceSnapshot(repository); + const scope = { repositoryId: "synthetic-repository" }; + const initial = new CheckpointedReviewRunner( + workflow, + runner, + source, + scope, + await reviewSettingsDigest(environment), + ); + await initial.run(review); + await initial.run(review); + expect(calls).toBe(1); + if (changed === "source") + await writeFile(join(repository, "source.txt"), "changed source"); + if (changed === "configuration") { + await mkdir(environment.CODEX_HOME); + await writeFile( + join(environment.CODEX_HOME, "config.toml"), + 'model_reasoning_summary = "none"\n', + ); + } + const next = { ...review }; + if (changed === "finding") + next.prompt = JSON.stringify([ + { ...document.findings[0], title: "Changed original" }, + ]); + if (changed === "model") next.model = "gpt-5.6-luna"; + if (changed === "effort") next.effort = "high"; + if (changed === "prompt") next.prompt += "Changed review contract"; + if (changed === "contract") + next.schema = { type: "object", required: ["decision"] }; + const resumed = new CheckpointedReviewRunner( + new FindingWorkflow(workflow.id, environment), + runner, + await workflow.sourceSnapshot(repository), + changed === "scope" ? { allRepositories: true } : scope, + await reviewSettingsDigest(environment), + ); + await resumed.run(next); + expect(calls).toBe(2); +}); + +test("source snapshots include revisions and ignored content without following directory links", async () => { + const { environment, repository, root } = await fixture(); + const git = (...args: string[]) => + execFileSync("git", args, { cwd: repository, stdio: "pipe" }); + git("init", "--quiet"); + await writeFile(join(repository, ".gitignore"), "ignored.txt\n"); + await writeFile(join(repository, "tracked.txt"), "original"); + await mkdir(join(repository, "tracked-directory")); + await writeFile( + join(repository, "tracked-directory", "source.txt"), + "tracked source", + ); + git("add", "."); + git( + "-c", + "user.name=Example", + "-c", + "user.email=example@example.test", + "commit", + "--quiet", + "-m", + "Synthetic source", + ); + const workflow = new FindingWorkflow("source-snapshots", environment); + const first = await workflow.sourceSnapshot(repository); + await writeFile(join(repository, "ignored.txt"), "ignored source"); + const second = await workflow.sourceSnapshot(repository); + expect(second["revision"]).toBe(first["revision"]); + expect(second["content"]).not.toBe(first["content"]); + git( + "-c", + "user.name=Example", + "-c", + "user.email=example@example.test", + "commit", + "--quiet", + "--allow-empty", + "-m", + "Next synthetic revision", + ); + expect((await workflow.sourceSnapshot(repository))["revision"]).not.toBe( + first["revision"], + ); + const beforeRef = await workflow.sourceSnapshot(repository); + git("branch", "synthetic-source-reference"); + const afterRef = await workflow.sourceSnapshot(repository); + expect(afterRef["revision"]).toBe(beforeRef["revision"]); + expect(afterRef["refsDigest"]).not.toBe(beforeRef["refsDigest"]); + const outside = join(root, "outside"); + await mkdir(outside); + await writeFile(join(outside, "private.txt"), "synthetic outside content"); + const { symlink } = await import("node:fs/promises"); + await symlink( + outside, + join(repository, "linked"), + process.platform === "win32" ? "junction" : "dir", + ); + const linked = await workflow.sourceSnapshot(repository); + await writeFile(join(outside, "private.txt"), "changed outside content"); + expect(await workflow.sourceSnapshot(repository)).toEqual(linked); + await rm(join(repository, "tracked-directory"), { recursive: true }); + await writeFile(join(outside, "source.txt"), "synthetic outside source"); + await symlink( + outside, + join(repository, "tracked-directory"), + process.platform === "win32" ? "junction" : "dir", + ); + const replaced = await workflow.sourceSnapshot(repository); + await writeFile(join(outside, "source.txt"), "changed outside source"); + expect(await workflow.sourceSnapshot(repository)).toEqual(replaced); +}); + +test("does not checkpoint a review when source changes during its execution", async () => { + const { environment, repository, document } = await fixture(); + const workflow = new FindingWorkflow("source-drift", environment); + await workflow.bind({}); + const source = await workflow.sourceSnapshot(repository); + const runner = new CheckpointedReviewRunner( + workflow, + { + async run(review: CodexReview): Promise { + await writeFile( + join(repository, "changed.txt"), + "changed during review", + ); + return review.validate(distinct); + }, + }, + source, + { allRepositories: true }, + ); + const review: CodexReview = { + model: "gpt-5.6-sol", + effort: "ultra", + prompt: JSON.stringify(document.findings), + schema: {}, + validate: () => distinct, + }; + await expect(runner.run(review)).rejects.toThrow( + "Source changed during deduplication", + ); + await rm(join(repository, "changed.txt")); + let calls = 0; + await new CheckpointedReviewRunner( + workflow, + { + async run(review: CodexReview): Promise { + calls++; + return review.validate(distinct); + }, + }, + source, + { allRepositories: true }, + ).run(review); + expect(calls).toBe(1); +}); diff --git a/sdk/typescript/tests-ts/findings-dashboard.test.ts b/sdk/typescript/tests-ts/findings-dashboard.test.ts new file mode 100644 index 000000000..d3c917ff1 --- /dev/null +++ b/sdk/typescript/tests-ts/findings-dashboard.test.ts @@ -0,0 +1,122 @@ +import { expect, test } from "bun:test"; +import { pollDashboard } from "../dashboard/polling.js"; +import type { DashboardSnapshot } from "../src/server/dashboard-types.js"; + +const snapshot: DashboardSnapshot = { + overview: { findings: 0, groups: 0 }, + repositories: [], + items: [], + total: 0, + limit: 50, + offset: 0, + nextOffset: null, + detail: null, +}; + +function clock() { + let tick = () => {}; + let interval = 0; + let cleared = false; + return { + timers: { + setInterval(callback: () => void, milliseconds: number) { + tick = callback; + interval = milliseconds; + return 1 as unknown as ReturnType; + }, + clearInterval() { + cleared = true; + }, + } as Pick, + tick: () => tick(), + interval: () => interval, + cleared: () => cleared, + }; +} + +test("dashboard polls immediately and every five seconds without overlapping requests", async () => { + const timer = clock(); + const calls: AbortSignal[] = []; + const received: DashboardSnapshot[] = []; + let resolve!: (value: DashboardSnapshot) => void; + const stop = pollDashboard( + (signal) => { + calls.push(signal); + return new Promise((done) => { + resolve = done; + }); + }, + (value) => received.push(value), + () => { + throw new Error("Unexpected poll failure"); + }, + timer.timers, + ); + try { + expect(calls).toHaveLength(1); + expect(timer.interval()).toBe(5_000); + timer.tick(); + expect(calls).toHaveLength(1); + resolve(snapshot); + await Promise.resolve(); + expect(received).toEqual([snapshot]); + timer.tick(); + expect(calls).toHaveLength(2); + } finally { + stop(); + } + expect(timer.cleared()).toBe(true); + expect(calls.every((signal) => signal.aborted)).toBe(true); + resolve(snapshot); + await Promise.resolve(); + expect(received).toHaveLength(1); +}); + +test("dashboard reports refresh errors and retries on the next polling tick", async () => { + const timer = clock(); + const received: DashboardSnapshot[] = []; + const errors: unknown[] = []; + let request = 0; + const stop = pollDashboard( + async () => { + if (++request === 2) throw new Error("Synthetic connection failure"); + return { ...snapshot, total: request }; + }, + (value) => received.push(value), + (error) => errors.push(error), + timer.timers, + ); + try { + await Promise.resolve(); + timer.tick(); + await Promise.resolve(); + expect(errors).toHaveLength(1); + expect(received.map((value) => value.total)).toEqual([1]); + timer.tick(); + await Promise.resolve(); + expect(received.map((value) => value.total)).toEqual([1, 3]); + } finally { + stop(); + } +}); + +test("disposing an old selection suppresses late errors as well as late responses", async () => { + const timer = clock(); + const errors: unknown[] = []; + let reject!: (reason: Error) => void; + const stop = pollDashboard( + () => + new Promise((_resolve, fail) => { + reject = fail; + }), + () => { + throw new Error("Unexpected data"); + }, + (error) => errors.push(error), + timer.timers, + ); + stop(); + reject(new Error("Old request failed")); + await Promise.resolve(); + expect(errors).toEqual([]); +}); diff --git a/sdk/typescript/tests-ts/findings-server.test.ts b/sdk/typescript/tests-ts/findings-server.test.ts index 3c0246b8b..c53cf36b0 100644 --- a/sdk/typescript/tests-ts/findings-server.test.ts +++ b/sdk/typescript/tests-ts/findings-server.test.ts @@ -11,6 +11,7 @@ import { FindingsError } from "../src/server/errors.js"; import { startFindingsServer } from "../src/server/server.js"; import { SqliteFindingsStore } from "../src/server/sqlite-store.js"; import type { EmbeddedFinding, FindingsPage } from "../src/server/storage.js"; +import type { DashboardSnapshot } from "../src/server/dashboard-types.js"; import { PLUGIN_ROOT } from "./plugin-root.js"; const servers: Server[] = []; @@ -114,6 +115,173 @@ function storeGroups(base: string, groups: unknown) { }); } +async function dashboard( + base: string, + parameters: Record = {}, +) { + const response = await fetch( + `${base}/v1/dashboard?${new URLSearchParams(parameters)}`, + ); + expect(response.status).toBe(200); + expect(response.headers.get("cache-control")).toBe("no-store"); + return (await response.json()) as DashboardSnapshot; +} + +test("dashboard serves only findings and groups, and never calls an embedding provider", async () => { + const { store } = await fixture(); + const base = await start(store, { + async embed() { + throw new Error("Read-only dashboard called embeddings"); + }, + }); + const redirect = await fetch(`${base}/dashboard`, { redirect: "manual" }); + expect(redirect.status).toBe(308); + for (const prefix of ["", "/service"]) { + expect( + new URL(redirect.headers.get("location")!, `${base}${prefix}/dashboard`) + .pathname, + ).toBe(`${prefix}/dashboard/`); + } + for (const view of ["findings", "groups"]) { + const result = await dashboard(base, { view }); + expect(result).toMatchObject({ + items: [], + total: 0, + nextOffset: null, + detail: null, + overview: { findings: 0, groups: 0 }, + }); + } + for (const parameters of [ + "view=unknown", + "view=scans", + "view=workflows", + "sort=unknown", + "offset=-1", + "limit=0", + ]) { + expect((await fetch(`${base}/v1/dashboard?${parameters}`)).status).toBe( + 400, + ); + } + expect( + (await fetch(`${base}/v1/dashboard`, { method: "POST", body: "{}" })) + .status, + ).toBe(404); + expect((await fetch(`${base}/dashboard/not-a-bundled-asset`)).status).toBe( + 404, + ); +}); + +test("dashboard browses imported findings and overlapping groups without local runs", async () => { + const { store, environment } = await fixture(); + const base = await start(store); + const first = finding(1), + second = finding(2), + third = finding(3); + first.title = "Évaluation synthétique"; + await store.insert( + [{ ...embedded(1), finding: first }, embedded(2)], + "repository-a", + ); + await store.insert([embedded(3)], "repository-b"); + const groups = await store.storeDedupeGroups([ + [first.findingId, second.findingId], + [second.findingId, third.findingId], + ]); + const before = await database( + environment, + "print(json.dumps(list(db.iterdump())))", + ); + expect( + await database( + environment, + `from workbench_dashboard import dashboard +allowed = {'findings', 'finding_repositories', 'finding_dedupe_groups', 'finding_dedupe_group_members'} +def authorize(action, table, column, database, source): + if action == sqlite3.SQLITE_READ and table not in allowed: + return sqlite3.SQLITE_DENY + return sqlite3.SQLITE_OK +db.set_authorizer(authorize) +queries = json.load(sys.stdin) +print(json.dumps([dashboard(db, query)['total'] for query in queries]))`, + [ + { + view: "findings", + limit: 50, + offset: 0, + sort: "activity", + id: first.findingId, + }, + { + view: "groups", + limit: 50, + offset: 0, + sort: "newest", + id: groups[0]!.groupId, + }, + ], + ), + ).toEqual([3, 2]); + + const page = await dashboard(base, { + limit: "1", + repository: "repository-a", + id: first.findingId, + }); + expect(page.total).toBe(2); + expect(page.repositories).toEqual([ + { id: "repository-a", label: "repository-a" }, + { id: "repository-b", label: "repository-b" }, + ]); + expect(page.nextOffset).toBe(1); + expect(page.overview).toEqual({ + findings: 3, + groups: 2, + }); + expect(page.detail).toMatchObject({ + finding: first, + groups: [groups[0]], + }); + const next = await dashboard(base, { + view: "findings", + limit: "1", + offset: "1", + repository: "repository-a", + }); + expect(next.items[0]!.id).not.toBe(page.items[0]!.id); + expect(next.nextOffset).toBeNull(); + expect( + (await dashboard(base, { view: "findings", query: first.title })).items.map( + (item) => item.id, + ), + ).toEqual([first.findingId]); + for (const query of ["évaluation", "SYNTHÉTIQUE"]) { + expect( + (await dashboard(base, { view: "findings", query })).items.map( + (item) => item.id, + ), + ).toEqual([first.findingId]); + } + expect( + ( + await dashboard(base, { view: "groups", repository: "repository-b" }) + ).items.map((item) => item.id), + ).toEqual([groups[1]!.groupId]); + const group = await dashboard(base, { + view: "groups", + id: groups[0]!.groupId, + }); + expect(group.detail!.group).toEqual(groups[0]!); + expect(group.items).toHaveLength(2); + expect( + (await dashboard(base, { view: "findings", id: "not-stored" })).detail, + ).toBeNull(); + expect( + await database(environment, "print(json.dumps(list(db.iterdump())))"), + ).toEqual(before); +}); + async function getGroups( base: string, findingId: string, diff --git a/sdk/typescript/tests-ts/skeleton.test.ts b/sdk/typescript/tests-ts/skeleton.test.ts index 10de13787..d450d9b15 100644 --- a/sdk/typescript/tests-ts/skeleton.test.ts +++ b/sdk/typescript/tests-ts/skeleton.test.ts @@ -320,7 +320,7 @@ describe("TypeScript package skeleton", () => { ); expect(packageJson.scripts.build).toBe( - "node --run clean && tsc -p tsconfig.build.json", + "node --run clean && tsc -p tsconfig.build.json && node scripts/build-dashboard.mjs", ); expect(packageJson.scripts["build:plugin"]).toBe( "node scripts/build-plugin.mjs", diff --git a/sdk/typescript/tsconfig.json b/sdk/typescript/tsconfig.json index 6dc292729..97ce7ad63 100644 --- a/sdk/typescript/tsconfig.json +++ b/sdk/typescript/tsconfig.json @@ -2,6 +2,8 @@ "include": [ "src/**/*.ts", "src/**/*.tsx", + "dashboard/**/*.ts", + "dashboard/**/*.tsx", "tests-ts/**/*.ts", "scripts/smoke-findings-service.ts", "scripts/fixtures/findings-service-sqlite.ts" @@ -16,7 +18,7 @@ "incremental": true, "isolatedModules": true, "jsx": "react-jsx", - "lib": ["esnext"], + "lib": ["esnext", "dom", "dom.iterable"], "module": "esnext", "moduleResolution": "bundler", "noEmit": true,