diff --git a/apps/backend/core/auth.py b/apps/backend/core/auth.py index 78faac550e..b66090de34 100644 --- a/apps/backend/core/auth.py +++ b/apps/backend/core/auth.py @@ -64,6 +64,8 @@ "CLAUDE_CLI_PATH", # Profile's custom config directory (for multi-profile token storage) "CLAUDE_CONFIG_DIR", + # Custom CA certificate for enterprise proxy SSL (Zscaler, etc.) + "NODE_EXTRA_CA_CERTS", ] diff --git a/apps/frontend/src/main/agent/agent-process.test.ts b/apps/frontend/src/main/agent/agent-process.test.ts index e2102d005e..904397853b 100644 --- a/apps/frontend/src/main/agent/agent-process.test.ts +++ b/apps/frontend/src/main/agent/agent-process.test.ts @@ -116,6 +116,12 @@ vi.mock('electron', () => ({ } })); +// Mock settings-utils to avoid electron.app.getPath dependency +vi.mock('../settings-utils', () => ({ + readSettingsFile: vi.fn(() => ({})), + getSettingsPath: vi.fn(() => '/fake/settings.json') +})); + // Mock cli-tool-manager to avoid blocking tool detection on Windows vi.mock('../cli-tool-manager', () => ({ getToolInfo: vi.fn((tool: string) => { diff --git a/apps/frontend/src/main/agent/agent-process.ts b/apps/frontend/src/main/agent/agent-process.ts index f46c9bfc4d..1fd05c5e31 100644 --- a/apps/frontend/src/main/agent/agent-process.ts +++ b/apps/frontend/src/main/agent/agent-process.ts @@ -1,7 +1,7 @@ import { spawn } from 'child_process'; import path from 'path'; import { fileURLToPath } from 'url'; -import { existsSync, readFileSync } from 'fs'; +import { existsSync, readFileSync, statSync } from 'fs'; import { app } from 'electron'; // ESM-compatible __dirname @@ -229,6 +229,27 @@ export class AgentProcessManager { const ghCliEnv = this.detectAndSetCliPath('gh'); const glabCliEnv = this.detectAndSetCliPath('glab'); + // Inject custom CA certificate path for enterprise proxy SSL support + const certEnv: Record = {}; + const appSettingsForCert = readSettingsFile() as Partial | null; + const rawCertPath = appSettingsForCert?.customCACertPath; + const configuredCertPath = typeof rawCertPath === 'string' ? rawCertPath.trim() : undefined; + if (configuredCertPath) { + if (!path.isAbsolute(configuredCertPath)) { + console.warn('[AgentProcess] customCACertPath must be an absolute path, skipping NODE_EXTRA_CA_CERTS:', configuredCertPath); + } else { + try { + if (existsSync(configuredCertPath) && statSync(configuredCertPath).isFile()) { + certEnv['NODE_EXTRA_CA_CERTS'] = configuredCertPath; + } else { + console.warn('[AgentProcess] customCACertPath is missing or not a file, skipping NODE_EXTRA_CA_CERTS:', configuredCertPath); + } + } catch (err) { + console.warn('[AgentProcess] customCACertPath stat failed, skipping NODE_EXTRA_CA_CERTS:', configuredCertPath, err); + } + } + } + // Profile env is spread last to ensure CLAUDE_CONFIG_DIR and auth vars // from the active profile always win over extraEnv or augmentedEnv. const mergedEnv = { @@ -237,6 +258,7 @@ export class AgentProcessManager { ...claudeCliEnv, ...ghCliEnv, ...glabCliEnv, + ...certEnv, ...extraEnv, ...profileEnv, PYTHONUNBUFFERED: '1', diff --git a/apps/frontend/src/main/ipc-handlers/profile-handlers.test.ts b/apps/frontend/src/main/ipc-handlers/profile-handlers.test.ts index 0e115e4647..c28aad92bc 100644 --- a/apps/frontend/src/main/ipc-handlers/profile-handlers.test.ts +++ b/apps/frontend/src/main/ipc-handlers/profile-handlers.test.ts @@ -23,6 +23,12 @@ vi.mock('electron', () => ({ } })); +// Mock settings-utils to avoid electron.app dependency +vi.mock('../settings-utils', () => ({ + readSettingsFile: vi.fn(() => ({})), + getSettingsPath: vi.fn(() => '/test/settings.json') +})); + // Mock profile service vi.mock('../services/profile', () => ({ loadProfilesFile: mockedLoadProfilesFile, @@ -244,7 +250,8 @@ describe('profile-handlers - testConnection', () => { expect(testConnection).toHaveBeenCalledWith( 'https://api.anthropic.com', 'sk-test-key-12chars', - expect.any(AbortSignal) + expect.any(AbortSignal), + undefined ); }); }); diff --git a/apps/frontend/src/main/ipc-handlers/profile-handlers.ts b/apps/frontend/src/main/ipc-handlers/profile-handlers.ts index 9b522a6553..c69f212e8d 100644 --- a/apps/frontend/src/main/ipc-handlers/profile-handlers.ts +++ b/apps/frontend/src/main/ipc-handlers/profile-handlers.ts @@ -13,6 +13,7 @@ import { ipcMain } from 'electron'; import { IPC_CHANNELS } from '../../shared/constants'; import type { IPCResult } from '../../shared/types'; +import type { AppSettings } from '../../shared/types'; import type { APIProfile, ProfileFormData, ProfilesFile, TestConnectionResult, DiscoverModelsResult } from '@shared/types/profile'; import { loadProfilesFile, @@ -25,10 +26,16 @@ import { testConnection, discoverModels } from '../services/profile'; +import { readSettingsFile } from '../settings-utils'; // Track active test connection requests for cancellation const activeTestConnections = new Map(); +function getCustomCaCertPath(): string | undefined { + const settings = readSettingsFile() as Partial | null; + return settings?.customCACertPath || undefined; +} + // Track active discover models requests for cancellation const activeDiscoverModelsRequests = new Map(); @@ -214,7 +221,7 @@ export function registerProfileHandlers(): void { } // Call testConnection from service layer with abort signal - const result = await testConnection(baseUrl, apiKey, controller.signal); + const result = await testConnection(baseUrl, apiKey, controller.signal, getCustomCaCertPath()); // Clear timeout on success clearTimeout(timeoutId); @@ -300,7 +307,7 @@ export function registerProfileHandlers(): void { } // Call discoverModels from service layer with abort signal - const result = await discoverModels(baseUrl, apiKey, controller.signal); + const result = await discoverModels(baseUrl, apiKey, controller.signal, getCustomCaCertPath()); // Clear timeout on success clearTimeout(timeoutId); diff --git a/apps/frontend/src/main/ipc-handlers/settings-handlers.ts b/apps/frontend/src/main/ipc-handlers/settings-handlers.ts index 697711049a..f3ed6709f4 100644 --- a/apps/frontend/src/main/ipc-handlers/settings-handlers.ts +++ b/apps/frontend/src/main/ipc-handlers/settings-handlers.ts @@ -384,6 +384,25 @@ export function registerSettingsHandlers( } ); + ipcMain.handle( + IPC_CHANNELS.DIALOG_SELECT_FILE, + async (_, filters?: { name: string; extensions: string[] }[]): Promise => { + const mainWindow = getMainWindow(); + if (!mainWindow) return null; + + const result = await dialog.showOpenDialog(mainWindow, { + properties: ['openFile'], + filters: filters || [{ name: 'All Files', extensions: ['*'] }] + }); + + if (result.canceled || result.filePaths.length === 0) { + return null; + } + + return result.filePaths[0]; + } + ); + ipcMain.handle( IPC_CHANNELS.DIALOG_CREATE_PROJECT_FOLDER, async ( diff --git a/apps/frontend/src/main/services/profile/profile-service.ts b/apps/frontend/src/main/services/profile/profile-service.ts index c57f60ce25..3e72708080 100644 --- a/apps/frontend/src/main/services/profile/profile-service.ts +++ b/apps/frontend/src/main/services/profile/profile-service.ts @@ -6,6 +6,10 @@ * Uses atomic operations with file locking to prevent TOCTOU race conditions. */ +import http from 'node:http'; +import https from 'node:https'; +import fs from 'node:fs'; +import type { OutgoingHttpHeaders } from 'node:http'; import Anthropic, { AuthenticationError, NotFoundError, @@ -16,6 +20,116 @@ import Anthropic, { import { loadProfilesFile, generateProfileId, atomicModifyProfiles } from './profile-manager'; import type { APIProfile, TestConnectionResult, ModelInfo, DiscoverModelsResult } from '@shared/types/profile'; +/** + * Creates a custom fetch function that routes requests through a Node.js + * http/https Agent configured with the provided CA certificate. + * Used to support custom CA certs (e.g., Zscaler, corporate proxies). + */ +function createFetchWithCA(ca: Buffer): typeof globalThis.fetch { + const httpsAgent = new https.Agent({ ca }); + + return async (input, init): Promise => { + // Derive URL string — use Request.url for Request objects, not toString() + const urlStr = + typeof input === 'string' + ? input + : input instanceof URL + ? input.toString() + : (input as Request).url; + const url = new URL(urlStr); + const method = init?.method ?? 'GET'; + + const rawHeaders: OutgoingHttpHeaders = {}; + const initHeaders = init?.headers; + if (initHeaders) { + if (initHeaders instanceof Headers) { + initHeaders.forEach((v, k) => { rawHeaders[k] = v; }); + } else if (Array.isArray(initHeaders)) { + for (const [k, v] of initHeaders as [string, string][]) rawHeaders[k] = v; + } else { + Object.assign(rawHeaders, initHeaders); + } + } + + // Abort signal: reject immediately if already aborted + const signal = init?.signal ?? undefined; + if (signal?.aborted) { + return Promise.reject(new DOMException('Aborted', 'AbortError')); + } + + return new Promise((resolve, reject) => { + // Support both HTTP and HTTPS — only inject the custom CA agent for HTTPS + const isHttps = url.protocol === 'https:'; + const requestFn = isHttps ? https.request : http.request; + const defaultPort = isHttps ? 443 : 80; + + const req = requestFn( + { + hostname: url.hostname, + port: Number(url.port) || defaultPort, + path: url.pathname + url.search, + method, + headers: rawHeaders, + agent: isHttps ? httpsAgent : undefined, + }, + (res) => { + const chunks: Buffer[] = []; + res.on('data', (c: Buffer) => chunks.push(c)); + res.on('end', () => { + const body = Buffer.concat(chunks); + const headers = new Headers(); + for (const [k, v] of Object.entries(res.headers)) { + if (v !== undefined) { + (Array.isArray(v) ? v : [v]).forEach((val) => headers.append(k, val)); + } + } + resolve(new Response(body, { status: res.statusCode ?? 200, headers })); + }); + res.on('error', reject); + } + ); + + // Wire abort signal to destroy the request + const onAbort = () => { + req.destroy(new DOMException('Aborted', 'AbortError')); + reject(new DOMException('Aborted', 'AbortError')); + }; + signal?.addEventListener('abort', onAbort, { once: true }); + req.on('close', () => signal?.removeEventListener('abort', onAbort)); + + req.on('error', reject); + + // Handle all valid BodyInit types the Anthropic SDK sends + const body = init?.body; + if (body != null) { + if (typeof body === 'string' || body instanceof Uint8Array) { + req.write(body); + } else if (body instanceof URLSearchParams) { + req.write(body.toString()); + } + } + req.end(); + }); + }; +} + +/** + * Build a custom fetch for the Anthropic SDK from a CA cert path. + * Throws with a clear message if the cert file cannot be read, + * so misconfiguration surfaces immediately rather than producing + * a misleading "network error". + */ +function buildCustomFetch(caCertPath?: string): typeof globalThis.fetch | undefined { + if (!caCertPath) return undefined; + try { + const ca = fs.readFileSync(caCertPath); + return createFetchWithCA(ca); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error(`Failed to read CA certificate at "${caCertPath}": ${msg}`); + } +} + /** * Input type for creating a profile (without id, createdAt, updatedAt) */ @@ -309,7 +423,8 @@ export async function getAPIProfileEnv(): Promise> { export async function testConnection( baseUrl: string, apiKey: string, - signal?: AbortSignal + signal?: AbortSignal, + caCertPath?: string ): Promise { // Validate API key first (key format doesn't depend on URL normalization) if (!validateApiKey(apiKey)) { @@ -390,13 +505,26 @@ export async function testConnection( }; } + // Build custom fetch before the SDK try-catch so cert errors surface clearly + let customFetch: typeof globalThis.fetch | undefined; + try { + customFetch = buildCustomFetch(caCertPath); + } catch (certErr) { + return { + success: false, + errorType: 'network', + message: certErr instanceof Error ? certErr.message : 'Failed to read CA certificate.' + }; + } + try { - // Create Anthropic client with SDK + // Create Anthropic client with SDK, using custom fetch if a CA cert is configured const client = new Anthropic({ apiKey, baseURL: normalizedUrl, timeout: 10000, // 10 seconds maxRetries: 0, // Disable retries for immediate feedback + fetch: customFetch, }); // Make minimal request to test connection (pass signal for cancellation) @@ -514,7 +642,8 @@ export async function testConnection( export async function discoverModels( baseUrl: string, apiKey: string, - signal?: AbortSignal + signal?: AbortSignal, + caCertPath?: string ): Promise { // Validate API key first if (!validateApiKey(apiKey)) { @@ -555,13 +684,26 @@ export async function discoverModels( throw error; } + // Build custom fetch before the SDK try-catch so cert errors surface clearly + let customFetchForDiscover: typeof globalThis.fetch | undefined; + try { + customFetchForDiscover = buildCustomFetch(caCertPath); + } catch (certErr) { + const certError: Error & { errorType?: string } = new Error( + certErr instanceof Error ? certErr.message : 'Failed to read CA certificate.' + ); + certError.errorType = 'network'; + throw certError; + } + try { - // Create Anthropic client with SDK + // Create Anthropic client with SDK, using custom fetch if a CA cert is configured const client = new Anthropic({ apiKey, baseURL: normalizedUrl, timeout: 10000, // 10 seconds maxRetries: 0, // Disable retries for immediate feedback + fetch: customFetchForDiscover, }); // Fetch models with pagination (1000 limit to get all), pass signal for cancellation diff --git a/apps/frontend/src/preload/api/project-api.ts b/apps/frontend/src/preload/api/project-api.ts index b37face307..9f8f51f7c8 100644 --- a/apps/frontend/src/preload/api/project-api.ts +++ b/apps/frontend/src/preload/api/project-api.ts @@ -58,6 +58,7 @@ export interface ProjectAPI { // Dialog Operations selectDirectory: () => Promise; + selectFile: (filters?: { name: string; extensions: string[] }[]) => Promise; createProjectFolder: ( location: string, name: string, @@ -219,6 +220,9 @@ export const createProjectAPI = (): ProjectAPI => ({ selectDirectory: (): Promise => ipcRenderer.invoke(IPC_CHANNELS.DIALOG_SELECT_DIRECTORY), + selectFile: (filters?: { name: string; extensions: string[] }[]): Promise => + ipcRenderer.invoke(IPC_CHANNELS.DIALOG_SELECT_FILE, filters), + createProjectFolder: ( location: string, name: string, diff --git a/apps/frontend/src/renderer/components/settings/GeneralSettings.tsx b/apps/frontend/src/renderer/components/settings/GeneralSettings.tsx index e44358c6c7..fc7e9ae948 100644 --- a/apps/frontend/src/renderer/components/settings/GeneralSettings.tsx +++ b/apps/frontend/src/renderer/components/settings/GeneralSettings.tsx @@ -2,6 +2,8 @@ import { useTranslation } from 'react-i18next'; import { useEffect, useState } from 'react'; import { Label } from '../ui/label'; import { Input } from '../ui/input'; +import { Button } from '../ui/button'; +import { FileText } from 'lucide-react'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; import { Switch } from '../ui/switch'; import { SettingsSection } from './SettingsSection'; @@ -352,6 +354,39 @@ export function GeneralSettings({ settings, onSettingsChange, section }: General onChange={(e) => onSettingsChange({ ...settings, autoBuildPath: e.target.value })} /> + {/* Custom CA Certificate */} +
+ +

{t('general.customCACertPathDescription')}

+
+ onSettingsChange({ ...settings, customCACertPath: e.target.value })} + /> + +
+
); diff --git a/apps/frontend/src/renderer/lib/mocks/project-mock.ts b/apps/frontend/src/renderer/lib/mocks/project-mock.ts index 153600e098..1314a9c51e 100644 --- a/apps/frontend/src/renderer/lib/mocks/project-mock.ts +++ b/apps/frontend/src/renderer/lib/mocks/project-mock.ts @@ -64,6 +64,10 @@ export const projectMock = { return prompt('Enter project path (browser mock):', '/Users/demo/projects/new-project'); }, + selectFile: async () => { + return prompt('Enter file path (browser mock):', '/path/to/certificate.pem'); + }, + createProjectFolder: async (_location: string, name: string, initGit: boolean) => ({ success: true, data: { diff --git a/apps/frontend/src/shared/constants/ipc.ts b/apps/frontend/src/shared/constants/ipc.ts index 48b3e95c22..9d23fe45b1 100644 --- a/apps/frontend/src/shared/constants/ipc.ts +++ b/apps/frontend/src/shared/constants/ipc.ts @@ -165,6 +165,7 @@ export const IPC_CHANNELS = { // Dialogs DIALOG_SELECT_DIRECTORY: 'dialog:selectDirectory', + DIALOG_SELECT_FILE: 'dialog:selectFile', DIALOG_CREATE_PROJECT_FOLDER: 'dialog:createProjectFolder', DIALOG_GET_DEFAULT_PROJECT_LOCATION: 'dialog:getDefaultProjectLocation', diff --git a/apps/frontend/src/shared/i18n/locales/en/settings.json b/apps/frontend/src/shared/i18n/locales/en/settings.json index bc7fd8fa8f..60a3255516 100644 --- a/apps/frontend/src/shared/i18n/locales/en/settings.json +++ b/apps/frontend/src/shared/i18n/locales/en/settings.json @@ -243,6 +243,11 @@ "autoClaudePath": "Auto Claude Path", "autoClaudePathDescription": "Relative path to auto-claude directory in projects", "autoClaudePathPlaceholder": "auto-claude (default)", + "customCACertPath": "Custom CA Certificate", + "customCACertPathDescription": "Path to a custom CA certificate file (.pem/.crt) for SSL connections (e.g., Zscaler, corporate proxies)", + "customCACertPathPlaceholder": "Leave empty to use system defaults", + "customCACertBrowse": "Browse for certificate file", + "customCACertFileFilter": "Certificates", "autoNameTerminals": "Automatically name terminals", "autoNameTerminalsDescription": "Use AI to generate descriptive names for terminal tabs based on their activity" }, diff --git a/apps/frontend/src/shared/i18n/locales/fr/settings.json b/apps/frontend/src/shared/i18n/locales/fr/settings.json index 8d506e900f..f21f2282d6 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/settings.json +++ b/apps/frontend/src/shared/i18n/locales/fr/settings.json @@ -243,6 +243,11 @@ "autoClaudePath": "Chemin Auto Claude", "autoClaudePathDescription": "Chemin relatif vers le répertoire auto-claude dans les projets", "autoClaudePathPlaceholder": "auto-claude (par défaut)", + "customCACertPath": "Certificat CA personnalisé", + "customCACertPathDescription": "Chemin vers un fichier de certificat CA (.pem/.crt) pour les connexions SSL (ex: Zscaler, proxies d'entreprise)", + "customCACertPathPlaceholder": "Laisser vide pour utiliser les paramètres système", + "customCACertBrowse": "Parcourir pour un fichier de certificat", + "customCACertFileFilter": "Certificats", "autoNameTerminals": "Nommer automatiquement les terminaux", "autoNameTerminalsDescription": "Utiliser l'IA pour générer des noms descriptifs pour les onglets de terminal en fonction de leur activité" }, diff --git a/apps/frontend/src/shared/types/ipc.ts b/apps/frontend/src/shared/types/ipc.ts index 532722db53..04a4cc7c7f 100644 --- a/apps/frontend/src/shared/types/ipc.ts +++ b/apps/frontend/src/shared/types/ipc.ts @@ -407,6 +407,7 @@ export interface ElectronAPI { // Dialog operations selectDirectory: () => Promise; + selectFile: (filters?: { name: string; extensions: string[] }[]) => Promise; createProjectFolder: (location: string, name: string, initGit: boolean) => Promise>; getDefaultProjectLocation: () => Promise; diff --git a/apps/frontend/src/shared/types/settings.ts b/apps/frontend/src/shared/types/settings.ts index 77d3d6a32f..e97f438b09 100644 --- a/apps/frontend/src/shared/types/settings.ts +++ b/apps/frontend/src/shared/types/settings.ts @@ -296,6 +296,8 @@ export interface AppSettings { sidebarCollapsed?: boolean; // GPU acceleration for terminal rendering (WebGL) gpuAcceleration?: GpuAcceleration; + // Custom CA certificate path for enterprise proxy SSL (e.g., Zscaler) + customCACertPath?: string; } // GPU acceleration mode for terminal WebGL rendering diff --git a/package-lock.json b/package-lock.json index 31ab465ad8..b657d2798d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "auto-claude", - "version": "2.7.6-beta.6", + "version": "2.7.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "auto-claude", - "version": "2.7.6-beta.6", + "version": "2.7.6", "license": "AGPL-3.0", "workspaces": [ "apps/*", @@ -25,7 +25,7 @@ }, "apps/frontend": { "name": "auto-claude-ui", - "version": "2.7.6-beta.6", + "version": "2.7.6", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { @@ -261,6 +261,7 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -825,6 +826,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -868,6 +870,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -907,6 +910,7 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", + "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -2193,6 +2197,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2214,6 +2219,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.4.0.tgz", "integrity": "sha512-jn0phJ+hU7ZuvaoZE/8/Euw3gvHJrn2yi+kXrymwObEPVPjtwCmkvXDRQCWli+fCTTF/aSOtXaLr7CLIvv3LQg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.19.0 || >=20.6.0" }, @@ -2226,6 +2232,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -2241,6 +2248,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz", "integrity": "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.208.0", "import-in-the-middle": "^2.0.0", @@ -2643,6 +2651,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.4.0", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -2659,6 +2668,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.4.0.tgz", "integrity": "sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.4.0", "@opentelemetry/resources": "2.4.0", @@ -2676,6 +2686,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz", "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=14" } @@ -4870,6 +4881,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -5172,6 +5184,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -5182,6 +5195,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -5451,6 +5465,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5483,6 +5498,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5953,6 +5969,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6923,6 +6940,7 @@ "integrity": "sha512-ce4Ogns4VMeisIuCSK0C62umG0lFy012jd8LMZ6w/veHUeX4fqfDrGe+HTWALAEwK6JwKP+dhPvizhArSOsFbg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "26.4.0", "builder-util": "26.3.4", @@ -7080,6 +7098,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", @@ -8438,6 +8457,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -8764,6 +8784,7 @@ "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@acemir/cssom": "^0.9.28", "@asamuzakjp/dom-selector": "^6.7.6", @@ -11250,6 +11271,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11440,6 +11462,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11449,6 +11472,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -12434,7 +12458,8 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -12613,6 +12638,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -12771,6 +12797,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13091,6 +13118,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -13683,6 +13711,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -14147,6 +14176,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }