From b51b67bca6844cfc7723d08db458ecefe1cb9980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:18:44 -0700 Subject: [PATCH 01/52] feat: add WizardSigninAuthClient for browser-mediated login flow --- src/auth.ts | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 src/auth.ts diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..1ed67b1 --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,219 @@ +export type WizardSigninCreateResponse = { + readonly id: string; + readonly poll_token: string; + readonly login_path: string; + readonly login_url: string; + readonly expires_at: string; +}; + +export type WizardSigninOrgInfo = { + readonly id: string; + readonly name: string; + readonly api_url?: string | null; + readonly proxy_url?: string | null; + readonly realtime_url?: string | null; + readonly is_universal_api?: boolean | null; + readonly git_metadata?: unknown; +}; + +export type WizardSigninProject = { + readonly id: string; + readonly name: string; + readonly org_id: string; + readonly description?: string | null; +}; + +export type WizardSigninCompleteResult = { + readonly apiKey: string; + readonly orgInfo: WizardSigninOrgInfo; + readonly project: WizardSigninProject; +}; + +export type WizardSigninEvents = { + readonly onLoginUrl: (info: { + readonly loginUrl: string; + readonly expiresAt: string; + }) => void; + readonly onTryOpenBrowser: (url: string) => Promise; +}; + +const POLL_INTERVAL_MS = 2000; +const SLOW_DOWN_INCREMENT_MS = 1000; +const POLL_HARD_TIMEOUT_MS = 30 * 60 * 1000; + +/** + * Browser-mediated wizard sign-in. + * + * Endpoints (added by the braintrust-wizard-login-flow PR): + * POST {appUrl}/api/cli/wizard-signin/create + * GET {appUrl}/api/cli/wizard-signin/poll?id=... + * (Authorization: Bearer ) + * + * The poll response is one of: + * { status: "pending", expires_at } + * { status: "expired" } + * { status: "claimed" } + * { status: "complete", api_key, org_info, project } + */ +export class WizardSigninAuthClient { + constructor( + private readonly appUrl: string, + private readonly clientName: string = "bt-wizard", + ) {} + + async createSession(): Promise { + const res = await fetch(`${this.appUrl}/api/cli/wizard-signin/create`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ client_name: this.clientName }), + }); + if (!res.ok) { + throw new Error( + `Wizard sign-in create failed: ${res.status} ${await res.text()}`, + ); + } + return (await res.json()) as WizardSigninCreateResponse; + } + + async pollSession(args: { + readonly id: string; + readonly pollToken: string; + readonly sleep?: (ms: number) => Promise; + }): Promise { + const sleep = args.sleep ?? defaultSleep; + let interval = POLL_INTERVAL_MS; + const deadline = Date.now() + POLL_HARD_TIMEOUT_MS; + while (Date.now() < deadline) { + await sleep(interval); + const url = `${this.appUrl}/api/cli/wizard-signin/poll?id=${encodeURIComponent(args.id)}`; + const res = await fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${args.pollToken}`, + Accept: "application/json", + }, + }); + const json = (await res.json().catch(() => ({}))) as Record< + string, + unknown + >; + if (res.status === 429) { + interval += SLOW_DOWN_INCREMENT_MS; + continue; + } + if (!res.ok) { + throw new Error( + `Wizard sign-in poll failed: ${res.status} ${JSON.stringify(json)}`, + ); + } + const status = json["status"]; + switch (status) { + case "pending": + continue; + case "expired": + throw new Error("Wizard sign-in session expired before approval."); + case "claimed": + throw new Error( + "Wizard sign-in session was already claimed by another client.", + ); + case "complete": + return parseCompleteResponse(json); + default: + throw new Error( + `Unexpected wizard sign-in status: ${JSON.stringify(json)}`, + ); + } + } + throw new Error("Wizard sign-in session timed out."); + } + + async login(events: WizardSigninEvents): Promise { + const session = await this.createSession(); + events.onLoginUrl({ + loginUrl: session.login_url, + expiresAt: session.expires_at, + }); + await events.onTryOpenBrowser(session.login_url); + return this.pollSession({ + id: session.id, + pollToken: session.poll_token, + }); + } +} + +function parseCompleteResponse( + json: Record, +): WizardSigninCompleteResult { + const apiKey = json["api_key"]; + if (typeof apiKey !== "string" || apiKey.length === 0) { + throw new Error("Wizard sign-in completed without an api_key"); + } + const orgInfo = parseOrgInfo(json["org_info"]); + const project = parseProject(json["project"]); + return { apiKey, orgInfo, project }; +} + +function parseOrgInfo(value: unknown): WizardSigninOrgInfo { + if (!isObject(value)) { + throw new Error("Wizard sign-in completed without org_info"); + } + const id = value["id"]; + const name = value["name"]; + if (typeof id !== "string" || typeof name !== "string") { + throw new Error("Wizard sign-in org_info missing id/name"); + } + return { + id, + name, + api_url: optionalString(value["api_url"]), + proxy_url: optionalString(value["proxy_url"]), + realtime_url: optionalString(value["realtime_url"]), + is_universal_api: + typeof value["is_universal_api"] === "boolean" + ? value["is_universal_api"] + : null, + git_metadata: value["git_metadata"], + }; +} + +function parseProject(value: unknown): WizardSigninProject { + if (!isObject(value)) { + throw new Error("Wizard sign-in completed without project"); + } + const id = value["id"]; + const name = value["name"]; + const orgId = value["org_id"]; + if ( + typeof id !== "string" || + typeof name !== "string" || + typeof orgId !== "string" + ) { + throw new Error("Wizard sign-in project missing id/name/org_id"); + } + return { + id, + name, + org_id: orgId, + description: optionalString(value["description"]), + }; +} + +function optionalString(value: unknown): string | null { + if (typeof value === "string") { + return value; + } + return null; +} + +function isObject(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function defaultSleep(ms: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} From a3bd5c4198c1dbd2637f1273a74b08a222442423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 15:09:41 -0700 Subject: [PATCH 02/52] fix: match full-block logo characters when PNG is unavailable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LOGO_PATTERN only matched ▀▄ (half-blocks from PNG rendering) but not █ (full-blocks from the fallback logo). In environments where the PNG fails to load, all three logo-presence assertions would fail. Co-Authored-By: Claude Sonnet 4.6 --- test/beau-app.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/beau-app.test.tsx b/test/beau-app.test.tsx index 81b9c61..65f47f0 100644 --- a/test/beau-app.test.tsx +++ b/test/beau-app.test.tsx @@ -9,8 +9,8 @@ import { AppRoot } from "../src/beau/AppRoot"; import { ACCOUNT_QUESTION } from "../src/wizard-copy"; const STRIP_PATTERN = /[▌▐]/; -const LOGO_PATTERN = /[▀▄]/; -const LOGO_BODY_SPLIT_PATTERN = /[█▀▄]{3,} +[█▀▄]{3,}/; +const LOGO_PATTERN = /[▟▜▘▝▖▗▙▛█]/; +const LOGO_BODY_SPLIT_PATTERN = /[█]{3,}[▌▐]+[█]{3,}/; const MIN_STRIP_MARKS_PER_LINE = 8; const LOGIN_BROWSER_PROMPT_START = "For the rest of the flow"; const LOGIN_BROWSER_PROMPT_END = "open the browser?"; From bddfc245e1c936d2f9c2a47f4cda0b614bba3172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 15:45:55 -0700 Subject: [PATCH 03/52] chore: max 3min rate limit for polling --- src/auth.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 1ed67b1..5d48ff4 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -39,7 +39,8 @@ export type WizardSigninEvents = { const POLL_INTERVAL_MS = 2000; const SLOW_DOWN_INCREMENT_MS = 1000; -const POLL_HARD_TIMEOUT_MS = 30 * 60 * 1000; +const MAX_POLL_INTERVAL_MS = 30_000; +const POLL_HARD_TIMEOUT_MS = 3 * 60 * 1000; /** * Browser-mediated wizard sign-in. @@ -58,7 +59,7 @@ const POLL_HARD_TIMEOUT_MS = 30 * 60 * 1000; export class WizardSigninAuthClient { constructor( private readonly appUrl: string, - private readonly clientName: string = "bt-wizard", + private readonly clientName: string = "crank", ) {} async createSession(): Promise { @@ -96,14 +97,17 @@ export class WizardSigninAuthClient { Accept: "application/json", }, }); + if (res.status === 429) { + interval = Math.min( + interval + SLOW_DOWN_INCREMENT_MS, + MAX_POLL_INTERVAL_MS, + ); + continue; + } const json = (await res.json().catch(() => ({}))) as Record< string, unknown >; - if (res.status === 429) { - interval += SLOW_DOWN_INCREMENT_MS; - continue; - } if (!res.ok) { throw new Error( `Wizard sign-in poll failed: ${res.status} ${JSON.stringify(json)}`, From bfff9ac8f18edfbc8fccd30ba2983592a4e978c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 18:14:26 -0700 Subject: [PATCH 04/52] chore: add request timeout release undici connection when rate-limited --- src/auth.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/auth.ts b/src/auth.ts index 5d48ff4..f017e21 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -41,6 +41,8 @@ const POLL_INTERVAL_MS = 2000; const SLOW_DOWN_INCREMENT_MS = 1000; const MAX_POLL_INTERVAL_MS = 30_000; const POLL_HARD_TIMEOUT_MS = 3 * 60 * 1000; +const CREATE_REQUEST_TIMEOUT_MS = 15_000; +const POLL_REQUEST_TIMEOUT_MS = 30_000; /** * Browser-mediated wizard sign-in. @@ -70,6 +72,7 @@ export class WizardSigninAuthClient { Accept: "application/json", }, body: JSON.stringify({ client_name: this.clientName }), + signal: AbortSignal.timeout(CREATE_REQUEST_TIMEOUT_MS), }); if (!res.ok) { throw new Error( @@ -96,12 +99,14 @@ export class WizardSigninAuthClient { Authorization: `Bearer ${args.pollToken}`, Accept: "application/json", }, + signal: AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS), }); if (res.status === 429) { interval = Math.min( interval + SLOW_DOWN_INCREMENT_MS, MAX_POLL_INTERVAL_MS, ); + res.body?.cancel(); continue; } const json = (await res.json().catch(() => ({}))) as Record< From b77521c6dba0c71f63e44ac0347af64e4f706263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:04:09 -0700 Subject: [PATCH 05/52] feat: Braintrust API client Co-Authored-By: Claude Sonnet 4.6 --- package.json | 1 + pnpm-lock.yaml | 2005 ++++++++++++++++++++++++++++++++++- src/braintrust-api.ts | 218 ++++ test/braintrust-api.test.ts | 93 ++ 4 files changed, 2278 insertions(+), 39 deletions(-) create mode 100644 src/braintrust-api.ts create mode 100644 test/braintrust-api.test.ts diff --git a/package.json b/package.json index 6805d1b..a804f51 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react-devtools-core": "7.0.1" }, "devDependencies": { + "@braintrust/proxy": "0.0.9", "@eslint/js": "10.0.1", "@types/node": "24.12.2", "@types/react": "19.2.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c0337d..3d08583 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,9 @@ importers: specifier: 7.0.1 version: 7.0.1 devDependencies: + '@braintrust/proxy': + specifier: 0.0.9 + version: 0.0.9(react@19.2.5)(solid-js@1.9.12)(svelte@4.2.20)(vue@3.5.34(typescript@6.0.3))(ws@8.20.0) '@eslint/js': specifier: 10.0.1 version: 10.0.1(eslint@10.3.0) @@ -62,7 +65,7 @@ importers: version: 8.59.2(eslint@10.3.0)(typescript@6.0.3) vitest: specifier: 4.1.5 - version: 4.1.5(@types/node@24.12.2)(vite@8.0.10(@types/node@24.12.2)) + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(vite@8.0.10(@types/node@24.12.2)(yaml@2.9.0)) packages: @@ -70,6 +73,171 @@ packages: resolution: {integrity: sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==} engines: {node: '>=18'} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@anthropic-ai/sdk@0.39.0': + resolution: {integrity: sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==} + + '@apidevtools/json-schema-ref-parser@11.9.3': + resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} + engines: {node: '>= 16'} + + '@asteasolutions/zod-to-openapi@6.4.0': + resolution: {integrity: sha512-8cxfF7AHHx2PqnN4Cd8/O8CBu/nVYJP9DpnfVLW3BFb66VJDnqI/CczZnkqMc3SNh6J9GiX7JbJ5T4BSP4HZ2Q==} + peerDependencies: + zod: ^3.20.2 + + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-bedrock-runtime@3.1045.0': + resolution: {integrity: sha512-aPC6gAz9uKRiwfnKB7peTs6yD0FpSzmVnSkx0f2QtJfosFM6J6KtBvR1lMKby050K4C4PAyEScwA5YTsGfTcGA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.974.8': + resolution: {integrity: sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.34': + resolution: {integrity: sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.36': + resolution: {integrity: sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.38': + resolution: {integrity: sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.38': + resolution: {integrity: sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.39': + resolution: {integrity: sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.34': + resolution: {integrity: sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.38': + resolution: {integrity: sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.38': + resolution: {integrity: sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/eventstream-handler-node@3.972.14': + resolution: {integrity: sha512-m4X56gxG76/CKfxNVbOFuYwnAZcHgS6HOH8lgp15HoGHIAVTcZfZrXvcYzJFOMLEJgVn+JHBu6EiNV+xSNXXFg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-eventstream@3.972.10': + resolution: {integrity: sha512-QUqLs7Af1II9X4fCRAu+EGHG3KHyOp4RkuLhRKoA3NuFlh6TL8i+zXBl8w2LUxqm44B/Kom45hgSlwA1SpTsXQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-host-header@3.972.10': + resolution: {integrity: sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-logger@3.972.10': + resolution: {integrity: sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.972.11': + resolution: {integrity: sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.37': + resolution: {integrity: sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-user-agent@3.972.38': + resolution: {integrity: sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-websocket@3.972.16': + resolution: {integrity: sha512-86+S9oCyRVGzoMRpQhxkArp7kD2K75GPmaNevd9B6EyNhWoNvnCZZ3WbgN4j7ZT+jvtvBCGZvI2XHsWZJ+BRIg==} + engines: {node: '>= 14.0.0'} + + '@aws-sdk/nested-clients@3.997.6': + resolution: {integrity: sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/region-config-resolver@3.972.13': + resolution: {integrity: sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.25': + resolution: {integrity: sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1041.0': + resolution: {integrity: sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1045.0': + resolution: {integrity: sha512-/o4qcty0DmQola0DBniRVeBakYY6ALOvKEFo1AtJpTmMn/cJ+Fk3RWGe5ieT/f/eYbHG9k5E7poKge/E+WGv4Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.8': + resolution: {integrity: sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-arn-parser@3.972.3': + resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-endpoints@3.996.8': + resolution: {integrity: sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-format-url@3.972.10': + resolution: {integrity: sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-user-agent-browser@3.972.10': + resolution: {integrity: sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==} + + '@aws-sdk/util-user-agent-node@3.973.24': + resolution: {integrity: sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.972.22': + resolution: {integrity: sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -137,6 +305,15 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@braintrust/core@0.0.87': + resolution: {integrity: sha512-yKo+2McKBcluVUq+5qoYI7QfGvqZ7c0ftTOmnRSToBR2RqGyHkClnnQZ3+M8Guuk9NEKJu92UMTTaR9AonIvvA==} + + '@braintrust/proxy@0.0.9': + resolution: {integrity: sha512-MIYznqHlb6eduHLg7tJHYUFcku4Wj1rLfyrCf+Uuel8vYowitOdCl8G6nR/NJl/g0xPGCtUOBkZQcJFJkQ2y0g==} + + '@breezystack/lamejs@1.2.7': + resolution: {integrity: sha512-6wc7ck65ctA75Hq7FYHTtTvGnYs6msgdxiSUICQ+A01nVOWg6rqouZB8IdyteRlfpYYiFovkf67dIeOgWIUzTA==} + '@clack/core@1.3.0': resolution: {integrity: sha512-xJPHpAmEQUBrXSLx0gF+q5K/IyihXpsHZcha+jB+tyahsKRK3Dxo4D0coZDewHo12NhiuzC3dTtMPbm53GEAAA==} engines: {node: '>= 20.12.0'} @@ -193,6 +370,10 @@ packages: resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@google/genai@0.13.0': + resolution: {integrity: sha512-eaEncWt875H7046T04mOpxpHJUM+jLIljEf+5QctRyOeChylE/nhpwm1bZWTRWoOu/t46R9r+PmgsJFhTpE7tQ==} + engines: {node: '>=18.0.0'} + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -229,12 +410,44 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@nodable/entities@2.1.0': + resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/core@1.30.1': + resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@1.30.1': + resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-metrics@1.30.1': + resolution: {integrity: sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.28.0': + resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} + engines: {node: '>=14'} + '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} @@ -437,6 +650,166 @@ packages: '@rolldown/pluginutils@1.0.0-rc.18': resolution: {integrity: sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==} + '@smithy/config-resolver@4.5.1': + resolution: {integrity: sha512-abXk3LhODsvRHsk0ZS9ztrg/fZatTa9Z/z4pgx65YSLR+rY6kvUG/1IgcDKEUciR8MfdnkT5oPeHJTy/HhzDIQ==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.24.1': + resolution: {integrity: sha512-3mT7o4qQyUWttYnVK3A0Z/u3Xha3E81tXn32Tz6vjZiUXhBrkEivpw1hBYfh84iFF9CSzkBU9Y1DJ3Q6RQ231g==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.3.1': + resolution: {integrity: sha512-0S/acwHnqX4WrjXzhdiDRxsG2s9SC0cpPIK9nZ1R6UOHd+j7uL28+4bHu22urbLk2TVw3fkp6na/+fkUt/pLNQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-codec@4.3.1': + resolution: {integrity: sha512-yS8AiJM3Kf7LR+lZyUilUyjdJGksAqxfSC3C9k3d1OCrAvWjpMlsJ+rW9cIslZJM4AtWh2UAqgZUWTtMeMdtDQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-browser@4.3.1': + resolution: {integrity: sha512-X7MyI1fu8M84IPKk49kO4kb27Mqp6un9/0o/MsA1ngZ5OxxWKGUxPS3S/AJ9q1cPVTSGmRcbaGNfGUSsflTJkg==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-config-resolver@4.4.1': + resolution: {integrity: sha512-JZGbSXaBk7JY8VPzsh66ksJ0nTWXbApduFDkA/pEl3aTm2EoAiUZE1Iltp6c+X1bB8kxPQW0mHDfVdYCpWTOzg==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-node@4.3.1': + resolution: {integrity: sha512-6Cn4xTNVxn9PWTHSbvf8zmcDhQW8lrLE1Xq5CJgmX6wEvdjS2S0KuE79Aiznv/jx51jpFJ98OuWyE+Bt+oG1MQ==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.4.1': + resolution: {integrity: sha512-r7bN6spQ+caZC8AnyvSxkRUb57zt2jhhRw3Z+2Ez8hjq6coIikDBFUUI/+CQ1xx9K6eX1Gx6wUKo4ylU66TIqw==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.3.1': + resolution: {integrity: sha512-u0/zo11mg7yNneoYgTkH4sXwSmcBpbl49o4UNCtQ7hYsXxynsN25KYHmXzqi7TPk5HQL5klGnpU5koOY0O+9hw==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.3.1': + resolution: {integrity: sha512-cLmwtDoulyZvRepAfyV+3rx5oMvuh51dbE+6En3vGC09j3uVSRt1U4oguNu32ub3soGX0oYtBs8E7S2Q4SxTqg==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/middleware-content-length@4.3.1': + resolution: {integrity: sha512-l4BUIP+wljW/Ar+0/QcGdmElI9lalrywfzNijXMBG34Z510FRzPyrDLx/blNTZOAm0C4Mvx5t/bf760CZo1ajg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.5.1': + resolution: {integrity: sha512-qtqu5TS+8Y18ZDkJoiXN5AMW1G4JAg1+xytzpsUvIR5a4EUsgd5HQg12lekEHWpm2TDUmOgg+hBaHK7dvyWdkA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.6.1': + resolution: {integrity: sha512-eTaQhxs0rfUuAkL2MSKrH8DTO7YCeAgrdN0B2/RAeuHmXQ+x52dk5qUBsi/jtcqe5LxItgq5AG5tI6Cp8c0sow==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.3.1': + resolution: {integrity: sha512-t7YtUe076zWVypVmy1rX91oKi2TFJCkpfFpfMhJFpEIRPP0iL9JxjeSyFQ+1bF45JUfDzOzslUJa150WcSrBug==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.3.1': + resolution: {integrity: sha512-1jKwiKZxCMQNqmp4uVPYA6r+MLGjEtH07gnOUdPgbnjuOIrl/0JY/ICdpQtFgeBsQ/Up01gnSv8GYEL0fb8yvg==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.4.1': + resolution: {integrity: sha512-q7tDJEJXcaSG/8TVpu2f2l9bzxTzDM9geWmltbzsY6Hfh3yiuXXTpLIO8+zwYASPPVFaTJpdKwjSSjdoDoccgw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.7.1': + resolution: {integrity: sha512-BdEYko85f/ldp68uH8XEyIvo810xFk6eyPH81SRggTOApYHWA+Xu7B2EzLuHbe37WVLaUA7F1fWR3/zBeme2WA==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.3.1': + resolution: {integrity: sha512-3NHoqVBhzpY2b4YBx9AqyKC4C8nnEjl5FyKuxrCjvnjinG0ODj+yg1xX360nNahT6wghYjSw1SooCt3kIdnqIA==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.4.1': + resolution: {integrity: sha512-8irPNCQgYxcSFp1aGcnDNFkTwSA+xPUaFq9V/v1+JXWu8sKr5b3cFmg2kBTkjkvypDmGeNffuNu0x5iqw1NoAw==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.3.1': + resolution: {integrity: sha512-toyi8sXPWDNoVH6yK7sXJ9dm5uxw2tWLCHzPy/t16Fvl62Es4vXQXzlilyNaw+DqFwxSlrFClh0rGLPUF2p9Lg==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.5.1': + resolution: {integrity: sha512-FKoKxVzdFPhyynFI+SPTWrgOP60fZ4l1UwukWYj4eyhpSmEI7MJ6p58hawIIt9bwp+aek9NEm8Zika7E+GEoeg==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.4.1': + resolution: {integrity: sha512-728lZZEWYWubBESrfntNslZQYDKRlJDY4dcDnYbL50+gu35pGPLblu4S0/RH/RDLF6me1M87ECHsHELGL7dA/Q==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.13.1': + resolution: {integrity: sha512-IcznNM8Qd9u1X3oflp12tkzyOB4HbT+sfYWlWiyEysgNzSHoWcHUUsTT4y1jjDjtVuuVVQbYks+g1kVd7u1eGQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.14.1': + resolution: {integrity: sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.3.1': + resolution: {integrity: sha512-tuelFlF2PZR/wogFC58NIrPOv+Zna4N1+3kA161/33D1Gbwvl6Nh4WsAsW05ZyPp0O6CMGsdbb0S2b/qVjRMCw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.4.1': + resolution: {integrity: sha512-fTHiwW2xbiRiWzfSk4IGAr3gNZCH4fuRYqt8+IuarsP/YON35576iVdePraZ6yJlFxlCL0eMec3/F7xYqoKzlg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.3.1': + resolution: {integrity: sha512-1scg5t4nV3hV7CZs996/XHb80aDZ5YotH4NcvkW/w/rHj+cSz0aCIzwz8aUNKB4nCDPSHRCbrKoj+TvycYefmw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.3.1': + resolution: {integrity: sha512-VRC8MKVPKrgUYThTA7ughcKMfjW6/X92H0wXGJoda0Apw4O5xbXL0GMLz40DTWlsb5hh2iItk6+XL72uJdxYcw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-config-provider@4.3.1': + resolution: {integrity: sha512-lw6L5GF5+W19vO6o3fZwRT2cXEG+8b2LH0b9ppjDT6nIxjUgmljEQGninx5XorylwKZZ4XLVABeroJ8oaF9RmQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.4.1': + resolution: {integrity: sha512-1rA7w+LjK1WJClsffC81Z/ZtjFt22QsKhBjUYEnZsGVS2nOTfOENKBzdg4SxhdwFvBCjcbpjscUfXOPwE3UHWQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.3.1': + resolution: {integrity: sha512-1fk1wfQHBenQD5NitVKOFgW0wsISYAFPIXGyStJWAeCtMyRhgHYvtJxBk2rwGWA0L5QX6oM6yeHSLKPFMk59ww==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.5.1': + resolution: {integrity: sha512-yORYzJD5zoGbSDkAACr0dIjDiSEA3X8h8lggDENl1dkKpCG0TQIoItPBqtvuJHzFFjRXumcoH+/09xIuixGyCw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.3.1': + resolution: {integrity: sha512-j6dAIaXfj2nsvv/sN9+fi7e/AJxBHgBoIdNjmQjp9jlii72rEniUGQkipnkHMP2XUKHx5q0B1iv0xQEG1AsLBA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.3.1': + resolution: {integrity: sha512-SRRMDcIgVXVhVbxviBaSZbuWuVW3jD08wv4ESV0V2oiw0Mki8TPVQ5IxwD3MvSTPg52QYsRP+JoMw5WdUdeWAg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.4.1': + resolution: {integrity: sha512-qkgWgwn1xw0GoY9Ea/B6FrYSPfHA0zyOtJkokwxZuvucRf2+2lfTut6adi4e4Y7LEAaxsFG7r6i05mtDCxbHKA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.6.1': + resolution: {integrity: sha512-GjZfEft0M0V3n2YM/LGkr5LeLd8gxHUIzW0rUz6VtTtlAq245GxHlJghvoPEjJHKTj255iHFAiA4IsIdK40Ueg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.3.1': + resolution: {integrity: sha512-FtRrSnriXtOs4+J8/y9SbQ1xmN71hrOsN/YJr5PQQj5nR1l7YNkGS/TEk4gr0WN7gyrUqw8/RFaYVjI18732ZA==} + engines: {node: '>=18.0.0'} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -466,6 +839,12 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/node@24.12.2': resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} @@ -560,6 +939,39 @@ packages: '@vitest/utils@4.1.5': resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + '@vue/compiler-core@3.5.34': + resolution: {integrity: sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==} + + '@vue/compiler-dom@3.5.34': + resolution: {integrity: sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==} + + '@vue/compiler-sfc@3.5.34': + resolution: {integrity: sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==} + + '@vue/compiler-ssr@3.5.34': + resolution: {integrity: sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==} + + '@vue/reactivity@3.5.34': + resolution: {integrity: sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==} + + '@vue/runtime-core@3.5.34': + resolution: {integrity: sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==} + + '@vue/runtime-dom@3.5.34': + resolution: {integrity: sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==} + + '@vue/server-renderer@3.5.34': + resolution: {integrity: sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==} + peerDependencies: + vue: 3.5.34 + + '@vue/shared@3.5.34': + resolution: {integrity: sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -570,6 +982,32 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + ai@2.2.37: + resolution: {integrity: sha512-JIYm5N1muGVqBqWnvkt29FmXhESoO5TcDxw74OE41SsM+uIou6NPDDs0XWb/ABcd1gmp6k5zym64KWMPM2xm0A==} + engines: {node: '>=14.6'} + peerDependencies: + react: ^18.2.0 + solid-js: ^1.7.7 + svelte: ^3.0.0 || ^4.0.0 + vue: ^3.3.4 + peerDependenciesMeta: + react: + optional: true + solid-js: + optional: true + svelte: + optional: true + vue: + optional: true + ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} @@ -585,6 +1023,13 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -621,6 +1066,9 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + auto-bind@5.0.1: resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -629,6 +1077,10 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -636,11 +1088,20 @@ packages: 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.10.27: resolution: {integrity: sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==} engines: {node: '>=6.0.0'} hasBin: true + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + brace-expansion@1.1.14: resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} @@ -653,6 +1114,12 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + cache-control-parser@2.2.0: + resolution: {integrity: sha512-Me01OJfiZiyGT42qApiunD9fKiE8WMk9w+zhu1LZlRHuDGW1gvy/IA+GcdJbWXVhZbQkn6NmZKomVaO7BFdl1g==} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -692,9 +1159,20 @@ packages: resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + code-red@1.0.4: + resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -706,6 +1184,10 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -721,6 +1203,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -741,6 +1226,14 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -753,9 +1246,16 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + electron-to-chromium@1.5.349: resolution: {integrity: sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==} + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -866,6 +1366,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -873,10 +1376,25 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + eventsource-parser@1.0.0: + resolution: {integrity: sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g==} + engines: {node: '>=14.18'} + + eventsource-parser@1.1.2: + resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==} + engines: {node: '>=14.18'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -895,6 +1413,13 @@ packages: fast-wrap-ansi@0.2.0: resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} + fast-xml-builder@1.2.0: + resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} + + fast-xml-parser@5.7.2: + resolution: {integrity: sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==} + hasBin: true + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -923,6 +1448,17 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -938,6 +1474,14 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gaxios@6.7.1: + resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} + engines: {node: '>=14'} + + gcp-metadata@6.1.1: + resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==} + engines: {node: '>=14'} + generator-function@2.0.1: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} @@ -974,10 +1518,22 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} + google-auth-library@9.15.1: + resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} + engines: {node: '>=14'} + + google-logging-utils@0.0.2: + resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} + engines: {node: '>=14'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + gtoken@7.1.0: + resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} + engines: {node: '>=14.0.0'} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -1007,6 +1563,13 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1109,6 +1672,9 @@ packages: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -1121,6 +1687,10 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -1155,14 +1725,24 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} + jose@5.10.0: + resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -1177,10 +1757,20 @@ packages: engines: {node: '>=6'} hasBin: true + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1262,10 +1852,34 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1280,6 +1894,17 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -1299,13 +1924,32 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + node-exports-info@1.6.0: resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} engines: {node: '>= 0.4'} + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-releases@2.0.38: resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} @@ -1344,6 +1988,21 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + openai@4.89.0: + resolution: {integrity: sha512-XNI0q2l8/Os6jmojxaID5EhyQjxZgzR2gWcpEjYWK5hGKwE7AcifxEY7UNwFDDHJQXqeiosQ0CJwQN+rvnwdjA==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + + openapi3-ts@4.5.0: + resolution: {integrity: sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1368,6 +2027,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} + engines: {node: '>=14.0.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1378,6 +2041,9 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1456,6 +2122,9 @@ packages: resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -1476,6 +2145,16 @@ packages: engines: {node: '>=10'} hasBin: true + seroval-plugins@1.5.4: + resolution: {integrity: sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.5.4: + resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==} + engines: {node: '>=10'} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -1529,10 +2208,25 @@ packages: resolution: {integrity: sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==} engines: {node: '>=22'} + solid-js@1.9.12: + resolution: {integrity: sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==} + + solid-swr-store@0.10.7: + resolution: {integrity: sha512-A6d68aJmRP471aWqKKPE2tpgOiR5fH4qXQNfKIec+Vap+MGQm3tvXlT8n0I8UgJSlNAsSAUuw2VTviH2h3Vv5g==} + engines: {node: '>=10'} + peerDependencies: + solid-js: ^1.2 + swr-store: ^0.10 + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + sswr@2.0.0: + resolution: {integrity: sha512-mV0kkeBHcjcb0M5NqKtKVg/uTIYNlIIniyDfSGrSfxpEdM9C365jK0z55pl9K0xAkNTJi2OAOVFQpgMPUk+V0w==} + peerDependencies: + svelte: ^4.0.0 + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -1574,10 +2268,34 @@ packages: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} + strnum@2.3.0: + resolution: {integrity: sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svelte@4.2.20: + resolution: {integrity: sha512-eeEgGc2DtiUil5ANdtd8vPwt9AgaMdnuUFnPft9F5oMvU/FHu5IHFic+p1dR/UOB7XU2mX2yHW+NcTch4DCh5Q==} + engines: {node: '>=16'} + + swr-store@0.10.6: + resolution: {integrity: sha512-xPjB1hARSiRaNNlUQvWSVrG5SirCjk2TmaUyzzvk69SZQan9hCJqw/5rG9iL7xElHU784GxRPISClq4488/XVw==} + engines: {node: '>=10'} + + swr@2.2.0: + resolution: {integrity: sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + + swrev@4.0.0: + resolution: {integrity: sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==} + + swrv@1.0.4: + resolution: {integrity: sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==} + peerDependencies: + vue: '>=3.2.26 < 4' + tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} @@ -1601,6 +2319,9 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -1650,6 +2371,9 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -1662,6 +2386,16 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + hasBin: true + vite@8.0.10: resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1746,6 +2480,24 @@ packages: jsdom: optional: true + vue@3.5.34: + resolution: {integrity: sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -1808,9 +2560,18 @@ packages: utf-8-validate: optional: true + xml-naming@0.1.0: + resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} + engines: {node: '>=16.0.0'} + 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 + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1818,12 +2579,20 @@ packages: yoga-layout@3.2.1: resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + zod-validation-error@4.0.2: resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} engines: {node: '>=18.0.0'} peerDependencies: zod: ^3.25.0 || ^4.0.0 + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} @@ -1834,6 +2603,455 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@anthropic-ai/sdk@0.39.0': + dependencies: + '@types/node': 18.19.130 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + '@apidevtools/json-schema-ref-parser@11.9.3': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.1 + + '@asteasolutions/zod-to-openapi@6.4.0(zod@3.25.76)': + dependencies: + openapi3-ts: 4.5.0 + zod: 3.25.76 + + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.8 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.8 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-bedrock-runtime@3.1045.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/credential-provider-node': 3.972.39 + '@aws-sdk/eventstream-handler-node': 3.972.14 + '@aws-sdk/middleware-eventstream': 3.972.10 + '@aws-sdk/middleware-host-header': 3.972.10 + '@aws-sdk/middleware-logger': 3.972.10 + '@aws-sdk/middleware-recursion-detection': 3.972.11 + '@aws-sdk/middleware-user-agent': 3.972.38 + '@aws-sdk/middleware-websocket': 3.972.16 + '@aws-sdk/region-config-resolver': 3.972.13 + '@aws-sdk/token-providers': 3.1045.0 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.8 + '@aws-sdk/util-user-agent-browser': 3.972.10 + '@aws-sdk/util-user-agent-node': 3.973.24 + '@smithy/config-resolver': 4.5.1 + '@smithy/core': 3.24.1 + '@smithy/eventstream-serde-browser': 4.3.1 + '@smithy/eventstream-serde-config-resolver': 4.4.1 + '@smithy/eventstream-serde-node': 4.3.1 + '@smithy/fetch-http-handler': 5.4.1 + '@smithy/hash-node': 4.3.1 + '@smithy/invalid-dependency': 4.3.1 + '@smithy/middleware-content-length': 4.3.1 + '@smithy/middleware-endpoint': 4.5.1 + '@smithy/middleware-retry': 4.6.1 + '@smithy/middleware-serde': 4.3.1 + '@smithy/middleware-stack': 4.3.1 + '@smithy/node-config-provider': 4.4.1 + '@smithy/node-http-handler': 4.7.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/smithy-client': 4.13.1 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.3.1 + '@smithy/util-base64': 4.4.1 + '@smithy/util-body-length-browser': 4.3.1 + '@smithy/util-body-length-node': 4.3.1 + '@smithy/util-defaults-mode-browser': 4.4.1 + '@smithy/util-defaults-mode-node': 4.3.1 + '@smithy/util-endpoints': 3.5.1 + '@smithy/util-middleware': 4.3.1 + '@smithy/util-retry': 4.4.1 + '@smithy/util-stream': 4.6.1 + '@smithy/util-utf8': 4.3.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.974.8': + dependencies: + '@aws-sdk/types': 3.973.8 + '@aws-sdk/xml-builder': 3.972.22 + '@smithy/core': 3.24.1 + '@smithy/node-config-provider': 4.4.1 + '@smithy/property-provider': 4.3.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/signature-v4': 5.4.1 + '@smithy/smithy-client': 4.13.1 + '@smithy/types': 4.14.1 + '@smithy/util-base64': 4.4.1 + '@smithy/util-middleware': 4.3.1 + '@smithy/util-retry': 4.4.1 + '@smithy/util-utf8': 4.3.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.34': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.36': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/types': 3.973.8 + '@smithy/fetch-http-handler': 5.4.1 + '@smithy/node-http-handler': 4.7.1 + '@smithy/property-provider': 4.3.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/smithy-client': 4.13.1 + '@smithy/types': 4.14.1 + '@smithy/util-stream': 4.6.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.38': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/credential-provider-env': 3.972.34 + '@aws-sdk/credential-provider-http': 3.972.36 + '@aws-sdk/credential-provider-login': 3.972.38 + '@aws-sdk/credential-provider-process': 3.972.34 + '@aws-sdk/credential-provider-sso': 3.972.38 + '@aws-sdk/credential-provider-web-identity': 3.972.38 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/types': 3.973.8 + '@smithy/credential-provider-imds': 4.3.1 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.972.38': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.972.39': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.34 + '@aws-sdk/credential-provider-http': 3.972.36 + '@aws-sdk/credential-provider-ini': 3.972.38 + '@aws-sdk/credential-provider-process': 3.972.34 + '@aws-sdk/credential-provider-sso': 3.972.38 + '@aws-sdk/credential-provider-web-identity': 3.972.38 + '@aws-sdk/types': 3.973.8 + '@smithy/credential-provider-imds': 4.3.1 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.972.34': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.38': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/token-providers': 3.1041.0 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.972.38': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/eventstream-handler-node@3.972.14': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/eventstream-codec': 4.3.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-eventstream@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/protocol-http': 5.4.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/protocol-http': 5.4.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.972.11': + dependencies: + '@aws-sdk/types': 3.973.8 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/protocol-http': 5.4.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.37': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/core': 3.24.1 + '@smithy/node-config-provider': 4.4.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/signature-v4': 5.4.1 + '@smithy/smithy-client': 4.13.1 + '@smithy/types': 4.14.1 + '@smithy/util-config-provider': 4.3.1 + '@smithy/util-middleware': 4.3.1 + '@smithy/util-stream': 4.6.1 + '@smithy/util-utf8': 4.3.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.972.38': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.8 + '@smithy/core': 3.24.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/types': 4.14.1 + '@smithy/util-retry': 4.4.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-websocket@3.972.16': + dependencies: + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-format-url': 3.972.10 + '@smithy/eventstream-codec': 4.3.1 + '@smithy/eventstream-serde-browser': 4.3.1 + '@smithy/fetch-http-handler': 5.4.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/signature-v4': 5.4.1 + '@smithy/types': 4.14.1 + '@smithy/util-base64': 4.4.1 + '@smithy/util-hex-encoding': 4.3.1 + '@smithy/util-utf8': 4.3.1 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.6': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/middleware-host-header': 3.972.10 + '@aws-sdk/middleware-logger': 3.972.10 + '@aws-sdk/middleware-recursion-detection': 3.972.11 + '@aws-sdk/middleware-user-agent': 3.972.38 + '@aws-sdk/region-config-resolver': 3.972.13 + '@aws-sdk/signature-v4-multi-region': 3.996.25 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.8 + '@aws-sdk/util-user-agent-browser': 3.972.10 + '@aws-sdk/util-user-agent-node': 3.973.24 + '@smithy/config-resolver': 4.5.1 + '@smithy/core': 3.24.1 + '@smithy/fetch-http-handler': 5.4.1 + '@smithy/hash-node': 4.3.1 + '@smithy/invalid-dependency': 4.3.1 + '@smithy/middleware-content-length': 4.3.1 + '@smithy/middleware-endpoint': 4.5.1 + '@smithy/middleware-retry': 4.6.1 + '@smithy/middleware-serde': 4.3.1 + '@smithy/middleware-stack': 4.3.1 + '@smithy/node-config-provider': 4.4.1 + '@smithy/node-http-handler': 4.7.1 + '@smithy/protocol-http': 5.4.1 + '@smithy/smithy-client': 4.13.1 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.3.1 + '@smithy/util-base64': 4.4.1 + '@smithy/util-body-length-browser': 4.3.1 + '@smithy/util-body-length-node': 4.3.1 + '@smithy/util-defaults-mode-browser': 4.4.1 + '@smithy/util-defaults-mode-node': 4.3.1 + '@smithy/util-endpoints': 3.5.1 + '@smithy/util-middleware': 4.3.1 + '@smithy/util-retry': 4.4.1 + '@smithy/util-utf8': 4.3.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.972.13': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/config-resolver': 4.5.1 + '@smithy/node-config-provider': 4.4.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.25': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.972.37 + '@aws-sdk/types': 3.973.8 + '@smithy/protocol-http': 5.4.1 + '@smithy/signature-v4': 5.4.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1041.0': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/token-providers@3.1045.0': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.3.1 + '@smithy/shared-ini-file-loader': 4.5.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.973.8': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.972.3': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.996.8': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.3.1 + '@smithy/util-endpoints': 3.5.1 + tslib: 2.8.1 + + '@aws-sdk/util-format-url@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/querystring-builder': 4.3.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.965.5': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.973.24': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.38 + '@aws-sdk/types': 3.973.8 + '@smithy/node-config-provider': 4.4.1 + '@smithy/types': 4.14.1 + '@smithy/util-config-provider': 4.3.1 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.22': + dependencies: + '@nodable/entities': 2.1.0 + '@smithy/types': 4.14.1 + fast-xml-parser: 5.7.2 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -1934,6 +3152,48 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@braintrust/core@0.0.87': + dependencies: + '@asteasolutions/zod-to-openapi': 6.4.0(zod@3.25.76) + uuid: 9.0.1 + zod: 3.25.76 + + '@braintrust/proxy@0.0.9(react@19.2.5)(solid-js@1.9.12)(svelte@4.2.20)(vue@3.5.34(typescript@6.0.3))(ws@8.20.0)': + dependencies: + '@anthropic-ai/sdk': 0.39.0 + '@apidevtools/json-schema-ref-parser': 11.9.3 + '@aws-sdk/client-bedrock-runtime': 3.1045.0 + '@braintrust/core': 0.0.87 + '@breezystack/lamejs': 1.2.7 + '@google/genai': 0.13.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 1.30.1(@opentelemetry/api@1.9.1) + ai: 2.2.37(react@19.2.5)(solid-js@1.9.12)(svelte@4.2.20)(vue@3.5.34(typescript@6.0.3)) + cache-control-parser: 2.2.0 + content-disposition: 0.5.4 + date-fns: 4.1.0 + eventsource-parser: 1.1.2 + jose: 5.10.0 + jsonwebtoken: 9.0.3 + openai: 4.89.0(ws@8.20.0)(zod@3.25.76) + uuid: 9.0.1 + zod: 3.25.76 + transitivePeerDependencies: + - aws-crt + - bufferutil + - encoding + - react + - solid-js + - supports-color + - svelte + - utf-8-validate + - vue + - ws + + '@breezystack/lamejs@1.2.7': {} + '@clack/core@1.3.0': dependencies: fast-wrap-ansi: 0.2.0 @@ -1996,6 +3256,18 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 + '@google/genai@0.13.0': + dependencies: + google-auth-library: 9.15.1 + ws: 8.20.0 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -2031,6 +3303,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@jsdevtools/ono@7.1.3': {} + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -2038,6 +3312,29 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true + '@nodable/entities@2.1.0': {} + + '@opentelemetry/api@1.9.1': {} + + '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/sdk-metrics@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/semantic-conventions@1.28.0': {} + '@oxc-project/types@0.127.0': {} '@oxc-project/types@0.128.0': {} @@ -2099,50 +3396,254 @@ snapshots: '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.18': - optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.18': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.18': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.18': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.18': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.18': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.18': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.17': {} + + '@rolldown/pluginutils@1.0.0-rc.18': {} + + '@smithy/config-resolver@4.5.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/core@3.24.1': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/eventstream-codec@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@4.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-node@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.4.1': + dependencies: + '@smithy/core': 3.24.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/hash-node@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.5.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.6.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/middleware-stack@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/node-config-provider@4.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.7.1': + dependencies: + '@smithy/core': 3.24.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/property-provider@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/protocol-http@5.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/querystring-builder@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/shared-ini-file-loader@4.5.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/signature-v4@5.4.1': + dependencies: + '@smithy/core': 3.24.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/smithy-client@4.13.1': + dependencies: + '@smithy/core': 3.24.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/types@4.14.1': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-base64@4.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 - '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': - optional: true + '@smithy/util-body-length-node@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 - '@rolldown/binding-linux-x64-musl@1.0.0-rc.18': - optional: true + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 - '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': - optional: true + '@smithy/util-config-provider@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 - '@rolldown/binding-openharmony-arm64@1.0.0-rc.18': - optional: true + '@smithy/util-defaults-mode-browser@4.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 - '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + '@smithy/util-defaults-mode-node@4.3.1': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - optional: true + '@smithy/core': 3.24.1 + tslib: 2.8.1 - '@rolldown/binding-wasm32-wasi@1.0.0-rc.18': + '@smithy/util-endpoints@3.5.1': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - optional: true + '@smithy/core': 3.24.1 + tslib: 2.8.1 - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': - optional: true + '@smithy/util-hex-encoding@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.18': - optional: true + '@smithy/util-middleware@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': - optional: true + '@smithy/util-retry@4.4.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.18': - optional: true + '@smithy/util-stream@4.6.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 - '@rolldown/pluginutils@1.0.0-rc.17': {} + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 - '@rolldown/pluginutils@1.0.0-rc.18': {} + '@smithy/util-utf8@4.3.1': + dependencies: + '@smithy/core': 3.24.1 + tslib: 2.8.1 '@standard-schema/spec@1.1.0': {} @@ -2171,6 +3672,15 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 24.12.2 + form-data: 4.0.5 + + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + '@types/node@24.12.2': dependencies: undici-types: 7.16.0 @@ -2279,13 +3789,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@24.12.2))': + '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@24.12.2)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.5 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.10(@types/node@24.12.2) + vite: 8.0.10(@types/node@24.12.2)(yaml@2.9.0) '@vitest/pretty-format@4.1.5': dependencies: @@ -2311,12 +3821,91 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@vue/compiler-core@3.5.34': + dependencies: + '@babel/parser': 7.29.3 + '@vue/shared': 3.5.34 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.34': + dependencies: + '@vue/compiler-core': 3.5.34 + '@vue/shared': 3.5.34 + + '@vue/compiler-sfc@3.5.34': + dependencies: + '@babel/parser': 7.29.3 + '@vue/compiler-core': 3.5.34 + '@vue/compiler-dom': 3.5.34 + '@vue/compiler-ssr': 3.5.34 + '@vue/shared': 3.5.34 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.14 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.34': + dependencies: + '@vue/compiler-dom': 3.5.34 + '@vue/shared': 3.5.34 + + '@vue/reactivity@3.5.34': + dependencies: + '@vue/shared': 3.5.34 + + '@vue/runtime-core@3.5.34': + dependencies: + '@vue/reactivity': 3.5.34 + '@vue/shared': 3.5.34 + + '@vue/runtime-dom@3.5.34': + dependencies: + '@vue/reactivity': 3.5.34 + '@vue/runtime-core': 3.5.34 + '@vue/shared': 3.5.34 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.34(vue@3.5.34(typescript@6.0.3))': + dependencies: + '@vue/compiler-ssr': 3.5.34 + '@vue/shared': 3.5.34 + vue: 3.5.34(typescript@6.0.3) + + '@vue/shared@3.5.34': {} + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 acorn@8.16.0: {} + agent-base@7.1.4: {} + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + ai@2.2.37(react@19.2.5)(solid-js@1.9.12)(svelte@4.2.20)(vue@3.5.34(typescript@6.0.3)): + dependencies: + eventsource-parser: 1.0.0 + nanoid: 3.3.6 + solid-swr-store: 0.10.7(solid-js@1.9.12)(swr-store@0.10.6) + sswr: 2.0.0(svelte@4.2.20) + swr: 2.2.0(react@19.2.5) + swr-store: 0.10.6 + swrv: 1.0.4(vue@3.5.34(typescript@6.0.3)) + optionalDependencies: + react: 19.2.5 + solid-js: 1.9.12 + svelte: 4.2.20 + vue: 3.5.34(typescript@6.0.3) + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -2332,6 +3921,10 @@ snapshots: ansi-styles@6.2.3: {} + argparse@2.0.1: {} + + aria-query@5.3.2: {} + array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -2393,18 +3986,28 @@ snapshots: async-function@1.0.0: {} + asynckit@0.4.0: {} + auto-bind@5.0.1: {} available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 + axobject-query@4.1.0: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} + base64-js@1.5.1: {} + baseline-browser-mapping@2.10.27: {} + bignumber.js@9.3.1: {} + + bowser@2.14.1: {} + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 @@ -2422,6 +4025,10 @@ snapshots: node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) + buffer-equal-constant-time@1.0.1: {} + + cache-control-parser@2.2.0: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -2460,8 +4067,24 @@ snapshots: dependencies: convert-to-spaces: 2.0.1 + code-red@1.0.4: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@types/estree': 1.0.8 + acorn: 8.16.0 + estree-walker: 3.0.3 + periscopic: 3.1.0 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + concat-map@0.0.1: {} + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + convert-source-map@2.0.0: {} convert-to-spaces@2.0.1: {} @@ -2472,6 +4095,11 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@2.3.1: + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.1 + csstype@3.2.3: {} data-view-buffer@1.0.2: @@ -2492,6 +4120,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + date-fns@4.1.0: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -2510,6 +4140,10 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + detect-libc@2.1.2: {} doctrine@2.1.0: @@ -2522,8 +4156,14 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + electron-to-chromium@1.5.349: {} + entities@7.0.1: {} + environment@1.1.0: {} es-abstract@1.24.2: @@ -2736,14 +4376,24 @@ snapshots: estraverse@5.3.0: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 esutils@2.0.3: {} + event-target-shim@5.0.1: {} + + eventsource-parser@1.0.0: {} + + eventsource-parser@1.1.2: {} + expect-type@1.3.0: {} + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -2760,6 +4410,18 @@ snapshots: dependencies: fast-string-width: 3.0.2 + fast-xml-builder@1.2.0: + dependencies: + path-expression-matcher: 1.5.0 + xml-naming: 0.1.0 + + fast-xml-parser@5.7.2: + dependencies: + '@nodable/entities': 2.1.0 + fast-xml-builder: 1.2.0 + path-expression-matcher: 1.5.0 + strnum: 2.3.0 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -2784,6 +4446,21 @@ snapshots: dependencies: is-callable: 1.2.7 + form-data-encoder@1.7.2: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.3 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + fsevents@2.3.3: optional: true @@ -2800,6 +4477,26 @@ snapshots: functions-have-names@1.2.3: {} + gaxios@6.7.1: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + is-stream: 2.0.1 + node-fetch: 2.7.0 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + + gcp-metadata@6.1.1: + dependencies: + gaxios: 6.7.1 + google-logging-utils: 0.0.2 + json-bigint: 1.0.0 + transitivePeerDependencies: + - encoding + - supports-color + generator-function@2.0.1: {} gensync@1.0.0-beta.2: {} @@ -2841,8 +4538,30 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 + google-auth-library@9.15.1: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 6.7.1 + gcp-metadata: 6.1.1 + gtoken: 7.1.0 + jws: 4.0.1 + transitivePeerDependencies: + - encoding + - supports-color + + google-logging-utils@0.0.2: {} + gopd@1.2.0: {} + gtoken@7.1.0: + dependencies: + gaxios: 6.7.1 + jws: 4.0.1 + transitivePeerDependencies: + - encoding + - supports-color + has-bigints@1.1.0: {} has-property-descriptors@1.0.2: @@ -2869,6 +4588,17 @@ snapshots: dependencies: hermes-estree: 0.25.1 + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -2991,6 +4721,10 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -3004,6 +4738,8 @@ snapshots: dependencies: call-bound: 1.0.4 + is-stream@2.0.1: {} + is-string@1.1.1: dependencies: call-bound: 1.0.4 @@ -3043,10 +4779,20 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 + jose@5.10.0: {} + js-tokens@4.0.0: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + jsesc@3.1.0: {} + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -3055,6 +4801,19 @@ snapshots: json5@2.2.3: {} + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.4 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -3062,6 +4821,17 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -3120,10 +4890,26 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + locate-character@3.0.0: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.once@4.1.1: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -3138,6 +4924,14 @@ snapshots: math-intrinsics@1.1.0: {} + mdn-data@2.0.30: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mimic-fn@2.1.0: {} minimatch@10.2.5: @@ -3152,8 +4946,12 @@ snapshots: nanoid@3.3.12: {} + nanoid@3.3.6: {} + natural-compare@1.4.0: {} + node-domexception@1.0.0: {} + node-exports-info@1.6.0: dependencies: array.prototype.flatmap: 1.3.3 @@ -3161,6 +4959,10 @@ snapshots: object.entries: 1.1.9 semver: 6.3.1 + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-releases@2.0.38: {} object-assign@4.1.1: {} @@ -3205,6 +5007,25 @@ snapshots: dependencies: mimic-fn: 2.1.0 + openai@4.89.0(ws@8.20.0)(zod@3.25.76): + dependencies: + '@types/node': 18.19.130 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + optionalDependencies: + ws: 8.20.0 + zod: 3.25.76 + transitivePeerDependencies: + - encoding + + openapi3-ts@4.5.0: + dependencies: + yaml: 2.9.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3232,12 +5053,20 @@ snapshots: path-exists@4.0.0: {} + path-expression-matcher@1.5.0: {} + path-key@3.1.1: {} path-parse@1.0.7: {} pathe@2.0.3: {} + periscopic@3.1.0: + dependencies: + '@types/estree': 1.0.8 + estree-walker: 3.0.3 + is-reference: 3.0.3 + picocolors@1.1.1: {} picomatch@4.0.4: {} @@ -3363,6 +5192,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -3380,6 +5211,12 @@ snapshots: semver@7.7.4: {} + seroval-plugins@1.5.4(seroval@1.5.4): + dependencies: + seroval: 1.5.4 + + seroval@1.5.4: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -3449,8 +5286,24 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + solid-js@1.9.12: + dependencies: + csstype: 3.2.3 + seroval: 1.5.4 + seroval-plugins: 1.5.4(seroval@1.5.4) + + solid-swr-store@0.10.7(solid-js@1.9.12)(swr-store@0.10.6): + dependencies: + solid-js: 1.9.12 + swr-store: 0.10.6 + source-map-js@1.2.1: {} + sswr@2.0.0(svelte@4.2.20): + dependencies: + svelte: 4.2.20 + swrev: 4.0.0 + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -3517,8 +5370,42 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strnum@2.3.0: {} + supports-preserve-symlinks-flag@1.0.0: {} + svelte@4.2.20: + dependencies: + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + '@types/estree': 1.0.8 + acorn: 8.16.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + code-red: 1.0.4 + css-tree: 2.3.1 + estree-walker: 3.0.3 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.21 + periscopic: 3.1.0 + + swr-store@0.10.6: + dependencies: + dequal: 2.0.3 + + swr@2.2.0(react@19.2.5): + dependencies: + react: 19.2.5 + use-sync-external-store: 1.6.0(react@19.2.5) + + swrev@4.0.0: {} + + swrv@1.0.4(vue@3.5.34(typescript@6.0.3)): + dependencies: + vue: 3.5.34(typescript@6.0.3) + tagged-tag@1.0.0: {} terminal-size@4.0.1: {} @@ -3534,12 +5421,13 @@ snapshots: tinyrainbow@3.1.0: {} + tr46@0.0.3: {} + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 - tslib@2.8.1: - optional: true + tslib@2.8.1: {} type-check@0.4.0: dependencies: @@ -3602,6 +5490,8 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + undici-types@5.26.5: {} + undici-types@7.16.0: {} update-browserslist-db@1.2.3(browserslist@4.28.2): @@ -3614,7 +5504,13 @@ snapshots: dependencies: punycode: 2.3.1 - vite@8.0.10(@types/node@24.12.2): + use-sync-external-store@1.6.0(react@19.2.5): + dependencies: + react: 19.2.5 + + uuid@9.0.1: {} + + vite@8.0.10(@types/node@24.12.2)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -3624,11 +5520,12 @@ snapshots: optionalDependencies: '@types/node': 24.12.2 fsevents: 2.3.3 + yaml: 2.9.0 - vitest@4.1.5(@types/node@24.12.2)(vite@8.0.10(@types/node@24.12.2)): + vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(vite@8.0.10(@types/node@24.12.2)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@24.12.2)) + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@24.12.2)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.5 '@vitest/runner': 4.1.5 '@vitest/snapshot': 4.1.5 @@ -3645,13 +5542,33 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.10(@types/node@24.12.2) + vite: 8.0.10(@types/node@24.12.2)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: + '@opentelemetry/api': 1.9.1 '@types/node': 24.12.2 transitivePeerDependencies: - msw + vue@3.5.34(typescript@6.0.3): + dependencies: + '@vue/compiler-dom': 3.5.34 + '@vue/compiler-sfc': 3.5.34 + '@vue/runtime-dom': 3.5.34 + '@vue/server-renderer': 3.5.34(vue@3.5.34(typescript@6.0.3)) + '@vue/shared': 3.5.34 + optionalDependencies: + typescript: 6.0.3 + + web-streams-polyfill@4.0.0-beta.3: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -3718,14 +5635,24 @@ snapshots: ws@8.20.0: {} + xml-naming@0.1.0: {} + yallist@3.1.1: {} + yaml@2.9.0: {} + yocto-queue@0.1.0: {} yoga-layout@3.2.1: {} + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + zod-validation-error@4.0.2(zod@4.4.3): dependencies: zod: 4.4.3 + zod@3.25.76: {} + zod@4.4.3: {} diff --git a/src/braintrust-api.ts b/src/braintrust-api.ts new file mode 100644 index 0000000..93c6398 --- /dev/null +++ b/src/braintrust-api.ts @@ -0,0 +1,218 @@ +export type Org = { + readonly id: string; + readonly name: string; + readonly api_url?: string | null; + readonly proxy_url?: string | null; +}; + +export type Project = { + readonly id: string; + readonly name: string; + readonly org_id: string; +}; + +export type ApiKey = { + readonly id: string; + readonly name: string; + readonly preview_name?: string; +}; + +export type ApiKeyWithSecret = ApiKey & { + readonly key: string; +}; + +export type CurrentUser = { + readonly id: string; + readonly given_name?: string | null; + readonly family_name?: string | null; + readonly email?: string | null; +}; + +export type DataPlane = "us" | "eu"; + +const DATA_PLANE_API_URLS: Record = { + us: "https://api.braintrust.dev", + eu: "https://api.eu.braintrust.dev", +}; + +export function dataPlaneApiUrl(plane: DataPlane): string { + return DATA_PLANE_API_URLS[plane]; +} + +export class BraintrustApiClient { + constructor( + private readonly apiUrl: string, + private readonly token: string, + ) {} + + private async request( + method: string, + path: string, + body?: unknown, + ): Promise<{ data: T; headers: Headers }> { + const res = await fetch(`${this.apiUrl}${path}`, { + method, + headers: { + Authorization: `Bearer ${this.token}`, + Accept: "application/json", + ...(body !== undefined ? { "Content-Type": "application/json" } : {}), + }, + ...(body !== undefined ? { body: JSON.stringify(body) } : {}), + }); + if (!res.ok) { + const text = await res.text().catch(() => ""); + throw new BraintrustApiError(res.status, method, path, text); + } + const data = (await res.json()) as T; + return { data, headers: res.headers }; + } + + async listOrgs(): Promise { + const { data } = await this.request<{ objects: Org[] }>( + "GET", + "/v1/organization?limit=200", + ); + return data.objects; + } + + async createOrg(args: { + readonly orgName: string; + readonly dataPlane: DataPlane; + }): Promise<{ readonly id: string; readonly existed: boolean }> { + const apiUrl = dataPlaneApiUrl(args.dataPlane); + const { data, headers } = await this.request( + "POST", + "/v1/organization", + { org_name: args.orgName, api_url: apiUrl }, + ); + const id = typeof data === "string" ? data : (data as { id: string }).id; + return { id, existed: headers.get("x-bt-found-existing") === "true" }; + } + + async listProjects(orgId: string): Promise { + const { data } = await this.request<{ objects: Project[] }>( + "GET", + `/v1/project?org_id=${encodeURIComponent(orgId)}&limit=500`, + ); + return data.objects; + } + + async createProject(args: { + readonly orgId: string; + readonly name: string; + }): Promise { + const { data } = await this.request("POST", "/v1/project", { + org_id: args.orgId, + name: args.name, + }); + return data; + } + + async listApiKeyNames(orgId: string): Promise { + const { data } = await this.request<{ objects: ApiKey[] }>( + "GET", + `/v1/api_key?org_id=${encodeURIComponent(orgId)}&limit=500`, + ); + return data.objects.map((k) => k.name); + } + + async createApiKey(args: { + readonly orgId: string; + readonly name: string; + }): Promise { + const { data } = await this.request( + "POST", + "/v1/api_key", + { org_id: args.orgId, name: args.name }, + ); + return data; + } + + async currentUser(): Promise { + const { data } = await this.request("GET", "/v1/user/me"); + return data; + } + + /** + * Like {@link currentUser}, but retries on 401 to ride out the brief + * Clerk-webhook race where a freshly-signed-up user's `bt_auth_id` hasn't + * landed in `publicMetadata` yet. See app/api/actions/util.ts:62 in the + * braintrust monorepo for the same race. + * + * Only 401s are retried; other failures throw immediately. + */ + async currentUserAwaitingProvisioning(args?: { + readonly delaysMs?: readonly number[]; + readonly sleep?: (ms: number) => Promise; + }): Promise { + const delays = args?.delaysMs ?? PROVISIONING_RETRY_DELAYS_MS; + const sleep = args?.sleep ?? defaultSleep; + let lastError: unknown; + for (let attempt = 0; attempt <= delays.length; attempt += 1) { + try { + return await this.currentUser(); + } catch (e) { + lastError = e; + if (!(e instanceof BraintrustApiError) || e.status !== 401) { + throw e; + } + if (attempt === delays.length) { + throw new Error( + "Your Braintrust account is still being provisioned. Please try `bt-wizard` again in a moment.", + { cause: e }, + ); + } + await sleep(delays[attempt]!); + } + } + throw lastError instanceof Error + ? lastError + : new Error("currentUserAwaitingProvisioning: unreachable"); + } +} + +const PROVISIONING_RETRY_DELAYS_MS: readonly number[] = [ + 1000, 1500, 2000, 3000, 4000, +]; + +function defaultSleep(ms: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +export class BraintrustApiError extends Error { + constructor( + readonly status: number, + readonly method: string, + readonly path: string, + readonly responseBody: string, + ) { + super(`${method} ${path} → ${status}: ${responseBody.slice(0, 200)}`); + this.name = "BraintrustApiError"; + } +} + +export function buildApiKeyName(args: { + readonly userHandle: string; + readonly existingNames: readonly string[]; +}): string { + const base = `${args.userHandle}-created-by-bt-wizard`; + for (let n = 0; n < 10000; n += 1) { + const candidate = `${base}${n}`; + if (!args.existingNames.includes(candidate)) { + return candidate; + } + } + throw new Error("Could not find a unique API key name"); +} + +export function userHandle(user: CurrentUser): string { + if (user.email && user.email.length > 0) { + return user.email.split("@")[0]?.toLowerCase() ?? user.id; + } + if (user.given_name) { + return user.given_name.toLowerCase().replace(/[^a-z0-9-]/g, "-"); + } + return user.id; +} diff --git a/test/braintrust-api.test.ts b/test/braintrust-api.test.ts new file mode 100644 index 0000000..d59e60e --- /dev/null +++ b/test/braintrust-api.test.ts @@ -0,0 +1,93 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { BraintrustApiClient, BraintrustApiError } from "../src/braintrust-api"; + +describe("currentUserAwaitingProvisioning", () => { + const originalFetch = globalThis.fetch; + beforeEach(() => { + // No-op; tests stub fetch per-case. + }); + afterEach(() => { + globalThis.fetch = originalFetch; + }); + + it("returns the user on first success", async () => { + const fetchMock = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ id: "u1", email: "a@b.com" }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }), + ); + globalThis.fetch = fetchMock as unknown as typeof fetch; + const api = new BraintrustApiClient("https://api.example", "tok"); + + const sleeps: number[] = []; + const user = await api.currentUserAwaitingProvisioning({ + sleep: async (ms) => { + sleeps.push(ms); + }, + }); + + expect(user.id).toBe("u1"); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(sleeps).toEqual([]); + }); + + it("retries on 401 and succeeds after a few attempts", async () => { + const responses = [ + new Response("nope", { status: 401 }), + new Response("nope", { status: 401 }), + new Response(JSON.stringify({ id: "u1" }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }), + ]; + const fetchMock = vi.fn().mockImplementation(() => responses.shift()!); + globalThis.fetch = fetchMock as unknown as typeof fetch; + + const api = new BraintrustApiClient("https://api.example", "tok"); + const sleeps: number[] = []; + const user = await api.currentUserAwaitingProvisioning({ + delaysMs: [10, 20, 30, 40, 50], + sleep: async (ms) => { + sleeps.push(ms); + }, + }); + + expect(user.id).toBe("u1"); + expect(fetchMock).toHaveBeenCalledTimes(3); + expect(sleeps).toEqual([10, 20]); + }); + + it("gives up after exhausting retries on persistent 401", async () => { + const fetchMock = vi + .fn() + .mockResolvedValue(new Response("nope", { status: 401 })); + globalThis.fetch = fetchMock as unknown as typeof fetch; + + const api = new BraintrustApiClient("https://api.example", "tok"); + await expect( + api.currentUserAwaitingProvisioning({ + delaysMs: [1, 2, 3], + sleep: async () => {}, + }), + ).rejects.toThrow(/still being provisioned/); + expect(fetchMock).toHaveBeenCalledTimes(4); + }); + + it("does not retry on non-401 errors", async () => { + const fetchMock = vi + .fn() + .mockResolvedValue(new Response("boom", { status: 500 })); + globalThis.fetch = fetchMock as unknown as typeof fetch; + + const api = new BraintrustApiClient("https://api.example", "tok"); + await expect( + api.currentUserAwaitingProvisioning({ + delaysMs: [1, 2, 3], + sleep: async () => {}, + }), + ).rejects.toBeInstanceOf(BraintrustApiError); + expect(fetchMock).toHaveBeenCalledTimes(1); + }); +}); From ca8c7c0beda878dbda6e7fc4d48f2f9928b5ae99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:31:02 -0700 Subject: [PATCH 06/52] feat: expand wizard-copy with new prompts and helpers --- src/wizard-copy.ts | 51 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/wizard-copy.ts b/src/wizard-copy.ts index b54ef3e..be7d2a1 100644 --- a/src/wizard-copy.ts +++ b/src/wizard-copy.ts @@ -5,15 +5,58 @@ export const WIZARD_DESCRIPTION = export const ACCOUNT_QUESTION = "Do you already have a Braintrust account?"; +// Legacy copy used by the beau (Ink) variant. The Clack wizard uses +// browser-mediated wizard sign-in and doesn't need this prompt. export const LOGIN_BROWSER_PROMPT = "For the rest of the flow, we require you to be logged in, do you want to open the browser?"; -export const WIZARD_CANCEL_MESSAGE = "Wizard cancelled."; - -export function loginPlaceholderOutro(openBrowser: boolean) { +export function loginPlaceholderOutro(openBrowser: boolean): string { if (openBrowser) { return "Browser login is not wired up yet, so no browser was opened."; } - return "Browser login skipped. Setup flow stops here for now."; } + +export const NOT_GIT_REPO_WARNING = + "Heads up: this folder is not a git repository. The wizard may edit files; consider running it inside a checked-in repo."; + +export const DOCS_URL = "https://www.braintrust.dev/docs"; + +export const WIZARD_CANCEL_MESSAGE = "Wizard cancelled."; + +export const PROVIDER_QUESTION = "Which LLM provider are you using?"; + +export const PROVIDER_KEY_QUESTION = (label: string): string => + `Enter your ${label} API key:`; + +export const RUN_HARNESS_QUESTION = + "Run the bt-wizard coding agent harness now to instrument this repo?"; + +export const HARNESS_NOT_FOUND = (checked: readonly string[]): string => + `Couldn't find the bt-wizard harness. Looked in:\n ${checked.join("\n ")}`; + +export function gitignoreNote(args: { + readonly added: boolean; + readonly alreadyCovered: boolean; +}): string { + if (args.added) { + return "Added .env.braintrust to .gitignore."; + } + if (args.alreadyCovered) { + return ".gitignore already covers .env.braintrust."; + } + return ".gitignore unchanged."; +} + +export function wizardLoginPrompt(args: { readonly loginUrl: string }): string { + return [ + "Open this URL in your browser to finish signing in:", + ` ${args.loginUrl}`, + "", + "Pick the org and project you want to use; the wizard will resume here.", + ].join("\n"); +} + +export function promptSavedNote(path: string): string { + return `Wrote the agent prompt to: ${path}\nYou can run a coding agent against it manually.`; +} From ec4fabb42b64369855fff87d2e241de55925acb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:31:07 -0700 Subject: [PATCH 07/52] feat: use open to open web page --- package.json | 1 + pnpm-lock.yaml | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/browser.ts | 10 ++++++ 3 files changed, 107 insertions(+) create mode 100644 src/browser.ts diff --git a/package.json b/package.json index a804f51..faa96a2 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@clack/prompts": "1.3.0", "@tanstack/react-query": "5.100.9", "ink": "7.0.2", + "open": "^11.0.0", "react": "19.2.5", "react-devtools-core": "7.0.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d08583..e3c2fec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: ink: specifier: 7.0.2 version: 7.0.2(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.5) + open: + specifier: ^11.0.0 + version: 11.0.0 react: specifier: 19.2.5 version: 19.2.5 @@ -1117,6 +1120,10 @@ packages: buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + cache-control-parser@2.2.0: resolution: {integrity: sha512-Me01OJfiZiyGT42qApiunD9fKiE8WMk9w+zhu1LZlRHuDGW1gvy/IA+GcdJbWXVhZbQkn6NmZKomVaO7BFdl1g==} @@ -1218,10 +1225,22 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -1635,6 +1654,11 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1660,6 +1684,15 @@ packages: engines: {node: '>=20'} hasBin: true + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -1715,6 +1748,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -1988,6 +2025,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + openai@4.89.0: resolution: {integrity: sha512-XNI0q2l8/Os6jmojxaID5EhyQjxZgzR2gWcpEjYWK5hGKwE7AcifxEY7UNwFDDHJQXqeiosQ0CJwQN+rvnwdjA==} hasBin: true @@ -2059,6 +2100,10 @@ packages: resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} engines: {node: ^10 || ^12 || >=14} + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2118,6 +2163,10 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + safe-array-concat@1.1.4: resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} @@ -2560,6 +2609,10 @@ packages: utf-8-validate: optional: true + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + xml-naming@0.1.0: resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} engines: {node: '>=16.0.0'} @@ -4027,6 +4080,10 @@ snapshots: buffer-equal-constant-time@1.0.1: {} + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + cache-control-parser@2.2.0: {} call-bind-apply-helpers@1.0.2: @@ -4128,12 +4185,21 @@ snapshots: deep-is@0.1.4: {} + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -4688,6 +4754,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -4712,6 +4780,12 @@ snapshots: is-in-ci@2.0.0: {} + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -4766,6 +4840,10 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + isarray@2.0.5: {} isexe@2.0.0: {} @@ -5007,6 +5085,15 @@ snapshots: dependencies: mimic-fn: 2.1.0 + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + openai@4.89.0(ws@8.20.0)(zod@3.25.76): dependencies: '@types/node': 18.19.130 @@ -5079,6 +5166,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + powershell-utils@0.1.0: {} + prelude-ls@1.2.1: {} prettier@3.8.3: {} @@ -5184,6 +5273,8 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.18 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.18 + run-applescript@7.1.0: {} + safe-array-concat@1.1.4: dependencies: call-bind: 1.0.9 @@ -5635,6 +5726,11 @@ snapshots: ws@8.20.0: {} + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + xml-naming@0.1.0: {} yallist@3.1.1: {} diff --git a/src/browser.ts b/src/browser.ts new file mode 100644 index 0000000..c484e73 --- /dev/null +++ b/src/browser.ts @@ -0,0 +1,10 @@ +import open from "open"; + +export async function openBrowser(url: string): Promise { + try { + await open(url); + return true; + } catch { + return false; + } +} From d0ab852a0d2583a36b7a4c463770ac1fe7d3aecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:31:07 -0700 Subject: [PATCH 08/52] feat: use open to open web page --- src/browser.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/browser.ts b/src/browser.ts index c484e73..908a48e 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -7,4 +7,3 @@ export async function openBrowser(url: string): Promise { } catch { return false; } -} From 8e021d059411761a80767427ca00faf1bbdd3a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:31:12 -0700 Subject: [PATCH 09/52] feat: add cleanup message helpers --- src/browser.ts | 1 + src/cleanup.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/cleanup.ts diff --git a/src/browser.ts b/src/browser.ts index 908a48e..c484e73 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -7,3 +7,4 @@ export async function openBrowser(url: string): Promise { } catch { return false; } +} diff --git a/src/cleanup.ts b/src/cleanup.ts new file mode 100644 index 0000000..02ced12 --- /dev/null +++ b/src/cleanup.ts @@ -0,0 +1,45 @@ +/** + * URL formats from /workspace/bt-main/skills/sdk-install/braintrust-url-formats.md. + * `appUrl` here is the *base* (e.g. https://www.braintrust.dev) — the docs reference + * `BRAINTRUST_APP_URL` which for the SaaS app is `https://www.braintrust.dev/app`. + */ +export type TraceLocation = { + readonly org: string; + readonly project: string; + readonly rootSpanId: string; + readonly spanId?: string; +}; + +export function buildLogsPermalink( + appUrl: string, + trace: TraceLocation, +): string { + const base = `${appUrl}/${encodeURIComponent(trace.org)}/p/${encodeURIComponent(trace.project)}/logs`; + const params = new URLSearchParams({ r: trace.rootSpanId }); + if (trace.spanId) { + params.set("s", trace.spanId); + } + return `${base}?${params.toString()}`; +} + +export type CleanupContext = { + readonly docsUrl: string; + readonly tracePermalink: string | undefined; + readonly resumeCommand?: string; +}; + +export function buildCleanupMessage(ctx: CleanupContext): string { + const lines = [ + "Setup complete.", + "", + "For production runs, set the BRAINTRUST_API_KEY environment variable.", + `Docs: ${ctx.docsUrl}`, + ]; + if (ctx.resumeCommand) { + lines.push("", "To resume the coding agent:", ` ${ctx.resumeCommand}`); + } + if (ctx.tracePermalink) { + lines.push(`Trace: ${ctx.tracePermalink}`); + } + return lines.join("\n"); +} From 285eb997ed6a0bd3c4cc6fee1b867c627695ee26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:31:45 -0700 Subject: [PATCH 10/52] feat: add fuzzy search prompt helper --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index faa96a2..0ea77f4 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "format:check": "prettier --check ." }, "dependencies": { + "@inquirer/search": "4.1.8", "@clack/prompts": "1.3.0", "@tanstack/react-query": "5.100.9", "ink": "7.0.2", From e3627c290534dc19714f89054e674f30e9cd3318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:31:51 -0700 Subject: [PATCH 11/52] feat: add git repo detection and .env writing helpers --- src/git.ts | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/git.ts diff --git a/src/git.ts b/src/git.ts new file mode 100644 index 0000000..d099df0 --- /dev/null +++ b/src/git.ts @@ -0,0 +1,89 @@ +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; + +export function findGitRoot(startDir: string): string | undefined { + let dir = resolve(startDir); + while (true) { + if (existsSync(join(dir, ".git"))) { + return dir; + } + const parent = dirname(dir); + if (parent === dir) { + return undefined; + } + dir = parent; + } +} + +export function isGitRepo(cwd: string): boolean { + return findGitRoot(cwd) !== undefined; +} + +const ENV_FILENAME = ".env.braintrust"; + +export type EnvFileWriteResult = { + readonly envFilePath: string; + readonly gitignorePath: string | undefined; + readonly addedToGitignore: boolean; + readonly alreadyCovered: boolean; +}; + +export function writeEnvBraintrust( + gitRoot: string, + apiKey: string, +): EnvFileWriteResult { + const envFilePath = join(gitRoot, ENV_FILENAME); + writeFileSync(envFilePath, `BRAINTRUST_API_KEY=${apiKey}\n`, { mode: 0o600 }); + + const gitignorePath = join(gitRoot, ".gitignore"); + const existing = existsSync(gitignorePath) + ? readFileSync(gitignorePath, "utf8") + : ""; + + const alreadyCovered = gitignoreCovers(existing, ENV_FILENAME); + if (alreadyCovered) { + return { + envFilePath, + gitignorePath, + addedToGitignore: false, + alreadyCovered: true, + }; + } + + const sep = existing.length === 0 || existing.endsWith("\n") ? "" : "\n"; + writeFileSync(gitignorePath, `${existing}${sep}${ENV_FILENAME}\n`); + return { + envFilePath, + gitignorePath, + addedToGitignore: true, + alreadyCovered: false, + }; +} + +export function gitignoreCovers(content: string, filename: string): boolean { + for (const rawLine of content.split(/\r?\n/)) { + const line = rawLine.trim(); + if (line.length === 0 || line.startsWith("#") || line.startsWith("!")) { + continue; + } + if (matchesGitignorePattern(line, filename)) { + return true; + } + } + return false; +} + +function matchesGitignorePattern(pattern: string, filename: string): boolean { + let p = pattern; + if (p.startsWith("/")) { + p = p.slice(1); + } + if (p.endsWith("/")) { + return false; + } + const regexSrc = `^${p + .replace(/[.+^${}()|[\]\\]/g, "\\$&") + .replace(/\*/g, ".*") + .replace(/\?/g, ".")}$`; + return new RegExp(regexSrc).test(filename); +} From 4fc525b138fa0724584d5485dec85f4f586f451a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:31:45 -0700 Subject: [PATCH 12/52] feat: add fuzzy search prompt helper --- package.json | 2 ++ pnpm-lock.yaml | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/fuzzy.ts | 31 +++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 src/fuzzy.ts diff --git a/package.json b/package.json index 0ea77f4..cea1074 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "dependencies": { "@inquirer/search": "4.1.8", "@clack/prompts": "1.3.0", + "@inquirer/search": "4.1.8", "@tanstack/react-query": "5.100.9", + "fuse.js": "^7.3.0", "ink": "7.0.2", "open": "^11.0.0", "react": "19.2.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3c2fec..bff8088 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,15 @@ importers: '@clack/prompts': specifier: 1.3.0 version: 1.3.0 + '@inquirer/search': + specifier: 4.1.8 + version: 4.1.8(@types/node@24.12.2) '@tanstack/react-query': specifier: 5.100.9 version: 5.100.9(react@19.2.5) + fuse.js: + specifier: ^7.3.0 + version: 7.3.0 ink: specifier: 7.0.2 version: 7.0.2(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.5) @@ -397,6 +403,41 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@inquirer/ansi@2.0.5': + resolution: {integrity: sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/core@11.1.10': + resolution: {integrity: sha512-a4Q5BXHQAHa9eO202sTaFCHFYVB3x5fauDuThEAdZ9gfn76pSxiKU7wWcEH0N1O0XmQvNfQNU6QXpiRxmYQx+A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.5': + resolution: {integrity: sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/search@4.1.8': + resolution: {integrity: sha512-fGiHKGD6DyPIYUWxoXnQTeXeyYqSOUrasDMABBmMHUalH/LxkuzY0xVRtimXAt1sUeeyYkVuKQx1bebMuN11Kw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@4.0.5': + resolution: {integrity: sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1162,6 +1203,10 @@ packages: resolution: {integrity: sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA==} engines: {node: '>=22'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + code-excerpt@4.0.0: resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1493,6 +1538,10 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + fuse.js@7.3.0: + resolution: {integrity: sha512-plz8RVjfcDedTGfVngWH1jmJvBvAwi1v2jecfDerbEnMcmOYUEEwKFTHbNoCiYyzaK2Ws8lABkTCcRSqCY1q4w==} + engines: {node: '>=10'} + gaxios@6.7.1: resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} engines: {node: '>=14'} @@ -1956,6 +2005,10 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2250,6 +2303,10 @@ packages: 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'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -3337,6 +3394,34 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@inquirer/ansi@2.0.5': {} + + '@inquirer/core@11.1.10(@types/node@24.12.2)': + dependencies: + '@inquirer/ansi': 2.0.5 + '@inquirer/figures': 2.0.5 + '@inquirer/type': 4.0.5(@types/node@24.12.2) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.0 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 24.12.2 + + '@inquirer/figures@2.0.5': {} + + '@inquirer/search@4.1.8(@types/node@24.12.2)': + dependencies: + '@inquirer/core': 11.1.10(@types/node@24.12.2) + '@inquirer/figures': 2.0.5 + '@inquirer/type': 4.0.5(@types/node@24.12.2) + optionalDependencies: + '@types/node': 24.12.2 + + '@inquirer/type@4.0.5(@types/node@24.12.2)': + optionalDependencies: + '@types/node': 24.12.2 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4120,6 +4205,8 @@ snapshots: slice-ansi: 9.0.0 string-width: 8.2.1 + cli-width@4.1.0: {} + code-excerpt@4.0.0: dependencies: convert-to-spaces: 2.0.1 @@ -4543,6 +4630,8 @@ snapshots: functions-have-names@1.2.3: {} + fuse.js@7.3.0: {} + gaxios@6.7.1: dependencies: extend: 3.0.2 @@ -5022,6 +5111,8 @@ snapshots: ms@2.1.3: {} + mute-stream@3.0.0: {} + nanoid@3.3.12: {} nanoid@3.3.6: {} @@ -5370,6 +5461,8 @@ snapshots: signal-exit@3.0.7: {} + signal-exit@4.1.0: {} + sisteransi@1.0.5: {} slice-ansi@9.0.0: diff --git a/src/fuzzy.ts b/src/fuzzy.ts new file mode 100644 index 0000000..224d103 --- /dev/null +++ b/src/fuzzy.ts @@ -0,0 +1,31 @@ +import search from "@inquirer/search"; +import Fuse from "fuse.js"; + +export type FuzzyChoice = { + readonly value: T; + readonly name: string; + readonly description?: string; +}; + +export async function fuzzySelect(args: { + readonly message: string; + readonly choices: ReadonlyArray>; +}): Promise { + if (args.choices.length === 0) { + throw new Error("fuzzySelect called with no choices"); + } + const fuse = new Fuse(args.choices, { keys: ["name"], threshold: 0.4 }); + return search({ + message: args.message, + source: (term) => { + const results = !term + ? args.choices + : fuse.search(term).map((r) => r.item); + return results.map((c) => ({ + name: c.name, + value: c.value, + description: c.description, + })); + }, + }); +} From 810250823ad149f8814e8374cedb885d9ebd2e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:31:55 -0700 Subject: [PATCH 13/52] feat: add language/framework detection --- src/language-detect.ts | 91 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/language-detect.ts diff --git a/src/language-detect.ts b/src/language-detect.ts new file mode 100644 index 0000000..e87cb2e --- /dev/null +++ b/src/language-detect.ts @@ -0,0 +1,91 @@ +import { existsSync, readdirSync, statSync } from "node:fs"; +import { join } from "node:path"; + +export type DetectedLanguage = + | "python" + | "typescript" + | "go" + | "java" + | "ruby" + | "csharp"; + +const FILENAME_INDICATORS: ReadonlyArray = + [ + ["pyproject.toml", "python"], + ["setup.py", "python"], + ["requirements.txt", "python"], + ["package.json", "typescript"], + ["tsconfig.json", "typescript"], + ["go.mod", "go"], + ["pom.xml", "java"], + ["build.gradle", "java"], + ["build.gradle.kts", "java"], + ["Gemfile", "ruby"], + ]; + +const EXTENSION_INDICATORS: ReadonlyArray = + [ + [".csproj", "csharp"], + [".sln", "csharp"], + [".gemspec", "ruby"], + ]; + +/** + * Detect candidate languages by scanning the directory; mirrors bt-main's + * detect_languages_from_dir. Scans cwd first; recurses into immediate + * subdirectories only if nothing matched at the top level. + */ +export function detectLanguages(dir: string): readonly DetectedLanguage[] { + const found = new Set(); + scan(dir, found); + if (found.size === 0) { + if (!existsSync(dir)) { + return []; + } + for (const entry of readdirSync(dir)) { + const child = join(dir, entry); + let isDir: boolean; + try { + isDir = statSync(child).isDirectory(); + } catch { + continue; + } + if (isDir) { + scan(child, found); + } + } + } + return [...found].sort(); +} + +function scan(dir: string, found: Set): void { + let entries: string[]; + try { + entries = readdirSync(dir); + } catch { + return; + } + for (const name of entries) { + const path = join(dir, name); + let isFile: boolean; + try { + isFile = statSync(path).isFile(); + } catch { + continue; + } + if (!isFile) { + continue; + } + const lower = name.toLowerCase(); + for (const [indicator, lang] of FILENAME_INDICATORS) { + if (lower === indicator.toLowerCase()) { + found.add(lang); + } + } + for (const [ext, lang] of EXTENSION_INDICATORS) { + if (lower.endsWith(ext.toLowerCase())) { + found.add(lang); + } + } + } +} From 184a9491b28bac646923157b6df0ebbcb0ecf31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:31:59 -0700 Subject: [PATCH 14/52] feat: add CLI argument parser --- src/options.ts | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/options.ts diff --git a/src/options.ts b/src/options.ts new file mode 100644 index 0000000..d26411d --- /dev/null +++ b/src/options.ts @@ -0,0 +1,83 @@ +export type WizardOptions = { + readonly apiUrl: string; + readonly appUrl: string; + readonly caCertPath: string | undefined; +}; + +export type ParsedArgs = { + readonly options: WizardOptions; + readonly help: boolean; +}; + +const DEFAULT_API_URL = "https://api.braintrust.dev"; +const DEFAULT_APP_URL = "https://www.braintrust.dev"; + +const HELP = `Usage: bt-wizard [options] + +Options: + --api-url Override API URL [env: BRAINTRUST_API_URL] + --app-url Override app URL [env: BRAINTRUST_APP_URL] + --ca-cert Path to PEM CA bundle [env: BRAINTRUST_CA_CERT; overrides SSL_CERT_FILE] + -h, --help Show help + +Environment: + CRANK_ENABLE_TELEMETRY=false Disable anonymous usage telemetry +`; + +export function helpText(): string { + return HELP; +} + +export function parseArgs( + argv: readonly string[], + env: NodeJS.ProcessEnv, +): ParsedArgs { + let apiUrl = env["BRAINTRUST_API_URL"] ?? DEFAULT_API_URL; + let appUrl = env["BRAINTRUST_APP_URL"] ?? DEFAULT_APP_URL; + let caCertPath = env["BRAINTRUST_CA_CERT"] ?? env["SSL_CERT_FILE"]; + let help = false; + + let i = 0; + while (i < argv.length) { + const arg = argv[i]; + const next = (): string => { + const v = argv[i + 1]; + if (v === undefined) { + throw new Error(`Missing value for ${arg}`); + } + i += 1; + return v; + }; + switch (arg) { + case "--api-url": + apiUrl = next(); + break; + case "--app-url": + appUrl = next(); + break; + case "--ca-cert": + caCertPath = next(); + break; + case "-h": + case "--help": + help = true; + break; + default: + throw new Error(`Unknown argument: ${arg}`); + } + i += 1; + } + + return { + help, + options: { + apiUrl: stripTrailingSlash(apiUrl), + appUrl: stripTrailingSlash(appUrl), + caCertPath: caCertPath || undefined, + }, + }; +} + +function stripTrailingSlash(url: string): string { + return url.replace(/\/+$/, ""); +} From 0adc6a0d66b28c045b5d4273fe722213bb0c9dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:32:04 -0700 Subject: [PATCH 15/52] feat: add LLM providers list --- src/providers.ts | 34 ++++++++++++++++++++++++++++++ test/providers.test.ts | 48 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/providers.ts create mode 100644 test/providers.test.ts diff --git a/src/providers.ts b/src/providers.ts new file mode 100644 index 0000000..d8017b9 --- /dev/null +++ b/src/providers.ts @@ -0,0 +1,34 @@ +/** + * LLM providers shown in the wizard. The list mirrors the API-key entries in + * `AISecretTypes` from `@braintrust/proxy/schema` — the providers users + * configure with a single API key. Cloud providers (Bedrock, Vertex, Azure, + * Databricks) use multi-field credentials and are intentionally excluded. + * + * Drift is enforced by `test/providers.test.ts`, which imports + * `@braintrust/proxy/schema` (devDep) and asserts equality against this list. + * + * If `custom: true`, no API key is requested and instrumentation is skipped. + */ +export type LlmProvider = { + readonly id: string; + readonly label: string; + readonly envVar?: string; + readonly custom?: boolean; +}; + +export const LLM_PROVIDERS: readonly LlmProvider[] = [ + { id: "openai", label: "OpenAI", envVar: "OPENAI_API_KEY" }, + { id: "anthropic", label: "Anthropic", envVar: "ANTHROPIC_API_KEY" }, + { id: "gemini", label: "Gemini", envVar: "GEMINI_API_KEY" }, + { id: "mistral", label: "Mistral", envVar: "MISTRAL_API_KEY" }, + { id: "together", label: "Together.ai", envVar: "TOGETHER_API_KEY" }, + { id: "fireworks", label: "Fireworks", envVar: "FIREWORKS_API_KEY" }, + { id: "perplexity", label: "Perplexity", envVar: "PERPLEXITY_API_KEY" }, + { id: "xai", label: "xAI", envVar: "XAI_API_KEY" }, + { id: "groq", label: "Groq", envVar: "GROQ_API_KEY" }, + { id: "lepton", label: "Lepton", envVar: "LEPTON_API_KEY" }, + { id: "cerebras", label: "Cerebras", envVar: "CEREBRAS_API_KEY" }, + { id: "replicate", label: "Replicate", envVar: "REPLICATE_API_KEY" }, + { id: "baseten", label: "Baseten", envVar: "BASETEN_API_KEY" }, + { id: "custom", label: "Custom (self-hosted, skip API key)", custom: true }, +]; diff --git a/test/providers.test.ts b/test/providers.test.ts new file mode 100644 index 0000000..5c8694a --- /dev/null +++ b/test/providers.test.ts @@ -0,0 +1,48 @@ +import { AISecretTypes } from "@braintrust/proxy/schema"; +import { describe, expect, it } from "vitest"; + +import { LLM_PROVIDERS } from "../src/providers"; + +// Provider IDs that the wizard offers but that aren't in the currently +// published `@braintrust/proxy@0.0.9` schema yet. Remove from this set +// once the corresponding proxy release ships. +const KNOWN_AHEAD: ReadonlySet = new Set(["baseten"]); + +function envVarToProviderId(envVar: string): string { + return envVar.toLowerCase().replace(/_api_key$/, ""); +} + +describe("LLM_PROVIDERS sync with @braintrust/proxy/schema", () => { + it("matches AISecretTypes (plus the custom entry and KNOWN_AHEAD)", () => { + const fromSchema = new Set( + Object.keys(AISecretTypes).map(envVarToProviderId), + ); + const fromWizard = new Set( + LLM_PROVIDERS.filter((p) => !p.custom).map((p) => p.id), + ); + + const missingFromWizard = [...fromSchema].filter( + (id) => !fromWizard.has(id), + ); + const extraInWizard = [...fromWizard].filter( + (id) => !fromSchema.has(id) && !KNOWN_AHEAD.has(id), + ); + + expect(missingFromWizard).toEqual([]); + expect(extraInWizard).toEqual([]); + }); + + it("includes the custom self-hosted entry", () => { + expect(LLM_PROVIDERS.some((p) => p.custom === true)).toBe(true); + }); + + it("uses each AISecretTypes env var verbatim", () => { + const envVars = new Set(Object.keys(AISecretTypes)); + for (const provider of LLM_PROVIDERS) { + if (provider.custom) continue; + if (KNOWN_AHEAD.has(provider.id)) continue; + expect(provider.envVar).toBeDefined(); + expect(envVars.has(provider.envVar!)).toBe(true); + } + }); +}); From 9dfc5e2d9d217abdb200e33c6395bc886f8615c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:32:09 -0700 Subject: [PATCH 16/52] feat: add instrumentation prompt template --- src/prompt.ts | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 src/prompt.ts diff --git a/src/prompt.ts b/src/prompt.ts new file mode 100644 index 0000000..ce873a6 --- /dev/null +++ b/src/prompt.ts @@ -0,0 +1,217 @@ +import type { DetectedLanguage } from "./language-detect"; + +/** + * The bt-wizard agent prompt. Adapted from + * /workspace/bt-main/skills/sdk-install/instrument-task.md, with the three + * Three sections handled deterministically by the wizard are omitted: + * - "Verify API Key (Install Precondition)" + * - "Verify in Braintrust (CRITICAL)" + * - "Post-Success Verification and Next Steps" + * + * Placeholders are filled in by `renderPrompt` using the detected language. + * `{SDK_INSTALL_DIR}` resolves to the public docs URL for the language + * (the harness fetches install guides via the `curl` tool, since bash/python + * aren't available). + */ +const TEMPLATE = `# Braintrust SDK Installation (Agent Instructions) + +## Hard Rules + +{RUN_MODE_CONTEXT} + +- **Only add Braintrust code.** Do not refactor or modify unrelated code. +- **One language, one service per install run.** If the repo has more than one candidate, ask the user which one to instrument before starting. Do not instrument multiple languages or services in the same run. +- **If the language is unclear, ask the user.** Do not guess. See Step 2. +- **Install the latest Braintrust SDK.** Do not hard-pin the Braintrust SDK version unless the user asks for it -- use the package manager's normal install (which may produce an exact or a ranged version, whichever is idiomatic for that ecosystem). Build-time dependencies (e.g. Orchestrion for Go) must still be pinned to an exact version -- see the language-specific resource. +- **Set the project name in code.** Do NOT configure project name via env vars. +- **App must run without Braintrust.** If \`BRAINTRUST_API_KEY\` is missing at runtime, do not crash. +- **Do not guess APIs.** Use official documentation/examples only. +- **Do not add eval code** unless explicitly requested. +- **Do not add manual flush/shutdown logic** unless the app is a short-lived script, serverless function, Lambda, or CLI that exits immediately after LLM calls -- in which case a single \`flush()\` (or language equivalent) right before exit is correct, since otherwise traces get dropped. Do not add flush/shutdown for long-running processes (servers, daemons, workers). +- **If SDK is already installed/configured, do not duplicate work.** +- **Do not create setup-only files or directories in the repo.** Do not write \`.bt/setup/\`, \`.bt/skills/docs/\`, agent skill directories, or setup task files unless explicitly asked by the user. + +--- + +## Execution Requirements + +Before writing any code: + +1. Create a **checklist** from the steps below. +2. Execute each step in order. +3. Do not skip steps. + +--- + +## Steps + +{LANGUAGE_CONTEXT} + +--- + +{INSTALL_SDK_CONTEXT} + +--- + +### 4. Verify Installation (MANDATORY) + +- If the SDK relies on build-time or launch-time auto-instrumentation, make sure the project's normal build/run path now uses it. A one-off verification command is not sufficient. +- Run the application. +- Confirm at least one log/trace is emitted to Braintrust. +- Confirm no runtime errors. +- Confirm the app still runs if \`BRAINTRUST_API_KEY\` is unset. + +If you do not know how to run the app, ask the user and wait for the response before proceeding. + +--- + +### 6. Final Summary + +Summarize: + +- What SDK version was installed +- Where code was modified +- What logs/traces were emitted +- The Braintrust permalink (required) + +If instrumentation succeeded, output the following sentinel on its own line exactly as written: + +INSTRUMENTATION_COMPLETE + +If instrumentation failed or could not be completed, output instead: + +INSTRUMENTATION_INCOMPLETE + +{RESULT_FILE_CONTEXT}{WORKFLOW_CONTEXT} +`; + +const LANGUAGE_DISPLAY: Record = { + python: "Python", + typescript: "TypeScript", + go: "Go", + java: "Java", + ruby: "Ruby", + csharp: "C#", +}; + +const SDK_INSTALL_DOCS_BASE = + "https://www.braintrust.dev/docs/instrument/trace-llm-calls"; + +const INSTALL_SDK_REQUIREMENTS = `- Install the latest Braintrust SDK via the language's package manager. Do not hard-pin the SDK version unless the user asks. Build-time dependencies called out by the language-specific resource (e.g. Orchestrion for Go) must still be pinned to an exact version. +- Modify only dependency files, a minimal application entry point (e.g., main/bootstrap), and any existing build/run scripts or checked-in env/config that must change to keep auto-instrumentation active in normal use. Auto-instrument the app (except for Java and C# which don't support auto-instrumentation). +- Do not change unrelated code.`; + +const DETECT_LANGUAGE_BLOCK = `### 2. Detect Language + +**Instrument exactly one language/service per install run.** Do not install Braintrust for multiple languages or multiple services in the same run, even if the repo contains more than one. If more than one candidate exists, stop and ask the user which single service to instrument before doing anything else. + +Determine the project language using concrete signals: + +- \`package.json\` -> TypeScript +- \`requirements.txt\`, \`setup.py\` or \`pyproject.toml\` -> Python +- \`pom.xml\` or \`build.gradle\` -> Java +- \`go.mod\` -> Go +- \`Gemfile\` -> Ruby +- \`.csproj\` -> C# + +**If exactly one of these matches at the repo root and there is no ambiguity, proceed with that language.** + +In every other case, **stop and ask the user** before continuing. Do not guess, do not pick the "most likely" language, and do not instrument more than one.`; + +export type RenderPromptOptions = { + readonly languages: readonly DetectedLanguage[]; + readonly interactive: boolean; + /** + * If set, a path the agent must write the trace permalink to (single line, + * just the URL) right before exiting. The wizard reads this file after the + * harness exits and surfaces the permalink in its cleanup message. + */ + readonly resultFilePath?: string; +}; + +export function renderPrompt(opts: RenderPromptOptions): string { + const runMode = opts.interactive + ? "- **Interactive mode:** You can ask the user questions through the chat interface.\n" + : "- **Non-interactive mode:** You cannot ask the user questions. If a step requires user input (e.g., ambiguous language in a polyglot repo, unknown run command), abort with a clear explanation of what is needed.\n"; + + let languageContext: string; + let installSdkContext: string; + + if (opts.languages.length === 0) { + languageContext = DETECT_LANGUAGE_BLOCK; + const rows = (Object.keys(LANGUAGE_DISPLAY) as DetectedLanguage[]) + .map( + (lang) => + `| ${LANGUAGE_DISPLAY[lang]} | \`${SDK_INSTALL_DOCS_BASE}#${lang}\` |`, + ) + .join("\n"); + installSdkContext = `### 3. Install SDK (Language-Specific) + +Read the install guide for the detected language from the canonical docs: + +| Language | Doc URL | +| -------- | ------- | +${rows} + +Requirements: + +${INSTALL_SDK_REQUIREMENTS}`; + } else if (opts.languages.length === 1) { + const lang = opts.languages[0]!; + languageContext = `### 2. Language + +The target language has been specified: **${LANGUAGE_DISPLAY[lang]}**.`; + installSdkContext = `### 3. Install SDK + +Read the install guide from the canonical docs: \`${SDK_INSTALL_DOCS_BASE}#${lang}\` + +Requirements: + +${INSTALL_SDK_REQUIREMENTS}`; + } else { + const list = opts.languages + .map((l) => `**${LANGUAGE_DISPLAY[l]}**`) + .join(", "); + languageContext = `### 2. Language + +Candidate languages detected: ${list}. Pick exactly one with the user before proceeding.`; + const rows = opts.languages + .map( + (l) => `| ${LANGUAGE_DISPLAY[l]} | \`${SDK_INSTALL_DOCS_BASE}#${l}\` |`, + ) + .join("\n"); + installSdkContext = `### 3. Install SDK + +Read the install guide for the chosen language from the canonical docs: + +| Language | Doc URL | +| -------- | ------- | +${rows} + +Requirements: + +${INSTALL_SDK_REQUIREMENTS}`; + } + + const resultFileContext = opts.resultFilePath + ? `## Reporting the Trace Permalink (REQUIRED) + +When you have obtained the Braintrust trace permalink in the Final Summary step, write it as plain text (just the URL, no surrounding text, single line) to this exact file path before finishing: + +\`${opts.resultFilePath}\` + +Use the \`write\` tool. The wizard reads this file after you exit and surfaces the permalink to the user. If you cannot produce a permalink, leave the file empty. + +` + : ""; + + const workflowContext = `## Latest Braintrust Setup Docs + +Use the canonical Braintrust docs at https://www.braintrust.dev/docs as the source of truth for SDK setup behavior. Prefer local \`bt\` CLI commands over direct API calls when verifying state.`; + + return TEMPLATE.replace("{RUN_MODE_CONTEXT}", runMode) + .replace("{LANGUAGE_CONTEXT}", languageContext) + .replace("{INSTALL_SDK_CONTEXT}", installSdkContext) + .replace("{RESULT_FILE_CONTEXT}", resultFileContext) + .replace("{WORKFLOW_CONTEXT}", workflowContext); +} From 28c750117853075ea49cf4e9c4d547ff48dccfa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:32:15 -0700 Subject: [PATCH 17/52] feat: implement clack wizard flow --- src/clack-wizard.ts | 175 +++++++++++++++++++++++++++----- src/cli.ts | 50 ++++++++- test/clack-wizard.test.ts | 206 ++++++++++++++++++++++++++------------ 3 files changed, 338 insertions(+), 93 deletions(-) diff --git a/src/clack-wizard.ts b/src/clack-wizard.ts index ae51bbe..9f14c0c 100644 --- a/src/clack-wizard.ts +++ b/src/clack-wizard.ts @@ -1,29 +1,66 @@ +import { cwd as processCwd } from "node:process"; + +import { WizardSigninAuthClient } from "./auth"; +import { openBrowser } from "./browser"; +import { buildLogsPermalink, buildCleanupMessage } from "./cleanup"; +import { fuzzySelect } from "./fuzzy"; +import { findGitRoot, isGitRepo, writeEnvBraintrust } from "./git"; +import type { WizardOptions } from "./options"; +import { LLM_PROVIDERS, type LlmProvider } from "./providers"; import { - ACCOUNT_QUESTION, - LOGIN_BROWSER_PROMPT, + DOCS_URL, + NOT_GIT_REPO_WARNING, + PROVIDER_KEY_QUESTION, + PROVIDER_QUESTION, WIZARD_CANCEL_MESSAGE, WIZARD_TITLE, - loginPlaceholderOutro, + gitignoreNote, + wizardLoginPrompt, } from "./wizard-copy"; -type ConfirmOptions = { - readonly initialValue?: boolean; - readonly message: string; +type SelectOption = { + readonly label: string; + readonly value: T; + readonly hint?: string; }; -type PromptResult = T | symbol; - export type ClackWizardPrompts = { readonly cancel: (message: string) => void; - readonly confirm: (options: ConfirmOptions) => Promise>; + readonly confirm: (options: { + readonly initialValue?: boolean; + readonly message: string; + }) => Promise; readonly intro: (message: string) => void; readonly isCancel: (value: unknown) => value is symbol; + readonly note: (message: string, title?: string) => void; readonly outro: (message: string) => void; + readonly password: (options: { + readonly message: string; + }) => Promise; + readonly select: (options: { + readonly message: string; + readonly options: ReadonlyArray>; + }) => Promise; + readonly text: (options: { + readonly message: string; + readonly placeholder?: string; + }) => Promise; + readonly log: { + readonly warn: (message: string) => void; + readonly info: (message: string) => void; + readonly error: (message: string) => void; + readonly success: (message: string) => void; + }; }; -export type ClackWizardResult = { - readonly hasBraintrustAccount: boolean; - readonly openBrowser: boolean; +export type WizardDeps = { + readonly cwd: string; + readonly env: NodeJS.ProcessEnv; + readonly options: WizardOptions; + readonly prompts: ClackWizardPrompts; + readonly authClient: WizardSigninAuthClient; + readonly fuzzy: typeof fuzzySelect; + readonly openBrowser: (url: string) => Promise; }; export class WizardCancelledError extends Error { @@ -33,32 +70,116 @@ export class WizardCancelledError extends Error { } } -async function confirmOrCancel(prompts: ClackWizardPrompts, message: string) { - const value = await prompts.confirm({ - initialValue: true, - message, - }); - +function unwrap(prompts: ClackWizardPrompts, value: T | symbol): T { if (prompts.isCancel(value)) { prompts.cancel(WIZARD_CANCEL_MESSAGE); throw new WizardCancelledError(); } - - return value; + return value as T; } -export async function runClackWizard( - prompts: ClackWizardPrompts, -): Promise { +export type WizardResult = { + readonly orgName: string; + readonly projectName: string; + readonly braintrustApiKey: string; +}; + +export async function runClackWizard(deps: WizardDeps): Promise { + const { prompts } = deps; prompts.intro(WIZARD_TITLE); - const hasBraintrustAccount = await confirmOrCancel(prompts, ACCOUNT_QUESTION); - const openBrowser = await confirmOrCancel(prompts, LOGIN_BROWSER_PROMPT); + if (!isGitRepo(deps.cwd)) { + prompts.log.warn(NOT_GIT_REPO_WARNING); + } + + const session = await deps.authClient.login({ + onLoginUrl: ({ loginUrl }) => { + prompts.note(wizardLoginPrompt({ loginUrl }), "Login"); + }, + onTryOpenBrowser: (url) => deps.openBrowser(url), + }); + + const provider = await selectProvider(deps); + const rawProviderKey = provider.custom + ? undefined + : unwrap( + prompts, + await prompts.password({ + message: PROVIDER_KEY_QUESTION(provider.label), + }), + ); + const providerKey = + rawProviderKey !== undefined && rawProviderKey.length > 0 + ? rawProviderKey + : undefined; + if (rawProviderKey !== undefined && providerKey === undefined) { + prompts.log.warn("No provider API key entered; skipping instrumentation."); + } + + const gitRoot = findGitRoot(deps.cwd); + if (gitRoot) { + const result = writeEnvBraintrust(gitRoot, session.apiKey); + prompts.log.success(`Wrote ${result.envFilePath}`); + prompts.log.info( + gitignoreNote({ + added: result.addedToGitignore, + alreadyCovered: result.alreadyCovered, + }), + ); + } else { + prompts.log.info( + `BRAINTRUST_API_KEY=${session.apiKey}\nNot in a git repo — set this in your environment manually.`, + ); + } - prompts.outro(loginPlaceholderOutro(openBrowser)); + prompts.outro( + buildCleanupMessage({ + docsUrl: DOCS_URL, + tracePermalink: undefined, + resumeCommand: undefined, + }), + ); return { - hasBraintrustAccount, + orgName: session.orgInfo.name, + projectName: session.project.name, + braintrustApiKey: session.apiKey, + }; +} + +async function selectProvider(deps: WizardDeps): Promise { + const { prompts } = deps; + const value = unwrap( + prompts, + await prompts.select({ + message: PROVIDER_QUESTION, + options: LLM_PROVIDERS.map((p) => ({ label: p.label, value: p })), + }), + ); + return value; +} + +export type DefaultDepsArgs = { + readonly options: WizardOptions; + readonly prompts: ClackWizardPrompts; + readonly cwd?: string; + readonly env?: NodeJS.ProcessEnv; +}; + +export function buildDefaultDeps(args: DefaultDepsArgs): WizardDeps { + const cwd = args.cwd ?? processCwd(); + const env = args.env ?? process.env; + const authClient = new WizardSigninAuthClient(args.options.appUrl); + return { + cwd, + env, + options: args.options, + prompts: args.prompts, + authClient, + fuzzy: fuzzySelect, openBrowser, }; } + +// Exported for permalink construction in callers that get a span back. +export { buildLogsPermalink }; diff --git a/src/cli.ts b/src/cli.ts index 2412909..652ffc3 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,13 +1,55 @@ +import { spawnSync } from "node:child_process"; + import * as prompts from "@clack/prompts"; -import { runClackWizard, WizardCancelledError } from "./clack-wizard"; +import { + buildDefaultDeps, + runClackWizard, + WizardCancelledError, +} from "./clack-wizard"; +import { helpText, parseArgs } from "./options"; + +const parsed = parseArgs(process.argv.slice(2), process.env); + +if (parsed.help) { + process.stdout.write(helpText()); + process.exit(0); +} + +// `NODE_EXTRA_CA_CERTS` is read once at Node startup, so we can't apply it +// in-process. If --ca-cert (or BRAINTRUST_CA_CERT / SSL_CERT_FILE) was set +// and the env var isn't already pointing at the same file, re-exec with it +// applied. The guard env var prevents an infinite re-exec loop. +const REEXEC_GUARD = "BT_WIZARD_REEXECED_FOR_CA"; +if ( + parsed.options.caCertPath && + process.env[REEXEC_GUARD] !== "1" && + process.env["NODE_EXTRA_CA_CERTS"] !== parsed.options.caCertPath +) { + const result = spawnSync(process.execPath, process.argv.slice(1), { + stdio: "inherit", + env: { + ...process.env, + NODE_EXTRA_CA_CERTS: parsed.options.caCertPath, + [REEXEC_GUARD]: "1", + }, + }); + process.exit(result.status ?? 1); +} + +const deps = buildDefaultDeps({ + options: parsed.options, + prompts: prompts as unknown as Parameters< + typeof buildDefaultDeps + >[0]["prompts"], +}); try { - await runClackWizard(prompts); + await runClackWizard(deps); } catch (error) { if (error instanceof WizardCancelledError) { process.exit(0); } - - throw error; + process.stderr.write(`${(error as Error).message}\n`); + process.exit(1); } diff --git a/test/clack-wizard.test.ts b/test/clack-wizard.test.ts index 7b8b2df..121eaae 100644 --- a/test/clack-wizard.test.ts +++ b/test/clack-wizard.test.ts @@ -1,43 +1,53 @@ +import { mkdtempSync, mkdirSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + import { describe, expect, it } from "vitest"; import { - runClackWizard, + type WizardSigninAuthClient, + type WizardSigninCompleteResult, + type WizardSigninEvents, +} from "../src/auth"; +import { type ClackWizardPrompts, + runClackWizard, WizardCancelledError, + type WizardDeps, } from "../src/clack-wizard"; -import { - ACCOUNT_QUESTION, - LOGIN_BROWSER_PROMPT, - WIZARD_CANCEL_MESSAGE, - WIZARD_TITLE, - loginPlaceholderOutro, -} from "../src/wizard-copy"; +import { WIZARD_TITLE, WIZARD_CANCEL_MESSAGE } from "../src/wizard-copy"; const CANCEL = Symbol("cancel"); -type ConfirmCall = { - readonly initialValue?: boolean; - readonly message: string; +type ConfirmAnswer = boolean | typeof CANCEL; +type SelectAnswer = T | typeof CANCEL; +type TextAnswer = string | typeof CANCEL; + +type FakePromptInputs = { + readonly confirms?: ConfirmAnswer[]; + readonly selects?: ReadonlyArray; + readonly texts?: TextAnswer[]; + readonly passwords?: TextAnswer[]; }; -function createPrompts(answers: Array) { - const confirmCalls: ConfirmCall[] = []; +function createPrompts(inputs: FakePromptInputs) { const events: string[] = []; + const confirms = [...(inputs.confirms ?? [])]; + const selects = [...(inputs.selects ?? [])]; + const texts = [...(inputs.texts ?? [])]; + const passwords = [...(inputs.passwords ?? [])]; const prompts: ClackWizardPrompts = { cancel(message) { events.push(`cancel:${message}`); }, async confirm(options) { - confirmCalls.push(options); - - const answer = answers.shift(); - - if (answer === undefined) { - throw new Error("No fake answer was provided."); + const next = confirms.shift(); + events.push(`confirm:${options.message}`); + if (next === undefined) { + throw new Error(`No confirm answer for: ${options.message}`); } - - return answer; + return next; }, intro(message) { events.push(`intro:${message}`); @@ -45,63 +55,135 @@ function createPrompts(answers: Array) { isCancel(value): value is symbol { return value === CANCEL; }, + note(message, title) { + events.push(`note:${title ?? ""}`); + void message; + }, outro(message) { - events.push(`outro:${message}`); + events.push(`outro:${message.split("\n")[0]}`); + }, + async password(options) { + const next = passwords.shift(); + events.push(`password:${options.message}`); + if (next === undefined) { + throw new Error(`No password answer for: ${options.message}`); + } + return next as string | symbol; + }, + async select(options) { + const next = selects.shift(); + events.push(`select:${options.message}`); + if (next === undefined) { + throw new Error(`No select answer for: ${options.message}`); + } + return next as SelectAnswer as symbol; + }, + async text(options) { + const next = texts.shift(); + events.push(`text:${options.message}`); + if (next === undefined) { + throw new Error(`No text answer for: ${options.message}`); + } + return next as string | symbol; + }, + log: { + warn: (m) => events.push(`warn:${m}`), + info: (m) => events.push(`info:${m}`), + error: (m) => events.push(`error:${m}`), + success: (m) => events.push(`success:${m}`), }, }; + return { prompts, events }; +} + +const DEFAULT_LOGIN_RESULT: WizardSigninCompleteResult = { + apiKey: "bt-secret-key", + orgInfo: { + id: "o1", + name: "acme", + api_url: "https://api.test", + proxy_url: null, + realtime_url: null, + is_universal_api: null, + git_metadata: null, + }, + project: { + id: "p1", + name: "demo", + org_id: "o1", + description: null, + }, +}; + +function buildDeps(args: { + readonly prompts: ClackWizardPrompts; + readonly authClient?: WizardSigninAuthClient; + readonly cwd?: string; +}): WizardDeps { + const cwd = args.cwd ?? mkdtempSync(join(tmpdir(), "bt-wizard-test-")); + const stubAuth = + args.authClient ?? + ({ + login: async (events: WizardSigninEvents) => { + events.onLoginUrl({ + loginUrl: "https://app.test/app/cli-login/test-session", + expiresAt: "2099-01-01T00:00:00.000Z", + }); + await events.onTryOpenBrowser( + "https://app.test/app/cli-login/test-session", + ); + return DEFAULT_LOGIN_RESULT; + }, + } as unknown as WizardSigninAuthClient); + return { - confirmCalls, - events, - prompts, + cwd, + env: {}, + options: { + apiUrl: "https://api.test", + appUrl: "https://app.test", + caCertPath: undefined, + }, + prompts: args.prompts, + authClient: stubAuth, + fuzzy: async ({ choices }) => choices[0]!.value, + openBrowser: async () => true, }; } describe("runClackWizard", () => { - it("asks both questions in order", async () => { - const { confirmCalls, events, prompts } = createPrompts([true, true]); - - await expect(runClackWizard(prompts)).resolves.toEqual({ - hasBraintrustAccount: true, - openBrowser: true, + it("walks through happy path with no harness run", async () => { + const customProvider = { id: "custom", label: "Custom", custom: true }; + const { prompts, events } = createPrompts({ + selects: [customProvider], }); + const deps = buildDeps({ prompts }); - expect(events).toEqual([ - `intro:${WIZARD_TITLE}`, - `outro:${loginPlaceholderOutro(true)}`, - ]); - expect(confirmCalls).toEqual([ - { initialValue: true, message: ACCOUNT_QUESTION }, - { initialValue: true, message: LOGIN_BROWSER_PROMPT }, - ]); - }); + const result = await runClackWizard(deps); - it("returns the selected answers and acknowledges the login choice", async () => { - const { confirmCalls, events, prompts } = createPrompts([false, false]); + expect(result.orgName).toBe("acme"); + expect(result.projectName).toBe("demo"); + expect(result.braintrustApiKey).toBe("bt-secret-key"); + expect(events[0]).toBe(`intro:${WIZARD_TITLE}`); + expect(events).toContain("note:Login"); + }); - await expect(runClackWizard(prompts)).resolves.toEqual({ - hasBraintrustAccount: false, - openBrowser: false, - }); + it("cancels cleanly when the user aborts the provider select", async () => { + const { prompts, events } = createPrompts({ selects: [CANCEL] }); + const deps = buildDeps({ prompts }); - expect(confirmCalls).toEqual([ - { initialValue: true, message: ACCOUNT_QUESTION }, - { initialValue: true, message: LOGIN_BROWSER_PROMPT }, - ]); - expect(events).toContain(`outro:${loginPlaceholderOutro(false)}`); + await expect(runClackWizard(deps)).rejects.toThrow(WizardCancelledError); + expect(events).toContain(`cancel:${WIZARD_CANCEL_MESSAGE}`); }); - it("cancels cleanly before completing the flow", async () => { - const { confirmCalls, events, prompts } = createPrompts([CANCEL]); - - await expect(runClackWizard(prompts)).rejects.toThrow(WizardCancelledError); + it("warns when not in a git repo", async () => { + const dir = mkdtempSync(join(tmpdir(), "bt-wizard-nogit-")); + mkdirSync(join(dir, "child"), { recursive: true }); + const { prompts, events } = createPrompts({ selects: [CANCEL] }); + const deps = buildDeps({ prompts, cwd: join(dir, "child") }); - expect(confirmCalls).toEqual([ - { initialValue: true, message: ACCOUNT_QUESTION }, - ]); - expect(events).toEqual([ - `intro:${WIZARD_TITLE}`, - `cancel:${WIZARD_CANCEL_MESSAGE}`, - ]); + await expect(runClackWizard(deps)).rejects.toThrow(WizardCancelledError); + expect(events.some((e) => e.startsWith("warn:Heads up"))).toBe(true); }); }); From a61246b13d7fbc7f1ae784ecc7652ce783eee5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Wed, 13 May 2026 11:01:30 -0700 Subject: [PATCH 18/52] feat: collect multi-credential provider fields in wizard flow Replace the single password prompt for provider API keys with collectCredentials(), which loops over provider.credentials for multi-field providers (Bedrock, Vertex, Azure) and falls back to a single secret prompt for single-key providers. Remove unused fuzzySelect import/dep from WizardDeps. Co-Authored-By: Claude Sonnet 4.6 --- src/clack-wizard.ts | 46 ++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/clack-wizard.ts b/src/clack-wizard.ts index 9f14c0c..9d0aed5 100644 --- a/src/clack-wizard.ts +++ b/src/clack-wizard.ts @@ -3,7 +3,6 @@ import { cwd as processCwd } from "node:process"; import { WizardSigninAuthClient } from "./auth"; import { openBrowser } from "./browser"; import { buildLogsPermalink, buildCleanupMessage } from "./cleanup"; -import { fuzzySelect } from "./fuzzy"; import { findGitRoot, isGitRepo, writeEnvBraintrust } from "./git"; import type { WizardOptions } from "./options"; import { LLM_PROVIDERS, type LlmProvider } from "./providers"; @@ -17,6 +16,7 @@ import { gitignoreNote, wizardLoginPrompt, } from "./wizard-copy"; +import type { CredentialField } from "./providers"; type SelectOption = { readonly label: string; @@ -59,7 +59,6 @@ export type WizardDeps = { readonly options: WizardOptions; readonly prompts: ClackWizardPrompts; readonly authClient: WizardSigninAuthClient; - readonly fuzzy: typeof fuzzySelect; readonly openBrowser: (url: string) => Promise; }; @@ -100,21 +99,9 @@ export async function runClackWizard(deps: WizardDeps): Promise { }); const provider = await selectProvider(deps); - const rawProviderKey = provider.custom + const providerCredentials = provider.custom ? undefined - : unwrap( - prompts, - await prompts.password({ - message: PROVIDER_KEY_QUESTION(provider.label), - }), - ); - const providerKey = - rawProviderKey !== undefined && rawProviderKey.length > 0 - ? rawProviderKey - : undefined; - if (rawProviderKey !== undefined && providerKey === undefined) { - prompts.log.warn("No provider API key entered; skipping instrumentation."); - } + : await collectCredentials(prompts, provider); const gitRoot = findGitRoot(deps.cwd); if (gitRoot) { @@ -147,6 +134,32 @@ export async function runClackWizard(deps: WizardDeps): Promise { }; } +async function collectCredentials( + prompts: ClackWizardPrompts, + provider: LlmProvider, +): Promise | undefined> { + const fields: readonly CredentialField[] = provider.credentials ?? [ + { envVar: provider.envVar!, label: provider.label, secret: true }, + ]; + const result: Record = {}; + for (const field of fields) { + const raw = unwrap( + prompts, + field.secret !== false + ? await prompts.password({ message: PROVIDER_KEY_QUESTION(field.label) }) + : await prompts.text({ message: PROVIDER_KEY_QUESTION(field.label) }), + ); + if (raw.length > 0) { + result[field.envVar] = raw; + } + } + if (Object.keys(result).length === 0) { + prompts.log.warn("No credentials entered; skipping instrumentation."); + return undefined; + } + return result; +} + async function selectProvider(deps: WizardDeps): Promise { const { prompts } = deps; const value = unwrap( @@ -176,7 +189,6 @@ export function buildDefaultDeps(args: DefaultDepsArgs): WizardDeps { options: args.options, prompts: args.prompts, authClient, - fuzzy: fuzzySelect, openBrowser, }; } From d22a82a9564d5e37f5032d788557420a0f78f183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:31:51 -0700 Subject: [PATCH 19/52] feat: add git repo detection and .env writing helpers --- .../braintrust-wizard/src}/git.ts | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) rename {src => packages/braintrust-wizard/src}/git.ts (69%) diff --git a/src/git.ts b/packages/braintrust-wizard/src/git.ts similarity index 69% rename from src/git.ts rename to packages/braintrust-wizard/src/git.ts index d099df0..43d9467 100644 --- a/src/git.ts +++ b/packages/braintrust-wizard/src/git.ts @@ -1,10 +1,19 @@ -import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { access, readFile, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; -export function findGitRoot(startDir: string): string | undefined { +async function pathExists(p: string): Promise { + return access(p).then( + () => true, + () => false, + ); +} + +export async function findGitRoot( + startDir: string, +): Promise { let dir = resolve(startDir); while (true) { - if (existsSync(join(dir, ".git"))) { + if (await pathExists(join(dir, ".git"))) { return dir; } const parent = dirname(dir); @@ -15,8 +24,8 @@ export function findGitRoot(startDir: string): string | undefined { } } -export function isGitRepo(cwd: string): boolean { - return findGitRoot(cwd) !== undefined; +export async function isGitRepo(cwd: string): Promise { + return (await findGitRoot(cwd)) !== undefined; } const ENV_FILENAME = ".env.braintrust"; @@ -28,16 +37,18 @@ export type EnvFileWriteResult = { readonly alreadyCovered: boolean; }; -export function writeEnvBraintrust( +export async function writeEnvBraintrust( gitRoot: string, apiKey: string, -): EnvFileWriteResult { +): Promise { const envFilePath = join(gitRoot, ENV_FILENAME); - writeFileSync(envFilePath, `BRAINTRUST_API_KEY=${apiKey}\n`, { mode: 0o600 }); + await writeFile(envFilePath, `BRAINTRUST_API_KEY=${apiKey}\n`, { + mode: 0o600, + }); const gitignorePath = join(gitRoot, ".gitignore"); - const existing = existsSync(gitignorePath) - ? readFileSync(gitignorePath, "utf8") + const existing = (await pathExists(gitignorePath)) + ? await readFile(gitignorePath, "utf8") : ""; const alreadyCovered = gitignoreCovers(existing, ENV_FILENAME); @@ -51,7 +62,7 @@ export function writeEnvBraintrust( } const sep = existing.length === 0 || existing.endsWith("\n") ? "" : "\n"; - writeFileSync(gitignorePath, `${existing}${sep}${ENV_FILENAME}\n`); + await writeFile(gitignorePath, `${existing}${sep}${ENV_FILENAME}\n`); return { envFilePath, gitignorePath, From 474205e1cba488f3f8e3d7fbfb3e331febf59e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Tue, 12 May 2026 16:24:17 -0700 Subject: [PATCH 20/52] chore: use ignore for .gitignore parsing instead of manual implementation --- package.json | 2 +- packages/braintrust-wizard/src/git.ts | 29 +++------------------------ pnpm-lock.yaml | 6 +++--- 3 files changed, 7 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index cea1074..778c4ed 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@clack/prompts": "1.3.0", "@inquirer/search": "4.1.8", "@tanstack/react-query": "5.100.9", - "fuse.js": "^7.3.0", + "ignore": "^7.0.5", "ink": "7.0.2", "open": "^11.0.0", "react": "19.2.5", diff --git a/packages/braintrust-wizard/src/git.ts b/packages/braintrust-wizard/src/git.ts index 43d9467..211d809 100644 --- a/packages/braintrust-wizard/src/git.ts +++ b/packages/braintrust-wizard/src/git.ts @@ -1,5 +1,6 @@ import { access, readFile, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; +import ignore from "ignore"; async function pathExists(p: string): Promise { return access(p).then( @@ -32,7 +33,7 @@ const ENV_FILENAME = ".env.braintrust"; export type EnvFileWriteResult = { readonly envFilePath: string; - readonly gitignorePath: string | undefined; + readonly gitignorePath: string; readonly addedToGitignore: boolean; readonly alreadyCovered: boolean; }; @@ -72,29 +73,5 @@ export async function writeEnvBraintrust( } export function gitignoreCovers(content: string, filename: string): boolean { - for (const rawLine of content.split(/\r?\n/)) { - const line = rawLine.trim(); - if (line.length === 0 || line.startsWith("#") || line.startsWith("!")) { - continue; - } - if (matchesGitignorePattern(line, filename)) { - return true; - } - } - return false; -} - -function matchesGitignorePattern(pattern: string, filename: string): boolean { - let p = pattern; - if (p.startsWith("/")) { - p = p.slice(1); - } - if (p.endsWith("/")) { - return false; - } - const regexSrc = `^${p - .replace(/[.+^${}()|[\]\\]/g, "\\$&") - .replace(/\*/g, ".*") - .replace(/\?/g, ".")}$`; - return new RegExp(regexSrc).test(filename); + return ignore().add(content).ignores(filename); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bff8088..40fd724 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,9 +17,9 @@ importers: '@tanstack/react-query': specifier: 5.100.9 version: 5.100.9(react@19.2.5) - fuse.js: - specifier: ^7.3.0 - version: 7.3.0 + ignore: + specifier: ^7.0.5 + version: 7.0.5 ink: specifier: 7.0.2 version: 7.0.2(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.5) From 4797dabc77639e42c315b9ee00a26d7385703f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Wed, 13 May 2026 09:57:33 -0700 Subject: [PATCH 21/52] chore: use yargs to generate the help --- package.json | 4 ++- pnpm-lock.yaml | 90 ++++++++++++++++++++++++++++++++++++++++++++---- src/options.ts | 92 ++++++++++++++++++++++---------------------------- 3 files changed, 127 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index 778c4ed..db666ed 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,15 @@ "ink": "7.0.2", "open": "^11.0.0", "react": "19.2.5", - "react-devtools-core": "7.0.1" + "react-devtools-core": "7.0.1", + "yargs": "^18.0.0" }, "devDependencies": { "@braintrust/proxy": "0.0.9", "@eslint/js": "10.0.1", "@types/node": "24.12.2", "@types/react": "19.2.14", + "@types/yargs": "^17.0.35", "eslint": "10.3.0", "eslint-config-prettier": "10.1.8", "eslint-plugin-react": "7.37.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40fd724..15ea188 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: react-devtools-core: specifier: 7.0.1 version: 7.0.1 + yargs: + specifier: ^18.0.0 + version: 18.0.0 devDependencies: '@braintrust/proxy': specifier: 0.0.9 @@ -45,6 +48,9 @@ importers: '@types/react': specifier: 19.2.14 version: 19.2.14 + '@types/yargs': + specifier: ^17.0.35 + version: 17.0.35 eslint: specifier: 10.3.0 version: 10.3.0 @@ -895,6 +901,12 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@typescript-eslint/eslint-plugin@8.59.2': resolution: {integrity: sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1207,6 +1219,10 @@ packages: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + code-excerpt@4.0.0: resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1316,6 +1332,9 @@ packages: electron-to-chromium@1.5.349: resolution: {integrity: sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + entities@7.0.1: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} @@ -1538,10 +1557,6 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - fuse.js@7.3.0: - resolution: {integrity: sha512-plz8RVjfcDedTGfVngWH1jmJvBvAwi1v2jecfDerbEnMcmOYUEEwKFTHbNoCiYyzaK2Ws8lABkTCcRSqCY1q4w==} - engines: {node: '>=10'} - gaxios@6.7.1: resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} engines: {node: '>=14'} @@ -1558,6 +1573,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.5.0: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} @@ -2347,6 +2366,10 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + string-width@8.2.1: resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} engines: {node: '>=20'} @@ -2642,6 +2665,10 @@ packages: resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} engines: {node: '>=20'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + ws@7.5.10: resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} engines: {node: '>=8.3.0'} @@ -2674,6 +2701,10 @@ packages: resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} engines: {node: '>=16.0.0'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -2682,6 +2713,14 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -3827,6 +3866,12 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.3.0)(typescript@6.0.3))(eslint@10.3.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -4207,6 +4252,12 @@ snapshots: cli-width@4.1.0: {} + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + code-excerpt@4.0.0: dependencies: convert-to-spaces: 2.0.1 @@ -4315,6 +4366,8 @@ snapshots: electron-to-chromium@1.5.349: {} + emoji-regex@10.6.0: {} + entities@7.0.1: {} environment@1.1.0: {} @@ -4630,8 +4683,6 @@ snapshots: functions-have-names@1.2.3: {} - fuse.js@7.3.0: {} - gaxios@6.7.1: dependencies: extend: 3.0.2 @@ -4656,6 +4707,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + get-east-asian-width@1.5.0: {} get-intrinsic@1.3.0: @@ -5501,6 +5554,12 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + string-width@8.2.1: dependencies: get-east-asian-width: 1.5.0 @@ -5815,6 +5874,12 @@ snapshots: string-width: 8.2.1 strip-ansi: 7.2.0 + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + ws@7.5.10: {} ws@8.20.0: {} @@ -5826,10 +5891,23 @@ snapshots: xml-naming@0.1.0: {} + y18n@5.0.8: {} + yallist@3.1.1: {} yaml@2.9.0: {} + yargs-parser@22.0.0: {} + + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + yocto-queue@0.1.0: {} yoga-layout@3.2.1: {} diff --git a/src/options.ts b/src/options.ts index d26411d..cf9040b 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,3 +1,5 @@ +import yargs from "yargs/yargs"; + export type WizardOptions = { readonly apiUrl: string; readonly appUrl: string; @@ -12,68 +14,54 @@ export type ParsedArgs = { const DEFAULT_API_URL = "https://api.braintrust.dev"; const DEFAULT_APP_URL = "https://www.braintrust.dev"; -const HELP = `Usage: bt-wizard [options] - -Options: - --api-url Override API URL [env: BRAINTRUST_API_URL] - --app-url Override app URL [env: BRAINTRUST_APP_URL] - --ca-cert Path to PEM CA bundle [env: BRAINTRUST_CA_CERT; overrides SSL_CERT_FILE] - -h, --help Show help - -Environment: - CRANK_ENABLE_TELEMETRY=false Disable anonymous usage telemetry -`; +function buildParser(env: NodeJS.ProcessEnv) { + return yargs([]) + .scriptName("bt-wizard") + .usage("$0 [options]") + .option("api-url", { + type: "string", + description: "Override API URL", + default: env["BRAINTRUST_API_URL"] ?? DEFAULT_API_URL, + }) + .option("app-url", { + type: "string", + description: "Override app URL", + default: env["BRAINTRUST_APP_URL"] ?? DEFAULT_APP_URL, + }) + .option("ca-cert", { + type: "string", + description: "Path to PEM CA bundle", + default: env["BRAINTRUST_CA_CERT"] ?? env["SSL_CERT_FILE"], + }) + .epilog( + "Environment:\n CRANK_ENABLE_TELEMETRY=false Disable anonymous usage telemetry", + ) + .help() + .alias("h", "help") + .strict(); +} -export function helpText(): string { - return HELP; +export async function helpText( + env: NodeJS.ProcessEnv = process.env, +): Promise { + return buildParser(env).getHelp(); } -export function parseArgs( +export async function parseArgs( argv: readonly string[], env: NodeJS.ProcessEnv, -): ParsedArgs { - let apiUrl = env["BRAINTRUST_API_URL"] ?? DEFAULT_API_URL; - let appUrl = env["BRAINTRUST_APP_URL"] ?? DEFAULT_APP_URL; - let caCertPath = env["BRAINTRUST_CA_CERT"] ?? env["SSL_CERT_FILE"]; - let help = false; +): Promise { + const parser = buildParser(env); + const parsed = await parser.parseAsync([...argv]); - let i = 0; - while (i < argv.length) { - const arg = argv[i]; - const next = (): string => { - const v = argv[i + 1]; - if (v === undefined) { - throw new Error(`Missing value for ${arg}`); - } - i += 1; - return v; - }; - switch (arg) { - case "--api-url": - apiUrl = next(); - break; - case "--app-url": - appUrl = next(); - break; - case "--ca-cert": - caCertPath = next(); - break; - case "-h": - case "--help": - help = true; - break; - default: - throw new Error(`Unknown argument: ${arg}`); - } - i += 1; - } + const help = argv.includes("--help") || argv.includes("-h"); return { help, options: { - apiUrl: stripTrailingSlash(apiUrl), - appUrl: stripTrailingSlash(appUrl), - caCertPath: caCertPath || undefined, + apiUrl: stripTrailingSlash(parsed["api-url"] as string), + appUrl: stripTrailingSlash(parsed["app-url"] as string), + caCertPath: (parsed["ca-cert"] as string | undefined) || undefined, }, }; } From 1939000664ab009444710da4e0d77bc62cfbcfd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Wed, 13 May 2026 10:48:31 -0700 Subject: [PATCH 22/52] fix: remove manual help catching --- src/cli.ts | 7 +------ src/options.ts | 18 ++++-------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 652ffc3..7812e84 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -9,12 +9,7 @@ import { } from "./clack-wizard"; import { helpText, parseArgs } from "./options"; -const parsed = parseArgs(process.argv.slice(2), process.env); - -if (parsed.help) { - process.stdout.write(helpText()); - process.exit(0); -} +const parsed = await parseArgs(process.argv.slice(2), process.env); // `NODE_EXTRA_CA_CERTS` is read once at Node startup, so we can't apply it // in-process. If --ca-cert (or BRAINTRUST_CA_CERT / SSL_CERT_FILE) was set diff --git a/src/options.ts b/src/options.ts index cf9040b..638da74 100644 --- a/src/options.ts +++ b/src/options.ts @@ -6,11 +6,6 @@ export type WizardOptions = { readonly caCertPath: string | undefined; }; -export type ParsedArgs = { - readonly options: WizardOptions; - readonly help: boolean; -}; - const DEFAULT_API_URL = "https://api.braintrust.dev"; const DEFAULT_APP_URL = "https://www.braintrust.dev"; @@ -50,19 +45,14 @@ export async function helpText( export async function parseArgs( argv: readonly string[], env: NodeJS.ProcessEnv, -): Promise { +): Promise { const parser = buildParser(env); const parsed = await parser.parseAsync([...argv]); - const help = argv.includes("--help") || argv.includes("-h"); - return { - help, - options: { - apiUrl: stripTrailingSlash(parsed["api-url"] as string), - appUrl: stripTrailingSlash(parsed["app-url"] as string), - caCertPath: (parsed["ca-cert"] as string | undefined) || undefined, - }, + apiUrl: stripTrailingSlash(parsed["api-url"] as string), + appUrl: stripTrailingSlash(parsed["app-url"] as string), + caCertPath: (parsed["ca-cert"] as string | undefined) || undefined, }; } From c53aaf0fa865625618744787c7241354ffc616bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Wed, 13 May 2026 11:54:23 -0700 Subject: [PATCH 23/52] feat: azure, vertex, bedrock api key support --- src/providers.ts | 61 +++++++++++++++++++++++++++++++++++++----- test/providers.test.ts | 27 ++++++++++++++++--- 2 files changed, 78 insertions(+), 10 deletions(-) diff --git a/src/providers.ts b/src/providers.ts index d8017b9..92ebc7b 100644 --- a/src/providers.ts +++ b/src/providers.ts @@ -1,18 +1,31 @@ /** - * LLM providers shown in the wizard. The list mirrors the API-key entries in - * `AISecretTypes` from `@braintrust/proxy/schema` — the providers users - * configure with a single API key. Cloud providers (Bedrock, Vertex, Azure, - * Databricks) use multi-field credentials and are intentionally excluded. + * LLM providers shown in the wizard. * - * Drift is enforced by `test/providers.test.ts`, which imports - * `@braintrust/proxy/schema` (devDep) and asserts equality against this list. + * Single-key providers (most): set `envVar` — one API key is collected and + * passed to the harness as that env var. The list of single-key providers + * mirrors `AISecretTypes` from `@braintrust/proxy/schema`; drift is enforced + * by `test/providers.test.ts`. * - * If `custom: true`, no API key is requested and instrumentation is skipped. + * Multi-credential providers (Bedrock, Vertex, Azure): set `credentials` with + * one entry per env var. Each field is prompted individually; all are passed + * to the harness. + * + * If `custom: true`, no credentials are requested and instrumentation is + * skipped. */ +export type CredentialField = { + readonly envVar: string; + readonly label: string; + readonly secret?: boolean; +}; + export type LlmProvider = { readonly id: string; readonly label: string; + /** Single-key providers: the env var for the API key. */ readonly envVar?: string; + /** Multi-credential providers: one entry per env var to collect. */ + readonly credentials?: readonly CredentialField[]; readonly custom?: boolean; }; @@ -30,5 +43,39 @@ export const LLM_PROVIDERS: readonly LlmProvider[] = [ { id: "cerebras", label: "Cerebras", envVar: "CEREBRAS_API_KEY" }, { id: "replicate", label: "Replicate", envVar: "REPLICATE_API_KEY" }, { id: "baseten", label: "Baseten", envVar: "BASETEN_API_KEY" }, + { + id: "bedrock", + label: "AWS Bedrock", + credentials: [ + { envVar: "AWS_ACCESS_KEY_ID", label: "Access Key ID" }, + { + envVar: "AWS_SECRET_ACCESS_KEY", + label: "Secret Access Key", + secret: true, + }, + { envVar: "AWS_REGION", label: "Region" }, + ], + }, + { + id: "vertex", + label: "Google Vertex AI", + credentials: [ + { envVar: "GOOGLE_CLOUD_PROJECT", label: "Project ID" }, + { envVar: "GOOGLE_CLOUD_LOCATION", label: "Location (e.g. us-central1)" }, + { + envVar: "GOOGLE_APPLICATION_CREDENTIALS", + label: "Path to service account JSON", + }, + ], + }, + { + id: "azure", + label: "Azure OpenAI", + credentials: [ + { envVar: "AZURE_OPENAI_API_KEY", label: "API Key", secret: true }, + { envVar: "AZURE_OPENAI_ENDPOINT", label: "Endpoint URL" }, + { envVar: "AZURE_OPENAI_DEPLOYMENT", label: "Deployment name" }, + ], + }, { id: "custom", label: "Custom (self-hosted, skip API key)", custom: true }, ]; diff --git a/test/providers.test.ts b/test/providers.test.ts index 5c8694a..376794d 100644 --- a/test/providers.test.ts +++ b/test/providers.test.ts @@ -8,12 +8,20 @@ import { LLM_PROVIDERS } from "../src/providers"; // once the corresponding proxy release ships. const KNOWN_AHEAD: ReadonlySet = new Set(["baseten"]); +// Multi-credential providers use several env vars instead of a single API key +// and are intentionally absent from AISecretTypes. +const MULTI_CREDENTIAL: ReadonlySet = new Set([ + "bedrock", + "vertex", + "azure", +]); + function envVarToProviderId(envVar: string): string { return envVar.toLowerCase().replace(/_api_key$/, ""); } describe("LLM_PROVIDERS sync with @braintrust/proxy/schema", () => { - it("matches AISecretTypes (plus the custom entry and KNOWN_AHEAD)", () => { + it("matches AISecretTypes (plus the custom entry, KNOWN_AHEAD, and MULTI_CREDENTIAL)", () => { const fromSchema = new Set( Object.keys(AISecretTypes).map(envVarToProviderId), ); @@ -25,7 +33,10 @@ describe("LLM_PROVIDERS sync with @braintrust/proxy/schema", () => { (id) => !fromWizard.has(id), ); const extraInWizard = [...fromWizard].filter( - (id) => !fromSchema.has(id) && !KNOWN_AHEAD.has(id), + (id) => + !fromSchema.has(id) && + !KNOWN_AHEAD.has(id) && + !MULTI_CREDENTIAL.has(id), ); expect(missingFromWizard).toEqual([]); @@ -36,13 +47,23 @@ describe("LLM_PROVIDERS sync with @braintrust/proxy/schema", () => { expect(LLM_PROVIDERS.some((p) => p.custom === true)).toBe(true); }); - it("uses each AISecretTypes env var verbatim", () => { + it("uses each AISecretTypes env var verbatim for single-key providers", () => { const envVars = new Set(Object.keys(AISecretTypes)); for (const provider of LLM_PROVIDERS) { if (provider.custom) continue; if (KNOWN_AHEAD.has(provider.id)) continue; + if (MULTI_CREDENTIAL.has(provider.id)) continue; expect(provider.envVar).toBeDefined(); expect(envVars.has(provider.envVar!)).toBe(true); } }); + + it("multi-credential providers have at least one credential field each", () => { + for (const id of MULTI_CREDENTIAL) { + const provider = LLM_PROVIDERS.find((p) => p.id === id); + expect(provider).toBeDefined(); + expect(provider!.credentials).toBeDefined(); + expect(provider!.credentials!.length).toBeGreaterThan(0); + } + }); }); From 3418d7552c500a0c2f43ffa8779bb2a5c4df78e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Wed, 13 May 2026 12:07:15 -0700 Subject: [PATCH 24/52] refactor: tighten LlmProvider type and test quality - Replace loose optional fields with a discriminated union so TypeScript catches providers that have neither envVar, credentials, nor custom at compile time - Set secret: false on GOOGLE_APPLICATION_CREDENTIALS so it uses a plain text prompt instead of a masked password field - Derive MULTI_CREDENTIAL set in test from the source list to prevent drift when new multi-credential providers are added - Drop redundant envVar assertion (now enforced by the union type) Co-Authored-By: Claude Sonnet 4.6 --- src/providers.ts | 28 +++++++++++++++++++--------- test/providers.test.ts | 22 +++++++++------------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/providers.ts b/src/providers.ts index 92ebc7b..5cde186 100644 --- a/src/providers.ts +++ b/src/providers.ts @@ -19,15 +19,24 @@ export type CredentialField = { readonly secret?: boolean; }; -export type LlmProvider = { - readonly id: string; - readonly label: string; - /** Single-key providers: the env var for the API key. */ - readonly envVar?: string; - /** Multi-credential providers: one entry per env var to collect. */ - readonly credentials?: readonly CredentialField[]; - readonly custom?: boolean; -}; +type LlmProviderBase = { readonly id: string; readonly label: string }; + +export type LlmProvider = + | (LlmProviderBase & { + readonly envVar: string; + readonly credentials?: never; + readonly custom?: never; + }) + | (LlmProviderBase & { + readonly credentials: readonly CredentialField[]; + readonly envVar?: never; + readonly custom?: never; + }) + | (LlmProviderBase & { + readonly custom: true; + readonly envVar?: never; + readonly credentials?: never; + }); export const LLM_PROVIDERS: readonly LlmProvider[] = [ { id: "openai", label: "OpenAI", envVar: "OPENAI_API_KEY" }, @@ -65,6 +74,7 @@ export const LLM_PROVIDERS: readonly LlmProvider[] = [ { envVar: "GOOGLE_APPLICATION_CREDENTIALS", label: "Path to service account JSON", + secret: false, }, ], }, diff --git a/test/providers.test.ts b/test/providers.test.ts index 376794d..3ec39bd 100644 --- a/test/providers.test.ts +++ b/test/providers.test.ts @@ -10,11 +10,9 @@ const KNOWN_AHEAD: ReadonlySet = new Set(["baseten"]); // Multi-credential providers use several env vars instead of a single API key // and are intentionally absent from AISecretTypes. -const MULTI_CREDENTIAL: ReadonlySet = new Set([ - "bedrock", - "vertex", - "azure", -]); +const MULTI_CREDENTIAL: ReadonlySet = new Set( + LLM_PROVIDERS.filter((p) => p.credentials !== undefined).map((p) => p.id), +); function envVarToProviderId(envVar: string): string { return envVar.toLowerCase().replace(/_api_key$/, ""); @@ -53,17 +51,15 @@ describe("LLM_PROVIDERS sync with @braintrust/proxy/schema", () => { if (provider.custom) continue; if (KNOWN_AHEAD.has(provider.id)) continue; if (MULTI_CREDENTIAL.has(provider.id)) continue; - expect(provider.envVar).toBeDefined(); - expect(envVars.has(provider.envVar!)).toBe(true); + expect(envVars.has(provider.envVar)).toBe(true); } }); - it("multi-credential providers have at least one credential field each", () => { - for (const id of MULTI_CREDENTIAL) { - const provider = LLM_PROVIDERS.find((p) => p.id === id); - expect(provider).toBeDefined(); - expect(provider!.credentials).toBeDefined(); - expect(provider!.credentials!.length).toBeGreaterThan(0); + it("multi-credential providers each have at least one credential field", () => { + for (const provider of LLM_PROVIDERS.filter( + (p) => p.credentials !== undefined, + )) { + expect(provider.credentials!.length).toBeGreaterThan(0); } }); }); From 06c40d0cf1eb5f6f3b26cab770e87bccb159a109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Wed, 13 May 2026 09:57:33 -0700 Subject: [PATCH 25/52] chore: use yargs to generate the help --- src/options.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/options.ts b/src/options.ts index 638da74..b18f91c 100644 --- a/src/options.ts +++ b/src/options.ts @@ -28,17 +28,13 @@ function buildParser(env: NodeJS.ProcessEnv) { description: "Path to PEM CA bundle", default: env["BRAINTRUST_CA_CERT"] ?? env["SSL_CERT_FILE"], }) - .epilog( - "Environment:\n CRANK_ENABLE_TELEMETRY=false Disable anonymous usage telemetry", - ) + .epilog("Environment:\n CRANK_ENABLE_TELEMETRY=false Disable anonymous usage telemetry") .help() .alias("h", "help") .strict(); } -export async function helpText( - env: NodeJS.ProcessEnv = process.env, -): Promise { +export async function helpText(env: NodeJS.ProcessEnv = process.env): Promise { return buildParser(env).getHelp(); } From 56dc57e00d67f069e5bb4f875451fbdadf6fc74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Thu, 14 May 2026 09:41:42 -0700 Subject: [PATCH 26/52] chore: formatting --- src/options.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/options.ts b/src/options.ts index b18f91c..638da74 100644 --- a/src/options.ts +++ b/src/options.ts @@ -28,13 +28,17 @@ function buildParser(env: NodeJS.ProcessEnv) { description: "Path to PEM CA bundle", default: env["BRAINTRUST_CA_CERT"] ?? env["SSL_CERT_FILE"], }) - .epilog("Environment:\n CRANK_ENABLE_TELEMETRY=false Disable anonymous usage telemetry") + .epilog( + "Environment:\n CRANK_ENABLE_TELEMETRY=false Disable anonymous usage telemetry", + ) .help() .alias("h", "help") .strict(); } -export async function helpText(env: NodeJS.ProcessEnv = process.env): Promise { +export async function helpText( + env: NodeJS.ProcessEnv = process.env, +): Promise { return buildParser(env).getHelp(); } From 063ec6d54ba3f25029ce888ac35735a5870a018a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Thu, 14 May 2026 10:42:58 -0700 Subject: [PATCH 27/52] chore: prompt comment --- src/prompt.ts | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/prompt.ts b/src/prompt.ts index ce873a6..8741351 100644 --- a/src/prompt.ts +++ b/src/prompt.ts @@ -1,17 +1,11 @@ import type { DetectedLanguage } from "./language-detect"; -/** - * The bt-wizard agent prompt. Adapted from - * /workspace/bt-main/skills/sdk-install/instrument-task.md, with the three - * Three sections handled deterministically by the wizard are omitted: - * - "Verify API Key (Install Precondition)" - * - "Verify in Braintrust (CRITICAL)" - * - "Post-Success Verification and Next Steps" - * - * Placeholders are filled in by `renderPrompt` using the detected language. - * `{SDK_INSTALL_DIR}` resolves to the public docs URL for the language - * (the harness fetches install guides via the `curl` tool, since bash/python - * aren't available). +/* + * spark agent prompt + * Adapt the link to the docs given depending on the language to instrument + * Instructs the agent to say INSTRUMENTATION_(IN)COMPLETE after it has completed the task. + * The harness catches these words and close the agent. + * This is non deterministic but knowing instrumentation is done is non deterministic too, I didn't think of a better solution. */ const TEMPLATE = `# Braintrust SDK Installation (Agent Instructions) @@ -65,7 +59,7 @@ If you do not know how to run the app, ask the user and wait for the response be --- -### 6. Final Summary +### 5. Final Summary Summarize: From 5db0c0898b344c42cbc6a05af3aa06c1d8993f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Wed, 13 May 2026 11:01:30 -0700 Subject: [PATCH 28/52] feat: collect multi-credential provider fields in wizard flow Replace the single password prompt for provider API keys with collectCredentials(), which loops over provider.credentials for multi-field providers (Bedrock, Vertex, Azure) and falls back to a single secret prompt for single-key providers. Co-Authored-By: Claude Sonnet 4.6 --- test/clack-wizard.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/clack-wizard.test.ts b/test/clack-wizard.test.ts index 121eaae..9cbc0ab 100644 --- a/test/clack-wizard.test.ts +++ b/test/clack-wizard.test.ts @@ -147,7 +147,6 @@ function buildDeps(args: { }, prompts: args.prompts, authClient: stubAuth, - fuzzy: async ({ choices }) => choices[0]!.value, openBrowser: async () => true, }; } From f0e9217a02718eaa1d053647bb9683df1519412f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:45:58 -0700 Subject: [PATCH 29/52] feat: scaffold bt-wizard-harness workspace package --- package.json | 1 + packages/bt-wizard-harness/package.json | 30 + pnpm-lock.yaml | 1256 ++++++++++++++++++++++- pnpm-workspace.yaml | 2 + 4 files changed, 1240 insertions(+), 49 deletions(-) create mode 100644 packages/bt-wizard-harness/package.json create mode 100644 pnpm-workspace.yaml diff --git a/package.json b/package.json index db666ed..b4d2a00 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "format:check": "prettier --check ." }, "dependencies": { + "@braintrust/bt-wizard-harness": "workspace:*", "@inquirer/search": "4.1.8", "@clack/prompts": "1.3.0", "@inquirer/search": "4.1.8", diff --git a/packages/bt-wizard-harness/package.json b/packages/bt-wizard-harness/package.json new file mode 100644 index 0000000..932d2ac --- /dev/null +++ b/packages/bt-wizard-harness/package.json @@ -0,0 +1,30 @@ +{ + "name": "@braintrust/bt-wizard-harness", + "version": "0.1.0", + "private": true, + "description": "Restricted pi-coding-agent harness for the bt-wizard CLI: write/edit/grep + web search + bt only.", + "type": "module", + "bin": { + "bt-wizard-harness": "./bin/bt-wizard-harness.mjs" + }, + "files": [ + "bin", + "extensions" + ], + "dependencies": { + "@mariozechner/pi-coding-agent": "*", + "@mariozechner/pi-tui": "*", + "node-pty": "^1.1.0", + "typebox": "^1.1.24" + }, + "pi": { + "extensions": [ + "./extensions/path-guard.ts", + "./extensions/bt-tool.ts", + "./extensions/curl-tool.ts", + "./extensions/git-tool.ts", + "./extensions/package-manager-tool.ts", + "./extensions/request-command-tool.ts" + ] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15ea188..4259693 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@braintrust/bt-wizard-harness': + specifier: workspace:* + version: link:packages/bt-wizard-harness '@clack/prompts': specifier: 1.3.0 version: 1.3.0 @@ -41,7 +44,7 @@ importers: version: 0.0.9(react@19.2.5)(solid-js@1.9.12)(svelte@4.2.20)(vue@3.5.34(typescript@6.0.3))(ws@8.20.0) '@eslint/js': specifier: 10.0.1 - version: 10.0.1(eslint@10.3.0) + version: 10.0.1(eslint@10.3.0(jiti@2.7.0)) '@types/node': specifier: 24.12.2 version: 24.12.2 @@ -53,16 +56,16 @@ importers: version: 17.0.35 eslint: specifier: 10.3.0 - version: 10.3.0 + version: 10.3.0(jiti@2.7.0) eslint-config-prettier: specifier: 10.1.8 - version: 10.1.8(eslint@10.3.0) + version: 10.1.8(eslint@10.3.0(jiti@2.7.0)) eslint-plugin-react: specifier: 7.37.5 - version: 7.37.5(eslint@10.3.0) + version: 7.37.5(eslint@10.3.0(jiti@2.7.0)) eslint-plugin-react-hooks: specifier: 7.1.1 - version: 7.1.1(eslint@10.3.0) + version: 7.1.1(eslint@10.3.0(jiti@2.7.0)) globals: specifier: 17.6.0 version: 17.6.0 @@ -77,10 +80,25 @@ importers: version: 6.0.3 typescript-eslint: specifier: 8.59.2 - version: 8.59.2(eslint@10.3.0)(typescript@6.0.3) + version: 8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) vitest: specifier: 4.1.5 - version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(vite@8.0.10(@types/node@24.12.2)(yaml@2.9.0)) + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(vite@8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0)) + + packages/bt-wizard-harness: + dependencies: + '@mariozechner/pi-coding-agent': + specifier: '*' + version: 0.73.1(ws@8.20.0)(zod@4.4.3) + '@mariozechner/pi-tui': + specifier: '*' + version: 0.73.1 + node-pty: + specifier: ^1.1.0 + version: 1.1.0 + typebox: + specifier: ^1.1.24 + version: 1.1.38 packages: @@ -95,6 +113,15 @@ packages: '@anthropic-ai/sdk@0.39.0': resolution: {integrity: sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==} + '@anthropic-ai/sdk@0.91.1': + resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + '@apidevtools/json-schema-ref-parser@11.9.3': resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} engines: {node: '>= 16'} @@ -308,6 +335,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -320,6 +351,9 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@borewit/text-codec@0.2.2': + resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + '@braintrust/core@0.0.87': resolution: {integrity: sha512-yKo+2McKBcluVUq+5qoYI7QfGvqZ7c0ftTOmnRSToBR2RqGyHkClnnQZ3+M8Guuk9NEKJu92UMTTaR9AonIvvA==} @@ -389,6 +423,15 @@ packages: resolution: {integrity: sha512-eaEncWt875H7046T04mOpxpHJUM+jLIljEf+5QctRyOeChylE/nhpwm1bZWTRWoOu/t46R9r+PmgsJFhTpE7tQ==} engines: {node: '>=18.0.0'} + '@google/genai@1.52.0': + resolution: {integrity: sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.25.2 + peerDependenciesMeta: + '@modelcontextprotocol/sdk': + optional: true + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -463,6 +506,99 @@ packages: '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@mariozechner/clipboard-darwin-arm64@0.3.2': + resolution: {integrity: sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@mariozechner/clipboard-darwin-universal@0.3.2': + resolution: {integrity: sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==} + engines: {node: '>= 10'} + os: [darwin] + + '@mariozechner/clipboard-darwin-x64@0.3.2': + resolution: {integrity: sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@mariozechner/clipboard-linux-arm64-gnu@0.3.2': + resolution: {integrity: sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@mariozechner/clipboard-linux-arm64-musl@0.3.2': + resolution: {integrity: sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@mariozechner/clipboard-linux-riscv64-gnu@0.3.2': + resolution: {integrity: sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@mariozechner/clipboard-linux-x64-gnu@0.3.2': + resolution: {integrity: sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@mariozechner/clipboard-linux-x64-musl@0.3.2': + resolution: {integrity: sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@mariozechner/clipboard-win32-arm64-msvc@0.3.2': + resolution: {integrity: sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@mariozechner/clipboard-win32-x64-msvc@0.3.2': + resolution: {integrity: sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@mariozechner/clipboard@0.3.5': + resolution: {integrity: sha512-D3F+UrU9CR7roJt0zDLp6Oc+4/KlLDIrN4frH+6V90SJNW2KKUec1oCQIPaaDjCqeOsQyX9dyqYbImIQIM45PA==} + engines: {node: '>= 10'} + + '@mariozechner/pi-agent-core@0.73.1': + resolution: {integrity: sha512-Y/KVOhuKSgRQgYBlwmRtO2gPkUcoavOSqGF9bpQIINvNZvc19k6Z1H3bFDTce3Vp5ApMmTsfLH3+tNvOg75fAQ==} + engines: {node: '>=20.0.0'} + deprecated: please use @earendil-works/pi-agent-core instead going forward + + '@mariozechner/pi-ai@0.73.1': + resolution: {integrity: sha512-Jh4lXawZYuC83HzSIYuVum9NBqJD49i4JOt3H96cGW/924cwJMOyUs1Mv/e4QPzTXnzrqMoGviNQnvGgSu1LSg==} + engines: {node: '>=20.0.0'} + deprecated: please use @earendil-works/pi-ai instead going forward + hasBin: true + + '@mariozechner/pi-coding-agent@0.73.1': + resolution: {integrity: sha512-gXQh3SaZmWTfVMc4Ao5+LGbVeKvzyO7tolok0nLsZgq9nGjZx/EEU3NM8C+qUnB4Nvs2rswG5qOVgLzQkq0fHQ==} + engines: {node: '>=20.6.0'} + deprecated: please use @earendil-works/pi-coding-agent instead going forward + hasBin: true + + '@mariozechner/pi-tui@0.73.1': + resolution: {integrity: sha512-ybVsRnUbzQRtbocltJ2OXb2QogrO67N2BlUyKjZz9BHcZYiDJtNkcKQockxDjsVvDc0uBCLDX6iZJoBElBd8fw==} + engines: {node: '>=20.0.0'} + deprecated: please use @earendil-works/pi-tui instead going forward + + '@mistralai/mistralai@2.2.1': + resolution: {integrity: sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==} + '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: @@ -504,6 +640,36 @@ packages: '@oxc-project/types@0.128.0': resolution: {integrity: sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.1': + resolution: {integrity: sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + '@rolldown/binding-android-arm64@1.0.0-rc.17': resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -700,6 +866,9 @@ packages: '@rolldown/pluginutils@1.0.0-rc.18': resolution: {integrity: sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==} + '@silvia-odwyer/photon-node@0.3.4': + resolution: {integrity: sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==} + '@smithy/config-resolver@4.5.1': resolution: {integrity: sha512-abXk3LhODsvRHsk0ZS9ztrg/fZatTa9Z/z4pgx65YSLR+rY6kvUG/1IgcDKEUciR8MfdnkT5oPeHJTy/HhzDIQ==} engines: {node: '>=18.0.0'} @@ -871,6 +1040,16 @@ packages: peerDependencies: react: ^18 || ^19 + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} @@ -889,6 +1068,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mime-types@2.1.4': + resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==} + '@types/node-fetch@2.6.13': resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} @@ -901,12 +1083,18 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@typescript-eslint/eslint-plugin@8.59.2': resolution: {integrity: sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1071,14 +1259,25 @@ packages: resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-regex@6.2.2: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1118,6 +1317,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} @@ -1152,6 +1355,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + basic-ftp@5.3.1: + resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==} + engines: {node: '>=10.0.0'} + bignumber.js@9.3.1: resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} @@ -1170,6 +1377,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} @@ -1199,6 +1409,10 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -1211,6 +1425,11 @@ packages: resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + cli-truncate@6.0.0: resolution: {integrity: sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA==} engines: {node: '>=22'} @@ -1219,6 +1438,9 @@ packages: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + cliui@9.0.1: resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} engines: {node: '>=20'} @@ -1230,6 +1452,13 @@ packages: code-red@1.0.4: resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1259,6 +1488,14 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -1306,6 +1543,10 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1318,6 +1559,10 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -1335,6 +1580,12 @@ packages: emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + entities@7.0.1: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} @@ -1393,6 +1644,11 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + eslint-config-prettier@10.1.8: resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} hasBin: true @@ -1437,6 +1693,11 @@ packages: resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} @@ -1478,6 +1739,11 @@ packages: 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-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1503,6 +1769,9 @@ packages: resolution: {integrity: sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==} hasBin: true + 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'} @@ -1512,10 +1781,18 @@ packages: picomatch: optional: true + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-type@21.3.4: + resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==} + engines: {node: '>=20'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1542,6 +1819,10 @@ packages: resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} engines: {node: '>= 12.20'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1561,10 +1842,18 @@ packages: resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} engines: {node: '>=14'} + gaxios@7.1.4: + resolution: {integrity: sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==} + engines: {node: '>=18'} + gcp-metadata@6.1.1: resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==} engines: {node: '>=14'} + gcp-metadata@8.1.2: + resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} + engines: {node: '>=18'} + generator-function@2.0.1: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} @@ -1589,14 +1878,26 @@ packages: 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-symbol-description@1.1.0: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} + get-uri@6.0.5: + resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} + engines: {node: '>= 14'} + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + globals@17.6.0: resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} engines: {node: '>=18'} @@ -1605,6 +1906,10 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} + google-auth-library@10.6.2: + resolution: {integrity: sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==} + engines: {node: '>=18'} + google-auth-library@9.15.1: resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} engines: {node: '>=14'} @@ -1613,10 +1918,17 @@ packages: resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} engines: {node: '>=14'} + google-logging-utils@1.1.3: + resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} + engines: {node: '>=14'} + 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==} + gtoken@7.1.0: resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} engines: {node: '>=14.0.0'} @@ -1625,6 +1937,10 @@ packages: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -1650,6 +1966,17 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + hosted-git-info@9.0.3: + resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} + engines: {node: ^20.17.0 || >=22.9.0} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -1657,6 +1984,9 @@ packages: humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1690,6 +2020,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -1735,6 +2069,10 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-fullwidth-code-point@5.1.0: resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} engines: {node: '>=18'} @@ -1830,6 +2168,10 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + jose@5.10.0: resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} @@ -1851,6 +2193,10 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -1879,6 +2225,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + koffi@2.16.2: + resolution: {integrity: sha512-owU0MRwv6xkrVqCd+33uw6BaYppkTRXbO/rVdJNI2dvZG0gzyRhYwW25eWtc5pauwK8TGh3AbkFONSezdykfSA==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1985,16 +2334,32 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@11.3.6: + resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -2006,10 +2371,18 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -2021,6 +2394,10 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2028,6 +2405,9 @@ packages: resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} engines: {node: ^20.17.0 || >=22.9.0} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2041,6 +2421,13 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + netmask@2.1.1: + resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==} + engines: {node: '>= 0.4.0'} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -2059,6 +2446,13 @@ packages: encoding: optional: true + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + node-pty@1.1.0: + resolution: {integrity: sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==} + node-releases@2.0.38: resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} @@ -2093,6 +2487,9 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + 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'} @@ -2113,6 +2510,18 @@ packages: zod: optional: true + openai@6.26.0: + resolution: {integrity: sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + openapi3-ts@4.5.0: resolution: {integrity: sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==} @@ -2132,6 +2541,30 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + + parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + partial-json@0.1.7: + resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} + patch-console@2.0.0: resolution: {integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2151,9 +2584,16 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} @@ -2188,6 +2628,23 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + + protobufjs@7.5.8: + resolution: {integrity: sha512-dvpCIeLPbXZS/Ete7yLaO7RenOdken2NHKykBXbsaGxZT0UTltcarBciw+A78SRQs9iMAAVpsYA+l8b1hTePIA==} + engines: {node: '>=12.0.0'} + + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2216,6 +2673,10 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + resolve@2.0.0-next.6: resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} engines: {node: '>= 0.4'} @@ -2225,6 +2686,14 @@ packages: resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + rolldown@1.0.0-rc.17: resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2333,6 +2802,18 @@ packages: resolution: {integrity: sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==} engines: {node: '>=22'} + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.9: + resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + solid-js@1.9.12: resolution: {integrity: sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==} @@ -2347,6 +2828,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + sswr@2.0.0: resolution: {integrity: sha512-mV0kkeBHcjcb0M5NqKtKVg/uTIYNlIIniyDfSGrSfxpEdM9C365jK0z55pl9K0xAkNTJi2OAOVFQpgMPUk+V0w==} peerDependencies: @@ -2366,6 +2851,10 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -2393,6 +2882,10 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + strip-ansi@7.2.0: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} @@ -2400,6 +2893,14 @@ packages: strnum@2.3.0: resolution: {integrity: sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==} + strtok3@10.3.5: + resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} + engines: {node: '>=18'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -2433,6 +2934,13 @@ packages: resolution: {integrity: sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==} engines: {node: '>=18'} + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2448,9 +2956,16 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} + engines: {node: '>=14.16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -2468,6 +2983,9 @@ packages: resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} engines: {node: '>=20'} + typebox@1.1.38: + resolution: {integrity: sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -2496,6 +3014,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -2506,6 +3028,10 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + engines: {node: '>=20.18.1'} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -2520,6 +3046,10 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + uuid@14.0.0: + resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} + hasBin: true + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). @@ -2617,6 +3147,10 @@ packages: typescript: optional: true + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + web-streams-polyfill@4.0.0-beta.3: resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} engines: {node: '>= 14'} @@ -2665,10 +3199,17 @@ packages: resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} engines: {node: '>=20'} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + 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@7.5.10: resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} engines: {node: '>=8.3.0'} @@ -2713,14 +3254,25 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + yargs-parser@22.0.0: resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + yargs@18.0.0: resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2769,6 +3321,12 @@ snapshots: transitivePeerDependencies: - encoding + '@anthropic-ai/sdk@0.91.1(zod@4.4.3)': + dependencies: + json-schema-to-ts: 3.1.1 + optionalDependencies: + zod: 4.4.3 + '@apidevtools/json-schema-ref-parser@11.9.3': dependencies: '@jsdevtools/ono': 7.1.3 @@ -3278,6 +3836,8 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/runtime@7.29.2': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -3301,6 +3861,8 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@borewit/text-codec@0.2.2': {} + '@braintrust/core@0.0.87': dependencies: '@asteasolutions/zod-to-openapi': 6.4.0(zod@3.25.76) @@ -3371,9 +3933,9 @@ snapshots: tslib: 2.8.1 optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.3.0)': + '@eslint-community/eslint-utils@4.9.1(eslint@10.3.0(jiti@2.7.0))': dependencies: - eslint: 10.3.0 + eslint: 10.3.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -3394,9 +3956,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.3.0)': + '@eslint/js@10.0.1(eslint@10.3.0(jiti@2.7.0))': optionalDependencies: - eslint: 10.3.0 + eslint: 10.3.0(jiti@2.7.0) '@eslint/object-schema@3.0.5': {} @@ -3417,6 +3979,17 @@ snapshots: - supports-color - utf-8-validate + '@google/genai@1.52.0': + dependencies: + google-auth-library: 10.6.2 + p-retry: 4.6.2 + protobufjs: 7.5.8 + ws: 8.20.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -3482,14 +4055,146 @@ snapshots: '@jsdevtools/ono@7.1.3': {} - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 + '@mariozechner/clipboard-darwin-arm64@0.3.2': optional: true - '@nodable/entities@2.1.0': {} + '@mariozechner/clipboard-darwin-universal@0.3.2': + optional: true + + '@mariozechner/clipboard-darwin-x64@0.3.2': + optional: true + + '@mariozechner/clipboard-linux-arm64-gnu@0.3.2': + optional: true + + '@mariozechner/clipboard-linux-arm64-musl@0.3.2': + optional: true + + '@mariozechner/clipboard-linux-riscv64-gnu@0.3.2': + optional: true + + '@mariozechner/clipboard-linux-x64-gnu@0.3.2': + optional: true + + '@mariozechner/clipboard-linux-x64-musl@0.3.2': + optional: true + + '@mariozechner/clipboard-win32-arm64-msvc@0.3.2': + optional: true + + '@mariozechner/clipboard-win32-x64-msvc@0.3.2': + optional: true + + '@mariozechner/clipboard@0.3.5': + optionalDependencies: + '@mariozechner/clipboard-darwin-arm64': 0.3.2 + '@mariozechner/clipboard-darwin-universal': 0.3.2 + '@mariozechner/clipboard-darwin-x64': 0.3.2 + '@mariozechner/clipboard-linux-arm64-gnu': 0.3.2 + '@mariozechner/clipboard-linux-arm64-musl': 0.3.2 + '@mariozechner/clipboard-linux-riscv64-gnu': 0.3.2 + '@mariozechner/clipboard-linux-x64-gnu': 0.3.2 + '@mariozechner/clipboard-linux-x64-musl': 0.3.2 + '@mariozechner/clipboard-win32-arm64-msvc': 0.3.2 + '@mariozechner/clipboard-win32-x64-msvc': 0.3.2 + optional: true + + '@mariozechner/pi-agent-core@0.73.1(ws@8.20.0)(zod@4.4.3)': + dependencies: + '@mariozechner/pi-ai': 0.73.1(ws@8.20.0)(zod@4.4.3) + typebox: 1.1.38 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - aws-crt + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@mariozechner/pi-ai@0.73.1(ws@8.20.0)(zod@4.4.3)': + dependencies: + '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) + '@aws-sdk/client-bedrock-runtime': 3.1045.0 + '@google/genai': 1.52.0 + '@mistralai/mistralai': 2.2.1 + chalk: 5.6.2 + openai: 6.26.0(ws@8.20.0)(zod@4.4.3) + partial-json: 0.1.7 + proxy-agent: 6.5.0 + typebox: 1.1.38 + undici: 7.25.0 + zod-to-json-schema: 3.25.2(zod@4.4.3) + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - aws-crt + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@mariozechner/pi-coding-agent@0.73.1(ws@8.20.0)(zod@4.4.3)': + dependencies: + '@mariozechner/pi-agent-core': 0.73.1(ws@8.20.0)(zod@4.4.3) + '@mariozechner/pi-ai': 0.73.1(ws@8.20.0)(zod@4.4.3) + '@mariozechner/pi-tui': 0.73.1 + '@silvia-odwyer/photon-node': 0.3.4 + chalk: 5.6.2 + cli-highlight: 2.1.11 + diff: 8.0.4 + extract-zip: 2.0.1 + file-type: 21.3.4 + glob: 13.0.6 + hosted-git-info: 9.0.3 + ignore: 7.0.5 + jiti: 2.7.0 + marked: 15.0.12 + minimatch: 10.2.5 + proper-lockfile: 4.1.2 + strip-ansi: 7.2.0 + typebox: 1.1.38 + undici: 7.25.0 + uuid: 14.0.0 + yaml: 2.9.0 + optionalDependencies: + '@mariozechner/clipboard': 0.3.5 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - aws-crt + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@mariozechner/pi-tui@0.73.1': + dependencies: + '@types/mime-types': 2.1.4 + chalk: 5.6.2 + get-east-asian-width: 1.5.0 + marked: 15.0.12 + mime-types: 3.0.2 + optionalDependencies: + koffi: 2.16.2 + + '@mistralai/mistralai@2.2.1': + dependencies: + ws: 8.20.0 + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@nodable/entities@2.1.0': {} '@opentelemetry/api@1.9.1': {} @@ -3516,6 +4221,29 @@ snapshots: '@oxc-project/types@0.128.0': {} + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.1 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.1': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + '@rolldown/binding-android-arm64@1.0.0-rc.17': optional: true @@ -3618,6 +4346,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.18': {} + '@silvia-odwyer/photon-node@0.3.4': {} + '@smithy/config-resolver@4.5.1': dependencies: '@smithy/core': 3.24.1 @@ -3831,6 +4561,17 @@ snapshots: '@tanstack/query-core': 5.100.9 react: 19.2.5 + '@tokenizer/inflate@0.4.1': + dependencies: + debug: 4.4.3 + token-types: 6.1.2 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + + '@tootallnate/quickjs-emscripten@0.23.0': {} + '@tybys/wasm-util@0.10.2': dependencies: tslib: 2.8.1 @@ -3849,6 +4590,8 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/mime-types@2.1.4': {} + '@types/node-fetch@2.6.13': dependencies: '@types/node': 24.12.2 @@ -3866,21 +4609,28 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/retry@0.12.0': {} + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.35': dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.3.0)(typescript@6.0.3))(eslint@10.3.0)(typescript@6.0.3)': + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 24.12.2 + optional: true + + '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.2(eslint@10.3.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.59.2 - '@typescript-eslint/type-utils': 8.59.2(eslint@10.3.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.2(eslint@10.3.0)(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.59.2 - eslint: 10.3.0 + eslint: 10.3.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -3888,14 +4638,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.2(eslint@10.3.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.59.2 '@typescript-eslint/types': 8.59.2 '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.59.2 debug: 4.4.3 - eslint: 10.3.0 + eslint: 10.3.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -3918,13 +4668,13 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.59.2(eslint@10.3.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.59.2 '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.2(eslint@10.3.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 10.3.0 + eslint: 10.3.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -3947,13 +4697,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.2(eslint@10.3.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.59.2 '@typescript-eslint/types': 8.59.2 '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3) - eslint: 10.3.0 + eslint: 10.3.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -3972,13 +4722,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@24.12.2)(yaml@2.9.0))': + '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.5 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.10(@types/node@24.12.2)(yaml@2.9.0) + vite: 8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0) '@vitest/pretty-format@4.1.5': dependencies: @@ -4100,10 +4850,18 @@ snapshots: dependencies: environment: 1.1.0 + ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + ansi-styles@6.2.3: {} + any-promise@1.3.0: {} + argparse@2.0.1: {} aria-query@5.3.2: {} @@ -4167,6 +4925,10 @@ snapshots: assertion-error@2.0.1: {} + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + async-function@1.0.0: {} asynckit@0.4.0: {} @@ -4187,6 +4949,8 @@ snapshots: baseline-browser-mapping@2.10.27: {} + basic-ftp@5.3.1: {} + bignumber.js@9.3.1: {} bowser@2.14.1: {} @@ -4208,6 +4972,8 @@ snapshots: node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) + buffer-crc32@0.2.13: {} + buffer-equal-constant-time@1.0.1: {} bundle-name@4.1.0: @@ -4237,6 +5003,11 @@ snapshots: chai@6.2.2: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chalk@5.6.2: {} cli-boxes@4.0.1: {} @@ -4245,6 +5016,15 @@ snapshots: dependencies: restore-cursor: 4.0.0 + cli-highlight@2.1.11: + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.0 + cli-truncate@6.0.0: dependencies: slice-ansi: 9.0.0 @@ -4252,6 +5032,12 @@ snapshots: cli-width@4.1.0: {} + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + cliui@9.0.1: dependencies: string-width: 7.2.0 @@ -4270,6 +5056,12 @@ snapshots: estree-walker: 3.0.3 periscopic: 3.1.0 + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -4297,6 +5089,10 @@ snapshots: csstype@3.2.3: {} + data-uri-to-buffer@4.0.1: {} + + data-uri-to-buffer@6.0.2: {} + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -4344,12 +5140,20 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + delayed-stream@1.0.0: {} dequal@2.0.3: {} detect-libc@2.1.2: {} + diff@8.0.4: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -4368,6 +5172,12 @@ snapshots: emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + entities@7.0.1: {} environment@1.1.0: {} @@ -4483,22 +5293,30 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.3.0): + escodegen@2.1.0: dependencies: - eslint: 10.3.0 + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 - eslint-plugin-react-hooks@7.1.1(eslint@10.3.0): + eslint-config-prettier@10.1.8(eslint@10.3.0(jiti@2.7.0)): + dependencies: + eslint: 10.3.0(jiti@2.7.0) + + eslint-plugin-react-hooks@7.1.1(eslint@10.3.0(jiti@2.7.0)): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.3 - eslint: 10.3.0 + eslint: 10.3.0(jiti@2.7.0) hermes-parser: 0.25.1 zod: 4.4.3 zod-validation-error: 4.0.2(zod@4.4.3) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@10.3.0): + eslint-plugin-react@7.37.5(eslint@10.3.0(jiti@2.7.0)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -4506,7 +5324,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.3.2 - eslint: 10.3.0 + eslint: 10.3.0(jiti@2.7.0) estraverse: 5.3.0 hasown: 2.0.3 jsx-ast-utils: 3.3.5 @@ -4531,9 +5349,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.3.0: + eslint@10.3.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.5.5 @@ -4563,6 +5381,8 @@ snapshots: minimatch: 10.2.5 natural-compare: 1.4.0 optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -4572,6 +5392,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 5.0.1 + esprima@4.0.1: {} + esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -4600,6 +5422,16 @@ snapshots: extend@3.0.2: {} + extract-zip@2.0.1: + dependencies: + debug: 4.4.3 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -4628,14 +5460,32 @@ snapshots: path-expression-matcher: 1.5.0 strnum: 2.3.0 + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 + file-type@21.3.4: + dependencies: + '@tokenizer/inflate': 0.4.1 + strtok3: 10.3.5 + token-types: 6.1.2 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -4667,6 +5517,10 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 4.0.0-beta.3 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + fsevents@2.3.3: optional: true @@ -4694,6 +5548,14 @@ snapshots: - encoding - supports-color + gaxios@7.1.4: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + transitivePeerDependencies: + - supports-color + gcp-metadata@6.1.1: dependencies: gaxios: 6.7.1 @@ -4703,6 +5565,14 @@ snapshots: - encoding - supports-color + gcp-metadata@8.1.2: + dependencies: + gaxios: 7.1.4 + google-logging-utils: 1.1.3 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + generator-function@2.0.1: {} gensync@1.0.0-beta.2: {} @@ -4729,16 +5599,34 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@5.2.0: + dependencies: + pump: 3.0.4 + get-symbol-description@1.1.0: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 + get-uri@6.0.5: + dependencies: + basic-ftp: 5.3.1 + data-uri-to-buffer: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + globals@17.6.0: {} globalthis@1.0.4: @@ -4746,6 +5634,17 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 + google-auth-library@10.6.2: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 7.1.4 + gcp-metadata: 8.1.2 + google-logging-utils: 1.1.3 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + google-auth-library@9.15.1: dependencies: base64-js: 1.5.1 @@ -4760,8 +5659,12 @@ snapshots: google-logging-utils@0.0.2: {} + google-logging-utils@1.1.3: {} + gopd@1.2.0: {} + graceful-fs@4.2.11: {} + gtoken@7.1.0: dependencies: gaxios: 6.7.1 @@ -4772,6 +5675,8 @@ snapshots: has-bigints@1.1.0: {} + has-flag@4.0.0: {} + has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 @@ -4796,6 +5701,19 @@ snapshots: dependencies: hermes-estree: 0.25.1 + highlight.js@10.7.3: {} + + hosted-git-info@9.0.3: + dependencies: + lru-cache: 11.3.6 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -4807,6 +5725,8 @@ snapshots: dependencies: ms: 2.1.3 + ieee754@1.2.1: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -4856,6 +5776,8 @@ snapshots: hasown: 2.0.3 side-channel: 1.1.0 + ip-address@10.2.0: {} + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.9 @@ -4904,6 +5826,8 @@ snapshots: dependencies: call-bound: 1.0.4 + is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@5.1.0: dependencies: get-east-asian-width: 1.5.0 @@ -4999,6 +5923,8 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 + jiti@2.7.0: {} + jose@5.10.0: {} js-tokens@4.0.0: {} @@ -5015,6 +5941,11 @@ snapshots: json-buffer@3.0.1: {} + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.29.2 + ts-algebra: 2.0.0 + json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -5056,6 +5987,9 @@ snapshots: dependencies: json-buffer: 3.0.1 + koffi@2.16.2: + optional: true + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -5130,28 +6064,42 @@ snapshots: lodash.once@4.1.1: {} + long@5.3.2: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 + lru-cache@11.3.6: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 + lru-cache@7.18.3: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + marked@15.0.12: {} + math-intrinsics@1.1.0: {} mdn-data@2.0.30: {} mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mimic-fn@2.1.0: {} minimatch@10.2.5: @@ -5162,16 +6110,28 @@ snapshots: dependencies: brace-expansion: 1.1.14 + minipass@7.1.3: {} + ms@2.1.3: {} mute-stream@3.0.0: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nanoid@3.3.12: {} nanoid@3.3.6: {} natural-compare@1.4.0: {} + netmask@2.1.1: {} + + node-addon-api@7.1.1: {} + node-domexception@1.0.0: {} node-exports-info@1.6.0: @@ -5185,6 +6145,16 @@ snapshots: dependencies: whatwg-url: 5.0.0 + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + node-pty@1.1.0: + dependencies: + node-addon-api: 7.1.1 + node-releases@2.0.38: {} object-assign@4.1.1: {} @@ -5225,6 +6195,10 @@ snapshots: obug@2.1.1: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -5253,6 +6227,11 @@ snapshots: transitivePeerDependencies: - encoding + openai@6.26.0(ws@8.20.0)(zod@4.4.3): + optionalDependencies: + ws: 8.20.0 + zod: 4.4.3 + openapi3-ts@4.5.0: dependencies: yaml: 2.9.0 @@ -5280,6 +6259,39 @@ snapshots: dependencies: p-limit: 3.1.0 + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.4 + debug: 4.4.3 + get-uri: 6.0.5 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.1.1 + + parse5-htmlparser2-tree-adapter@6.0.1: + dependencies: + parse5: 6.0.1 + + parse5@5.1.1: {} + + parse5@6.0.1: {} + + partial-json@0.1.7: {} + patch-console@2.0.0: {} path-exists@4.0.0: {} @@ -5290,8 +6302,15 @@ snapshots: path-parse@1.0.7: {} + path-scurry@2.0.2: + dependencies: + lru-cache: 11.3.6 + minipass: 7.1.3 + pathe@2.0.3: {} + pend@1.2.0: {} + periscopic@3.1.0: dependencies: '@types/estree': 1.0.8 @@ -5322,6 +6341,47 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + + protobufjs@7.5.8: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.1 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/node': 24.12.2 + long: 5.3.2 + + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@2.3.1: {} react-devtools-core@7.0.1: @@ -5361,6 +6421,8 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + require-directory@2.1.1: {} + resolve@2.0.0-next.6: dependencies: es-errors: 1.3.0 @@ -5375,6 +6437,10 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + retry@0.12.0: {} + + retry@0.13.1: {} + rolldown@1.0.0-rc.17: dependencies: '@oxc-project/types': 0.127.0 @@ -5523,6 +6589,21 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + socks: 2.8.9 + transitivePeerDependencies: + - supports-color + + socks@2.8.9: + dependencies: + ip-address: 10.2.0 + smart-buffer: 4.2.0 + solid-js@1.9.12: dependencies: csstype: 3.2.3 @@ -5536,6 +6617,9 @@ snapshots: source-map-js@1.2.1: {} + source-map@0.6.1: + optional: true + sswr@2.0.0(svelte@4.2.20): dependencies: svelte: 4.2.20 @@ -5554,6 +6638,12 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -5609,12 +6699,24 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 strnum@2.3.0: {} + strtok3@10.3.5: + dependencies: + '@tokenizer/token': 0.3.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} svelte@4.2.20: @@ -5653,6 +6755,14 @@ snapshots: terminal-size@4.0.1: {} + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + tinybench@2.9.0: {} tinyexec@1.1.2: {} @@ -5664,8 +6774,16 @@ snapshots: tinyrainbow@3.1.0: {} + token-types@6.1.2: + dependencies: + '@borewit/text-codec': 0.2.2 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + tr46@0.0.3: {} + ts-algebra@2.0.0: {} + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 @@ -5680,6 +6798,8 @@ snapshots: dependencies: tagged-tag: 1.0.0 + typebox@1.1.38: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -5713,19 +6833,21 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.59.2(eslint@10.3.0)(typescript@6.0.3): + typescript-eslint@8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.3.0)(typescript@6.0.3))(eslint@10.3.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.59.2(eslint@10.3.0)(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.2(eslint@10.3.0)(typescript@6.0.3) - eslint: 10.3.0 + '@typescript-eslint/utils': 8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.3.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color typescript@6.0.3: {} + uint8array-extras@1.5.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -5737,6 +6859,8 @@ snapshots: undici-types@7.16.0: {} + undici@7.25.0: {} + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -5751,9 +6875,11 @@ snapshots: dependencies: react: 19.2.5 + uuid@14.0.0: {} + uuid@9.0.1: {} - vite@8.0.10(@types/node@24.12.2)(yaml@2.9.0): + vite@8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -5763,12 +6889,13 @@ snapshots: optionalDependencies: '@types/node': 24.12.2 fsevents: 2.3.3 + jiti: 2.7.0 yaml: 2.9.0 - vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(vite@8.0.10(@types/node@24.12.2)(yaml@2.9.0)): + vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(vite@8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@24.12.2)(yaml@2.9.0)) + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.5 '@vitest/runner': 4.1.5 '@vitest/snapshot': 4.1.5 @@ -5785,7 +6912,7 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.10(@types/node@24.12.2)(yaml@2.9.0) + vite: 8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 @@ -5803,6 +6930,8 @@ snapshots: optionalDependencies: typescript: 6.0.3 + web-streams-polyfill@3.3.3: {} + web-streams-polyfill@4.0.0-beta.3: {} webidl-conversions@3.0.1: {} @@ -5874,12 +7003,20 @@ snapshots: string-width: 8.2.1 strip-ansi: 7.2.0 + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 strip-ansi: 7.2.0 + wrappy@1.0.2: {} + ws@7.5.10: {} ws@8.20.0: {} @@ -5897,8 +7034,20 @@ snapshots: yaml@2.9.0: {} + yargs-parser@20.2.9: {} + yargs-parser@22.0.0: {} + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + yargs@18.0.0: dependencies: cliui: 9.0.1 @@ -5908,6 +7057,11 @@ snapshots: y18n: 5.0.8 yargs-parser: 22.0.0 + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + yocto-queue@0.1.0: {} yoga-layout@3.2.1: {} @@ -5916,6 +7070,10 @@ snapshots: dependencies: zod: 3.25.76 + zod-to-json-schema@3.25.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + zod-validation-error@4.0.2(zod@4.4.3): dependencies: zod: 4.4.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..dee51e9 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - "packages/*" From 3562b8aa7ac09b8e687b51b1a210f6789afdce90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Thu, 14 May 2026 13:08:33 -0700 Subject: [PATCH 30/52] chore: move wizard code into packages/ and wire up harness tooling All wizard source, tests, and build configs move from the repo root into packages/braintrust-wizard so nothing lives half at root / half in packages. Root package.json becomes a lean workspace root that delegates scripts via --filter and -r. The bt-wizard-harness scaffold gains ESLint, TypeScript, and Prettier configs plus the corresponding devDependencies and scripts. --- package.json | 50 ++++-------------- .../braintrust-wizard/eslint.config.mjs | 0 packages/braintrust-wizard/package.json | 51 +++++++++++++++++++ .../rolldown.beau.config.mjs | 0 .../braintrust-wizard/rolldown.config.mjs | 0 .../braintrust-wizard/src}/auth.ts | 0 .../braintrust-wizard/src}/beau/App.tsx | 0 .../braintrust-wizard/src}/beau/AppRoot.tsx | 0 .../src}/beau/SignalStrips.tsx | 0 .../src}/beau/brand-colors.ts | 0 .../src}/beau/brand-logo.tsx | 0 .../braintrust-wizard/src}/beau/cli.tsx | 0 .../braintrust-wizard/src}/beau/tui-state.tsx | 0 .../braintrust-wizard/src}/braintrust-api.ts | 0 .../braintrust-wizard/src}/browser.ts | 0 .../braintrust-wizard/src}/clack-wizard.ts | 10 ++-- .../braintrust-wizard/src}/cleanup.ts | 0 .../braintrust-wizard/src}/cli.ts | 0 .../braintrust-wizard/src}/language-detect.ts | 0 .../braintrust-wizard/src}/options.ts | 0 .../braintrust-wizard/src}/prompt.ts | 0 .../braintrust-wizard/src}/providers.ts | 0 .../braintrust-wizard/src}/query-client.ts | 0 .../braintrust-wizard/src}/wizard-copy.ts | 0 .../braintrust-wizard/test}/beau-app.test.tsx | 0 .../test}/braintrust-api.test.ts | 0 .../test}/clack-wizard.test.ts | 0 .../braintrust-wizard/test}/providers.test.ts | 0 .../braintrust-wizard/tsconfig.json | 0 .../braintrust-wizard/vitest.config.ts | 0 packages/bt-wizard-harness/eslint.config.mjs | 21 ++++++++ packages/bt-wizard-harness/package.json | 16 ++++++ packages/bt-wizard-harness/tsconfig.json | 26 ++++++++++ pnpm-lock.yaml | 33 +++++++++++- src/fuzzy.ts | 31 ----------- 35 files changed, 162 insertions(+), 76 deletions(-) rename eslint.config.mjs => packages/braintrust-wizard/eslint.config.mjs (100%) create mode 100644 packages/braintrust-wizard/package.json rename rolldown.beau.config.mjs => packages/braintrust-wizard/rolldown.beau.config.mjs (100%) rename rolldown.config.mjs => packages/braintrust-wizard/rolldown.config.mjs (100%) rename {src => packages/braintrust-wizard/src}/auth.ts (100%) rename {src => packages/braintrust-wizard/src}/beau/App.tsx (100%) rename {src => packages/braintrust-wizard/src}/beau/AppRoot.tsx (100%) rename {src => packages/braintrust-wizard/src}/beau/SignalStrips.tsx (100%) rename {src => packages/braintrust-wizard/src}/beau/brand-colors.ts (100%) rename {src => packages/braintrust-wizard/src}/beau/brand-logo.tsx (100%) rename {src => packages/braintrust-wizard/src}/beau/cli.tsx (100%) rename {src => packages/braintrust-wizard/src}/beau/tui-state.tsx (100%) rename {src => packages/braintrust-wizard/src}/braintrust-api.ts (100%) rename {src => packages/braintrust-wizard/src}/browser.ts (100%) rename {src => packages/braintrust-wizard/src}/clack-wizard.ts (96%) rename {src => packages/braintrust-wizard/src}/cleanup.ts (100%) rename {src => packages/braintrust-wizard/src}/cli.ts (100%) rename {src => packages/braintrust-wizard/src}/language-detect.ts (100%) rename {src => packages/braintrust-wizard/src}/options.ts (100%) rename {src => packages/braintrust-wizard/src}/prompt.ts (100%) rename {src => packages/braintrust-wizard/src}/providers.ts (100%) rename {src => packages/braintrust-wizard/src}/query-client.ts (100%) rename {src => packages/braintrust-wizard/src}/wizard-copy.ts (100%) rename {test => packages/braintrust-wizard/test}/beau-app.test.tsx (100%) rename {test => packages/braintrust-wizard/test}/braintrust-api.test.ts (100%) rename {test => packages/braintrust-wizard/test}/clack-wizard.test.ts (100%) rename {test => packages/braintrust-wizard/test}/providers.test.ts (100%) rename tsconfig.json => packages/braintrust-wizard/tsconfig.json (100%) rename vitest.config.ts => packages/braintrust-wizard/vitest.config.ts (100%) create mode 100644 packages/bt-wizard-harness/eslint.config.mjs create mode 100644 packages/bt-wizard-harness/tsconfig.json delete mode 100644 src/fuzzy.ts diff --git a/package.json b/package.json index b4d2a00..d99b846 100644 --- a/package.json +++ b/package.json @@ -1,53 +1,23 @@ { - "name": "braintrust-wizard", + "name": "crank", "version": "0.0.0", "private": true, - "description": "CLI wizard to get your project set up with Braintrust", + "description": "Braintrust wizard monorepo", "license": "MIT", "type": "module", - "bin": { - "braintrust-wizard": "./dist/cli.js" - }, "packageManager": "pnpm@10.33.3", "scripts": { - "start": "pnpm build && node dist/cli.js", - "build": "rolldown -c", - "start:beau": "pnpm build:beau && node dist/cli.beau.js", - "build:beau": "rolldown -c rolldown.beau.config.mjs", - "typings": "tsc --noEmit", - "test": "vitest run", - "lint": "eslint .", + "start": "pnpm --filter braintrust-wizard start", + "build": "pnpm --filter braintrust-wizard build", + "start:beau": "pnpm --filter braintrust-wizard start:beau", + "build:beau": "pnpm --filter braintrust-wizard build:beau", + "typings": "pnpm -r run typings", + "test": "pnpm -r run test", + "lint": "pnpm -r run lint", "format": "prettier --write .", "format:check": "prettier --check ." }, - "dependencies": { - "@braintrust/bt-wizard-harness": "workspace:*", - "@inquirer/search": "4.1.8", - "@clack/prompts": "1.3.0", - "@inquirer/search": "4.1.8", - "@tanstack/react-query": "5.100.9", - "ignore": "^7.0.5", - "ink": "7.0.2", - "open": "^11.0.0", - "react": "19.2.5", - "react-devtools-core": "7.0.1", - "yargs": "^18.0.0" - }, "devDependencies": { - "@braintrust/proxy": "0.0.9", - "@eslint/js": "10.0.1", - "@types/node": "24.12.2", - "@types/react": "19.2.14", - "@types/yargs": "^17.0.35", - "eslint": "10.3.0", - "eslint-config-prettier": "10.1.8", - "eslint-plugin-react": "7.37.5", - "eslint-plugin-react-hooks": "7.1.1", - "globals": "17.6.0", - "prettier": "3.8.3", - "rolldown": "1.0.0-rc.18", - "typescript": "6.0.3", - "typescript-eslint": "8.59.2", - "vitest": "4.1.5" + "prettier": "3.8.3" } } diff --git a/eslint.config.mjs b/packages/braintrust-wizard/eslint.config.mjs similarity index 100% rename from eslint.config.mjs rename to packages/braintrust-wizard/eslint.config.mjs diff --git a/packages/braintrust-wizard/package.json b/packages/braintrust-wizard/package.json new file mode 100644 index 0000000..91a0601 --- /dev/null +++ b/packages/braintrust-wizard/package.json @@ -0,0 +1,51 @@ +{ + "name": "braintrust-wizard", + "version": "0.0.0", + "private": true, + "description": "CLI wizard to get your project set up with Braintrust", + "license": "MIT", + "type": "module", + "bin": { + "braintrust-wizard": "./dist/cli.js" + }, + "scripts": { + "start": "pnpm build && node dist/cli.js", + "build": "rolldown -c", + "start:beau": "pnpm build:beau && node dist/cli.beau.js", + "build:beau": "rolldown -c rolldown.beau.config.mjs", + "typings": "tsc --noEmit", + "test": "vitest run", + "lint": "eslint .", + "format": "prettier --write .", + "format:check": "prettier --check ." + }, + "dependencies": { + "@braintrust/bt-wizard-harness": "workspace:*", + "@inquirer/search": "4.1.8", + "@clack/prompts": "1.3.0", + "@tanstack/react-query": "5.100.9", + "ignore": "^7.0.5", + "ink": "7.0.2", + "open": "^11.0.0", + "react": "19.2.5", + "react-devtools-core": "7.0.1", + "yargs": "^18.0.0" + }, + "devDependencies": { + "@braintrust/proxy": "0.0.9", + "@eslint/js": "10.0.1", + "@types/node": "24.12.2", + "@types/react": "19.2.14", + "@types/yargs": "^17.0.35", + "eslint": "10.3.0", + "eslint-config-prettier": "10.1.8", + "eslint-plugin-react": "7.37.5", + "eslint-plugin-react-hooks": "7.1.1", + "globals": "17.6.0", + "prettier": "3.8.3", + "rolldown": "1.0.0-rc.18", + "typescript": "6.0.3", + "typescript-eslint": "8.59.2", + "vitest": "4.1.5" + } +} diff --git a/rolldown.beau.config.mjs b/packages/braintrust-wizard/rolldown.beau.config.mjs similarity index 100% rename from rolldown.beau.config.mjs rename to packages/braintrust-wizard/rolldown.beau.config.mjs diff --git a/rolldown.config.mjs b/packages/braintrust-wizard/rolldown.config.mjs similarity index 100% rename from rolldown.config.mjs rename to packages/braintrust-wizard/rolldown.config.mjs diff --git a/src/auth.ts b/packages/braintrust-wizard/src/auth.ts similarity index 100% rename from src/auth.ts rename to packages/braintrust-wizard/src/auth.ts diff --git a/src/beau/App.tsx b/packages/braintrust-wizard/src/beau/App.tsx similarity index 100% rename from src/beau/App.tsx rename to packages/braintrust-wizard/src/beau/App.tsx diff --git a/src/beau/AppRoot.tsx b/packages/braintrust-wizard/src/beau/AppRoot.tsx similarity index 100% rename from src/beau/AppRoot.tsx rename to packages/braintrust-wizard/src/beau/AppRoot.tsx diff --git a/src/beau/SignalStrips.tsx b/packages/braintrust-wizard/src/beau/SignalStrips.tsx similarity index 100% rename from src/beau/SignalStrips.tsx rename to packages/braintrust-wizard/src/beau/SignalStrips.tsx diff --git a/src/beau/brand-colors.ts b/packages/braintrust-wizard/src/beau/brand-colors.ts similarity index 100% rename from src/beau/brand-colors.ts rename to packages/braintrust-wizard/src/beau/brand-colors.ts diff --git a/src/beau/brand-logo.tsx b/packages/braintrust-wizard/src/beau/brand-logo.tsx similarity index 100% rename from src/beau/brand-logo.tsx rename to packages/braintrust-wizard/src/beau/brand-logo.tsx diff --git a/src/beau/cli.tsx b/packages/braintrust-wizard/src/beau/cli.tsx similarity index 100% rename from src/beau/cli.tsx rename to packages/braintrust-wizard/src/beau/cli.tsx diff --git a/src/beau/tui-state.tsx b/packages/braintrust-wizard/src/beau/tui-state.tsx similarity index 100% rename from src/beau/tui-state.tsx rename to packages/braintrust-wizard/src/beau/tui-state.tsx diff --git a/src/braintrust-api.ts b/packages/braintrust-wizard/src/braintrust-api.ts similarity index 100% rename from src/braintrust-api.ts rename to packages/braintrust-wizard/src/braintrust-api.ts diff --git a/src/browser.ts b/packages/braintrust-wizard/src/browser.ts similarity index 100% rename from src/browser.ts rename to packages/braintrust-wizard/src/browser.ts diff --git a/src/clack-wizard.ts b/packages/braintrust-wizard/src/clack-wizard.ts similarity index 96% rename from src/clack-wizard.ts rename to packages/braintrust-wizard/src/clack-wizard.ts index 9d0aed5..8bdc755 100644 --- a/src/clack-wizard.ts +++ b/packages/braintrust-wizard/src/clack-wizard.ts @@ -99,9 +99,9 @@ export async function runClackWizard(deps: WizardDeps): Promise { }); const provider = await selectProvider(deps); - const providerCredentials = provider.custom - ? undefined - : await collectCredentials(prompts, provider); + if (!provider.custom) { + await collectCredentials(prompts, provider); + } const gitRoot = findGitRoot(deps.cwd); if (gitRoot) { @@ -146,7 +146,9 @@ async function collectCredentials( const raw = unwrap( prompts, field.secret !== false - ? await prompts.password({ message: PROVIDER_KEY_QUESTION(field.label) }) + ? await prompts.password({ + message: PROVIDER_KEY_QUESTION(field.label), + }) : await prompts.text({ message: PROVIDER_KEY_QUESTION(field.label) }), ); if (raw.length > 0) { diff --git a/src/cleanup.ts b/packages/braintrust-wizard/src/cleanup.ts similarity index 100% rename from src/cleanup.ts rename to packages/braintrust-wizard/src/cleanup.ts diff --git a/src/cli.ts b/packages/braintrust-wizard/src/cli.ts similarity index 100% rename from src/cli.ts rename to packages/braintrust-wizard/src/cli.ts diff --git a/src/language-detect.ts b/packages/braintrust-wizard/src/language-detect.ts similarity index 100% rename from src/language-detect.ts rename to packages/braintrust-wizard/src/language-detect.ts diff --git a/src/options.ts b/packages/braintrust-wizard/src/options.ts similarity index 100% rename from src/options.ts rename to packages/braintrust-wizard/src/options.ts diff --git a/src/prompt.ts b/packages/braintrust-wizard/src/prompt.ts similarity index 100% rename from src/prompt.ts rename to packages/braintrust-wizard/src/prompt.ts diff --git a/src/providers.ts b/packages/braintrust-wizard/src/providers.ts similarity index 100% rename from src/providers.ts rename to packages/braintrust-wizard/src/providers.ts diff --git a/src/query-client.ts b/packages/braintrust-wizard/src/query-client.ts similarity index 100% rename from src/query-client.ts rename to packages/braintrust-wizard/src/query-client.ts diff --git a/src/wizard-copy.ts b/packages/braintrust-wizard/src/wizard-copy.ts similarity index 100% rename from src/wizard-copy.ts rename to packages/braintrust-wizard/src/wizard-copy.ts diff --git a/test/beau-app.test.tsx b/packages/braintrust-wizard/test/beau-app.test.tsx similarity index 100% rename from test/beau-app.test.tsx rename to packages/braintrust-wizard/test/beau-app.test.tsx diff --git a/test/braintrust-api.test.ts b/packages/braintrust-wizard/test/braintrust-api.test.ts similarity index 100% rename from test/braintrust-api.test.ts rename to packages/braintrust-wizard/test/braintrust-api.test.ts diff --git a/test/clack-wizard.test.ts b/packages/braintrust-wizard/test/clack-wizard.test.ts similarity index 100% rename from test/clack-wizard.test.ts rename to packages/braintrust-wizard/test/clack-wizard.test.ts diff --git a/test/providers.test.ts b/packages/braintrust-wizard/test/providers.test.ts similarity index 100% rename from test/providers.test.ts rename to packages/braintrust-wizard/test/providers.test.ts diff --git a/tsconfig.json b/packages/braintrust-wizard/tsconfig.json similarity index 100% rename from tsconfig.json rename to packages/braintrust-wizard/tsconfig.json diff --git a/vitest.config.ts b/packages/braintrust-wizard/vitest.config.ts similarity index 100% rename from vitest.config.ts rename to packages/braintrust-wizard/vitest.config.ts diff --git a/packages/bt-wizard-harness/eslint.config.mjs b/packages/bt-wizard-harness/eslint.config.mjs new file mode 100644 index 0000000..933b2ab --- /dev/null +++ b/packages/bt-wizard-harness/eslint.config.mjs @@ -0,0 +1,21 @@ +import js from "@eslint/js"; +import prettier from "eslint-config-prettier"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { + ignores: ["dist/**", "coverage/**", "node_modules/**"], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ["**/*.{js,mjs,ts}"], + languageOptions: { + ecmaVersion: "latest", + globals: globals.node, + sourceType: "module", + }, + }, + prettier, +); diff --git a/packages/bt-wizard-harness/package.json b/packages/bt-wizard-harness/package.json index 932d2ac..cd31a1e 100644 --- a/packages/bt-wizard-harness/package.json +++ b/packages/bt-wizard-harness/package.json @@ -11,12 +11,28 @@ "bin", "extensions" ], + "scripts": { + "typings": "tsc --noEmit", + "lint": "eslint .", + "format": "prettier --write .", + "format:check": "prettier --check ." + }, "dependencies": { "@mariozechner/pi-coding-agent": "*", "@mariozechner/pi-tui": "*", "node-pty": "^1.1.0", "typebox": "^1.1.24" }, + "devDependencies": { + "@eslint/js": "10.0.1", + "@types/node": "24.12.2", + "eslint": "10.3.0", + "eslint-config-prettier": "10.1.8", + "globals": "17.6.0", + "prettier": "3.8.3", + "typescript": "6.0.3", + "typescript-eslint": "8.59.2" + }, "pi": { "extensions": [ "./extensions/path-guard.ts", diff --git a/packages/bt-wizard-harness/tsconfig.json b/packages/bt-wizard-harness/tsconfig.json new file mode 100644 index 0000000..ded62c8 --- /dev/null +++ b/packages/bt-wizard-harness/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "exactOptionalPropertyTypes": true, + "isolatedModules": true, + "lib": ["ES2024"], + "module": "ESNext", + "moduleDetection": "force", + "moduleResolution": "Bundler", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2024", + "types": ["node"] + }, + "include": [ + "bin/**/*.ts", + "bin/**/*.mjs", + "extensions/**/*.ts", + "*.config.mjs" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4259693..0b0d218 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,10 +7,16 @@ settings: importers: .: + devDependencies: + prettier: + specifier: 3.8.3 + version: 3.8.3 + + packages/braintrust-wizard: dependencies: '@braintrust/bt-wizard-harness': specifier: workspace:* - version: link:packages/bt-wizard-harness + version: link:../bt-wizard-harness '@clack/prompts': specifier: 1.3.0 version: 1.3.0 @@ -99,6 +105,31 @@ importers: typebox: specifier: ^1.1.24 version: 1.1.38 + devDependencies: + '@eslint/js': + specifier: 10.0.1 + version: 10.0.1(eslint@10.3.0(jiti@2.7.0)) + '@types/node': + specifier: 24.12.2 + version: 24.12.2 + eslint: + specifier: 10.3.0 + version: 10.3.0(jiti@2.7.0) + eslint-config-prettier: + specifier: 10.1.8 + version: 10.1.8(eslint@10.3.0(jiti@2.7.0)) + globals: + specifier: 17.6.0 + version: 17.6.0 + prettier: + specifier: 3.8.3 + version: 3.8.3 + typescript: + specifier: 6.0.3 + version: 6.0.3 + typescript-eslint: + specifier: 8.59.2 + version: 8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) packages: diff --git a/src/fuzzy.ts b/src/fuzzy.ts deleted file mode 100644 index 224d103..0000000 --- a/src/fuzzy.ts +++ /dev/null @@ -1,31 +0,0 @@ -import search from "@inquirer/search"; -import Fuse from "fuse.js"; - -export type FuzzyChoice = { - readonly value: T; - readonly name: string; - readonly description?: string; -}; - -export async function fuzzySelect(args: { - readonly message: string; - readonly choices: ReadonlyArray>; -}): Promise { - if (args.choices.length === 0) { - throw new Error("fuzzySelect called with no choices"); - } - const fuse = new Fuse(args.choices, { keys: ["name"], threshold: 0.4 }); - return search({ - message: args.message, - source: (term) => { - const results = !term - ? args.choices - : fuse.search(term).map((r) => r.item); - return results.map((c) => ({ - name: c.name, - value: c.value, - description: c.description, - })); - }, - }); -} From 20d930f0ac3c3a0fab3ef1cb3a76f4865f28fa1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Thu, 14 May 2026 13:19:50 -0700 Subject: [PATCH 31/52] fix: await parseArgs in cli.ts and drop dead helpText export parseArgs is async but was called without await, making parsed a Promise and causing parsed.options to be undefined at runtime. Awaiting it fixes the crash. The manual --help branch and helpText export are removed since yargs' built-in .help() already handles --help by printing and exiting during parseAsync. --- AGENTS.md | 21 +++++++++++++++------ packages/braintrust-wizard/src/cli.ts | 12 ++++++------ packages/braintrust-wizard/src/options.ts | 6 ------ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1075dee..dfc31a4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,12 +32,21 @@ Default implementation work should go into the Clack implementation. Do not work - Keep beau Ink UI components focused on rendering and input handling. - Keep backend/API request logic out of presentation components; expose it through query or mutation hooks. +## Workspace Layout + +This is a pnpm workspace. All packages live under `packages/`. + +- `packages/braintrust-wizard/` — the main wizard CLI (Clack + beau variants). +- `packages/bt-wizard-harness/` — the pi-coding-agent harness scaffold. + +Root-level scripts (`pnpm build`, `pnpm lint`, etc.) delegate to the packages via `--filter` or `-r`. + ## Implementation Notes -- Source files live under `src/`. -- Tests live under `test/`. -- The default CLI entrypoint is `src/cli.ts`; Rolldown emits `dist/cli.js`. -- The beau CLI entrypoint is `src/beau/cli.tsx`; Rolldown emits `dist/cli.beau.js`. -- Shared wizard copy lives in `src/wizard-copy.ts`; keep both variants using it. -- `src/query-client.ts` owns QueryClient creation and should remain the central place for query defaults. +- Wizard source files live under `packages/braintrust-wizard/src/`. +- Wizard tests live under `packages/braintrust-wizard/test/`. +- The default CLI entrypoint is `packages/braintrust-wizard/src/cli.ts`; Rolldown emits `packages/braintrust-wizard/dist/cli.js`. +- The beau CLI entrypoint is `packages/braintrust-wizard/src/beau/cli.tsx`; Rolldown emits `packages/braintrust-wizard/dist/cli.beau.js`. +- Shared wizard copy lives in `packages/braintrust-wizard/src/wizard-copy.ts`; keep both variants using it. +- `packages/braintrust-wizard/src/query-client.ts` owns QueryClient creation and should remain the central place for query defaults. - Do not add SEA packaging yet; the current build targets are JavaScript bundles. diff --git a/packages/braintrust-wizard/src/cli.ts b/packages/braintrust-wizard/src/cli.ts index 7812e84..e33b0df 100644 --- a/packages/braintrust-wizard/src/cli.ts +++ b/packages/braintrust-wizard/src/cli.ts @@ -7,9 +7,9 @@ import { runClackWizard, WizardCancelledError, } from "./clack-wizard"; -import { helpText, parseArgs } from "./options"; +import { parseArgs } from "./options"; -const parsed = await parseArgs(process.argv.slice(2), process.env); +const options = await parseArgs(process.argv.slice(2), process.env); // `NODE_EXTRA_CA_CERTS` is read once at Node startup, so we can't apply it // in-process. If --ca-cert (or BRAINTRUST_CA_CERT / SSL_CERT_FILE) was set @@ -17,15 +17,15 @@ const parsed = await parseArgs(process.argv.slice(2), process.env); // applied. The guard env var prevents an infinite re-exec loop. const REEXEC_GUARD = "BT_WIZARD_REEXECED_FOR_CA"; if ( - parsed.options.caCertPath && + options.caCertPath && process.env[REEXEC_GUARD] !== "1" && - process.env["NODE_EXTRA_CA_CERTS"] !== parsed.options.caCertPath + process.env["NODE_EXTRA_CA_CERTS"] !== options.caCertPath ) { const result = spawnSync(process.execPath, process.argv.slice(1), { stdio: "inherit", env: { ...process.env, - NODE_EXTRA_CA_CERTS: parsed.options.caCertPath, + NODE_EXTRA_CA_CERTS: options.caCertPath, [REEXEC_GUARD]: "1", }, }); @@ -33,7 +33,7 @@ if ( } const deps = buildDefaultDeps({ - options: parsed.options, + options, prompts: prompts as unknown as Parameters< typeof buildDefaultDeps >[0]["prompts"], diff --git a/packages/braintrust-wizard/src/options.ts b/packages/braintrust-wizard/src/options.ts index 638da74..dc30909 100644 --- a/packages/braintrust-wizard/src/options.ts +++ b/packages/braintrust-wizard/src/options.ts @@ -36,12 +36,6 @@ function buildParser(env: NodeJS.ProcessEnv) { .strict(); } -export async function helpText( - env: NodeJS.ProcessEnv = process.env, -): Promise { - return buildParser(env).getHelp(); -} - export async function parseArgs( argv: readonly string[], env: NodeJS.ProcessEnv, From 0847d33a56b1a8b36ea607593f81b0342d1b8ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Thu, 14 May 2026 13:56:41 -0700 Subject: [PATCH 32/52] fix: await async git helpers in clack-wizard --- packages/braintrust-wizard/src/clack-wizard.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/braintrust-wizard/src/clack-wizard.ts b/packages/braintrust-wizard/src/clack-wizard.ts index 8bdc755..95145cc 100644 --- a/packages/braintrust-wizard/src/clack-wizard.ts +++ b/packages/braintrust-wizard/src/clack-wizard.ts @@ -87,7 +87,7 @@ export async function runClackWizard(deps: WizardDeps): Promise { const { prompts } = deps; prompts.intro(WIZARD_TITLE); - if (!isGitRepo(deps.cwd)) { + if (!await isGitRepo(deps.cwd)) { prompts.log.warn(NOT_GIT_REPO_WARNING); } @@ -103,9 +103,9 @@ export async function runClackWizard(deps: WizardDeps): Promise { await collectCredentials(prompts, provider); } - const gitRoot = findGitRoot(deps.cwd); + const gitRoot = await findGitRoot(deps.cwd); if (gitRoot) { - const result = writeEnvBraintrust(gitRoot, session.apiKey); + const result = await writeEnvBraintrust(gitRoot, session.apiKey); prompts.log.success(`Wrote ${result.envFilePath}`); prompts.log.info( gitignoreNote({ From 071d5ea44f03a914e643608a0ee7e26e0a005c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Thu, 14 May 2026 14:07:51 -0700 Subject: [PATCH 33/52] fix: clean up harness scaffold and fix typings errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop bin/files/typings from bt-wizard-harness (no source files yet), fix typebox → @sinclair/typebox, and resolve two exactOptionalPropertyTypes errors in clack-wizard.ts and providers.test.ts so pnpm -r run typings passes. --- packages/braintrust-wizard/src/clack-wizard.ts | 3 +-- packages/braintrust-wizard/test/providers.test.ts | 2 +- packages/bt-wizard-harness/package.json | 10 +--------- pnpm-lock.yaml | 11 ++++++++--- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/braintrust-wizard/src/clack-wizard.ts b/packages/braintrust-wizard/src/clack-wizard.ts index 95145cc..159d140 100644 --- a/packages/braintrust-wizard/src/clack-wizard.ts +++ b/packages/braintrust-wizard/src/clack-wizard.ts @@ -87,7 +87,7 @@ export async function runClackWizard(deps: WizardDeps): Promise { const { prompts } = deps; prompts.intro(WIZARD_TITLE); - if (!await isGitRepo(deps.cwd)) { + if (!(await isGitRepo(deps.cwd))) { prompts.log.warn(NOT_GIT_REPO_WARNING); } @@ -123,7 +123,6 @@ export async function runClackWizard(deps: WizardDeps): Promise { buildCleanupMessage({ docsUrl: DOCS_URL, tracePermalink: undefined, - resumeCommand: undefined, }), ); diff --git a/packages/braintrust-wizard/test/providers.test.ts b/packages/braintrust-wizard/test/providers.test.ts index 3ec39bd..c330a79 100644 --- a/packages/braintrust-wizard/test/providers.test.ts +++ b/packages/braintrust-wizard/test/providers.test.ts @@ -51,7 +51,7 @@ describe("LLM_PROVIDERS sync with @braintrust/proxy/schema", () => { if (provider.custom) continue; if (KNOWN_AHEAD.has(provider.id)) continue; if (MULTI_CREDENTIAL.has(provider.id)) continue; - expect(envVars.has(provider.envVar)).toBe(true); + expect(envVars.has(provider.envVar!)).toBe(true); } }); diff --git a/packages/bt-wizard-harness/package.json b/packages/bt-wizard-harness/package.json index cd31a1e..01d2d3c 100644 --- a/packages/bt-wizard-harness/package.json +++ b/packages/bt-wizard-harness/package.json @@ -4,15 +4,7 @@ "private": true, "description": "Restricted pi-coding-agent harness for the bt-wizard CLI: write/edit/grep + web search + bt only.", "type": "module", - "bin": { - "bt-wizard-harness": "./bin/bt-wizard-harness.mjs" - }, - "files": [ - "bin", - "extensions" - ], "scripts": { - "typings": "tsc --noEmit", "lint": "eslint .", "format": "prettier --write .", "format:check": "prettier --check ." @@ -21,7 +13,7 @@ "@mariozechner/pi-coding-agent": "*", "@mariozechner/pi-tui": "*", "node-pty": "^1.1.0", - "typebox": "^1.1.24" + "@sinclair/typebox": "^0.34.49" }, "devDependencies": { "@eslint/js": "10.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b0d218..a57dd13 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,12 +99,12 @@ importers: '@mariozechner/pi-tui': specifier: '*' version: 0.73.1 + '@sinclair/typebox': + specifier: ^0.34.49 + version: 0.34.49 node-pty: specifier: ^1.1.0 version: 1.1.0 - typebox: - specifier: ^1.1.24 - version: 1.1.38 devDependencies: '@eslint/js': specifier: 10.0.1 @@ -900,6 +900,9 @@ packages: '@silvia-odwyer/photon-node@0.3.4': resolution: {integrity: sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==} + '@sinclair/typebox@0.34.49': + resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} + '@smithy/config-resolver@4.5.1': resolution: {integrity: sha512-abXk3LhODsvRHsk0ZS9ztrg/fZatTa9Z/z4pgx65YSLR+rY6kvUG/1IgcDKEUciR8MfdnkT5oPeHJTy/HhzDIQ==} engines: {node: '>=18.0.0'} @@ -4379,6 +4382,8 @@ snapshots: '@silvia-odwyer/photon-node@0.3.4': {} + '@sinclair/typebox@0.34.49': {} + '@smithy/config-resolver@4.5.1': dependencies: '@smithy/core': 3.24.1 From 83effa0360161cd3e33c9d3f1a0b70b16d9c033b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Thu, 14 May 2026 14:09:51 -0700 Subject: [PATCH 34/52] chore: drop phantom harness dep from braintrust-wizard --- packages/braintrust-wizard/package.json | 1 - pnpm-lock.yaml | 3 --- 2 files changed, 4 deletions(-) diff --git a/packages/braintrust-wizard/package.json b/packages/braintrust-wizard/package.json index 91a0601..238e0dc 100644 --- a/packages/braintrust-wizard/package.json +++ b/packages/braintrust-wizard/package.json @@ -20,7 +20,6 @@ "format:check": "prettier --check ." }, "dependencies": { - "@braintrust/bt-wizard-harness": "workspace:*", "@inquirer/search": "4.1.8", "@clack/prompts": "1.3.0", "@tanstack/react-query": "5.100.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a57dd13..9fb065d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,6 @@ importers: packages/braintrust-wizard: dependencies: - '@braintrust/bt-wizard-harness': - specifier: workspace:* - version: link:../bt-wizard-harness '@clack/prompts': specifier: 1.3.0 version: 1.3.0 From 8e7e1c425383ce2832c29d2abd36ec4c34950b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Thu, 14 May 2026 14:11:15 -0700 Subject: [PATCH 35/52] chore: migrate pi deps to @earendil-works/* namespace --- packages/bt-wizard-harness/package.json | 4 +- pnpm-lock.yaml | 206 ++++++++++++------------ 2 files changed, 103 insertions(+), 107 deletions(-) diff --git a/packages/bt-wizard-harness/package.json b/packages/bt-wizard-harness/package.json index 01d2d3c..0219782 100644 --- a/packages/bt-wizard-harness/package.json +++ b/packages/bt-wizard-harness/package.json @@ -10,8 +10,8 @@ "format:check": "prettier --check ." }, "dependencies": { - "@mariozechner/pi-coding-agent": "*", - "@mariozechner/pi-tui": "*", + "@earendil-works/pi-coding-agent": "*", + "@earendil-works/pi-tui": "*", "node-pty": "^1.1.0", "@sinclair/typebox": "^0.34.49" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9fb065d..d230d50 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,12 +90,12 @@ importers: packages/bt-wizard-harness: dependencies: - '@mariozechner/pi-coding-agent': + '@earendil-works/pi-coding-agent': specifier: '*' - version: 0.73.1(ws@8.20.0)(zod@4.4.3) - '@mariozechner/pi-tui': + version: 0.74.0(ws@8.20.0)(zod@4.4.3) + '@earendil-works/pi-tui': specifier: '*' - version: 0.73.1 + version: 0.74.0 '@sinclair/typebox': specifier: ^0.34.49 version: 0.34.49 @@ -399,6 +399,24 @@ packages: resolution: {integrity: sha512-GgcWwRCs/xPtaqlMy8qRhPnZf9vlWcWZNHAitnVQ3yk7JmSralSiq5q07yaffYE8SogtDm7zFeKccx1QNVARpw==} engines: {node: '>= 20.12.0'} + '@earendil-works/pi-agent-core@0.74.0': + resolution: {integrity: sha512-6GMR7/wwjEJ1EsXLWEz03QOWin4AMrJ/AZoMpgm5DJ6GHsF6q6GOhQbj5Zip4dow3vo/TmBAVqM+vmGfrjGAFQ==} + engines: {node: '>=20.0.0'} + + '@earendil-works/pi-ai@0.74.0': + resolution: {integrity: sha512-7M7qcrZY/KEkH4wFkX3eqzvmKru4O88wezNKoN0KD2m4aAOmp9tdW2xCmUgSTSWlKB7b2Xw9QtAgrzHtg6t6iw==} + engines: {node: '>=20.0.0'} + hasBin: true + + '@earendil-works/pi-coding-agent@0.74.0': + resolution: {integrity: sha512-Q5GikbB5vRBrsrrf/uvet53rPSQ1sn5I5mO+l7sIobdXYpS04/X2oOc2UHFm90fNdkl3yU+ANTZL0zOtHbnqRw==} + engines: {node: '>=20.6.0'} + hasBin: true + + '@earendil-works/pi-tui@0.74.0': + resolution: {integrity: sha512-1aIfXZp7D/z+1VlZX8BZcs6pgO8rjmil7kwyhctNDsWvce3Yfl8GVgu4eq+I0Mjhr8Cj+ipBiv9CLIzdoyCOIQ==} + engines: {node: '>=20.0.0'} + '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} @@ -602,28 +620,6 @@ packages: resolution: {integrity: sha512-D3F+UrU9CR7roJt0zDLp6Oc+4/KlLDIrN4frH+6V90SJNW2KKUec1oCQIPaaDjCqeOsQyX9dyqYbImIQIM45PA==} engines: {node: '>= 10'} - '@mariozechner/pi-agent-core@0.73.1': - resolution: {integrity: sha512-Y/KVOhuKSgRQgYBlwmRtO2gPkUcoavOSqGF9bpQIINvNZvc19k6Z1H3bFDTce3Vp5ApMmTsfLH3+tNvOg75fAQ==} - engines: {node: '>=20.0.0'} - deprecated: please use @earendil-works/pi-agent-core instead going forward - - '@mariozechner/pi-ai@0.73.1': - resolution: {integrity: sha512-Jh4lXawZYuC83HzSIYuVum9NBqJD49i4JOt3H96cGW/924cwJMOyUs1Mv/e4QPzTXnzrqMoGviNQnvGgSu1LSg==} - engines: {node: '>=20.0.0'} - deprecated: please use @earendil-works/pi-ai instead going forward - hasBin: true - - '@mariozechner/pi-coding-agent@0.73.1': - resolution: {integrity: sha512-gXQh3SaZmWTfVMc4Ao5+LGbVeKvzyO7tolok0nLsZgq9nGjZx/EEU3NM8C+qUnB4Nvs2rswG5qOVgLzQkq0fHQ==} - engines: {node: '>=20.6.0'} - deprecated: please use @earendil-works/pi-coding-agent instead going forward - hasBin: true - - '@mariozechner/pi-tui@0.73.1': - resolution: {integrity: sha512-ybVsRnUbzQRtbocltJ2OXb2QogrO67N2BlUyKjZz9BHcZYiDJtNkcKQockxDjsVvDc0uBCLDX6iZJoBElBd8fw==} - engines: {node: '>=20.0.0'} - deprecated: please use @earendil-works/pi-tui instead going forward - '@mistralai/mistralai@2.2.1': resolution: {integrity: sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==} @@ -3948,6 +3944,85 @@ snapshots: fast-wrap-ansi: 0.2.0 sisteransi: 1.0.5 + '@earendil-works/pi-agent-core@0.74.0(ws@8.20.0)(zod@4.4.3)': + dependencies: + '@earendil-works/pi-ai': 0.74.0(ws@8.20.0)(zod@4.4.3) + typebox: 1.1.38 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - aws-crt + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-ai@0.74.0(ws@8.20.0)(zod@4.4.3)': + dependencies: + '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) + '@aws-sdk/client-bedrock-runtime': 3.1045.0 + '@google/genai': 1.52.0 + '@mistralai/mistralai': 2.2.1 + chalk: 5.6.2 + openai: 6.26.0(ws@8.20.0)(zod@4.4.3) + partial-json: 0.1.7 + proxy-agent: 6.5.0 + typebox: 1.1.38 + undici: 7.25.0 + zod-to-json-schema: 3.25.2(zod@4.4.3) + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - aws-crt + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-coding-agent@0.74.0(ws@8.20.0)(zod@4.4.3)': + dependencies: + '@earendil-works/pi-agent-core': 0.74.0(ws@8.20.0)(zod@4.4.3) + '@earendil-works/pi-ai': 0.74.0(ws@8.20.0)(zod@4.4.3) + '@earendil-works/pi-tui': 0.74.0 + '@silvia-odwyer/photon-node': 0.3.4 + chalk: 5.6.2 + cli-highlight: 2.1.11 + diff: 8.0.4 + extract-zip: 2.0.1 + file-type: 21.3.4 + glob: 13.0.6 + hosted-git-info: 9.0.3 + ignore: 7.0.5 + jiti: 2.7.0 + marked: 15.0.12 + minimatch: 10.2.5 + proper-lockfile: 4.1.2 + strip-ansi: 7.2.0 + typebox: 1.1.38 + undici: 7.25.0 + uuid: 14.0.0 + yaml: 2.9.0 + optionalDependencies: + '@mariozechner/clipboard': 0.3.5 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - aws-crt + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-tui@0.74.0': + dependencies: + '@types/mime-types': 2.1.4 + chalk: 5.6.2 + get-east-asian-width: 1.5.0 + marked: 15.0.12 + mime-types: 3.0.2 + optionalDependencies: + koffi: 2.16.2 + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 @@ -4130,85 +4205,6 @@ snapshots: '@mariozechner/clipboard-win32-x64-msvc': 0.3.2 optional: true - '@mariozechner/pi-agent-core@0.73.1(ws@8.20.0)(zod@4.4.3)': - dependencies: - '@mariozechner/pi-ai': 0.73.1(ws@8.20.0)(zod@4.4.3) - typebox: 1.1.38 - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@mariozechner/pi-ai@0.73.1(ws@8.20.0)(zod@4.4.3)': - dependencies: - '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) - '@aws-sdk/client-bedrock-runtime': 3.1045.0 - '@google/genai': 1.52.0 - '@mistralai/mistralai': 2.2.1 - chalk: 5.6.2 - openai: 6.26.0(ws@8.20.0)(zod@4.4.3) - partial-json: 0.1.7 - proxy-agent: 6.5.0 - typebox: 1.1.38 - undici: 7.25.0 - zod-to-json-schema: 3.25.2(zod@4.4.3) - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@mariozechner/pi-coding-agent@0.73.1(ws@8.20.0)(zod@4.4.3)': - dependencies: - '@mariozechner/pi-agent-core': 0.73.1(ws@8.20.0)(zod@4.4.3) - '@mariozechner/pi-ai': 0.73.1(ws@8.20.0)(zod@4.4.3) - '@mariozechner/pi-tui': 0.73.1 - '@silvia-odwyer/photon-node': 0.3.4 - chalk: 5.6.2 - cli-highlight: 2.1.11 - diff: 8.0.4 - extract-zip: 2.0.1 - file-type: 21.3.4 - glob: 13.0.6 - hosted-git-info: 9.0.3 - ignore: 7.0.5 - jiti: 2.7.0 - marked: 15.0.12 - minimatch: 10.2.5 - proper-lockfile: 4.1.2 - strip-ansi: 7.2.0 - typebox: 1.1.38 - undici: 7.25.0 - uuid: 14.0.0 - yaml: 2.9.0 - optionalDependencies: - '@mariozechner/clipboard': 0.3.5 - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@mariozechner/pi-tui@0.73.1': - dependencies: - '@types/mime-types': 2.1.4 - chalk: 5.6.2 - get-east-asian-width: 1.5.0 - marked: 15.0.12 - mime-types: 3.0.2 - optionalDependencies: - koffi: 2.16.2 - '@mistralai/mistralai@2.2.1': dependencies: ws: 8.20.0 From ecf776f9145be85e448637d269c274cc28dc50ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Thu, 14 May 2026 13:08:33 -0700 Subject: [PATCH 36/52] chore: move wizard code into packages/ and wire up harness tooling All wizard source, tests, and build configs move from the repo root into packages/braintrust-wizard so nothing lives half at root / half in packages. Root package.json becomes a lean workspace root that delegates scripts via --filter and -r. The bt-wizard-harness scaffold gains ESLint, TypeScript, and Prettier configs plus the corresponding devDependencies and scripts. --- packages/braintrust-wizard/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/braintrust-wizard/package.json b/packages/braintrust-wizard/package.json index 238e0dc..91a0601 100644 --- a/packages/braintrust-wizard/package.json +++ b/packages/braintrust-wizard/package.json @@ -20,6 +20,7 @@ "format:check": "prettier --check ." }, "dependencies": { + "@braintrust/bt-wizard-harness": "workspace:*", "@inquirer/search": "4.1.8", "@clack/prompts": "1.3.0", "@tanstack/react-query": "5.100.9", From 7a6cd2eb1e059e58e3e63303d819a22a408c9894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:46:02 -0700 Subject: [PATCH 37/52] feat: add path-guard extension --- .../extensions/path-guard.ts | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 packages/bt-wizard-harness/extensions/path-guard.ts diff --git a/packages/bt-wizard-harness/extensions/path-guard.ts b/packages/bt-wizard-harness/extensions/path-guard.ts new file mode 100644 index 0000000..5a15fe0 --- /dev/null +++ b/packages/bt-wizard-harness/extensions/path-guard.ts @@ -0,0 +1,133 @@ +/** + * Path-guard extension. + * + * Restricts file-touching tool calls (read/write/edit/grep/find/ls) to: + * - the harness's working directory and its subtree, AND + * - `/.env.braintrust` exactly, AND + * - the path in `BT_WIZARD_RESULT_FILE` (used by the wizard to receive the + * trace permalink), if set. + * + * Anything outside that scope is blocked with a clear reason. This is the + * file-system half of the bt-wizard tool whitelist; bash/python are removed + * by running pi with --no-builtin-tools and an explicit -t allowlist. + */ + +import { spawnSync } from "node:child_process"; +import { resolve, isAbsolute } from "node:path"; +import { existsSync } from "node:fs"; + +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; + +function gitRoot(cwd: string): string | undefined { + const r = spawnSync("git", ["rev-parse", "--show-toplevel"], { + cwd, + encoding: "utf8", + }); + if (r.status !== 0) { + return undefined; + } + const out = r.stdout.trim(); + return out.length > 0 ? out : undefined; +} + +function isUnderRoot(absPath: string, root: string): boolean { + const rel = absPath.startsWith(root + "/") || absPath === root; + return rel; +} + +const PATH_FIELDS = [ + "path", + "file_path", + "filename", + "directory", + "dir", +] as const; + +function extractPath(input: Record): string | undefined { + for (const f of PATH_FIELDS) { + const v = input[f]; + if (typeof v === "string" && v.length > 0) { + return v; + } + } + return undefined; +} + +// Tools that can mutate the filesystem — strictly scoped to cwd. +const WRITE_TOOLS = new Set(["write", "edit"]); +// Read-only tools — also allowed to access pi's data dir (skills, extensions). +const READ_TOOLS = new Set(["read", "grep", "find", "ls"]); + +export default function pathGuard(pi: ExtensionAPI) { + const cwd = process.cwd(); + const cwdAbs = resolve(cwd); + const root = gitRoot(cwdAbs); + const envBraintrust = root ? resolve(root, ".env.braintrust") : undefined; + const envFile = root ? resolve(root, ".env") : undefined; + const resultFileRaw = process.env["BT_WIZARD_RESULT_FILE"]; + const resultFile = + resultFileRaw && resultFileRaw.length > 0 + ? resolve(resultFileRaw) + : undefined; + + const exceptions = [envBraintrust, resultFile].filter( + (p): p is string => typeof p === "string", + ); + // Paths that must never be touched regardless of cwd. + const blockedPaths = [envFile].filter( + (p): p is string => typeof p === "string", + ); + + // pi stores skills, extensions, and config under ~/.agents/ + const homeDir = process.env["HOME"] ?? process.env["USERPROFILE"] ?? ""; + const piDataDir = homeDir ? resolve(homeDir, ".agents") : undefined; + + pi.on("tool_call", async (event) => { + const isWrite = WRITE_TOOLS.has(event.toolName); + const isRead = READ_TOOLS.has(event.toolName); + if (!isWrite && !isRead) { + return undefined; + } + const raw = extractPath(event.input as Record); + if (!raw) { + return undefined; + } + const abs = isAbsolute(raw) ? resolve(raw) : resolve(cwdAbs, raw); + + if (blockedPaths.includes(abs)) { + return { + block: true, + reason: `Accessing "${raw}" is not allowed; use .env.braintrust instead.`, + }; + } + if (exceptions.includes(abs)) { + return undefined; + } + if (isUnderRoot(abs, cwdAbs)) { + return undefined; + } + // Allow read-only tools to access pi's data directory (skills, extensions). + if (isRead && piDataDir && isUnderRoot(abs, piDataDir)) { + return undefined; + } + return { + block: true, + reason: `Path "${raw}" is outside the bt-wizard scope (cwd subtree${ + exceptions.length > 0 ? ` plus ${exceptions.join(", ")}` : "" + }).`, + }; + }); + + pi.on("session_start", (_event, ctx) => { + if (!ctx.hasUI) { + return; + } + ctx.ui.setToolsExpanded(false); + ctx.ui.notify( + `bt-wizard path-guard active: cwd=${cwdAbs}${ + exceptions.length > 0 ? `, exception=${exceptions.join(", ")}` : "" + }${existsSync(cwdAbs) ? "" : " (cwd missing!)"}`, + "info", + ); + }); +} From 825716036449f707c01e05d259363f0a4f25a44d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:46:08 -0700 Subject: [PATCH 38/52] feat: add bt, curl, git, request-command tool extensions --- .../bt-wizard-harness/extensions/bt-tool.ts | 152 ++++++++++ .../bt-wizard-harness/extensions/curl-tool.ts | 182 ++++++++++++ .../bt-wizard-harness/extensions/git-tool.ts | 262 ++++++++++++++++++ .../extensions/request-command-tool.ts | 194 +++++++++++++ 4 files changed, 790 insertions(+) create mode 100644 packages/bt-wizard-harness/extensions/bt-tool.ts create mode 100644 packages/bt-wizard-harness/extensions/curl-tool.ts create mode 100644 packages/bt-wizard-harness/extensions/git-tool.ts create mode 100644 packages/bt-wizard-harness/extensions/request-command-tool.ts diff --git a/packages/bt-wizard-harness/extensions/bt-tool.ts b/packages/bt-wizard-harness/extensions/bt-tool.ts new file mode 100644 index 0000000..a74a7e9 --- /dev/null +++ b/packages/bt-wizard-harness/extensions/bt-tool.ts @@ -0,0 +1,152 @@ +/** + * `bt` CLI extension. + * + * The bt-wizard harness disables `bash`, but the agent still needs to invoke + * the Braintrust `bt` CLI. We expose `bt` as a first-class tool so + * the model can call it with a structured argv and stdin instead of a raw + * shell line. + * + * Argv is an array of strings — no shell metacharacter interpretation. stdin + * is optional plain text. The tool spawns the `bt` binary directly with + * `shell: false`. + */ + +import { spawn } from "node:child_process"; +import { Type } from "typebox"; + +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; + +const BT_PARAMS = Type.Object({ + args: Type.Array(Type.String(), { + description: + "Argv passed to the bt CLI (no shell expansion). Example: ['status','--json'].", + }), + stdin: Type.Optional( + Type.String({ + description: "Optional text written to bt's stdin.", + }), + ), + timeout_ms: Type.Optional( + Type.Integer({ + description: "Hard timeout in milliseconds (default 60000).", + minimum: 1000, + maximum: 600000, + }), + ), +}); + +type BtParams = { + args: string[]; + stdin?: string; + timeout_ms?: number; +}; + +const DEFAULT_TIMEOUT_MS = 60_000; +const MAX_OUTPUT_BYTES = 1_000_000; + +function runBt(params: BtParams): Promise<{ + exitCode: number; + stdout: string; + stderr: string; + timedOut: boolean; +}> { + return new Promise((resolve) => { + const child = spawn("bt", params.args, { + stdio: ["pipe", "pipe", "pipe"], + shell: false, + }); + + let stdoutBytes = 0; + let stderrBytes = 0; + const stdoutChunks: string[] = []; + const stderrChunks: string[] = []; + let timedOut = false; + + const timer = setTimeout(() => { + timedOut = true; + child.kill("SIGTERM"); + setTimeout(() => child.kill("SIGKILL"), 1000).unref(); + }, params.timeout_ms ?? DEFAULT_TIMEOUT_MS); + + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (d: string) => { + stdoutBytes += Buffer.byteLength(d); + if (stdoutBytes < MAX_OUTPUT_BYTES) { + stdoutChunks.push(d); + } + }); + child.stderr.on("data", (d: string) => { + stderrBytes += Buffer.byteLength(d); + if (stderrBytes < MAX_OUTPUT_BYTES) { + stderrChunks.push(d); + } + }); + + child.on("error", (err) => { + clearTimeout(timer); + resolve({ + exitCode: 127, + stdout: "", + stderr: `Failed to spawn bt: ${err.message}`, + timedOut: false, + }); + }); + + child.on("close", (code) => { + clearTimeout(timer); + resolve({ + exitCode: code ?? -1, + stdout: stdoutChunks.join(""), + stderr: stderrChunks.join(""), + timedOut, + }); + }); + + if (params.stdin && params.stdin.length > 0) { + child.stdin.write(params.stdin); + } + child.stdin.end(); + }); +} + +export default function btTool(pi: ExtensionAPI) { + pi.registerTool({ + name: "bt", + label: "bt CLI", + description: + "Run the Braintrust `bt` CLI with a structured argv. Use for `bt status`, `bt sql`, etc.", + promptSnippet: + "Invoke `bt` for Braintrust CLI operations (status, queries, project info). bash/python are not available.", + promptGuidelines: [ + "Pass argv as an array — do not embed shell quoting.", + "Prefer `bt status --json` to inspect the active org/project.", + "Use `bt sql --json` for BTQL queries to verify traces; include a timestamp filter.", + ], + parameters: BT_PARAMS, + async execute(_toolCallId, params) { + const result = await runBt(params as BtParams); + const summary = result.timedOut + ? `bt timed out (exit ${result.exitCode})` + : `bt exited ${result.exitCode}`; + return { + content: [ + { + type: "text", + text: [ + summary, + "--- stdout ---", + result.stdout, + "--- stderr ---", + result.stderr, + ].join("\n"), + }, + ], + details: { + exitCode: result.exitCode, + timedOut: result.timedOut, + }, + }; + }, + }); +} diff --git a/packages/bt-wizard-harness/extensions/curl-tool.ts b/packages/bt-wizard-harness/extensions/curl-tool.ts new file mode 100644 index 0000000..e722571 --- /dev/null +++ b/packages/bt-wizard-harness/extensions/curl-tool.ts @@ -0,0 +1,182 @@ +/** + * `curl` extension. + * + * Read-only HTTP fetcher. GET and HEAD are permitted; POST/PUT/DELETE and + * other stateful methods are blocked. Redirects are followed. Used by the + * agent to read external documentation (e.g. https://www.braintrust.dev/docs, + * library README pages, package indexes). + * + * No URL allow/block list — the agent may need to consult arbitrary + * library docs. Path-guard still constrains the file system. + */ + +import { Type } from "typebox"; + +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; + +const CURL_PARAMS = Type.Object({ + url: Type.String({ description: "Absolute http(s) URL." }), + method: Type.Optional( + Type.Union([Type.Literal("GET"), Type.Literal("HEAD")], { + description: + "HTTP method. Defaults to GET. Only GET and HEAD are allowed.", + }), + ), + headers: Type.Optional( + Type.Record(Type.String(), Type.String(), { + description: "Extra request headers (e.g. Accept).", + }), + ), + timeout_ms: Type.Optional( + Type.Integer({ + description: "Hard timeout in milliseconds (default 30000).", + minimum: 1000, + maximum: 300000, + }), + ), +}); + +type CurlParams = { + url: string; + method?: "GET" | "HEAD"; + headers?: Record; + timeout_ms?: number; +}; + +const DEFAULT_TIMEOUT_MS = 30_000; +const MAX_BODY_BYTES = 1_000; + +const ALLOWED_METHODS = new Set(["GET", "HEAD"]); + +function isHttpUrl(raw: string): boolean { + try { + const u = new URL(raw); + return u.protocol === "http:" || u.protocol === "https:"; + } catch { + return false; + } +} + +async function runCurl(params: CurlParams): Promise<{ + status: number; + statusText: string; + url: string; + headers: Record; + body: string; + truncated: boolean; +}> { + const method = params.method ?? "GET"; + if (!ALLOWED_METHODS.has(method)) { + throw new Error(`Method ${method} is not permitted (GET/HEAD only).`); + } + if (!isHttpUrl(params.url)) { + throw new Error(`URL must be http(s): ${params.url}`); + } + + const controller = new AbortController(); + const timer = setTimeout( + () => controller.abort(), + params.timeout_ms ?? DEFAULT_TIMEOUT_MS, + ); + try { + const res = await fetch(params.url, { + method, + headers: params.headers, + redirect: "follow", + signal: controller.signal, + }); + + const headers: Record = {}; + res.headers.forEach((v, k) => { + headers[k] = v; + }); + + let body = ""; + let truncated = false; + if (method !== "HEAD") { + const reader = res.body?.getReader(); + if (reader) { + const decoder = new TextDecoder("utf-8", { fatal: false }); + let bytes = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + if (bytes + value.byteLength > MAX_BODY_BYTES) { + body += decoder.decode(value.subarray(0, MAX_BODY_BYTES - bytes), { + stream: false, + }); + truncated = true; + try { + await reader.cancel(); + } catch { + /* ignore */ + } + break; + } + bytes += value.byteLength; + body += decoder.decode(value, { stream: true }); + } + if (!truncated) { + body += decoder.decode(); + } + } + } + + return { + status: res.status, + statusText: res.statusText, + url: res.url, + headers, + body, + truncated, + }; + } finally { + clearTimeout(timer); + } +} + +export default function curlTool(pi: ExtensionAPI) { + pi.registerTool({ + name: "curl", + label: "curl (GET/HEAD)", + description: + "Fetch a URL via HTTP GET or HEAD. Follows redirects. No POST/PUT/DELETE. Use to read docs, READMEs, package indexes.", + promptSnippet: + "Use `curl` to fetch documentation pages or other read-only HTTP resources. POST and stateful methods are not available — anything stateful must go through `bt`.", + promptGuidelines: [ + "Only GET and HEAD are permitted; the tool errors otherwise.", + "Pass full absolute URLs.", + "Prefer https://www.braintrust.dev/docs/... for Braintrust-specific guidance.", + "Bodies above 1 MB are truncated; the response indicates truncation.", + ], + parameters: CURL_PARAMS, + async execute(_toolCallId, params) { + try { + const result = await runCurl(params as CurlParams); + const truncatedNote = result.truncated ? "\n[truncated]" : ""; + const text = [ + `${result.status} ${result.statusText} (${result.url})`, + result.body + truncatedNote, + ].join("\n"); + return { + content: [{ type: "text", text }], + details: { + status: result.status, + url: result.url, + truncated: result.truncated, + }, + }; + } catch (err) { + return { + content: [ + { + type: "text", + text: `curl failed: ${(err as Error).message}`, + }, + ], + details: { error: (err as Error).message }, + }; + } + }, + }); +} diff --git a/packages/bt-wizard-harness/extensions/git-tool.ts b/packages/bt-wizard-harness/extensions/git-tool.ts new file mode 100644 index 0000000..c0a5d7d --- /dev/null +++ b/packages/bt-wizard-harness/extensions/git-tool.ts @@ -0,0 +1,262 @@ +/** + * `git` extension. + * + * Exposes a structured `git` tool. An allowlist of safe subcommands is + * enforced; destructive remote and force operations are blocked. + * + * Allowed: read-only queries (status, log, diff, show, blame, grep, ls-files, + * ls-tree, rev-parse, describe, branch, tag, shortlog) plus staging and + * committing (add, commit) and file-level restore (checkout, restore). + * + * Blocked: push, pull, fetch, clone, remote, reset --hard, clean -f, and + * any subcommand not in the allowlist. + */ + +import { spawn } from "node:child_process"; +import { Type } from "typebox"; + +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; + +const ALLOWED_SUBCOMMANDS = new Set([ + // read-only + "status", + "log", + "diff", + "show", + "blame", + "grep", + "ls-files", + "ls-tree", + "ls-remote", + "rev-parse", + "rev-list", + "describe", + "branch", + "tag", + "shortlog", + "stash", + "format-patch", + // staging / committing + "add", + "commit", + // file-level restore (must be used with a path, not to switch branches) + "checkout", + "restore", + // submodule inspection + "submodule", +]); + +// argv elements that make otherwise-safe subcommands dangerous +const BLOCKED_FLAGS = new Set([ + "--force", + "-f", + "--hard", + "--delete", + "-D", + "--mirror", + "--all", +]); + +// Subcommand-specific blocks regardless of flags +const BLOCKED_SUBCOMMANDS = new Set([ + "push", + "pull", + "fetch", + "clone", + "remote", + "clean", + "rebase", + "merge", + "cherry-pick", + "revert", + "bisect", + "reflog", + "gc", + "fsck", + "filter-branch", + "am", + "apply", +]); + +const GIT_PARAMS = Type.Object({ + args: Type.Array(Type.String(), { + description: + "Argv passed to git (no shell expansion). Example: ['status', '--short'].", + }), + stdin: Type.Optional( + Type.String({ + description: "Optional text written to git's stdin.", + }), + ), + timeout_ms: Type.Optional( + Type.Integer({ + description: "Hard timeout in milliseconds (default 30000).", + minimum: 1000, + maximum: 120000, + }), + ), +}); + +type GitParams = { + args: string[]; + stdin?: string; + timeout_ms?: number; +}; + +const DEFAULT_TIMEOUT_MS = 30_000; +const MAX_OUTPUT_BYTES = 500_000; + +function runGit( + params: GitParams, + cwd: string, +): Promise<{ + exitCode: number; + stdout: string; + stderr: string; + timedOut: boolean; +}> { + return new Promise((resolve) => { + const child = spawn("git", params.args, { + cwd, + stdio: ["pipe", "pipe", "pipe"], + shell: false, + }); + + let stdoutBytes = 0; + let stderrBytes = 0; + const stdoutChunks: string[] = []; + const stderrChunks: string[] = []; + let timedOut = false; + + const timer = setTimeout(() => { + timedOut = true; + child.kill("SIGTERM"); + setTimeout(() => child.kill("SIGKILL"), 1000).unref(); + }, params.timeout_ms ?? DEFAULT_TIMEOUT_MS); + + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (d: string) => { + stdoutBytes += Buffer.byteLength(d); + if (stdoutBytes < MAX_OUTPUT_BYTES) stdoutChunks.push(d); + }); + child.stderr.on("data", (d: string) => { + stderrBytes += Buffer.byteLength(d); + if (stderrBytes < MAX_OUTPUT_BYTES) stderrChunks.push(d); + }); + + child.on("error", (err) => { + clearTimeout(timer); + resolve({ + exitCode: 127, + stdout: "", + stderr: `Failed to spawn git: ${err.message}`, + timedOut: false, + }); + }); + + child.on("close", (code) => { + clearTimeout(timer); + resolve({ + exitCode: code ?? -1, + stdout: stdoutChunks.join(""), + stderr: stderrChunks.join(""), + timedOut, + }); + }); + + if (params.stdin && params.stdin.length > 0) { + child.stdin.write(params.stdin); + } + child.stdin.end(); + }); +} + +export default function gitTool(pi: ExtensionAPI) { + pi.registerTool({ + name: "git", + label: "git", + description: + "Run safe git commands (read-only queries, add, commit, checkout/restore for files). Push, pull, fetch, remote modifications, and destructive resets are blocked.", + promptSnippet: + "Use `git` for version-control operations: status, log, diff, add, commit. bash/python are not available.", + promptGuidelines: [ + "Pass argv as an array — do not embed shell quoting.", + "Use `git status` and `git diff` to inspect changes before committing.", + "Use `git add ` then `git commit -m '...'` to stage and commit SDK changes.", + "Push, pull, fetch, and destructive resets are blocked.", + ], + parameters: GIT_PARAMS, + async execute(_toolCallId, params) { + const p = params as GitParams; + const subcommand = p.args[0]; + + if (!subcommand) { + return { + content: [{ type: "text", text: "error: no git subcommand given" }], + details: { blocked: true }, + }; + } + + if (BLOCKED_SUBCOMMANDS.has(subcommand)) { + return { + content: [ + { + type: "text", + text: `error: git ${subcommand} is not permitted in the bt-wizard harness.`, + }, + ], + details: { blocked: true, subcommand }, + }; + } + + if (!ALLOWED_SUBCOMMANDS.has(subcommand)) { + return { + content: [ + { + type: "text", + text: `error: git ${subcommand} is not in the allowed list. Use request_command if you need it.`, + }, + ], + details: { blocked: true, subcommand }, + }; + } + + // Block dangerous flags on otherwise-allowed subcommands. + // Exception: `git checkout -f` on a file path is still blocked. + for (const arg of p.args.slice(1)) { + if (BLOCKED_FLAGS.has(arg)) { + return { + content: [ + { + type: "text", + text: `error: flag "${arg}" is not permitted in git ${subcommand}.`, + }, + ], + details: { blocked: true, flag: arg }, + }; + } + } + + const result = await runGit(p, process.cwd()); + const summary = result.timedOut + ? `git timed out (exit ${result.exitCode})` + : `git exited ${result.exitCode}`; + return { + content: [ + { + type: "text", + text: [ + summary, + "--- stdout ---", + result.stdout, + "--- stderr ---", + result.stderr, + ].join("\n"), + }, + ], + details: { exitCode: result.exitCode, timedOut: result.timedOut }, + }; + }, + }); +} diff --git a/packages/bt-wizard-harness/extensions/request-command-tool.ts b/packages/bt-wizard-harness/extensions/request-command-tool.ts new file mode 100644 index 0000000..9913f56 --- /dev/null +++ b/packages/bt-wizard-harness/extensions/request-command-tool.ts @@ -0,0 +1,194 @@ +/** + * `request_command` extension. + * + * Lets the agent ask the user to approve a one-off command that is not in the + * default allowed tool list (git, pkg, bt, curl, read/write/edit/grep/find/ls). + * + * Uses ctx.ui.confirm() so it works in both interactive and RPC mode. + * In RPC mode the harness handles the extension_ui_request sub-protocol; + * the tool does not touch stdin directly. + */ + +import { spawn } from "node:child_process"; +import { Type } from "typebox"; + +import type { + ExtensionAPI, + ExtensionContext, +} from "@mariozechner/pi-coding-agent"; + +const REQUEST_PARAMS = Type.Object({ + command: Type.String({ + description: + "The executable or script to run (e.g. 'npx', 'node', 'make').", + }), + args: Type.Array(Type.String(), { + description: "Arguments for the command (no shell expansion).", + }), + reason: Type.String({ + description: "Why this command is needed — shown to the user.", + }), + timeout_ms: Type.Optional( + Type.Integer({ + description: "Hard timeout in milliseconds (default 120000).", + minimum: 1000, + maximum: 600000, + }), + ), +}); + +type RequestParams = { + command: string; + args: string[]; + reason: string; + timeout_ms?: number; +}; + +const DEFAULT_TIMEOUT_MS = 120_000; +const MAX_OUTPUT_BYTES = 1_000_000; + +function runCommand( + command: string, + args: string[], + timeoutMs: number, + cwd: string, +): Promise<{ + exitCode: number; + stdout: string; + stderr: string; + timedOut: boolean; +}> { + return new Promise((resolve) => { + const child = spawn(command, args, { + cwd, + stdio: ["ignore", "pipe", "pipe"], + shell: false, + }); + + let stdoutBytes = 0; + let stderrBytes = 0; + const stdoutChunks: string[] = []; + const stderrChunks: string[] = []; + let timedOut = false; + + const timer = setTimeout(() => { + timedOut = true; + child.kill("SIGTERM"); + setTimeout(() => child.kill("SIGKILL"), 2000).unref(); + }, timeoutMs); + + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (d: string) => { + stdoutBytes += Buffer.byteLength(d); + if (stdoutBytes < MAX_OUTPUT_BYTES) stdoutChunks.push(d); + }); + child.stderr.on("data", (d: string) => { + stderrBytes += Buffer.byteLength(d); + if (stderrBytes < MAX_OUTPUT_BYTES) stderrChunks.push(d); + }); + + child.on("error", (err) => { + clearTimeout(timer); + resolve({ + exitCode: 127, + stdout: "", + stderr: `Failed to spawn ${command}: ${err.message}`, + timedOut: false, + }); + }); + + child.on("close", (code) => { + clearTimeout(timer); + resolve({ + exitCode: code ?? -1, + stdout: stdoutChunks.join(""), + stderr: stderrChunks.join(""), + timedOut, + }); + }); + }); +} + +export default function requestCommandTool(pi: ExtensionAPI) { + pi.registerTool({ + name: "request_command", + label: "Request command approval", + description: + "Ask the user to approve running a command that is not in the default allowed tool list. The command only runs if the user confirms.", + promptSnippet: + "Use `request_command` when you need to run a tool not otherwise available (e.g. npx, node scripts, make). The user must approve each call.", + promptGuidelines: [ + "Use this only when no other allowed tool can accomplish the task.", + "Be specific and concise in the `reason` field.", + "Pass argv as an array — do not embed shell quoting.", + "The user sees the full command and reason before deciding.", + ], + parameters: REQUEST_PARAMS, + async execute( + _toolCallId, + params, + _signal, + _onUpdate, + ctx: ExtensionContext, + ) { + const p = params as RequestParams; + const fullCommand = [p.command, ...p.args].join(" "); + + if (p.command === "sudo" || p.args.includes("sudo")) { + return { + content: [ + { type: "text", text: `Command denied: sudo is not allowed.` }, + ], + details: { approved: false }, + }; + } + + const approved = await ctx.ui.confirm( + "bt-wizard: command approval requested", + `Command: ${fullCommand}\nReason: ${p.reason}`, + ); + + if (!approved) { + return { + content: [ + { + type: "text", + text: `Command denied by user: ${fullCommand}`, + }, + ], + details: { approved: false }, + }; + } + + const result = await runCommand( + p.command, + p.args, + p.timeout_ms ?? DEFAULT_TIMEOUT_MS, + process.cwd(), + ); + const summary = result.timedOut + ? `${p.command} timed out (exit ${result.exitCode})` + : `${p.command} exited ${result.exitCode}`; + return { + content: [ + { + type: "text", + text: [ + summary, + "--- stdout ---", + result.stdout, + "--- stderr ---", + result.stderr, + ].join("\n"), + }, + ], + details: { + approved: true, + exitCode: result.exitCode, + timedOut: result.timedOut, + }, + }; + }, + }); +} From 02ebeea08489aabdec2fb0ce24a7ba4b3438d798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:46:12 -0700 Subject: [PATCH 39/52] feat: add package-manager tool extension --- .../extensions/package-manager-tool.ts | 422 ++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 packages/bt-wizard-harness/extensions/package-manager-tool.ts diff --git a/packages/bt-wizard-harness/extensions/package-manager-tool.ts b/packages/bt-wizard-harness/extensions/package-manager-tool.ts new file mode 100644 index 0000000..7daba25 --- /dev/null +++ b/packages/bt-wizard-harness/extensions/package-manager-tool.ts @@ -0,0 +1,422 @@ +/** + * Package-manager / formatter / linter / test-runner extension. + * + * Exposes a `pkg` tool so the agent can run language tooling without needing + * bash. The allowed tools are gated by the languages detected by bt-wizard, + * passed in via the `BT_WIZARD_LANGUAGES` environment variable. + * + * If no languages are detected, all known tools are permitted. + * + * Per-language allowlists: + * + * Python + * pkg managers: conda, hatch, mamba, pdm, pip, pipenv, pipx, poetry, rye, uv + * formatters: autopep8, black, isort, ruff, yapf + * linters: flake8, mypy, pylint, pyright, ruff + * test: coverage, nose2, pytest, tox, unittest + * + * JavaScript / TypeScript + * pkg managers: bun, deno, ni, npm, pnpm, yarn + * formatters: biome, dprint, prettier + * linters: biome, eslint, oxlint + * test: ava, jasmine, jest, mocha, vitest + * + * Go + * pkg managers: dep, glide, go + * formatters: gofmt, gofumpt, goimports + * linters: golangci-lint, revive, staticcheck + * test: ginkgo, gomock, testify + * + * C# + * pkg managers: choco, dotnet, nuget, paket + * formatters: csharpier, dotnet + * linters: dotnet + * test: dotnet + * + * Java + * pkg managers: bazel, gradle, ivy, mvn, mill, sbt + * formatters: checkstyle, google-java-format, spotless + * linters: checkstyle, pmd, spotbugs + * test: gradle, mvn, sbt + * + * Ruby + * pkg managers: asdf, bundle, gem, rbenv, rvm + * formatters: rubocop, rufo, standardrb + * linters: brakeman, reek, rubocop, standardrb + * test: cucumber, minitest, rspec, test-unit + */ + +import { spawn } from "node:child_process"; +import { Type } from "typebox"; + +import type { ExtensionAPI, Theme } from "@mariozechner/pi-coding-agent"; +import { Text } from "@mariozechner/pi-tui"; + +type Language = "python" | "typescript" | "go" | "csharp" | "java" | "ruby"; + +const LANGUAGE_TOOLS: Record = { + python: [ + // interpreters + "python", + "python3", + // package managers + "conda", + "hatch", + "mamba", + "pdm", + "pip", + "pipenv", + "pipx", + "poetry", + "rye", + "uv", + // formatters + "autopep8", + "black", + "isort", + "yapf", + // linters + "flake8", + "mypy", + "pylint", + "pyright", + // ruff handles both formatting and linting + "ruff", + // test + "coverage", + "nose2", + "pytest", + "tox", + "unittest", + ], + typescript: [ + // interpreters / runtimes + "node", + "npx", + "ts-node", + "tsx", + // package managers (bun and deno double as runtimes) + "bun", + "deno", + "ni", + "npm", + "pnpm", + "yarn", + // formatters + "dprint", + "prettier", + // linters + "eslint", + "oxlint", + // biome handles formatting + linting + "biome", + // test + "ava", + "jasmine", + "jest", + "mocha", + "vitest", + ], + go: [ + // interpreter/compiler (go run, go build, go test, go mod …) + "dep", + "glide", + "go", + // formatters + "gofmt", + "gofumpt", + "goimports", + // linters + "golangci-lint", + "revive", + "staticcheck", + // test (go test is via "go", others are standalone) + "ginkgo", + "gomock", + "testify", + ], + csharp: [ + // runtime / package managers / build — dotnet covers all of these + "dotnet", + // package managers + "choco", + "nuget", + "paket", + // formatters + linters + "csharpier", + ], + java: [ + // interpreter / compiler + "java", + "javac", + // package managers / build tools + "bazel", + "gradle", + "ivy", + "mvn", + "mill", + "sbt", + // formatters / linters + "checkstyle", + "google-java-format", + "spotless", + "pmd", + "spotbugs", + ], + ruby: [ + // interpreter + "ruby", + // package managers + "asdf", + "bundle", + "gem", + "rbenv", + "rvm", + // formatters + linters (rubocop / standardrb handle both) + "rubocop", + "rufo", + "standardrb", + // linters + "brakeman", + "reek", + // test + "cucumber", + "minitest", + "rspec", + "test-unit", + ], +}; + +// Always allowed regardless of detected language. +const UNIVERSAL_TOOLS: ReadonlySet = new Set(["env"]); + +const ALL_TOOLS: ReadonlySet = new Set([ + ...UNIVERSAL_TOOLS, + ...(Object.values(LANGUAGE_TOOLS) as readonly string[][]).flat(), +]); + +function allowedTools(): ReadonlySet { + const raw = process.env["BT_WIZARD_LANGUAGES"] ?? ""; + const langs = raw + .split(",") + .map((s) => s.trim()) + .filter((s): s is Language => s.length > 0 && s in LANGUAGE_TOOLS); + if (langs.length === 0) { + return ALL_TOOLS; + } + const allowed = new Set([...UNIVERSAL_TOOLS]); + for (const lang of langs) { + for (const tool of LANGUAGE_TOOLS[lang]) { + allowed.add(tool); + } + } + return allowed; +} + +const PKG_PARAMS = Type.Object({ + manager: Type.String({ + description: + "Tool binary name (package manager, formatter, linter, or test runner — e.g. npm, pip, go, pytest, eslint). Must be in the allowed list for the project's language.", + }), + args: Type.Array(Type.String(), { + description: + "Arguments passed to the package manager (no shell expansion). Example: ['install', 'braintrust'].", + }), + timeout_ms: Type.Optional( + Type.Integer({ + description: "Hard timeout in milliseconds (default 120000).", + minimum: 1000, + maximum: 600000, + }), + ), +}); + +type PkgParams = { + manager: string; + args: string[]; + timeout_ms?: number; +}; + +const DEFAULT_TIMEOUT_MS = 120_000; +const MAX_STREAM_BYTES = 500; + +function tailTruncate( + s: string, + maxBytes: number, +): { text: string; truncated: boolean } { + const buf = Buffer.from(s, "utf8"); + if (buf.byteLength <= maxBytes) return { text: s, truncated: false }; + return { text: buf.slice(-maxBytes).toString("utf8"), truncated: true }; +} + +function runPkg( + manager: string, + args: string[], + timeoutMs: number, + cwd: string, +): Promise<{ + exitCode: number; + stdout: string; + stderr: string; + timedOut: boolean; +}> { + return new Promise((resolve) => { + const child = spawn(manager, args, { + cwd, + stdio: ["ignore", "pipe", "pipe"], + shell: false, + }); + + const stdoutChunks: string[] = []; + const stderrChunks: string[] = []; + let timedOut = false; + + const timer = setTimeout(() => { + timedOut = true; + child.kill("SIGTERM"); + setTimeout(() => child.kill("SIGKILL"), 2000).unref(); + }, timeoutMs); + + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (d: string) => { + stdoutChunks.push(d); + }); + child.stderr.on("data", (d: string) => { + stderrChunks.push(d); + }); + + child.on("error", (err) => { + clearTimeout(timer); + resolve({ + exitCode: 127, + stdout: "", + stderr: `Failed to spawn ${manager}: ${err.message}`, + timedOut: false, + }); + }); + + child.on("close", (code) => { + clearTimeout(timer); + resolve({ + exitCode: code ?? -1, + stdout: stdoutChunks.join(""), + stderr: stderrChunks.join(""), + timedOut, + }); + }); + }); +} + +export default function packageManagerTool(pi: ExtensionAPI) { + const allowed = allowedTools(); + const allowedList = [...allowed].sort().join(", "); + + pi.registerTool({ + name: "pkg", + label: "language tooling", + description: `Run language package managers, formatters, linters, and test runners. Allowed tools for this project: ${allowedList}.`, + promptSnippet: + "Use `pkg` to run package managers, formatters, linters, and test runners (npm, pip, pytest, eslint, etc.). bash/python are not available.", + promptGuidelines: [ + `Allowed tools: ${allowedList}.`, + "Pass argv as an array — do not embed shell quoting.", + "Example: manager='npm', args=['install','braintrust']", + "Example: manager='pytest', args=['tests/']", + "The command runs in the current working directory.", + ], + parameters: PKG_PARAMS, + renderShell: "self", + renderCall(args: PkgParams, theme: Theme) { + const cmd = [args.manager, ...args.args].join(" "); + return new Text( + theme.fg("toolTitle", "$ ") + theme.fg("accent", cmd), + 0, + 0, + ); + }, + renderResult(result, _options, theme, context) { + const details = result.details as + | { exitCode?: number; timedOut?: boolean; blocked?: boolean } + | undefined; + const a = context.args as PkgParams; + const cmd = [a.manager, ...a.args].join(" "); + + if (details?.blocked) { + return new Text( + theme.fg("toolTitle", "$ ") + + theme.fg("accent", cmd) + + " → " + + theme.fg("error", "blocked"), + 0, + 0, + ); + } + + const exitCode = details?.exitCode ?? -1; + const timedOut = details?.timedOut ?? false; + const status = timedOut + ? theme.fg("warning", `timed out (exit ${exitCode})`) + : exitCode === 0 + ? theme.fg("success", `exit ${exitCode}`) + : theme.fg("error", `exit ${exitCode}`); + + return new Text( + theme.fg("toolTitle", "$ ") + + theme.fg("accent", cmd) + + " → " + + status, + 0, + 0, + ); + }, + async execute(_toolCallId, params) { + const p = params as PkgParams; + const mgr = p.manager.trim().toLowerCase(); + + if (!allowed.has(mgr)) { + return { + content: [ + { + type: "text", + text: [ + `error: "${mgr}" is not an allowed tool for this project.`, + `Allowed: ${allowedList}.`, + `Use request_command if you need a different tool.`, + ].join("\n"), + }, + ], + details: { blocked: true, manager: mgr }, + }; + } + + const result = await runPkg( + mgr, + p.args, + p.timeout_ms ?? DEFAULT_TIMEOUT_MS, + process.cwd(), + ); + const cmd = [mgr, ...p.args].join(" "); + const summary = result.timedOut + ? `$ ${cmd} → timed out (exit ${result.exitCode})` + : `$ ${cmd} → exit ${result.exitCode}`; + const out = tailTruncate(result.stdout, MAX_STREAM_BYTES); + const err = tailTruncate(result.stderr, MAX_STREAM_BYTES); + const parts: string[] = [summary]; + if (out.text) { + if (out.truncated) parts.push("--- stdout (last 500B) ---"); + else parts.push("--- stdout ---"); + parts.push(out.text); + } + if (err.text) { + if (err.truncated) parts.push("--- stderr (last 500B) ---"); + else parts.push("--- stderr ---"); + parts.push(err.text); + } + return { + content: [{ type: "text", text: parts.join("\n") }], + details: { exitCode: result.exitCode, timedOut: result.timedOut }, + }; + }, + }); +} From 7a4116d221b102f7a26fba45308eefcafd2364f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:46:20 -0700 Subject: [PATCH 40/52] feat: add bt-wizard-harness launcher binary --- .../bin/bt-wizard-harness.mjs | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100755 packages/bt-wizard-harness/bin/bt-wizard-harness.mjs diff --git a/packages/bt-wizard-harness/bin/bt-wizard-harness.mjs b/packages/bt-wizard-harness/bin/bt-wizard-harness.mjs new file mode 100755 index 0000000..de69330 --- /dev/null +++ b/packages/bt-wizard-harness/bin/bt-wizard-harness.mjs @@ -0,0 +1,219 @@ +#!/usr/bin/env node +/** + * Thin launcher for the bt-wizard pi harness. + * + * Usage: bt-wizard-harness --prompt-file [extra pi args...] + * + * Runs pi inside a PTY so it gets a real terminal: correct window size, + * ANSI colours, cursor control, and automatic resize on SIGWINCH. + * We intercept the PTY output to scan for "summary" (case-insensitive); + * when detected we kill pi and exit so the wizard can run its cleanup phase. + * + * Tools loaded (--no-builtin-tools baseline): + * read,write,edit,grep,find,ls built-in file ops + * path-guard restrict writes to cwd / .env.braintrust + * bt-tool bt CLI + * curl-tool GET/HEAD only HTTP + * git-tool safe git subcommands + * package-manager-tool language-gated pkg/fmt/lint/test + * request-command-tool user-approved one-off commands + */ + +import { existsSync, readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import pty from "node-pty"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const pkgDir = resolve(__dirname, ".."); + +// --------------------------------------------------------------------------- +// Arg parsing +// --------------------------------------------------------------------------- + +const argv = process.argv.slice(2); +let promptFile; +const passthrough = []; +for (let i = 0; i < argv.length; i += 1) { + const a = argv[i]; + if (a === "--prompt-file") { + promptFile = argv[i + 1]; + i += 1; + } else if (a === "-h" || a === "--help") { + process.stdout.write( + "Usage: bt-wizard-harness --prompt-file [extra pi args...]\n", + ); + process.exit(0); + } else { + passthrough.push(a); + } +} + +if (!promptFile) { + process.stderr.write("error: --prompt-file is required\n"); + process.exit(2); +} +if (!existsSync(promptFile)) { + process.stderr.write(`error: prompt file not found: ${promptFile}\n`); + process.exit(2); +} + +const promptText = readFileSync(promptFile, "utf8"); + +// Resolve pi's actual JS entry point so node-pty can spawn `node ` +// directly. The .bin/pi shim is a POSIX shell script that node-pty cannot +// exec via posix_spawnp, so we must bypass it. +function resolvePiJs() { + const shimCandidates = [ + resolve(pkgDir, "node_modules", ".bin", "pi"), + resolve(pkgDir, "..", "..", "node_modules", ".bin", "pi"), + ]; + for (const shim of shimCandidates) { + if (!existsSync(shim)) continue; + // The shim contains: exec node "$basedir/../../../../path/to/cli.js" "$@" + // $basedir is the directory containing the shim file. + const shimDir = dirname(shim); + const content = readFileSync(shim, "utf8"); + const m = content.match(/exec\s+\S*node\S*\s+"([^"]+\.js)"/); + if (!m) continue; + // Replace the literal "$basedir" token with the shim's directory. + const jsPath = resolve(m[1].replace("$basedir", shimDir)); + if (existsSync(jsPath)) return jsPath; + } + return null; +} + +const piJs = resolvePiJs(); +// spawn args: if we found the JS file, use `node `; else fall back to +// spawning the `pi` executable directly (works if installed globally as a +// real binary rather than a pnpm shim). +const [spawnBin, spawnArgs] = piJs ? [process.execPath, [piJs]] : ["pi", []]; + +const piArgs = [ + "--no-session", + "--no-builtin-tools", + "-t", + "read,write,edit,grep,find,ls,bt,pkg,curl,git,request_command", + "-e", + resolve(pkgDir, "extensions/path-guard.ts"), + "-e", + resolve(pkgDir, "extensions/bt-tool.ts"), + "-e", + resolve(pkgDir, "extensions/curl-tool.ts"), + "-e", + resolve(pkgDir, "extensions/git-tool.ts"), + "-e", + resolve(pkgDir, "extensions/package-manager-tool.ts"), + "-e", + resolve(pkgDir, "extensions/request-command-tool.ts"), + "--append-system-prompt", + promptText, + "Begin the Braintrust SDK instrumentation.", + ...passthrough, +]; + +// --------------------------------------------------------------------------- +// PTY spawn — pi sees a real terminal on all three fds +// --------------------------------------------------------------------------- + +const cols = process.stdout.columns ?? 80; +const rows = process.stdout.rows ?? 24; + +const piProc = pty.spawn(spawnBin, [...spawnArgs, ...piArgs], { + name: process.env.TERM ?? "xterm-256color", + cols, + rows, + cwd: process.cwd(), + env: process.env, +}); + +// --------------------------------------------------------------------------- +// Summary detection +// --------------------------------------------------------------------------- + +// Scan a sliding window so "summary" split across chunks is still caught. +const SENTINEL_COMPLETE = "INSTRUMENTATION_COMPLETE"; +const SENTINEL_INCOMPLETE = "INSTRUMENTATION_INCOMPLETE"; +const WINDOW = SENTINEL_INCOMPLETE.length - 1; // longest sentinel + +let tail = ""; +let summaryDetected = false; +let shutdownTimer = null; +// While the user is typing, pause scanning so echoed keystrokes don't trigger +// shutdown. The timer is reset on every keystroke and expires 150 ms after the +// last one — well before any agent response could arrive. +let userTypingTimer = null; + +function scheduleShutdown() { + // (Re-)arm a timer: kill pi after 1 s of PTY silence, i.e. when the agent + // has finished outputting and is waiting for the user to type. + clearTimeout(shutdownTimer); + shutdownTimer = setTimeout(() => { + try { + piProc.kill("SIGTERM"); + } catch { + // already gone + } + }, 1000); +} + +piProc.onData((data) => { + process.stdout.write(data); + + if (userTypingTimer) return; + + if (summaryDetected) { + // Agent is still outputting after the summary word — keep pushing the + // shutdown deadline until output goes quiet. + scheduleShutdown(); + return; + } + + const text = tail + data; + if (text.includes(SENTINEL_COMPLETE) || text.includes(SENTINEL_INCOMPLETE)) { + summaryDetected = true; + scheduleShutdown(); + } else { + tail = text.length > WINDOW ? text.slice(-WINDOW) : text; + } +}); + +piProc.onExit(() => { + process.stdin.setRawMode?.(false); + process.exit(summaryDetected ? 0 : 130); +}); + +// --------------------------------------------------------------------------- +// Forward stdin and resize events +// --------------------------------------------------------------------------- + +if (process.stdin.isTTY) { + process.stdin.setRawMode(true); +} +process.stdin.resume(); +process.stdin.on("data", (data) => { + clearTimeout(userTypingTimer); + userTypingTimer = setTimeout(() => { + userTypingTimer = null; + tail = ""; // discard any echoed chars that landed in the window + }, 150); + piProc.write(typeof data === "string" ? data : data.toString("binary")); +}); + +process.on("SIGWINCH", () => { + piProc.resize(process.stdout.columns ?? 80, process.stdout.rows ?? 24); +}); + +// --------------------------------------------------------------------------- +// Signal forwarding +// --------------------------------------------------------------------------- + +process.on("SIGINT", () => { + // In raw mode, Ctrl-C is forwarded as a data byte (\x03) via stdin.on("data") + // above, so we only need this as a fallback for non-TTY contexts. + try { + piProc.kill("SIGINT"); + } catch { + // already gone + } +}); From 9c1b83fd6cdad18722e45ebaa383562dd655657d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:42:19 -0700 Subject: [PATCH 41/52] feat: add instrument.ts harness bridge --- packages/braintrust-wizard/src/instrument.ts | 194 +++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 packages/braintrust-wizard/src/instrument.ts diff --git a/packages/braintrust-wizard/src/instrument.ts b/packages/braintrust-wizard/src/instrument.ts new file mode 100644 index 0000000..789cedc --- /dev/null +++ b/packages/braintrust-wizard/src/instrument.ts @@ -0,0 +1,194 @@ +import { spawn } from "node:child_process"; +import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { tmpdir, platform } from "node:os"; +import { join } from "node:path"; + +import type { DetectedLanguage } from "./language-detect"; + +const HARNESS_PACKAGE_BIN = + "@braintrust/bt-wizard-harness/bin/bt-wizard-harness.mjs"; + +function resolveHarnessPath(): + | { readonly ok: true; readonly path: string } + | { readonly ok: false; readonly reason: string } { + try { + const require = createRequire(import.meta.url); + return { ok: true, path: require.resolve(HARNESS_PACKAGE_BIN) }; + } catch (err) { + return { + ok: false, + reason: err instanceof Error ? err.message : String(err), + }; + } +} + +/** + * Build the shell command a user can copy-paste to re-run the harness against + * a saved prompt file. Returns undefined if the harness binary cannot be + * resolved. + */ +export function buildHarnessCommand( + promptFilePath: string, +): string | undefined { + const resolved = resolveHarnessPath(); + if (!resolved.ok) { + return undefined; + } + return `node ${JSON.stringify(resolved.path)} --prompt-file ${JSON.stringify(promptFilePath)}`; +} + +export type InstallBtResult = + | { readonly status: "already-installed" } + | { readonly status: "installed" } + | { readonly status: "skipped"; readonly reason: string } + | { readonly status: "failed"; readonly reason: string }; + +const BT_INSTALL_URL = "https://bt.dev/cli/install.sh"; + +export async function ensureBtOnPath(): Promise { + if (await commandExists("bt")) { + return { status: "already-installed" }; + } + const plat = platform(); + if (plat === "win32") { + return { + status: "skipped", + reason: "Windows install of `bt` is not yet supported.", + }; + } + if (plat !== "darwin" && plat !== "linux") { + return { + status: "skipped", + reason: `Automatic install of \`bt\` not supported on ${plat}.`, + }; + } + return runShellPipeInstall(); +} + +function commandExists(cmd: string): Promise { + return new Promise((resolve) => { + const which = spawn("sh", ["-c", `command -v ${cmd}`], { + stdio: "ignore", + }); + which.on("error", () => resolve(false)); + which.on("close", (code) => resolve(code === 0)); + }); +} + +function runShellPipeInstall(): Promise { + return new Promise((resolve) => { + const child = spawn("sh", ["-c", `curl -fsSL ${BT_INSTALL_URL} | bash`], { + stdio: "inherit", + }); + child.on("error", (err) => + resolve({ status: "failed", reason: err.message }), + ); + child.on("close", (code) => { + if (code === 0) { + resolve({ status: "installed" }); + } else { + resolve({ + status: "failed", + reason: `installer exited with code ${code}`, + }); + } + }); + }); +} + +export type WritePromptToTempResult = { + readonly path: string; +}; + +export function writePromptToTemp(prompt: string): WritePromptToTempResult { + const dir = mkdtempSync(join(tmpdir(), "bt-wizard-")); + const path = join(dir, "instrument-prompt.md"); + writeFileSync(path, prompt); + return { path }; +} + +export type RunHarnessResult = + | { + readonly status: "completed"; + readonly exitCode: number; + readonly signal: NodeJS.Signals | null; + readonly tracePermalink: string | undefined; + readonly promptFilePath: string; + } + | { + readonly status: "harness-not-found"; + readonly checked: readonly string[]; + }; + +/** + * Allocate a fresh result-file path that the harness will write the trace + * permalink to. The path is also injected into the agent prompt via + * {@link renderPrompt}'s `resultFilePath` and exposed to the harness via + * `BT_WIZARD_RESULT_FILE` (path-guard whitelists it). + */ +export function allocateResultFile(): string { + const dir = mkdtempSync(join(tmpdir(), "bt-wizard-")); + return join(dir, "result.txt"); +} + +function readResultFile(path: string): string | undefined { + try { + const raw = readFileSync(path, "utf8").trim(); + return raw.length > 0 ? raw : undefined; + } catch { + return undefined; + } +} + +export async function runHarness(args: { + readonly prompt: string; + readonly cwd: string; + readonly braintrustApiKey: string; + readonly resultFilePath: string; + readonly providerEnvVar?: string; + readonly providerApiKey?: string; + readonly languages?: readonly DetectedLanguage[]; +}): Promise { + const resolved = resolveHarnessPath(); + if (!resolved.ok) { + return { status: "harness-not-found", checked: [resolved.reason] }; + } + const promptFile = writePromptToTemp(args.prompt).path; + // Touch the result file so the agent knows the path is writable and so + // a missing file vs. an empty file are distinguishable. + writeFileSync(args.resultFilePath, ""); + return new Promise((resolve) => { + const child = spawn("node", [resolved.path, "--prompt-file", promptFile], { + cwd: args.cwd, + env: { + ...process.env, + BRAINTRUST_API_KEY: args.braintrustApiKey, + BT_WIZARD_RESULT_FILE: args.resultFilePath, + BT_WIZARD_LANGUAGES: (args.languages ?? []).join(","), + ...(args.providerEnvVar && args.providerApiKey + ? { [args.providerEnvVar]: args.providerApiKey } + : {}), + }, + stdio: "inherit", + }); + child.on("error", () => + resolve({ + status: "completed", + exitCode: 1, + signal: null, + tracePermalink: readResultFile(args.resultFilePath), + promptFilePath: promptFile, + }), + ); + child.on("close", (code, signal) => + resolve({ + status: "completed", + exitCode: code ?? 1, + signal, + tracePermalink: readResultFile(args.resultFilePath), + promptFilePath: promptFile, + }), + ); + }); +} From 31052d38de7b70af71dc57c9752d17796878198d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Wed, 13 May 2026 11:01:44 -0700 Subject: [PATCH 42/52] feat: accept providerCredentials map in runHarness Replace the providerEnvVar/providerApiKey pair with providerCredentials?: Record, spread directly into the child process env. Supports multi-credential providers (Bedrock, Vertex, Azure) alongside existing single-key providers. --- packages/braintrust-wizard/src/instrument.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/braintrust-wizard/src/instrument.ts b/packages/braintrust-wizard/src/instrument.ts index 789cedc..c82bafa 100644 --- a/packages/braintrust-wizard/src/instrument.ts +++ b/packages/braintrust-wizard/src/instrument.ts @@ -146,8 +146,7 @@ export async function runHarness(args: { readonly cwd: string; readonly braintrustApiKey: string; readonly resultFilePath: string; - readonly providerEnvVar?: string; - readonly providerApiKey?: string; + readonly providerCredentials?: Readonly>; readonly languages?: readonly DetectedLanguage[]; }): Promise { const resolved = resolveHarnessPath(); @@ -166,9 +165,7 @@ export async function runHarness(args: { BRAINTRUST_API_KEY: args.braintrustApiKey, BT_WIZARD_RESULT_FILE: args.resultFilePath, BT_WIZARD_LANGUAGES: (args.languages ?? []).join(","), - ...(args.providerEnvVar && args.providerApiKey - ? { [args.providerEnvVar]: args.providerApiKey } - : {}), + ...args.providerCredentials, }, stdio: "inherit", }); From b4d57e60439e6f6ae3d2c469a16625f122580669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:42:28 -0700 Subject: [PATCH 43/52] feat: wire PI instrumentation into wizard flow --- .../braintrust-wizard/src/clack-wizard.ts | 115 +++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/packages/braintrust-wizard/src/clack-wizard.ts b/packages/braintrust-wizard/src/clack-wizard.ts index 159d140..48414d1 100644 --- a/packages/braintrust-wizard/src/clack-wizard.ts +++ b/packages/braintrust-wizard/src/clack-wizard.ts @@ -4,16 +4,27 @@ import { WizardSigninAuthClient } from "./auth"; import { openBrowser } from "./browser"; import { buildLogsPermalink, buildCleanupMessage } from "./cleanup"; import { findGitRoot, isGitRepo, writeEnvBraintrust } from "./git"; +import { + allocateResultFile, + buildHarnessCommand, + ensureBtOnPath, + runHarness, + writePromptToTemp, +} from "./instrument"; +import { detectLanguages, type DetectedLanguage } from "./language-detect"; import type { WizardOptions } from "./options"; +import { renderPrompt } from "./prompt"; import { LLM_PROVIDERS, type LlmProvider } from "./providers"; import { DOCS_URL, NOT_GIT_REPO_WARNING, PROVIDER_KEY_QUESTION, PROVIDER_QUESTION, + RUN_HARNESS_QUESTION, WIZARD_CANCEL_MESSAGE, WIZARD_TITLE, gitignoreNote, + promptSavedNote, wizardLoginPrompt, } from "./wizard-copy"; import type { CredentialField } from "./providers"; @@ -119,10 +130,48 @@ export async function runClackWizard(deps: WizardDeps): Promise { ); } + const canInstrument = !provider.custom && providerKey !== undefined; + const languages = detectLanguages(deps.cwd); + + let tracePermalink: string | undefined; + let resumeCommand: string | undefined; + if (canInstrument) { + const runIt = unwrap( + prompts, + await prompts.confirm({ + initialValue: true, + message: RUN_HARNESS_QUESTION, + }), + ); + if (runIt) { + const result = await runInstrumentation(deps, { + org: session.orgInfo.name, + project: session.project.name, + apiKey: session.apiKey, + providerEnvVar: provider.envVar, + providerApiKey: providerKey, + languages, + }); + tracePermalink = result.tracePermalink; + resumeCommand = result.resumeCommand; + } else { + const { path } = writePromptToTemp( + renderPrompt({ languages, interactive: false }), + ); + prompts.note(promptSavedNote(path), "Prompt saved"); + } + } else { + const { path } = writePromptToTemp( + renderPrompt({ languages, interactive: false }), + ); + prompts.note(promptSavedNote(path), "Prompt saved"); + } + prompts.outro( buildCleanupMessage({ docsUrl: DOCS_URL, - tracePermalink: undefined, + tracePermalink, + resumeCommand, }), ); @@ -173,6 +222,70 @@ async function selectProvider(deps: WizardDeps): Promise { return value; } +type InstrumentationResult = { + readonly tracePermalink: string | undefined; + readonly resumeCommand: string | undefined; +}; + +async function runInstrumentation( + deps: WizardDeps, + args: { + readonly org: string; + readonly project: string; + readonly apiKey: string; + readonly providerEnvVar?: string; + readonly providerApiKey?: string; + readonly languages: readonly DetectedLanguage[]; + }, +): Promise { + const { prompts } = deps; + const installResult = await ensureBtOnPath(); + switch (installResult.status) { + case "already-installed": + break; + case "installed": + prompts.log.success("Installed `bt`."); + break; + case "skipped": + prompts.log.warn(`Skipping \`bt\` install: ${installResult.reason}`); + break; + case "failed": + prompts.log.error(`Couldn't install \`bt\`: ${installResult.reason}`); + break; + } + + const resultFilePath = allocateResultFile(); + const promptText = renderPrompt({ + languages: args.languages, + interactive: true, + resultFilePath, + }); + const harnessResult = await runHarness({ + prompt: promptText, + cwd: deps.cwd, + braintrustApiKey: args.apiKey, + resultFilePath, + providerEnvVar: args.providerEnvVar, + providerApiKey: args.providerApiKey, + languages: args.languages, + }); + + if (harnessResult.status === "harness-not-found") { + const { path } = writePromptToTemp(promptText); + prompts.log.warn( + `Harness not found. Wrote prompt to ${path}; run a coding agent against it manually.`, + ); + return { tracePermalink: undefined, resumeCommand: undefined }; + } + if (harnessResult.exitCode !== 0) { + prompts.log.warn(`Harness exited with code ${harnessResult.exitCode}.`); + } + return { + tracePermalink: harnessResult.tracePermalink, + resumeCommand: buildHarnessCommand(harnessResult.promptFilePath), + }; +} + export type DefaultDepsArgs = { readonly options: WizardOptions; readonly prompts: ClackWizardPrompts; From ac95e583c5a84c3cf6e255d946120a4e185c7c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Wed, 13 May 2026 11:01:45 -0700 Subject: [PATCH 44/52] feat: pass providerCredentials map to runInstrumentation Update credential collection and runInstrumentation to use the new providerCredentials: Record shape from the wizard flow and instrument.ts harness. Supports multi-credential providers. --- .../braintrust-wizard/src/clack-wizard.ts | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/braintrust-wizard/src/clack-wizard.ts b/packages/braintrust-wizard/src/clack-wizard.ts index 48414d1..2f5c984 100644 --- a/packages/braintrust-wizard/src/clack-wizard.ts +++ b/packages/braintrust-wizard/src/clack-wizard.ts @@ -14,7 +14,7 @@ import { import { detectLanguages, type DetectedLanguage } from "./language-detect"; import type { WizardOptions } from "./options"; import { renderPrompt } from "./prompt"; -import { LLM_PROVIDERS, type LlmProvider } from "./providers"; +import { LLM_PROVIDERS, type LlmProvider, type CredentialField } from "./providers"; import { DOCS_URL, NOT_GIT_REPO_WARNING, @@ -130,7 +130,7 @@ export async function runClackWizard(deps: WizardDeps): Promise { ); } - const canInstrument = !provider.custom && providerKey !== undefined; + const canInstrument = !provider.custom && providerCredentials !== undefined; const languages = detectLanguages(deps.cwd); let tracePermalink: string | undefined; @@ -148,8 +148,7 @@ export async function runClackWizard(deps: WizardDeps): Promise { org: session.orgInfo.name, project: session.project.name, apiKey: session.apiKey, - providerEnvVar: provider.envVar, - providerApiKey: providerKey, + providerCredentials, languages, }); tracePermalink = result.tracePermalink; @@ -227,14 +226,39 @@ type InstrumentationResult = { readonly resumeCommand: string | undefined; }; +async function collectCredentials( + prompts: ClackWizardPrompts, + provider: LlmProvider, +): Promise | undefined> { + const fields: readonly CredentialField[] = provider.credentials ?? [ + { envVar: provider.envVar!, label: provider.label, secret: true }, + ]; + const result: Record = {}; + for (const field of fields) { + const raw = unwrap( + prompts, + field.secret !== false + ? await prompts.password({ message: PROVIDER_KEY_QUESTION(field.label) }) + : await prompts.text({ message: PROVIDER_KEY_QUESTION(field.label) }), + ); + if (raw.length > 0) { + result[field.envVar] = raw; + } + } + if (Object.keys(result).length === 0) { + prompts.log.warn("No credentials entered; skipping instrumentation."); + return undefined; + } + return result; +} + async function runInstrumentation( deps: WizardDeps, args: { readonly org: string; readonly project: string; readonly apiKey: string; - readonly providerEnvVar?: string; - readonly providerApiKey?: string; + readonly providerCredentials?: Readonly>; readonly languages: readonly DetectedLanguage[]; }, ): Promise { @@ -265,8 +289,7 @@ async function runInstrumentation( cwd: deps.cwd, braintrustApiKey: args.apiKey, resultFilePath, - providerEnvVar: args.providerEnvVar, - providerApiKey: args.providerApiKey, + providerCredentials: args.providerCredentials, languages: args.languages, }); From f9a4cb212b1818b137295c6ad155a2ea9dde4145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:42:28 -0700 Subject: [PATCH 45/52] feat: wire PI instrumentation into wizard flow --- .../braintrust-wizard/src/clack-wizard.ts | 33 ++----------------- 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/packages/braintrust-wizard/src/clack-wizard.ts b/packages/braintrust-wizard/src/clack-wizard.ts index 2f5c984..9a0f519 100644 --- a/packages/braintrust-wizard/src/clack-wizard.ts +++ b/packages/braintrust-wizard/src/clack-wizard.ts @@ -27,7 +27,6 @@ import { promptSavedNote, wizardLoginPrompt, } from "./wizard-copy"; -import type { CredentialField } from "./providers"; type SelectOption = { readonly label: string; @@ -110,9 +109,9 @@ export async function runClackWizard(deps: WizardDeps): Promise { }); const provider = await selectProvider(deps); - if (!provider.custom) { - await collectCredentials(prompts, provider); - } + const providerCredentials = provider.custom + ? undefined + : await collectCredentials(prompts, provider); const gitRoot = await findGitRoot(deps.cwd); if (gitRoot) { @@ -226,32 +225,6 @@ type InstrumentationResult = { readonly resumeCommand: string | undefined; }; -async function collectCredentials( - prompts: ClackWizardPrompts, - provider: LlmProvider, -): Promise | undefined> { - const fields: readonly CredentialField[] = provider.credentials ?? [ - { envVar: provider.envVar!, label: provider.label, secret: true }, - ]; - const result: Record = {}; - for (const field of fields) { - const raw = unwrap( - prompts, - field.secret !== false - ? await prompts.password({ message: PROVIDER_KEY_QUESTION(field.label) }) - : await prompts.text({ message: PROVIDER_KEY_QUESTION(field.label) }), - ); - if (raw.length > 0) { - result[field.envVar] = raw; - } - } - if (Object.keys(result).length === 0) { - prompts.log.warn("No credentials entered; skipping instrumentation."); - return undefined; - } - return result; -} - async function runInstrumentation( deps: WizardDeps, args: { From 5a112dc756bbc3b7a020200e9fc3ab8e88c54a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Wed, 13 May 2026 11:01:45 -0700 Subject: [PATCH 46/52] feat: pass providerCredentials map to runInstrumentation Update credential collection and runInstrumentation to use the new providerCredentials: Record shape from the wizard flow and instrument.ts harness. Supports multi-credential providers. --- .../braintrust-wizard/src/clack-wizard.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/braintrust-wizard/src/clack-wizard.ts b/packages/braintrust-wizard/src/clack-wizard.ts index 9a0f519..04d379f 100644 --- a/packages/braintrust-wizard/src/clack-wizard.ts +++ b/packages/braintrust-wizard/src/clack-wizard.ts @@ -225,6 +225,32 @@ type InstrumentationResult = { readonly resumeCommand: string | undefined; }; +async function collectCredentials( + prompts: ClackWizardPrompts, + provider: LlmProvider, +): Promise | undefined> { + const fields: readonly CredentialField[] = provider.credentials ?? [ + { envVar: provider.envVar!, label: provider.label, secret: true }, + ]; + const result: Record = {}; + for (const field of fields) { + const raw = unwrap( + prompts, + field.secret !== false + ? await prompts.password({ message: PROVIDER_KEY_QUESTION(field.label) }) + : await prompts.text({ message: PROVIDER_KEY_QUESTION(field.label) }), + ); + if (raw.length > 0) { + result[field.envVar] = raw; + } + } + if (Object.keys(result).length === 0) { + prompts.log.warn("No credentials entered; skipping instrumentation."); + return undefined; + } + return result; +} + async function runInstrumentation( deps: WizardDeps, args: { From 3db886d32541f46a605abb1c6f4a1d053b12639f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:32:15 -0700 Subject: [PATCH 47/52] feat: implement clack wizard flow --- .../braintrust-wizard/src/clack-wizard.ts | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/packages/braintrust-wizard/src/clack-wizard.ts b/packages/braintrust-wizard/src/clack-wizard.ts index 04d379f..9a0f519 100644 --- a/packages/braintrust-wizard/src/clack-wizard.ts +++ b/packages/braintrust-wizard/src/clack-wizard.ts @@ -225,32 +225,6 @@ type InstrumentationResult = { readonly resumeCommand: string | undefined; }; -async function collectCredentials( - prompts: ClackWizardPrompts, - provider: LlmProvider, -): Promise | undefined> { - const fields: readonly CredentialField[] = provider.credentials ?? [ - { envVar: provider.envVar!, label: provider.label, secret: true }, - ]; - const result: Record = {}; - for (const field of fields) { - const raw = unwrap( - prompts, - field.secret !== false - ? await prompts.password({ message: PROVIDER_KEY_QUESTION(field.label) }) - : await prompts.text({ message: PROVIDER_KEY_QUESTION(field.label) }), - ); - if (raw.length > 0) { - result[field.envVar] = raw; - } - } - if (Object.keys(result).length === 0) { - prompts.log.warn("No credentials entered; skipping instrumentation."); - return undefined; - } - return result; -} - async function runInstrumentation( deps: WizardDeps, args: { From b86d2c5e580ac21dfb7888f1505bf1e1c7d78342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 13:47:36 -0700 Subject: [PATCH 48/52] feat: single executable (SEA) Co-Authored-By: Claude Sonnet 4.6 --- packages/braintrust-wizard/rolldown.config.mjs | 2 +- sea-config.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 sea-config.json diff --git a/packages/braintrust-wizard/rolldown.config.mjs b/packages/braintrust-wizard/rolldown.config.mjs index ef8d49b..faf3770 100644 --- a/packages/braintrust-wizard/rolldown.config.mjs +++ b/packages/braintrust-wizard/rolldown.config.mjs @@ -5,7 +5,7 @@ export default defineConfig({ output: { banner: "#!/usr/bin/env node", codeSplitting: false, - file: "dist/cli.js", + file: "dist/cli.mjs", format: "esm", sourcemap: true, }, diff --git a/sea-config.json b/sea-config.json new file mode 100644 index 0000000..44d6b8a --- /dev/null +++ b/sea-config.json @@ -0,0 +1 @@ +{ "main": "dist/cli.mjs", "output": "crank", "mainFormat": "module" } From 048ec1c38aebdbffe0457b2752bc7a21334d69ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Thu, 14 May 2026 14:11:42 -0700 Subject: [PATCH 49/52] feat: bun as js runtime --- .mise.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mise.toml b/.mise.toml index e4a1848..0b617da 100644 --- a/.mise.toml +++ b/.mise.toml @@ -1,3 +1,4 @@ [tools] -node = "24.15.0" +node = "26.1.0" +bun= "1.3.14" pnpm = "10.33.3" From f7ec5a7523022463e9cc45c7f69e3d254fe6193e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Wed, 13 May 2026 11:01:45 -0700 Subject: [PATCH 50/52] feat: pass providerCredentials map to runInstrumentation Update credential collection and runInstrumentation to use the new providerCredentials: Record shape from the wizard flow and instrument.ts harness. Supports multi-credential providers. --- .../braintrust-wizard/src/clack-wizard.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/braintrust-wizard/src/clack-wizard.ts b/packages/braintrust-wizard/src/clack-wizard.ts index 9a0f519..04d379f 100644 --- a/packages/braintrust-wizard/src/clack-wizard.ts +++ b/packages/braintrust-wizard/src/clack-wizard.ts @@ -225,6 +225,32 @@ type InstrumentationResult = { readonly resumeCommand: string | undefined; }; +async function collectCredentials( + prompts: ClackWizardPrompts, + provider: LlmProvider, +): Promise | undefined> { + const fields: readonly CredentialField[] = provider.credentials ?? [ + { envVar: provider.envVar!, label: provider.label, secret: true }, + ]; + const result: Record = {}; + for (const field of fields) { + const raw = unwrap( + prompts, + field.secret !== false + ? await prompts.password({ message: PROVIDER_KEY_QUESTION(field.label) }) + : await prompts.text({ message: PROVIDER_KEY_QUESTION(field.label) }), + ); + if (raw.length > 0) { + result[field.envVar] = raw; + } + } + if (Object.keys(result).length === 0) { + prompts.log.warn("No credentials entered; skipping instrumentation."); + return undefined; + } + return result; +} + async function runInstrumentation( deps: WizardDeps, args: { From a29f9bf0cf009507ce16b651f050a890634dd21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 14:32:15 -0700 Subject: [PATCH 51/52] feat: implement clack wizard flow --- .../braintrust-wizard/src/clack-wizard.ts | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/packages/braintrust-wizard/src/clack-wizard.ts b/packages/braintrust-wizard/src/clack-wizard.ts index 04d379f..9a0f519 100644 --- a/packages/braintrust-wizard/src/clack-wizard.ts +++ b/packages/braintrust-wizard/src/clack-wizard.ts @@ -225,32 +225,6 @@ type InstrumentationResult = { readonly resumeCommand: string | undefined; }; -async function collectCredentials( - prompts: ClackWizardPrompts, - provider: LlmProvider, -): Promise | undefined> { - const fields: readonly CredentialField[] = provider.credentials ?? [ - { envVar: provider.envVar!, label: provider.label, secret: true }, - ]; - const result: Record = {}; - for (const field of fields) { - const raw = unwrap( - prompts, - field.secret !== false - ? await prompts.password({ message: PROVIDER_KEY_QUESTION(field.label) }) - : await prompts.text({ message: PROVIDER_KEY_QUESTION(field.label) }), - ); - if (raw.length > 0) { - result[field.envVar] = raw; - } - } - if (Object.keys(result).length === 0) { - prompts.log.warn("No credentials entered; skipping instrumentation."); - return undefined; - } - return result; -} - async function runInstrumentation( deps: WizardDeps, args: { From a030d11dbc3cdc6b512ff59e7842693941c342bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 13:48:01 -0700 Subject: [PATCH 52/52] feat: telemetry Co-Authored-By: Claude Sonnet 4.6 --- .../braintrust-wizard/rolldown.config.mjs | 35 ++++--- .../braintrust-wizard/src/clack-wizard.ts | 17 +++- packages/braintrust-wizard/src/cli.ts | 11 +++ .../braintrust-wizard/src/crank-telemetry.ts | 83 ++++++++++++++++ packages/braintrust-wizard/src/telemetry.ts | 95 +++++++++++++++++++ 5 files changed, 228 insertions(+), 13 deletions(-) create mode 100644 packages/braintrust-wizard/src/crank-telemetry.ts create mode 100644 packages/braintrust-wizard/src/telemetry.ts diff --git a/packages/braintrust-wizard/rolldown.config.mjs b/packages/braintrust-wizard/rolldown.config.mjs index faf3770..224bbb1 100644 --- a/packages/braintrust-wizard/rolldown.config.mjs +++ b/packages/braintrust-wizard/rolldown.config.mjs @@ -1,14 +1,27 @@ import { defineConfig } from "rolldown"; -export default defineConfig({ - input: "src/cli.ts", - output: { - banner: "#!/usr/bin/env node", - codeSplitting: false, - file: "dist/cli.mjs", - format: "esm", - sourcemap: true, +export default defineConfig([ + { + input: "src/cli.ts", + output: { + banner: "#!/usr/bin/env node", + codeSplitting: false, + file: "dist/cli.mjs", + format: "esm", + sourcemap: true, + }, + platform: "node", + external: [/^node:/], }, - platform: "node", - external: [/^node:/], -}); + { + input: "src/crank-telemetry.ts", + output: { + codeSplitting: false, + file: "dist/crank-telemetry.mjs", + format: "esm", + sourcemap: true, + }, + platform: "node", + external: [/^node:/], + }, +]); diff --git a/packages/braintrust-wizard/src/clack-wizard.ts b/packages/braintrust-wizard/src/clack-wizard.ts index 9a0f519..1bcf468 100644 --- a/packages/braintrust-wizard/src/clack-wizard.ts +++ b/packages/braintrust-wizard/src/clack-wizard.ts @@ -4,6 +4,7 @@ import { WizardSigninAuthClient } from "./auth"; import { openBrowser } from "./browser"; import { buildLogsPermalink, buildCleanupMessage } from "./cleanup"; import { findGitRoot, isGitRepo, writeEnvBraintrust } from "./git"; +import { detectLanguages, type DetectedLanguage } from "./language-detect"; import { allocateResultFile, buildHarnessCommand, @@ -11,10 +12,15 @@ import { runHarness, writePromptToTemp, } from "./instrument"; -import { detectLanguages, type DetectedLanguage } from "./language-detect"; import type { WizardOptions } from "./options"; import { renderPrompt } from "./prompt"; import { LLM_PROVIDERS, type LlmProvider, type CredentialField } from "./providers"; +import { + setTelemetryLanguage, + setTelemetryProvider, + notifyPiRunning, + markTelemetrySigint, +} from "./telemetry"; import { DOCS_URL, NOT_GIT_REPO_WARNING, @@ -23,8 +29,8 @@ import { RUN_HARNESS_QUESTION, WIZARD_CANCEL_MESSAGE, WIZARD_TITLE, - gitignoreNote, promptSavedNote, + gitignoreNote, wizardLoginPrompt, } from "./wizard-copy"; @@ -131,6 +137,8 @@ export async function runClackWizard(deps: WizardDeps): Promise { const canInstrument = !provider.custom && providerCredentials !== undefined; const languages = detectLanguages(deps.cwd); + setTelemetryLanguage(languages.length === 1 ? languages[0]! : "unknown"); + setTelemetryProvider(provider.id); let tracePermalink: string | undefined; let resumeCommand: string | undefined; @@ -257,6 +265,7 @@ async function runInstrumentation( interactive: true, resultFilePath, }); + notifyPiRunning(true); const harnessResult = await runHarness({ prompt: promptText, cwd: deps.cwd, @@ -265,6 +274,10 @@ async function runInstrumentation( providerCredentials: args.providerCredentials, languages: args.languages, }); + notifyPiRunning(false); + if (harnessResult.status === "completed" && harnessResult.exitCode !== 0) { + markTelemetrySigint(); + } if (harnessResult.status === "harness-not-found") { const { path } = writePromptToTemp(promptText); diff --git a/packages/braintrust-wizard/src/cli.ts b/packages/braintrust-wizard/src/cli.ts index e33b0df..5ffcbfb 100644 --- a/packages/braintrust-wizard/src/cli.ts +++ b/packages/braintrust-wizard/src/cli.ts @@ -8,6 +8,11 @@ import { WizardCancelledError, } from "./clack-wizard"; import { parseArgs } from "./options"; +import { + startTelemetry, + finishTelemetry, + markTelemetrySigint, +} from "./telemetry"; const options = await parseArgs(process.argv.slice(2), process.env); @@ -32,6 +37,8 @@ if ( process.exit(result.status ?? 1); } +startTelemetry(process.env); + const deps = buildDefaultDeps({ options, prompts: prompts as unknown as Parameters< @@ -41,10 +48,14 @@ const deps = buildDefaultDeps({ try { await runClackWizard(deps); + finishTelemetry(); } catch (error) { if (error instanceof WizardCancelledError) { + markTelemetrySigint(); + finishTelemetry(); process.exit(0); } + finishTelemetry(); process.stderr.write(`${(error as Error).message}\n`); process.exit(1); } diff --git a/packages/braintrust-wizard/src/crank-telemetry.ts b/packages/braintrust-wizard/src/crank-telemetry.ts new file mode 100644 index 0000000..3c24ee9 --- /dev/null +++ b/packages/braintrust-wizard/src/crank-telemetry.ts @@ -0,0 +1,83 @@ +/** + * crank-telemetry — spawned by crank at startup as a detached background + * process. Reads JSON-line updates from stdin (language, provider, + * stop-reason), waits until stdin closes (crank exited) or 15 minutes, + * then POSTs the payload once. + */ + +import * as readline from "node:readline"; + +const TELEMETRY_URL = + process.env["CRANK_TELEMETRY_URL"] ?? + "https://www.braintrust.dev/app/cli-telemetry"; +const REQUEST_TIMEOUT_MS = 5_000; +const MAX_WAIT_MS = 15 * 60 * 1_000; +const VERSION = "dev"; + +type Update = { + language?: string; + provider?: string; + stopReason?: string; +}; + +const state = { + start: Math.floor(Date.now() / 1_000), + language: undefined as string | undefined, + provider: undefined as string | undefined, + stopReason: "SIGINT", +}; + +const rl = readline.createInterface({ input: process.stdin }); + +rl.on("line", (line) => { + try { + const update: Update = JSON.parse(line); + if (update.language) state.language = update.language; + if (update.provider) state.provider = update.provider; + if (update.stopReason) state.stopReason = update.stopReason; + } catch { + // ignore malformed lines + } +}); + +async function send(): Promise { + const payload: Record = { + start: state.start, + end: Math.floor(Date.now() / 1_000), + "stop-reason": state.stopReason, + }; + if (state.language !== undefined) payload["language"] = state.language; + if (state.provider !== undefined) payload["provider"] = state.provider; + const body = JSON.stringify(payload); + const headers = { + "Content-Type": "application/json", + "User-Agent": `crank/${VERSION}`, + }; + for (let attempt = 0; attempt < 2; attempt++) { + try { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); + await fetch(TELEMETRY_URL, { + method: "POST", + headers, + body, + signal: controller.signal, + }); + clearTimeout(timer); + return; + } catch { + // retry once then give up + } + } +} + +const timeout = setTimeout(() => { + rl.close(); +}, MAX_WAIT_MS); +timeout.unref(); + +rl.once("close", async () => { + clearTimeout(timeout); + await send(); + process.exit(0); +}); diff --git a/packages/braintrust-wizard/src/telemetry.ts b/packages/braintrust-wizard/src/telemetry.ts new file mode 100644 index 0000000..ba1fc71 --- /dev/null +++ b/packages/braintrust-wizard/src/telemetry.ts @@ -0,0 +1,95 @@ +import { spawn } from "node:child_process"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +import type { DetectedLanguage } from "./language-detect"; + +type TelemetryChild = { + write: (update: Record) => void; + finish: () => void; + setPiRunning: (running: boolean) => void; + markSigint: () => void; +}; + +let child: TelemetryChild | null = null; + +function telemetryBin(): string { + const dir = dirname(fileURLToPath(import.meta.url)); + return join(dir, "crank-telemetry.mjs"); +} + +export function startTelemetry(env: NodeJS.ProcessEnv): void { + const flag = env["CRANK_ENABLE_TELEMETRY"]; + if (flag !== undefined && flag.toUpperCase() === "FALSE") return; + + const spawnEnv: NodeJS.ProcessEnv = { ...env }; + if (env["CRANK_TELEMETRY_URL"]) { + spawnEnv["CRANK_TELEMETRY_URL"] = env["CRANK_TELEMETRY_URL"]; + } + + const proc = spawn(process.execPath, [telemetryBin()], { + detached: true, + stdio: ["pipe", "ignore", "ignore"], + env: spawnEnv, + }); + proc.unref(); + + let finished = false; + let sigintReceived = false; + let piRunning = false; + + process.on("SIGINT", () => { + if (piRunning) return; + sigintReceived = true; + child?.finish(); + process.exit(1); + }); + + child = { + write(update) { + if (finished) return; + try { + proc.stdin?.write(JSON.stringify(update) + "\n"); + } catch { + // ignore write errors + } + }, + finish() { + if (finished) return; + finished = true; + if (sigintReceived) { + proc.stdin?.end(); + } else { + proc.stdin?.end(JSON.stringify({ stopReason: "finished" }) + "\n"); + } + }, + setPiRunning(running: boolean) { + piRunning = running; + }, + markSigint() { + sigintReceived = true; + }, + }; +} + +export function setTelemetryLanguage( + language: DetectedLanguage | "unknown", +): void { + child?.write({ language }); +} + +export function setTelemetryProvider(provider: string): void { + child?.write({ provider }); +} + +export function finishTelemetry(): void { + child?.finish(); +} + +export function notifyPiRunning(running: boolean): void { + child?.setPiRunning(running); +} + +export function markTelemetrySigint(): void { + child?.markSigint(); +}