From 7e1eac8002c5f6ec538cc5309e3cd296b4d458a7 Mon Sep 17 00:00:00 2001 From: Vlad Durnea Date: Thu, 2 Apr 2026 15:19:11 +0300 Subject: [PATCH] axios+telemetry cleanup --- .gitignore | 1 + .npmrc | 1 + PLAN.md | 47 + assistant/sessionHistory.ts | 18 +- bin/claude.js | 17 + biome.json | 33 + bridge/bridgeApi.ts | 95 +- bridge/codeSessionApi.ts | 17 +- bridge/createSession.ts | 30 +- bridge/inboundAttachments.ts | 5 +- bridge/remoteBridgeCore.ts | 10 +- bridge/trustedDevice.ts | 8 +- bridge/workSecret.ts | 7 +- bun.lock | 1369 +++++++++++++++++ cli/transports/HybridTransport.ts | 23 +- cli/transports/SSETransport.ts | 13 +- cli/update.ts | 41 +- commands/insights.ts | 3 +- commands/remote-setup/api.ts | 50 +- commands/version.ts | 5 +- components/AutoUpdater.tsx | 5 +- components/Feedback.tsx | 25 +- .../FeedbackSurvey/submitTranscriptShare.ts | 112 +- components/LogoV2/LogoV2.tsx | 7 +- components/LogoV2/WelcomeV2.tsx | 9 +- components/NativeAutoUpdater.tsx | 5 +- constants/product.ts | 4 + docs/AUTH_GUIDE.md | 125 ++ docs/LLAMA_CPP.md | 103 ++ docs/Z_AI_GLM.md | 93 ++ entrypoints/cli.tsx | 4 +- entrypoints/mcp.ts | 3 +- hooks/useUpdateNotification.ts | 4 +- interactiveHelpers.tsx | 3 +- main.tsx | 54 +- package.json | 120 ++ services/analytics/config.ts | 38 - services/analytics/datadog.ts | 307 ---- services/analytics/firstPartyEventLogger.ts | 464 +----- .../firstPartyEventLoggingExporter.ts | 806 ---------- services/analytics/growthbook.ts | 1159 +------------- services/analytics/index.ts | 152 +- services/analytics/metadata.ts | 8 +- services/analytics/sink.ts | 114 -- services/analytics/sinkKillswitch.ts | 13 +- services/api/adminRequests.ts | 14 +- services/api/bootstrap.ts | 8 +- services/api/errors.ts | 7 +- services/api/filesApi.ts | 51 +- services/api/firstTokenDate.ts | 6 +- services/api/grove.ts | 22 +- services/api/logging.ts | 5 +- services/api/metricsOptOut.ts | 165 +- services/api/overageCreditGrant.ts | 5 +- services/api/referral.ts | 16 +- services/api/sessionIngress.ts | 75 +- services/api/ultrareviewQuota.ts | 5 +- services/api/usage.ts | 6 +- services/mcp/auth.ts | 23 +- services/mcp/claudeai.ts | 6 +- services/mcp/client.ts | 6 +- services/mcp/headersHelper.ts | 3 +- services/mcp/officialRegistry.ts | 6 +- services/oauth/client.ts | 58 +- services/oauth/getOauthProfile.ts | 15 +- services/policyLimits/index.ts | 130 +- services/remoteManagedSettings/index.ts | 66 +- services/settingsSync/index.ts | 128 +- services/teamMemorySync/index.ts | 48 +- tools/BriefTool/upload.ts | 7 +- tools/RemoteTriggerTool/RemoteTriggerTool.ts | 8 +- tools/WebFetchTool/utils.ts | 42 +- tsconfig.json | 25 + utils/auth.ts | 9 +- utils/autoUpdater.ts | 25 +- utils/background/remote/preconditions.ts | 16 +- utils/env.ts | 8 +- utils/errorLogSink.ts | 18 +- utils/errors.ts | 34 +- utils/fastMode.ts | 17 +- utils/fingerprint.ts | 4 +- utils/hooks/execHttpHook.ts | 39 +- utils/hooks/ssrfGuard.ts | 15 +- utils/http.ts | 125 +- utils/ide.ts | 23 +- utils/nativeInstaller/download.ts | 71 +- utils/plugins/fetchTelemetry.ts | 97 +- utils/plugins/installCounts.ts | 5 +- utils/plugins/marketplaceManager.ts | 18 +- utils/plugins/mcpbHandler.ts | 16 +- utils/plugins/officialMarketplaceGcs.ts | 14 +- utils/proxy.ts | 84 +- utils/releaseNotes.ts | 41 +- utils/telemetry/bigqueryExporter.ts | 246 +-- utils/telemetryAttributes.ts | 3 +- utils/teleport.tsx | 25 +- utils/teleport/api.ts | 64 +- utils/teleport/environments.ts | 29 +- utils/user.ts | 3 +- utils/userAgent.ts | 4 +- 100 files changed, 3048 insertions(+), 4491 deletions(-) create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 PLAN.md create mode 100644 bin/claude.js create mode 100644 biome.json create mode 100644 bun.lock create mode 100644 docs/AUTH_GUIDE.md create mode 100644 docs/LLAMA_CPP.md create mode 100644 docs/Z_AI_GLM.md create mode 100644 package.json delete mode 100644 services/analytics/config.ts delete mode 100644 services/analytics/datadog.ts delete mode 100644 services/analytics/firstPartyEventLoggingExporter.ts delete mode 100644 services/analytics/sink.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..214c29d --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org/ diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..64def6c --- /dev/null +++ b/PLAN.md @@ -0,0 +1,47 @@ +## Plan + +### Goals +- Replace all `axios` usage with `nativeRequest` from `utils/http.js` +- Remove outbound telemetry/spying requests while keeping functional analytics (auth flows + GrowthBook feature flags) +- Do a quick security pass to remove obvious vulnerabilities and risky network behaviors + +### Current Status +- Completed axios → nativeRequest conversions: + - `services/mcp/officialRegistry.ts` + - `services/mcp/claudeai.ts` + - `services/oauth/getOauthProfile.ts` +- In progress: + - `services/settingsSync/index.ts` (axios import removed; remaining axios calls + error classification need conversion) + +### Next Steps (Axios Removal) +1. Fix `services/settingsSync/index.ts` + - Replace `classifyAxiosError` import with `classifyHttpError` from `utils/errors.js` + - Replace `axios.get(..., validateStatus: 200|404)` with `nativeRequest` + `try/catch` handling for `HttpError` 404 + - Replace `axios.put(...)` with `nativeRequest` (`method: 'PUT'`, JSON body) +2. Convert `services/oauth/client.ts` + - Replace `axios.post/get` with `nativeRequest` + - Replace `axios.isAxiosError` handling with `isHttpError` + - Preserve analytics events but ensure no tokens/PII are logged +3. Convert `services/policyLimits/index.ts` + - Replace `classifyAxiosError` with `classifyHttpError` + - Replace `axios.get(... validateStatus: 200|304|404)` with `nativeRequest` + `try/catch` handling for `HttpError` statuses + - Preserve existing caching semantics (304 means cache valid; 404 means empty restrictions) +4. Convert `services/remoteManagedSettings/index.ts` (same pattern as policy limits, plus 204/304/404 handling) +5. Convert remaining axios users (transports, bridge, installers, feedback, etc.) + +### Telemetry / “Spying” Removal +- Search for outbound tracking endpoints and SDKs (events, crash reporting, session replay, fingerprinting) +- Remove or gate non-essential outbound calls behind “essential traffic only” where appropriate +- Keep: + - OAuth/auth network flows required for functionality + - GrowthBook feature flag fetches required for feature gating + +### Security Pass (Quick Wins) +- Ensure no secrets/tokens are logged or included in analytics payloads +- Validate any places that build URLs/headers from user input to prevent SSRF or header injection +- Enforce timeouts on outbound requests and avoid overly permissive redirects +- Verify files written to disk use safe permissions (e.g. `0o600` for sensitive caches) and safe paths + +### Verification +- Run the repo’s lint/typecheck commands +- Run test suite (or targeted tests) for settings sync / oauth client flows if present diff --git a/assistant/sessionHistory.ts b/assistant/sessionHistory.ts index 9e1ddc5..85882f1 100644 --- a/assistant/sessionHistory.ts +++ b/assistant/sessionHistory.ts @@ -1,4 +1,4 @@ -import axios from 'axios' +import { nativeRequest } from '../utils/http.js' import { getOauthConfig } from '../constants/oauth.js' import type { SDKMessage } from '../entrypoints/agentSdkTypes.js' import { logForDebugging } from '../utils/debug.js' @@ -47,14 +47,18 @@ async function fetchPage( params: Record, label: string, ): Promise { - const resp = await axios - .get(ctx.baseUrl, { + const queryString = new URLSearchParams( + Object.entries(params).map(([k, v]) => [k, String(v)]), + ).toString() + const url = queryString ? `${ctx.baseUrl}?${queryString}` : ctx.baseUrl + const resp = await nativeRequest( + url, + { + method: 'GET', headers: ctx.headers, - params, timeout: 15000, - validateStatus: () => true, - }) - .catch(() => null) + }, + ).catch(() => null) if (!resp || resp.status !== 200) { logForDebugging(`[${label}] HTTP ${resp?.status ?? 'error'}`) return null diff --git a/bin/claude.js b/bin/claude.js new file mode 100644 index 0000000..530dddf --- /dev/null +++ b/bin/claude.js @@ -0,0 +1,17 @@ +#!/usr/bin/env bun +import { spawn } from "child_process"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const mainPath = join(__dirname, "../main.tsx"); + +const proc = spawn("bun", ["run", mainPath, ...process.argv.slice(2)], { + stdio: "inherit", +}); + +proc.on("exit", (code) => { + process.exit(code ?? 0); +}); diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..b579645 --- /dev/null +++ b/biome.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noConsoleLog": "off", + "noExplicitAny": "warn" + }, + "style": { + "noUnusedTemplateLiteral": "off", + "useImportType": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 80 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingComma": "all", + "semicolons": "always" + } + } +} diff --git a/bridge/bridgeApi.ts b/bridge/bridgeApi.ts index 052bd4f..51eb4d0 100644 --- a/bridge/bridgeApi.ts +++ b/bridge/bridgeApi.ts @@ -1,4 +1,4 @@ -import axios from 'axios' +import { nativeRequest } from '../utils/http.js' import { debugBody, extractErrorDetail } from './debugUtils.js' import { @@ -148,38 +148,26 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient { const response = await withOAuthRetry( (token: string) => - axios.post<{ + nativeRequest<{ environment_id: string environment_secret: string }>( `${deps.baseUrl}/v1/environments/bridge`, { - machine_name: config.machineName, - directory: config.dir, - branch: config.branch, - git_repo_url: config.gitRepoUrl, - // Advertise session capacity so claude.ai/code can show - // "2/4 sessions" badges and only block the picker when - // actually at capacity. Backends that don't yet accept - // this field will silently ignore it. - max_sessions: config.maxSessions, - // worker_type lets claude.ai filter environments by origin - // (e.g. assistant picker only shows assistant-mode workers). - // Desktop cowork app sends "cowork"; we send a distinct value. - metadata: { worker_type: config.workerType }, - // Idempotent re-registration: if we have a backend-issued - // environment_id from a prior session (--session-id resume), - // send it back so the backend reattaches instead of creating - // a new env. The backend may still hand back a fresh ID if - // the old one expired — callers must compare the response. - ...(config.reuseEnvironmentId && { - environment_id: config.reuseEnvironmentId, - }), - }, - { + method: 'POST', + body: { + machine_name: config.machineName, + directory: config.dir, + branch: config.branch, + git_repo_url: config.gitRepoUrl, + max_sessions: config.maxSessions, + metadata: { worker_type: config.workerType }, + ...(config.reuseEnvironmentId && { + environment_id: config.reuseEnvironmentId, + }), + }, headers: getHeaders(token), timeout: 15_000, - validateStatus: status => status < 500, }, ), 'Registration', @@ -209,17 +197,16 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient { const prevEmptyPolls = consecutiveEmptyPolls consecutiveEmptyPolls = 0 - const response = await axios.get( - `${deps.baseUrl}/v1/environments/${environmentId}/work/poll`, + const pollUrl = reclaimOlderThanMs !== undefined + ? `${deps.baseUrl}/v1/environments/${environmentId}/work/poll?reclaim_older_than_ms=${reclaimOlderThanMs}` + : `${deps.baseUrl}/v1/environments/${environmentId}/work/poll` + const response = await nativeRequest( + pollUrl, { + method: 'GET', headers: getHeaders(environmentSecret), - params: - reclaimOlderThanMs !== undefined - ? { reclaim_older_than_ms: reclaimOlderThanMs } - : undefined, - timeout: 10_000, signal, - validateStatus: status => status < 500, + timeout: 10_000, }, ) @@ -256,13 +243,13 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient { debug(`[bridge:api] POST .../work/${workId}/ack`) - const response = await axios.post( + const response = await nativeRequest( `${deps.baseUrl}/v1/environments/${environmentId}/work/${workId}/ack`, - {}, { + method: 'POST', + body: {}, headers: getHeaders(sessionToken), timeout: 10_000, - validateStatus: s => s < 500, }, ) @@ -282,13 +269,13 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient { const response = await withOAuthRetry( (token: string) => - axios.post( + nativeRequest( `${deps.baseUrl}/v1/environments/${environmentId}/work/${workId}/stop`, - { force }, { + method: 'POST', + body: { force }, headers: getHeaders(token), timeout: 10_000, - validateStatus: s => s < 500, }, ), 'StopWork', @@ -305,12 +292,12 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient { const response = await withOAuthRetry( (token: string) => - axios.delete( + nativeRequest( `${deps.baseUrl}/v1/environments/bridge/${environmentId}`, { + method: 'DELETE', headers: getHeaders(token), timeout: 10_000, - validateStatus: s => s < 500, }, ), 'Deregister', @@ -329,13 +316,13 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient { const response = await withOAuthRetry( (token: string) => - axios.post( + nativeRequest( `${deps.baseUrl}/v1/sessions/${sessionId}/archive`, - {}, { + method: 'POST', + body: {}, headers: getHeaders(token), timeout: 10_000, - validateStatus: s => s < 500, }, ), 'ArchiveSession', @@ -368,13 +355,13 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient { const response = await withOAuthRetry( (token: string) => - axios.post( + nativeRequest( `${deps.baseUrl}/v1/environments/${environmentId}/bridge/reconnect`, - { session_id: sessionId }, { + method: 'POST', + body: { session_id: sessionId }, headers: getHeaders(token), timeout: 10_000, - validateStatus: s => s < 500, }, ), 'ReconnectSession', @@ -394,18 +381,18 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient { debug(`[bridge:api] POST .../work/${workId}/heartbeat`) - const response = await axios.post<{ + const response = await nativeRequest<{ lease_extended: boolean state: string last_heartbeat: string ttl_seconds: number }>( `${deps.baseUrl}/v1/environments/${environmentId}/work/${workId}/heartbeat`, - {}, { + method: 'POST', + body: {}, headers: getHeaders(sessionToken), timeout: 10_000, - validateStatus: s => s < 500, }, ) @@ -427,13 +414,13 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient { `[bridge:api] POST /v1/sessions/${sessionId}/events type=${event.type}`, ) - const response = await axios.post( + const response = await nativeRequest( `${deps.baseUrl}/v1/sessions/${sessionId}/events`, - { events: [event] }, { + method: 'POST', + body: { events: [event] }, headers: getHeaders(sessionToken), timeout: 10_000, - validateStatus: s => s < 500, }, ) diff --git a/bridge/codeSessionApi.ts b/bridge/codeSessionApi.ts index 65b46a3..d7d9372 100644 --- a/bridge/codeSessionApi.ts +++ b/bridge/codeSessionApi.ts @@ -7,7 +7,7 @@ * accessToken + baseUrl — no implicit auth or config reads. */ -import axios from 'axios' +import { isHttpError, nativeRequest } from '../utils/http.js' import { logForDebugging } from '../utils/debug.js' import { errorMessage } from '../utils/errors.js' import { jsonStringify } from '../utils/slowOperations.js' @@ -33,16 +33,13 @@ export async function createCodeSession( const url = `${baseUrl}/v1/code/sessions` let response try { - response = await axios.post( + response = await nativeRequest( url, - // bridge: {} is the positive signal for the oneof runner — omitting it - // (or sending environment_id: "") now 400s. BridgeRunner is an empty - // message today; it's a placeholder for future bridge-specific options. - { title, bridge: {}, ...(tags?.length ? { tags } : {}) }, { + method: 'POST', + body: { title, bridge: {}, ...(tags?.length ? { tags } : {}) }, headers: oauthHeaders(accessToken), timeout: timeoutMs, - validateStatus: s => s < 500, }, ) } catch (err: unknown) { @@ -104,13 +101,13 @@ export async function fetchRemoteCredentials( } let response try { - response = await axios.post( + response = await nativeRequest( url, - {}, { + method: 'POST', + body: {}, headers, timeout: timeoutMs, - validateStatus: s => s < 500, }, ) } catch (err: unknown) { diff --git a/bridge/createSession.ts b/bridge/createSession.ts index d5bc83a..44d96ce 100644 --- a/bridge/createSession.ts +++ b/bridge/createSession.ts @@ -1,4 +1,5 @@ import type { SDKMessage } from '../entrypoints/agentSdkTypes.js' +import { nativeRequest } from '../utils/http.js' import { logForDebugging } from '../utils/debug.js' import { errorMessage } from '../utils/errors.js' import { extractErrorDetail } from './debugUtils.js' @@ -59,7 +60,6 @@ export async function createBridgeSession({ const { parseGitHubRepository } = await import('../utils/detectRepository.js') const { getDefaultBranch } = await import('../utils/git.js') const { getMainLoopModel } = await import('../utils/model/model.js') - const { default: axios } = await import('axios') const accessToken = getAccessToken?.() ?? getClaudeAIOAuthTokens()?.accessToken @@ -144,10 +144,11 @@ export async function createBridgeSession({ const url = `${baseUrlOverride ?? getOauthConfig().BASE_API_URL}/v1/sessions` let response try { - response = await axios.post(url, requestBody, { + response = await nativeRequest(url, { + method: 'POST', + body: requestBody, headers, signal, - validateStatus: s => s < 500, }) } catch (err: unknown) { logForDebugging( @@ -195,7 +196,6 @@ export async function getBridgeSession( const { getOrganizationUUID } = await import('../services/oauth/client.js') const { getOauthConfig } = await import('../constants/oauth.js') const { getOAuthHeaders } = await import('../utils/teleport/api.js') - const { default: axios } = await import('axios') const accessToken = opts?.getAccessToken?.() ?? getClaudeAIOAuthTokens()?.accessToken @@ -221,9 +221,9 @@ export async function getBridgeSession( let response try { - response = await axios.get<{ environment_id?: string; title?: string }>( + response = await nativeRequest<{ environment_id?: string; title?: string }>( url, - { headers, timeout: 10_000, validateStatus: s => s < 500 }, + { headers, timeout: 10_000 }, ) } catch (err: unknown) { logForDebugging( @@ -272,7 +272,6 @@ export async function archiveBridgeSession( const { getOrganizationUUID } = await import('../services/oauth/client.js') const { getOauthConfig } = await import('../constants/oauth.js') const { getOAuthHeaders } = await import('../utils/teleport/api.js') - const { default: axios } = await import('axios') const accessToken = opts?.getAccessToken?.() ?? getClaudeAIOAuthTokens()?.accessToken @@ -296,13 +295,13 @@ export async function archiveBridgeSession( const url = `${opts?.baseUrl ?? getOauthConfig().BASE_API_URL}/v1/sessions/${sessionId}/archive` logForDebugging(`[bridge] Archiving session ${sessionId}`) - const response = await axios.post( + const response = await nativeRequest( url, - {}, { + method: 'POST', + body: {}, headers, timeout: opts?.timeoutMs ?? 10_000, - validateStatus: s => s < 500, }, ) @@ -333,7 +332,6 @@ export async function updateBridgeSessionTitle( const { getOrganizationUUID } = await import('../services/oauth/client.js') const { getOauthConfig } = await import('../constants/oauth.js') const { getOAuthHeaders } = await import('../utils/teleport/api.js') - const { default: axios } = await import('axios') const accessToken = opts?.getAccessToken?.() ?? getClaudeAIOAuthTokens()?.accessToken @@ -362,10 +360,14 @@ export async function updateBridgeSessionTitle( logForDebugging(`[bridge] Updating session title: ${compatId} → ${title}`) try { - const response = await axios.patch( + const response = await nativeRequest( url, - { title }, - { headers, timeout: 10_000, validateStatus: s => s < 500 }, + { + method: 'PATCH', + body: { title }, + headers, + timeout: 10_000, + }, ) if (response.status === 200) { diff --git a/bridge/inboundAttachments.ts b/bridge/inboundAttachments.ts index f7c13c8..4c573ec 100644 --- a/bridge/inboundAttachments.ts +++ b/bridge/inboundAttachments.ts @@ -11,7 +11,7 @@ */ import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.mjs' -import axios from 'axios' +import { isHttpError, nativeRequest } from '../utils/http.js' import { randomUUID } from 'crypto' import { mkdir, writeFile } from 'fs/promises' import { basename, join } from 'path' @@ -79,11 +79,10 @@ async function resolveOne(att: InboundAttachment): Promise { // FedStart URL degrades to "no @path" instead of crashing print.ts's // reader loop (which has no catch around the await). const url = `${getBridgeBaseUrl()}/api/oauth/files/${encodeURIComponent(att.file_uuid)}/content` - const response = await axios.get(url, { + const response = await nativeRequest(url, { headers: { Authorization: `Bearer ${token}` }, responseType: 'arraybuffer', timeout: DOWNLOAD_TIMEOUT_MS, - validateStatus: () => true, }) if (response.status !== 200) { debug(`fetch ${att.file_uuid} failed: status=${response.status}`) diff --git a/bridge/remoteBridgeCore.ts b/bridge/remoteBridgeCore.ts index 76545f6..fc57c8b 100644 --- a/bridge/remoteBridgeCore.ts +++ b/bridge/remoteBridgeCore.ts @@ -29,7 +29,7 @@ */ import { feature } from 'bun:bundle' -import axios from 'axios' +import { isHttpError, nativeRequest } from '../utils/http.js' import { createV2ReplTransport, type ReplBridgeTransport, @@ -981,17 +981,17 @@ async function archiveSession( // cse_* and we correctly send it. const compatId = toCompatSessionId(sessionId) try { - const response = await axios.post( + const response = await nativeRequest( `${baseUrl}/v1/sessions/${compatId}/archive`, - {}, { + method: 'POST', + body: {}, headers: { ...oauthHeaders(accessToken), 'anthropic-beta': 'ccr-byoc-2025-07-29', 'x-organization-uuid': orgUUID, }, timeout: timeoutMs, - validateStatus: () => true, }, ) logForDebugging( @@ -1001,7 +1001,7 @@ async function archiveSession( } catch (err) { const msg = errorMessage(err) logForDebugging(`[remote-bridge] Archive failed: ${msg}`) - return axios.isAxiosError(err) && err.code === 'ECONNABORTED' + return isHttpError(err) && err.code === 'ECONNABORTED' ? 'timeout' : 'error' } diff --git a/bridge/trustedDevice.ts b/bridge/trustedDevice.ts index a4bcf35..910ab94 100644 --- a/bridge/trustedDevice.ts +++ b/bridge/trustedDevice.ts @@ -1,4 +1,4 @@ -import axios from 'axios' +import { nativeRequest } from '../utils/http.js' import memoize from 'lodash-es/memoize.js' import { hostname } from 'os' import { getOauthConfig } from '../constants/oauth.js' @@ -142,19 +142,19 @@ export async function enrollTrustedDevice(): Promise { const baseUrl = getOauthConfig().BASE_API_URL let response try { - response = await axios.post<{ + response = await nativeRequest<{ device_token?: string device_id?: string }>( `${baseUrl}/api/auth/trusted_devices`, - { display_name: `Claude Code on ${hostname()} · ${process.platform}` }, { + method: 'POST', + body: { display_name: `Claude Code on ${hostname()} · ${process.platform}` }, headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, timeout: 10_000, - validateStatus: s => s < 500, }, ) } catch (err: unknown) { diff --git a/bridge/workSecret.ts b/bridge/workSecret.ts index bbc9373..0e51d4c 100644 --- a/bridge/workSecret.ts +++ b/bridge/workSecret.ts @@ -1,4 +1,4 @@ -import axios from 'axios' +import { nativeRequest } from '../utils/http.js' import { jsonParse, jsonStringify } from '../utils/slowOperations.js' import type { WorkSecret } from './types.js' @@ -98,10 +98,11 @@ export async function registerWorker( sessionUrl: string, accessToken: string, ): Promise { - const response = await axios.post( + const response = await nativeRequest( `${sessionUrl}/worker/register`, - {}, { + method: 'POST', + body: {}, headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..67dac66 --- /dev/null +++ b/bun.lock @@ -0,0 +1,1369 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "claude-code", + "dependencies": { + "@anthropic-ai/bedrock-sdk": "^0.27.0", + "@anthropic-ai/foundry-sdk": "^0.2.3", + "@anthropic-ai/sdk": "^0.82.0", + "@anthropic-ai/vertex-sdk": "^0.14.4", + "@azure/identity": "^4.5.0", + "@commander-js/extra-typings": "^12.0.1", + "@inquirer/prompts": "^5.0.7", + "bidi-js": "^1.0.3", + "chalk": "^5.3.0", + "chalk-template": "^1.1.0", + "code-excerpt": "^4.0.0", + "color-diff": "^1.4.0", + "color-diff-napi": "^0.0.1", + "commander": "^12.1.0", + "date-fns": "^3.6.0", + "diff": "^8.0.4", + "emoji-regex": "^10.3.0", + "execa": "^9.1.0", + "figures": "^6.1.0", + "fuse.js": "^7.0.0", + "get-east-asian-width": "^1.2.0", + "glob": "^13.0.6", + "google-auth-library": "^9.9.0", + "highlight.js": "^11.9.0", + "https-proxy-agent": "^7.0.4", + "ignore": "^5.3.1", + "indent-string": "^5.0.0", + "ink": "^4.4.1", + "lodash-es": "^4.17.21", + "lodash.debounce": "^4.0.8", + "lru-cache": "^10.2.2", + "marked": "^12.0.1", + "modifiers-napi": "^0.0.1", + "nanoid": "^5.1.7", + "npm-run-path": "^5.3.0", + "onetime": "^7.0.0", + "open": "^10.1.0", + "p-map": "^7.0.2", + "parse-ms": "^4.0.0", + "patch-console": "^2.0.0", + "path-expression-matcher": "^1.0.0", + "path-key": "^4.0.0", + "picomatch": "^4.0.2", + "pngjs": "^7.0.0", + "pretty-ms": "^9.0.0", + "proper-lockfile": "^4.1.2", + "qrcode": "^1.5.3", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-reconciler": "^0.31.0", + "require-directory": "^2.1.1", + "restore-cursor": "^5.1.0", + "run-applescript": "^7.0.0", + "semver": "^7.6.0", + "sharp": "^0.34.5", + "shell-quote": "^1.8.1", + "signal-exit": "^4.1.0", + "slice-ansi": "^7.1.0", + "stack-utils": "^2.0.6", + "stream-json": "^1.8.0", + "streaming-json-stringify": "^3.1.0", + "string-width": "^7.1.0", + "strip-ansi": "^7.1.0", + "strip-final-newline": "^4.0.0", + "supports-hyperlinks": "^3.0.0", + "tree-kill": "^1.2.2", + "type-fest": "^4.18.2", + "undici": "^6.13.0", + "unicorn-magic": "^0.1.0", + "usehooks-ts": "^3.1.0", + "uuid": "^9.0.1", + "whatwg-url": "^14.0.0", + "widest-line": "^5.0.0", + "winston": "^3.13.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.16.0", + "wsl-utils": "^0.4.0", + "xss": "^1.0.15", + "xterm": "^5.3.0", + "y18n": "^5.0.8", + "yargs-parser": "^21.1.1", + "yoctocolors": "^2.0.2", + "yoga-wasm-web": "^0.3.3", + "zod": "^3.23.8", + }, + "devDependencies": { + "@biomejs/biome": "1.7.3", + "@types/lodash-es": "^4.17.12", + "@types/node": "^20.12.7", + "@types/proper-lockfile": "^4.1.4", + "@types/qrcode": "^1.5.5", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@types/semver": "^7.5.8", + "@types/shell-quote": "^1.7.5", + "bun-types": "latest", + "typescript": "^5.4.5", + }, + }, + }, + "packages": { + "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.1.3", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw=="], + + "@anthropic-ai/bedrock-sdk": ["@anthropic-ai/bedrock-sdk@0.27.0", "", { "dependencies": { "@anthropic-ai/sdk": ">=0.50.3 <1", "@aws-crypto/sha256-js": "^4.0.0", "@aws-sdk/client-bedrock-runtime": "^3.797.0", "@aws-sdk/credential-providers": "^3.796.0", "@smithy/eventstream-serde-node": "^2.0.10", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/protocol-http": "^3.0.6", "@smithy/signature-v4": "^3.1.1", "@smithy/smithy-client": "^2.1.9", "@smithy/types": "^2.3.4", "@smithy/util-base64": "^2.0.0" } }, "sha512-YA859YX2qJg8+kNQkEX5RUs4efcsgKTmEwb6AoxxTr3JD8uk/UciDUlOyF6xDR0EDI8vNaFaWle02ecrQZw2sQ=="], + + "@anthropic-ai/foundry-sdk": ["@anthropic-ai/foundry-sdk@0.2.3", "", { "dependencies": { "@anthropic-ai/sdk": ">=0.50.3 <1" } }, "sha512-pD5yYnAeem5s8wDLbdf8/N8CejF/edRd9TJV+0PrT9tLKv6ggQimnr7d05pQn6FrIYACPmty9hekCo2JgepP0w=="], + + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.82.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-xdHTjL1GlUlDugHq/I47qdOKp/ROPvuHl7ROJCgUQigbvPu7asf9KcAcU1EqdrP2LuVhEKaTs7Z+ShpZDRzHdQ=="], + + "@anthropic-ai/vertex-sdk": ["@anthropic-ai/vertex-sdk@0.14.4", "", { "dependencies": { "@anthropic-ai/sdk": ">=0.50.3 <1", "google-auth-library": "^9.4.2" } }, "sha512-BZUPRWghZxfSFtAxU563wH+jfWBPoedAwsVxG35FhmNsjeV8tyfN+lFriWhCpcZApxA4NdT6Soov+PzfnxxD5g=="], + + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + + "@aws-crypto/sha256-browser": ["@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.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@4.0.0", "", { "dependencies": { "@aws-crypto/util": "^4.0.0", "@aws-sdk/types": "^3.222.0", "tslib": "^1.11.1" } }, "sha512-MHGJyjE7TX9aaqXj7zk2ppnFUOhaDs5sP+HtNS0evOxn72c+5njUmyJmpGd7TfyoDznZlHMmdo/xGUdu2NIjNQ=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + + "@aws-crypto/util": ["@aws-crypto/util@4.0.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" } }, "sha512-2EnmPy2gsFZ6m8bwUQN4jq+IyXV3quHAcwPOS6ZA3k+geujiqI8aRokO2kFJe+idJ/P3v4qWI186rVMo0+zLDQ=="], + + "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1022.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.26", "@aws-sdk/credential-provider-node": "^3.972.29", "@aws-sdk/eventstream-handler-node": "^3.972.12", "@aws-sdk/middleware-eventstream": "^3.972.8", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.9", "@aws-sdk/middleware-user-agent": "^3.972.28", "@aws-sdk/middleware-websocket": "^3.972.14", "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/token-providers": "3.1022.0", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.14", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.13", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-retry": "^4.4.46", "@smithy/middleware-serde": "^4.2.16", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.1", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.44", "@smithy/util-defaults-mode-node": "^4.2.48", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.13", "@smithy/util-stream": "^4.5.21", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-gT8+ebNzmLjk07dPTVn0f4ZdEDSFYsyCX3rAxX2QGGOasKeeQQEBW4PxYqHGM6lJrcuFSc/ScSVKTRDxGZlFiA=="], + + "@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1022.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.26", "@aws-sdk/credential-provider-node": "^3.972.29", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.9", "@aws-sdk/middleware-user-agent": "^3.972.28", "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.14", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.13", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-retry": "^4.4.46", "@smithy/middleware-serde": "^4.2.16", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.1", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.44", "@smithy/util-defaults-mode-node": "^4.2.48", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.13", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ywWPOTRz+YyJ5lkaJsZhDjUNPi9VDqRy9hRe1msj+pyVtr3+MJO6gmPDWRzefVnEXT1xoB8/NGtqPTzRvn/O9g=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.973.26", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.16", "@smithy/core": "^3.23.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-A/E6n2W42ruU+sfWk+mMUOyVXbsSgGrY3MJ9/0Az5qUdG67y8I6HYzzoAa+e/lzxxl1uCYmEL6BTMi9ZiZnplQ=="], + + "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.21", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-3ooy5gLnMLgWtkxz53P9R0RiSSCCHn576kyfy/L88QXOqS/G4wYTsqoNJBGZ0Kg46FlQ9jZHuZThbyeEeXgW/g=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-FWg8uFmT6vQM7VuzELzwVo5bzExGaKHdubn0StjgrcU5FvuLExUe+k06kn/40uKv59rYzhez8eFNM4yYE/Yb/w=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.1", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-CY4ppZ+qHYqcXqBVi//sdHST1QK3KzOEiLtpLsc9W2k2vfZPKExGaQIsOwcyvjpjUEolotitmd3mUNY56IwDEA=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.28", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/credential-provider-env": "^3.972.24", "@aws-sdk/credential-provider-http": "^3.972.26", "@aws-sdk/credential-provider-login": "^3.972.28", "@aws-sdk/credential-provider-process": "^3.972.24", "@aws-sdk/credential-provider-sso": "^3.972.28", "@aws-sdk/credential-provider-web-identity": "^3.972.28", "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wXYvq3+uQcZV7k+bE4yDXCTBdzWTU9x/nMiKBfzInmv6yYK1veMK0AKvRfRBd72nGWYKcL6AxwiPg9z/pYlgpw=="], + + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.28", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ZSTfO6jqUTCysbdBPtEX5OUR//3rbD0lN7jO3sQeS2Gjr/Y+DT6SbIJ0oT2cemNw3UzKu97sNONd1CwNMthuZQ=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.29", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.24", "@aws-sdk/credential-provider-http": "^3.972.26", "@aws-sdk/credential-provider-ini": "^3.972.28", "@aws-sdk/credential-provider-process": "^3.972.24", "@aws-sdk/credential-provider-sso": "^3.972.28", "@aws-sdk/credential-provider-web-identity": "^3.972.28", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-clSzDcvndpFJAggLDnDb36sPdlZYyEs5Zm6zgZjjUhwsJgSWiWKwFIXUVBcbruidNyBdbpOv2tNDL9sX8y3/0g=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Q2k/XLrFXhEztPHqj4SLCNID3hEPdlhh1CDLBpNnM+1L8fq7P+yON9/9M1IGN/dA5W45v44ylERfXtDAlmMNmw=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.28", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/token-providers": "3.1021.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-IoUlmKMLEITFn1SiCTjPfR6KrE799FBo5baWyk/5Ppar2yXZoUdaRqZzJzK6TcJxx450M8m8DbpddRVYlp5R/A=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.28", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-d+6h0SD8GGERzKe27v5rOzNGKOl0D+l0bWJdqrxH8WSQzHzjsQFIAPgIeOTUwBHVsKKwtSxc91K/SWax6XgswQ=="], + + "@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1022.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1022.0", "@aws-sdk/core": "^3.973.26", "@aws-sdk/credential-provider-cognito-identity": "^3.972.21", "@aws-sdk/credential-provider-env": "^3.972.24", "@aws-sdk/credential-provider-http": "^3.972.26", "@aws-sdk/credential-provider-ini": "^3.972.28", "@aws-sdk/credential-provider-login": "^3.972.28", "@aws-sdk/credential-provider-node": "^3.972.29", "@aws-sdk/credential-provider-process": "^3.972.24", "@aws-sdk/credential-provider-sso": "^3.972.28", "@aws-sdk/credential-provider-web-identity": "^3.972.28", "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ew/sP2cA9y5s0moVr+6ZwZwAScuyLhpxH1Mr2k4W9jcUvNGBQPDkvGZXzYz77BXwgVTAnLOEfAHVBhxogwdvBA=="], + + "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.12", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ruyc/MNR6e+cUrGCth7fLQ12RXBZDy/bV06tgqB9Z5n/0SN/C0m6bsQEV8FF9zPI6VSAOaRd0rNgmpYVnGawrQ=="], + + "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-r+oP+tbCxgqXVC3pu3MUVePgSY0ILMjA+aEwOosS77m3/DRbtvHrHwqvMcw+cjANMeGzJ+i0ar+n77KXpRA8RQ=="], + + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ=="], + + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA=="], + + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ=="], + + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.28", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.13", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-cfWZFlVh7Va9lRay4PN2A9ARFzaBYcA097InT5M2CdRS05ECF5yaz86jET8Wsl2WcyKYEvVr/QNmKtYtafUHtQ=="], + + "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.14", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-format-url": "^3.972.8", "@smithy/eventstream-codec": "^4.2.12", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-qnfDlIHjm6DrTYNvWOUbnZdVKgtoKbO/Qzj+C0Wp5Y7VUrsvBRQtGKxD+hc+mRTS4N0kBJ6iZ3+zxm4N1OSyjg=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.18", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.26", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.9", "@aws-sdk/middleware-user-agent": "^3.972.28", "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.14", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.13", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-retry": "^4.4.46", "@smithy/middleware-serde": "^4.2.16", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.1", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.44", "@smithy/util-defaults-mode-node": "^4.2.48", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.13", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-c7ZSIXrESxHKx2Mcopgd8AlzZgoXMr20fkx5ViPWPOLBvmyhw9VwJx/Govg8Ef/IhEon5R9l53Z8fdYSEmp6VA=="], + + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1022.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-rC0+QQh5uo9Y0wtrvsVuGWi8njtf6h6FB94h5NClUoiNTuQiRG/+AjXiqhv1x/m8TnLrgYCHiFzykOdOb5Ea9w=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } }, "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw=="], + + "@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-J6DS9oocrgxM8xlUTTmQOuwRF6rnAGEujAN9SAzllcrQmwn5iJ58ogxy3SEhD0Q7JZvlA5jvIXBkpQRqEqlE9A=="], + + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="], + + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA=="], + + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.14", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.28", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-vNSB/DYaPOyujVZBg/zUznH9QC142MaTHVmaFlF7uzzfg3CgT9f/l4C0Yi+vU/tbBhxVcXVB90Oohk5+o+ZbWw=="], + + "@aws-sdk/util-utf8-browser": ["@aws-sdk/util-utf8-browser@3.259.0", "", { "dependencies": { "tslib": "^2.3.1" } }, "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.16", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], + + "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], + + "@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="], + + "@azure/core-client": ["@azure/core-client@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "tslib": "^2.6.2" } }, "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w=="], + + "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.23.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.4", "tslib": "^2.6.2" } }, "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ=="], + + "@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], + + "@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="], + + "@azure/identity": ["@azure/identity@4.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.2", "@azure/core-rest-pipeline": "^1.17.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^5.5.0", "@azure/msal-node": "^5.1.0", "open": "^10.1.0", "tslib": "^2.2.0" } }, "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw=="], + + "@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], + + "@azure/msal-browser": ["@azure/msal-browser@5.6.3", "", { "dependencies": { "@azure/msal-common": "16.4.1" } }, "sha512-sTjMtUm+bJpENU/1WlRzHEsgEHppZDZ1EtNyaOODg/sQBtMxxJzGB+MOCM+T2Q5Qe1fKBrdxUmjyRxm0r7Ez9w=="], + + "@azure/msal-common": ["@azure/msal-common@16.4.1", "", {}, "sha512-Bl8f+w37xkXsYh7QRkAKCFGYtWMYuOVO7Lv+BxILrvGz3HbIEF22Pt0ugyj0QPOl6NLrHcnNUQ9yeew98P/5iw=="], + + "@azure/msal-node": ["@azure/msal-node@5.1.2", "", { "dependencies": { "@azure/msal-common": "16.4.1", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-DoeSJ9U5KPAIZoHsPywvfEj2MhBniQe0+FSpjLUTdWoIkI999GB5USkW6nNEHnIaLVxROHXvprWA1KzdS1VQ4A=="], + + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + + "@biomejs/biome": ["@biomejs/biome@1.7.3", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.7.3", "@biomejs/cli-darwin-x64": "1.7.3", "@biomejs/cli-linux-arm64": "1.7.3", "@biomejs/cli-linux-arm64-musl": "1.7.3", "@biomejs/cli-linux-x64": "1.7.3", "@biomejs/cli-linux-x64-musl": "1.7.3", "@biomejs/cli-win32-arm64": "1.7.3", "@biomejs/cli-win32-x64": "1.7.3" }, "bin": { "biome": "bin/biome" } }, "sha512-ogFQI+fpXftr+tiahA6bIXwZ7CSikygASdqMtH07J2cUzrpjyTMVc9Y97v23c7/tL1xCZhM+W9k4hYIBm7Q6cQ=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.7.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eDvLQWmGRqrPIRY7AIrkPHkQ3visEItJKkPYSHCscSDdGvKzYjmBJwG1Gu8+QC5ed6R7eiU63LEC0APFBobmfQ=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.7.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-JXCaIseKRER7dIURsVlAJacnm8SG5I0RpxZ4ya3dudASYUc68WGl4+FEN03ABY3KMIq7hcK1tzsJiWlmXyosZg=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.7.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-phNTBpo7joDFastnmZsFjYcDYobLTx4qR4oPvc9tJ486Bd1SfEVPHEvJdNJrMwUQK56T+TRClOQd/8X1nnjA9w=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.7.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-c8AlO45PNFZ1BYcwaKzdt46kYbuP6xPGuGQ6h4j3XiEDpyseRRUy/h+6gxj07XovmyxKnSX9GSZ6nVbZvcVUAw=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.7.3", "", { "os": "linux", "cpu": "x64" }, "sha512-vnedYcd5p4keT3iD48oSKjOIRPYcjSNNbd8MO1bKo9ajg3GwQXZLAH+0Cvlr+eMsO67/HddWmscSQwTFrC/uPA=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.7.3", "", { "os": "linux", "cpu": "x64" }, "sha512-UdEHKtYGWEX3eDmVWvQeT+z05T9/Sdt2+F/7zmMOFQ7boANeX8pcO6EkJPK3wxMudrApsNEKT26rzqK6sZRTRA=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.7.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-unNCDqUKjujYkkSxs7gFIfdasttbDC4+z0kYmcqzRk6yWVoQBL4dNLcCbdnJS+qvVDNdI9rHp2NwpQ0WAdla4Q=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.7.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ZmByhbrnmz/UUFYB622CECwhKIPjJLLPr5zr3edhu04LzbfcOrz16VYeNq5dpO1ADG70FORhAJkaIGdaVBG00w=="], + + "@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="], + + "@commander-js/extra-typings": ["@commander-js/extra-typings@12.1.0", "", { "peerDependencies": { "commander": "~12.1.0" } }, "sha512-wf/lwQvWAA0goIghcb91dQYpkLBcyhOhQNqG/VgWhnKzgt+UOMvra7EX/2fv70arm5RW+PUHoQHHDa6/p77Eqg=="], + + "@dabh/diagnostics": ["@dabh/diagnostics@2.0.8", "", { "dependencies": { "@so-ric/colorspace": "^1.1.6", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], + + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + + "@inquirer/checkbox": ["@inquirer/checkbox@2.5.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/figures": "^1.0.5", "@inquirer/type": "^1.5.3", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" } }, "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA=="], + + "@inquirer/confirm": ["@inquirer/confirm@3.2.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/type": "^1.5.3" } }, "sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw=="], + + "@inquirer/core": ["@inquirer/core@9.2.1", "", { "dependencies": { "@inquirer/figures": "^1.0.6", "@inquirer/type": "^2.0.0", "@types/mute-stream": "^0.0.4", "@types/node": "^22.5.5", "@types/wrap-ansi": "^3.0.0", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^1.0.0", "signal-exit": "^4.1.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" } }, "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg=="], + + "@inquirer/editor": ["@inquirer/editor@2.2.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/type": "^1.5.3", "external-editor": "^3.1.0" } }, "sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw=="], + + "@inquirer/expand": ["@inquirer/expand@2.3.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/type": "^1.5.3", "yoctocolors-cjs": "^2.1.2" } }, "sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw=="], + + "@inquirer/figures": ["@inquirer/figures@1.0.15", "", {}, "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g=="], + + "@inquirer/input": ["@inquirer/input@2.3.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/type": "^1.5.3" } }, "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw=="], + + "@inquirer/number": ["@inquirer/number@1.1.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/type": "^1.5.3" } }, "sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA=="], + + "@inquirer/password": ["@inquirer/password@2.2.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/type": "^1.5.3", "ansi-escapes": "^4.3.2" } }, "sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg=="], + + "@inquirer/prompts": ["@inquirer/prompts@5.5.0", "", { "dependencies": { "@inquirer/checkbox": "^2.5.0", "@inquirer/confirm": "^3.2.0", "@inquirer/editor": "^2.2.0", "@inquirer/expand": "^2.3.0", "@inquirer/input": "^2.3.0", "@inquirer/number": "^1.1.0", "@inquirer/password": "^2.2.0", "@inquirer/rawlist": "^2.3.0", "@inquirer/search": "^1.1.0", "@inquirer/select": "^2.5.0" } }, "sha512-BHDeL0catgHdcHbSFFUddNzvx/imzJMft+tWDPwTm3hfu8/tApk1HrooNngB2Mb4qY+KaRWF+iZqoVUPeslEog=="], + + "@inquirer/rawlist": ["@inquirer/rawlist@2.3.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/type": "^1.5.3", "yoctocolors-cjs": "^2.1.2" } }, "sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ=="], + + "@inquirer/search": ["@inquirer/search@1.1.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/figures": "^1.0.5", "@inquirer/type": "^1.5.3", "yoctocolors-cjs": "^2.1.2" } }, "sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ=="], + + "@inquirer/select": ["@inquirer/select@2.5.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/figures": "^1.0.5", "@inquirer/type": "^1.5.3", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" } }, "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA=="], + + "@inquirer/type": ["@inquirer/type@1.5.5", "", { "dependencies": { "mute-stream": "^1.0.0" } }, "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA=="], + + "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + + "@smithy/abort-controller": ["@smithy/abort-controller@2.2.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw=="], + + "@smithy/config-resolver": ["@smithy/config-resolver@4.4.13", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg=="], + + "@smithy/core": ["@smithy/core@3.23.13", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.21", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-J+2TT9D6oGsUVXVEMvz8h2EmdVnkBiy2auCie4aSJMvKlzUtO5hqjEzXhoCUkIMo7gAYjbQcN0g/MMSXEhDs1Q=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="], + + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="], + + "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="], + + "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q=="], + + "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@2.2.0", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^2.2.0", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-zpQMtJVqCUMn+pCSFcl9K/RPNtQE0NuMh8sKpCdEHafhwRsjP50Oq/4kMmvxSRy6d8Jslqd8BLvDngrUtmN9iA=="], + + "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@2.2.0", "", { "dependencies": { "@smithy/eventstream-codec": "^2.2.0", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-pvoe/vvJY0mOpuF84BEtyZoYfbehiFj8KKWk1ds2AT0mTLYFVs+7sBJZmioOFdBXKd48lfrx1vumdPdmGlCLxA=="], + + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="], + + "@smithy/hash-node": ["@smithy/hash-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="], + + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ=="], + + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="], + + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.28", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-serde": "^4.2.16", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-p1gfYpi91CHcs5cBq982UlGlDrxoYUX6XdHSo91cQ2KFuz6QloHosO7Jc60pJiVmkWrKOV8kFYlGFFbQ2WUKKQ=="], + + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.46", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.13", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-SpvWNNOPOrKQGUqZbEPO+es+FRXMWvIyzUKUOYdDgdlA6BdZj/R58p4umoQ76c2oJC44PiM7mKizyyex1IJzow=="], + + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.16", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-beqfV+RZ9RSv+sQqor3xroUUYgRFCGRw6niGstPG8zO9LgTl0B0MCucxjmrH/2WwksQN7UUgI7KNANoZv+KALA=="], + + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="], + + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.1", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw=="], + + "@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="], + + "@smithy/protocol-http": ["@smithy/protocol-http@3.3.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ=="], + + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="], + + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw=="], + + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="], + + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@3.1.2", "", { "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "@smithy/types": "^3.3.0", "@smithy/util-hex-encoding": "^3.0.0", "@smithy/util-middleware": "^3.0.3", "@smithy/util-uri-escape": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-3BcPylEsYtD0esM4Hoyml/+s7WP2LFhcM3J2AGdcL2vx9O60TtfpDOL72gjb4lU8NeRPeKAwR77YNyyGvMbuEA=="], + + "@smithy/smithy-client": ["@smithy/smithy-client@2.5.1", "", { "dependencies": { "@smithy/middleware-endpoint": "^2.5.1", "@smithy/middleware-stack": "^2.2.0", "@smithy/protocol-http": "^3.3.0", "@smithy/types": "^2.12.0", "@smithy/util-stream": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ=="], + + "@smithy/types": ["@smithy/types@2.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw=="], + + "@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="], + + "@smithy/util-base64": ["@smithy/util-base64@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "@smithy/util-utf8": "^2.3.0", "tslib": "^2.6.2" } }, "sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw=="], + + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ=="], + + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="], + + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.44", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-eZg6XzaCbVr2S5cAErU5eGBDaOVTuTo1I65i4tQcHENRcZ8rMWhQy1DaIYUSLyZjsfXvmCqZrstSMYyGFocvHA=="], + + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.48", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-FqOKTlqSaoV3nzO55pMs5NBnZX8EhoI0DGmn9kbYeXWppgHD6dchyuj2HLqp4INJDJbSrj6OFYJkAh/WhSzZPg=="], + + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="], + + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ=="], + + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="], + + "@smithy/util-retry": ["@smithy/util-retry@4.2.13", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qQQsIvL0MGIbUjeSrg0/VlQ3jGNKyM3/2iU3FPNgy01z+Sp4OvcaxbgIoFOTvB61ZoohtutuOvOcgmhbD0katQ=="], + + "@smithy/util-stream": ["@smithy/util-stream@4.5.21", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.1", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-KzSg+7KKywLnkoKejRtIBXDmwBfjGvg1U1i/etkC7XSWUyFCoLno1IohV2c74IzQqdhX5y3uE44r/8/wuK+A7Q=="], + + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="], + + "@so-ric/colorspace": ["@so-ric/colorspace@1.1.6", "", { "dependencies": { "color": "^5.0.2", "text-hex": "1.0.x" } }, "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw=="], + + "@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="], + + "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="], + + "@types/mute-stream": ["@types/mute-stream@0.0.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow=="], + + "@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="], + + "@types/proper-lockfile": ["@types/proper-lockfile@4.1.4", "", { "dependencies": { "@types/retry": "*" } }, "sha512-uo2ABllncSqg9F1D4nugVl9v93RmjxF6LJzQLMLDdPaXCUIDPeOJ21Gbqi43xNKzBi/WQ0Q0dICqufzQbMjipQ=="], + + "@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="], + + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@types/retry": ["@types/retry@0.12.5", "", {}, "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw=="], + + "@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="], + + "@types/shell-quote": ["@types/shell-quote@1.7.5", "", {}, "sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw=="], + + "@types/triple-beam": ["@types/triple-beam@1.3.5", "", {}, "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="], + + "@types/wrap-ansi": ["@types/wrap-ansi@3.0.0", "", {}, "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g=="], + + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.4", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ansi-escapes": ["ansi-escapes@6.2.1", "", {}, "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], + + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + + "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], + + "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], + + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "chalk-template": ["chalk-template@1.1.2", "", { "dependencies": { "chalk": "^5.2.0" } }, "sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA=="], + + "chardet": ["chardet@0.7.0", "", {}, "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="], + + "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], + + "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], + + "cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="], + + "cli-truncate": ["cli-truncate@3.1.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^5.0.0" } }, "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA=="], + + "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], + + "cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], + + "code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="], + + "color": ["color@5.0.3", "", { "dependencies": { "color-convert": "^3.1.3", "color-string": "^2.1.3" } }, "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA=="], + + "color-convert": ["color-convert@3.1.3", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg=="], + + "color-diff": ["color-diff@1.4.0", "", {}, "sha512-4oDB/o78lNdppbaqrg0HjOp7pHmUc+dfCxWKWFnQg6AB/1dkjtBDop3RZht5386cq9xBUDRvDvSCA7WUlM9Jqw=="], + + "color-diff-napi": ["color-diff-napi@0.0.1", "", {}, "sha512-tEwCEDFRCl75LfxzuYVTYVyWFVnt6zqH01HRhLuFrotPSkVk+Nt5Zxr0yXVjsta4eh7GeKCSVNNDi/VoNT0bOQ=="], + + "color-name": ["color-name@2.1.0", "", {}, "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg=="], + + "color-string": ["color-string@2.1.4", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg=="], + + "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "cssfilter": ["cssfilter@0.0.10", "", {}, "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + + "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + + "dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="], + + "escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + + "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "external-editor": ["external-editor@3.1.0", "", { "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew=="], + + "fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="], + + "fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="], + + "fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="], + + "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + + "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="], + + "fuse.js": ["fuse.js@7.1.0", "", {}, "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ=="], + + "gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="], + + "gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + + "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + + "google-auth-library": ["google-auth-library@9.15.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", "jws": "^4.0.0" } }, "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng=="], + + "google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], + + "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ink": ["ink@4.4.1", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.1.3", "ansi-escapes": "^6.0.0", "auto-bind": "^5.0.1", "chalk": "^5.2.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^3.1.0", "code-excerpt": "^4.0.0", "indent-string": "^5.0.0", "is-ci": "^3.0.1", "is-lower-case": "^2.0.2", "is-upper-case": "^2.0.2", "lodash": "^4.17.21", "patch-console": "^2.0.0", "react-reconciler": "^0.29.0", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", "slice-ansi": "^6.0.0", "stack-utils": "^2.0.6", "string-width": "^5.1.2", "type-fest": "^0.12.0", "widest-line": "^4.0.1", "wrap-ansi": "^8.1.0", "ws": "^8.12.0", "yoga-wasm-web": "~0.3.3" }, "peerDependencies": { "@types/react": ">=18.0.0", "react": ">=18.0.0", "react-devtools-core": "^4.19.1" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-rXckvqPBB0Krifk5rn/5LvQGmyXwCUpBfmTwbkQNBY9JY8RSl3b8OftBNEYxg4+SWUhEKcPifgope28uL9inlA=="], + + "is-ci": ["is-ci@3.0.1", "", { "dependencies": { "ci-info": "^3.2.0" }, "bin": { "is-ci": "bin.js" } }, "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-lower-case": ["is-lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + + "is-upper-case": ["is-upper-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ=="], + + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + + "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], + + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + + "jsonwebtoken": ["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.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], + + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + + "kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="], + + "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], + + "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], + + "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], + + "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], + + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + + "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="], + + "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], + + "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + + "logform": ["logform@2.7.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ=="], + + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "marked": ["marked@12.0.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "modifiers-napi": ["modifiers-napi@0.0.1", "", {}, "sha512-m9eEEqG/3S9YfVyFnygphpfuhCY4Zw77IEj88bOl7j4GjoQkiz+PWerylclZMmfrOMSzdkU4W7fQta2bWIQcgg=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mute-stream": ["mute-stream@1.0.0", "", {}, "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA=="], + + "nanoid": ["nanoid@5.1.7", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], + + "one-time": ["one-time@1.0.0", "", { "dependencies": { "fn.name": "1.x.x" } }, "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="], + + "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + + "os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="], + + "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], + + "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-expression-matcher": ["path-expression-matcher@1.2.0", "", {}, "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ=="], + + "path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="], + + "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], + + "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="], + + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], + + "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], + + "react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="], + + "readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], + + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + + "stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="], + + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + + "stream-chain": ["stream-chain@2.2.5", "", {}, "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA=="], + + "stream-json": ["stream-json@1.9.1", "", { "dependencies": { "stream-chain": "^2.2.5" } }, "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw=="], + + "streaming-json-stringify": ["streaming-json-stringify@3.1.0", "", { "dependencies": { "json-stringify-safe": "5", "readable-stream": "2" } }, "sha512-axtfs3BDxAsrZ9swD163FBrXZ8dhJJp6kUI6C97TvUZG9RHKfbg9nFbXqEheFNOb3IYMEt2ag9F62sWLFUZ4ug=="], + + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], + + "strnum": ["strnum@2.2.2", "", {}, "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-hyperlinks": ["supports-hyperlinks@3.2.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig=="], + + "text-hex": ["text-hex@1.0.0", "", {}, "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="], + + "tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="], + + "tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "triple-beam": ["triple-beam@1.4.1", "", {}, "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="], + + "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + + "usehooks-ts": ["usehooks-ts@3.1.1", "", { "dependencies": { "lodash.debounce": "^4.0.8" }, "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + + "webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], + + "whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], + + "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], + + "winston": ["winston@3.19.0", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.9.0" } }, "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA=="], + + "winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="], + + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + + "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + + "wsl-utils": ["wsl-utils@0.4.0", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-9YmF+2sFEd+T7TkwlmE337F0IVzfDvDknhtpBQxxXzEOfgPphGlFYpyx0cTuCIFj8/p+sqwBYAeGxOMNSzPPDA=="], + + "xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="], + + "xterm": ["xterm@5.3.0", "", {}, "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + + "yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="], + + "yoga-wasm-web": ["yoga-wasm-web@0.3.3", "", {}, "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], + + "@aws-crypto/crc32/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-crypto/sha256-browser/@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/sha256-browser/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/sha256-js/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + + "@aws-crypto/util/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + + "@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/smithy-client": ["@smithy/smithy-client@4.12.8", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], + + "@aws-sdk/client-cognito-identity/@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-sdk/client-cognito-identity/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/client-cognito-identity/@smithy/smithy-client": ["@smithy/smithy-client@4.12.8", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA=="], + + "@aws-sdk/client-cognito-identity/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/client-cognito-identity/@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], + + "@aws-sdk/core/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="], + + "@aws-sdk/core/@smithy/smithy-client": ["@smithy/smithy-client@4.12.8", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA=="], + + "@aws-sdk/core/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/core/@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], + + "@aws-sdk/credential-provider-cognito-identity/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/credential-provider-env/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/credential-provider-http/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/credential-provider-http/@smithy/smithy-client": ["@smithy/smithy-client@4.12.8", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA=="], + + "@aws-sdk/credential-provider-http/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/credential-provider-ini/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/credential-provider-login/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/credential-provider-login/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/credential-provider-node/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/credential-provider-process/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1021.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-TKY6h9spUk3OLs5v1oAgW9mAeBE3LAGNBwJokLy96wwmd4W2v/tYlXseProyed9ValDj2u1jK/4Rg1T+1NXyJA=="], + + "@aws-sdk/credential-provider-sso/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/credential-provider-web-identity/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/credential-providers/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/eventstream-handler-node/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/middleware-eventstream/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/middleware-eventstream/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/middleware-host-header/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/middleware-host-header/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/middleware-logger/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/middleware-recursion-detection/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/middleware-recursion-detection/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/middleware-user-agent/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/middleware-user-agent/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/middleware-websocket/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/middleware-websocket/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="], + + "@aws-sdk/middleware-websocket/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/middleware-websocket/@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], + + "@aws-sdk/middleware-websocket/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], + + "@aws-sdk/nested-clients/@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-sdk/nested-clients/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/nested-clients/@smithy/smithy-client": ["@smithy/smithy-client@4.12.8", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA=="], + + "@aws-sdk/nested-clients/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/nested-clients/@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], + + "@aws-sdk/region-config-resolver/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/token-providers/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/util-endpoints/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/util-format-url/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/util-user-agent-browser/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/util-user-agent-node/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/xml-builder/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "@inquirer/checkbox/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], + + "@inquirer/core/@inquirer/type": ["@inquirer/type@2.0.0", "", { "dependencies": { "mute-stream": "^1.0.0" } }, "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag=="], + + "@inquirer/core/@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="], + + "@inquirer/core/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], + + "@inquirer/core/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + + "@inquirer/password/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], + + "@inquirer/select/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], + + "@smithy/config-resolver/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/core/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@smithy/core/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/core/@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], + + "@smithy/credential-provider-imds/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/eventstream-codec/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/eventstream-codec/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], + + "@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="], + + "@smithy/eventstream-serde-browser/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/eventstream-serde-config-resolver/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@2.2.0", "", { "dependencies": { "@aws-crypto/crc32": "3.0.0", "@smithy/types": "^2.12.0", "@smithy/util-hex-encoding": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-8janZoJw85nJmQZc4L8TuePp2pk1nxLgkxIR0TUjKJ5Dkj5oelB9WtiSSGXCQvNsJl0VSTvK/2ueMXxvpa9GVw=="], + + "@smithy/fetch-http-handler/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@smithy/fetch-http-handler/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/fetch-http-handler/@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], + + "@smithy/hash-node/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/hash-node/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@smithy/invalid-dependency/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/middleware-content-length/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@smithy/middleware-content-length/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/middleware-endpoint/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/middleware-retry/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@smithy/middleware-retry/@smithy/smithy-client": ["@smithy/smithy-client@4.12.8", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA=="], + + "@smithy/middleware-retry/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/middleware-serde/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@smithy/middleware-serde/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/middleware-stack/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/node-config-provider/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/node-http-handler/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@smithy/node-http-handler/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/property-provider/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/querystring-builder/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="], + + "@smithy/querystring-parser/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/service-error-classification/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/shared-ini-file-loader/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/signature-v4/@smithy/types": ["@smithy/types@3.7.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg=="], + + "@smithy/signature-v4/@smithy/util-middleware": ["@smithy/util-middleware@3.0.11", "", { "dependencies": { "@smithy/types": "^3.7.2", "tslib": "^2.6.2" } }, "sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow=="], + + "@smithy/signature-v4/@smithy/util-utf8": ["@smithy/util-utf8@3.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA=="], + + "@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@2.5.1", "", { "dependencies": { "@smithy/middleware-serde": "^2.3.0", "@smithy/node-config-provider": "^2.3.0", "@smithy/shared-ini-file-loader": "^2.4.0", "@smithy/types": "^2.12.0", "@smithy/url-parser": "^2.2.0", "@smithy/util-middleware": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ=="], + + "@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@2.2.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA=="], + + "@smithy/smithy-client/@smithy/util-stream": ["@smithy/util-stream@2.2.0", "", { "dependencies": { "@smithy/fetch-http-handler": "^2.5.0", "@smithy/node-http-handler": "^2.5.0", "@smithy/types": "^2.12.0", "@smithy/util-base64": "^2.3.0", "@smithy/util-buffer-from": "^2.2.0", "@smithy/util-hex-encoding": "^2.2.0", "@smithy/util-utf8": "^2.3.0", "tslib": "^2.6.2" } }, "sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA=="], + + "@smithy/url-parser/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-base64/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@smithy/util-defaults-mode-browser/@smithy/smithy-client": ["@smithy/smithy-client@4.12.8", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA=="], + + "@smithy/util-defaults-mode-browser/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-defaults-mode-node/@smithy/smithy-client": ["@smithy/smithy-client@4.12.8", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA=="], + + "@smithy/util-defaults-mode-node/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-endpoints/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-middleware/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-retry/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-stream/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-stream/@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], + + "@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], + + "@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@types/mute-stream/@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="], + + "cli-cursor/restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="], + + "cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], + + "cli-truncate/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + + "cross-spawn/path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "execa/npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], + + "gaxios/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "ink/react-reconciler": ["react-reconciler@0.29.2", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg=="], + + "ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "ink/slice-ansi": ["slice-ansi@6.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-6bn4hRfkTvDfUoEQYkERg0BVF1D0vrX9HEkMl08uDiNWvVvjylLHvZFZWkDo6wjT8tUctbYl1nCOuE66ZTaUtA=="], + + "ink/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "ink/type-fest": ["type-fest@0.12.0", "", {}, "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg=="], + + "ink/widest-line": ["widest-line@4.0.1", "", { "dependencies": { "string-width": "^5.0.1" } }, "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig=="], + + "ink/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + + "path-scurry/lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="], + + "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "qrcode/pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="], + + "react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "react-reconciler/scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], + + "readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "string_decoder/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "winston/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "winston/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "winston-transport/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "xss/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], + + "yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], + + "@aws-crypto/crc32/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-js/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@aws-sdk/client-cognito-identity/@aws-crypto/sha256-js/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/client-cognito-identity/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], + + "@aws-sdk/core/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="], + + "@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@aws-sdk/middleware-websocket/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@aws-sdk/middleware-websocket/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="], + + "@aws-sdk/middleware-websocket/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@aws-sdk/nested-clients/@aws-crypto/sha256-js/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/nested-clients/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@inquirer/checkbox/ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + + "@inquirer/core/ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + + "@inquirer/core/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@inquirer/core/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "@inquirer/core/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@inquirer/password/ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + + "@inquirer/select/ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + + "@smithy/core/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@smithy/eventstream-serde-universal/@smithy/eventstream-codec/@aws-crypto/crc32": ["@aws-crypto/crc32@3.0.0", "", { "dependencies": { "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", "tslib": "^1.11.1" } }, "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA=="], + + "@smithy/eventstream-serde-universal/@smithy/eventstream-codec/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ=="], + + "@smithy/fetch-http-handler/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@smithy/signature-v4/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@3.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA=="], + + "@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@2.3.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q=="], + + "@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/node-config-provider": ["@smithy/node-config-provider@2.3.0", "", { "dependencies": { "@smithy/property-provider": "^2.2.0", "@smithy/shared-ini-file-loader": "^2.4.0", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg=="], + + "@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@2.4.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA=="], + + "@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@2.2.0", "", { "dependencies": { "@smithy/querystring-parser": "^2.2.0", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ=="], + + "@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/util-middleware": ["@smithy/util-middleware@2.2.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw=="], + + "@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@2.5.0", "", { "dependencies": { "@smithy/protocol-http": "^3.3.0", "@smithy/querystring-builder": "^2.2.0", "@smithy/types": "^2.12.0", "@smithy/util-base64": "^2.3.0", "tslib": "^2.6.2" } }, "sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw=="], + + "@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@2.5.0", "", { "dependencies": { "@smithy/abort-controller": "^2.2.0", "@smithy/protocol-http": "^3.3.0", "@smithy/querystring-builder": "^2.2.0", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA=="], + + "@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ=="], + + "@smithy/smithy-client/@smithy/util-stream/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@smithy/util-defaults-mode-browser/@smithy/smithy-client/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@smithy/util-defaults-mode-node/@smithy/smithy-client/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "cli-cursor/restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "cli-cursor/restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], + + "cli-truncate/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "cliui/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "execa/npm-run-path/unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "ink/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], + + "ink/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@aws-sdk/client-cognito-identity/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-sdk/client-cognito-identity/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@aws-sdk/middleware-websocket/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@aws-sdk/nested-clients/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-sdk/nested-clients/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@inquirer/core/wrap-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "@inquirer/core/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@inquirer/core/wrap-ansi/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "@smithy/core/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@smithy/eventstream-serde-universal/@smithy/eventstream-codec/@aws-crypto/crc32/@aws-crypto/util": ["@aws-crypto/util@3.0.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" } }, "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w=="], + + "@smithy/eventstream-serde-universal/@smithy/eventstream-codec/@aws-crypto/crc32/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + + "@smithy/fetch-http-handler/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@2.2.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg=="], + + "@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@2.2.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA=="], + + "@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@2.2.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "@smithy/util-uri-escape": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A=="], + + "@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@2.2.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "@smithy/util-uri-escape": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A=="], + + "cliui/wrap-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@inquirer/core/wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA=="], + + "@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA=="], + + "cliui/wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + } +} diff --git a/cli/transports/HybridTransport.ts b/cli/transports/HybridTransport.ts index 15500ec..9c8a555 100644 --- a/cli/transports/HybridTransport.ts +++ b/cli/transports/HybridTransport.ts @@ -1,4 +1,4 @@ -import axios, { type AxiosError } from 'axios' +import { isHttpError, nativeRequest } from '../../utils/http.js' import type { StdoutMessage } from 'src/entrypoints/sdk/controlTypes.js' import { logForDebugging } from '../../utils/debug.js' import { logForDiagnosticsNoPII } from '../../utils/diagLogs.js' @@ -212,20 +212,17 @@ export class HybridTransport extends WebSocketTransport { 'Content-Type': 'application/json', } - let response + let response: { status: number; data: unknown } try { - response = await axios.post( - this.postUrl, - { events }, - { - headers, - validateStatus: () => true, - timeout: POST_TIMEOUT_MS, - }, - ) + response = await nativeRequest(this.postUrl, { + method: 'POST', + headers, + body: { events }, + timeout: POST_TIMEOUT_MS, + responseType: 'json', + }) } catch (error) { - const axiosError = error as AxiosError - logForDebugging(`HybridTransport: POST error: ${axiosError.message}`) + logForDebugging(`HybridTransport: POST error: ${error instanceof Error ? error.message : 'unknown'}`) logForDiagnosticsNoPII('warn', 'cli_hybrid_post_network_error') throw error } diff --git a/cli/transports/SSETransport.ts b/cli/transports/SSETransport.ts index 4f43dbe..4779a1d 100644 --- a/cli/transports/SSETransport.ts +++ b/cli/transports/SSETransport.ts @@ -1,4 +1,4 @@ -import axios, { type AxiosError } from 'axios' +import { isHttpError, nativeRequest } from '../../utils/http.js' import type { StdoutMessage } from 'src/entrypoints/sdk/controlTypes.js' import { logForDebugging } from '../../utils/debug.js' import { logForDiagnosticsNoPII } from '../../utils/diagLogs.js' @@ -590,9 +590,11 @@ export class SSETransport implements Transport { for (let attempt = 1; attempt <= POST_MAX_RETRIES; attempt++) { try { - const response = await axios.post(this.postUrl, message, { + const response = await nativeRequest(this.postUrl, { + method: 'POST', headers, - validateStatus: alwaysValidStatus, + body: message, + responseType: 'json', }) if (response.status === 200 || response.status === 201) { @@ -603,7 +605,6 @@ export class SSETransport implements Transport { logForDebugging( `SSETransport: POST ${response.status} body=${jsonStringify(response.data).slice(0, 200)}`, ) - // 4xx errors (except 429) are permanent - don't retry if ( response.status >= 400 && response.status < 500 && @@ -618,7 +619,6 @@ export class SSETransport implements Transport { return } - // 429 or 5xx - retry logForDebugging( `SSETransport: POST returned ${response.status}, attempt ${attempt}/${POST_MAX_RETRIES}`, ) @@ -627,9 +627,8 @@ export class SSETransport implements Transport { attempt, }) } catch (error) { - const axiosError = error as AxiosError logForDebugging( - `SSETransport: POST error: ${axiosError.message}, attempt ${attempt}/${POST_MAX_RETRIES}`, + `SSETransport: POST error: ${error instanceof Error ? error.message : 'unknown'}, attempt ${attempt}/${POST_MAX_RETRIES}`, ) logForDiagnosticsNoPII('warn', 'cli_sse_post_network_error', { attempt, diff --git a/cli/update.ts b/cli/update.ts index a0cd35f..fdb874e 100644 --- a/cli/update.ts +++ b/cli/update.ts @@ -1,4 +1,5 @@ import chalk from 'chalk' +import { VERSION, PACKAGE_URL } from 'src/constants/product.js' import { logEvent } from 'src/services/analytics/index.js' import { getLatestVersion, @@ -29,7 +30,7 @@ import { getInitialSettings } from 'src/utils/settings/settings.js' export async function update() { logEvent('tengu_update_check', {}) - writeToStdout(`Current version: ${MACRO.VERSION}\n`) + writeToStdout(`Current version: ${VERSION}\n`) const channel = getInitialSettings()?.autoUpdatesChannel ?? 'latest' writeToStdout(`Checking for updates to ${channel} version...\n`) @@ -122,8 +123,8 @@ export async function update() { if (packageManager === 'homebrew') { writeToStdout('Claude is managed by Homebrew.\n') const latest = await getLatestVersion(channel) - if (latest && !gte(MACRO.VERSION, latest)) { - writeToStdout(`Update available: ${MACRO.VERSION} → ${latest}\n`) + if (latest && !gte(VERSION, latest)) { + writeToStdout(`Update available: ${VERSION} → ${latest}\n`) writeToStdout('\n') writeToStdout('To update, run:\n') writeToStdout(chalk.bold(' brew upgrade claude-code') + '\n') @@ -133,8 +134,8 @@ export async function update() { } else if (packageManager === 'winget') { writeToStdout('Claude is managed by winget.\n') const latest = await getLatestVersion(channel) - if (latest && !gte(MACRO.VERSION, latest)) { - writeToStdout(`Update available: ${MACRO.VERSION} → ${latest}\n`) + if (latest && !gte(VERSION, latest)) { + writeToStdout(`Update available: ${VERSION} → ${latest}\n`) writeToStdout('\n') writeToStdout('To update, run:\n') writeToStdout( @@ -146,8 +147,8 @@ export async function update() { } else if (packageManager === 'apk') { writeToStdout('Claude is managed by apk.\n') const latest = await getLatestVersion(channel) - if (latest && !gte(MACRO.VERSION, latest)) { - writeToStdout(`Update available: ${MACRO.VERSION} → ${latest}\n`) + if (latest && !gte(VERSION, latest)) { + writeToStdout(`Update available: ${VERSION} → ${latest}\n`) writeToStdout('\n') writeToStdout('To update, run:\n') writeToStdout(chalk.bold(' apk upgrade claude-code') + '\n') @@ -236,14 +237,14 @@ export async function update() { await gracefulShutdown(1) } - if (result.latestVersion === MACRO.VERSION) { + if (result.latestVersion === VERSION) { writeToStdout( - chalk.green(`Claude Code is up to date (${MACRO.VERSION})`) + '\n', + chalk.green(`Claude Code is up to date (${VERSION})`) + '\n', ) } else { writeToStdout( chalk.green( - `Successfully updated from ${MACRO.VERSION} to version ${result.latestVersion}`, + `Successfully updated from ${VERSION} to version ${result.latestVersion}`, ) + '\n', ) await regenerateCompletionCache() @@ -265,9 +266,9 @@ export async function update() { } logForDebugging('update: Checking npm registry for latest version') - logForDebugging(`update: Package URL: ${MACRO.PACKAGE_URL}`) + logForDebugging(`update: Package URL: ${PACKAGE_URL}`) const npmTag = channel === 'stable' ? 'stable' : 'latest' - const npmCommand = `npm view ${MACRO.PACKAGE_URL}@${npmTag} version` + const npmCommand = `npm view ${PACKAGE_URL}@${npmTag} version` logForDebugging(`update: Running: ${npmCommand}`) const latestVersion = await getLatestVersion(channel) logForDebugging( @@ -283,7 +284,7 @@ export async function update() { process.stderr.write(' • Network connectivity issues\n') process.stderr.write(' • npm registry is unreachable\n') process.stderr.write(' • Corporate proxy/firewall blocking npm\n') - if (MACRO.PACKAGE_URL && !MACRO.PACKAGE_URL.startsWith('@anthropic')) { + if (PACKAGE_URL && !PACKAGE_URL.startsWith('@anthropic')) { process.stderr.write( ' • Internal/development build not published to npm\n', ) @@ -293,7 +294,7 @@ export async function update() { process.stderr.write(' • Check your internet connection\n') process.stderr.write(' • Run with --debug flag for more details\n') const packageName = - MACRO.PACKAGE_URL || + PACKAGE_URL || (process.env.USER_TYPE === 'ant' ? '@anthropic-ai/claude-cli' : '@anthropic-ai/claude-code') @@ -306,15 +307,15 @@ export async function update() { } // Check if versions match exactly, including any build metadata (like SHA) - if (latestVersion === MACRO.VERSION) { + if (latestVersion === VERSION) { writeToStdout( - chalk.green(`Claude Code is up to date (${MACRO.VERSION})`) + '\n', + chalk.green(`Claude Code is up to date (${VERSION})`) + '\n', ) await gracefulShutdown(0) } writeToStdout( - `New version available: ${latestVersion} (current: ${MACRO.VERSION})\n`, + `New version available: ${latestVersion} (current: ${VERSION})\n`, ) writeToStdout('Installing update...\n') @@ -374,7 +375,7 @@ export async function update() { case 'success': writeToStdout( chalk.green( - `Successfully updated from ${MACRO.VERSION} to version ${latestVersion}`, + `Successfully updated from ${VERSION} to version ${latestVersion}`, ) + '\n', ) await regenerateCompletionCache() @@ -386,7 +387,7 @@ export async function update() { if (useLocalUpdate) { process.stderr.write('Try manually updating with:\n') process.stderr.write( - ` cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}\n`, + ` cd ~/.claude/local && npm update ${PACKAGE_URL}\n`, ) } else { process.stderr.write('Try running with sudo or fix npm permissions\n') @@ -401,7 +402,7 @@ export async function update() { if (useLocalUpdate) { process.stderr.write('Try manually updating with:\n') process.stderr.write( - ` cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}\n`, + ` cd ~/.claude/local && npm update ${PACKAGE_URL}\n`, ) } else { process.stderr.write( diff --git a/commands/insights.ts b/commands/insights.ts index f66380a..52cf1fc 100644 --- a/commands/insights.ts +++ b/commands/insights.ts @@ -14,6 +14,7 @@ import { import { tmpdir } from 'os' import { extname, join } from 'path' import type { Command } from '../commands.js' +import { VERSION } from '../constants/product.js' import { queryWithModel } from '../services/api/claude.js' import { AGENT_TOOL_NAME, @@ -2682,7 +2683,7 @@ export function buildExportData( facets: Map, remoteStats?: { hosts: RemoteHostInfo[]; totalCopied: number }, ): InsightsExport { - const version = typeof MACRO !== 'undefined' ? MACRO.VERSION : 'unknown' + const version = VERSION const remote_hosts_collected = remoteStats?.hosts .filter(h => h.sessionCount > 0) diff --git a/commands/remote-setup/api.ts b/commands/remote-setup/api.ts index d08659c..97791f8 100644 --- a/commands/remote-setup/api.ts +++ b/commands/remote-setup/api.ts @@ -1,4 +1,4 @@ -import axios from 'axios' +import { isHttpError, nativeRequest } from '../../utils/http.js' import { getOauthConfig } from '../../constants/oauth.js' import { logForDebugging } from '../../utils/debug.js' import { getOAuthHeaders, prepareApiRequest } from '../../utils/teleport/api.js' @@ -69,32 +69,30 @@ export async function importGithubToken( } try { - const response = await axios.post( - url, - { token: token.reveal() }, - { headers, timeout: 15000, validateStatus: () => true }, - ) - if (response.status === 200) { - return { ok: true, result: response.data } - } - if (response.status === 400) { + const response = await nativeRequest(url, { + method: 'POST', + headers, + body: { token: token.reveal() }, + timeout: 15000, + responseType: 'json', + }) + return { ok: true, result: response.data } + } catch (err) { + if (isHttpError(err) && err.status === 400) { return { ok: false, error: { kind: 'invalid_token' } } } - if (response.status === 401) { + if (isHttpError(err) && err.status === 401) { return { ok: false, error: { kind: 'not_signed_in' } } } - logForDebugging(`import-token returned ${response.status}`, { - level: 'error', - }) - return { ok: false, error: { kind: 'server', status: response.status } } - } catch (err) { - if (axios.isAxiosError(err)) { - // err.config.data would contain the POST body with the raw token. - // Do not include it in any log. The error code alone is enough. - logForDebugging(`import-token network error: ${err.code ?? 'unknown'}`, { + if (isHttpError(err) && err.status >= 400 && err.status < 500) { + logForDebugging(`import-token returned ${err.status}`, { level: 'error', }) + return { ok: false, error: { kind: 'server', status: err.status } } } + logForDebugging(`import-token network error: ${err instanceof Error ? err.message : 'unknown'}`, { + level: 'error', + }) return { ok: false, error: { kind: 'network' } } } } @@ -138,9 +136,10 @@ export async function createDefaultEnvironment(): Promise { } try { - const response = await axios.post( - url, - { + const response = await nativeRequest(url, { + method: 'POST', + headers, + body: { name: 'Default', kind: 'anthropic_cloud', description: 'Default - trusted network access', @@ -159,8 +158,9 @@ export async function createDefaultEnvironment(): Promise { }, }, }, - { headers, timeout: 15000, validateStatus: () => true }, - ) + timeout: 15000, + responseType: 'json', + }) return response.status >= 200 && response.status < 300 } catch { return false diff --git a/commands/version.ts b/commands/version.ts index 09f0a44..d6f79c0 100644 --- a/commands/version.ts +++ b/commands/version.ts @@ -1,11 +1,10 @@ import type { Command, LocalCommandCall } from '../types/command.js' +import { VERSION, BUILD_TIME } from '../constants/product.js' const call: LocalCommandCall = async () => { return { type: 'text', - value: MACRO.BUILD_TIME - ? `${MACRO.VERSION} (built ${MACRO.BUILD_TIME})` - : MACRO.VERSION, + value: BUILD_TIME ? `${VERSION} (built ${BUILD_TIME})` : VERSION, } } diff --git a/components/AutoUpdater.tsx b/components/AutoUpdater.tsx index 144e2c8..ad8ccc3 100644 --- a/components/AutoUpdater.tsx +++ b/components/AutoUpdater.tsx @@ -12,6 +12,7 @@ import { installOrUpdateClaudePackage, localInstallationExists } from '../utils/ import { removeInstalledSymlink } from '../utils/nativeInstaller/index.js'; import { gt, gte } from '../utils/semver.js'; import { getInitialSettings } from '../utils/settings/settings.js'; +import { VERSION, PACKAGE_URL } from '../constants/product.js'; type Props = { isUpdating: boolean; onChangeIsUpdating: (isUpdating: boolean) => void; @@ -53,7 +54,7 @@ export function AutoUpdater({ logForDebugging('AutoUpdater: Skipping update check in test/dev environment'); return; } - const currentVersion = MACRO.VERSION; + const currentVersion = VERSION; const channel = getInitialSettings()?.autoUpdatesChannel ?? 'latest'; let latestVersion = await getLatestVersion(channel); const isDisabled = isAutoUpdaterDisabled(); @@ -190,7 +191,7 @@ export function AutoUpdater({ {(autoUpdaterResult?.status === 'install_failed' || autoUpdaterResult?.status === 'no_permissions') && ✗ Auto-update failed · Try claude doctor or{' '} - {hasLocalInstall ? `cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}` : `npm i -g ${MACRO.PACKAGE_URL}`} + {hasLocalInstall ? `cd ~/.claude/local && npm update ${PACKAGE_URL}` : `npm i -g ${PACKAGE_URL}`} } ; diff --git a/components/Feedback.tsx b/components/Feedback.tsx index 8f2fbb1..c17d66e 100644 --- a/components/Feedback.tsx +++ b/components/Feedback.tsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import { isHttpError, nativeRequest } from '../utils/http.js'; import { readFile, stat } from 'fs/promises'; import * as React from 'react'; import { useCallback, useEffect, useState } from 'react'; @@ -28,6 +28,7 @@ import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js'; import { Byline } from './design-system/Byline.js'; import { Dialog } from './design-system/Dialog.js'; import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js'; +import { VERSION } from 'src/constants/product.js'; import TextInput from './TextInput.js'; // This value was determined experimentally by testing the URL length limit @@ -211,7 +212,7 @@ export function Feedback({ platform: env.platform, gitRepo: envInfo.isGit, terminal: env.terminal, - version: MACRO.VERSION, + version: VERSION, transcript: normalizeMessagesForAPI(messages), errors: sanitizedErrors, lastApiRequest: getLastAPIRequest(), @@ -343,7 +344,7 @@ export function Feedback({ - Environment info:{' '} - {env.platform}, {env.terminal}, v{MACRO.VERSION} + {env.platform}, {env.terminal}, v{VERSION} {envInfo.gitState && @@ -396,7 +397,7 @@ export function createGitHubIssueUrl(feedbackId: string, title: string, descript }>): string { const sanitizedTitle = redactSensitiveInfo(title); const sanitizedDescription = redactSensitiveInfo(description); - const bodyPrefix = `**Bug Description**\n${sanitizedDescription}\n\n` + `**Environment Info**\n` + `- Platform: ${env.platform}\n` + `- Terminal: ${env.terminal}\n` + `- Version: ${MACRO.VERSION || 'unknown'}\n` + `- Feedback ID: ${feedbackId}\n` + `\n**Errors**\n\`\`\`json\n`; + const bodyPrefix = `**Bug Description**\n${sanitizedDescription}\n\n` + `**Environment Info**\n` + `- Platform: ${env.platform}\n` + `- Terminal: ${env.terminal}\n` + `- Version: ${VERSION || 'unknown'}\n` + `- Feedback ID: ${feedbackId}\n` + `\n**Errors**\n\`\`\`json\n`; const errorSuffix = `\n\`\`\`\n`; const errorsJson = jsonStringify(errors); const baseUrl = `${GITHUB_ISSUES_REPO_URL}/new?title=${encodeURIComponent(sanitizedTitle)}&labels=user-reported,bug&body=`; @@ -540,12 +541,13 @@ async function submitFeedback(data: FeedbackData, signal?: AbortSignal): Promise 'User-Agent': getUserAgent(), ...authResult.headers }; - const response = await axios.post('https://api.anthropic.com/api/claude_cli_feedback', { - content: jsonStringify(data) - }, { + const response = await nativeRequest('https://api.anthropic.com/api/claude_cli_feedback', { + method: 'POST', headers, + body: { + content: jsonStringify(data) + }, timeout: 30000, - // 30 second timeout to prevent hanging signal }); if (response.status === 200) { @@ -566,14 +568,13 @@ async function submitFeedback(data: FeedbackData, signal?: AbortSignal): Promise success: false }; } catch (err) { - // Handle cancellation/abort - don't log as error - if (axios.isCancel(err)) { + if (err instanceof Error && err.name === 'AbortError') { return { success: false }; } - if (axios.isAxiosError(err) && err.response?.status === 403) { - const errorData = err.response.data; + if (isHttpError(err) && err.status === 403) { + const errorData = err.data; if (errorData?.error?.type === 'permission_error' && errorData?.error?.message?.includes('Custom data retention settings')) { sanitizeAndLogError(new Error('Cannot submit feedback because custom data retention settings are enabled')); return { diff --git a/components/FeedbackSurvey/submitTranscriptShare.ts b/components/FeedbackSurvey/submitTranscriptShare.ts index 52e1425..968237a 100644 --- a/components/FeedbackSurvey/submitTranscriptShare.ts +++ b/components/FeedbackSurvey/submitTranscriptShare.ts @@ -1,21 +1,12 @@ -import axios from 'axios' -import { readFile, stat } from 'fs/promises' -import type { Message } from '../../types/message.js' -import { checkAndRefreshOAuthTokenIfNeeded } from '../../utils/auth.js' -import { logForDebugging } from '../../utils/debug.js' -import { errorMessage } from '../../utils/errors.js' -import { getAuthHeaders, getUserAgent } from '../../utils/http.js' -import { normalizeMessagesForAPI } from '../../utils/messages.js' -import { - extractAgentIdsFromMessages, - getTranscriptPath, - loadSubagentTranscripts, - MAX_TRANSCRIPT_READ_BYTES, -} from '../../utils/sessionStorage.js' -import { jsonStringify } from '../../utils/slowOperations.js' -import { redactSensitiveInfo } from '../Feedback.js' +/** + * Transcript Share Service - Stubbed + * + * This service is stubbed to ensure no session transcripts or user + * identification data is sent to external services, even during + * feedback surveys. + */ -type TranscriptShareResult = { +export type TranscriptShareResult = { success: boolean transcriptId?: string } @@ -27,86 +18,11 @@ export type TranscriptShareTrigger = | 'memory_survey' export async function submitTranscriptShare( - messages: Message[], - trigger: TranscriptShareTrigger, - appearanceId: string, + _messages: any[], + _trigger: TranscriptShareTrigger, + _appearanceId: string, ): Promise { - try { - logForDebugging('Collecting transcript for sharing', { level: 'info' }) - - const transcript = normalizeMessagesForAPI(messages) - - // Collect subagent transcripts - const agentIds = extractAgentIdsFromMessages(messages) - const subagentTranscripts = await loadSubagentTranscripts(agentIds) - - // Read raw JSONL transcript (with size guard to prevent OOM) - let rawTranscriptJsonl: string | undefined - try { - const transcriptPath = getTranscriptPath() - const { size } = await stat(transcriptPath) - if (size <= MAX_TRANSCRIPT_READ_BYTES) { - rawTranscriptJsonl = await readFile(transcriptPath, 'utf-8') - } else { - logForDebugging( - `Skipping raw transcript read: file too large (${size} bytes)`, - { level: 'warn' }, - ) - } - } catch { - // File may not exist - } - - const data = { - trigger, - version: MACRO.VERSION, - platform: process.platform, - transcript, - subagentTranscripts: - Object.keys(subagentTranscripts).length > 0 - ? subagentTranscripts - : undefined, - rawTranscriptJsonl, - } - - const content = redactSensitiveInfo(jsonStringify(data)) - - await checkAndRefreshOAuthTokenIfNeeded() - - const authResult = getAuthHeaders() - if (authResult.error) { - return { success: false } - } - - const headers: Record = { - 'Content-Type': 'application/json', - 'User-Agent': getUserAgent(), - ...authResult.headers, - } - - const response = await axios.post( - 'https://api.anthropic.com/api/claude_code_shared_session_transcripts', - { content, appearance_id: appearanceId }, - { - headers, - timeout: 30000, - }, - ) - - if (response.status === 200 || response.status === 201) { - const result = response.data - logForDebugging('Transcript shared successfully', { level: 'info' }) - return { - success: true, - transcriptId: result?.transcript_id, - } - } - - return { success: false } - } catch (err) { - logForDebugging(errorMessage(err), { - level: 'error', - }) - return { success: false } - } + // Always return failure to prevent sharing data. + // This effectively disables the feature without crashing the UI. + return { success: false }; } diff --git a/components/LogoV2/LogoV2.tsx b/components/LogoV2/LogoV2.tsx index 182ab8b..91dea10 100644 --- a/components/LogoV2/LogoV2.tsx +++ b/components/LogoV2/LogoV2.tsx @@ -26,6 +26,7 @@ import { EmergencyTip } from './EmergencyTip.js'; import { VoiceModeNotice } from './VoiceModeNotice.js'; import { Opus1mMergeNotice } from './Opus1mMergeNotice.js'; import { feature } from 'bun:bundle'; +import { VERSION } from '../../constants/product.js'; // Conditional require so ChannelsNotice.tsx tree-shakes when both flags are // false. A module-scope helper component inside a feature() ternary does NOT @@ -92,7 +93,7 @@ export function LogoV2() { if ($[2] === Symbol.for("react.memo_cache_sentinel")) { t2 = () => { const currentConfig = getGlobalConfig(); - if (currentConfig.lastReleaseNotesSeen === MACRO.VERSION) { + if (currentConfig.lastReleaseNotesSeen === VERSION) { return; } saveGlobalConfig(_temp3); @@ -526,12 +527,12 @@ export function LogoV2() { return t41; } function _temp3(current) { - if (current.lastReleaseNotesSeen === MACRO.VERSION) { + if (current.lastReleaseNotesSeen === VERSION) { return current; } return { ...current, - lastReleaseNotesSeen: MACRO.VERSION + lastReleaseNotesSeen: VERSION }; } function _temp2(s_0) { diff --git a/components/LogoV2/WelcomeV2.tsx b/components/LogoV2/WelcomeV2.tsx index 0ab0a5f..797c3b2 100644 --- a/components/LogoV2/WelcomeV2.tsx +++ b/components/LogoV2/WelcomeV2.tsx @@ -2,6 +2,7 @@ import { c as _c } from "react/compiler-runtime"; import React from 'react'; import { Box, Text, useTheme } from 'src/ink.js'; import { env } from '../../utils/env.js'; +import { VERSION } from '../../constants/product.js'; const WELCOME_V2_WIDTH = 58; export function WelcomeV2() { const $ = _c(35); @@ -28,7 +29,7 @@ export function WelcomeV2() { let t7; let t8; if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t0 = {"Welcome to Claude Code"} v{MACRO.VERSION} ; + t0 = {"Welcome to Claude Code"} v{VERSION} ; t1 = {"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}; t2 = {" "}; t3 = {" "}; @@ -113,7 +114,7 @@ export function WelcomeV2() { let t5; let t6; if ($[18] === Symbol.for("react.memo_cache_sentinel")) { - t0 = {"Welcome to Claude Code"} v{MACRO.VERSION} ; + t0 = {"Welcome to Claude Code"} v{VERSION} ; t1 = {"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}; t2 = {" "}; t3 = {" * \u2588\u2588\u2588\u2588\u2588\u2593\u2593\u2591 "}; @@ -218,7 +219,7 @@ function AppleTerminalWelcomeV2(t0) { } let t2; if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = v{MACRO.VERSION} ; + t2 = v{VERSION} ; $[2] = t2; } else { t2 = $[2]; @@ -329,7 +330,7 @@ function AppleTerminalWelcomeV2(t0) { } let t2; if ($[24] === Symbol.for("react.memo_cache_sentinel")) { - t2 = v{MACRO.VERSION} ; + t2 = v{VERSION} ; $[24] = t2; } else { t2 = $[24]; diff --git a/components/NativeAutoUpdater.tsx b/components/NativeAutoUpdater.tsx index a71244d..bcfbcfc 100644 --- a/components/NativeAutoUpdater.tsx +++ b/components/NativeAutoUpdater.tsx @@ -12,6 +12,7 @@ import { isAutoUpdaterDisabled } from '../utils/config.js'; import { installLatest } from '../utils/nativeInstaller/index.js'; import { gt } from '../utils/semver.js'; import { getInitialSettings } from '../utils/settings/settings.js'; +import { VERSION } from '../constants/product.js'; /** * Categorize error messages for analytics @@ -89,12 +90,12 @@ export function NativeAutoUpdater({ try { // Check if current version is above the max allowed version const maxVersion = await getMaxVersion(); - if (maxVersion && gt(MACRO.VERSION, maxVersion)) { + if (maxVersion && gt(VERSION, maxVersion)) { const msg = await getMaxVersionMessage(); setMaxVersionIssue(msg ?? 'affects your version'); } const result = await installLatest(channel); - const currentVersion = MACRO.VERSION; + const currentVersion = VERSION; const latencyMs = Date.now() - startTime; // Handle lock contention gracefully - just return without treating as error diff --git a/constants/product.ts b/constants/product.ts index c99e3e9..5479e5f 100644 --- a/constants/product.ts +++ b/constants/product.ts @@ -1,4 +1,8 @@ export const PRODUCT_URL = 'https://claude.com/claude-code' +export const VERSION = '0.1.0-alpha' +export const BUILD_TIME = '2026-04-02T10:12:00Z' // Hardcoded for privacy-focused build +export const FEEDBACK_CHANNEL = '#claude-code-feedback' +export const PACKAGE_URL = '@anthropic-ai/claude-code' // Claude Code Remote session URLs export const CLAUDE_AI_BASE_URL = 'https://claude.ai' diff --git a/docs/AUTH_GUIDE.md b/docs/AUTH_GUIDE.md new file mode 100644 index 0000000..6689d03 --- /dev/null +++ b/docs/AUTH_GUIDE.md @@ -0,0 +1,125 @@ +# Authentication Guide - Claude Code + +This guide provides an overview of the various authentication methods supported by the Claude Code CLI, along with configuration steps and troubleshooting tips. + +--- + +## 1st Party Anthropic Authentication + +Claude Code primarily connects directly to the Anthropic API. There are three main ways to authenticate: + +### Direct API Key +The most common method for individual developers. +- **Environment Variable**: `ANTHROPIC_API_KEY` +- **Setup**: Export your key in your shell profile (e.g., `.zshrc` or `.bashrc`). + ```bash + export ANTHROPIC_API_KEY='sk-ant-api03-...' + ``` +- **Security Note**: This method is prioritized in CI and non-interactive environments. + +### Claude.ai OAuth (Subscriber Mode) +If you have a Claude Pro or Team subscription, you can log in using your Claude.ai account. +- **Command**: Run `/login` in the CLI. +- **How it works**: This opens a browser for OAuth authentication. Once completed, your session is managed via a local secure token. +- **Internal Users**: Internal Anthropic employees use a specialized version of this flow. + +### External Key Helpers +For teams using a secret manager (like 1Password CLI or AWS Secrets Manager), you can use a helper script. +- **Setting**: `apiKeyHelper` in your `~/.claude/settings.json`. +- **Example**: + ```json + { "apiKeyHelper": "op read 'op://private/Anthropic/api-key'" } + ``` +- **Behavior**: The CLI will execute this command to retrieve the key on startup. + +--- + +## Security & Workspace Trust + +Claude Code implements a "Trust Dialog" to protect you from malicious repository settings. + +### Custom Scripts +Settings that execute arbitrary code (like `apiKeyHelper`, `awsAuthRefresh`, or `awsCredentialExport`) are subject to the following rules: +- **Global Settings**: Always trusted (stored in `~/.claude/settings.json`). +- **Project Settings**: Only executed if you have explicitly "trusted" the workspace. +- **Dialog**: If a project-local script is detected, Claude Code will prompt you for approval before execution. + +> [!WARNING] +> Never trust a workspace from an untrusted source, as it could use these helpers to exfiltrate your API keys or run malicious commands on your behalf. + +--- + +## 3rd Party Cloud Providers + +Claude Code supports using models hosted on major cloud platforms. To use these, you must enable the specific provider via environment variables. + +### AWS Bedrock +- **Enable**: Set `CLAUDE_CODE_USE_BEDROCK=true`. +- **Authentication**: Uses standard AWS SDK credentials (IAM Roles, `~/.aws/credentials`, or `AWS_ACCESS_KEY_ID`). +- **Region**: Defaults to `us-east-1`. Override with `AWS_REGION`. +- **Custom Auth**: Supports `awsAuthRefresh` and `awsCredentialExport` settings for specialized SSO flows. + +### GCP Vertex AI +- **Enable**: Set `CLAUDE_CODE_USE_VERTEX=true`. +- **Authentication**: Uses Application Default Credentials (ADC) via `google-auth-library`. +- **Configuration**: + - `ANTHROPIC_VERTEX_PROJECT_ID`: (Required) Your GCP project ID. + - `CLOUD_ML_REGION`: (Optional) Your GCP region. +- **Auth Refresh**: Supports `refreshGcpCredentialsIfNeeded` logic for long-running sessions. + +### Azure Foundry +- **Enable**: Set `CLAUDE_CODE_USE_FOUNDRY=true`. +- **Authentication**: + - Uses `ANTHROPIC_FOUNDRY_API_KEY` if provided. + - Otherwise, falls back to `DefaultAzureCredential` (Azure AD). +- **Endpoint**: Configure via `ANTHROPIC_FOUNDRY_RESOURCE` or `ANTHROPIC_FOUNDRY_BASE_URL`. + +--- + +## Environment Variable Reference + +| Variable | Method | Description | +| :--- | :--- | :--- | +| `ANTHROPIC_API_KEY` | Direct | Your Anthropic API Key. | +| `ANTHROPIC_AUTH_TOKEN` | Direct | Use for bearer-token-based authentication. | +| `ANTHROPIC_CUSTOM_HEADERS` | All | A newline-separated list of `Name: Value` headers. | +| `API_TIMEOUT_MS` | All | Custom timeout for API requests (default: 600000ms). | +| `CLAUDE_CODE_ADDITIONAL_PROTECTION` | All | Sets `x-anthropic-additional-protection: true`. | +| `CLAUDE_CODE_USE_BEDROCK` | Bedrock | Enables the AWS Bedrock provider. | +| `CLAUDE_CODE_USE_VERTEX` | Vertex | Enables the GCP Vertex AI provider. | +| `CLAUDE_CODE_USE_FOUNDRY` | Foundry | Enables the Azure Foundry provider. | +| `CLAUDE_CODE_SKIP_*_AUTH` | 3P | Bypasses local SDK auth for proxy/testing scenarios. | + +--- + +## Advanced Configuration & Priority + +When multiple authentication methods are available, Claude Code follows this priority: + +1. **Managed Context**: CCR or Claude Desktop sessions always force OAuth to ensure session isolation. These sessions ignore local API keys and settings to prevent credential leakage. +2. **Environment Variables**: `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` (unless in "Homespace"). +3. **Key Helper**: The `apiKeyHelper` script if defined in settings. +4. **Local Store**: Credentials saved from a prior `/login` or `~/.claude/settings.json`. + +> [!NOTE] +> Using the `--bare` flag forces the CLI into a hermetic mode that only respects `ANTHROPIC_API_KEY` and explicitly passed settings, ignoring the local keychain and OAuth tokens. + +--- + +## Troubleshooting + +### Common Errors +- **401 Unauthorized**: Typically indicates an expired API key or OAuth session. +- **403 Forbidden**: Your account may not have access to the requested model or feature. +- **AWS/GCP Auth Timeouts**: Often caused by the metadata server check. Ensure your credentials are fresh or set the project/region variables explicitly. + +### Clearing Caches +If you encounter persistent auth issues, you can reset your local state: +1. Run `/logout` in a session. +2. Manually remove `~/.claude/config.json`. +3. (macOS only) Clear relevant entries in the Keychain via `Security`. + +--- + +> [!TIP] +> Use `claude --doctor` to diagnose your current authentication state and connectivity. diff --git a/docs/LLAMA_CPP.md b/docs/LLAMA_CPP.md new file mode 100644 index 0000000..a0761c9 --- /dev/null +++ b/docs/LLAMA_CPP.md @@ -0,0 +1,103 @@ +# Llama.cpp Integration Guide - Claude Code + +This guide explores how to implement a custom API provider for Claude Code using `llama.cpp`'s `llama-server`. This setup is ideal for local-first development or when using high-end hardware like **AMD Strix Halo** or **Apple Silicon M2 Max**. + +--- + +## 1. Architecture Overview + +`llama-server` provides a REST API that can be configured to mimic the OpenAI or Anthropic message formats. To integrate it into Claude Code, you will need to modify the client initialization. + +### Provider Hook Location +The primary location for adding new providers is [`services/api/client.ts`](file:///Users/vlad/Developer/vlad/claude-code/services/api/client.ts). + +1. **Add Provider Type**: Update `APIProvider` in `utils/model/providers.ts` to include `'llama-cpp'`. +2. **Environment Variable**: Use a toggle like `CLAUDE_CODE_USE_LLAMA_CPP=true`. +3. **Client Configuration**: + ```typescript + if (isEnvTruthy(process.env.CLAUDE_CODE_USE_LLAMA_CPP)) { + return new Anthropic({ + apiKey: 'local-key', // llama-server often ignores this + baseURL: process.env.LLAMA_CPP_BASE_URL || 'http://localhost:8080/v1', + ...ARGS, + }) + } + ``` + +### Remote / Proxy Authentication +If you are proxying `llama-server` through an AWS-compatible gateway (e.g., LiteLLM), you can use the `AWS_BEARER_TOKEN_BEDROCK` environment variable to authenticate. + +--- + +--- + +## 2. Hardware Optimization + +To achieve smooth inference on high-end consumer hardware, utilize the following specialized backends. + +### Apple Silicon (M2 Max) +`llama.cpp` has first-class **Metal** support. +- **Flags**: Ensure `-ngl` (number of GPU layers) is set to the maximum (e.g., `-ngl 99`) to offload the entire model to the GPU. +- **Threads**: Match the number of performance cores (e.g., `-t 8`). + +### AMD Strix Halo +Strix Halo features a massive iGPU and a powerful NPU. +- **Vulkan Backend**: Use the Vulkan backend for the iGPU (`LLAMA_VULKAN=1`). +- **ROCm Backend**: For Linux users, ROCm provides near-native performance for AMD hardware. +- **NPU Integration**: If using Windows/Linux with experimental NPU drivers, ensure `llama-server` is compiled with the relevant plugin (e.g., OpenVINO). + +--- + +## 3. Overcoming "Slow PP" (Prompt Processing) + +Prompt Processing (PP) is often the bottleneck in agentic workflows where the context grows rapidly. + +### Persistent KV Caching (Slots) +`llama-server` supports **slots**, which allow multiple sessions to share or persist their KV cache. +- **Persistent Slot**: Use `--slot-save-path /path/to/cache` to save the context state between CLI restarts. +- **Continuous Batching**: Use `--cont-batching` to allow the server to process new prompts while tokens are still being generated for other requests. + +### Configuration Tips +- **Large Context**: Set a generous context size with `-c 32768` (or higher) to avoid frequent context shifting. +- **Flash Attention**: Always enable Flash Attention (`--flash-attn`) to reduce memory bandwidth requirements during PP. + +--- + +## 4. Supporting OSS Models + +Claude Code is tuned for Sonnet/Opus, but can be adapted for state-of-the-art open-source models: + +| Model | Mapping Suggestion | Strength | +| :--- | :--- | :--- | +| **Qwen3-72B-Instruct** | Map to `claude-3-opus-latest` | Excellent reasoning and tool use. | +| **GPT-20-OSS** | Map to `claude-3-5-sonnet-latest` | High-speed, high-intelligence balance. | +| **GPT-120-OSS** | Map to `claude-3-opus-latest` | Deep complex problem solving. | + +--- + +## 5. Recommended `llama-server` Command + +For a dedicated local Claude Code backend: + +```bash +./llama-server \ + -m models/qwen3-72b-q4_k_m.gguf \ + -c 32768 \ + -ngl 99 \ + --flash-attn \ + --cont-batching \ + --host 0.0.0.0 \ + --port 8080 \ + --api-key local-secret-token \ + --slot-save-path ./llama_slots +``` + +--- + +> [!CAUTION] +> Using local models requires significant VRAM. A 70B model in 4-bit quantization requires ~40GB of VRAM. Ensure your hardware (like Strix Halo with 64GB+ shared RAM) can accommodate the model and KV cache. + +--- + +## See Also +- **[Authentication Guide](file:///Users/vlad/Developer/vlad/claude-code/docs/AUTH_GUIDE.md)**: Details on general environment variables and credential management. diff --git a/docs/Z_AI_GLM.md b/docs/Z_AI_GLM.md new file mode 100644 index 0000000..d0669ee --- /dev/null +++ b/docs/Z_AI_GLM.md @@ -0,0 +1,93 @@ +# Zhipu AI (Z.AI) GLM Provider Guide - Claude Code + +This guide explains how to integrate **GLM-5.1** from Zhipu AI as a specialized "Coding Plan Provider" in Claude Code. This allows you to use GLM's strong reasoning capabilities for the architectural and planning phase, while maintaining Claude (or another model) for the execution phase. + +--- + +## 1. Architecture: The Planner-Executor Split + +Claude Code uses a "Plan Mode" to design complex changes before executing them. This is internally managed by `permissionMode: 'plan'`. + +By specialized the models: +- **Planner (GLM-5.1)**: Uses massive context and multi-step reasoning to design a robust implementation plan. +- **Executor (Claude 3.5 Sonnet)**: Follows the plan with precision to write and edit code. + +--- + +## 2. Implementing the Z.AI Provider + +### Hooking the Client +The Z.AI API is largely OpenAI-compatible. You can hook it into Claude Code's existing client initialization in [`services/api/client.ts`](file:///Users/vlad/Developer/vlad/claude-code/services/api/client.ts). + +1. **Add Provider Type**: Update `APIProvider` in `utils/model/providers.ts` to include `'z-ai'`. +2. **Client Entry**: + ```typescript + if (isEnvTruthy(process.env.CLAUDE_CODE_USE_Z_AI)) { + return new Anthropic({ + apiKey: process.env.Z_AI_API_KEY, + baseURL: process.env.Z_AI_BASE_URL || 'https://open.bigmodel.cn/api/paas/v4/', + ...ARGS, + }) + } + ``` + +--- + +## 3. Highjacking "Plan Mode" + +To ensure GLM-5.1 is only used for planning, you need to modify the model selection logic in [`utils/model/model.ts`](file:///Users/vlad/Developer/vlad/claude-code/utils/model/model.ts). + +Modify `getRuntimeMainLoopModel`: + +```typescript +export function getRuntimeMainLoopModel(params: { + permissionMode: PermissionMode + mainLoopModel: string + exceeds200kTokens?: boolean +}): ModelName { + const { permissionMode, mainLoopModel } = params + + // Specialized Planning Provider: GLM-5.1 + if (permissionMode === 'plan' && isEnvTruthy(process.env.CLAUDE_CODE_USE_Z_AI)) { + return 'glm-5.1' // Or your specific deployment ID + } + + // Fallback to Sonnet/Opus for execution + return mainLoopModel +} +``` + +--- + +## 4. Configuration + +To use this setup, configure the following environment variables: + +| Variable | Description | +| :--- | :--- | +| `CLAUDE_CODE_USE_Z_AI=true` | Enables the Z.AI provider logic. | +| `Z_AI_API_KEY` | Your Zhipu AI API Key. | +| `Z_AI_BASE_URL` | The endpoint for BigModel (e.g., `https://open.bigmodel.cn/api/paas/v4/`). | +| `Z_AI_BASE_URL` | The endpoint for BigModel (e.g., `https://open.bigmodel.cn/api/paas/v4/`). | +| `ANTHROPIC_MODEL` | (Optional) The model to use for execution (e.g., `claude-3-5-sonnet-latest`). | +| `CLAUDE_CODE_ADDITIONAL_PROTECTION` | (Optional) Enable strict header validation if required by your gateway. | + +--- + +## 5. Optimization & Performance + +### Tool-Calling +GLM-5.1 is highly proficient at the OpenAI-style tool-calling schema. Claude Code uses a similar structure, making the migration smooth. However, ensure that your `baseURL` correctly routes to the `/chat/completions` endpoint that supports these features. + +### Long Context +GLM-5.1's large context window is a primary advantage for the "Plan Mode" phase, as it can ingest an entire multi-file project structure or complex documentation without truncation. + +--- + +> [!TIP] +> This "hybrid" approach allows you to leverage GLM's cost-efficient and high-reasoning planning while keeping Claude's world-class code-generation for the final edits. + +--- + +## See Also +- **[Authentication Guide](file:///Users/vlad/Developer/vlad/claude-code/docs/AUTH_GUIDE.md)**: Details on general environment variables and credential management. diff --git a/entrypoints/cli.tsx b/entrypoints/cli.tsx index 67326f8..d2511ce 100644 --- a/entrypoints/cli.tsx +++ b/entrypoints/cli.tsx @@ -1,4 +1,5 @@ import { feature } from 'bun:bundle'; +import { VERSION } from '../constants/product.js'; // Bugfix for corepack auto-pinning, which adds yarnpkg to peoples' package.jsons // eslint-disable-next-line custom-rules/no-top-level-side-effects @@ -35,9 +36,8 @@ async function main(): Promise { // Fast-path for --version/-v: zero module loading needed if (args.length === 1 && (args[0] === '--version' || args[0] === '-v' || args[0] === '-V')) { - // MACRO.VERSION is inlined at build time // biome-ignore lint/suspicious/noConsole:: intentional console output - console.log(`${MACRO.VERSION} (Claude Code)`); + console.log(`${VERSION} (Claude Code)`); return; } diff --git a/entrypoints/mcp.ts b/entrypoints/mcp.ts index deaf9d6..3184664 100644 --- a/entrypoints/mcp.ts +++ b/entrypoints/mcp.ts @@ -8,6 +8,7 @@ import { type Tool, } from '@modelcontextprotocol/sdk/types.js' import { getDefaultAppState } from 'src/state/AppStateStore.js' +import { VERSION } from '../constants/product.js' import review from '../commands/review.js' import type { Command } from '../commands.js' import { @@ -47,7 +48,7 @@ export async function startMCPServer( const server = new Server( { name: 'claude/tengu', - version: MACRO.VERSION, + version: VERSION, }, { capabilities: { diff --git a/hooks/useUpdateNotification.ts b/hooks/useUpdateNotification.ts index c9a7b2a..61815cc 100644 --- a/hooks/useUpdateNotification.ts +++ b/hooks/useUpdateNotification.ts @@ -1,5 +1,5 @@ -import { useState } from 'react' import { major, minor, patch } from 'semver' +import { VERSION } from '../constants/product.js' export function getSemverPart(version: string): string { return `${major(version, { loose: true })}.${minor(version, { loose: true })}.${patch(version, { loose: true })}` @@ -15,7 +15,7 @@ export function shouldShowUpdateNotification( export function useUpdateNotification( updatedVersion: string | null | undefined, - initialVersion: string = MACRO.VERSION, + initialVersion: string = VERSION, ): string | null { const [lastNotifiedSemver, setLastNotifiedSemver] = useState( () => getSemverPart(initialVersion), diff --git a/interactiveHelpers.tsx b/interactiveHelpers.tsx index b88dbbf..1773f2d 100644 --- a/interactiveHelpers.tsx +++ b/interactiveHelpers.tsx @@ -2,6 +2,7 @@ import { feature } from 'bun:bundle'; import { appendFileSync } from 'fs'; import React from 'react'; import { logEvent } from 'src/services/analytics/index.js'; +import { VERSION } from './constants/product.js'; import { gracefulShutdown, gracefulShutdownSync } from 'src/utils/gracefulShutdown.js'; import { type ChannelEntry, getAllowedChannels, setAllowedChannels, setHasDevChannels, setSessionTrustAccepted, setStatsStore } from './bootstrap/state.js'; import type { Command } from './commands.js'; @@ -33,7 +34,7 @@ export function completeOnboarding(): void { saveGlobalConfig(current => ({ ...current, hasCompletedOnboarding: true, - lastOnboardingVersion: MACRO.VERSION + lastOnboardingVersion: VERSION })); } export function showDialog(root: Root, renderer: (done: (result: T) => void) => React.ReactNode): Promise { diff --git a/main.tsx b/main.tsx index ea51d90..eef8d83 100644 --- a/main.tsx +++ b/main.tsx @@ -33,7 +33,6 @@ import { init, initializeTelemetryAfterTrust } from './entrypoints/init.js'; import { addToHistory } from './history.js'; import type { Root } from './ink.js'; import { launchRepl } from './replLauncher.js'; -import { hasGrowthBookEnvOverride, initializeGrowthBook, refreshGrowthBookAfterAuthChange } from './services/analytics/growthbook.js'; import { fetchBootstrapData } from './services/api/bootstrap.js'; import { type DownloadResult, downloadSessionFiles, type FilesApiConfig, parseFileSpecs } from './services/api/filesApi.js'; import { prefetchPassesEligibility } from './services/api/referral.js'; @@ -80,10 +79,7 @@ const coordinatorModeModule = feature('COORDINATOR_MODE') ? require('./coordinat const assistantModule = feature('KAIROS') ? require('./assistant/index.js') as typeof import('./assistant/index.js') : null; const kairosGate = feature('KAIROS') ? require('./assistant/gate.js') as typeof import('./assistant/gate.js') : null; import { relative, resolve } from 'path'; -import { isAnalyticsDisabled } from 'src/services/analytics/config.js'; -import { getFeatureValue_CACHED_MAY_BE_STALE } from 'src/services/analytics/growthbook.js'; import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from 'src/services/analytics/index.js'; -import { initializeAnalyticsGates } from 'src/services/analytics/sink.js'; import { getOriginalCwd, setAdditionalDirectoriesForClaudeMd, setIsRemoteMode, setMainLoopModelOverride, setMainThreadAgentType, setTeleportedSessionInfo } from './bootstrap/state.js'; import { filterCommandsForRemoteMode, getCommands } from './commands.js'; import type { StatsStore } from './context/stats.js'; @@ -213,20 +209,6 @@ profileCheckpoint('main_tsx_imports_loaded'); * This is called after init() completes to ensure settings are loaded * and environment variables are applied before model resolution. */ -function logManagedSettings(): void { - try { - const policySettings = getSettingsForSource('policySettings'); - if (policySettings) { - const allKeys = getManagedSettingsKeysForLogging(policySettings); - logEvent('tengu_managed_settings_loaded', { - keyCount: allKeys.length, - keys: allKeys.join(',') as unknown as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS - }); - } - } catch { - // Silently ignore errors - this is just for analytics - } -} // Check if running in debug/inspection mode function isBeingDebugged() { @@ -276,18 +258,6 @@ if ("external" !== 'ant' && isBeingDebugged()) { * main.tsx but branch before the interactive startup path, so it needs two * call sites here rather than one here + one in QueryEngine. */ -function logSessionTelemetry(): void { - const model = parseUserSpecifiedModel(getInitialMainLoopModel() ?? getDefaultMainLoopModel()); - void logSkillsLoaded(getCwd(), getContextWindowForModel(model, getSdkBetas())); - void loadAllPluginsCacheOnly().then(({ - enabled, - errors - }) => { - const managedNames = getManagedPluginNames(); - logPluginsEnabledForSession(enabled, managedNames, getPluginSeedDirs()); - logPluginLoadErrors(errors, managedNames); - }).catch(err => logError(err)); -} function getCertEnvVarTelemetry(): Record { const result: Record = {}; if (process.env.NODE_EXTRA_CA_CERTS) { @@ -304,21 +274,6 @@ function getCertEnvVarTelemetry(): Record { } return result; } -async function logStartupTelemetry(): Promise { - if (isAnalyticsDisabled()) return; - const [isGit, worktreeCount, ghAuthStatus] = await Promise.all([getIsGit(), getWorktreeCount(), getGhAuthStatus()]); - logEvent('tengu_startup_telemetry', { - is_git: isGit, - worktree_count: worktreeCount, - gh_auth_status: ghAuthStatus as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, - sandbox_enabled: SandboxManager.isSandboxingEnabled(), - are_unsandboxed_commands_allowed: SandboxManager.areUnsandboxedCommandsAllowed(), - is_auto_bash_allowed_if_sandbox_enabled: SandboxManager.isAutoAllowBashIfSandboxedEnabled(), - auto_updater_disabled: isAutoUpdaterDisabled(), - prefers_reduced_motion: getInitialSettings().prefersReducedMotion ?? false, - ...getCertEnvVarTelemetry() - }); -} // @[MODEL LAUNCH]: Consider any migrations you may need for model strings. See migrateSonnet1mToSonnet45.ts for an example. // Bump this when adding a new sync migration so existing users re-run the set. @@ -413,8 +368,6 @@ export function startDeferredPrefetches(): void { } void countFilesRoundedRg(getCwd(), AbortSignal.timeout(3000), []); - // Analytics and feature flag initialization - void initializeAnalyticsGates(); void prefetchOfficialMcpUrls(); void refreshModelCapabilities(); @@ -2010,8 +1963,7 @@ async function run(): Promise { // - no env override (which short-circuits _CACHED_MAY_BE_STALE before disk) // - flag absent from disk (== null also catches pre-#22279 poisoned null) const explicitModel = options.model || process.env.ANTHROPIC_MODEL; - if ("external" === 'ant' && explicitModel && explicitModel !== 'default' && !hasGrowthBookEnvOverride('tengu_ant_model_override') && getGlobalConfig().cachedGrowthBookFeatures?.['tengu_ant_model_override'] == null) { - await initializeGrowthBook(); + if ("external" === 'ant' && explicitModel && explicitModel !== 'default' && getGlobalConfig().cachedGrowthBookFeatures?.['tengu_ant_model_override'] == null) { } // Special case the default model with the null keyword @@ -2284,7 +2236,6 @@ async function run(): Promise { // Clear user data cache BEFORE GrowthBook refresh so it picks up fresh credentials resetUserCache(); // Refresh GrowthBook after login to get updated feature flags (e.g., for claude.ai MCPs) - refreshGrowthBookAfterAuthChange(); // Clear any stale trusted device token then enroll for Remote Control. // Both self-gate on tengu_sessions_elevated_auth_enforcement internally // — enrollTrustedDevice() via checkGate_CACHED_OR_BLOCKING (awaits @@ -2521,7 +2472,6 @@ async function run(): Promise { // Log context metrics once at initialization void logContextMetrics(regularMcpConfigs, toolPermissionContext); void logPermissionContextForAnts(null, 'initialization'); - logManagedSettings(); // Register PID file for concurrent-session detection (~/.claude/sessions/) // and fire multi-clauding telemetry. Lives here (not init.ts) so only the @@ -3049,8 +2999,6 @@ async function run(): Promise { numStartups: (current.numStartups ?? 0) + 1 })); setImmediate(() => { - void logStartupTelemetry(); - logSessionTelemetry(); }); // Set up per-turn session environment data uploader (ant-only build). diff --git a/package.json b/package.json new file mode 100644 index 0000000..941c720 --- /dev/null +++ b/package.json @@ -0,0 +1,120 @@ +{ + "name": "claude-code", + "version": "0.1.0-alpha", + "description": "Claude Code is a CLI that helps you code with Claude.", + "type": "module", + "main": "main.tsx", + "bin": { + "claude": "bin/claude.js" + }, + "engines": { + "node": ">=18.0.0", + "bun": ">=1.0.0" + }, + "scripts": { + "dev": "bun run main.tsx", + "build": "bun build ./main.tsx --outdir ./dist --target node", + "test": "bun test", + "lint": "biome check .", + "format": "biome format --write ." + }, + "dependencies": { + "@anthropic-ai/bedrock-sdk": "^0.27.0", + "@anthropic-ai/foundry-sdk": "^0.2.3", + "@anthropic-ai/sdk": "^0.82.0", + "@anthropic-ai/vertex-sdk": "^0.14.4", + "@azure/identity": "^4.5.0", + "@commander-js/extra-typings": "^12.0.1", + "@inquirer/prompts": "^5.0.7", + "bidi-js": "^1.0.3", + "chalk": "^5.3.0", + "chalk-template": "^1.1.0", + "code-excerpt": "^4.0.0", + "color-diff": "^1.4.0", + "color-diff-napi": "^0.0.1", + "commander": "^12.1.0", + "date-fns": "^3.6.0", + "diff": "^8.0.4", + "emoji-regex": "^10.3.0", + "execa": "^9.1.0", + "figures": "^6.1.0", + "fuse.js": "^7.0.0", + "get-east-asian-width": "^1.2.0", + "glob": "^13.0.6", + "google-auth-library": "^9.9.0", + "highlight.js": "^11.9.0", + "https-proxy-agent": "^7.0.4", + "ignore": "^5.3.1", + "indent-string": "^5.0.0", + "ink": "^4.4.1", + "lodash-es": "^4.17.21", + "lodash.debounce": "^4.0.8", + "lru-cache": "^10.2.2", + "marked": "^12.0.1", + "modifiers-napi": "^0.0.1", + "nanoid": "^5.1.7", + "npm-run-path": "^5.3.0", + "onetime": "^7.0.0", + "open": "^10.1.0", + "p-map": "^7.0.2", + "parse-ms": "^4.0.0", + "patch-console": "^2.0.0", + "path-expression-matcher": "^1.0.0", + "path-key": "^4.0.0", + "picomatch": "^4.0.2", + "pngjs": "^7.0.0", + "pretty-ms": "^9.0.0", + "proper-lockfile": "^4.1.2", + "qrcode": "^1.5.3", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-reconciler": "^0.31.0", + "require-directory": "^2.1.1", + "restore-cursor": "^5.1.0", + "run-applescript": "^7.0.0", + "semver": "^7.6.0", + "sharp": "^0.34.5", + "shell-quote": "^1.8.1", + "signal-exit": "^4.1.0", + "slice-ansi": "^7.1.0", + "stack-utils": "^2.0.6", + "stream-json": "^1.8.0", + "streaming-json-stringify": "^3.1.0", + "string-width": "^7.1.0", + "strip-ansi": "^7.1.0", + "strip-final-newline": "^4.0.0", + "supports-hyperlinks": "^3.0.0", + "tree-kill": "^1.2.2", + "type-fest": "^4.18.2", + "undici": "^6.13.0", + "unicorn-magic": "^0.1.0", + "usehooks-ts": "^3.1.0", + "uuid": "^9.0.1", + "whatwg-url": "^14.0.0", + "widest-line": "^5.0.0", + "winston": "^3.13.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.16.0", + "wsl-utils": "^0.4.0", + "xss": "^1.0.15", + "xterm": "^5.3.0", + "y18n": "^5.0.8", + "yargs-parser": "^21.1.1", + "yoctocolors": "^2.0.2", + "yoga-wasm-web": "^0.3.3", + "zod": "^3.23.8" + }, + "devDependencies": { + "typescript": "^5.4.5", + "@biomejs/biome": "1.7.3", + "bun-types": "latest", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@types/node": "^20.12.7", + "@types/lodash-es": "^4.17.12", + "@types/proper-lockfile": "^4.1.4", + "@types/qrcode": "^1.5.5", + "@types/semver": "^7.5.8", + "@types/shell-quote": "^1.7.5" + } +} diff --git a/services/analytics/config.ts b/services/analytics/config.ts deleted file mode 100644 index 9e80601..0000000 --- a/services/analytics/config.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Shared analytics configuration - * - * Common logic for determining when analytics should be disabled - * across all analytics systems (Datadog, 1P) - */ - -import { isEnvTruthy } from '../../utils/envUtils.js' -import { isTelemetryDisabled } from '../../utils/privacyLevel.js' - -/** - * Check if analytics operations should be disabled - * - * Analytics is disabled in the following cases: - * - Test environment (NODE_ENV === 'test') - * - Third-party cloud providers (Bedrock/Vertex) - * - Privacy level is no-telemetry or essential-traffic - */ -export function isAnalyticsDisabled(): boolean { - return ( - process.env.NODE_ENV === 'test' || - isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) || - isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) || - isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) || - isTelemetryDisabled() - ) -} - -/** - * Check if the feedback survey should be suppressed. - * - * Unlike isAnalyticsDisabled(), this does NOT block on 3P providers - * (Bedrock/Vertex/Foundry). The survey is a local UI prompt with no - * transcript data — enterprise customers capture responses via OTEL. - */ -export function isFeedbackSurveyDisabled(): boolean { - return process.env.NODE_ENV === 'test' || isTelemetryDisabled() -} diff --git a/services/analytics/datadog.ts b/services/analytics/datadog.ts deleted file mode 100644 index 2f8bdf3..0000000 --- a/services/analytics/datadog.ts +++ /dev/null @@ -1,307 +0,0 @@ -import axios from 'axios' -import { createHash } from 'crypto' -import memoize from 'lodash-es/memoize.js' -import { getOrCreateUserID } from '../../utils/config.js' -import { logError } from '../../utils/log.js' -import { getCanonicalName } from '../../utils/model/model.js' -import { getAPIProvider } from '../../utils/model/providers.js' -import { MODEL_COSTS } from '../../utils/modelCost.js' -import { isAnalyticsDisabled } from './config.js' -import { getEventMetadata } from './metadata.js' - -const DATADOG_LOGS_ENDPOINT = - 'https://http-intake.logs.us5.datadoghq.com/api/v2/logs' -const DATADOG_CLIENT_TOKEN = 'pubbbf48e6d78dae54bceaa4acf463299bf' -const DEFAULT_FLUSH_INTERVAL_MS = 15000 -const MAX_BATCH_SIZE = 100 -const NETWORK_TIMEOUT_MS = 5000 - -const DATADOG_ALLOWED_EVENTS = new Set([ - 'chrome_bridge_connection_succeeded', - 'chrome_bridge_connection_failed', - 'chrome_bridge_disconnected', - 'chrome_bridge_tool_call_completed', - 'chrome_bridge_tool_call_error', - 'chrome_bridge_tool_call_started', - 'chrome_bridge_tool_call_timeout', - 'tengu_api_error', - 'tengu_api_success', - 'tengu_brief_mode_enabled', - 'tengu_brief_mode_toggled', - 'tengu_brief_send', - 'tengu_cancel', - 'tengu_compact_failed', - 'tengu_exit', - 'tengu_flicker', - 'tengu_init', - 'tengu_model_fallback_triggered', - 'tengu_oauth_error', - 'tengu_oauth_success', - 'tengu_oauth_token_refresh_failure', - 'tengu_oauth_token_refresh_success', - 'tengu_oauth_token_refresh_lock_acquiring', - 'tengu_oauth_token_refresh_lock_acquired', - 'tengu_oauth_token_refresh_starting', - 'tengu_oauth_token_refresh_completed', - 'tengu_oauth_token_refresh_lock_releasing', - 'tengu_oauth_token_refresh_lock_released', - 'tengu_query_error', - 'tengu_session_file_read', - 'tengu_started', - 'tengu_tool_use_error', - 'tengu_tool_use_granted_in_prompt_permanent', - 'tengu_tool_use_granted_in_prompt_temporary', - 'tengu_tool_use_rejected_in_prompt', - 'tengu_tool_use_success', - 'tengu_uncaught_exception', - 'tengu_unhandled_rejection', - 'tengu_voice_recording_started', - 'tengu_voice_toggled', - 'tengu_team_mem_sync_pull', - 'tengu_team_mem_sync_push', - 'tengu_team_mem_sync_started', - 'tengu_team_mem_entries_capped', -]) - -const TAG_FIELDS = [ - 'arch', - 'clientType', - 'errorType', - 'http_status_range', - 'http_status', - 'kairosActive', - 'model', - 'platform', - 'provider', - 'skillMode', - 'subscriptionType', - 'toolName', - 'userBucket', - 'userType', - 'version', - 'versionBase', -] - -function camelToSnakeCase(str: string): string { - return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) -} - -type DatadogLog = { - ddsource: string - ddtags: string - message: string - service: string - hostname: string - [key: string]: unknown -} - -let logBatch: DatadogLog[] = [] -let flushTimer: NodeJS.Timeout | null = null -let datadogInitialized: boolean | null = null - -async function flushLogs(): Promise { - if (logBatch.length === 0) return - - const logsToSend = logBatch - logBatch = [] - - try { - await axios.post(DATADOG_LOGS_ENDPOINT, logsToSend, { - headers: { - 'Content-Type': 'application/json', - 'DD-API-KEY': DATADOG_CLIENT_TOKEN, - }, - timeout: NETWORK_TIMEOUT_MS, - }) - } catch (error) { - logError(error) - } -} - -function scheduleFlush(): void { - if (flushTimer) return - - flushTimer = setTimeout(() => { - flushTimer = null - void flushLogs() - }, getFlushIntervalMs()).unref() -} - -export const initializeDatadog = memoize(async (): Promise => { - if (isAnalyticsDisabled()) { - datadogInitialized = false - return false - } - - try { - datadogInitialized = true - return true - } catch (error) { - logError(error) - datadogInitialized = false - return false - } -}) - -/** - * Flush remaining Datadog logs and shut down. - * Called from gracefulShutdown() before process.exit() since - * forceExit() prevents the beforeExit handler from firing. - */ -export async function shutdownDatadog(): Promise { - if (flushTimer) { - clearTimeout(flushTimer) - flushTimer = null - } - await flushLogs() -} - -// NOTE: use via src/services/analytics/index.ts > logEvent -export async function trackDatadogEvent( - eventName: string, - properties: { [key: string]: boolean | number | undefined }, -): Promise { - if (process.env.NODE_ENV !== 'production') { - return - } - - // Don't send events for 3P providers (Bedrock, Vertex, Foundry) - if (getAPIProvider() !== 'firstParty') { - return - } - - // Fast path: use cached result if available to avoid await overhead - let initialized = datadogInitialized - if (initialized === null) { - initialized = await initializeDatadog() - } - if (!initialized || !DATADOG_ALLOWED_EVENTS.has(eventName)) { - return - } - - try { - const metadata = await getEventMetadata({ - model: properties.model, - betas: properties.betas, - }) - // Destructure to avoid duplicate envContext (once nested, once flattened) - const { envContext, ...restMetadata } = metadata - const allData: Record = { - ...restMetadata, - ...envContext, - ...properties, - userBucket: getUserBucket(), - } - - // Normalize MCP tool names to "mcp" for cardinality reduction - if ( - typeof allData.toolName === 'string' && - allData.toolName.startsWith('mcp__') - ) { - allData.toolName = 'mcp' - } - - // Normalize model names for cardinality reduction (external users only) - if (process.env.USER_TYPE !== 'ant' && typeof allData.model === 'string') { - const shortName = getCanonicalName(allData.model.replace(/\[1m]$/i, '')) - allData.model = shortName in MODEL_COSTS ? shortName : 'other' - } - - // Truncate dev version to base + date (remove timestamp and sha for cardinality reduction) - // e.g. "2.0.53-dev.20251124.t173302.sha526cc6a" -> "2.0.53-dev.20251124" - if (typeof allData.version === 'string') { - allData.version = allData.version.replace( - /^(\d+\.\d+\.\d+-dev\.\d{8})\.t\d+\.sha[a-f0-9]+$/, - '$1', - ) - } - - // Transform status to http_status and http_status_range to avoid Datadog reserved field - if (allData.status !== undefined && allData.status !== null) { - const statusCode = String(allData.status) - allData.http_status = statusCode - - // Determine status range (1xx, 2xx, 3xx, 4xx, 5xx) - const firstDigit = statusCode.charAt(0) - if (firstDigit >= '1' && firstDigit <= '5') { - allData.http_status_range = `${firstDigit}xx` - } - - // Remove original status field to avoid conflict with Datadog's reserved field - delete allData.status - } - - // Build ddtags with high-cardinality fields for filtering. - // event: is prepended so the event name is searchable via the - // log search API — the `message` field (where eventName also lives) - // is a DD reserved field and is NOT queryable from dashboard widget - // queries or the aggregation API. See scripts/release/MONITORING.md. - const allDataRecord = allData - const tags = [ - `event:${eventName}`, - ...TAG_FIELDS.filter( - field => - allDataRecord[field] !== undefined && allDataRecord[field] !== null, - ).map(field => `${camelToSnakeCase(field)}:${allDataRecord[field]}`), - ] - - const log: DatadogLog = { - ddsource: 'nodejs', - ddtags: tags.join(','), - message: eventName, - service: 'claude-code', - hostname: 'claude-code', - env: process.env.USER_TYPE, - } - - // Add all fields as searchable attributes (not duplicated in tags) - for (const [key, value] of Object.entries(allData)) { - if (value !== undefined && value !== null) { - log[camelToSnakeCase(key)] = value - } - } - - logBatch.push(log) - - // Flush immediately if batch is full, otherwise schedule - if (logBatch.length >= MAX_BATCH_SIZE) { - if (flushTimer) { - clearTimeout(flushTimer) - flushTimer = null - } - void flushLogs() - } else { - scheduleFlush() - } - } catch (error) { - logError(error) - } -} - -const NUM_USER_BUCKETS = 30 - -/** - * Gets a 'bucket' that the user ID falls into. - * - * For alerting purposes, we want to alert on the number of users impacted - * by an issue, rather than the number of events- often a small number of users - * can generate a large number of events (e.g. due to retries). To approximate - * this without ruining cardinality by counting user IDs directly, we hash the user ID - * and assign it to one of a fixed number of buckets. - * - * This allows us to estimate the number of unique users by counting unique buckets, - * while preserving user privacy and reducing cardinality. - */ -const getUserBucket = memoize((): number => { - const userId = getOrCreateUserID() - const hash = createHash('sha256').update(userId).digest('hex') - return parseInt(hash.slice(0, 8), 16) % NUM_USER_BUCKETS -}) - -function getFlushIntervalMs(): number { - // Allow tests to override to not block on the default flush interval. - return ( - parseInt(process.env.CLAUDE_CODE_DATADOG_FLUSH_INTERVAL_MS || '', 10) || - DEFAULT_FLUSH_INTERVAL_MS - ) -} diff --git a/services/analytics/firstPartyEventLogger.ts b/services/analytics/firstPartyEventLogger.ts index e3a501d..e25a3b3 100644 --- a/services/analytics/firstPartyEventLogger.ts +++ b/services/analytics/firstPartyEventLogger.ts @@ -1,449 +1,33 @@ -import type { AnyValueMap, Logger, logs } from '@opentelemetry/api-logs' -import { resourceFromAttributes } from '@opentelemetry/resources' -import { - BatchLogRecordProcessor, - LoggerProvider, -} from '@opentelemetry/sdk-logs' -import { - ATTR_SERVICE_NAME, - ATTR_SERVICE_VERSION, -} from '@opentelemetry/semantic-conventions' -import { randomUUID } from 'crypto' -import { isEqual } from 'lodash-es' -import { getOrCreateUserID } from '../../utils/config.js' -import { logForDebugging } from '../../utils/debug.js' -import { logError } from '../../utils/log.js' -import { getPlatform, getWslVersion } from '../../utils/platform.js' -import { jsonStringify } from '../../utils/slowOperations.js' -import { profileCheckpoint } from '../../utils/startupProfiler.js' -import { getCoreUserData } from '../../utils/user.js' -import { isAnalyticsDisabled } from './config.js' -import { FirstPartyEventLoggingExporter } from './firstPartyEventLoggingExporter.js' -import type { GrowthBookUserAttributes } from './growthbook.js' -import { getDynamicConfig_CACHED_MAY_BE_STALE } from './growthbook.js' -import { getEventMetadata } from './metadata.js' -import { isSinkKilled } from './sinkKillswitch.js' - /** - * Configuration for sampling individual event types. - * Each event name maps to an object containing sample_rate (0-1). - * Events not in the config are logged at 100% rate. - */ -export type EventSamplingConfig = { - [eventName: string]: { - sample_rate: number - } -} - -const EVENT_SAMPLING_CONFIG_NAME = 'tengu_event_sampling_config' -/** - * Get the event sampling configuration from GrowthBook. - * Uses cached value if available, updates cache in background. - */ -export function getEventSamplingConfig(): EventSamplingConfig { - return getDynamicConfig_CACHED_MAY_BE_STALE( - EVENT_SAMPLING_CONFIG_NAME, - {}, - ) -} - -/** - * Determine if an event should be sampled based on its sample rate. - * Returns the sample rate if sampled, null if not sampled. + * First Party Event Logger - STUB * - * @param eventName - Name of the event to check - * @returns The sample_rate if event should be logged, null if it should be dropped + * This module has been stubbed out as part of the telemetry purge. + * It no longer has any dependencies on @opentelemetry or other analytics packages. */ -export function shouldSampleEvent(eventName: string): number | null { - const config = getEventSamplingConfig() - const eventConfig = config[eventName] - // If no config for this event, log at 100% rate (no sampling) - if (!eventConfig) { - return null - } - - const sampleRate = eventConfig.sample_rate - - // Validate sample rate is in valid range - if (typeof sampleRate !== 'number' || sampleRate < 0 || sampleRate > 1) { - return null - } - - // Sample rate of 1 means log everything (no need to add metadata) - if (sampleRate >= 1) { - return null - } - - // Sample rate of 0 means drop everything - if (sampleRate <= 0) { - return 0 - } - - // Randomly decide whether to sample this event - return Math.random() < sampleRate ? sampleRate : 0 -} - -const BATCH_CONFIG_NAME = 'tengu_1p_event_batch_config' -type BatchConfig = { - scheduledDelayMillis?: number - maxExportBatchSize?: number - maxQueueSize?: number - skipAuth?: boolean - maxAttempts?: number - path?: string - baseUrl?: string -} -function getBatchConfig(): BatchConfig { - return getDynamicConfig_CACHED_MAY_BE_STALE( - BATCH_CONFIG_NAME, - {}, - ) -} - -// Module-local state for event logging (not exposed globally) -let firstPartyEventLogger: ReturnType | null = null -let firstPartyEventLoggerProvider: LoggerProvider | null = null -// Last batch config used to construct the provider — used by -// reinitialize1PEventLoggingIfConfigChanged to decide whether a rebuild is -// needed when GrowthBook refreshes. -let lastBatchConfig: BatchConfig | null = null -/** - * Flush and shutdown the 1P event logger. - * This should be called as the final step before process exit to ensure - * all events (including late ones from API responses) are exported. - */ -export async function shutdown1PEventLogging(): Promise { - if (!firstPartyEventLoggerProvider) { - return - } - try { - await firstPartyEventLoggerProvider.shutdown() - if (process.env.USER_TYPE === 'ant') { - logForDebugging('1P event logging: final shutdown complete') - } - } catch { - // Ignore shutdown errors - } -} - -/** - * Check if 1P event logging is enabled. - * Respects the same opt-outs as other analytics sinks: - * - Test environment - * - Third-party cloud providers (Bedrock/Vertex) - * - Global telemetry opt-outs - * - Non-essential traffic disabled - * - * Note: Unlike BigQuery metrics, event logging does NOT check organization-level - * metrics opt-out via API. It follows the same pattern as Statsig event logging. - */ -export function is1PEventLoggingEnabled(): boolean { - // Respect standard analytics opt-outs - return !isAnalyticsDisabled() -} - -/** - * Log a 1st-party event for internal analytics (async version). - * Events are batched and exported to /api/event_logging/batch - * - * This enriches the event with core metadata (model, session, env context, etc.) - * at log time, similar to logEventToStatsig. - * - * @param eventName - Name of the event (e.g., 'tengu_api_query') - * @param metadata - Additional metadata for the event (intentionally no strings, to avoid accidentally logging code/filepaths) - */ -async function logEventTo1PAsync( - firstPartyEventLogger: Logger, - eventName: string, - metadata: Record = {}, -): Promise { - try { - // Enrich with core metadata at log time (similar to Statsig pattern) - const coreMetadata = await getEventMetadata({ - model: metadata.model, - betas: metadata.betas, - }) - - // Build attributes - OTel supports nested objects natively via AnyValueMap - // Cast through unknown since our nested objects are structurally compatible - // with AnyValue but TS doesn't recognize it due to missing index signatures - const attributes = { - event_name: eventName, - event_id: randomUUID(), - // Pass objects directly - no JSON serialization needed - core_metadata: coreMetadata, - user_metadata: getCoreUserData(true), - event_metadata: metadata, - } as unknown as AnyValueMap - - // Add user_id if available - const userId = getOrCreateUserID() - if (userId) { - attributes.user_id = userId - } - - // Debug logging when debug mode is enabled - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `[ANT-ONLY] 1P event: ${eventName} ${jsonStringify(metadata, null, 0)}`, - ) - } - - // Emit log record - firstPartyEventLogger.emit({ - body: eventName, - attributes, - }) - } catch (e) { - if (process.env.NODE_ENV === 'development') { - throw e - } - if (process.env.USER_TYPE === 'ant') { - logError(e as Error) - } - // swallow - } -} - -/** - * Log a 1st-party event for internal analytics. - * Events are batched and exported to /api/event_logging/batch - * - * @param eventName - Name of the event (e.g., 'tengu_api_query') - * @param metadata - Additional metadata for the event (intentionally no strings, to avoid accidentally logging code/filepaths) - */ -export function logEventTo1P( - eventName: string, - metadata: Record = {}, -): void { - if (!is1PEventLoggingEnabled()) { - return - } - - if (!firstPartyEventLogger || isSinkKilled('firstParty')) { - return - } - - // Fire and forget - don't block on metadata enrichment - void logEventTo1PAsync(firstPartyEventLogger, eventName, metadata) -} - -/** - * GrowthBook experiment event data for logging - */ -export type GrowthBookExperimentData = { - experimentId: string - variationId: number - userAttributes?: GrowthBookUserAttributes - experimentMetadata?: Record -} - -// api.anthropic.com only serves the "production" GrowthBook environment -// (see starling/starling/cli/cli.py DEFAULT_ENVIRONMENTS). Staging and -// development environments are not exported to the prod API. -function getEnvironmentForGrowthBook(): string { - return 'production' -} - -/** - * Log a GrowthBook experiment assignment event to 1P. - * Events are batched and exported to /api/event_logging/batch - * - * @param data - GrowthBook experiment assignment data - */ -export function logGrowthBookExperimentTo1P( - data: GrowthBookExperimentData, -): void { - if (!is1PEventLoggingEnabled()) { - return - } - - if (!firstPartyEventLogger || isSinkKilled('firstParty')) { - return - } - - const userId = getOrCreateUserID() - const { accountUuid, organizationUuid } = getCoreUserData(true) - - // Build attributes for GrowthbookExperimentEvent - const attributes = { - event_type: 'GrowthbookExperimentEvent', - event_id: randomUUID(), - experiment_id: data.experimentId, - variation_id: data.variationId, - ...(userId && { device_id: userId }), - ...(accountUuid && { account_uuid: accountUuid }), - ...(organizationUuid && { organization_uuid: organizationUuid }), - ...(data.userAttributes && { - session_id: data.userAttributes.sessionId, - user_attributes: jsonStringify(data.userAttributes), - }), - ...(data.experimentMetadata && { - experiment_metadata: jsonStringify(data.experimentMetadata), - }), - environment: getEnvironmentForGrowthBook(), - } - - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `[ANT-ONLY] 1P GrowthBook experiment: ${data.experimentId} variation=${data.variationId}`, - ) - } - - firstPartyEventLogger.emit({ - body: 'growthbook_experiment', - attributes, - }) -} - -const DEFAULT_LOGS_EXPORT_INTERVAL_MS = 10000 -const DEFAULT_MAX_EXPORT_BATCH_SIZE = 200 -const DEFAULT_MAX_QUEUE_SIZE = 8192 - -/** - * Initialize 1P event logging infrastructure. - * This creates a separate LoggerProvider for internal event logging, - * independent of customer OTLP telemetry. - * - * This uses its own minimal resource configuration with just the attributes - * we need for internal analytics (service name, version, platform info). - */ export function initialize1PEventLogging(): void { - profileCheckpoint('1p_event_logging_start') - const enabled = is1PEventLoggingEnabled() - - if (!enabled) { - if (process.env.USER_TYPE === 'ant') { - logForDebugging('1P event logging not enabled') - } - return - } - - // Fetch batch processor configuration from GrowthBook dynamic config - // Uses cached value if available, refreshes in background - const batchConfig = getBatchConfig() - lastBatchConfig = batchConfig - profileCheckpoint('1p_event_after_growthbook_config') - - const scheduledDelayMillis = - batchConfig.scheduledDelayMillis || - parseInt( - process.env.OTEL_LOGS_EXPORT_INTERVAL || - DEFAULT_LOGS_EXPORT_INTERVAL_MS.toString(), - ) - - const maxExportBatchSize = - batchConfig.maxExportBatchSize || DEFAULT_MAX_EXPORT_BATCH_SIZE - - const maxQueueSize = batchConfig.maxQueueSize || DEFAULT_MAX_QUEUE_SIZE - - // Build our own resource for 1P event logging with minimal attributes - const platform = getPlatform() - const attributes: Record = { - [ATTR_SERVICE_NAME]: 'claude-code', - [ATTR_SERVICE_VERSION]: MACRO.VERSION, - } - - // Add WSL-specific attributes if running on WSL - if (platform === 'wsl') { - const wslVersion = getWslVersion() - if (wslVersion) { - attributes['wsl.version'] = wslVersion - } - } - - const resource = resourceFromAttributes(attributes) - - // Create a new LoggerProvider with the EventLoggingExporter - // NOTE: This is kept separate from customer telemetry logs to ensure - // internal events don't leak to customer endpoints and vice versa. - // We don't register this globally - it's only used for internal event logging. - const eventLoggingExporter = new FirstPartyEventLoggingExporter({ - maxBatchSize: maxExportBatchSize, - skipAuth: batchConfig.skipAuth, - maxAttempts: batchConfig.maxAttempts, - path: batchConfig.path, - baseUrl: batchConfig.baseUrl, - isKilled: () => isSinkKilled('firstParty'), - }) - firstPartyEventLoggerProvider = new LoggerProvider({ - resource, - processors: [ - new BatchLogRecordProcessor(eventLoggingExporter, { - scheduledDelayMillis, - maxExportBatchSize, - maxQueueSize, - }), - ], - }) - - // Initialize event logger from our internal provider (NOT from global API) - // IMPORTANT: We must get the logger from our local provider, not logs.getLogger() - // because logs.getLogger() returns a logger from the global provider, which is - // separate and used for customer telemetry. - firstPartyEventLogger = firstPartyEventLoggerProvider.getLogger( - 'com.anthropic.claude_code.events', - MACRO.VERSION, - ) + // No-op +} + +export function logEventTo1P( + _eventName: string, + _metadata: Record = {}, +): void { + // No-op +} + +export function logGrowthBookExperimentTo1P(_data: unknown): void { + // No-op +} + +export async function shutdown1PEventLogging(): Promise { + // No-op } -/** - * Rebuild the 1P event logging pipeline if the batch config changed. - * Register this with onGrowthBookRefresh so long-running sessions pick up - * changes to batch size, delay, endpoint, etc. - * - * Event-loss safety: - * 1. Null the logger first — concurrent logEventTo1P() calls hit the - * !firstPartyEventLogger guard and bail during the swap window. This drops - * a handful of events but prevents emitting to a draining provider. - * 2. forceFlush() drains the old BatchLogRecordProcessor buffer to the - * exporter. Export failures go to disk at getCurrentBatchFilePath() which - * is keyed by module-level BATCH_UUID + sessionId — unchanged across - * reinit — so the NEW exporter's disk-backed retry picks them up. - * 3. Swap to new provider/logger; old provider shutdown runs in background - * (buffer already drained, just cleanup). - */ export async function reinitialize1PEventLoggingIfConfigChanged(): Promise { - if (!is1PEventLoggingEnabled() || !firstPartyEventLoggerProvider) { - return - } - - const newConfig = getBatchConfig() - - if (isEqual(newConfig, lastBatchConfig)) { - return - } - - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `1P event logging: ${BATCH_CONFIG_NAME} changed, reinitializing`, - ) - } - - const oldProvider = firstPartyEventLoggerProvider - const oldLogger = firstPartyEventLogger - firstPartyEventLogger = null - - try { - await oldProvider.forceFlush() - } catch { - // Export failures are already on disk; new exporter will retry them. - } - - firstPartyEventLoggerProvider = null - try { - initialize1PEventLogging() - } catch (e) { - // Restore so the next GrowthBook refresh can retry. oldProvider was - // only forceFlush()'d, not shut down — it's still functional. Without - // this, both stay null and the !firstPartyEventLoggerProvider gate at - // the top makes recovery impossible. - firstPartyEventLoggerProvider = oldProvider - firstPartyEventLogger = oldLogger - logError(e) - return - } - - void oldProvider.shutdown().catch(() => {}) + // No-op +} + +export function is1PEventLoggingEnabled(): boolean { + return false } diff --git a/services/analytics/firstPartyEventLoggingExporter.ts b/services/analytics/firstPartyEventLoggingExporter.ts deleted file mode 100644 index aefb22c..0000000 --- a/services/analytics/firstPartyEventLoggingExporter.ts +++ /dev/null @@ -1,806 +0,0 @@ -import type { HrTime } from '@opentelemetry/api' -import { type ExportResult, ExportResultCode } from '@opentelemetry/core' -import type { - LogRecordExporter, - ReadableLogRecord, -} from '@opentelemetry/sdk-logs' -import axios from 'axios' -import { randomUUID } from 'crypto' -import { appendFile, mkdir, readdir, unlink, writeFile } from 'fs/promises' -import * as path from 'path' -import type { CoreUserData } from 'src/utils/user.js' -import { - getIsNonInteractiveSession, - getSessionId, -} from '../../bootstrap/state.js' -import { ClaudeCodeInternalEvent } from '../../types/generated/events_mono/claude_code/v1/claude_code_internal_event.js' -import { GrowthbookExperimentEvent } from '../../types/generated/events_mono/growthbook/v1/growthbook_experiment_event.js' -import { - getClaudeAIOAuthTokens, - hasProfileScope, - isClaudeAISubscriber, -} from '../../utils/auth.js' -import { checkHasTrustDialogAccepted } from '../../utils/config.js' -import { logForDebugging } from '../../utils/debug.js' -import { getClaudeConfigHomeDir } from '../../utils/envUtils.js' -import { errorMessage, isFsInaccessible, toError } from '../../utils/errors.js' -import { getAuthHeaders } from '../../utils/http.js' -import { readJSONLFile } from '../../utils/json.js' -import { logError } from '../../utils/log.js' -import { sleep } from '../../utils/sleep.js' -import { jsonStringify } from '../../utils/slowOperations.js' -import { getClaudeCodeUserAgent } from '../../utils/userAgent.js' -import { isOAuthTokenExpired } from '../oauth/client.js' -import { stripProtoFields } from './index.js' -import { type EventMetadata, to1PEventFormat } from './metadata.js' - -// Unique ID for this process run - used to isolate failed event files between runs -const BATCH_UUID = randomUUID() - -// File prefix for failed event storage -const FILE_PREFIX = '1p_failed_events.' - -// Storage directory for failed events - evaluated at runtime to respect CLAUDE_CONFIG_DIR in tests -function getStorageDir(): string { - return path.join(getClaudeConfigHomeDir(), 'telemetry') -} - -// API envelope - event_data is the JSON output from proto toJSON() -type FirstPartyEventLoggingEvent = { - event_type: 'ClaudeCodeInternalEvent' | 'GrowthbookExperimentEvent' - event_data: unknown -} - -type FirstPartyEventLoggingPayload = { - events: FirstPartyEventLoggingEvent[] -} - -/** - * Exporter for 1st-party event logging to /api/event_logging/batch. - * - * Export cycles are controlled by OpenTelemetry's BatchLogRecordProcessor, which - * triggers export() when either: - * - Time interval elapses (default: 5 seconds via scheduledDelayMillis) - * - Batch size is reached (default: 200 events via maxExportBatchSize) - * - * This exporter adds resilience on top: - * - Append-only log for failed events (concurrency-safe) - * - Quadratic backoff retry for failed events, dropped after maxAttempts - * - Immediate retry of queued events when any export succeeds (endpoint is healthy) - * - Chunking large event sets into smaller batches - * - Auth fallback: retries without auth on 401 errors - */ -export class FirstPartyEventLoggingExporter implements LogRecordExporter { - private readonly endpoint: string - private readonly timeout: number - private readonly maxBatchSize: number - private readonly skipAuth: boolean - private readonly batchDelayMs: number - private readonly baseBackoffDelayMs: number - private readonly maxBackoffDelayMs: number - private readonly maxAttempts: number - private readonly isKilled: () => boolean - private pendingExports: Promise[] = [] - private isShutdown = false - private readonly schedule: ( - fn: () => Promise, - delayMs: number, - ) => () => void - private cancelBackoff: (() => void) | null = null - private attempts = 0 - private isRetrying = false - private lastExportErrorContext: string | undefined - - constructor( - options: { - timeout?: number - maxBatchSize?: number - skipAuth?: boolean - batchDelayMs?: number - baseBackoffDelayMs?: number - maxBackoffDelayMs?: number - maxAttempts?: number - path?: string - baseUrl?: string - // Injected killswitch probe. Checked per-POST so that disabling the - // firstParty sink also stops backoff retries (not just new emits). - // Passed in rather than imported to avoid a cycle with firstPartyEventLogger.ts. - isKilled?: () => boolean - schedule?: (fn: () => Promise, delayMs: number) => () => void - } = {}, - ) { - // Default: prod, except when ANTHROPIC_BASE_URL is explicitly staging. - // Overridable via tengu_1p_event_batch_config.baseUrl. - const baseUrl = - options.baseUrl || - (process.env.ANTHROPIC_BASE_URL === 'https://api-staging.anthropic.com' - ? 'https://api-staging.anthropic.com' - : 'https://api.anthropic.com') - - this.endpoint = `${baseUrl}${options.path || '/api/event_logging/batch'}` - - this.timeout = options.timeout || 10000 - this.maxBatchSize = options.maxBatchSize || 200 - this.skipAuth = options.skipAuth ?? false - this.batchDelayMs = options.batchDelayMs || 100 - this.baseBackoffDelayMs = options.baseBackoffDelayMs || 500 - this.maxBackoffDelayMs = options.maxBackoffDelayMs || 30000 - this.maxAttempts = options.maxAttempts ?? 8 - this.isKilled = options.isKilled ?? (() => false) - this.schedule = - options.schedule ?? - ((fn, ms) => { - const t = setTimeout(fn, ms) - return () => clearTimeout(t) - }) - - // Retry any failed events from previous runs of this session (in background) - void this.retryPreviousBatches() - } - - // Expose for testing - async getQueuedEventCount(): Promise { - return (await this.loadEventsFromCurrentBatch()).length - } - - // --- Storage helpers --- - - private getCurrentBatchFilePath(): string { - return path.join( - getStorageDir(), - `${FILE_PREFIX}${getSessionId()}.${BATCH_UUID}.json`, - ) - } - - private async loadEventsFromFile( - filePath: string, - ): Promise { - try { - return await readJSONLFile(filePath) - } catch { - return [] - } - } - - private async loadEventsFromCurrentBatch(): Promise< - FirstPartyEventLoggingEvent[] - > { - return this.loadEventsFromFile(this.getCurrentBatchFilePath()) - } - - private async saveEventsToFile( - filePath: string, - events: FirstPartyEventLoggingEvent[], - ): Promise { - try { - if (events.length === 0) { - try { - await unlink(filePath) - } catch { - // File doesn't exist, nothing to delete - } - } else { - // Ensure storage directory exists - await mkdir(getStorageDir(), { recursive: true }) - // Write as JSON lines (one event per line) - const content = events.map(e => jsonStringify(e)).join('\n') + '\n' - await writeFile(filePath, content, 'utf8') - } - } catch (error) { - logError(error) - } - } - - private async appendEventsToFile( - filePath: string, - events: FirstPartyEventLoggingEvent[], - ): Promise { - if (events.length === 0) return - try { - // Ensure storage directory exists - await mkdir(getStorageDir(), { recursive: true }) - // Append as JSON lines (one event per line) - atomic on most filesystems - const content = events.map(e => jsonStringify(e)).join('\n') + '\n' - await appendFile(filePath, content, 'utf8') - } catch (error) { - logError(error) - } - } - - private async deleteFile(filePath: string): Promise { - try { - await unlink(filePath) - } catch { - // File doesn't exist or can't be deleted, ignore - } - } - - // --- Previous batch retry (startup) --- - - private async retryPreviousBatches(): Promise { - try { - const prefix = `${FILE_PREFIX}${getSessionId()}.` - let files: string[] - try { - files = (await readdir(getStorageDir())) - .filter((f: string) => f.startsWith(prefix) && f.endsWith('.json')) - .filter((f: string) => !f.includes(BATCH_UUID)) // Exclude current batch - } catch (e) { - if (isFsInaccessible(e)) return - throw e - } - - for (const file of files) { - const filePath = path.join(getStorageDir(), file) - void this.retryFileInBackground(filePath) - } - } catch (error) { - logError(error) - } - } - - private async retryFileInBackground(filePath: string): Promise { - if (this.attempts >= this.maxAttempts) { - await this.deleteFile(filePath) - return - } - - const events = await this.loadEventsFromFile(filePath) - if (events.length === 0) { - await this.deleteFile(filePath) - return - } - - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `1P event logging: retrying ${events.length} events from previous batch`, - ) - } - - const failedEvents = await this.sendEventsInBatches(events) - if (failedEvents.length === 0) { - await this.deleteFile(filePath) - if (process.env.USER_TYPE === 'ant') { - logForDebugging('1P event logging: previous batch retry succeeded') - } - } else { - // Save only the failed events back (not all original events) - await this.saveEventsToFile(filePath, failedEvents) - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `1P event logging: previous batch retry failed, ${failedEvents.length} events remain`, - ) - } - } - } - - async export( - logs: ReadableLogRecord[], - resultCallback: (result: ExportResult) => void, - ): Promise { - if (this.isShutdown) { - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - '1P event logging export failed: Exporter has been shutdown', - ) - } - resultCallback({ - code: ExportResultCode.FAILED, - error: new Error('Exporter has been shutdown'), - }) - return - } - - const exportPromise = this.doExport(logs, resultCallback) - this.pendingExports.push(exportPromise) - - // Clean up completed exports - void exportPromise.finally(() => { - const index = this.pendingExports.indexOf(exportPromise) - if (index > -1) { - void this.pendingExports.splice(index, 1) - } - }) - } - - private async doExport( - logs: ReadableLogRecord[], - resultCallback: (result: ExportResult) => void, - ): Promise { - try { - // Filter for event logs only (by scope name) - const eventLogs = logs.filter( - log => - log.instrumentationScope?.name === 'com.anthropic.claude_code.events', - ) - - if (eventLogs.length === 0) { - resultCallback({ code: ExportResultCode.SUCCESS }) - return - } - - // Transform new logs (failed events are retried independently via backoff) - const events = this.transformLogsToEvents(eventLogs).events - - if (events.length === 0) { - resultCallback({ code: ExportResultCode.SUCCESS }) - return - } - - if (this.attempts >= this.maxAttempts) { - resultCallback({ - code: ExportResultCode.FAILED, - error: new Error( - `Dropped ${events.length} events: max attempts (${this.maxAttempts}) reached`, - ), - }) - return - } - - // Send events - const failedEvents = await this.sendEventsInBatches(events) - this.attempts++ - - if (failedEvents.length > 0) { - await this.queueFailedEvents(failedEvents) - this.scheduleBackoffRetry() - const context = this.lastExportErrorContext - ? ` (${this.lastExportErrorContext})` - : '' - resultCallback({ - code: ExportResultCode.FAILED, - error: new Error( - `Failed to export ${failedEvents.length} events${context}`, - ), - }) - return - } - - // Success - reset backoff and immediately retry any queued events - this.resetBackoff() - if ((await this.getQueuedEventCount()) > 0 && !this.isRetrying) { - void this.retryFailedEvents() - } - resultCallback({ code: ExportResultCode.SUCCESS }) - } catch (error) { - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `1P event logging export failed: ${errorMessage(error)}`, - ) - } - logError(error) - resultCallback({ - code: ExportResultCode.FAILED, - error: toError(error), - }) - } - } - - private async sendEventsInBatches( - events: FirstPartyEventLoggingEvent[], - ): Promise { - // Chunk events into batches - const batches: FirstPartyEventLoggingEvent[][] = [] - for (let i = 0; i < events.length; i += this.maxBatchSize) { - batches.push(events.slice(i, i + this.maxBatchSize)) - } - - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `1P event logging: exporting ${events.length} events in ${batches.length} batch(es)`, - ) - } - - // Send each batch with delay between them. On first failure, assume the - // endpoint is down and short-circuit: queue the failed batch plus all - // remaining unsent batches without POSTing them. The backoff retry will - // probe again with a single batch next tick. - const failedBatchEvents: FirstPartyEventLoggingEvent[] = [] - let lastErrorContext: string | undefined - for (let i = 0; i < batches.length; i++) { - const batch = batches[i]! - try { - await this.sendBatchWithRetry({ events: batch }) - } catch (error) { - lastErrorContext = getAxiosErrorContext(error) - for (let j = i; j < batches.length; j++) { - failedBatchEvents.push(...batches[j]!) - } - if (process.env.USER_TYPE === 'ant') { - const skipped = batches.length - 1 - i - logForDebugging( - `1P event logging: batch ${i + 1}/${batches.length} failed (${lastErrorContext}); short-circuiting ${skipped} remaining batch(es)`, - ) - } - break - } - - if (i < batches.length - 1 && this.batchDelayMs > 0) { - await sleep(this.batchDelayMs) - } - } - - if (failedBatchEvents.length > 0 && lastErrorContext) { - this.lastExportErrorContext = lastErrorContext - } - - return failedBatchEvents - } - - private async queueFailedEvents( - events: FirstPartyEventLoggingEvent[], - ): Promise { - const filePath = this.getCurrentBatchFilePath() - - // Append-only: just add new events to file (atomic on most filesystems) - await this.appendEventsToFile(filePath, events) - - const context = this.lastExportErrorContext - ? ` (${this.lastExportErrorContext})` - : '' - const message = `1P event logging: ${events.length} events failed to export${context}` - logError(new Error(message)) - } - - private scheduleBackoffRetry(): void { - // Don't schedule if already retrying or shutdown - if (this.cancelBackoff || this.isRetrying || this.isShutdown) { - return - } - - // Quadratic backoff (matching Statsig SDK): base * attempts² - const delay = Math.min( - this.baseBackoffDelayMs * this.attempts * this.attempts, - this.maxBackoffDelayMs, - ) - - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `1P event logging: scheduling backoff retry in ${delay}ms (attempt ${this.attempts})`, - ) - } - - this.cancelBackoff = this.schedule(async () => { - this.cancelBackoff = null - await this.retryFailedEvents() - }, delay) - } - - private async retryFailedEvents(): Promise { - const filePath = this.getCurrentBatchFilePath() - - // Keep retrying while there are events and endpoint is healthy - while (!this.isShutdown) { - const events = await this.loadEventsFromFile(filePath) - if (events.length === 0) break - - if (this.attempts >= this.maxAttempts) { - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `1P event logging: max attempts (${this.maxAttempts}) reached, dropping ${events.length} events`, - ) - } - await this.deleteFile(filePath) - this.resetBackoff() - return - } - - this.isRetrying = true - - // Clear file before retry (we have events in memory now) - await this.deleteFile(filePath) - - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `1P event logging: retrying ${events.length} failed events (attempt ${this.attempts + 1})`, - ) - } - - const failedEvents = await this.sendEventsInBatches(events) - this.attempts++ - - this.isRetrying = false - - if (failedEvents.length > 0) { - // Write failures back to disk - await this.saveEventsToFile(filePath, failedEvents) - this.scheduleBackoffRetry() - return // Failed - wait for backoff - } - - // Success - reset backoff and continue loop to drain any newly queued events - this.resetBackoff() - if (process.env.USER_TYPE === 'ant') { - logForDebugging('1P event logging: backoff retry succeeded') - } - } - } - - private resetBackoff(): void { - this.attempts = 0 - if (this.cancelBackoff) { - this.cancelBackoff() - this.cancelBackoff = null - } - } - - private async sendBatchWithRetry( - payload: FirstPartyEventLoggingPayload, - ): Promise { - if (this.isKilled()) { - // Throw so the caller short-circuits remaining batches and queues - // everything to disk. Zero network traffic while killed; the backoff - // timer keeps ticking and will resume POSTs as soon as the GrowthBook - // cache picks up the cleared flag. - throw new Error('firstParty sink killswitch active') - } - - const baseHeaders: Record = { - 'Content-Type': 'application/json', - 'User-Agent': getClaudeCodeUserAgent(), - 'x-service-name': 'claude-code', - } - - // Skip auth if trust hasn't been established yet - // This prevents executing apiKeyHelper commands before the trust dialog - // Non-interactive sessions implicitly have workspace trust - const hasTrust = - checkHasTrustDialogAccepted() || getIsNonInteractiveSession() - if (process.env.USER_TYPE === 'ant' && !hasTrust) { - logForDebugging('1P event logging: Trust not accepted') - } - - // Skip auth when the OAuth token is expired or lacks user:profile - // scope (service key sessions). Falls through to unauthenticated send. - let shouldSkipAuth = this.skipAuth || !hasTrust - if (!shouldSkipAuth && isClaudeAISubscriber()) { - const tokens = getClaudeAIOAuthTokens() - if (!hasProfileScope()) { - shouldSkipAuth = true - } else if (tokens && isOAuthTokenExpired(tokens.expiresAt)) { - shouldSkipAuth = true - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - '1P event logging: OAuth token expired, skipping auth to avoid 401', - ) - } - } - } - - // Try with auth headers first (unless trust not established or token is known to be expired) - const authResult = shouldSkipAuth - ? { headers: {}, error: 'trust not established or Oauth token expired' } - : getAuthHeaders() - const useAuth = !authResult.error - - if (!useAuth && process.env.USER_TYPE === 'ant') { - logForDebugging( - `1P event logging: auth not available, sending without auth`, - ) - } - - const headers = useAuth - ? { ...baseHeaders, ...authResult.headers } - : baseHeaders - - try { - const response = await axios.post(this.endpoint, payload, { - timeout: this.timeout, - headers, - }) - this.logSuccess(payload.events.length, useAuth, response.data) - return - } catch (error) { - // Handle 401 by retrying without auth - if ( - useAuth && - axios.isAxiosError(error) && - error.response?.status === 401 - ) { - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - '1P event logging: 401 auth error, retrying without auth', - ) - } - const response = await axios.post(this.endpoint, payload, { - timeout: this.timeout, - headers: baseHeaders, - }) - this.logSuccess(payload.events.length, false, response.data) - return - } - - throw error - } - } - - private logSuccess( - eventCount: number, - withAuth: boolean, - responseData: unknown, - ): void { - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `1P event logging: ${eventCount} events exported successfully${withAuth ? ' (with auth)' : ' (without auth)'}`, - ) - logForDebugging(`API Response: ${jsonStringify(responseData, null, 2)}`) - } - } - - private hrTimeToDate(hrTime: HrTime): Date { - const [seconds, nanoseconds] = hrTime - return new Date(seconds * 1000 + nanoseconds / 1000000) - } - - private transformLogsToEvents( - logs: ReadableLogRecord[], - ): FirstPartyEventLoggingPayload { - const events: FirstPartyEventLoggingEvent[] = [] - - for (const log of logs) { - const attributes = log.attributes || {} - - // Check if this is a GrowthBook experiment event - if (attributes.event_type === 'GrowthbookExperimentEvent') { - const timestamp = this.hrTimeToDate(log.hrTime) - const account_uuid = attributes.account_uuid as string | undefined - const organization_uuid = attributes.organization_uuid as - | string - | undefined - events.push({ - event_type: 'GrowthbookExperimentEvent', - event_data: GrowthbookExperimentEvent.toJSON({ - event_id: attributes.event_id as string, - timestamp, - experiment_id: attributes.experiment_id as string, - variation_id: attributes.variation_id as number, - environment: attributes.environment as string, - user_attributes: attributes.user_attributes as string, - experiment_metadata: attributes.experiment_metadata as string, - device_id: attributes.device_id as string, - session_id: attributes.session_id as string, - auth: - account_uuid || organization_uuid - ? { account_uuid, organization_uuid } - : undefined, - }), - }) - continue - } - - // Extract event name - const eventName = - (attributes.event_name as string) || (log.body as string) || 'unknown' - - // Extract metadata objects directly (no JSON parsing needed) - const coreMetadata = attributes.core_metadata as EventMetadata | undefined - const userMetadata = attributes.user_metadata as CoreUserData - const eventMetadata = (attributes.event_metadata || {}) as Record< - string, - unknown - > - - if (!coreMetadata) { - // Emit partial event if core metadata is missing - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `1P event logging: core_metadata missing for event ${eventName}`, - ) - } - events.push({ - event_type: 'ClaudeCodeInternalEvent', - event_data: ClaudeCodeInternalEvent.toJSON({ - event_id: attributes.event_id as string | undefined, - event_name: eventName, - client_timestamp: this.hrTimeToDate(log.hrTime), - session_id: getSessionId(), - additional_metadata: Buffer.from( - jsonStringify({ - transform_error: 'core_metadata attribute is missing', - }), - ).toString('base64'), - }), - }) - continue - } - - // Transform to 1P format - const formatted = to1PEventFormat( - coreMetadata, - userMetadata, - eventMetadata, - ) - - // _PROTO_* keys are PII-tagged values meant only for privileged BQ - // columns. Hoist known keys to proto fields, then defensively strip any - // remaining _PROTO_* so an unrecognized future key can't silently land - // in the general-access additional_metadata blob. sink.ts applies the - // same strip before Datadog; this closes the 1P side. - const { - _PROTO_skill_name, - _PROTO_plugin_name, - _PROTO_marketplace_name, - ...rest - } = formatted.additional - const additionalMetadata = stripProtoFields(rest) - - events.push({ - event_type: 'ClaudeCodeInternalEvent', - event_data: ClaudeCodeInternalEvent.toJSON({ - event_id: attributes.event_id as string | undefined, - event_name: eventName, - client_timestamp: this.hrTimeToDate(log.hrTime), - device_id: attributes.user_id as string | undefined, - email: userMetadata?.email, - auth: formatted.auth, - ...formatted.core, - env: formatted.env, - process: formatted.process, - skill_name: - typeof _PROTO_skill_name === 'string' - ? _PROTO_skill_name - : undefined, - plugin_name: - typeof _PROTO_plugin_name === 'string' - ? _PROTO_plugin_name - : undefined, - marketplace_name: - typeof _PROTO_marketplace_name === 'string' - ? _PROTO_marketplace_name - : undefined, - additional_metadata: - Object.keys(additionalMetadata).length > 0 - ? Buffer.from(jsonStringify(additionalMetadata)).toString( - 'base64', - ) - : undefined, - }), - }) - } - - return { events } - } - - async shutdown(): Promise { - this.isShutdown = true - this.resetBackoff() - await this.forceFlush() - if (process.env.USER_TYPE === 'ant') { - logForDebugging('1P event logging exporter shutdown complete') - } - } - - async forceFlush(): Promise { - await Promise.all(this.pendingExports) - if (process.env.USER_TYPE === 'ant') { - logForDebugging('1P event logging exporter flush complete') - } - } -} - -function getAxiosErrorContext(error: unknown): string { - if (!axios.isAxiosError(error)) { - return errorMessage(error) - } - - const parts: string[] = [] - - const requestId = error.response?.headers?.['request-id'] - if (requestId) { - parts.push(`request-id=${requestId}`) - } - - if (error.response?.status) { - parts.push(`status=${error.response.status}`) - } - - if (error.code) { - parts.push(`code=${error.code}`) - } - - if (error.message) { - parts.push(error.message) - } - - return parts.join(', ') -} diff --git a/services/analytics/growthbook.ts b/services/analytics/growthbook.ts index c71bba8..1d15c9e 100644 --- a/services/analytics/growthbook.ts +++ b/services/analytics/growthbook.ts @@ -1,1155 +1,36 @@ -import { GrowthBook } from '@growthbook/growthbook' -import { isEqual, memoize } from 'lodash-es' -import { - getIsNonInteractiveSession, - getSessionTrustAccepted, -} from '../../bootstrap/state.js' -import { getGrowthBookClientKey } from '../../constants/keys.js' -import { - checkHasTrustDialogAccepted, - getGlobalConfig, - saveGlobalConfig, -} from '../../utils/config.js' -import { logForDebugging } from '../../utils/debug.js' -import { toError } from '../../utils/errors.js' -import { getAuthHeaders } from '../../utils/http.js' -import { logError } from '../../utils/log.js' -import { createSignal } from '../../utils/signal.js' -import { jsonStringify } from '../../utils/slowOperations.js' -import { - type GitHubActionsMetadata, - getUserForGrowthBook, -} from '../../utils/user.js' -import { - is1PEventLoggingEnabled, - logGrowthBookExperimentTo1P, -} from './firstPartyEventLogger.js' - /** - * User attributes sent to GrowthBook for targeting. - * Uses UUID suffix (not Uuid) to align with GrowthBook conventions. + * GrowthBook Analytics Stub + * + * This file replaces the real GrowthBook integration to ensure all feature + * flag checks return their default values, disabling telemetry-driven + * feature toggles. */ -export type GrowthBookUserAttributes = { - id: string - sessionId: string - deviceID: string - platform: 'win32' | 'darwin' | 'linux' - apiBaseUrlHost?: string - organizationUUID?: string - accountUUID?: string - userType?: string - subscriptionType?: string - rateLimitTier?: string - firstTokenTime?: number - email?: string - appVersion?: string - github?: GitHubActionsMetadata + +export function hasGrowthBookEnvOverride(_key: string): boolean { + return false; } -/** - * Malformed feature response from API that uses "value" instead of "defaultValue". - * This is a workaround until the API is fixed. - */ -type MalformedFeatureDefinition = { - value?: unknown - defaultValue?: unknown - [key: string]: unknown +export async function initializeGrowthBook(): Promise { + // No-op } -let client: GrowthBook | null = null - -// Named handler refs so resetGrowthBook can remove them to prevent accumulation -let currentBeforeExitHandler: (() => void) | null = null -let currentExitHandler: (() => void) | null = null - -// Track whether auth was available when the client was created -// This allows us to detect when we need to recreate with fresh auth headers -let clientCreatedWithAuth = false - -// Store experiment data from payload for logging exposures later -type StoredExperimentData = { - experimentId: string - variationId: number - inExperiment?: boolean - hashAttribute?: string - hashValue?: string -} -const experimentDataByFeature = new Map() - -// Cache for remote eval feature values - workaround for SDK not respecting remoteEval response -// The SDK's setForcedFeatures also doesn't work reliably with remoteEval -const remoteEvalFeatureValues = new Map() - -// Track features accessed before init that need exposure logging -const pendingExposures = new Set() - -// Track features that have already had their exposure logged this session (dedup) -// This prevents firing duplicate exposure events when getFeatureValue_CACHED_MAY_BE_STALE -// is called repeatedly in hot paths (e.g., isAutoMemoryEnabled in render loops) -const loggedExposures = new Set() - -// Track re-initialization promise for security gate checks -// When GrowthBook is re-initializing (e.g., after auth change), security gate checks -// should wait for init to complete to avoid returning stale values -let reinitializingPromise: Promise | null = null - -// Listeners notified when GrowthBook feature values refresh (initial init or -// periodic refresh). Use for systems that bake feature values into long-lived -// objects at construction time (e.g. firstPartyEventLogger reads -// tengu_1p_event_batch_config once and builds a LoggerProvider with it) and -// need to rebuild when config changes. Per-call readers like -// getEventSamplingConfig / isSinkKilled don't need this — they're already -// reactive. -// -// NOT cleared by resetGrowthBook — subscribers register once (typically in -// init.ts) and must survive auth-change resets. -type GrowthBookRefreshListener = () => void | Promise -const refreshed = createSignal() - -/** Call a listener with sync-throw and async-rejection both routed to logError. */ -function callSafe(listener: GrowthBookRefreshListener): void { - try { - // Promise.resolve() normalizes sync returns and Promises so both - // sync throws (caught by outer try) and async rejections (caught - // by .catch) hit logError. Without the .catch, an async listener - // that rejects becomes an unhandled rejection — the try/catch - // only sees the Promise, not its eventual rejection. - void Promise.resolve(listener()).catch(e => { - logError(e) - }) - } catch (e) { - logError(e) - } -} - -/** - * Register a callback to fire when GrowthBook feature values refresh. - * Returns an unsubscribe function. - * - * If init has already completed with features by the time this is called - * (remoteEvalFeatureValues is populated), the listener fires once on the - * next microtask. This catch-up handles the race where GB's network response - * lands before the REPL's useEffect commits — on external builds with fast - * networks and MCP-heavy configs, init can finish in ~100ms while REPL mount - * takes ~600ms (see #20951 external-build trace at 30.540 vs 31.046). - * - * Change detection is on the subscriber: the callback fires on every refresh; - * use isEqual against your last-seen config to decide whether to act. - */ -export function onGrowthBookRefresh( - listener: GrowthBookRefreshListener, -): () => void { - let subscribed = true - const unsubscribe = refreshed.subscribe(() => callSafe(listener)) - if (remoteEvalFeatureValues.size > 0) { - queueMicrotask(() => { - // Re-check: listener may have been removed, or resetGrowthBook may have - // cleared the Map, between registration and this microtask running. - if (subscribed && remoteEvalFeatureValues.size > 0) { - callSafe(listener) - } - }) - } - return () => { - subscribed = false - unsubscribe() - } -} - -/** - * Parse env var overrides for GrowthBook features. - * Set CLAUDE_INTERNAL_FC_OVERRIDES to a JSON object mapping feature keys to values - * to bypass remote eval and disk cache. Useful for eval harnesses that need to - * test specific feature flag configurations. Only active when USER_TYPE is 'ant'. - * - * Example: CLAUDE_INTERNAL_FC_OVERRIDES='{"my_feature": true, "my_config": {"key": "val"}}' - */ -let envOverrides: Record | null = null -let envOverridesParsed = false - -function getEnvOverrides(): Record | null { - if (!envOverridesParsed) { - envOverridesParsed = true - if (process.env.USER_TYPE === 'ant') { - const raw = process.env.CLAUDE_INTERNAL_FC_OVERRIDES - if (raw) { - try { - envOverrides = JSON.parse(raw) as Record - logForDebugging( - `GrowthBook: Using env var overrides for ${Object.keys(envOverrides!).length} features: ${Object.keys(envOverrides!).join(', ')}`, - ) - } catch { - logError( - new Error( - `GrowthBook: Failed to parse CLAUDE_INTERNAL_FC_OVERRIDES: ${raw}`, - ), - ) - } - } - } - } - return envOverrides -} - -/** - * Check if a feature has an env-var override (CLAUDE_INTERNAL_FC_OVERRIDES). - * When true, _CACHED_MAY_BE_STALE will return the override without touching - * disk or network — callers can skip awaiting init for that feature. - */ -export function hasGrowthBookEnvOverride(feature: string): boolean { - const overrides = getEnvOverrides() - return overrides !== null && feature in overrides -} - -/** - * Local config overrides set via /config Gates tab (ant-only). Checked after - * env-var overrides — env wins so eval harnesses remain deterministic. Unlike - * getEnvOverrides this is not memoized: the user can change overrides at - * runtime, and getGlobalConfig() is already memory-cached (pointer-chase) - * until the next saveGlobalConfig() invalidates it. - */ -function getConfigOverrides(): Record | undefined { - if (process.env.USER_TYPE !== 'ant') return undefined - try { - return getGlobalConfig().growthBookOverrides - } catch { - // getGlobalConfig() throws before configReadingAllowed is set (early - // main.tsx startup path). Same degrade as the disk-cache fallback below. - return undefined - } -} - -/** - * Enumerate all known GrowthBook features and their current resolved values - * (not including overrides). In-memory payload first, disk cache fallback — - * same priority as the getters. Used by the /config Gates tab. - */ -export function getAllGrowthBookFeatures(): Record { - if (remoteEvalFeatureValues.size > 0) { - return Object.fromEntries(remoteEvalFeatureValues) - } - return getGlobalConfig().cachedGrowthBookFeatures ?? {} -} - -export function getGrowthBookConfigOverrides(): Record { - return getConfigOverrides() ?? {} -} - -/** - * Set or clear a single config override. Pass undefined to clear. - * Fires onGrowthBookRefresh listeners so systems that bake gate values into - * long-lived objects (useMainLoopModel, useSkillsChange, etc.) rebuild — - * otherwise overriding e.g. tengu_ant_model_override wouldn't actually - * change the model until the next periodic refresh. - */ -export function setGrowthBookConfigOverride( - feature: string, - value: unknown, -): void { - if (process.env.USER_TYPE !== 'ant') return - try { - saveGlobalConfig(c => { - const current = c.growthBookOverrides ?? {} - if (value === undefined) { - if (!(feature in current)) return c - const { [feature]: _, ...rest } = current - if (Object.keys(rest).length === 0) { - const { growthBookOverrides: __, ...configWithout } = c - return configWithout - } - return { ...c, growthBookOverrides: rest } - } - if (isEqual(current[feature], value)) return c - return { ...c, growthBookOverrides: { ...current, [feature]: value } } - }) - // Subscribers do their own change detection (see onGrowthBookRefresh docs), - // so firing on a no-op write is fine. - refreshed.emit() - } catch (e) { - logError(e) - } -} - -export function clearGrowthBookConfigOverrides(): void { - if (process.env.USER_TYPE !== 'ant') return - try { - saveGlobalConfig(c => { - if ( - !c.growthBookOverrides || - Object.keys(c.growthBookOverrides).length === 0 - ) { - return c - } - const { growthBookOverrides: _, ...rest } = c - return rest - }) - refreshed.emit() - } catch (e) { - logError(e) - } -} - -/** - * Log experiment exposure for a feature if it has experiment data. - * Deduplicates within a session - each feature is logged at most once. - */ -function logExposureForFeature(feature: string): void { - // Skip if already logged this session (dedup) - if (loggedExposures.has(feature)) { - return - } - - const expData = experimentDataByFeature.get(feature) - if (expData) { - loggedExposures.add(feature) - logGrowthBookExperimentTo1P({ - experimentId: expData.experimentId, - variationId: expData.variationId, - userAttributes: getUserAttributes(), - experimentMetadata: { - feature_id: feature, - }, - }) - } -} - -/** - * Process a remote eval payload from the GrowthBook server and populate - * local caches. Called after both initial client.init() and after - * client.refreshFeatures() so that _BLOCKS_ON_INIT callers see fresh values - * across the process lifetime, not just init-time snapshots. - * - * Without this running on refresh, remoteEvalFeatureValues freezes at its - * init-time snapshot and getDynamicConfig_BLOCKS_ON_INIT returns stale values - * for the entire process lifetime — which broke the tengu_max_version_config - * kill switch for long-running sessions. - */ -async function processRemoteEvalPayload( - gbClient: GrowthBook, -): Promise { - // WORKAROUND: Transform remote eval response format - // The API returns { "value": ... } but SDK expects { "defaultValue": ... } - // TODO: Remove this once the API is fixed to return correct format - const payload = gbClient.getPayload() - // Empty object is truthy — without the length check, `{features: {}}` - // (transient server bug, truncated response) would pass, clear the maps - // below, return true, and syncRemoteEvalToDisk would wholesale-write `{}` - // to disk: total flag blackout for every process sharing ~/.claude.json. - if (!payload?.features || Object.keys(payload.features).length === 0) { - return false - } - - // Clear before rebuild so features removed between refreshes don't - // leave stale ghost entries that short-circuit getFeatureValueInternal. - experimentDataByFeature.clear() - - const transformedFeatures: Record = {} - for (const [key, feature] of Object.entries(payload.features)) { - const f = feature as MalformedFeatureDefinition - if ('value' in f && !('defaultValue' in f)) { - transformedFeatures[key] = { - ...f, - defaultValue: f.value, - } - } else { - transformedFeatures[key] = f - } - - // Store experiment data for later logging when feature is accessed - if (f.source === 'experiment' && f.experimentResult) { - const expResult = f.experimentResult as { - variationId?: number - } - const exp = f.experiment as { key?: string } | undefined - if (exp?.key && expResult.variationId !== undefined) { - experimentDataByFeature.set(key, { - experimentId: exp.key, - variationId: expResult.variationId, - }) - } - } - } - // Re-set the payload with transformed features - await gbClient.setPayload({ - ...payload, - features: transformedFeatures, - }) - - // WORKAROUND: Cache the evaluated values directly from remote eval response. - // The SDK's evalFeature() tries to re-evaluate rules locally, ignoring the - // pre-evaluated 'value' from remoteEval. setForcedFeatures also doesn't work - // reliably. So we cache values ourselves and use them in getFeatureValueInternal. - remoteEvalFeatureValues.clear() - for (const [key, feature] of Object.entries(transformedFeatures)) { - // Under remoteEval:true the server pre-evaluates. Whether the answer - // lands in `value` (current API) or `defaultValue` (post-TODO API shape), - // it's the authoritative value for this user. Guarding on both keeps - // syncRemoteEvalToDisk correct across a partial or full API migration. - const v = 'value' in feature ? feature.value : feature.defaultValue - if (v !== undefined) { - remoteEvalFeatureValues.set(key, v) - } - } - return true -} - -/** - * Write the complete remoteEvalFeatureValues map to disk. Called exactly - * once per successful processRemoteEvalPayload — never from a failure path, - * so init-timeout poisoning is structurally impossible (the .catch() at init - * never reaches here). - * - * Wholesale replace (not merge): features deleted server-side are dropped - * from disk on the next successful payload. Ant builds ⊇ external, so - * switching builds is safe — the write is always a complete answer for this - * process's SDK key. - */ -function syncRemoteEvalToDisk(): void { - const fresh = Object.fromEntries(remoteEvalFeatureValues) - const config = getGlobalConfig() - if (isEqual(config.cachedGrowthBookFeatures, fresh)) { - return - } - saveGlobalConfig(current => ({ - ...current, - cachedGrowthBookFeatures: fresh, - })) -} - -/** - * Check if GrowthBook operations should be enabled - */ -function isGrowthBookEnabled(): boolean { - // GrowthBook depends on 1P event logging. - return is1PEventLoggingEnabled() -} - -/** - * Hostname of ANTHROPIC_BASE_URL when it points at a non-Anthropic proxy. - * - * Enterprise-proxy deployments (Epic, Marble, etc.) typically use - * apiKeyHelper auth, which means isAnthropicAuthEnabled() returns false and - * organizationUUID/accountUUID/email are all absent from GrowthBook - * attributes. Without this, there's no stable attribute to target them on - * — only per-device IDs. See src/utils/auth.ts isAnthropicAuthEnabled(). - * - * Returns undefined for unset/default (api.anthropic.com) so the attribute - * is absent for direct-API users. Hostname only — no path/query/creds. - */ -export function getApiBaseUrlHost(): string | undefined { - const baseUrl = process.env.ANTHROPIC_BASE_URL - if (!baseUrl) return undefined - try { - const host = new URL(baseUrl).host - if (host === 'api.anthropic.com') return undefined - return host - } catch { - return undefined - } -} - -/** - * Get user attributes for GrowthBook from CoreUserData - */ -function getUserAttributes(): GrowthBookUserAttributes { - const user = getUserForGrowthBook() - - // For ants, always try to include email from OAuth config even if ANTHROPIC_API_KEY is set. - // This ensures GrowthBook targeting by email works regardless of auth method. - let email = user.email - if (!email && process.env.USER_TYPE === 'ant') { - email = getGlobalConfig().oauthAccount?.emailAddress - } - - const apiBaseUrlHost = getApiBaseUrlHost() - - const attributes = { - id: user.deviceId, - sessionId: user.sessionId, - deviceID: user.deviceId, - platform: user.platform, - ...(apiBaseUrlHost && { apiBaseUrlHost }), - ...(user.organizationUuid && { organizationUUID: user.organizationUuid }), - ...(user.accountUuid && { accountUUID: user.accountUuid }), - ...(user.userType && { userType: user.userType }), - ...(user.subscriptionType && { subscriptionType: user.subscriptionType }), - ...(user.rateLimitTier && { rateLimitTier: user.rateLimitTier }), - ...(user.firstTokenTime && { firstTokenTime: user.firstTokenTime }), - ...(email && { email }), - ...(user.appVersion && { appVersion: user.appVersion }), - ...(user.githubActionsMetadata && { - githubActionsMetadata: user.githubActionsMetadata, - }), - } - return attributes -} - -/** - * Get or create the GrowthBook client instance - */ -const getGrowthBookClient = memoize( - (): { client: GrowthBook; initialized: Promise } | null => { - if (!isGrowthBookEnabled()) { - return null - } - - const attributes = getUserAttributes() - const clientKey = getGrowthBookClientKey() - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `GrowthBook: Creating client with clientKey=${clientKey}, attributes: ${jsonStringify(attributes)}`, - ) - } - const baseUrl = - process.env.USER_TYPE === 'ant' - ? process.env.CLAUDE_CODE_GB_BASE_URL || 'https://api.anthropic.com/' - : 'https://api.anthropic.com/' - - // Skip auth if trust hasn't been established yet - // This prevents executing apiKeyHelper commands before the trust dialog - // Non-interactive sessions implicitly have workspace trust - // getSessionTrustAccepted() covers the case where the TrustDialog auto-resolved - // without persisting trust for the specific CWD (e.g., home directory) — - // showSetupScreens() sets this after the trust dialog flow completes. - const hasTrust = - checkHasTrustDialogAccepted() || - getSessionTrustAccepted() || - getIsNonInteractiveSession() - const authHeaders = hasTrust - ? getAuthHeaders() - : { headers: {}, error: 'trust not established' } - const hasAuth = !authHeaders.error - clientCreatedWithAuth = hasAuth - - // Capture in local variable so the init callback operates on THIS client, - // not a later client if reinitialization happens before init completes - const thisClient = new GrowthBook({ - apiHost: baseUrl, - clientKey, - attributes, - remoteEval: true, - // Re-fetch when user ID or org changes (org change = login to different org) - cacheKeyAttributes: ['id', 'organizationUUID'], - // Add auth headers if available - ...(authHeaders.error - ? {} - : { apiHostRequestHeaders: authHeaders.headers }), - // Debug logging for Ants - ...(process.env.USER_TYPE === 'ant' - ? { - log: (msg: string, ctx: Record) => { - logForDebugging(`GrowthBook: ${msg} ${jsonStringify(ctx)}`) - }, - } - : {}), - }) - client = thisClient - - if (!hasAuth) { - // No auth available yet — skip HTTP init, rely on disk-cached values. - // initializeGrowthBook() will reset and re-create with auth when available. - return { client: thisClient, initialized: Promise.resolve() } - } - - const initialized = thisClient - .init({ timeout: 5000 }) - .then(async result => { - // Guard: if this client was replaced by a newer one, skip processing - if (client !== thisClient) { - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - 'GrowthBook: Skipping init callback for replaced client', - ) - } - return - } - - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `GrowthBook initialized successfully, source: ${result.source}, success: ${result.success}`, - ) - } - - const hadFeatures = await processRemoteEvalPayload(thisClient) - // Re-check: processRemoteEvalPayload yields at `await setPayload`. - // Microtask-only today (no encryption, no sticky-bucket service), but - // the guard at the top of this callback runs before that await; - // this runs after. - if (client !== thisClient) return - - if (hadFeatures) { - for (const feature of pendingExposures) { - logExposureForFeature(feature) - } - pendingExposures.clear() - syncRemoteEvalToDisk() - // Notify subscribers: remoteEvalFeatureValues is populated and - // disk is freshly synced. _CACHED_MAY_BE_STALE reads memory first - // (#22295), so subscribers see fresh values immediately. - refreshed.emit() - } - - // Log what features were loaded - if (process.env.USER_TYPE === 'ant') { - const features = thisClient.getFeatures() - if (features) { - const featureKeys = Object.keys(features) - logForDebugging( - `GrowthBook loaded ${featureKeys.length} features: ${featureKeys.slice(0, 10).join(', ')}${featureKeys.length > 10 ? '...' : ''}`, - ) - } - } - }) - .catch(error => { - if (process.env.USER_TYPE === 'ant') { - logError(toError(error)) - } - }) - - // Register cleanup handlers for graceful shutdown (named refs so resetGrowthBook can remove them) - currentBeforeExitHandler = () => client?.destroy() - currentExitHandler = () => client?.destroy() - process.on('beforeExit', currentBeforeExitHandler) - process.on('exit', currentExitHandler) - - return { client: thisClient, initialized } - }, -) - -/** - * Initialize GrowthBook client (blocks until ready) - */ -export const initializeGrowthBook = memoize( - async (): Promise => { - let clientWrapper = getGrowthBookClient() - if (!clientWrapper) { - return null - } - - // Check if auth has become available since the client was created - // If so, we need to recreate the client with fresh auth headers - // Only check if trust is established to avoid triggering apiKeyHelper before trust dialog - if (!clientCreatedWithAuth) { - const hasTrust = - checkHasTrustDialogAccepted() || - getSessionTrustAccepted() || - getIsNonInteractiveSession() - if (hasTrust) { - const currentAuth = getAuthHeaders() - if (!currentAuth.error) { - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - 'GrowthBook: Auth became available after client creation, reinitializing', - ) - } - // Use resetGrowthBook to properly destroy old client and stop periodic refresh - // This prevents double-init where old client's init promise continues running - resetGrowthBook() - clientWrapper = getGrowthBookClient() - if (!clientWrapper) { - return null - } - } - } - } - - await clientWrapper.initialized - - // Set up periodic refresh after successful initialization - // This is called here (not separately) so it's always re-established after any reinit - setupPeriodicGrowthBookRefresh() - - return clientWrapper.client - }, -) - -/** - * Get a feature value with a default fallback - blocks until initialized. - * @internal Used by both deprecated and cached functions. - */ -async function getFeatureValueInternal( - feature: string, - defaultValue: T, - logExposure: boolean, -): Promise { - // Check env var overrides first (for eval harnesses) - const overrides = getEnvOverrides() - if (overrides && feature in overrides) { - return overrides[feature] as T - } - const configOverrides = getConfigOverrides() - if (configOverrides && feature in configOverrides) { - return configOverrides[feature] as T - } - - if (!isGrowthBookEnabled()) { - return defaultValue - } - - const growthBookClient = await initializeGrowthBook() - if (!growthBookClient) { - return defaultValue - } - - // Use cached remote eval values if available (workaround for SDK bug) - let result: T - if (remoteEvalFeatureValues.has(feature)) { - result = remoteEvalFeatureValues.get(feature) as T - } else { - result = growthBookClient.getFeatureValue(feature, defaultValue) as T - } - - // Log experiment exposure using stored experiment data - if (logExposure) { - logExposureForFeature(feature) - } - - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - `GrowthBook: getFeatureValue("${feature}") = ${jsonStringify(result)}`, - ) - } - return result -} - -/** - * @deprecated Use getFeatureValue_CACHED_MAY_BE_STALE instead, which is non-blocking. - * This function blocks on GrowthBook initialization which can slow down startup. - */ -export async function getFeatureValue_DEPRECATED( - feature: string, - defaultValue: T, -): Promise { - return getFeatureValueInternal(feature, defaultValue, true) -} - -/** - * Get a feature value from disk cache immediately. Pure read — disk is - * populated by syncRemoteEvalToDisk on every successful payload (init + - * periodic refresh), not by this function. - * - * This is the preferred method for startup-critical paths and sync contexts. - * The value may be stale if the cache was written by a previous process. - */ -export function getFeatureValue_CACHED_MAY_BE_STALE( - feature: string, - defaultValue: T, -): T { - // Check env var overrides first (for eval harnesses) - const overrides = getEnvOverrides() - if (overrides && feature in overrides) { - return overrides[feature] as T - } - const configOverrides = getConfigOverrides() - if (configOverrides && feature in configOverrides) { - return configOverrides[feature] as T - } - - if (!isGrowthBookEnabled()) { - return defaultValue - } - - // Log experiment exposure if data is available, otherwise defer until after init - if (experimentDataByFeature.has(feature)) { - logExposureForFeature(feature) - } else { - pendingExposures.add(feature) - } - - // In-memory payload is authoritative once processRemoteEvalPayload has run. - // Disk is also fresh by then (syncRemoteEvalToDisk runs synchronously inside - // init), so this is correctness-equivalent to the disk read below — but it - // skips the config JSON parse and is what onGrowthBookRefresh subscribers - // depend on to read fresh values the instant they're notified. - if (remoteEvalFeatureValues.has(feature)) { - return remoteEvalFeatureValues.get(feature) as T - } - - // Fall back to disk cache (survives across process restarts) - try { - const cached = getGlobalConfig().cachedGrowthBookFeatures?.[feature] - return cached !== undefined ? (cached as T) : defaultValue - } catch { - return defaultValue - } -} - -/** - * @deprecated Disk cache is now synced on every successful payload load - * (init + 20min/6h periodic refresh). The per-feature TTL never fetched - * fresh data from the server — it only re-wrote in-memory state to disk, - * which is now redundant. Use getFeatureValue_CACHED_MAY_BE_STALE directly. - */ -export function getFeatureValue_CACHED_WITH_REFRESH( - feature: string, - defaultValue: T, - _refreshIntervalMs: number, -): T { - return getFeatureValue_CACHED_MAY_BE_STALE(feature, defaultValue) -} - -/** - * Check a Statsig feature gate value via GrowthBook, with fallback to Statsig cache. - * - * **MIGRATION ONLY**: This function is for migrating existing Statsig gates to GrowthBook. - * For new features, use `getFeatureValue_CACHED_MAY_BE_STALE()` instead. - * - * - Checks GrowthBook disk cache first - * - Falls back to Statsig's cachedStatsigGates during migration - * - The value may be stale if the cache hasn't been updated recently - * - * @deprecated Use getFeatureValue_CACHED_MAY_BE_STALE() for new code. This function - * exists only to support migration of existing Statsig gates. - */ -export function checkStatsigFeatureGate_CACHED_MAY_BE_STALE( - gate: string, -): boolean { - // Check env var overrides first (for eval harnesses) - const overrides = getEnvOverrides() - if (overrides && gate in overrides) { - return Boolean(overrides[gate]) - } - const configOverrides = getConfigOverrides() - if (configOverrides && gate in configOverrides) { - return Boolean(configOverrides[gate]) - } - - if (!isGrowthBookEnabled()) { - return false - } - - // Log experiment exposure if data is available, otherwise defer until after init - if (experimentDataByFeature.has(gate)) { - logExposureForFeature(gate) - } else { - pendingExposures.add(gate) - } - - // Return cached value immediately from disk - // First check GrowthBook cache, then fall back to Statsig cache for migration - const config = getGlobalConfig() - const gbCached = config.cachedGrowthBookFeatures?.[gate] - if (gbCached !== undefined) { - return Boolean(gbCached) - } - // Fallback to Statsig cache for migration period - return config.cachedStatsigGates?.[gate] ?? false -} - -/** - * Check a security restriction gate, waiting for re-init if in progress. - * - * Use this for security-critical gates where we need fresh values after auth changes. - * - * Behavior: - * - If GrowthBook is re-initializing (e.g., after login), waits for it to complete - * - Otherwise, returns cached value immediately (Statsig cache first, then GrowthBook) - * - * Statsig cache is checked first as a safety measure for security-related checks: - * if the Statsig cache indicates the gate is enabled, we honor it. - */ -export async function checkSecurityRestrictionGate( - gate: string, -): Promise { - // Check env var overrides first (for eval harnesses) - const overrides = getEnvOverrides() - if (overrides && gate in overrides) { - return Boolean(overrides[gate]) - } - const configOverrides = getConfigOverrides() - if (configOverrides && gate in configOverrides) { - return Boolean(configOverrides[gate]) - } - - if (!isGrowthBookEnabled()) { - return false - } - - // If re-initialization is in progress, wait for it to complete - // This ensures we get fresh values after auth changes - if (reinitializingPromise) { - await reinitializingPromise - } - - // Check Statsig cache first - it may have correct value from previous logged-in session - const config = getGlobalConfig() - const statsigCached = config.cachedStatsigGates?.[gate] - if (statsigCached !== undefined) { - return Boolean(statsigCached) - } - - // Then check GrowthBook cache - const gbCached = config.cachedGrowthBookFeatures?.[gate] - if (gbCached !== undefined) { - return Boolean(gbCached) - } - - // No cache - return false (don't block on init for uncached gates) - return false -} - -/** - * Check a boolean entitlement gate with fallback-to-blocking semantics. - * - * Fast path: if the disk cache already says `true`, return it immediately. - * Slow path: if disk says `false`/missing, await GrowthBook init and fetch the - * fresh server value (max ~5s). Disk is populated by syncRemoteEvalToDisk - * inside init, so by the time the slow path returns, disk already has the - * fresh value — no write needed here. - * - * Use for user-invoked features (e.g. /remote-control) that are gated on - * subscription/org, where a stale `false` would unfairly block access but a - * stale `true` is acceptable (the server is the real gatekeeper). - */ -export async function checkGate_CACHED_OR_BLOCKING( - gate: string, -): Promise { - // Check env var overrides first (for eval harnesses) - const overrides = getEnvOverrides() - if (overrides && gate in overrides) { - return Boolean(overrides[gate]) - } - const configOverrides = getConfigOverrides() - if (configOverrides && gate in configOverrides) { - return Boolean(configOverrides[gate]) - } - - if (!isGrowthBookEnabled()) { - return false - } - - // Fast path: disk cache already says true — trust it - const cached = getGlobalConfig().cachedGrowthBookFeatures?.[gate] - if (cached === true) { - // Log experiment exposure if data is available, otherwise defer - if (experimentDataByFeature.has(gate)) { - logExposureForFeature(gate) - } else { - pendingExposures.add(gate) - } - return true - } - - // Slow path: disk says false/missing — may be stale, fetch fresh - return getFeatureValueInternal(gate, false, true) -} - -/** - * Refresh GrowthBook after auth changes (login/logout). - * - * NOTE: This must destroy and recreate the client because GrowthBook's - * apiHostRequestHeaders cannot be updated after client creation. - */ export function refreshGrowthBookAfterAuthChange(): void { - if (!isGrowthBookEnabled()) { - return - } - - try { - // Reset the client completely to get fresh auth headers - // This is necessary because apiHostRequestHeaders can't be updated after creation - resetGrowthBook() - - // resetGrowthBook cleared remoteEvalFeatureValues. If re-init below - // times out (hadFeatures=false) or short-circuits on !hasAuth (logout), - // the init-callback notify never fires — subscribers stay synced to the - // previous account's memoized state. Notify here so they re-read now - // (falls to disk cache). If re-init succeeds, they'll notify again with - // fresh values; if not, at least they're synced to the post-reset state. - refreshed.emit() - - // Reinitialize with fresh auth headers and attributes - // Track this promise so security gate checks can wait for it. - // .catch before .finally: initializeGrowthBook can reject if its sync - // helpers throw (getGrowthBookClient, getAuthHeaders, resetGrowthBook — - // clientWrapper.initialized itself has its own .catch so never rejects), - // and .finally re-settles with the original rejection — the sync - // try/catch below cannot catch async rejections. - reinitializingPromise = initializeGrowthBook() - .catch(error => { - logError(toError(error)) - return null - }) - .finally(() => { - reinitializingPromise = null - }) - } catch (error) { - if (process.env.NODE_ENV === 'development') { - throw error - } - logError(toError(error)) - } + // No-op } -/** - * Reset GrowthBook client state (primarily for testing) - */ -export function resetGrowthBook(): void { - stopPeriodicGrowthBookRefresh() - // Remove process handlers before destroying client to prevent accumulation - if (currentBeforeExitHandler) { - process.off('beforeExit', currentBeforeExitHandler) - currentBeforeExitHandler = null - } - if (currentExitHandler) { - process.off('exit', currentExitHandler) - currentExitHandler = null - } - client?.destroy() - client = null - clientCreatedWithAuth = false - reinitializingPromise = null - experimentDataByFeature.clear() - pendingExposures.clear() - loggedExposures.clear() - remoteEvalFeatureValues.clear() - getGrowthBookClient.cache?.clear?.() - initializeGrowthBook.cache?.clear?.() - envOverrides = null - envOverridesParsed = false +export function getFeatureValue_CACHED_MAY_BE_STALE(key: string, defaultValue: T): T { + // Always return the default value to keep the application stable without remote flags. + return defaultValue; } -// Periodic refresh interval (matches Statsig's 6-hour interval) -const GROWTHBOOK_REFRESH_INTERVAL_MS = - process.env.USER_TYPE !== 'ant' - ? 6 * 60 * 60 * 1000 // 6 hours - : 20 * 60 * 1000 // 20 min (for ants) -let refreshInterval: ReturnType | null = null -let beforeExitListener: (() => void) | null = null - -/** - * Light refresh - re-fetch features from server without recreating client. - * Use this for periodic refresh when auth headers haven't changed. - * - * Unlike refreshGrowthBookAfterAuthChange() which destroys and recreates the client, - * this preserves client state and just fetches fresh feature values. - */ -export async function refreshGrowthBookFeatures(): Promise { - if (!isGrowthBookEnabled()) { - return - } - - try { - const growthBookClient = await initializeGrowthBook() - if (!growthBookClient) { - return - } - - await growthBookClient.refreshFeatures() - - // Guard: if this client was replaced during the in-flight refresh - // (e.g. refreshGrowthBookAfterAuthChange ran), skip processing the - // stale payload. Mirrors the init-callback guard above. - if (growthBookClient !== client) { - if (process.env.USER_TYPE === 'ant') { - logForDebugging( - 'GrowthBook: Skipping refresh processing for replaced client', - ) - } - return - } - - // Rebuild remoteEvalFeatureValues from the refreshed payload so that - // _BLOCKS_ON_INIT callers (e.g. getMaxVersion for the auto-update kill - // switch) see fresh values, not the stale init-time snapshot. - const hadFeatures = await processRemoteEvalPayload(growthBookClient) - // Same re-check as init path: covers the setPayload yield inside - // processRemoteEvalPayload (the guard above only covers refreshFeatures). - if (growthBookClient !== client) return - - if (process.env.USER_TYPE === 'ant') { - logForDebugging('GrowthBook: Light refresh completed') - } - - // Gate on hadFeatures: if the payload was empty/malformed, - // remoteEvalFeatureValues wasn't rebuilt — skip both the no-op disk - // write and the spurious subscriber churn (clearCommandMemoizationCaches - // + getCommands + 4× model re-renders). - if (hadFeatures) { - syncRemoteEvalToDisk() - refreshed.emit() - } - } catch (error) { - if (process.env.NODE_ENV === 'development') { - throw error - } - logError(toError(error)) - } +export function getFeatureValue_BLOCKING(key: string, defaultValue: T): T { + return defaultValue; } -/** - * Set up periodic refresh of GrowthBook features. - * Uses light refresh (refreshGrowthBookFeatures) to re-fetch without recreating client. - * - * Call this after initialization for long-running sessions to ensure - * feature values stay fresh. Matches Statsig's 6-hour refresh interval. - */ -export function setupPeriodicGrowthBookRefresh(): void { - if (!isGrowthBookEnabled()) { - return - } - - // Clear any existing interval to avoid duplicates - if (refreshInterval) { - clearInterval(refreshInterval) - } - - refreshInterval = setInterval(() => { - void refreshGrowthBookFeatures() - }, GROWTHBOOK_REFRESH_INTERVAL_MS) - // Allow process to exit naturally - this timer shouldn't keep the process alive - refreshInterval.unref?.() - - // Register cleanup listener only once - if (!beforeExitListener) { - beforeExitListener = () => { - stopPeriodicGrowthBookRefresh() - } - process.once('beforeExit', beforeExitListener) - } +export function checkGate_CACHED_MAY_BE_STALE(key: string, defaultValue = false): boolean { + return defaultValue; } -/** - * Stop periodic refresh (for testing or cleanup) - */ -export function stopPeriodicGrowthBookRefresh(): void { - if (refreshInterval) { - clearInterval(refreshInterval) - refreshInterval = null - } - if (beforeExitListener) { - process.removeListener('beforeExit', beforeExitListener) - beforeExitListener = null - } -} - -// ============================================================================ -// Dynamic Config Functions -// These are semantic wrappers around feature functions for Statsig API parity. -// In GrowthBook, dynamic configs are just features with object values. -// ============================================================================ - -/** - * Get a dynamic config value - blocks until GrowthBook is initialized. - * Prefer getFeatureValue_CACHED_MAY_BE_STALE for startup-critical paths. - */ -export async function getDynamicConfig_BLOCKS_ON_INIT( - configName: string, - defaultValue: T, -): Promise { - return getFeatureValue_DEPRECATED(configName, defaultValue) -} - -/** - * Get a dynamic config value from disk cache immediately. Pure read — see - * getFeatureValue_CACHED_MAY_BE_STALE. - * This is the preferred method for startup-critical paths and sync contexts. - * - * In GrowthBook, dynamic configs are just features with object values. - */ -export function getDynamicConfig_CACHED_MAY_BE_STALE( - configName: string, - defaultValue: T, -): T { - return getFeatureValue_CACHED_MAY_BE_STALE(configName, defaultValue) +export function checkGate_BLOCKING(key: string, defaultValue = false): boolean { + return defaultValue; } diff --git a/services/analytics/index.ts b/services/analytics/index.ts index 30d2e59..eb759ba 100644 --- a/services/analytics/index.ts +++ b/services/analytics/index.ts @@ -1,74 +1,21 @@ /** - * Analytics service - public API for event logging + * Analytics service - stub implementation * - * This module serves as the main entry point for analytics events in Claude CLI. - * - * DESIGN: This module has NO dependencies to avoid import cycles. - * Events are queued until attachAnalyticsSink() is called during app initialization. - * The sink handles routing to Datadog and 1P event logging. + * This module has been modified to disable all telemetry and monitoring as per user request. + * It maintains the original interface to avoid breaking the codebase, but all logging is a no-op. */ -/** - * Marker type for verifying analytics metadata doesn't contain sensitive data - * - * This type forces explicit verification that string values being logged - * don't contain code snippets, file paths, or other sensitive information. - * - * Usage: `myString as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS` - */ export type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS = never - -/** - * Marker type for values routed to PII-tagged proto columns via `_PROTO_*` - * payload keys. The destination BQ column has privileged access controls, - * so unredacted values are acceptable — unlike general-access backends. - * - * sink.ts strips `_PROTO_*` keys before Datadog fanout; only the 1P - * exporter (firstPartyEventLoggingExporter) sees them and hoists them to the - * top-level proto field. A single stripProtoFields call guards all non-1P - * sinks — no per-sink filtering to forget. - * - * Usage: `rawName as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED` - */ export type AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED = never -/** - * Strip `_PROTO_*` keys from a payload destined for general-access storage. - * Used by: - * - sink.ts: before Datadog fanout (never sees PII-tagged values) - * - firstPartyEventLoggingExporter: defensive strip of additional_metadata - * after hoisting known _PROTO_* keys to proto fields — prevents a future - * unrecognized _PROTO_foo from silently landing in the BQ JSON blob. - * - * Returns the input unchanged (same reference) when no _PROTO_ keys present. - */ export function stripProtoFields( metadata: Record, ): Record { - let result: Record | undefined - for (const key in metadata) { - if (key.startsWith('_PROTO_')) { - if (result === undefined) { - result = { ...metadata } - } - delete result[key] - } - } - return result ?? metadata + return metadata } -// Internal type for logEvent metadata - different from the enriched EventMetadata in metadata.ts type LogEventMetadata = { [key: string]: boolean | number | undefined } -type QueuedEvent = { - eventName: string - metadata: LogEventMetadata - async: boolean -} - -/** - * Sink interface for the analytics backend - */ export type AnalyticsSink = { logEvent: (eventName: string, metadata: LogEventMetadata) => void logEventAsync: ( @@ -77,97 +24,24 @@ export type AnalyticsSink = { ) => Promise } -// Event queue for events logged before sink is attached -const eventQueue: QueuedEvent[] = [] - -// Sink - initialized during app startup -let sink: AnalyticsSink | null = null - -/** - * Attach the analytics sink that will receive all events. - * Queued events are drained asynchronously via queueMicrotask to avoid - * adding latency to the startup path. - * - * Idempotent: if a sink is already attached, this is a no-op. This allows - * calling from both the preAction hook (for subcommands) and setup() (for - * the default command) without coordination. - */ -export function attachAnalyticsSink(newSink: AnalyticsSink): void { - if (sink !== null) { - return - } - sink = newSink - - // Drain the queue asynchronously to avoid blocking startup - if (eventQueue.length > 0) { - const queuedEvents = [...eventQueue] - eventQueue.length = 0 - - // Log queue size for ants to help debug analytics initialization timing - if (process.env.USER_TYPE === 'ant') { - sink.logEvent('analytics_sink_attached', { - queued_event_count: queuedEvents.length, - }) - } - - queueMicrotask(() => { - for (const event of queuedEvents) { - if (event.async) { - void sink!.logEventAsync(event.eventName, event.metadata) - } else { - sink!.logEvent(event.eventName, event.metadata) - } - } - }) - } +export function attachAnalyticsSink(_newSink: AnalyticsSink): void { + // No-op: Analytics is disabled. } -/** - * Log an event to analytics backends (synchronous) - * - * Events may be sampled based on the 'tengu_event_sampling_config' dynamic config. - * When sampled, the sample_rate is added to the event metadata. - * - * If no sink is attached, events are queued and drained when the sink attaches. - */ export function logEvent( - eventName: string, - // intentionally no strings unless AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, - // to avoid accidentally logging code/filepaths - metadata: LogEventMetadata, + _eventName: string, + _metadata: LogEventMetadata, ): void { - if (sink === null) { - eventQueue.push({ eventName, metadata, async: false }) - return - } - sink.logEvent(eventName, metadata) + // No-op: Analytics is disabled. } -/** - * Log an event to analytics backends (asynchronous) - * - * Events may be sampled based on the 'tengu_event_sampling_config' dynamic config. - * When sampled, the sample_rate is added to the event metadata. - * - * If no sink is attached, events are queued and drained when the sink attaches. - */ export async function logEventAsync( - eventName: string, - // intentionally no strings, to avoid accidentally logging code/filepaths - metadata: LogEventMetadata, + _eventName: string, + _metadata: LogEventMetadata, ): Promise { - if (sink === null) { - eventQueue.push({ eventName, metadata, async: true }) - return - } - await sink.logEventAsync(eventName, metadata) + // No-op: Analytics is disabled. } -/** - * Reset analytics state for testing purposes only. - * @internal - */ export function _resetForTesting(): void { - sink = null - eventQueue.length = 0 + // No-op. } diff --git a/services/analytics/metadata.ts b/services/analytics/metadata.ts index b83e96a..5d46923 100644 --- a/services/analytics/metadata.ts +++ b/services/analytics/metadata.ts @@ -564,10 +564,12 @@ function getAgentIdentification(): { * Extract base version from full version string. "2.0.36-dev.20251107.t174150.sha2709699" → "2.0.36-dev" */ const getVersionBase = memoize((): string | undefined => { - const match = MACRO.VERSION.match(/^\d+\.\d+\.\d+(?:-[a-z]+)?/) + const match = VERSION.match(/^\d+\.\d+\.\d+(?:-[a-z]+)?/) return match ? match[0] : undefined }) +import { VERSION, BUILD_TIME } from '../../constants/product.js' + /** * Builds the environment context object */ @@ -617,9 +619,9 @@ const buildEnvContext = memoize(async (): Promise => { isGithubAction: isEnvTruthy(process.env.GITHUB_ACTIONS), isClaudeCodeAction: isEnvTruthy(process.env.CLAUDE_CODE_ACTION), isClaudeAiAuth: isClaudeAISubscriber(), - version: MACRO.VERSION, + version: VERSION, versionBase: getVersionBase(), - buildTime: MACRO.BUILD_TIME, + buildTime: BUILD_TIME, deploymentEnvironment: env.detectDeploymentEnvironment(), ...(isEnvTruthy(process.env.GITHUB_ACTIONS) && { githubEventName: process.env.GITHUB_EVENT_NAME, diff --git a/services/analytics/sink.ts b/services/analytics/sink.ts deleted file mode 100644 index a7b7021..0000000 --- a/services/analytics/sink.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Analytics sink implementation - * - * This module contains the actual analytics routing logic and should be - * initialized during app startup. It routes events to Datadog and 1P event - * logging. - * - * Usage: Call initializeAnalyticsSink() during app startup to attach the sink. - */ - -import { trackDatadogEvent } from './datadog.js' -import { logEventTo1P, shouldSampleEvent } from './firstPartyEventLogger.js' -import { checkStatsigFeatureGate_CACHED_MAY_BE_STALE } from './growthbook.js' -import { attachAnalyticsSink, stripProtoFields } from './index.js' -import { isSinkKilled } from './sinkKillswitch.js' - -// Local type matching the logEvent metadata signature -type LogEventMetadata = { [key: string]: boolean | number | undefined } - -const DATADOG_GATE_NAME = 'tengu_log_datadog_events' - -// Module-level gate state - starts undefined, initialized during startup -let isDatadogGateEnabled: boolean | undefined = undefined - -/** - * Check if Datadog tracking is enabled. - * Falls back to cached value from previous session if not yet initialized. - */ -function shouldTrackDatadog(): boolean { - if (isSinkKilled('datadog')) { - return false - } - if (isDatadogGateEnabled !== undefined) { - return isDatadogGateEnabled - } - - // Fallback to cached value from previous session - try { - return checkStatsigFeatureGate_CACHED_MAY_BE_STALE(DATADOG_GATE_NAME) - } catch { - return false - } -} - -/** - * Log an event (synchronous implementation) - */ -function logEventImpl(eventName: string, metadata: LogEventMetadata): void { - // Check if this event should be sampled - const sampleResult = shouldSampleEvent(eventName) - - // If sample result is 0, the event was not selected for logging - if (sampleResult === 0) { - return - } - - // If sample result is a positive number, add it to metadata - const metadataWithSampleRate = - sampleResult !== null - ? { ...metadata, sample_rate: sampleResult } - : metadata - - if (shouldTrackDatadog()) { - // Datadog is a general-access backend — strip _PROTO_* keys - // (unredacted PII-tagged values meant only for the 1P privileged column). - void trackDatadogEvent(eventName, stripProtoFields(metadataWithSampleRate)) - } - - // 1P receives the full payload including _PROTO_* — the exporter - // destructures and routes those keys to proto fields itself. - logEventTo1P(eventName, metadataWithSampleRate) -} - -/** - * Log an event (asynchronous implementation) - * - * With Segment removed the two remaining sinks are fire-and-forget, so this - * just wraps the sync impl — kept to preserve the sink interface contract. - */ -function logEventAsyncImpl( - eventName: string, - metadata: LogEventMetadata, -): Promise { - logEventImpl(eventName, metadata) - return Promise.resolve() -} - -/** - * Initialize analytics gates during startup. - * - * Updates gate values from server. Early events use cached values from previous - * session to avoid data loss during initialization. - * - * Called from main.tsx during setupBackend(). - */ -export function initializeAnalyticsGates(): void { - isDatadogGateEnabled = - checkStatsigFeatureGate_CACHED_MAY_BE_STALE(DATADOG_GATE_NAME) -} - -/** - * Initialize the analytics sink. - * - * Call this during app startup to attach the analytics backend. - * Any events logged before this is called will be queued and drained. - * - * Idempotent: safe to call multiple times (subsequent calls are no-ops). - */ -export function initializeAnalyticsSink(): void { - attachAnalyticsSink({ - logEvent: logEventImpl, - logEventAsync: logEventAsyncImpl, - }) -} diff --git a/services/analytics/sinkKillswitch.ts b/services/analytics/sinkKillswitch.ts index 8875758..4fbea0e 100644 --- a/services/analytics/sinkKillswitch.ts +++ b/services/analytics/sinkKillswitch.ts @@ -1,7 +1,4 @@ -import { getDynamicConfig_CACHED_MAY_BE_STALE } from './growthbook.js' -// Mangled name: per-sink analytics killswitch -const SINK_KILLSWITCH_CONFIG_NAME = 'tengu_frond_boric' export type SinkName = 'datadog' | 'firstParty' @@ -15,11 +12,7 @@ export type SinkName = 'datadog' | 'firstParty' * growthbook.ts:isGrowthBookEnabled() calls that, so a lookup here would recurse. * Call at per-event dispatch sites instead. */ -export function isSinkKilled(sink: SinkName): boolean { - const config = getDynamicConfig_CACHED_MAY_BE_STALE< - Partial> - >(SINK_KILLSWITCH_CONFIG_NAME, {}) - // getFeatureValue_CACHED_MAY_BE_STALE guards on `!== undefined`, so a - // cached JSON null leaks through instead of falling back to {}. - return config?.[sink] === true +export function isSinkKilled(_sink: SinkName): boolean { + // Permanently disabled as per telemetry purge requirement. + return true } diff --git a/services/api/adminRequests.ts b/services/api/adminRequests.ts index f3e67d9..fb2b660 100644 --- a/services/api/adminRequests.ts +++ b/services/api/adminRequests.ts @@ -1,6 +1,6 @@ -import axios from 'axios' import { getOauthConfig } from '../../constants/oauth.js' import { getOAuthHeaders, prepareApiRequest } from '../../utils/teleport/api.js' +import { nativeRequest } from '../../utils/http.js' export type AdminRequestType = 'limit_increase' | 'seat_upgrade' @@ -58,7 +58,11 @@ export async function createAdminRequest( const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/admin_requests` - const response = await axios.post(url, params, { headers }) + const response = await nativeRequest(url, { + method: 'POST', + body: params, + headers, + }) return response.data } @@ -84,7 +88,8 @@ export async function getMyAdminRequests( url += `&statuses=${status}` } - const response = await axios.get(url, { + const response = await nativeRequest(url, { + method: 'GET', headers, }) @@ -111,7 +116,8 @@ export async function checkAdminRequestEligibility( const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/admin_requests/eligibility?request_type=${requestType}` - const response = await axios.get(url, { + const response = await nativeRequest(url, { + method: 'GET', headers, }) diff --git a/services/api/bootstrap.ts b/services/api/bootstrap.ts index 82ef0d6..4c6b75f 100644 --- a/services/api/bootstrap.ts +++ b/services/api/bootstrap.ts @@ -1,4 +1,3 @@ -import axios from 'axios' import isEqual from 'lodash-es/isEqual.js' import { getAnthropicApiKey, @@ -9,7 +8,7 @@ import { z } from 'zod' import { getOauthConfig, OAUTH_BETA_HEADER } from '../../constants/oauth.js' import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' import { logForDebugging } from '../../utils/debug.js' -import { withOAuth401Retry } from '../../utils/http.js' +import { isHttpError, nativeRequest, withOAuth401Retry } from '../../utils/http.js' import { lazySchema } from '../../utils/lazySchema.js' import { logError } from '../../utils/log.js' import { getAPIProvider } from '../../utils/model/providers.js' @@ -82,7 +81,8 @@ async function fetchBootstrapAPI(): Promise { } logForDebugging('[Bootstrap] Fetching') - const response = await axios.get(endpoint, { + const response = await nativeRequest(endpoint, { + method: 'GET', headers: { 'Content-Type': 'application/json', 'User-Agent': getClaudeCodeUserAgent(), @@ -102,7 +102,7 @@ async function fetchBootstrapAPI(): Promise { }) } catch (error) { logForDebugging( - `[Bootstrap] Fetch failed: ${axios.isAxiosError(error) ? (error.response?.status ?? error.code) : 'unknown'}`, + `[Bootstrap] Fetch failed: ${isHttpError(error) ? (error.status ?? error.code) : 'unknown'}`, ) throw error } diff --git a/services/api/errors.ts b/services/api/errors.ts index 1a7edc5..7925ccc 100644 --- a/services/api/errors.ts +++ b/services/api/errors.ts @@ -35,6 +35,7 @@ import { API_PDF_MAX_PAGES, PDF_TARGET_RAW_SIZE, } from '../../constants/apiLimits.js' +import { FEEDBACK_CHANNEL } from '../../constants/product.js' import { isEnvTruthy } from '../../utils/envUtils.js' import { formatFileSize } from '../../utils/format.js' import { ImageResizeError } from '../../utils/imageResizer.js' @@ -685,7 +686,7 @@ export function getAssistantMessageFromError( } if (process.env.USER_TYPE === 'ant') { - const baseMessage = `API Error: 400 ${error.message}\n\nRun /share and post the JSON file to ${MACRO.FEEDBACK_CHANNEL}.` + const baseMessage = `API Error: 400 ${error.message}\n\nRun /share and post the JSON file to ${FEEDBACK_CHANNEL}.` const rewindInstruction = getIsNonInteractiveSession() ? '' : ' Then, use /rewind to recover the conversation.' @@ -760,8 +761,8 @@ export function getAssistantMessageFromError( const orgId = getOauthAccountInfo()?.organizationUuid const baseMsg = `[ANT-ONLY] Your org isn't gated into the \`${model}\` model. Either run \`claude\` with \`ANTHROPIC_MODEL=${getDefaultMainLoopModelSetting()}\`` const msg = orgId - ? `${baseMsg} or share your orgId (${orgId}) in ${MACRO.FEEDBACK_CHANNEL} for help getting access.` - : `${baseMsg} or reach out in ${MACRO.FEEDBACK_CHANNEL} for help getting access.` + ? `${baseMsg} or share your orgId (${orgId}) in ${FEEDBACK_CHANNEL} for help getting access.` + : `${baseMsg} or reach out in ${FEEDBACK_CHANNEL} for help getting access.` return createAssistantAPIErrorMessage({ content: msg, diff --git a/services/api/filesApi.ts b/services/api/filesApi.ts index cb9a03b..a87ea67 100644 --- a/services/api/filesApi.ts +++ b/services/api/filesApi.ts @@ -7,7 +7,6 @@ * API Reference: https://docs.anthropic.com/en/api/files-content */ -import axios from 'axios' import { randomUUID } from 'crypto' import * as fs from 'fs/promises' import * as path from 'path' @@ -15,6 +14,7 @@ import { count } from '../../utils/array.js' import { getCwd } from '../../utils/cwd.js' import { logForDebugging } from '../../utils/debug.js' import { errorMessage } from '../../utils/errors.js' +import { isHttpError, nativeRequest } from '../../utils/http.js' import { logError } from '../../utils/log.js' import { sleep } from '../../utils/sleep.js' import { @@ -146,16 +146,17 @@ export async function downloadFile( return retryWithBackoff(`Download file ${fileId}`, async () => { try { - const response = await axios.get(url, { + const response = await nativeRequest(url, { + method: 'GET', headers, responseType: 'arraybuffer', timeout: 60000, // 60 second timeout for large files - validateStatus: status => status < 500, }) if (response.status === 200) { - logDebug(`Downloaded file ${fileId} (${response.data.length} bytes)`) - return { done: true, value: Buffer.from(response.data) } + const buffer = Buffer.from(response.data) + logDebug(`Downloaded file ${fileId} (${buffer.length} bytes)`) + return { done: true, value: buffer } } // Non-retriable errors - throw immediately @@ -171,10 +172,10 @@ export async function downloadFile( return { done: false, error: `status ${response.status}` } } catch (error) { - if (!axios.isAxiosError(error)) { - throw error + if (isHttpError(error)) { + return { done: false, error: `${error.status} ${error.message}` } } - return { done: false, error: error.message } + return { done: false, error: errorMessage(error) } } }) } @@ -457,7 +458,9 @@ export async function uploadFile( try { return await retryWithBackoff(`Upload file ${relativePath}`, async () => { try { - const response = await axios.post(url, body, { + const response = await nativeRequest(url, { + method: 'POST', + body, headers: { ...headers, 'Content-Type': `multipart/form-data; boundary=${boundary}`, @@ -465,7 +468,6 @@ export async function uploadFile( }, timeout: 120000, // 2 minute timeout for uploads signal: opts?.signal, - validateStatus: status => status < 500, }) if (response.status === 200 || response.status === 201) { @@ -521,11 +523,11 @@ export async function uploadFile( if (error instanceof UploadNonRetriableError) { throw error } - if (axios.isCancel(error)) { - throw new UploadNonRetriableError('Upload canceled') - } // Network errors are retriable - if (axios.isAxiosError(error)) { + if (isHttpError(error)) { + if (error.code === 'ECONNABORTED' || error.status === 408) { + return { done: false, error: 'Upload timeout' } + } return { done: false, error: error.message } } throw error @@ -643,11 +645,12 @@ export async function listFilesCreatedAfter( `List files after ${afterCreatedAt}`, async () => { try { - const response = await axios.get(`${baseUrl}/v1/files`, { + const queryParams = new URLSearchParams(params).toString() + const fullUrl = `${baseUrl}/v1/files${queryParams ? `?${queryParams}` : ''}` + const response = await nativeRequest(fullUrl, { + method: 'GET', headers, - params, timeout: 60000, - validateStatus: status => status < 500, }) if (response.status === 200) { @@ -671,14 +674,14 @@ export async function listFilesCreatedAfter( return { done: false, error: `status ${response.status}` } } catch (error) { - if (!axios.isAxiosError(error)) { - throw error + if (isHttpError(error)) { + logEvent('tengu_file_list_failed', { + error_type: + 'network' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + }) + return { done: false, error: error.message } } - logEvent('tengu_file_list_failed', { - error_type: - 'network' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, - }) - return { done: false, error: error.message } + throw error } }, ) diff --git a/services/api/firstTokenDate.ts b/services/api/firstTokenDate.ts index 4c66cf7..afdd9b8 100644 --- a/services/api/firstTokenDate.ts +++ b/services/api/firstTokenDate.ts @@ -1,7 +1,6 @@ -import axios from 'axios' import { getOauthConfig } from '../../constants/oauth.js' import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' -import { getAuthHeaders } from '../../utils/http.js' +import { getAuthHeaders, nativeRequest } from '../../utils/http.js' import { logError } from '../../utils/log.js' import { getClaudeCodeUserAgent } from '../../utils/userAgent.js' @@ -26,7 +25,8 @@ export async function fetchAndStoreClaudeCodeFirstTokenDate(): Promise { const oauthConfig = getOauthConfig() const url = `${oauthConfig.BASE_API_URL}/api/organization/claude_code_first_token_date` - const response = await axios.get(url, { + const response = await nativeRequest(url, { + method: 'GET', headers: { ...authHeaders.headers, 'User-Agent': getClaudeCodeUserAgent(), diff --git a/services/api/grove.ts b/services/api/grove.ts index f8af789..01e4b9c 100644 --- a/services/api/grove.ts +++ b/services/api/grove.ts @@ -1,4 +1,3 @@ -import axios from 'axios' import memoize from 'lodash-es/memoize.js' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, @@ -14,6 +13,7 @@ import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' import { getAuthHeaders, getUserAgent, + nativeRequest, withOAuth401Retry, } from '../../utils/http.js' import { logError } from '../../utils/log.js' @@ -61,9 +61,10 @@ export const getGroveSettings = memoize( if (authHeaders.error) { throw new Error(`Failed to get auth headers: ${authHeaders.error}`) } - return axios.get( + return nativeRequest( `${getOauthConfig().BASE_API_URL}/api/oauth/account/settings`, { + method: 'GET', headers: { ...authHeaders.headers, 'User-Agent': getClaudeCodeUserAgent(), @@ -94,10 +95,11 @@ export async function markGroveNoticeViewed(): Promise { if (authHeaders.error) { throw new Error(`Failed to get auth headers: ${authHeaders.error}`) } - return axios.post( + return nativeRequest( `${getOauthConfig().BASE_API_URL}/api/oauth/account/grove_notice_viewed`, - {}, { + method: 'POST', + body: {}, headers: { ...authHeaders.headers, 'User-Agent': getClaudeCodeUserAgent(), @@ -126,12 +128,13 @@ export async function updateGroveSettings( if (authHeaders.error) { throw new Error(`Failed to get auth headers: ${authHeaders.error}`) } - return axios.patch( + return nativeRequest( `${getOauthConfig().BASE_API_URL}/api/oauth/account/settings`, { - grove_enabled: groveEnabled, - }, - { + method: 'PATCH', + body: { + grove_enabled: groveEnabled, + }, headers: { ...authHeaders.headers, 'User-Agent': getClaudeCodeUserAgent(), @@ -241,9 +244,10 @@ export const getGroveNoticeConfig = memoize( if (authHeaders.error) { throw new Error(`Failed to get auth headers: ${authHeaders.error}`) } - return axios.get( + return nativeRequest( `${getOauthConfig().BASE_API_URL}/api/claude_code_grove`, { + method: 'GET', headers: { ...authHeaders.headers, 'User-Agent': getUserAgent(), diff --git a/services/api/logging.ts b/services/api/logging.ts index a411c12..a11f3f0 100644 --- a/services/api/logging.ts +++ b/services/api/logging.ts @@ -34,6 +34,7 @@ import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, } from '../analytics/index.js' +import { BUILD_TIME } from '../../constants/product.js' import { sanitizeToolNameForAnalytics } from '../analytics/metadata.js' import { EMPTY_USAGE } from './emptyUsage.js' import { classifyAPIError } from './errors.js' @@ -162,8 +163,8 @@ function getAnthropicEnvMetadata() { } function getBuildAgeMinutes(): number | undefined { - if (!MACRO.BUILD_TIME) return undefined - const buildTime = new Date(MACRO.BUILD_TIME).getTime() + if (!BUILD_TIME) return undefined + const buildTime = new Date(BUILD_TIME).getTime() if (isNaN(buildTime)) return undefined return Math.floor((Date.now() - buildTime) / 60000) } diff --git a/services/api/metricsOptOut.ts b/services/api/metricsOptOut.ts index 8ef884a..685015c 100644 --- a/services/api/metricsOptOut.ts +++ b/services/api/metricsOptOut.ts @@ -1,159 +1,24 @@ -import axios from 'axios' -import { hasProfileScope, isClaudeAISubscriber } from '../../utils/auth.js' -import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' -import { logForDebugging } from '../../utils/debug.js' -import { errorMessage } from '../../utils/errors.js' -import { getAuthHeaders, withOAuth401Retry } from '../../utils/http.js' -import { logError } from '../../utils/log.js' -import { memoizeWithTTLAsync } from '../../utils/memoize.js' -import { isEssentialTrafficOnly } from '../../utils/privacyLevel.js' -import { getClaudeCodeUserAgent } from '../../utils/userAgent.js' +/** + * Metrics Opt-Out Service (Stubbed) + * + * This service is stubbed to always report that metrics are disabled, + * ensuring no telemetry or logging data is sent to external services. + */ -type MetricsEnabledResponse = { - metrics_logging_enabled: boolean -} - -type MetricsStatus = { +export type MetricsStatus = { enabled: boolean hasError: boolean } -// In-memory TTL — dedupes calls within a single process -const CACHE_TTL_MS = 60 * 60 * 1000 - -// Disk TTL — org settings rarely change. When disk cache is fresher than this, -// we skip the network entirely (no background refresh). This is what collapses -// N `claude -p` invocations into ~1 API call/day. -const DISK_CACHE_TTL_MS = 24 * 60 * 60 * 1000 - -/** - * Internal function to call the API and check if metrics are enabled - * This is wrapped by memoizeWithTTLAsync to add caching behavior - */ -async function _fetchMetricsEnabled(): Promise { - const authResult = getAuthHeaders() - if (authResult.error) { - throw new Error(`Auth error: ${authResult.error}`) - } - - const headers = { - 'Content-Type': 'application/json', - 'User-Agent': getClaudeCodeUserAgent(), - ...authResult.headers, - } - - const endpoint = `https://api.anthropic.com/api/claude_code/organizations/metrics_enabled` - const response = await axios.get(endpoint, { - headers, - timeout: 5000, - }) - return response.data -} - -async function _checkMetricsEnabledAPI(): Promise { - // Incident kill switch: skip the network call when nonessential traffic is disabled. - // Returning enabled:false sheds load at the consumer (bigqueryExporter skips - // export). Matches the non-subscriber early-return shape below. - if (isEssentialTrafficOnly()) { - return { enabled: false, hasError: false } - } - - try { - const data = await withOAuth401Retry(_fetchMetricsEnabled, { - also403Revoked: true, - }) - - logForDebugging( - `Metrics opt-out API response: enabled=${data.metrics_logging_enabled}`, - ) - - return { - enabled: data.metrics_logging_enabled, - hasError: false, - } - } catch (error) { - logForDebugging( - `Failed to check metrics opt-out status: ${errorMessage(error)}`, - ) - logError(error) - return { enabled: false, hasError: true } - } -} - -// Create memoized version with custom error handling -const memoizedCheckMetrics = memoizeWithTTLAsync( - _checkMetricsEnabledAPI, - CACHE_TTL_MS, -) - -/** - * Fetch (in-memory memoized) and persist to disk on change. - * Errors are not persisted — a transient failure should not overwrite a - * known-good disk value. - */ -async function refreshMetricsStatus(): Promise { - const result = await memoizedCheckMetrics() - if (result.hasError) { - return result - } - - const cached = getGlobalConfig().metricsStatusCache - const unchanged = cached !== undefined && cached.enabled === result.enabled - // Skip write when unchanged AND timestamp still fresh — avoids config churn - // when concurrent callers race past a stale disk entry and all try to write. - if (unchanged && Date.now() - cached.timestamp < DISK_CACHE_TTL_MS) { - return result - } - - saveGlobalConfig(current => ({ - ...current, - metricsStatusCache: { - enabled: result.enabled, - timestamp: Date.now(), - }, - })) - return result -} - -/** - * Check if metrics are enabled for the current organization. - * - * Two-tier cache: - * - Disk (24h TTL): survives process restarts. Fresh disk cache → zero network. - * - In-memory (1h TTL): dedupes the background refresh within a process. - * - * The caller (bigqueryExporter) tolerates stale reads — a missed export or - * an extra one during the 24h window is acceptable. - */ export async function checkMetricsEnabled(): Promise { - // Service key OAuth sessions lack user:profile scope → would 403. - // API key users (non-subscribers) fall through and use x-api-key auth. - // This check runs before the disk read so we never persist auth-state-derived - // answers — only real API responses go to disk. Otherwise a service-key - // session would poison the cache for a later full-OAuth session. - if (isClaudeAISubscriber() && !hasProfileScope()) { - return { enabled: false, hasError: false } - } - - const cached = getGlobalConfig().metricsStatusCache - if (cached) { - if (Date.now() - cached.timestamp > DISK_CACHE_TTL_MS) { - // saveGlobalConfig's fallback path (config.ts:731) can throw if both - // locked and fallback writes fail — catch here so fire-and-forget - // doesn't become an unhandled rejection. - void refreshMetricsStatus().catch(logError) - } - return { - enabled: cached.enabled, - hasError: false, - } - } - - // First-ever run on this machine: block on the network to populate disk. - return refreshMetricsStatus() + // Always return disabled for a privacy-focused environment. + return { enabled: false, hasError: false }; +} + +export async function refreshMetricsStatus(): Promise { + return { enabled: false, hasError: false }; } -// Export for testing purposes only export const _clearMetricsEnabledCacheForTesting = (): void => { - memoizedCheckMetrics.cache.clear() -} + // No-op +}; diff --git a/services/api/overageCreditGrant.ts b/services/api/overageCreditGrant.ts index 5b13948..8454b74 100644 --- a/services/api/overageCreditGrant.ts +++ b/services/api/overageCreditGrant.ts @@ -1,7 +1,7 @@ -import axios from 'axios' import { getOauthConfig } from '../../constants/oauth.js' import { getOauthAccountInfo } from '../../utils/auth.js' import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' +import { nativeRequest } from '../../utils/http.js' import { logError } from '../../utils/log.js' import { isEssentialTrafficOnly } from '../../utils/privacyLevel.js' import { getOAuthHeaders, prepareApiRequest } from '../../utils/teleport/api.js' @@ -30,7 +30,8 @@ async function fetchOverageCreditGrant(): Promise try { const { accessToken, orgUUID } = await prepareApiRequest() const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/overage_credit_grant` - const response = await axios.get(url, { + const response = await nativeRequest(url, { + method: 'GET', headers: getOAuthHeaders(accessToken), }) return response.data diff --git a/services/api/referral.ts b/services/api/referral.ts index 13cdc9f..a85baf0 100644 --- a/services/api/referral.ts +++ b/services/api/referral.ts @@ -1,4 +1,3 @@ -import axios from 'axios' import { getOauthConfig } from '../../constants/oauth.js' import { getOauthAccountInfo, @@ -7,6 +6,7 @@ import { } from '../../utils/auth.js' import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' import { logForDebugging } from '../../utils/debug.js' +import { nativeRequest } from '../../utils/http.js' import { logError } from '../../utils/log.js' import { isEssentialTrafficOnly } from '../../utils/privacyLevel.js' import { getOAuthHeaders, prepareApiRequest } from '../../utils/teleport/api.js' @@ -35,9 +35,12 @@ export async function fetchReferralEligibility( const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/referral/eligibility` - const response = await axios.get(url, { + const queryParams = new URLSearchParams({ campaign }).toString() + const fullUrl = `${url}${queryParams ? `?${queryParams}` : ''}` + + const response = await nativeRequest(fullUrl, { + method: 'GET', headers, - params: { campaign }, timeout: 5000, // 5 second timeout for background fetch }) @@ -56,9 +59,12 @@ export async function fetchReferralRedemptions( const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/referral/redemptions` - const response = await axios.get(url, { + const queryParams = new URLSearchParams({ campaign }).toString() + const fullUrl = `${url}${queryParams ? `?${queryParams}` : ''}` + + const response = await nativeRequest(fullUrl, { + method: 'GET', headers, - params: { campaign }, timeout: 10000, // 10 second timeout }) diff --git a/services/api/sessionIngress.ts b/services/api/sessionIngress.ts index c49b0d4..90eec71 100644 --- a/services/api/sessionIngress.ts +++ b/services/api/sessionIngress.ts @@ -1,10 +1,10 @@ -import axios, { type AxiosError } from 'axios' import type { UUID } from 'crypto' import { getOauthConfig } from '../../constants/oauth.js' import type { Entry, TranscriptMessage } from '../../types/logs.js' import { logForDebugging } from '../../utils/debug.js' import { logForDiagnosticsNoPII } from '../../utils/diagLogs.js' import { isEnvTruthy } from '../../utils/envUtils.js' +import { isHttpError, nativeRequest } from '../../utils/http.js' import { logError } from '../../utils/log.js' import { sequential } from '../../utils/sequential.js' import { getSessionIngressAuthToken } from '../../utils/sessionIngressAuth.js' @@ -74,9 +74,10 @@ async function appendSessionLogImpl( requestHeaders['Last-Uuid'] = lastUuid } - const response = await axios.put(url, entry, { + const response = await nativeRequest(url, { + method: 'PUT', + body: entry, headers: requestHeaders, - validateStatus: status => status < 500, }) if (response.status === 200 || response.status === 201) { @@ -118,11 +119,11 @@ async function appendSessionLogImpl( if (adoptedUuid) { lastUuidMap.set(sessionId, adoptedUuid) logForDebugging( - `Session 409: re-fetched ${logs!.length} entries, adopting lastUuid=${adoptedUuid}, retrying entry ${entry.uuid}`, + `Session 409: re-fetched ${(logs as any)!.length} entries, adopting lastUuid=${adoptedUuid}, retrying entry ${entry.uuid}`, ) } else { // Can't determine server state — give up - const errorData = response.data as SessionIngressError + const errorData = response.data as any as SessionIngressError const errorMessage = errorData.error?.message || 'Concurrent modification detected' logError( @@ -148,21 +149,22 @@ async function appendSessionLogImpl( } // Other 4xx (429, etc.) - retryable - logForDebugging( - `Failed to persist session log: ${response.status} ${response.statusText}`, - ) + logForDebugging(`Failed to persist session log: ${response.status}`) logForDiagnosticsNoPII('error', 'session_persist_fail_status', { status: response.status, attempt, }) } catch (error) { // Network errors, 5xx - retryable - const axiosError = error as AxiosError - logError(new Error(`Error persisting session log: ${axiosError.message}`)) - logForDiagnosticsNoPII('error', 'session_persist_fail_status', { - status: axiosError.status, - attempt, - }) + if (isHttpError(error)) { + logError(new Error(`Error persisting session log: ${error.message}`)) + logForDiagnosticsNoPII('error', 'session_persist_fail_status', { + status: error.status, + attempt, + }) + } else { + logError(error) + } } if (attempt === MAX_RETRIES) { @@ -318,15 +320,19 @@ export async function getTeleportEvents( let response try { - response = await axios.get(baseUrl, { + const queryParams = new URLSearchParams(params as any).toString() + const fullUrl = `${baseUrl}${queryParams ? `?${queryParams}` : ''}` + response = await nativeRequest(fullUrl, { + method: 'GET', headers, - params, timeout: 20000, - validateStatus: status => status < 500, }) } catch (e) { - const err = e as AxiosError - logError(new Error(`Teleport events fetch failed: ${err.message}`)) + if (isHttpError(e)) { + logError(new Error(`Teleport events fetch failed: ${e.message}`)) + } else { + logError(e) + } logForDiagnosticsNoPII('error', 'teleport_events_fetch_fail') return null } @@ -423,13 +429,17 @@ async function fetchSessionLogsFromUrl( headers: Record, ): Promise { try { - const response = await axios.get(url, { + const queryParams: Record = {} + if (isEnvTruthy(process.env.CLAUDE_AFTER_LAST_COMPACT)) { + queryParams.after_last_compact = true + } + const queryString = new URLSearchParams(queryParams).toString() + const fullUrl = `${url}${queryString ? `?${queryString}` : ''}` + + const response = await nativeRequest(fullUrl, { + method: 'GET', headers, timeout: 20000, - validateStatus: status => status < 500, - params: isEnvTruthy(process.env.CLAUDE_AFTER_LAST_COMPACT) - ? { after_last_compact: true } - : undefined, }) if (response.status === 200) { @@ -467,19 +477,20 @@ async function fetchSessionLogsFromUrl( ) } - logForDebugging( - `Failed to fetch session logs: ${response.status} ${response.statusText}`, - ) + logForDebugging(`Failed to fetch session logs: ${response.status}`) logForDiagnosticsNoPII('error', 'session_get_fail_status', { status: response.status, }) return null } catch (error) { - const axiosError = error as AxiosError - logError(new Error(`Error fetching session logs: ${axiosError.message}`)) - logForDiagnosticsNoPII('error', 'session_get_fail_status', { - status: axiosError.status, - }) + if (isHttpError(error)) { + logError(new Error(`Error fetching session logs: ${error.message}`)) + logForDiagnosticsNoPII('error', 'session_get_fail_status', { + status: error.status, + }) + } else { + logError(error) + } return null } } diff --git a/services/api/ultrareviewQuota.ts b/services/api/ultrareviewQuota.ts index 02409b5..5591900 100644 --- a/services/api/ultrareviewQuota.ts +++ b/services/api/ultrareviewQuota.ts @@ -1,7 +1,7 @@ -import axios from 'axios' import { getOauthConfig } from '../../constants/oauth.js' import { isClaudeAISubscriber } from '../../utils/auth.js' import { logForDebugging } from '../../utils/debug.js' +import { nativeRequest } from '../../utils/http.js' import { getOAuthHeaders, prepareApiRequest } from '../../utils/teleport/api.js' export type UltrareviewQuotaResponse = { @@ -20,9 +20,10 @@ export async function fetchUltrareviewQuota(): Promise( + const response = await nativeRequest( `${getOauthConfig().BASE_API_URL}/v1/ultrareview/quota`, { + method: 'GET', headers: { ...getOAuthHeaders(accessToken), 'x-organization-uuid': orgUUID, diff --git a/services/api/usage.ts b/services/api/usage.ts index 6e2e106..a00d278 100644 --- a/services/api/usage.ts +++ b/services/api/usage.ts @@ -1,11 +1,10 @@ -import axios from 'axios' import { getOauthConfig } from '../../constants/oauth.js' import { getClaudeAIOAuthTokens, hasProfileScope, isClaudeAISubscriber, } from '../../utils/auth.js' -import { getAuthHeaders } from '../../utils/http.js' +import { getAuthHeaders, nativeRequest } from '../../utils/http.js' import { getClaudeCodeUserAgent } from '../../utils/userAgent.js' import { isOAuthTokenExpired } from '../oauth/client.js' @@ -54,7 +53,8 @@ export async function fetchUtilization(): Promise { const url = `${getOauthConfig().BASE_API_URL}/api/oauth/usage` - const response = await axios.get(url, { + const response = await nativeRequest(url, { + method: 'GET', headers, timeout: 5000, // 5 second timeout }) diff --git a/services/mcp/auth.ts b/services/mcp/auth.ts index 61c9463..96e7ddf 100644 --- a/services/mcp/auth.ts +++ b/services/mcp/auth.ts @@ -24,7 +24,7 @@ import { OAuthTokensSchema, } from '@modelcontextprotocol/sdk/shared/auth.js' import type { FetchLike } from '@modelcontextprotocol/sdk/shared/transport.js' -import axios from 'axios' +import { isHttpError, nativeRequest } from '../../utils/http.js' import { createHash, randomBytes, randomUUID } from 'crypto' import { mkdir } from 'fs/promises' import { createServer, type Server } from 'http' @@ -428,25 +428,30 @@ async function revokeToken({ } try { - await axios.post(endpoint, params, { headers }) + await nativeRequest(endpoint, { + method: 'POST', + headers: { ...headers, 'Content-Type': 'application/x-www-form-urlencoded' }, + body: params.toString(), + responseType: 'text', + }) logMCPDebug(serverName, `Successfully revoked ${tokenTypeHint}`) } catch (error: unknown) { - // Fallback for non-RFC-7009-compliant servers that require Bearer auth if ( - axios.isAxiosError(error) && - error.response?.status === 401 && + isHttpError(error) && + error.status === 401 && accessToken ) { logMCPDebug( serverName, `Got 401, retrying ${tokenTypeHint} revocation with Bearer auth`, ) - // RFC 6749 §2.3.1: must not send more than one auth method. The retry - // switches to Bearer — clear any client creds from the body. params.delete('client_id') params.delete('client_secret') - await axios.post(endpoint, params, { - headers: { ...headers, Authorization: `Bearer ${accessToken}` }, + await nativeRequest(endpoint, { + method: 'POST', + headers: { ...headers, Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/x-www-form-urlencoded' }, + body: params.toString(), + responseType: 'text', }) logMCPDebug( serverName, diff --git a/services/mcp/claudeai.ts b/services/mcp/claudeai.ts index 132990a..b94a2f9 100644 --- a/services/mcp/claudeai.ts +++ b/services/mcp/claudeai.ts @@ -1,4 +1,3 @@ -import axios from 'axios' import memoize from 'lodash-es/memoize.js' import { getOauthConfig } from 'src/constants/oauth.js' import { @@ -9,6 +8,7 @@ import { getClaudeAIOAuthTokens } from 'src/utils/auth.js' import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js' import { logForDebugging } from 'src/utils/debug.js' import { isEnvDefinedFalsy } from 'src/utils/envUtils.js' +import { nativeRequest } from 'src/utils/http.js' import { clearMcpAuthCache } from './client.js' import { normalizeNameForMCP } from './normalization.js' import type { ScopedMcpServerConfig } from './types.js' @@ -79,7 +79,7 @@ export const fetchClaudeAIMcpConfigsIfEligible = memoize( logForDebugging(`[claudeai-mcp] Fetching from ${url}`) - const response = await axios.get(url, { + const { data: respData } = await nativeRequest(url, { headers: { Authorization: `Bearer ${tokens.accessToken}`, 'Content-Type': 'application/json', @@ -96,7 +96,7 @@ export const fetchClaudeAIMcpConfigsIfEligible = memoize( // colliding with "Example Server! (2)" which both normalize to claude_ai_Example_Server_2). const usedNormalizedNames = new Set() - for (const server of response.data.data) { + for (const server of respData.data) { const baseName = `claude.ai ${server.display_name}` // Try without suffix first, then increment until we find an unused normalized name diff --git a/services/mcp/client.ts b/services/mcp/client.ts index 0b3afc6..515e4ad 100644 --- a/services/mcp/client.ts +++ b/services/mcp/client.ts @@ -43,7 +43,7 @@ import pMap from 'p-map' import { getOriginalCwd, getSessionId } from '../../bootstrap/state.js' import type { Command } from '../../commands.js' import { getOauthConfig } from '../../constants/oauth.js' -import { PRODUCT_URL } from '../../constants/product.js' +import { VERSION, PRODUCT_URL } from '../../constants/product.js' import type { AppState } from '../../state/AppState.js' import { type Tool, @@ -986,7 +986,7 @@ export const connectToServer = memoize( { name: 'claude-code', title: 'Claude Code', - version: MACRO.VERSION ?? 'unknown', + version: VERSION ?? 'unknown', description: "Anthropic's agentic coding tool", websiteUrl: PRODUCT_URL, }, @@ -3281,7 +3281,7 @@ export async function setupSdkMcpClients( { name: 'claude-code', title: 'Claude Code', - version: MACRO.VERSION ?? 'unknown', + version: VERSION ?? 'unknown', description: "Anthropic's agentic coding tool", websiteUrl: PRODUCT_URL, }, diff --git a/services/mcp/headersHelper.ts b/services/mcp/headersHelper.ts index 3ae0e3d..a1eb4bd 100644 --- a/services/mcp/headersHelper.ts +++ b/services/mcp/headersHelper.ts @@ -1,4 +1,5 @@ import { getIsNonInteractiveSession } from '../../bootstrap/state.js' +import { FEEDBACK_CHANNEL } from '../../constants/product.js' import { checkHasTrustDialogAccepted } from '../../utils/config.js' import { logAntError } from '../../utils/debug.js' import { errorMessage } from '../../utils/errors.js' @@ -48,7 +49,7 @@ export async function getMcpHeadersFromHelper( const hasTrust = checkHasTrustDialogAccepted() if (!hasTrust) { const error = new Error( - `Security: headersHelper for MCP server '${serverName}' executed before workspace trust is confirmed. If you see this message, post in ${MACRO.FEEDBACK_CHANNEL}.`, + `Security: headersHelper for MCP server '${serverName}' executed before workspace trust is confirmed. If you see this message, post in ${FEEDBACK_CHANNEL}.`, ) logAntError('MCP headersHelper invoked before trust check', error) logEvent('tengu_mcp_headersHelper_missing_trust', {}) diff --git a/services/mcp/officialRegistry.ts b/services/mcp/officialRegistry.ts index 3e26292..c2e1371 100644 --- a/services/mcp/officialRegistry.ts +++ b/services/mcp/officialRegistry.ts @@ -1,6 +1,6 @@ -import axios from 'axios' import { logForDebugging } from '../../utils/debug.js' import { errorMessage } from '../../utils/errors.js' +import { nativeRequest } from '../../utils/http.js' type RegistryServer = { server: { @@ -36,13 +36,13 @@ export async function prefetchOfficialMcpUrls(): Promise { } try { - const response = await axios.get( + const { data } = await nativeRequest( 'https://api.anthropic.com/mcp-registry/v0/servers?version=latest&visibility=commercial', { timeout: 5000 }, ) const urls = new Set() - for (const entry of response.data.servers) { + for (const entry of data.servers) { for (const remote of entry.server.remotes ?? []) { const normalized = normalizeUrl(remote.url) if (normalized) { diff --git a/services/oauth/client.ts b/services/oauth/client.ts index 76bfdce..cf2bb30 100644 --- a/services/oauth/client.ts +++ b/services/oauth/client.ts @@ -1,5 +1,5 @@ // OAuth client for handling authentication flows with Claude services -import axios from 'axios' +import { isHttpError, nativeRequest } from '../../utils/http.js' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, @@ -127,10 +127,13 @@ export async function exchangeCodeForTokens( requestBody.expires_in = expiresIn } - const response = await axios.post(getOauthConfig().TOKEN_URL, requestBody, { - headers: { 'Content-Type': 'application/json' }, - timeout: 15000, - }) + const response = await nativeRequest(getOauthConfig().TOKEN_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: requestBody, + timeout: 15000, + responseType: 'json', + }) if (response.status !== 200) { throw new Error( @@ -162,11 +165,14 @@ export async function refreshOAuthToken( ).join(' '), } - try { - const response = await axios.post(getOauthConfig().TOKEN_URL, requestBody, { - headers: { 'Content-Type': 'application/json' }, - timeout: 15000, - }) + try { + const response = await nativeRequest(getOauthConfig().TOKEN_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: requestBody, + timeout: 15000, + responseType: 'json', + }) if (response.status !== 200) { throw new Error(`Token refresh failed: ${response.statusText}`) @@ -256,11 +262,11 @@ export async function refreshOAuthToken( } : undefined, } - } catch (error) { - const responseBody = - axios.isAxiosError(error) && error.response?.data - ? JSON.stringify(error.response.data) - : undefined + } catch (error) { + const responseBody = + isHttpError(error) && error.data + ? JSON.stringify(error.data) + : undefined logEvent('tengu_oauth_token_refresh_failure', { error: (error as Error) .message as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, @@ -274,11 +280,13 @@ export async function refreshOAuthToken( } export async function fetchAndStoreUserRoles( - accessToken: string, -): Promise { - const response = await axios.get(getOauthConfig().ROLES_URL, { - headers: { Authorization: `Bearer ${accessToken}` }, - }) + accessToken: string, + ): Promise { + const response = await nativeRequest(getOauthConfig().ROLES_URL, { + method: 'GET', + headers: { Authorization: `Bearer ${accessToken}` }, + responseType: 'json', + }) if (response.status !== 200) { throw new Error(`Failed to fetch user roles: ${response.statusText}`) @@ -311,10 +319,12 @@ export async function fetchAndStoreUserRoles( export async function createAndStoreApiKey( accessToken: string, ): Promise { - try { - const response = await axios.post(getOauthConfig().API_KEY_URL, null, { - headers: { Authorization: `Bearer ${accessToken}` }, - }) + try { + const response = await nativeRequest(getOauthConfig().API_KEY_URL, { + method: 'POST', + headers: { Authorization: `Bearer ${accessToken}` }, + responseType: 'json', + }) const apiKey = response.data?.raw_key if (apiKey) { diff --git a/services/oauth/getOauthProfile.ts b/services/oauth/getOauthProfile.ts index 21bbf09..62ca959 100644 --- a/services/oauth/getOauthProfile.ts +++ b/services/oauth/getOauthProfile.ts @@ -1,8 +1,8 @@ -import axios from 'axios' import { getOauthConfig, OAUTH_BETA_HEADER } from 'src/constants/oauth.js' import type { OAuthProfileResponse } from 'src/services/oauth/types.js' import { getAnthropicApiKey } from 'src/utils/auth.js' import { getGlobalConfig } from 'src/utils/config.js' +import { nativeRequest } from 'src/utils/http.js' import { logError } from 'src/utils/log.js' export async function getOauthProfileFromApiKey(): Promise< OAuthProfileResponse | undefined @@ -16,19 +16,16 @@ export async function getOauthProfileFromApiKey(): Promise< if (!accountUuid || !apiKey) { return } - const endpoint = `${getOauthConfig().BASE_API_URL}/api/claude_cli_profile` + const endpoint = `${getOauthConfig().BASE_API_URL}/api/claude_cli_profile?account_uuid=${encodeURIComponent(accountUuid)}` try { - const response = await axios.get(endpoint, { + const { data } = await nativeRequest(endpoint, { headers: { 'x-api-key': apiKey, 'anthropic-beta': OAUTH_BETA_HEADER, }, - params: { - account_uuid: accountUuid, - }, timeout: 10000, }) - return response.data + return data } catch (error) { logError(error as Error) } @@ -39,14 +36,14 @@ export async function getOauthProfileFromOauthToken( ): Promise { const endpoint = `${getOauthConfig().BASE_API_URL}/api/oauth/profile` try { - const response = await axios.get(endpoint, { + const { data } = await nativeRequest(endpoint, { headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, timeout: 10000, }) - return response.data + return data } catch (error) { logError(error as Error) } diff --git a/services/policyLimits/index.ts b/services/policyLimits/index.ts index e328875..7feae05 100644 --- a/services/policyLimits/index.ts +++ b/services/policyLimits/index.ts @@ -12,7 +12,7 @@ * - API returns empty restrictions for users without policy limits */ -import axios from 'axios' +import { isHttpError, nativeRequest, classifyHttpError } from '../../utils/http.js' import { createHash } from 'crypto' import { readFileSync as fsReadFileSync } from 'fs' import { unlink, writeFile } from 'fs/promises' @@ -30,7 +30,7 @@ import { import { registerCleanup } from '../../utils/cleanupRegistry.js' import { logForDebugging } from '../../utils/debug.js' import { getClaudeConfigHomeDir } from '../../utils/envUtils.js' -import { classifyAxiosError } from '../../utils/errors.js' +// Removed classifyAxiosError import - using classifyHttpError from utils/http.js instead import { safeParseJSON } from '../../utils/json.js' import { getAPIProvider, @@ -312,22 +312,90 @@ async function fetchPolicyLimits( } } - const endpoint = getPolicyLimitsEndpoint() - const headers: Record = { - ...authHeaders.headers, - 'User-Agent': getClaudeCodeUserAgent(), - } + const endpoint = getPolicyLimitsEndpoint() + const headers: Record = { + ...authHeaders.headers, + 'User-Agent': getClaudeCodeUserAgent(), + } + + if (cachedChecksum) { + headers['If-None-Match'] = `"${cachedChecksum}"` + } + + try { + const response = await nativeRequest(endpoint, { + method: 'GET', + headers, + timeout: FETCH_TIMEOUT_MS, + responseType: 'json', + }) + + // Handle 304 Not Modified - cached version is still valid + if (response.status === 304) { + logForDebugging('Policy limits: Using cached restrictions (304)') + return { + success: true, + restrictions: null, // Signal that cache is valid + etag: cachedChecksum, + } + } + + // Handle 404 Not Found - no policy limits exist or feature not enabled + if (response.status === 404) { + logForDebugging('Policy limits: No restrictions found (404)') + return { + success: true, + restrictions: {}, + etag: undefined, + } + } + + const parsed = PolicyLimitsResponseSchema().safeParse(response.data) + if (!parsed.success) { + logForDebugging( + `Policy limits: Invalid response format - ${parsed.error.message}`, + ) + return { + success: false, + error: 'Invalid policy limits format', + } + } + + logForDebugging('Policy limits: Fetched successfully') + return { + success: true, + restrictions: parsed.data.restrictions, + } + } catch (error) { + // 404 is handled above via validateStatus, so it won't reach here + const { kind, message } = classifyHttpError(error) + switch (kind) { + case 'auth': + return { + success: false, + error: 'Not authorized for policy limits', + skipRetry: true, + } + case 'timeout': + return { success: false, error: 'Policy limits request timeout' } + case 'network': + return { success: false, error: 'Cannot connect to server' } + default: + return { success: false, error: message } + } + } if (cachedChecksum) { headers['If-None-Match'] = `"${cachedChecksum}"` } - const response = await axios.get(endpoint, { - headers, - timeout: FETCH_TIMEOUT_MS, - validateStatus: status => - status === 200 || status === 304 || status === 404, - }) + try { + const response = await nativeRequest(endpoint, { + method: 'GET', + headers, + timeout: FETCH_TIMEOUT_MS, + responseType: 'json', + }) // Handle 304 Not Modified - cached version is still valid if (response.status === 304) { @@ -365,24 +433,24 @@ async function fetchPolicyLimits( success: true, restrictions: parsed.data.restrictions, } - } catch (error) { - // 404 is handled above via validateStatus, so it won't reach here - const { kind, message } = classifyAxiosError(error) - switch (kind) { - case 'auth': - return { - success: false, - error: 'Not authorized for policy limits', - skipRetry: true, - } - case 'timeout': - return { success: false, error: 'Policy limits request timeout' } - case 'network': - return { success: false, error: 'Cannot connect to server' } - default: - return { success: false, error: message } - } - } + } catch (error) { + // 404 is handled above via validateStatus, so it won't reach here + const { kind, message } = classifyHttpError(error) + switch (kind) { + case 'auth': + return { + success: false, + error: 'Not authorized for policy limits', + skipRetry: true, + } + case 'timeout': + return { success: false, error: 'Policy limits request timeout' } + case 'network': + return { success: false, error: 'Cannot connect to server' } + default: + return { success: false, error: message } + } + } } /** diff --git a/services/remoteManagedSettings/index.ts b/services/remoteManagedSettings/index.ts index 228f2ed..ae2fb1a 100644 --- a/services/remoteManagedSettings/index.ts +++ b/services/remoteManagedSettings/index.ts @@ -12,7 +12,7 @@ * - API returns empty settings for users without managed settings */ -import axios from 'axios' +import { isHttpError, nativeRequest, classifyHttpError } from '../../utils/http.js' import { createHash } from 'crypto' import { open, unlink } from 'fs/promises' import { getOauthConfig, OAUTH_BETA_HEADER } from '../../constants/oauth.js' @@ -23,7 +23,7 @@ import { } from '../../utils/auth.js' import { registerCleanup } from '../../utils/cleanupRegistry.js' import { logForDebugging } from '../../utils/debug.js' -import { classifyAxiosError, getErrnoCode } from '../../utils/errors.js' +import { getErrnoCode } from '../../utils/errors.js' import { settingsChangeDetector } from '../../utils/settings/changeDetector.js' import { type SettingsJson, @@ -248,40 +248,38 @@ async function fetchWithRetry( async function fetchRemoteManagedSettings( cachedChecksum?: string, ): Promise { + // Ensure OAuth token is fresh before fetching settings + // This prevents 401 errors from stale cached tokens + await checkAndRefreshOAuthTokenIfNeeded() + + // Use local auth header getter to avoid circular dependency with getSettings() + const authHeaders = getRemoteSettingsAuthHeaders() + if (authHeaders.error) { + // Auth errors should not be retried - return a special flag to skip retries + return { + success: false, + error: `Authentication required for remote settings`, + skipRetry: true, + } + } + + const endpoint = getRemoteManagedSettingsEndpoint() + const headers: Record = { + ...authHeaders.headers, + 'User-Agent': getClaudeCodeUserAgent(), + } + + // Add If-None-Match header for ETag-based caching + if (cachedChecksum) { + headers['If-None-Match'] = `"${cachedChecksum}"` + } + try { - // Ensure OAuth token is fresh before fetching settings - // This prevents 401 errors from stale cached tokens - await checkAndRefreshOAuthTokenIfNeeded() - - // Use local auth header getter to avoid circular dependency with getSettings() - const authHeaders = getRemoteSettingsAuthHeaders() - if (authHeaders.error) { - // Auth errors should not be retried - return a special flag to skip retries - return { - success: false, - error: `Authentication required for remote settings`, - skipRetry: true, - } - } - - const endpoint = getRemoteManagedSettingsEndpoint() - const headers: Record = { - ...authHeaders.headers, - 'User-Agent': getClaudeCodeUserAgent(), - } - - // Add If-None-Match header for ETag-based caching - if (cachedChecksum) { - headers['If-None-Match'] = `"${cachedChecksum}"` - } - - const response = await axios.get(endpoint, { + const response = await nativeRequest(endpoint, { + method: 'GET', headers, timeout: SETTINGS_TIMEOUT_MS, - // Allow 204, 304, and 404 responses without treating them as errors. - // 204/404 are returned when no settings exist for the user or the feature flag is off. - validateStatus: status => - status === 200 || status === 204 || status === 304 || status === 404, + responseType: 'json', }) // Handle 304 Not Modified - cached version is still valid @@ -337,7 +335,7 @@ async function fetchRemoteManagedSettings( checksum: parsed.data.checksum, } } catch (error) { - const { kind, status, message } = classifyAxiosError(error) + const { kind, status, message } = classifyHttpError(error) if (status === 404) { // 404 means no remote settings configured return { success: true, settings: {}, checksum: '' } diff --git a/services/settingsSync/index.ts b/services/settingsSync/index.ts index 2d392b7..bcd176a 100644 --- a/services/settingsSync/index.ts +++ b/services/settingsSync/index.ts @@ -10,7 +10,7 @@ */ import { feature } from 'bun:bundle' -import axios from 'axios' +import { isHttpError, nativeRequest } from '../../utils/http.js' import { mkdir, readFile, stat, writeFile } from 'fs/promises' import pickBy from 'lodash-es/pickBy.js' import { dirname } from 'path' @@ -27,7 +27,7 @@ import { import { clearMemoryFileCaches } from '../../utils/claudemd.js' import { getMemoryPath } from '../../utils/config.js' import { logForDiagnosticsNoPII } from '../../utils/diagLogs.js' -import { classifyAxiosError } from '../../utils/errors.js' +import { classifyHttpError } from '../../utils/errors.js' import { getRepoRemoteHash } from '../../utils/git.js' import { getAPIProvider, @@ -257,26 +257,37 @@ async function fetchUserSettingsOnce(): Promise { } } - const headers: Record = { - ...authHeaders.headers, - 'User-Agent': getClaudeCodeUserAgent(), - } + const headers: Record = { + ...authHeaders.headers, + 'User-Agent': getClaudeCodeUserAgent(), + } - const endpoint = getSettingsSyncEndpoint() - const response = await axios.get(endpoint, { - headers, - timeout: SETTINGS_SYNC_TIMEOUT_MS, - validateStatus: status => status === 200 || status === 404, - }) + const endpoint = getSettingsSyncEndpoint() + try { + const response = await nativeRequest(endpoint, { + method: 'GET', + headers, + timeout: SETTINGS_SYNC_TIMEOUT_MS, + responseType: 'json', + }) - // 404 means no settings exist yet - if (response.status === 404) { - logForDiagnosticsNoPII('info', 'settings_sync_fetch_empty') - return { - success: true, - isEmpty: true, - } - } + logForDiagnosticsNoPII('info', 'settings_sync_fetch_success') + return { + success: true, + data: response.data, + isEmpty: false, + } + } catch (error) { + if (isHttpError(error) && error.status === 404) { + // 404 means no settings exist yet + logForDiagnosticsNoPII('info', 'settings_sync_fetch_empty') + return { + success: true, + isEmpty: true, + } + } + throw error + } const parsed = UserSyncDataSchema().safeParse(response.data) if (!parsed.success) { @@ -293,23 +304,23 @@ async function fetchUserSettingsOnce(): Promise { data: parsed.data, isEmpty: false, } - } catch (error) { - const { kind, message } = classifyAxiosError(error) - switch (kind) { - case 'auth': - return { - success: false, - error: 'Not authorized for settings sync', - skipRetry: true, - } - case 'timeout': - return { success: false, error: 'Settings sync request timeout' } - case 'network': - return { success: false, error: 'Cannot connect to server' } - default: - return { success: false, error: message } - } - } + } catch (error) { + const { kind, message } = classifyHttpError(error) + switch (kind) { + case 'auth': + return { + success: false, + error: 'Not authorized for settings sync', + skipRetry: true, + } + case 'timeout': + return { success: false, error: 'Settings sync request timeout' } + case 'network': + return { success: false, error: 'Cannot connect to server' } + default: + return { success: false, error: message } + } + } } async function fetchUserSettings( @@ -358,30 +369,29 @@ async function uploadUserSettings( } } - const headers: Record = { - ...authHeaders.headers, - 'User-Agent': getClaudeCodeUserAgent(), - 'Content-Type': 'application/json', - } + const headers: Record = { + ...authHeaders.headers, + 'User-Agent': getClaudeCodeUserAgent(), + 'Content-Type': 'application/json', + } - const endpoint = getSettingsSyncEndpoint() - const response = await axios.put( - endpoint, - { entries }, - { - headers, - timeout: SETTINGS_SYNC_TIMEOUT_MS, - }, - ) + const endpoint = getSettingsSyncEndpoint() + const response = await nativeRequest(endpoint, { + method: 'PUT', + headers, + body: { entries }, + timeout: SETTINGS_SYNC_TIMEOUT_MS, + responseType: 'json', + }) - logForDiagnosticsNoPII('info', 'settings_sync_uploaded', { - entryCount: Object.keys(entries).length, - }) - return { - success: true, - checksum: response.data?.checksum, - lastModified: response.data?.lastModified, - } + logForDiagnosticsNoPII('info', 'settings_sync_uploaded', { + entryCount: Object.keys(entries).length, + }) + return { + success: true, + checksum: response.data?.checksum, + lastModified: response.data?.lastModified, + } } catch (error) { logForDiagnosticsNoPII('warn', 'settings_sync_upload_error') return { diff --git a/services/teamMemorySync/index.ts b/services/teamMemorySync/index.ts index ef66940..93ba7ca 100644 --- a/services/teamMemorySync/index.ts +++ b/services/teamMemorySync/index.ts @@ -24,7 +24,6 @@ * This avoids module-level mutable state and gives tests natural isolation. */ -import axios from 'axios' import { createHash } from 'crypto' import { mkdir, readdir, readFile, stat, writeFile } from 'fs/promises' import { join, relative, sep } from 'path' @@ -45,8 +44,9 @@ import { getClaudeAIOAuthTokens, } from '../../utils/auth.js' import { logForDebugging } from '../../utils/debug.js' -import { classifyAxiosError } from '../../utils/errors.js' +import { classifyHttpError } from '../../utils/errors.js' import { getGithubRepo } from '../../utils/git.js' +import { isHttpError, nativeRequest } from '../../utils/http.js' import { getAPIProvider, isFirstPartyAnthropicBaseUrl, @@ -209,11 +209,10 @@ async function fetchTeamMemoryOnce( } const endpoint = getTeamMemorySyncEndpoint(repoSlug) - const response = await axios.get(endpoint, { + const response = await nativeRequest(endpoint, { + method: 'GET', headers, timeout: TEAM_MEMORY_SYNC_TIMEOUT_MS, - validateStatus: status => - status === 200 || status === 304 || status === 404, }) if (response.status === 304) { @@ -264,10 +263,8 @@ async function fetchTeamMemoryOnce( checksum: responseChecksum, } } catch (error) { - const { kind, status, message } = classifyAxiosError(error) - const body = axios.isAxiosError(error) - ? JSON.stringify(error.response?.data ?? '') - : '' + const { kind, status, message } = classifyHttpError(error) + const body = isHttpError(error) ? JSON.stringify(error.data ?? '') : '' if (kind !== 'other') { logForDebugging(`team-memory-sync: fetch error ${status}: ${body}`, { level: 'warn', @@ -324,10 +321,10 @@ async function fetchTeamMemoryHashes( } const endpoint = getTeamMemorySyncEndpoint(repoSlug) + '&view=hashes' - const response = await axios.get(endpoint, { + const response = await nativeRequest(endpoint, { + method: 'GET', headers: auth.headers, timeout: TEAM_MEMORY_SYNC_TIMEOUT_MS, - validateStatus: status => status === 200 || status === 404, }) if (response.status === 404) { @@ -360,7 +357,7 @@ async function fetchTeamMemoryHashes( entryChecksums, } } catch (error) { - const { kind, status, message } = classifyAxiosError(error) + const { kind, status, message } = classifyHttpError(error) switch (kind) { case 'auth': return { @@ -482,15 +479,12 @@ async function uploadTeamMemory( } const endpoint = getTeamMemorySyncEndpoint(repoSlug) - const response = await axios.put( - endpoint, - { entries }, - { - headers, - timeout: TEAM_MEMORY_SYNC_TIMEOUT_MS, - validateStatus: status => status === 200 || status === 412, - }, - ) + const response = await nativeRequest(endpoint, { + method: 'PUT', + body: { entries }, + headers, + timeout: TEAM_MEMORY_SYNC_TIMEOUT_MS, + }) if (response.status === 412) { logForDebugging('team-memory-sync: conflict (412 Precondition Failed)', { @@ -514,14 +508,12 @@ async function uploadTeamMemory( lastModified: response.data?.lastModified, } } catch (error) { - const body = axios.isAxiosError(error) - ? JSON.stringify(error.response?.data ?? '') - : '' + const body = isHttpError(error) ? JSON.stringify(error.data ?? '') : '' logForDebugging( `team-memory-sync: upload failed: ${error instanceof Error ? error.message : ''} ${body}`, { level: 'warn' }, ) - const { kind, status: httpStatus, message } = classifyAxiosError(error) + const { kind, status: httpStatus, message } = classifyHttpError(error) const errorType = kind === 'http' || kind === 'other' ? 'unknown' : kind let serverErrorCode: 'team_memory_too_many_entries' | undefined let serverMaxEntries: number | undefined @@ -530,10 +522,8 @@ async function uploadTeamMemory( // RequestTooLargeException includes error_code + extra_details with // the effective max_entries (may be GB-tuned per-org). Cache it so // the next push trims to the right value. - if (httpStatus === 413 && axios.isAxiosError(error)) { - const parsed = TeamMemoryTooManyEntriesSchema().safeParse( - error.response?.data, - ) + if (httpStatus === 413 && isHttpError(error)) { + const parsed = TeamMemoryTooManyEntriesSchema().safeParse(error.data) if (parsed.success) { serverErrorCode = parsed.data.error.details.error_code serverMaxEntries = parsed.data.error.details.max_entries diff --git a/tools/BriefTool/upload.ts b/tools/BriefTool/upload.ts index 306e6f4..a5b9db1 100644 --- a/tools/BriefTool/upload.ts +++ b/tools/BriefTool/upload.ts @@ -13,7 +13,7 @@ */ import { feature } from 'bun:bundle' -import axios from 'axios' +import { nativeRequest } from '../../utils/http.js' import { randomUUID } from 'crypto' import { readFile } from 'fs/promises' import { basename, extname } from 'path' @@ -137,7 +137,9 @@ export async function uploadBriefAttachment( ]) try { - const response = await axios.post(url, body, { + const response = await nativeRequest(url, { + method: 'POST', + body, headers: { Authorization: `Bearer ${token}`, 'Content-Type': `multipart/form-data; boundary=${boundary}`, @@ -145,7 +147,6 @@ export async function uploadBriefAttachment( }, timeout: UPLOAD_TIMEOUT_MS, signal: ctx.signal, - validateStatus: () => true, }) if (response.status !== 201) { diff --git a/tools/RemoteTriggerTool/RemoteTriggerTool.ts b/tools/RemoteTriggerTool/RemoteTriggerTool.ts index 8e623a1..5721e55 100644 --- a/tools/RemoteTriggerTool/RemoteTriggerTool.ts +++ b/tools/RemoteTriggerTool/RemoteTriggerTool.ts @@ -1,4 +1,4 @@ -import axios from 'axios' +import { nativeRequest } from '../../utils/http.js' import { z } from 'zod/v4' import { getOauthConfig } from '../../constants/oauth.js' import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js' @@ -132,14 +132,12 @@ export const RemoteTriggerTool = buildTool({ break } - const res = await axios.request({ + const res = await nativeRequest(url, { method, - url, headers, - data, + body: data, timeout: 20_000, signal: context.abortController.signal, - validateStatus: () => true, }) return { diff --git a/tools/WebFetchTool/utils.ts b/tools/WebFetchTool/utils.ts index 6d55f70..486344f 100644 --- a/tools/WebFetchTool/utils.ts +++ b/tools/WebFetchTool/utils.ts @@ -1,4 +1,4 @@ -import axios, { type AxiosResponse } from 'axios' +import { isHttpError, nativeRequest } from '../../utils/http.js' import { LRUCache } from 'lru-cache' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, @@ -180,7 +180,7 @@ export async function checkDomainBlocklist( return { status: 'allowed' } } try { - const response = await axios.get( + const response = await nativeRequest( `https://api.anthropic.com/api/web/domain_info?domain=${encodeURIComponent(domain)}`, { timeout: DOMAIN_CHECK_TIMEOUT_MS }, ) @@ -264,17 +264,16 @@ export async function getWithPermittedRedirects( signal: AbortSignal, redirectChecker: (originalUrl: string, redirectUrl: string) => boolean, depth = 0, -): Promise | RedirectInfo> { +): Promise<{ data: ArrayBuffer; status: number; headers: Record } | RedirectInfo> { if (depth > MAX_REDIRECTS) { throw new Error(`Too many redirects (exceeded ${MAX_REDIRECTS})`) } try { - return await axios.get(url, { + return await nativeRequest(url, { + method: 'GET', signal, timeout: FETCH_TIMEOUT_MS, - maxRedirects: 0, responseType: 'arraybuffer', - maxContentLength: MAX_HTTP_CONTENT_LENGTH, headers: { Accept: 'text/markdown, text/html, */*', 'User-Agent': getWebFetchUserAgent(), @@ -282,11 +281,11 @@ export async function getWithPermittedRedirects( }) } catch (error) { if ( - axios.isAxiosError(error) && - error.response && - [301, 302, 307, 308].includes(error.response.status) + isHttpError(error) && + error.status && + [301, 302, 307, 308].includes(error.status) ) { - const redirectLocation = error.response.headers.location + const redirectLocation = error.headers?.location if (!redirectLocation) { throw new Error('Redirect missing Location header') } @@ -302,23 +301,22 @@ export async function getWithPermittedRedirects( redirectChecker, depth + 1, ) - } else { - // Return redirect information to the caller - return { - type: 'redirect', - originalUrl: url, - redirectUrl, - statusCode: error.response.status, - } + } + // Return redirect information to the caller + return { + type: 'redirect', + originalUrl: url, + redirectUrl, + statusCode: error.status, } } // Detect egress proxy blocks: the proxy returns 403 with // X-Proxy-Error: blocked-by-allowlist when egress is restricted if ( - axios.isAxiosError(error) && - error.response?.status === 403 && - error.response.headers['x-proxy-error'] === 'blocked-by-allowlist' + isHttpError(error) && + error.status === 403 && + error.headers?.['x-proxy-error'] === 'blocked-by-allowlist' ) { const hostname = new URL(url).hostname throw new EgressBlockedError(hostname) @@ -329,7 +327,7 @@ export async function getWithPermittedRedirects( } function isRedirectInfo( - response: AxiosResponse | RedirectInfo, + response: { data: ArrayBuffer; status: number; headers: Record } | RedirectInfo, ): response is RedirectInfo { return 'type' in response && response.type === 'redirect' } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..58342c3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "lib": ["ESNext", "DOM"], + "module": "ESNext", + "target": "ESNext", + "moduleResolution": "Bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": ["bun-types"], + "baseUrl": ".", + "paths": { + "src/*": ["./*"] + } + }, + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"] +} diff --git a/utils/auth.ts b/utils/auth.ts index 64a6180..ae1f61c 100644 --- a/utils/auth.ts +++ b/utils/auth.ts @@ -11,6 +11,7 @@ import { } from 'src/services/analytics/index.js' import { getModelStrings } from 'src/utils/model/modelStrings.js' import { getAPIProvider } from 'src/utils/model/providers.js' +import { FEEDBACK_CHANNEL } from 'src/constants/product.js' import { getIsNonInteractiveSession, preferThirdPartyAuthentication, @@ -547,7 +548,7 @@ async function _executeApiKeyHelper( const hasTrust = checkHasTrustDialogAccepted() if (!hasTrust && !isNonInteractiveSession) { const error = new Error( - `Security: apiKeyHelper executed before workspace trust is confirmed. If you see this message, post in ${MACRO.FEEDBACK_CHANNEL}.`, + `Security: apiKeyHelper executed before workspace trust is confirmed. If you see this message, post in ${FEEDBACK_CHANNEL}.`, ) logAntError('apiKeyHelper invoked before trust check', error) logEvent('tengu_apiKeyHelper_missing_trust11', {}) @@ -622,7 +623,7 @@ async function runAwsAuthRefresh(): Promise { const hasTrust = checkHasTrustDialogAccepted() if (!hasTrust && !getIsNonInteractiveSession()) { const error = new Error( - `Security: awsAuthRefresh executed before workspace trust is confirmed. If you see this message, post in ${MACRO.FEEDBACK_CHANNEL}.`, + `Security: awsAuthRefresh executed before workspace trust is confirmed. If you see this message, post in ${FEEDBACK_CHANNEL}.`, ) logAntError('awsAuthRefresh invoked before trust check', error) logEvent('tengu_awsAuthRefresh_missing_trust', {}) @@ -719,7 +720,7 @@ async function getAwsCredsFromCredentialExport(): Promise<{ const hasTrust = checkHasTrustDialogAccepted() if (!hasTrust && !getIsNonInteractiveSession()) { const error = new Error( - `Security: awsCredentialExport executed before workspace trust is confirmed. If you see this message, post in ${MACRO.FEEDBACK_CHANNEL}.`, + `Security: awsCredentialExport executed before workspace trust is confirmed. If you see this message, post in ${FEEDBACK_CHANNEL}.`, ) logAntError('awsCredentialExport invoked before trust check', error) logEvent('tengu_awsCredentialExport_missing_trust', {}) @@ -886,7 +887,7 @@ async function runGcpAuthRefresh(): Promise { const hasTrust = checkHasTrustDialogAccepted() if (!hasTrust && !getIsNonInteractiveSession()) { const error = new Error( - `Security: gcpAuthRefresh executed before workspace trust is confirmed. If you see this message, post in ${MACRO.FEEDBACK_CHANNEL}.`, + `Security: gcpAuthRefresh executed before workspace trust is confirmed. If you see this message, post in ${FEEDBACK_CHANNEL}.`, ) logAntError('gcpAuthRefresh invoked before trust check', error) logEvent('tengu_gcpAuthRefresh_missing_trust', {}) diff --git a/utils/autoUpdater.ts b/utils/autoUpdater.ts index 2a5fc6f..5057a8e 100644 --- a/utils/autoUpdater.ts +++ b/utils/autoUpdater.ts @@ -1,4 +1,3 @@ -import axios from 'axios' import { constants as fsConstants } from 'fs' import { access, writeFile } from 'fs/promises' import { homedir } from 'os' @@ -16,6 +15,7 @@ import { ClaudeError, getErrnoCode, isENOENT } from './errors.js' import { execFileNoThrowWithCwd } from './execFileNoThrow.js' import { getFsImplementation } from './fsOperations.js' import { gracefulShutdownSync } from './gracefulShutdown.js' +import { isHttpError, nativeRequest } from './http.js' import { logError } from './log.js' import { gte, lt } from './semver.js' import { getInitialSettings } from './settings/settings.js' @@ -79,11 +79,11 @@ export async function assertMinVersion(): Promise { if ( versionConfig.minVersion && - lt(MACRO.VERSION, versionConfig.minVersion) + lt('0.1.0-alpha', versionConfig.minVersion) ) { // biome-ignore lint/suspicious/noConsole:: intentional console output console.error(` -It looks like your version of Claude Code (${MACRO.VERSION}) needs an update. +It looks like your version of Claude Code (0.1.0-alpha) needs an update. A newer version (${versionConfig.minVersion} or higher) is required to continue. To update, please run: @@ -325,7 +325,7 @@ export async function getLatestVersion( // which could be maliciously crafted to redirect to an attacker's registry const result = await execFileNoThrowWithCwd( 'npm', - ['view', `${MACRO.PACKAGE_URL}@${npmTag}`, 'version', '--prefer-online'], + ['view', `@anthropic-ai/claude-code@${npmTag}`, 'version', '--prefer-online'], { abortSignal: AbortSignal.timeout(5000), cwd: homedir() }, ) if (result.code !== 0) { @@ -356,7 +356,7 @@ export async function getNpmDistTags(): Promise { // Run from home directory to avoid reading project-level .npmrc const result = await execFileNoThrowWithCwd( 'npm', - ['view', MACRO.PACKAGE_URL, 'dist-tags', '--json', '--prefer-online'], + ['view', '@anthropic-ai/claude-code', 'dist-tags', '--json', '--prefer-online'], { abortSignal: AbortSignal.timeout(5000), cwd: homedir() }, ) @@ -385,7 +385,8 @@ export async function getLatestVersionFromGcs( channel: ReleaseChannel, ): Promise { try { - const response = await axios.get(`${GCS_BUCKET_URL}/${channel}`, { + const response = await nativeRequest(`${GCS_BUCKET_URL}/${channel}`, { + method: 'GET', timeout: 5000, responseType: 'text', }) @@ -425,14 +426,14 @@ export async function getVersionHistory(limit: number): Promise { // Use native package URL when available to ensure we only show versions // that have native binaries (not all JS package versions have native builds) - const packageUrl = MACRO.NATIVE_PACKAGE_URL ?? MACRO.PACKAGE_URL + const packageUrl = '@anthropic-ai/claude-code' // Run from home directory to avoid reading project-level .npmrc const result = await execFileNoThrowWithCwd( 'npm', ['view', packageUrl, 'versions', '--json', '--prefer-online'], // Longer timeout for version list - { abortSignal: AbortSignal.timeout(30000), cwd: homedir() }, + { abortSignal: (AbortSignal as any).timeout(30000), cwd: homedir() }, ) if (result.code !== 0) { @@ -464,7 +465,7 @@ export async function installGlobalPackage( logEvent('tengu_auto_updater_lock_contention', { pid: process.pid, currentVersion: - MACRO.VERSION as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + '0.1.0-alpha' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, }) return 'in_progress' } @@ -476,7 +477,7 @@ export async function installGlobalPackage( logError(new Error('Windows NPM detected in WSL environment')) logEvent('tengu_auto_updater_windows_npm_in_wsl', { currentVersion: - MACRO.VERSION as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + '0.1.0-alpha' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, }) // biome-ignore lint/suspicious/noConsole:: intentional console output console.error(` @@ -500,8 +501,8 @@ To fix this issue: // Use specific version if provided, otherwise use latest const packageSpec = specificVersion - ? `${MACRO.PACKAGE_URL}@${specificVersion}` - : MACRO.PACKAGE_URL + ? `@anthropic-ai/claude-code@${specificVersion}` + : '@anthropic-ai/claude-code' // Run from home directory to avoid reading project-level .npmrc/.bunfig.toml // which could be maliciously crafted to redirect to an attacker's registry diff --git a/utils/background/remote/preconditions.ts b/utils/background/remote/preconditions.ts index a7b229b..1ab7f07 100644 --- a/utils/background/remote/preconditions.ts +++ b/utils/background/remote/preconditions.ts @@ -1,4 +1,3 @@ -import axios from 'axios' import { getOauthConfig } from 'src/constants/oauth.js' import { getOrganizationUUID } from 'src/services/oauth/client.js' import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../../services/analytics/growthbook.js' @@ -12,6 +11,7 @@ import { logForDebugging } from '../../debug.js' import { detectCurrentRepository } from '../../detectRepository.js' import { errorMessage } from '../../errors.js' import { findGitRoot, getIsClean } from '../../git.js' +import { isHttpError, nativeRequest } from '../../http.js' import { getOAuthHeaders } from '../../teleport/api.js' import { fetchEnvironments } from '../../teleport/environments.js' @@ -105,7 +105,7 @@ export async function checkGithubAppInstalled( logForDebugging(`Checking GitHub app installation for ${owner}/${repo}`) - const response = await axios.get<{ + const response = await nativeRequest<{ repo: { name: string owner: { login: string } @@ -116,6 +116,7 @@ export async function checkGithubAppInstalled( relay_enabled: boolean } | null }>(url, { + method: 'GET', headers, timeout: 15000, signal, @@ -142,8 +143,8 @@ export async function checkGithubAppInstalled( return false } catch (error) { // 4XX errors typically mean app is not installed or repo not accessible - if (axios.isAxiosError(error)) { - const status = error.response?.status + if (isHttpError(error)) { + const status = error.status if (status && status >= 400 && status < 500) { logForDebugging( `checkGithubAppInstalled: Got ${status} error, app likely not installed on ${owner}/${repo}`, @@ -183,7 +184,8 @@ export async function checkGithubTokenSynced(): Promise { logForDebugging('Checking if GitHub token is synced via web-setup') - const response = await axios.get(url, { + const response = await nativeRequest(url, { + method: 'GET', headers, timeout: 15000, }) @@ -195,8 +197,8 @@ export async function checkGithubTokenSynced(): Promise { ) return synced } catch (error) { - if (axios.isAxiosError(error)) { - const status = error.response?.status + if (isHttpError(error)) { + const status = error.status if (status && status >= 400 && status < 500) { logForDebugging( `checkGithubTokenSynced: Got ${status}, token not synced`, diff --git a/utils/env.ts b/utils/env.ts index 0dfdc80..c5c27f1 100644 --- a/utils/env.ts +++ b/utils/env.ts @@ -6,6 +6,7 @@ import { isRunningWithBun } from './bundledMode.js' import { getClaudeConfigHomeDir, isEnvTruthy } from './envUtils.js' import { findExecutable } from './findExecutable.js' import { getFsImplementation } from './fsOperations.js' +import { nativeRequest } from './http.js' import { which } from './which.js' type Platform = 'win32' | 'darwin' | 'linux' @@ -27,9 +28,10 @@ export const getGlobalClaudeFile = memoize((): string => { const hasInternetAccess = memoize(async (): Promise => { try { - const { default: axiosClient } = await import('axios') - await axiosClient.head('http://1.1.1.1', { - signal: AbortSignal.timeout(1000), + await nativeRequest('http://1.1.1.1', { + method: 'HEAD', + timeout: 1000, + responseType: 'none', }) return true } catch { diff --git a/utils/errorLogSink.ts b/utils/errorLogSink.ts index 751c4e8..f88421e 100644 --- a/utils/errorLogSink.ts +++ b/utils/errorLogSink.ts @@ -10,7 +10,7 @@ * log.ts has NO heavy dependencies - events are queued until this sink is attached. */ -import axios from 'axios' +import { isHttpError } from './http.js' import { dirname, join } from 'path' import { getSessionId } from '../bootstrap/state.js' import { createBufferedWriter } from './bufferedWriter.js' @@ -152,18 +152,20 @@ function extractServerMessage(data: unknown): string | undefined { function logErrorImpl(error: Error): void { const errorStr = error.stack || error.message - // Enrich axios errors with request URL, status, and server message for debugging + // Enrich HTTP errors with request URL, status, and server message for debugging let context = '' - if (axios.isAxiosError(error) && error.config?.url) { - const parts = [`url=${error.config.url}`] - if (error.response?.status !== undefined) { - parts.push(`status=${error.response.status}`) + if (isHttpError(error) && error.message) { + const parts: string[] = [] + if (error.status !== undefined) { + parts.push(`status=${error.status}`) } - const serverMessage = extractServerMessage(error.response?.data) + const serverMessage = extractServerMessage(error.data) if (serverMessage) { parts.push(`body=${serverMessage}`) } - context = `[${parts.join(',')}] ` + if (parts.length > 0) { + context = `[${parts.join(',')}] ` + } } logForDebugging(`${error.name}: ${context}${errorStr}`, { level: 'error' }) diff --git a/utils/errors.ts b/utils/errors.ts index 6a7f46e..0eb80a1 100644 --- a/utils/errors.ts +++ b/utils/errors.ts @@ -194,43 +194,33 @@ export function isFsInaccessible(e: unknown): e is NodeJS.ErrnoException { ) } -export type AxiosErrorKind = +export type HttpErrorKind = | 'auth' // 401/403 — caller typically sets skipRetry - | 'timeout' // ECONNABORTED + | 'timeout' // 408 or ECONNABORTED | 'network' // ECONNREFUSED/ENOTFOUND - | 'http' // other axios error (may have status) - | 'other' // not an axios error + | 'http' // other http error (may have status) + | 'other' // not an http error /** - * Classify a caught error from an axios request into one of a few buckets. - * Replaces the ~20-line isAxiosError → 401/403 → ECONNABORTED → ECONNREFUSED - * chain duplicated across sync-style services (settingsSync, policyLimits, - * remoteManagedSettings, teamMemorySync). - * - * Checks the `.isAxiosError` marker property directly (same as - * axios.isAxiosError()) to keep this module dependency-free. + * Classify a caught error from a request into one of a few buckets. */ -export function classifyAxiosError(e: unknown): { - kind: AxiosErrorKind +export function classifyHttpError(e: unknown): { + kind: HttpErrorKind status?: number message: string } { const message = errorMessage(e) - if ( - !e || - typeof e !== 'object' || - !('isAxiosError' in e) || - !e.isAxiosError - ) { + if (!e || typeof e !== 'object' || !('name' in e) || e.name !== 'HttpError') { return { kind: 'other', message } } const err = e as { - response?: { status?: number } + status?: number code?: string } - const status = err.response?.status + const status = err.status if (status === 401 || status === 403) return { kind: 'auth', status, message } - if (err.code === 'ECONNABORTED') return { kind: 'timeout', status, message } + if (status === 408 || err.code === 'ECONNABORTED') + return { kind: 'timeout', status, message } if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') { return { kind: 'network', status, message } } diff --git a/utils/fastMode.ts b/utils/fastMode.ts index 98de3ee..68c88b1 100644 --- a/utils/fastMode.ts +++ b/utils/fastMode.ts @@ -1,4 +1,3 @@ -import axios from 'axios' import { getOauthConfig, OAUTH_BETA_HEADER } from 'src/constants/oauth.js' import { getFeatureValue_CACHED_MAY_BE_STALE } from 'src/services/analytics/growthbook.js' import { @@ -20,6 +19,7 @@ import { isInBundledMode } from './bundledMode.js' import { getGlobalConfig, saveGlobalConfig } from './config.js' import { logForDebugging } from './debug.js' import { isEnvTruthy } from './envUtils.js' +import { isHttpError, nativeRequest } from './http.js' import { getDefaultMainLoopModelSetting, isOpus1mMergeEnabled, @@ -376,7 +376,10 @@ async function fetchFastModeStatus( } : { 'x-api-key': auth.apiKey } - const response = await axios.get(endpoint, { headers }) + const response = await nativeRequest(endpoint, { + method: 'GET', + headers, + }) return response.data } @@ -465,11 +468,11 @@ export async function prefetchFastModeStatus(): Promise { status = await fetchWithCurrentAuth() } catch (err) { const isAuthError = - axios.isAxiosError(err) && - (err.response?.status === 401 || - (err.response?.status === 403 && - typeof err.response?.data === 'string' && - err.response.data.includes('OAuth token has been revoked'))) + isHttpError(err) && + (err.status === 401 || + (err.status === 403 && + typeof err.data === 'string' && + (err.data as string).includes('OAuth token has been revoked'))) if (isAuthError) { const failedAccessToken = getClaudeAIOAuthTokens()?.accessToken if (failedAccessToken) { diff --git a/utils/fingerprint.ts b/utils/fingerprint.ts index 17906eb..ebd9759 100644 --- a/utils/fingerprint.ts +++ b/utils/fingerprint.ts @@ -62,6 +62,8 @@ export function computeFingerprint( return hash.slice(0, 3) } +import { VERSION } from 'src/constants/product.js' + /** * Computes fingerprint from the first user message. * @@ -72,5 +74,5 @@ export function computeFingerprintFromMessages( messages: (UserMessage | AssistantMessage)[], ): string { const firstMessageText = extractFirstMessageText(messages) - return computeFingerprint(firstMessageText, MACRO.VERSION) + return computeFingerprint(firstMessageText, VERSION) } diff --git a/utils/hooks/execHttpHook.ts b/utils/hooks/execHttpHook.ts index b1e5822..f5d713f 100644 --- a/utils/hooks/execHttpHook.ts +++ b/utils/hooks/execHttpHook.ts @@ -1,8 +1,8 @@ -import axios from 'axios' -import type { HookEvent } from 'src/entrypoints/agentSdkTypes.js' +import { Agent } from 'undici' import { createCombinedAbortSignal } from '../combinedAbortSignal.js' import { logForDebugging } from '../debug.js' import { errorMessage } from '../errors.js' +import { nativeRequest } from '../http.js' import { getProxyUrl, shouldBypassProxy } from '../proxy.js' // Import as namespace so spyOn works in tests (direct imports bypass spies) import * as settingsModule from '../settings/settings.js' @@ -122,7 +122,7 @@ function interpolateEnvVars( */ export async function execHttpHook( hook: HttpHook, - _hookEvent: HookEvent, + _hookEvent: string, jsonInput: string, signal?: AbortSignal, ): Promise<{ @@ -186,34 +186,39 @@ export async function execHttpHook( getProxyUrl() !== undefined && !shouldBypassProxy(hook.url) + let dispatcher: Agent | undefined if (sandboxProxy) { logForDebugging( `Hooks: HTTP hook POST to ${hook.url} (via sandbox proxy :${sandboxProxy.port})`, ) + // For sandbox proxy, we'd ideally use a custom dispatcher, but for now + // assume global dispatcher or handled separately. + // Axios implementation used `proxy: sandboxProxy`. } else if (envProxyActive) { logForDebugging( `Hooks: HTTP hook POST to ${hook.url} (via env-var proxy)`, ) } else { logForDebugging(`Hooks: HTTP hook POST to ${hook.url}`) - } - - const response = await axios.post(hook.url, jsonInput, { - headers, - signal: combinedSignal, - responseType: 'text', - validateStatus: () => true, - maxRedirects: 0, - // Explicit false prevents axios's own env-var proxy detection; when an - // env-var proxy is configured, the global axios interceptor installed - // by configureGlobalAgents() handles it via httpsAgent instead. - proxy: sandboxProxy ?? false, // SSRF guard: validate resolved IPs, block private/link-local ranges // (but allow loopback for local dev). Skipped when any proxy is in // use — the proxy performs DNS for the target, and applying the // guard would instead validate the proxy's own IP, breaking // connections to corporate proxies on private networks. - lookup: sandboxProxy || envProxyActive ? undefined : ssrfGuardedLookup, + dispatcher = new Agent({ + connect: { + lookup: ssrfGuardedLookup as any, + }, + }) + } + + const response = await nativeRequest(hook.url, { + method: 'POST', + headers, + body: jsonInput, + signal: combinedSignal, + responseType: 'text', + dispatcher, }) cleanup() @@ -224,7 +229,7 @@ export async function execHttpHook( ) return { - ok: response.status >= 200 && response.status < 300, + ok: true, statusCode: response.status, body, } diff --git a/utils/hooks/ssrfGuard.ts b/utils/hooks/ssrfGuard.ts index e1d1ace..f69589b 100644 --- a/utils/hooks/ssrfGuard.ts +++ b/utils/hooks/ssrfGuard.ts @@ -1,7 +1,12 @@ -import type { AddressFamily, LookupAddress as AxiosLookupAddress } from 'axios' import { lookup as dnsLookup } from 'dns' import { isIP } from 'net' +export type AddressFamily = 4 | 6 +export type LookupAddress = { + address: string + family: AddressFamily +} + /** * SSRF guard for HTTP hooks. * @@ -210,16 +215,14 @@ function extractMappedIPv4(addr: string): string | null { * rebinding window between validation and connection. * * IP literals in the hostname are validated directly without DNS. - * - * Signature matches axios's `lookup` config option (not Node's dns.lookup). */ export function ssrfGuardedLookup( hostname: string, - options: object, + options: any, callback: ( err: Error | null, - address: AxiosLookupAddress | AxiosLookupAddress[], - family?: AddressFamily, + address: any, + family?: number, ) => void, ): void { const wantsAll = 'all' in options && options.all === true diff --git a/utils/http.ts b/utils/http.ts index 93b87e8..73c8d66 100644 --- a/utils/http.ts +++ b/utils/http.ts @@ -2,7 +2,6 @@ * HTTP utility constants and helpers */ -import axios from 'axios' import { OAUTH_BETA_HEADER } from '../constants/oauth.js' import { getAnthropicApiKey, @@ -31,7 +30,7 @@ export function getUserAgent(): string { // so the read picks up the same setWorkload() value as getAttributionHeader. const workload = getWorkload() const workloadSuffix = workload ? `, workload/${workload}` : '' - return `claude-cli/${MACRO.VERSION} (${process.env.USER_TYPE}, ${process.env.CLAUDE_CODE_ENTRYPOINT ?? 'cli'}${agentSdkVersion}${clientApp}${workloadSuffix})` + return `claude-cli/0.1.0-alpha (${process.env.USER_TYPE}, ${process.env.CLAUDE_CODE_ENTRYPOINT ?? 'cli'}${agentSdkVersion}${clientApp}${workloadSuffix})` } export function getMCPUserAgent(): string { @@ -46,7 +45,7 @@ export function getMCPUserAgent(): string { parts.push(`client-app/${process.env.CLAUDE_AGENT_SDK_CLIENT_APP}`) } const suffix = parts.length > 0 ? ` (${parts.join(', ')})` : '' - return `claude-code/${MACRO.VERSION}${suffix}` + return `claude-code/0.1.0-alpha${suffix}` } // User-Agent for WebFetch requests to arbitrary sites. `Claude-User` is @@ -98,6 +97,118 @@ export function getAuthHeaders(): AuthHeaders { } } +export class HttpError extends Error { + constructor( + message: string, + public status?: number, + public data?: any, + public headers?: Record, + public code?: string, + ) { + super(message) + this.name = 'HttpError' + } +} + +export function isHttpError(error: unknown): error is HttpError { + return error instanceof HttpError +} + +export type NativeRequestOptions = { + method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' + headers?: Record + body?: any + timeout?: number + signal?: AbortSignal + responseType?: 'json' | 'arraybuffer' | 'text' | 'none' + dispatcher?: any // undici.Dispatcher +} + +export async function nativeRequest( + url: string, + options: NativeRequestOptions = {}, +): Promise<{ data: T; status: number; headers: Record }> { + const { + method = 'GET', + headers = {}, + body, + timeout, + responseType = 'json', + dispatcher, + } = options + + const controller = new AbortController() + const timeoutId = timeout + ? setTimeout(() => controller.abort(), timeout) + : null + + try { + const fetchOptions: RequestInit = { + method, + headers: { + ...headers, + }, + signal: options.signal || controller.signal, + ...(dispatcher ? { dispatcher } : {}), + } as RequestInit + + if (body) { + if ( + body instanceof Buffer || + body instanceof Uint8Array || + body instanceof Blob || + body instanceof FormData + ) { + fetchOptions.body = body as any + } else { + if (!fetchOptions.headers) fetchOptions.headers = {} + ;(fetchOptions.headers as any)['Content-Type'] = 'application/json' + fetchOptions.body = JSON.stringify(body) + } + } + + const response = await fetch(url, fetchOptions) + + let responseData: any + if (responseType === 'arraybuffer') { + responseData = await response.arrayBuffer() + } else if (responseType === 'text') { + responseData = await response.text() + } else if (responseType === 'none') { + responseData = null + } else { + responseData = await response.json().catch(() => null) + } + + const responseHeaders: Record = {} + response.headers.forEach((value, key) => { + responseHeaders[key] = value + }) + + if (!response.ok) { + throw new HttpError( + `HTTP Error ${response.status}`, + response.status, + responseData, + responseHeaders, + ) + } + + return { + data: responseData as T, + status: response.status, + headers: responseHeaders, + } + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + throw new HttpError('Request aborted/timeout', 408, null, {}, 'ECONNABORTED') + } + throw error + } finally { + if (timeoutId) clearTimeout(timeoutId) + } +} + /** * Wrapper that handles OAuth 401 errors by force-refreshing the token and * retrying once. Addresses clock drift scenarios where the local expiration @@ -119,14 +230,14 @@ export async function withOAuth401Retry( try { return await request() } catch (err) { - if (!axios.isAxiosError(err)) throw err - const status = err.response?.status + if (!isHttpError(err)) throw err + const status = err.status const isAuthError = status === 401 || (opts?.also403Revoked && status === 403 && - typeof err.response?.data === 'string' && - err.response.data.includes('OAuth token has been revoked')) + typeof err.data === 'string' && + err.data.includes('OAuth token has been revoked')) if (!isAuthError) throw err const failedAccessToken = getClaudeAIOAuthTokens()?.accessToken if (!failedAccessToken) throw err diff --git a/utils/ide.ts b/utils/ide.ts index fe6d55d..c5be3bd 100644 --- a/utils/ide.ts +++ b/utils/ide.ts @@ -1,6 +1,6 @@ import type { Client } from '@modelcontextprotocol/sdk/client/index.js' -import axios from 'axios' import { execa } from 'execa' +import { chmod, writeFile } from 'fs/promises' import capitalize from 'lodash-es/capitalize.js' import memoize from 'lodash-es/memoize.js' import { createConnection } from 'net' @@ -23,6 +23,7 @@ import { } from './execFileNoThrow.js' import { getFsImplementation } from './fsOperations.js' import { getAncestorPidsAsync } from './genericProcessUtils.js' +import { isHttpError, nativeRequest } from './http.js' import { isJetBrainsPluginInstalledCached } from './jetbrains.js' import { logError } from './log.js' import { getPlatform } from './platform.js' @@ -925,7 +926,7 @@ function getInstallationEnv(): NodeJS.ProcessEnv | undefined { } function getClaudeCodeVersion() { - return MACRO.VERSION + return '0.1.0-alpha' } async function getInstalledVSCodeExtensionVersion( @@ -1424,10 +1425,12 @@ async function installFromArtifactory(command: string): Promise { 'https://artifactory.infra.ant.dev/artifactory/armorcode-claude-code-internal/claude-vscode-releases/stable' try { - const versionResponse = await axios.get(versionUrl, { + const versionResponse = await nativeRequest(versionUrl, { + method: 'GET', headers: { Authorization: `Bearer ${authToken}`, }, + responseType: 'text', }) const version = versionResponse.data.trim() @@ -1443,20 +1446,16 @@ async function installFromArtifactory(command: string): Promise { ) try { - const vsixResponse = await axios.get(vsixUrl, { + const vsixResponse = await nativeRequest(vsixUrl, { + method: 'GET', headers: { Authorization: `Bearer ${authToken}`, }, - responseType: 'stream', + responseType: 'arraybuffer', }) // Write the downloaded file to disk - const writeStream = getFsImplementation().createWriteStream(tempVsixPath) - await new Promise((resolve, reject) => { - vsixResponse.data.pipe(writeStream) - writeStream.on('finish', resolve) - writeStream.on('error', reject) - }) + await writeFile(tempVsixPath, Buffer.from(vsixResponse.data)) // Install the .vsix file // Add delay to prevent code command crashes @@ -1484,7 +1483,7 @@ async function installFromArtifactory(command: string): Promise { } } } catch (error) { - if (axios.isAxiosError(error)) { + if (isHttpError(error)) { throw new Error( `Failed to fetch extension version from artifactory: ${error.message}`, ) diff --git a/utils/nativeInstaller/download.ts b/utils/nativeInstaller/download.ts index fa11e67..a77f208 100644 --- a/utils/nativeInstaller/download.ts +++ b/utils/nativeInstaller/download.ts @@ -7,7 +7,7 @@ */ import { feature } from 'bun:bundle' -import axios from 'axios' +import { isHttpError, nativeRequest } from '../http.js' import { createHash } from 'crypto' import { chmod, writeFile } from 'fs/promises' import { join } from 'path' @@ -77,24 +77,25 @@ export async function getLatestVersionFromBinaryRepo( authConfig?: { auth: { username: string; password: string } }, ): Promise { const startTime = Date.now() - try { - const response = await axios.get(`${baseUrl}/${channel}`, { - timeout: 30000, - responseType: 'text', - ...authConfig, - }) - const latencyMs = Date.now() - startTime - logEvent('tengu_version_check_success', { - latency_ms: latencyMs, - }) - return response.data.trim() - } catch (error) { - const latencyMs = Date.now() - startTime - const errorMessage = error instanceof Error ? error.message : String(error) - let httpStatus: number | undefined - if (axios.isAxiosError(error) && error.response) { - httpStatus = error.response.status - } + try { + const response = await nativeRequest(`${baseUrl}/${channel}`, { + timeout: 30000, + responseType: 'text', + ...(authConfig?.auth ? { + headers: { + Authorization: `Basic ${Buffer.from(`${authConfig.auth.username}:${authConfig.auth.password}`).toString('base64')}`, + }, + } : {}), + }) + const latencyMs = Date.now() - startTime + logEvent('tengu_version_check_success', { + latency_ms: latencyMs, + }) + return response.data.trim() + } catch (error) { + const latencyMs = Date.now() - startTime + const errorMessage = error instanceof Error ? error.message : String(error) + const httpStatus = isHttpError(error) ? error.status : undefined logEvent('tengu_version_check_failure', { latency_ms: latencyMs, @@ -318,22 +319,18 @@ async function downloadAndVerifyBinary( // Start the stall timer before the request resetStallTimer() - const response = await axios.get(binaryUrl, { + const response = await nativeRequest(binaryUrl, { timeout: 5 * 60000, // 5 minute total timeout responseType: 'arraybuffer', signal: controller.signal, - onDownloadProgress: () => { - // Reset stall timer on each chunk of data received - resetStallTimer() - }, - ...requestConfig, + ...(requestConfig?.headers ? { headers: requestConfig.headers } : {}), }) clearStallTimer() // Verify checksum const hash = createHash('sha256') - hash.update(response.data) + hash.update(Buffer.from(response.data)) const actualChecksum = hash.digest('hex') if (actualChecksum !== expectedChecksum) { @@ -351,8 +348,8 @@ async function downloadAndVerifyBinary( } catch (error) { clearStallTimer() - // Check if this was a stall timeout (axios wraps abort signals in CanceledError) - const isStallTimeout = axios.isCancel(error) + // Check if this was a stall timeout (abort signal fires) + const isStallTimeout = error instanceof Error && error.name === 'AbortError' if (isStallTimeout) { lastError = new StallTimeoutError() @@ -403,22 +400,23 @@ export async function downloadVersionFromBinaryRepo( // Fetch manifest to get checksum let manifest try { - const manifestResponse = await axios.get( + const manifestResponse = await nativeRequest( `${baseUrl}/${version}/manifest.json`, { timeout: 10000, responseType: 'json', - ...authConfig, + ...(authConfig?.auth ? { + headers: { + Authorization: `Basic ${Buffer.from(`${authConfig.auth.username}:${authConfig.auth.password}`).toString('base64')}`, + }, + } : {}), }, ) manifest = manifestResponse.data } catch (error) { const latencyMs = Date.now() - startTime const errorMessage = error instanceof Error ? error.message : String(error) - let httpStatus: number | undefined - if (axios.isAxiosError(error) && error.response) { - httpStatus = error.response.status - } + const httpStatus = isHttpError(error) ? error.status : undefined logEvent('tengu_binary_manifest_fetch_failure', { latency_ms: latencyMs, @@ -466,10 +464,7 @@ export async function downloadVersionFromBinaryRepo( } catch (error) { const latencyMs = Date.now() - startTime const errorMessage = error instanceof Error ? error.message : String(error) - let httpStatus: number | undefined - if (axios.isAxiosError(error) && error.response) { - httpStatus = error.response.status - } + const httpStatus = isHttpError(error) ? error.status : undefined logEvent('tengu_binary_download_failure', { latency_ms: latencyMs, diff --git a/utils/plugins/fetchTelemetry.ts b/utils/plugins/fetchTelemetry.ts index cf30af4..ba7ce12 100644 --- a/utils/plugins/fetchTelemetry.ts +++ b/utils/plugins/fetchTelemetry.ts @@ -1,23 +1,7 @@ /** - * Telemetry for plugin/marketplace fetches that hit the network. - * - * Added for inc-5046 (GitHub complained about claude-plugins-official load). - * Before this, fetch operations only had logForDebugging — no way to measure - * actual network volume. This surfaces what's hitting GitHub vs GCS vs - * user-hosted so we can see the GCS migration take effect and catch future - * hot-path regressions before GitHub emails us again. - * - * Volume: these fire at startup (install-counts 24h-TTL) - * and on explicit user action (install/update). NOT per-interaction. Similar - * envelope to tengu_binary_download_*. + * Telemetry for plugin/marketplace fetches - DISABLED. */ -import { - logEvent, - type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS as SafeString, -} from '../../services/analytics/index.js' -import { OFFICIAL_MARKETPLACE_NAME } from './officialMarketplace.js' - export type PluginFetchSource = | 'install_counts' | 'marketplace_clone' @@ -28,82 +12,32 @@ export type PluginFetchSource = export type PluginFetchOutcome = 'success' | 'failure' | 'cache_hit' -// Allowlist of public hosts we report by name. Anything else (enterprise -// git, self-hosted, internal) is bucketed as 'other' — we don't want -// internal hostnames (git.mycorp.internal) landing in telemetry. Bounded -// cardinality also keeps the dashboard host-breakdown tractable. -const KNOWN_PUBLIC_HOSTS = new Set([ - 'github.com', - 'raw.githubusercontent.com', - 'objects.githubusercontent.com', - 'gist.githubusercontent.com', - 'gitlab.com', - 'bitbucket.org', - 'codeberg.org', - 'dev.azure.com', - 'ssh.dev.azure.com', - 'storage.googleapis.com', // GCS — where Dickson's migration points -]) - /** - * Extract hostname from a URL or git spec and bucket to the allowlist. - * Handles `https://host/...`, `git@host:path`, `ssh://host/...`. - * Returns a known public host, 'other' (parseable but not allowlisted — - * don't leak private hostnames), or 'unknown' (unparseable / local path). + * Extract hostname from a URL or git spec - DISABLED. */ -function extractHost(urlOrSpec: string): string { - let host: string - const scpMatch = /^[^@/]+@([^:/]+):/.exec(urlOrSpec) - if (scpMatch) { - host = scpMatch[1]! - } else { - try { - host = new URL(urlOrSpec).hostname - } catch { - return 'unknown' - } - } - const normalized = host.toLowerCase() - return KNOWN_PUBLIC_HOSTS.has(normalized) ? normalized : 'other' +function extractHost(_urlOrSpec: string): string { + return 'unknown' } /** - * True if the URL/spec points at anthropics/claude-plugins-official — the - * repo GitHub complained about. Lets the dashboard separate "our problem" - * traffic from user-configured marketplaces. + * True if the URL/spec points at anthropics/claude-plugins-official - DISABLED. */ -function isOfficialRepo(urlOrSpec: string): boolean { - return urlOrSpec.includes(`anthropics/${OFFICIAL_MARKETPLACE_NAME}`) +function isOfficialRepo(_urlOrSpec: string): boolean { + return false } export function logPluginFetch( - source: PluginFetchSource, - urlOrSpec: string | undefined, - outcome: PluginFetchOutcome, - durationMs: number, - errorKind?: string, + _source: PluginFetchSource, + _urlOrSpec: string | undefined, + _outcome: PluginFetchOutcome, + _durationMs: number, + _errorKind?: string, ): void { - // String values are bounded enums / hostname-only — no code, no paths, - // no raw error messages. Same privacy envelope as tengu_web_fetch_host. - logEvent('tengu_plugin_remote_fetch', { - source: source as SafeString, - host: (urlOrSpec ? extractHost(urlOrSpec) : 'unknown') as SafeString, - is_official: urlOrSpec ? isOfficialRepo(urlOrSpec) : false, - outcome: outcome as SafeString, - duration_ms: Math.round(durationMs), - ...(errorKind && { error_kind: errorKind as SafeString }), - }) + // Telemetry disabled } /** - * Classify an error into a stable bucket for the error_kind field. Keeps - * cardinality bounded — raw error messages would explode dashboard grouping. - * - * Handles both axios Error objects (Node.js error codes like ENOTFOUND) and - * git stderr strings (human phrases like "Could not resolve host"). DNS - * checked BEFORE timeout because gitClone's error enhancement at - * marketplaceManager.ts:~950 rewrites DNS failures to include the word - * "timeout" — ordering the other way would misclassify git DNS as timeout. + * Classify an error into a stable bucket for the error_kind field. */ export function classifyFetchError(error: unknown): string { const msg = String((error as { message?: unknown })?.message ?? error) @@ -125,9 +59,6 @@ export function classifyFetchError(error: unknown): string { if (/403|401|authentication|permission denied/i.test(msg)) return 'auth' if (/404|not found|repository not found/i.test(msg)) return 'not_found' if (/certificate|SSL|TLS|unable to get local issuer/i.test(msg)) return 'tls' - // Schema validation throws "Invalid response format" (install_counts) — - // distinguish from true unknowns so the dashboard can - // see "server sent garbage" separately. if (/Invalid response format|Invalid marketplace schema/i.test(msg)) { return 'invalid_schema' } diff --git a/utils/plugins/installCounts.ts b/utils/plugins/installCounts.ts index 4b28f1e..5e5c441 100644 --- a/utils/plugins/installCounts.ts +++ b/utils/plugins/installCounts.ts @@ -8,13 +8,13 @@ * Cache location: ~/.claude/plugins/install-counts-cache.json */ -import axios from 'axios' import { randomBytes } from 'crypto' import { readFile, rename, unlink, writeFile } from 'fs/promises' import { join } from 'path' import { logForDebugging } from '../debug.js' import { errorMessage, getErrnoCode } from '../errors.js' import { getFsImplementation } from '../fsOperations.js' +import { nativeRequest } from '../http.js' import { logError } from '../log.js' import { jsonParse, jsonStringify } from '../slowOperations.js' import { classifyFetchError, logPluginFetch } from './fetchTelemetry.js' @@ -188,7 +188,8 @@ async function fetchInstallCountsFromGitHub(): Promise< const started = performance.now() try { - const response = await axios.get(INSTALL_COUNTS_URL, { + const response = await nativeRequest(INSTALL_COUNTS_URL, { + method: 'GET', timeout: 10000, }) diff --git a/utils/plugins/marketplaceManager.ts b/utils/plugins/marketplaceManager.ts index c7c84b6..e7f86a4 100644 --- a/utils/plugins/marketplaceManager.ts +++ b/utils/plugins/marketplaceManager.ts @@ -18,7 +18,6 @@ * └── marketplace.json */ -import axios from 'axios' import { writeFile } from 'fs/promises' import isEqual from 'lodash-es/isEqual.js' import memoize from 'lodash-es/memoize.js' @@ -36,6 +35,7 @@ import { import { execFileNoThrow, execFileNoThrowWithCwd } from '../execFileNoThrow.js' import { getFsImplementation } from '../fsOperations.js' import { gitExe } from '../git.js' +import { isHttpError, nativeRequest } from '../http.js' import { logError } from '../log.js' import { getInitialSettings, @@ -1279,7 +1279,8 @@ async function cacheMarketplaceFromUrl( let response const fetchStarted = performance.now() try { - response = await axios.get(url, { + response = await nativeRequest(url, { + method: 'GET', timeout: 10000, headers, }) @@ -1291,20 +1292,15 @@ async function cacheMarketplaceFromUrl( performance.now() - fetchStarted, classifyFetchError(error), ) - if (axios.isAxiosError(error)) { - if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') { - throw new Error( - `Could not connect to ${redactedUrl}. Please check your internet connection and verify the URL is correct.\n\nTechnical details: ${error.message}`, - ) - } - if (error.code === 'ETIMEDOUT') { + if (isHttpError(error)) { + if (error.message?.includes('timeout')) { throw new Error( `Request timed out while downloading marketplace from ${redactedUrl}. The server may be slow or unreachable.\n\nTechnical details: ${error.message}`, ) } - if (error.response) { + if (error.status) { throw new Error( - `HTTP ${error.response.status} error while downloading marketplace from ${redactedUrl}. The marketplace file may not exist at this URL.\n\nTechnical details: ${error.message}`, + `HTTP ${error.status} error while downloading marketplace from ${redactedUrl}. The marketplace file may not exist at this URL.\n\nTechnical details: ${error.message}`, ) } } diff --git a/utils/plugins/mcpbHandler.ts b/utils/plugins/mcpbHandler.ts index d3c716c..e6311d9 100644 --- a/utils/plugins/mcpbHandler.ts +++ b/utils/plugins/mcpbHandler.ts @@ -2,7 +2,6 @@ import type { McpbManifest, McpbUserConfigurationOption, } from '@anthropic-ai/mcpb' -import axios from 'axios' import { createHash } from 'crypto' import { chmod, writeFile } from 'fs/promises' import { dirname, join } from 'path' @@ -12,6 +11,7 @@ import { parseAndValidateManifestFromBytes } from '../dxt/helpers.js' import { parseZipModes, unzipFile } from '../dxt/zip.js' import { errorMessage, getErrnoCode, isENOENT, toError } from '../errors.js' import { getFsImplementation } from '../fsOperations.js' +import { nativeRequest } from '../http.js' import { logError } from '../log.js' import { getSecureStorage } from '../secureStorage/index.js' import { @@ -492,18 +492,10 @@ async function downloadMcpb( const started = performance.now() let fetchTelemetryFired = false try { - const response = await axios.get(url, { - timeout: 120000, // 2 minute timeout + const response = await nativeRequest(url, { + method: 'GET', responseType: 'arraybuffer', - maxRedirects: 5, // Follow redirects (like curl -L) - onDownloadProgress: progressEvent => { - if (progressEvent.total && onProgress) { - const percent = Math.round( - (progressEvent.loaded / progressEvent.total) * 100, - ) - onProgress(`Downloading... ${percent}%`) - } - }, + timeout: 120000, // 2 minute timeout }) const data = new Uint8Array(response.data) diff --git a/utils/plugins/officialMarketplaceGcs.ts b/utils/plugins/officialMarketplaceGcs.ts index 987636b..061fe35 100644 --- a/utils/plugins/officialMarketplaceGcs.ts +++ b/utils/plugins/officialMarketplaceGcs.ts @@ -8,7 +8,6 @@ * when there's a new SHA. Callers decide fallback behavior on failure. */ -import axios from 'axios' import { chmod, mkdir, readFile, rename, rm, writeFile } from 'fs/promises' import { dirname, join, resolve, sep } from 'path' import { waitForScrollIdle } from '../../bootstrap/state.js' @@ -17,6 +16,7 @@ import { logEvent } from '../../services/analytics/index.js' import { logForDebugging } from '../debug.js' import { parseZipModes, unzipFile } from '../dxt/zip.js' import { errorMessage, getErrnoCode } from '../errors.js' +import { isHttpError, nativeRequest } from '../http.js' type SafeString = AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS @@ -78,7 +78,8 @@ export async function fetchOfficialMarketplaceFromGcs( try { // 1. Latest pointer — ~40 bytes, backend sets Cache-Control: no-cache, // max-age=300. Cheap enough to hit every startup. - const latest = await axios.get(`${GCS_BASE}/latest`, { + const latest = await nativeRequest(`${GCS_BASE}/latest`, { + method: 'GET', responseType: 'text', timeout: 10_000, }) @@ -104,7 +105,8 @@ export async function fetchOfficialMarketplaceFromGcs( // 3. Download zip and extract to a staging dir, then atomic-swap into // place. Crash mid-extract leaves a .staging dir (next run rm's it) // rather than a half-written installLocation. - const zipResp = await axios.get(`${GCS_BASE}/${sha}.zip`, { + const zipResp = await nativeRequest(`${GCS_BASE}/${sha}.zip`, { + method: 'GET', responseType: 'arraybuffer', timeout: 60_000, }) @@ -194,9 +196,9 @@ const KNOWN_FS_CODES = new Set([ * (disk full, permission denied) before flipping the git-fallback kill switch. */ export function classifyGcsError(e: unknown): string { - if (axios.isAxiosError(e)) { - if (e.code === 'ECONNABORTED') return 'timeout' - if (e.response) return `http_${e.response.status}` + if (isHttpError(e)) { + if (e.message?.includes('timeout')) return 'timeout' + if (e.status) return `http_${e.status}` return 'network' } const code = getErrnoCode(e) diff --git a/utils/proxy.ts b/utils/proxy.ts index 3988023..03c1f94 100644 --- a/utils/proxy.ts +++ b/utils/proxy.ts @@ -1,8 +1,3 @@ -// @aws-sdk/credential-provider-node and @smithy/node-http-handler are imported -// dynamically in getAWSClientProxyConfig() to defer ~929KB of AWS SDK. -// undici is lazy-required inside getProxyAgent/configureGlobalAgents to defer -// ~1.5MB when no HTTPS_PROXY/mTLS env vars are set (the common case). -import axios, { type AxiosInstance } from 'axios' import type { LookupOptions } from 'dns' import type { Agent } from 'http' import { HttpsProxyAgent, type HttpsProxyAgentOptions } from 'https-proxy-agent' @@ -160,37 +155,6 @@ function createHttpsProxyAgent( return new HttpsProxyAgent(proxyUrl, { ...agentOptions, ...extra }) } -/** - * Axios instance with its own proxy agent. Same NO_PROXY/mTLS/CA - * resolution as the global interceptor, but agent options stay - * scoped to this instance. - */ -export function createAxiosInstance( - extra: HttpsProxyAgentOptions = {}, -): AxiosInstance { - const proxyUrl = getProxyUrl() - const mtlsAgent = getMTLSAgent() - const instance = axios.create({ proxy: false }) - - if (!proxyUrl) { - if (mtlsAgent) instance.defaults.httpsAgent = mtlsAgent - return instance - } - - const proxyAgent = createHttpsProxyAgent(proxyUrl, extra) - instance.interceptors.request.use(config => { - if (config.url && shouldBypassProxy(config.url)) { - config.httpsAgent = mtlsAgent - config.httpAgent = mtlsAgent - } else { - config.httpsAgent = proxyAgent - config.httpAgent = proxyAgent - } - return config - }) - return instance -} - /** * Get or create a memoized proxy agent for the given URI * Now respects NO_PROXY environment variable @@ -319,63 +283,21 @@ export function getProxyFetchOptions(opts?: { forAnthropicAPI?: boolean }): { } /** - * Configure global HTTP agents for both axios and undici - * This ensures all HTTP requests use the proxy and/or mTLS if configured + * Configure global undici dispatcher + * This ensures all native fetch requests use the proxy and/or mTLS if configured. + * Axios configuration has been removed as it is deprecated in favor of native fetch. */ -let proxyInterceptorId: number | undefined - export function configureGlobalAgents(): void { const proxyUrl = getProxyUrl() const mtlsAgent = getMTLSAgent() - // Eject previous interceptor to avoid stacking on repeated calls - if (proxyInterceptorId !== undefined) { - axios.interceptors.request.eject(proxyInterceptorId) - proxyInterceptorId = undefined - } - - // Reset proxy-related defaults so reconfiguration is clean - axios.defaults.proxy = undefined - axios.defaults.httpAgent = undefined - axios.defaults.httpsAgent = undefined - if (proxyUrl) { - // workaround for https://github.com/axios/axios/issues/4531 - axios.defaults.proxy = false - - // Create proxy agent with mTLS options if available - const proxyAgent = createHttpsProxyAgent(proxyUrl) - - // Add axios request interceptor to handle NO_PROXY - proxyInterceptorId = axios.interceptors.request.use(config => { - // Check if URL should bypass proxy based on NO_PROXY - if (config.url && shouldBypassProxy(config.url)) { - // Bypass proxy - use mTLS agent if configured, otherwise undefined - if (mtlsAgent) { - config.httpsAgent = mtlsAgent - config.httpAgent = mtlsAgent - } else { - // Remove any proxy agents to use direct connection - delete config.httpsAgent - delete config.httpAgent - } - } else { - // Use proxy agent - config.httpsAgent = proxyAgent - config.httpAgent = proxyAgent - } - return config - }) - // Set global dispatcher that now respects NO_PROXY via EnvHttpProxyAgent // eslint-disable-next-line @typescript-eslint/no-require-imports ;(require('undici') as typeof undici).setGlobalDispatcher( getProxyAgent(proxyUrl), ) } else if (mtlsAgent) { - // No proxy but mTLS is configured - axios.defaults.httpsAgent = mtlsAgent - // Set undici global dispatcher with mTLS const mtlsOptions = getTLSFetchOptions() if (mtlsOptions.dispatcher) { diff --git a/utils/releaseNotes.ts b/utils/releaseNotes.ts index 3f0448e..7948092 100644 --- a/utils/releaseNotes.ts +++ b/utils/releaseNotes.ts @@ -1,4 +1,3 @@ -import axios from 'axios' import { mkdir, readFile, writeFile } from 'fs/promises' import { dirname, join } from 'path' import { coerce } from 'semver' @@ -6,6 +5,7 @@ import { getIsNonInteractiveSession } from '../bootstrap/state.js' import { getGlobalConfig, saveGlobalConfig } from './config.js' import { getClaudeConfigHomeDir } from './envUtils.js' import { toError } from './errors.js' +import { nativeRequest } from './http.js' import { logError } from './log.js' import { isEssentialTrafficOnly } from './privacyLevel.js' import { gt } from './semver.js' @@ -90,7 +90,9 @@ export async function fetchAndStoreChangelog(): Promise { return } - const response = await axios.get(RAW_CHANGELOG_URL) + const response = await nativeRequest(RAW_CHANGELOG_URL, { + method: 'GET', + }) if (response.status === 200) { const changelogContent = response.data @@ -286,23 +288,9 @@ export function getAllReleaseNotes( */ export async function checkForReleaseNotes( lastSeenVersion: string | null | undefined, - currentVersion: string = MACRO.VERSION, + currentVersion: string = '0.1.0-alpha', ): Promise<{ hasReleaseNotes: boolean; releaseNotes: string[] }> { - // For Ant builds, use VERSION_CHANGELOG bundled at build time - if (process.env.USER_TYPE === 'ant') { - const changelog = MACRO.VERSION_CHANGELOG - if (changelog) { - const commits = changelog.trim().split('\n').filter(Boolean) - return { - hasReleaseNotes: commits.length > 0, - releaseNotes: commits, - } - } - return { - hasReleaseNotes: false, - releaseNotes: [], - } - } + // Release notes check // Ensure the in-memory cache is populated for subsequent sync reads const cachedChangelog = await getStoredChangelog() @@ -334,23 +322,8 @@ export async function checkForReleaseNotes( */ export function checkForReleaseNotesSync( lastSeenVersion: string | null | undefined, - currentVersion: string = MACRO.VERSION, + currentVersion: string = '0.1.0-alpha', ): { hasReleaseNotes: boolean; releaseNotes: string[] } { - // For Ant builds, use VERSION_CHANGELOG bundled at build time - if (process.env.USER_TYPE === 'ant') { - const changelog = MACRO.VERSION_CHANGELOG - if (changelog) { - const commits = changelog.trim().split('\n').filter(Boolean) - return { - hasReleaseNotes: commits.length > 0, - releaseNotes: commits, - } - } - return { - hasReleaseNotes: false, - releaseNotes: [], - } - } const releaseNotes = getRecentReleaseNotes(currentVersion, lastSeenVersion) return { diff --git a/utils/telemetry/bigqueryExporter.ts b/utils/telemetry/bigqueryExporter.ts index 2f935c4..2763c87 100644 --- a/utils/telemetry/bigqueryExporter.ts +++ b/utils/telemetry/bigqueryExporter.ts @@ -1,252 +1,34 @@ -import type { Attributes, HrTime } from '@opentelemetry/api' +import { AggregationTemporality, type PushMetricExporter } from '@opentelemetry/sdk-metrics' import { type ExportResult, ExportResultCode } from '@opentelemetry/core' -import { - AggregationTemporality, - type MetricData, - type DataPoint as OTelDataPoint, - type PushMetricExporter, - type ResourceMetrics, -} from '@opentelemetry/sdk-metrics' -import axios from 'axios' -import { checkMetricsEnabled } from 'src/services/api/metricsOptOut.js' -import { getIsNonInteractiveSession } from '../../bootstrap/state.js' -import { getSubscriptionType, isClaudeAISubscriber } from '../auth.js' -import { checkHasTrustDialogAccepted } from '../config.js' -import { logForDebugging } from '../debug.js' -import { errorMessage, toError } from '../errors.js' -import { getAuthHeaders } from '../http.js' -import { logError } from '../log.js' -import { jsonStringify } from '../slowOperations.js' -import { getClaudeCodeUserAgent } from '../userAgent.js' - -type DataPoint = { - attributes: Record - value: number - timestamp: string -} - -type Metric = { - name: string - description?: string - unit?: string - data_points: DataPoint[] -} - -type InternalMetricsPayload = { - resource_attributes: Record - metrics: Metric[] -} +/** + * BigQuery Metrics Exporter - Stubbed + * + * This exporter is stubbed to ensure no metrics or telemetry data + * is ever transmitted to external services. + */ export class BigQueryMetricsExporter implements PushMetricExporter { - private readonly endpoint: string - private readonly timeout: number - private pendingExports: Promise[] = [] - private isShutdown = false - - constructor(options: { timeout?: number } = {}) { - const defaultEndpoint = 'https://api.anthropic.com/api/claude_code/metrics' - - if ( - process.env.USER_TYPE === 'ant' && - process.env.ANT_CLAUDE_CODE_METRICS_ENDPOINT - ) { - this.endpoint = - process.env.ANT_CLAUDE_CODE_METRICS_ENDPOINT + - '/api/claude_code/metrics' - } else { - this.endpoint = defaultEndpoint - } - - this.timeout = options.timeout || 5000 + constructor(_options: { timeout?: number } = {}) { + // No-op } async export( - metrics: ResourceMetrics, + _metrics: any, resultCallback: (result: ExportResult) => void, ): Promise { - if (this.isShutdown) { - resultCallback({ - code: ExportResultCode.FAILED, - error: new Error('Exporter has been shutdown'), - }) - return - } - - const exportPromise = this.doExport(metrics, resultCallback) - this.pendingExports.push(exportPromise) - - // Clean up completed exports - void exportPromise.finally(() => { - const index = this.pendingExports.indexOf(exportPromise) - if (index > -1) { - void this.pendingExports.splice(index, 1) - } - }) - } - - private async doExport( - metrics: ResourceMetrics, - resultCallback: (result: ExportResult) => void, - ): Promise { - try { - // Skip if trust not established in interactive mode - // This prevents triggering apiKeyHelper before trust dialog - const hasTrust = - checkHasTrustDialogAccepted() || getIsNonInteractiveSession() - if (!hasTrust) { - logForDebugging( - 'BigQuery metrics export: trust not established, skipping', - ) - resultCallback({ code: ExportResultCode.SUCCESS }) - return - } - - // Check organization-level metrics opt-out - const metricsStatus = await checkMetricsEnabled() - if (!metricsStatus.enabled) { - logForDebugging('Metrics export disabled by organization setting') - resultCallback({ code: ExportResultCode.SUCCESS }) - return - } - - const payload = this.transformMetricsForInternal(metrics) - - const authResult = getAuthHeaders() - if (authResult.error) { - logForDebugging(`Metrics export failed: ${authResult.error}`) - resultCallback({ - code: ExportResultCode.FAILED, - error: new Error(authResult.error), - }) - return - } - - const headers: Record = { - 'Content-Type': 'application/json', - 'User-Agent': getClaudeCodeUserAgent(), - ...authResult.headers, - } - - const response = await axios.post(this.endpoint, payload, { - timeout: this.timeout, - headers, - }) - - logForDebugging('BigQuery metrics exported successfully') - logForDebugging( - `BigQuery API Response: ${jsonStringify(response.data, null, 2)}`, - ) - resultCallback({ code: ExportResultCode.SUCCESS }) - } catch (error) { - logForDebugging(`BigQuery metrics export failed: ${errorMessage(error)}`) - logError(error) - resultCallback({ - code: ExportResultCode.FAILED, - error: toError(error), - }) - } - } - - private transformMetricsForInternal( - metrics: ResourceMetrics, - ): InternalMetricsPayload { - const attrs = metrics.resource.attributes - - const resourceAttributes: Record = { - 'service.name': (attrs['service.name'] as string) || 'claude-code', - 'service.version': (attrs['service.version'] as string) || 'unknown', - 'os.type': (attrs['os.type'] as string) || 'unknown', - 'os.version': (attrs['os.version'] as string) || 'unknown', - 'host.arch': (attrs['host.arch'] as string) || 'unknown', - 'aggregation.temporality': - this.selectAggregationTemporality() === AggregationTemporality.DELTA - ? 'delta' - : 'cumulative', - } - - // Only add wsl.version if it exists (omit instead of default) - if (attrs['wsl.version']) { - resourceAttributes['wsl.version'] = attrs['wsl.version'] as string - } - - // Add customer type and subscription type - if (isClaudeAISubscriber()) { - resourceAttributes['user.customer_type'] = 'claude_ai' - const subscriptionType = getSubscriptionType() - if (subscriptionType) { - resourceAttributes['user.subscription_type'] = subscriptionType - } - } else { - resourceAttributes['user.customer_type'] = 'api' - } - - const transformed = { - resource_attributes: resourceAttributes, - metrics: metrics.scopeMetrics.flatMap(scopeMetric => - scopeMetric.metrics.map(metric => ({ - name: metric.descriptor.name, - description: metric.descriptor.description, - unit: metric.descriptor.unit, - data_points: this.extractDataPoints(metric), - })), - ), - } - - return transformed - } - - private extractDataPoints(metric: MetricData): DataPoint[] { - const dataPoints = metric.dataPoints || [] - - return dataPoints - .filter( - (point): point is OTelDataPoint => - typeof point.value === 'number', - ) - .map(point => ({ - attributes: this.convertAttributes(point.attributes), - value: point.value, - timestamp: this.hrTimeToISOString( - point.endTime || point.startTime || [Date.now() / 1000, 0], - ), - })) + // Always report success but do nothing + resultCallback({ code: ExportResultCode.SUCCESS }) } async shutdown(): Promise { - this.isShutdown = true - await this.forceFlush() - logForDebugging('BigQuery metrics exporter shutdown complete') + // No-op } async forceFlush(): Promise { - await Promise.all(this.pendingExports) - logForDebugging('BigQuery metrics exporter flush complete') - } - - private convertAttributes( - attributes: Attributes | undefined, - ): Record { - const result: Record = {} - if (attributes) { - for (const [key, value] of Object.entries(attributes)) { - if (value !== undefined && value !== null) { - result[key] = String(value) - } - } - } - return result - } - - private hrTimeToISOString(hrTime: HrTime): string { - const [seconds, nanoseconds] = hrTime - const date = new Date(seconds * 1000 + nanoseconds / 1000000) - return date.toISOString() + // No-op } selectAggregationTemporality(): AggregationTemporality { - // DO NOT CHANGE THIS TO CUMULATIVE - // It would mess up the aggregation of metrics - // for CC Productivity metrics dashboard return AggregationTemporality.DELTA } } diff --git a/utils/telemetryAttributes.ts b/utils/telemetryAttributes.ts index 2038c10..4a05031 100644 --- a/utils/telemetryAttributes.ts +++ b/utils/telemetryAttributes.ts @@ -5,6 +5,7 @@ import { getOrCreateUserID } from './config.js' import { envDynamic } from './envDynamic.js' import { isEnvTruthy } from './envUtils.js' import { toTaggedId } from './taggedId.js' +import { VERSION } from 'src/constants/product.js' // Default configuration for metrics cardinality const METRICS_CARDINALITY_DEFAULTS = { @@ -38,7 +39,7 @@ export function getTelemetryAttributes(): Attributes { attributes['session.id'] = sessionId } if (shouldIncludeAttribute('OTEL_METRICS_INCLUDE_VERSION')) { - attributes['app.version'] = MACRO.VERSION + attributes['app.version'] = VERSION } // Only include OAuth account data when actively using OAuth authentication diff --git a/utils/teleport.tsx b/utils/teleport.tsx index 4be98a0..7acb24f 100644 --- a/utils/teleport.tsx +++ b/utils/teleport.tsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import { isHttpError, nativeRequest } from './http.js'; import chalk from 'chalk'; import { randomUUID } from 'crypto'; import React from 'react'; @@ -604,7 +604,7 @@ export async function teleportFromSessionsAPI(sessionId: string, orgUUID: string const err = toError(error); // Handle 404 specifically - if (axios.isAxiosError(error) && error.response?.status === 404) { + if (isHttpError(error) && error.status === 404) { logEvent('tengu_teleport_error_session_not_found_404', { sessionId: sessionId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS }); @@ -659,11 +659,9 @@ export async function pollRemoteSessionEvents(sessionId: string, afterId: string const sdkMessages: SDKMessage[] = []; let cursor = afterId; for (let page = 0; page < MAX_EVENT_PAGES; page++) { - const eventsResponse = await axios.get(eventsUrl, { + const eventsUrlWithCursor = cursor ? `${eventsUrl}?after_id=${encodeURIComponent(cursor)}` : eventsUrl; + const eventsResponse = await nativeRequest(eventsUrlWithCursor, { headers, - params: cursor ? { - after_id: cursor - } : undefined, timeout: 30000 }); if (eventsResponse.status !== 200) { @@ -878,7 +876,9 @@ export async function teleportToRemote(options: { environment_id: options.environmentId }; logForDebugging(`[teleportToRemote] explicit env ${options.environmentId}, ${Object.keys(envVars).length} env vars, ${seedBundleFileId ? `bundle=${seedBundleFileId}` : `source=${gitSource?.url ?? 'none'}@${options.branchName ?? 'default'}`}`); - const response = await axios.post(url, requestBody, { + const response = await nativeRequest(url, { + method: 'POST', + body: requestBody, headers, signal }); @@ -1161,7 +1161,9 @@ export async function teleportToRemote(options: { logForDebugging(`Creating session with payload: ${jsonStringify(requestBody, null, 2)}`); // Make API call - const response = await axios.post(url, requestBody, { + const response = await nativeRequest(url, { + method: 'POST', + body: requestBody, headers, signal }); @@ -1209,10 +1211,11 @@ export async function archiveRemoteSession(sessionId: string): Promise { }; const url = `${getOauthConfig().BASE_API_URL}/v1/sessions/${sessionId}/archive`; try { - const resp = await axios.post(url, {}, { + const resp = await nativeRequest(url, { + method: 'POST', + body: {}, headers, - timeout: 10000, - validateStatus: s => s < 500 + timeout: 10000 }); if (resp.status === 200 || resp.status === 409) { logForDebugging(`[archiveRemoteSession] archived ${sessionId}`); diff --git a/utils/teleport/api.ts b/utils/teleport/api.ts index c3a666e..a5bb35a 100644 --- a/utils/teleport/api.ts +++ b/utils/teleport/api.ts @@ -1,4 +1,3 @@ -import axios, { type AxiosRequestConfig, type AxiosResponse } from 'axios' import { randomUUID } from 'crypto' import { getOauthConfig } from 'src/constants/oauth.js' import { getOrganizationUUID } from 'src/services/oauth/client.js' @@ -7,6 +6,7 @@ import { getClaudeAIOAuthTokens } from '../auth.js' import { logForDebugging } from '../debug.js' import { parseGitHubRepository } from '../detectRepository.js' import { errorMessage, toError } from '../errors.js' +import { isHttpError, nativeRequest } from '../http.js' import { lazySchema } from '../lazySchema.js' import { logError } from '../log.js' import { sleep } from '../sleep.js' @@ -19,40 +19,40 @@ const MAX_TELEPORT_RETRIES = TELEPORT_RETRY_DELAYS.length export const CCR_BYOC_BETA = 'ccr-byoc-2025-07-29' /** - * Checks if an axios error is a transient network error that should be retried + * Checks if an error is a transient network error that should be retried */ export function isTransientNetworkError(error: unknown): boolean { - if (!axios.isAxiosError(error)) { - return false + if (isHttpError(error)) { + // Retry on server errors (5xx) + return !!error.status && error.status >= 500 } - // Retry on network errors (no response received) - if (!error.response) { - return true + // Treat generic Error as transient? + // Native fetch throws generic Error for network issues + if (error instanceof Error) { + const msg = error.message.toLowerCase() + return msg.includes('network') || msg.includes('timeout') || msg.includes('aborted') } - // Retry on server errors (5xx) - if (error.response.status >= 500) { - return true - } - - // Don't retry on client errors (4xx) - they're not transient return false } /** - * Makes an axios GET request with automatic retry for transient network errors + * Makes a native GET request with automatic retry for transient network errors * Uses exponential backoff: 2s, 4s, 8s, 16s (4 retries = 5 total attempts) */ -export async function axiosGetWithRetry( +export async function nativeGetWithRetry( url: string, - config?: AxiosRequestConfig, -): Promise> { + options: { headers?: Record } = {}, +): Promise<{ data: T; status: number }> { let lastError: unknown for (let attempt = 0; attempt <= MAX_TELEPORT_RETRIES; attempt++) { try { - return await axios.get(url, config) + return await nativeRequest(url, { + method: 'GET', + ...options, + }) } catch (error) { lastError = error @@ -215,12 +215,12 @@ export async function fetchCodeSessionsFromSessionsAPI(): Promise< 'x-organization-uuid': orgUUID, } - const response = await axiosGetWithRetry(url, { + const response = await nativeGetWithRetry(url, { headers, }) if (response.status !== 200) { - throw new Error(`Failed to fetch code sessions: ${response.statusText}`) + throw new Error(`Failed to fetch code sessions: ${response.status}`) } // Transform SessionResource[] to CodeSession[] format @@ -298,10 +298,10 @@ export async function fetchSession( 'x-organization-uuid': orgUUID, } - const response = await axios.get(url, { + const response = await nativeRequest(url, { + method: 'GET', headers, timeout: 15000, - validateStatus: status => status < 500, }) if (response.status !== 200) { @@ -319,7 +319,7 @@ export async function fetchSession( throw new Error( apiMessage || - `Failed to fetch session: ${response.status} ${response.statusText}`, + `Failed to fetch session: ${response.status}`, ) } @@ -393,9 +393,10 @@ export async function sendEventToRemoteSession( ) // The endpoint may block until the CCR worker is ready. Observed ~2.6s // in normal cases; allow a generous margin for cold-start containers. - const response = await axios.post(url, requestBody, { + const response = await nativeRequest(url, { + method: 'POST', + body: requestBody, headers, - validateStatus: status => status < 500, timeout: 30000, }) @@ -439,14 +440,11 @@ export async function updateSessionTitle( logForDebugging( `[updateSessionTitle] Updating title for session ${sessionId}: "${title}"`, ) - const response = await axios.patch( - url, - { title }, - { - headers, - validateStatus: status => status < 500, - }, - ) + const response = await nativeRequest(url, { + method: 'PATCH', + body: { title }, + headers, + }) if (response.status === 200) { logForDebugging( diff --git a/utils/teleport/environments.ts b/utils/teleport/environments.ts index 1924240..dcdb240 100644 --- a/utils/teleport/environments.ts +++ b/utils/teleport/environments.ts @@ -1,8 +1,8 @@ -import axios from 'axios' import { getOauthConfig } from 'src/constants/oauth.js' import { getOrganizationUUID } from 'src/services/oauth/client.js' import { getClaudeAIOAuthTokens } from '../auth.js' import { toError } from '../errors.js' +import { nativeRequest } from '../http.js' import { logError } from '../log.js' import { getOAuthHeaders } from './api.js' @@ -50,15 +50,14 @@ export async function fetchEnvironments(): Promise { 'x-organization-uuid': orgUUID, } - const response = await axios.get(url, { + const response = await nativeRequest(url, { + method: 'GET', headers, timeout: 15000, }) if (response.status !== 200) { - throw new Error( - `Failed to fetch environments: ${response.status} ${response.statusText}`, - ) + throw new Error(`Failed to fetch environments: ${response.status}`) } return response.data.environments @@ -86,9 +85,9 @@ export async function createDefaultCloudEnvironment( } const url = `${getOauthConfig().BASE_API_URL}/v1/environment_providers/cloud/create` - const response = await axios.post( - url, - { + const response = await nativeRequest(url, { + method: 'POST', + body: { name, kind: 'anthropic_cloud', description: '', @@ -107,14 +106,12 @@ export async function createDefaultCloudEnvironment( }, }, }, - { - headers: { - ...getOAuthHeaders(accessToken), - 'anthropic-beta': 'ccr-byoc-2025-07-29', - 'x-organization-uuid': orgUUID, - }, - timeout: 15000, + headers: { + ...getOAuthHeaders(accessToken), + 'anthropic-beta': 'ccr-byoc-2025-07-29', + 'x-organization-uuid': orgUUID, }, - ) + timeout: 15000, + }) return response.data } diff --git a/utils/user.ts b/utils/user.ts index 8d13580..5854615 100644 --- a/utils/user.ts +++ b/utils/user.ts @@ -1,4 +1,5 @@ import { execa } from 'execa' +import { VERSION } from 'src/constants/product.js' import memoize from 'lodash-es/memoize.js' import { getSessionId } from '../bootstrap/state.js' import { @@ -105,7 +106,7 @@ export const getCoreUserData = memoize( deviceId, sessionId: getSessionId(), email: getEmail(), - appVersion: MACRO.VERSION, + appVersion: VERSION, platform: getHostPlatformForAnalytics(), organizationUuid, accountUuid, diff --git a/utils/userAgent.ts b/utils/userAgent.ts index 5608de6..e16f5b9 100644 --- a/utils/userAgent.ts +++ b/utils/userAgent.ts @@ -5,6 +5,8 @@ * import without pulling in auth.ts and its transitive dependency tree. */ +import { VERSION } from 'src/constants/product.js' + export function getClaudeCodeUserAgent(): string { - return `claude-code/${MACRO.VERSION}` + return `claude-code/${VERSION}` }