Compare commits

..

19 Commits

Author SHA1 Message Date
Owen Schwartz c3f6a9f70e New translations en-us.json (French)
[ci skip]
2026-08-18 15:04:14 -04:00
Owen Schwartz f316522ef2 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-08-18 15:04:10 -04:00
Owen Schwartz 1c786d7908 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-08-18 15:04:07 -04:00
Owen Schwartz ffd46e2c23 New translations en-us.json (Turkish)
[ci skip]
2026-08-18 15:04:05 -04:00
Owen Schwartz bfbb770e7f New translations en-us.json (Russian)
[ci skip]
2026-08-18 15:04:02 -04:00
Owen Schwartz f14e4525de New translations en-us.json (Portuguese)
[ci skip]
2026-08-18 15:03:59 -04:00
Owen Schwartz d418c36365 New translations en-us.json (Polish)
[ci skip]
2026-08-18 15:03:57 -04:00
Owen Schwartz 9ac99f8d53 New translations en-us.json (Dutch)
[ci skip]
2026-08-18 15:03:54 -04:00
Owen Schwartz c7035d1720 New translations en-us.json (Korean)
[ci skip]
2026-08-18 15:03:52 -04:00
Owen Schwartz a20abe2acc New translations en-us.json (Italian)
[ci skip]
2026-08-18 15:03:49 -04:00
Owen Schwartz 40cbbccdcc New translations en-us.json (German)
[ci skip]
2026-08-18 15:03:47 -04:00
Owen Schwartz ce32266ad1 New translations en-us.json (Danish)
[ci skip]
2026-08-18 15:03:45 -04:00
Owen Schwartz bcdfe51f31 New translations en-us.json (Czech)
[ci skip]
2026-08-18 15:03:42 -04:00
Owen Schwartz 3c7d5e76d9 New translations en-us.json (Bulgarian)
[ci skip]
2026-08-18 15:03:40 -04:00
Owen Schwartz 4623924b01 New translations en-us.json (Spanish)
[ci skip]
2026-08-18 15:03:38 -04:00
Owen 03085eb188 Pass 3 - add commands, remove run, formatting, mobile view 2026-08-18 14:59:29 -04:00
Owen 8feca67431 Fix spacing issue 2026-08-18 14:59:29 -04:00
Owen 1cddff194c Pass 2 showing the usage commands 2026-08-18 14:59:29 -04:00
Owen d26de4c932 Pass 1 of the config instructions 2026-08-18 14:59:28 -04:00
38 changed files with 641 additions and 264 deletions
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "Неназован ключ", "myVirtualApiKeysUnnamed": "Неназован ключ",
"myVirtualApiKeysRevealSecret": "Разкриване на секрет", "myVirtualApiKeysRevealSecret": "Разкриване на секрет",
"myVirtualApiKeysViewSecretDescription": "Този секрет ви идентифицира за AI Gateway ресурси", "myVirtualApiKeysViewSecretDescription": "Този секрет ви идентифицира за AI Gateway ресурси",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "AI Доставчици", "aiProvidersTitle": "AI Доставчици",
"aiProvidersDescription": "Свързване на модели доставчици за AI задачи в тази организация", "aiProvidersDescription": "Свързване на модели доставчици за AI задачи в тази организация",
"aiProvidersBannerTitle": "Свързване на модели доставчици", "aiProvidersBannerTitle": "Свързване на модели доставчици",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "Nepojmenovaný klíč", "myVirtualApiKeysUnnamed": "Nepojmenovaný klíč",
"myVirtualApiKeysRevealSecret": "Odhalit tajemství", "myVirtualApiKeysRevealSecret": "Odhalit tajemství",
"myVirtualApiKeysViewSecretDescription": "Toto tajemství vás autentizuje k AI bránovým zdrojům", "myVirtualApiKeysViewSecretDescription": "Toto tajemství vás autentizuje k AI bránovým zdrojům",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "AI Poskytovatelé", "aiProvidersTitle": "AI Poskytovatelé",
"aiProvidersDescription": "Připojení poskytovatelů modelů pro AI úkoly v této organizaci", "aiProvidersDescription": "Připojení poskytovatelů modelů pro AI úkoly v této organizaci",
"aiProvidersBannerTitle": "Připojit poskytovatele modelů", "aiProvidersBannerTitle": "Připojit poskytovatele modelů",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "Navnløs nøgle", "myVirtualApiKeysUnnamed": "Navnløs nøgle",
"myVirtualApiKeysRevealSecret": "Vis hemmelighed", "myVirtualApiKeysRevealSecret": "Vis hemmelighed",
"myVirtualApiKeysViewSecretDescription": "Denne hemmelighed autentificerer dig til AI Gateway ressourcer", "myVirtualApiKeysViewSecretDescription": "Denne hemmelighed autentificerer dig til AI Gateway ressourcer",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "AI-udbydere", "aiProvidersTitle": "AI-udbydere",
"aiProvidersDescription": "Forbind modeludbydere til AI-arbejdsopgaver i denne organisation", "aiProvidersDescription": "Forbind modeludbydere til AI-arbejdsopgaver i denne organisation",
"aiProvidersBannerTitle": "Forbind Modeludbydere", "aiProvidersBannerTitle": "Forbind Modeludbydere",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "Unbenannter Schlüssel", "myVirtualApiKeysUnnamed": "Unbenannter Schlüssel",
"myVirtualApiKeysRevealSecret": "Geheimnis offenlegen", "myVirtualApiKeysRevealSecret": "Geheimnis offenlegen",
"myVirtualApiKeysViewSecretDescription": "Dieses Geheimnis authentifiziert Sie zu KI-Gateway-Ressourcen", "myVirtualApiKeysViewSecretDescription": "Dieses Geheimnis authentifiziert Sie zu KI-Gateway-Ressourcen",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "KI-Anbieter", "aiProvidersTitle": "KI-Anbieter",
"aiProvidersDescription": "Modellanbieter für KI-Workloads in dieser Organisation verbinden", "aiProvidersDescription": "Modellanbieter für KI-Workloads in dieser Organisation verbinden",
"aiProvidersBannerTitle": "Modellanbieter verbinden", "aiProvidersBannerTitle": "Modellanbieter verbinden",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "Clave sin nombre", "myVirtualApiKeysUnnamed": "Clave sin nombre",
"myVirtualApiKeysRevealSecret": "Revelar Secreto", "myVirtualApiKeysRevealSecret": "Revelar Secreto",
"myVirtualApiKeysViewSecretDescription": "Este secreto te autentica en los recursos del Gateway de IA", "myVirtualApiKeysViewSecretDescription": "Este secreto te autentica en los recursos del Gateway de IA",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "Proveedores de IA", "aiProvidersTitle": "Proveedores de IA",
"aiProvidersDescription": "Conecta proveedores de modelos para cargas de trabajo de IA en esta organización", "aiProvidersDescription": "Conecta proveedores de modelos para cargas de trabajo de IA en esta organización",
"aiProvidersBannerTitle": "Conecta Proveedores de Modelos", "aiProvidersBannerTitle": "Conecta Proveedores de Modelos",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "Révéler le secret", "myVirtualApiKeysUnnamed": "Révéler le secret",
"myVirtualApiKeysRevealSecret": "Ce secret vous authentifie aux ressources de la passerelle AI", "myVirtualApiKeysRevealSecret": "Ce secret vous authentifie aux ressources de la passerelle AI",
"myVirtualApiKeysViewSecretDescription": "Fournisseurs AI", "myVirtualApiKeysViewSecretDescription": "Fournisseurs AI",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "Connectez des fournisseurs de modèles pour des charges de travail AI dans cette organisation", "aiProvidersTitle": "Connectez des fournisseurs de modèles pour des charges de travail AI dans cette organisation",
"aiProvidersDescription": "Connecter les fournisseurs de modèles", "aiProvidersDescription": "Connecter les fournisseurs de modèles",
"aiProvidersBannerTitle": "Les fournisseurs sont les backends de modèle que Pangolin utilise pour les charges de travail AI. Connectez OpenAI, Anthropic et d'autres fournisseurs ici, puis attachez-les aux passerelles AI publiques afin que les utilisateurs puissent appeler des modèles avec un accès conscient de l'identité, des budgets et des journaux.", "aiProvidersBannerTitle": "Les fournisseurs sont les backends de modèle que Pangolin utilise pour les charges de travail AI. Connectez OpenAI, Anthropic et d'autres fournisseurs ici, puis attachez-les aux passerelles AI publiques afin que les utilisateurs puissent appeler des modèles avec un accès conscient de l'identité, des budgets et des journaux.",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "Chiave senza nome", "myVirtualApiKeysUnnamed": "Chiave senza nome",
"myVirtualApiKeysRevealSecret": "Rivela Segreto", "myVirtualApiKeysRevealSecret": "Rivela Segreto",
"myVirtualApiKeysViewSecretDescription": "Questo segreto ti autentica alle risorse Gateway AI", "myVirtualApiKeysViewSecretDescription": "Questo segreto ti autentica alle risorse Gateway AI",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "Provider AI", "aiProvidersTitle": "Provider AI",
"aiProvidersDescription": "Connetti provider di modelli per i carichi di lavoro AI in questa organizzazione", "aiProvidersDescription": "Connetti provider di modelli per i carichi di lavoro AI in questa organizzazione",
"aiProvidersBannerTitle": "Connetti Provider di Modelli", "aiProvidersBannerTitle": "Connetti Provider di Modelli",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "이름 없는 키", "myVirtualApiKeysUnnamed": "이름 없는 키",
"myVirtualApiKeysRevealSecret": "비밀 공개", "myVirtualApiKeysRevealSecret": "비밀 공개",
"myVirtualApiKeysViewSecretDescription": "이 비밀은 AI 게이트웨이 리소스에 대한 귀하의 인증을 수행합니다", "myVirtualApiKeysViewSecretDescription": "이 비밀은 AI 게이트웨이 리소스에 대한 귀하의 인증을 수행합니다",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "AI 공급자", "aiProvidersTitle": "AI 공급자",
"aiProvidersDescription": "이 조직의 AI 작업량에 대한 모델 공급자를 연결하십시오", "aiProvidersDescription": "이 조직의 AI 작업량에 대한 모델 공급자를 연결하십시오",
"aiProvidersBannerTitle": "모델 공급자 연결", "aiProvidersBannerTitle": "모델 공급자 연결",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "Uten navngitt nøkkel", "myVirtualApiKeysUnnamed": "Uten navngitt nøkkel",
"myVirtualApiKeysRevealSecret": "Avslør hemmelighet", "myVirtualApiKeysRevealSecret": "Avslør hemmelighet",
"myVirtualApiKeysViewSecretDescription": "Denne hemmeligheten autentiserer deg til AI Gateway-ressurser", "myVirtualApiKeysViewSecretDescription": "Denne hemmeligheten autentiserer deg til AI Gateway-ressurser",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "AI-leverandører", "aiProvidersTitle": "AI-leverandører",
"aiProvidersDescription": "Koble modellleverandører for AI-arbeidslaster i denne organisasjonen", "aiProvidersDescription": "Koble modellleverandører for AI-arbeidslaster i denne organisasjonen",
"aiProvidersBannerTitle": "Koble modellleverandører", "aiProvidersBannerTitle": "Koble modellleverandører",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "Naamloos sleutel", "myVirtualApiKeysUnnamed": "Naamloos sleutel",
"myVirtualApiKeysRevealSecret": "Geheim onthullen", "myVirtualApiKeysRevealSecret": "Geheim onthullen",
"myVirtualApiKeysViewSecretDescription": "Dit geheim authenticeert u bij AI Gateway-resources", "myVirtualApiKeysViewSecretDescription": "Dit geheim authenticeert u bij AI Gateway-resources",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "AI-aanbieders", "aiProvidersTitle": "AI-aanbieders",
"aiProvidersDescription": "Verbind modellenproviders voor AI-werklasten in deze organisatie", "aiProvidersDescription": "Verbind modellenproviders voor AI-werklasten in deze organisatie",
"aiProvidersBannerTitle": "Modellenproviders verbinden", "aiProvidersBannerTitle": "Modellenproviders verbinden",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "Bez nazwy", "myVirtualApiKeysUnnamed": "Bez nazwy",
"myVirtualApiKeysRevealSecret": "Ujawnij Sekret", "myVirtualApiKeysRevealSecret": "Ujawnij Sekret",
"myVirtualApiKeysViewSecretDescription": "Ten sekret uwierzytelnia cię do zasobów Gateway AI", "myVirtualApiKeysViewSecretDescription": "Ten sekret uwierzytelnia cię do zasobów Gateway AI",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "Dostawcy AI", "aiProvidersTitle": "Dostawcy AI",
"aiProvidersDescription": "Połącz dostawców modeli dla obciążeń AI w tej organizacji", "aiProvidersDescription": "Połącz dostawców modeli dla obciążeń AI w tej organizacji",
"aiProvidersBannerTitle": "Połącz dostawców modeli", "aiProvidersBannerTitle": "Połącz dostawców modeli",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "Chave sem nome", "myVirtualApiKeysUnnamed": "Chave sem nome",
"myVirtualApiKeysRevealSecret": "Revelar Segredo", "myVirtualApiKeysRevealSecret": "Revelar Segredo",
"myVirtualApiKeysViewSecretDescription": "Este segredo o autentica em recursos do Gateway de IA", "myVirtualApiKeysViewSecretDescription": "Este segredo o autentica em recursos do Gateway de IA",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "Provedores de IA", "aiProvidersTitle": "Provedores de IA",
"aiProvidersDescription": "Conecte provedores de modelo para cargas de trabalho de IA nesta organização", "aiProvidersDescription": "Conecte provedores de modelo para cargas de trabalho de IA nesta organização",
"aiProvidersBannerTitle": "Conectar Provedores de Modelo", "aiProvidersBannerTitle": "Conectar Provedores de Modelo",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "Неназванный ключ", "myVirtualApiKeysUnnamed": "Неназванный ключ",
"myVirtualApiKeysRevealSecret": "Показать секрет", "myVirtualApiKeysRevealSecret": "Показать секрет",
"myVirtualApiKeysViewSecretDescription": "Этот секрет аутентифицирует вас в ресурсах ИИ шлюза", "myVirtualApiKeysViewSecretDescription": "Этот секрет аутентифицирует вас в ресурсах ИИ шлюза",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "Поставщики ИИ", "aiProvidersTitle": "Поставщики ИИ",
"aiProvidersDescription": "Подключите поставщиков моделей для ИИ нагрузок в этой организации", "aiProvidersDescription": "Подключите поставщиков моделей для ИИ нагрузок в этой организации",
"aiProvidersBannerTitle": "Подключить поставщиков моделей", "aiProvidersBannerTitle": "Подключить поставщиков моделей",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "İsimsiz anahtar", "myVirtualApiKeysUnnamed": "İsimsiz anahtar",
"myVirtualApiKeysRevealSecret": "Gizliyi Açıklayın", "myVirtualApiKeysRevealSecret": "Gizliyi Açıklayın",
"myVirtualApiKeysViewSecretDescription": "Bu gizli, sizi AI Gateway kaynaklarına kimlik doğrular", "myVirtualApiKeysViewSecretDescription": "Bu gizli, sizi AI Gateway kaynaklarına kimlik doğrular",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "AI Sağlayıcıları", "aiProvidersTitle": "AI Sağlayıcıları",
"aiProvidersDescription": "Bu organizasyonda AI iş yükleri için model sağlayıcılarla bağlantı kurun", "aiProvidersDescription": "Bu organizasyonda AI iş yükleri için model sağlayıcılarla bağlantı kurun",
"aiProvidersBannerTitle": "Model Sağlayıcılarla Bağlan", "aiProvidersBannerTitle": "Model Sağlayıcılarla Bağlan",
+12
View File
@@ -1780,6 +1780,18 @@
"myVirtualApiKeysUnnamed": "未命名的密钥", "myVirtualApiKeysUnnamed": "未命名的密钥",
"myVirtualApiKeysRevealSecret": "显示密钥", "myVirtualApiKeysRevealSecret": "显示密钥",
"myVirtualApiKeysViewSecretDescription": "此密钥认证您访问AI网关资源", "myVirtualApiKeysViewSecretDescription": "此密钥认证您访问AI网关资源",
"aiClientConfigTitle": "Configure Coding Agents",
"aiClientConfigDescription": "Copy configuration for popular coding agents, or let the Pangolin CLI set it up for you automatically.",
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
"aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration",
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
"aiClientConfigRevealError": "Could not load your API key.",
"aiClientConfigRevealRetry": "Try again",
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
"aiProvidersTitle": "AI提供者", "aiProvidersTitle": "AI提供者",
"aiProvidersDescription": "连接模型提供者用于本组织的AI工作负载", "aiProvidersDescription": "连接模型提供者用于本组织的AI工作负载",
"aiProvidersBannerTitle": "连接模型提供者", "aiProvidersBannerTitle": "连接模型提供者",
-10
View File
@@ -1,10 +0,0 @@
<svg width="256" height="257" viewBox="0 0 256 257" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_29_2)">
<path d="M50.2278 170.321L100.585 142.064L101.428 139.601L100.585 138.24H98.1225L89.6972 137.722L60.9215 136.944L35.9696 135.907L11.7954 134.611L5.70329 133.314L0 125.796L0.583291 122.037L5.70329 118.603L13.0268 119.251L29.2294 120.352L53.5332 122.037L71.1615 123.074L97.28 125.796H101.428L102.011 124.111L100.585 123.074L99.4835 122.037L74.3372 104.992L47.117 86.9752L32.8587 76.6056L25.1463 71.3559L21.2577 66.4304L19.5727 55.6719L26.5722 47.9595L35.9696 48.6076L38.3676 49.2557L47.8947 56.5792L68.2451 72.3281L94.8172 91.9008L98.7058 95.1413L100.261 94.0395L100.456 93.2618L98.7058 90.3453L84.2532 64.2268L68.8284 37.6547L61.9585 26.637L60.1438 20.0263C59.4957 17.3043 59.042 15.0359 59.042 12.2491L67.0137 1.42582L71.4208 0L82.0496 1.42582L86.5215 5.31443L93.1321 20.4152L103.826 44.2005L120.417 76.5408L125.278 86.1327L127.87 95.0116L128.843 97.7337H130.528V96.1782L131.889 77.9666L134.416 55.6071L136.879 26.8314L137.722 18.7301L141.74 9.00861L149.711 3.75899L155.933 6.74025L161.053 14.0638L160.34 18.7949L157.294 38.562L151.332 69.5413L147.443 90.2805H149.711L152.304 87.6881L162.803 73.7539L180.431 51.7185L188.209 42.9691L197.282 33.3124L203.115 28.7109H214.133L222.234 40.7656L218.605 53.2091L207.263 67.597L197.865 79.7813L184.385 97.9281L175.959 112.446L176.737 113.612L178.746 113.418L209.207 106.937L225.669 103.955L245.306 100.585L254.185 104.733L255.157 108.946L251.658 117.566L230.659 122.75L206.031 127.676L169.349 136.361L168.895 136.685L169.414 137.333L185.94 138.888L193.005 139.277H210.309L242.52 141.675L250.945 147.249L256 154.054L255.157 159.238L242.195 165.849L224.697 161.701L183.866 151.98L169.867 148.48H167.923V149.647L179.589 161.053L200.976 180.367L227.743 205.254L229.104 211.411L225.669 216.271L222.039 215.753L198.513 198.06L189.44 190.088L168.895 172.784H167.534V174.598L172.265 181.533L197.282 219.123L198.578 230.659L196.764 234.418L190.283 236.687L183.153 235.39L168.506 214.846L153.406 191.708L141.221 170.969L139.731 171.812L132.537 249.26L129.167 253.213L121.389 256.194L114.908 251.269L111.473 243.297L114.908 227.548L119.056 207.004L122.426 190.671L125.472 170.386L127.287 163.646L127.157 163.192L125.667 163.386L110.372 184.385L87.1048 215.818L68.6987 235.52L64.2916 237.27L56.6441 233.316L57.357 226.252L61.6344 219.966L87.1048 187.561L102.465 167.469L112.381 155.868L112.316 154.183H111.733L44.0709 198.125L32.0162 199.68L26.8314 194.819L27.4795 186.848L29.9423 184.255L50.2927 170.256L50.2278 170.321Z" fill="#0B0B0B"/>
</g>
<defs>
<clipPath id="clip0_29_2">
<rect width="256" height="257" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

-10
View File
@@ -1,10 +0,0 @@
<svg width="256" height="257" viewBox="0 0 256 257" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_29_2)">
<path d="M50.2278 170.321L100.585 142.064L101.428 139.601L100.585 138.24H98.1225L89.6972 137.722L60.9215 136.944L35.9696 135.907L11.7954 134.611L5.70329 133.314L0 125.796L0.583291 122.037L5.70329 118.603L13.0268 119.251L29.2294 120.352L53.5332 122.037L71.1615 123.074L97.28 125.796H101.428L102.011 124.111L100.585 123.074L99.4835 122.037L74.3372 104.992L47.117 86.9752L32.8587 76.6056L25.1463 71.3559L21.2577 66.4304L19.5727 55.6719L26.5722 47.9595L35.9696 48.6076L38.3676 49.2557L47.8947 56.5792L68.2451 72.3281L94.8172 91.9008L98.7058 95.1413L100.261 94.0395L100.456 93.2618L98.7058 90.3453L84.2532 64.2268L68.8284 37.6547L61.9585 26.637L60.1438 20.0263C59.4957 17.3043 59.042 15.0359 59.042 12.2491L67.0137 1.42582L71.4208 0L82.0496 1.42582L86.5215 5.31443L93.1321 20.4152L103.826 44.2005L120.417 76.5408L125.278 86.1327L127.87 95.0116L128.843 97.7337H130.528V96.1782L131.889 77.9666L134.416 55.6071L136.879 26.8314L137.722 18.7301L141.74 9.00861L149.711 3.75899L155.933 6.74025L161.053 14.0638L160.34 18.7949L157.294 38.562L151.332 69.5413L147.443 90.2805H149.711L152.304 87.6881L162.803 73.7539L180.431 51.7185L188.209 42.9691L197.282 33.3124L203.115 28.7109H214.133L222.234 40.7656L218.605 53.2091L207.263 67.597L197.865 79.7813L184.385 97.9281L175.959 112.446L176.737 113.612L178.746 113.418L209.207 106.937L225.669 103.955L245.306 100.585L254.185 104.733L255.157 108.946L251.658 117.566L230.659 122.75L206.031 127.676L169.349 136.361L168.895 136.685L169.414 137.333L185.94 138.888L193.005 139.277H210.309L242.52 141.675L250.945 147.249L256 154.054L255.157 159.238L242.195 165.849L224.697 161.701L183.866 151.98L169.867 148.48H167.923V149.647L179.589 161.053L200.976 180.367L227.743 205.254L229.104 211.411L225.669 216.271L222.039 215.753L198.513 198.06L189.44 190.088L168.895 172.784H167.534V174.598L172.265 181.533L197.282 219.123L198.578 230.659L196.764 234.418L190.283 236.687L183.153 235.39L168.506 214.846L153.406 191.708L141.221 170.969L139.731 171.812L132.537 249.26L129.167 253.213L121.389 256.194L114.908 251.269L111.473 243.297L114.908 227.548L119.056 207.004L122.426 190.671L125.472 170.386L127.287 163.646L127.157 163.192L125.667 163.386L110.372 184.385L87.1048 215.818L68.6987 235.52L64.2916 237.27L56.6441 233.316L57.357 226.252L61.6344 219.966L87.1048 187.561L102.465 167.469L112.381 155.868L112.316 154.183H111.733L44.0709 198.125L32.0162 199.68L26.8314 194.819L27.4795 186.848L29.9423 184.255L50.2927 170.256L50.2278 170.321Z" fill="#F0EFEC"/>
</g>
<defs>
<clipPath id="clip0_29_2">
<rect width="256" height="257" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 466.73 532.09">
<!-- Generator: Adobe Illustrator 29.6.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 9) -->
<defs>
<style>
.st0 {
fill: #26251e;
}
</style>
</defs>
<path class="st0" d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 793 B

-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 466.73 532.09">
<!-- Generator: Adobe Illustrator 29.6.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 9) -->
<defs>
<style>
.st0 {
fill: #edecec;
}
</style>
</defs>
<path class="st0" d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 793 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="716" height="716" viewBox="157 157 402 402" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M508.749 317.399C516.777 287.314 508.991 253.884 485.389 230.282C461.788 206.681 428.36 198.895 398.273 206.923C376.231 184.928 343.39 174.956 311.148 183.596C278.906 192.234 255.45 217.292 247.36 247.361C217.291 255.451 192.233 278.91 183.595 311.149C174.957 343.391 184.927 376.232 206.924 398.274C198.896 428.359 206.683 461.789 230.284 485.391C253.885 508.992 287.313 516.779 317.401 508.75C339.442 530.745 372.286 540.717 404.525 532.079C436.767 523.441 460.223 498.384 468.313 468.315C498.383 460.224 523.44 436.766 532.078 404.526C540.716 372.285 530.747 339.443 508.749 317.402V317.399ZM470.899 244.776C486.892 260.77 493.488 282.601 490.687 303.412L415.577 260.046C412.411 258.218 408.509 258.218 405.345 260.046L317.401 310.82V277.526C317.401 275.191 318.652 273.005 320.676 271.837L387.644 233.174C414.178 218.353 448.346 222.223 470.901 244.776H470.899ZM357.837 311.144L398.275 334.491V381.185L357.837 404.532L317.398 381.185V334.491L357.837 311.144ZM264.776 269.693C265.207 239.305 285.644 211.649 316.453 203.393C338.3 197.54 360.505 202.744 377.127 215.573L302.014 258.937C298.848 260.764 296.898 264.144 296.898 267.798V369.346L268.065 352.699C266.043 351.531 264.776 349.353 264.776 347.017V269.691V269.693ZM203.391 316.454C209.244 294.608 224.854 277.978 244.276 269.999V356.73C244.276 360.384 246.226 363.763 249.392 365.591L337.337 416.365L308.503 433.013C306.481 434.181 303.961 434.188 301.939 433.02L234.971 394.357C208.868 378.789 195.138 347.261 203.391 316.454ZM244.775 470.9C228.781 454.906 222.186 433.075 224.986 412.264L300.096 455.63C303.263 457.457 307.164 457.457 310.328 455.63L398.273 404.856V438.149C398.273 440.485 397.022 442.671 394.997 443.839L328.029 482.502C301.495 497.322 267.327 493.452 244.772 470.9H244.775ZM450.897 445.982C450.466 476.371 430.029 504.027 399.22 512.283C377.373 518.136 355.168 512.932 338.547 500.102L413.659 456.738C416.826 454.911 418.775 451.532 418.775 447.877V346.329L447.609 362.977C449.631 364.145 450.897 366.323 450.897 368.659V445.985V445.982ZM512.282 399.221C506.429 421.068 490.819 437.697 471.397 445.676V358.946C471.397 355.292 469.448 351.912 466.281 350.085L378.336 299.311L407.17 282.663C409.192 281.495 411.712 281.487 413.734 282.655L480.702 321.318C506.805 336.887 520.536 368.415 512.282 399.221Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="716" height="716" viewBox="157 157 402 402" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M508.749 317.399C516.777 287.314 508.991 253.884 485.389 230.282C461.788 206.681 428.36 198.895 398.273 206.923C376.231 184.928 343.39 174.956 311.148 183.596C278.906 192.234 255.45 217.292 247.36 247.361C217.291 255.451 192.233 278.91 183.595 311.149C174.957 343.391 184.927 376.232 206.924 398.274C198.896 428.359 206.683 461.789 230.284 485.391C253.885 508.992 287.313 516.779 317.401 508.75C339.442 530.745 372.286 540.717 404.525 532.079C436.767 523.441 460.223 498.384 468.313 468.315C498.383 460.224 523.44 436.766 532.078 404.526C540.716 372.285 530.747 339.443 508.749 317.402V317.399ZM470.899 244.776C486.892 260.77 493.488 282.601 490.687 303.412L415.577 260.046C412.411 258.218 408.509 258.218 405.345 260.046L317.401 310.82V277.526C317.401 275.191 318.652 273.005 320.676 271.837L387.644 233.174C414.178 218.353 448.346 222.223 470.901 244.776H470.899ZM357.837 311.144L398.275 334.491V381.185L357.837 404.532L317.398 381.185V334.491L357.837 311.144ZM264.776 269.693C265.207 239.305 285.644 211.649 316.453 203.393C338.3 197.54 360.505 202.744 377.127 215.573L302.014 258.937C298.848 260.764 296.898 264.144 296.898 267.798V369.346L268.065 352.699C266.043 351.531 264.776 349.353 264.776 347.017V269.691V269.693ZM203.391 316.454C209.244 294.608 224.854 277.978 244.276 269.999V356.73C244.276 360.384 246.226 363.763 249.392 365.591L337.337 416.365L308.503 433.013C306.481 434.181 303.961 434.188 301.939 433.02L234.971 394.357C208.868 378.789 195.138 347.261 203.391 316.454ZM244.775 470.9C228.781 454.906 222.186 433.075 224.986 412.264L300.096 455.63C303.263 457.457 307.164 457.457 310.328 455.63L398.273 404.856V438.149C398.273 440.485 397.022 442.671 394.997 443.839L328.029 482.502C301.495 497.322 267.327 493.452 244.772 470.9H244.775ZM450.897 445.982C450.466 476.371 430.029 504.027 399.22 512.283C377.373 518.136 355.168 512.932 338.547 500.102L413.659 456.738C416.826 454.911 418.775 451.532 418.775 447.877V346.329L447.609 362.977C449.631 364.145 450.897 366.323 450.897 368.659V445.985V445.982ZM512.282 399.221C506.429 421.068 490.819 437.697 471.397 445.676V358.946C471.397 355.292 469.448 351.912 466.281 350.085L378.336 299.311L407.17 282.663C409.192 281.495 411.712 281.487 413.734 282.655L480.702 321.318C506.805 336.887 520.536 368.415 512.282 399.221Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

-1
View File
@@ -1 +0,0 @@
<svg width='240' height='300' viewBox='0 0 240 300' fill='none' xmlns='http://www.w3.org/2000/svg'><g clip-path='url(#clip0_1401_86274)'><mask id='mask0_1401_86274' style='mask-type:luminance' maskUnits='userSpaceOnUse' x='0' y='0' width='240' height='300'><path d='M240 0H0V300H240V0Z' fill='white'/></mask><g mask='url(#mask0_1401_86274)'><path d='M180 240H60V120H180V240Z' fill='#CFCECD'/><path d='M180 60H60V240H180V60ZM240 300H0V0H240V300Z' fill='#211E1E'/></g></g><defs><clipPath id='clip0_1401_86274'><rect width='240' height='300' fill='white'/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 577 B

-1
View File
@@ -1 +0,0 @@
<svg width='240' height='300' viewBox='0 0 240 300' fill='none' xmlns='http://www.w3.org/2000/svg'><g clip-path='url(#clip0_1401_86283)'><mask id='mask0_1401_86283' style='mask-type:luminance' maskUnits='userSpaceOnUse' x='0' y='0' width='240' height='300'><path d='M240 0H0V300H240V0Z' fill='white'/></mask><g mask='url(#mask0_1401_86283)'><path d='M180 240H60V120H180V240Z' fill='#4B4646'/><path d='M180 60H60V240H180V60ZM240 300H0V0H240V300Z' fill='#F1ECEC'/></g></g><defs><clipPath id='clip0_1401_86283'><rect width='240' height='300' fill='white'/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 577 B

+2 -14
View File
@@ -7,18 +7,6 @@ import VirtualApiKeyGenerated from "@server/emails/templates/VirtualApiKeyGenera
import { formatVirtualApiKeyCredential } from "@server/lib/virtualApiKey"; import { formatVirtualApiKeyCredential } from "@server/lib/virtualApiKey";
const EMAIL_GATEWAY_URL_LIMIT = 5; const EMAIL_GATEWAY_URL_LIMIT = 5;
const VIRTUAL_API_KEY_EMAIL_BATCH_SIZE = 50;
export async function mapInBatches<T>(
items: T[],
fn: (item: T) => Promise<void>,
batchSize = VIRTUAL_API_KEY_EMAIL_BATCH_SIZE
): Promise<void> {
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
await Promise.all(batch.map(fn));
}
}
async function listVirtualApiKeyGatewayUrls(params: { async function listVirtualApiKeyGatewayUrls(params: {
orgId: string; orgId: string;
@@ -173,7 +161,7 @@ export async function sendVirtualApiKeyEmails(params: {
? `Your identity key for ${params.orgName}` ? `Your identity key for ${params.orgName}`
: `Virtual API key for ${params.orgName}`; : `Virtual API key for ${params.orgName}`;
await mapInBatches(params.recipients, async (to) => { for (const to of params.recipients) {
await sendEmail( await sendEmail(
params.isIdentityKey params.isIdentityKey
? IdentityApiKeyGenerated({ ? IdentityApiKeyGenerated({
@@ -196,5 +184,5 @@ export async function sendVirtualApiKeyEmails(params: {
subject subject
} }
); );
}); }
} }
@@ -316,7 +316,6 @@ function buildSearchConditionForPublic(query: string) {
const pattern = searchPattern(query.toLowerCase()); const pattern = searchPattern(query.toLowerCase());
const queryList = [ const queryList = [
like(sql`LOWER(${resources.name})`, pattern), like(sql`LOWER(${resources.name})`, pattern),
like(sql`LOWER(${resources.niceId})`, pattern),
like(sql`LOWER(${resources.fullDomain})`, pattern), like(sql`LOWER(${resources.fullDomain})`, pattern),
like(sql`LOWER(cast(${resources.proxyPort} as text))`, pattern), like(sql`LOWER(cast(${resources.proxyPort} as text))`, pattern),
inArray( inArray(
@@ -349,7 +348,6 @@ function buildSearchConditionForSiteResource(query: string) {
const pattern = searchPattern(query.toLowerCase()); const pattern = searchPattern(query.toLowerCase());
const queryList = [ const queryList = [
like(sql`LOWER(${siteResources.name})`, pattern), like(sql`LOWER(${siteResources.name})`, pattern),
like(sql`LOWER(${siteResources.niceId})`, pattern),
like(sql`LOWER(${siteResources.destination})`, pattern), like(sql`LOWER(${siteResources.destination})`, pattern),
like( like(
sql`LOWER(cast(${siteResources.destinationPort} as text))`, sql`LOWER(cast(${siteResources.destinationPort} as text))`,
@@ -12,8 +12,7 @@ import config from "@server/lib/config";
import { getOrCreateUserVirtualApiKey } from "@server/lib/virtualApiKey"; import { getOrCreateUserVirtualApiKey } from "@server/lib/virtualApiKey";
import { import {
sendVirtualApiKeyEmails, sendVirtualApiKeyEmails,
listOrgInferenceGatewayUrls, listOrgInferenceGatewayUrls
mapInBatches
} from "@server/lib/sendVirtualApiKeyEmail"; } from "@server/lib/sendVirtualApiKeyEmail";
import type { EmailIdentityKeysResponse } from "@server/routers/virtualApiKey/types"; import type { EmailIdentityKeysResponse } from "@server/routers/virtualApiKey/types";
import rateLimit, { ipKeyGenerator } from "express-rate-limit"; import rateLimit, { ipKeyGenerator } from "express-rate-limit";
@@ -219,13 +218,15 @@ export async function emailIdentityKeys(
const orgName = org?.name || orgId; const orgName = org?.name || orgId;
const gatewayUrls = await listOrgInferenceGatewayUrls(orgId); const gatewayUrls = await listOrgInferenceGatewayUrls(orgId);
const recipients = members.flatMap(({ user }) =>
user.email ? [{ user, email: user.email }] : []
);
let sent = 0; let sent = 0;
const skipped = members.length - recipients.length; let skipped = 0;
for (const { user } of members) {
if (!user.email) {
skipped += 1;
continue;
}
await mapInBatches(recipients, async ({ user, email }) => {
const { key, secret } = await getOrCreateUserVirtualApiKey({ const { key, secret } = await getOrCreateUserVirtualApiKey({
orgId, orgId,
user, user,
@@ -233,7 +234,7 @@ export async function emailIdentityKeys(
}); });
await sendVirtualApiKeyEmails({ await sendVirtualApiKeyEmails({
recipients: [email], recipients: [user.email],
orgName, orgName,
orgId, orgId,
keyName: key.name, keyName: key.name,
@@ -241,11 +242,11 @@ export async function emailIdentityKeys(
secret, secret,
allResources: true, allResources: true,
isIdentityKey: true, isIdentityKey: true,
accountLabel: email, accountLabel: user.email || user.name || user.username,
gatewayUrls gatewayUrls
}); });
sent += 1; sent += 1;
}); }
return response<EmailIdentityKeysResponse>(res, { return response<EmailIdentityKeysResponse>(res, {
data: { sent, skipped }, data: { sent, skipped },
@@ -20,7 +20,6 @@ import {
toPublicVirtualApiKey toPublicVirtualApiKey
} from "@server/lib/virtualApiKey"; } from "@server/lib/virtualApiKey";
import type { ListMyVirtualApiKeysResponse } from "@server/routers/virtualApiKey/types"; import type { ListMyVirtualApiKeysResponse } from "@server/routers/virtualApiKey/types";
import { formatPublicResourceAccess } from "@server/routers/launcher/formatLauncherAccess";
const paramsSchema = z.strictObject({ const paramsSchema = z.strictObject({
orgId: z.string().nonempty() orgId: z.string().nonempty()
@@ -126,19 +125,11 @@ export async function listMyVirtualApiKeys(
let resourceId: number | undefined; let resourceId: number | undefined;
let resourceName: string | undefined; let resourceName: string | undefined;
let resourceNiceId: string | undefined;
let resourceAccessUrl: string | null = null;
if (resourceGuid) { if (resourceGuid) {
const [resource] = await db const [resource] = await db
.select({ .select({
resourceId: resources.resourceId, resourceId: resources.resourceId,
name: resources.name, name: resources.name
niceId: resources.niceId,
mode: resources.mode,
fullDomain: resources.fullDomain,
ssl: resources.ssl,
proxyPort: resources.proxyPort,
wildcard: resources.wildcard
}) })
.from(resources) .from(resources)
.where( .where(
@@ -160,14 +151,6 @@ export async function listMyVirtualApiKeys(
resourceId = resource.resourceId; resourceId = resource.resourceId;
resourceName = resource.name; resourceName = resource.name;
resourceNiceId = resource.niceId;
resourceAccessUrl = formatPublicResourceAccess({
mode: resource.mode ?? "",
fullDomain: resource.fullDomain,
ssl: resource.ssl,
proxyPort: resource.proxyPort,
wildcard: resource.wildcard
}).accessUrl;
} }
const [user] = await db const [user] = await db
@@ -240,9 +223,7 @@ export async function listMyVirtualApiKeys(
manualKeys: manualRows.map((row) => manualKeys: manualRows.map((row) =>
toKeyWithResources(row, resourceIdsByKey) toKeyWithResources(row, resourceIdsByKey)
), ),
...(resourceName !== undefined ? { resourceName } : {}), ...(resourceName !== undefined ? { resourceName } : {})
...(resourceNiceId !== undefined ? { resourceNiceId } : {}),
...(resourceNiceId !== undefined ? { resourceAccessUrl } : {})
}, },
success: true, success: true,
error: false, error: false,
-2
View File
@@ -23,8 +23,6 @@ export type ListMyVirtualApiKeysResponse = {
userKey: VirtualApiKeyWithResources; userKey: VirtualApiKeyWithResources;
manualKeys: VirtualApiKeyWithResources[]; manualKeys: VirtualApiKeyWithResources[];
resourceName?: string | null; resourceName?: string | null;
resourceNiceId?: string | null;
resourceAccessUrl?: string | null;
}; };
export type GetMyVirtualApiKeyResponse = { export type GetMyVirtualApiKeyResponse = {
@@ -113,12 +113,7 @@ export default async function ResourceKeysPage(props: ResourceKeysPageProps) {
launcherMode launcherMode
showViewAsAdmin={isAdminOrOwner} showViewAsAdmin={isAdminOrOwner}
> >
<UserVirtualApiKeys <UserVirtualApiKeys orgId={orgId} initialData={keysData} />
orgId={orgId}
initialData={keysData}
resourceNiceId={keysData.resourceNiceId ?? undefined}
endpoint={keysData.resourceAccessUrl ?? undefined}
/>
</Layout> </Layout>
</UserProvider> </UserProvider>
); );
+3 -3
View File
@@ -327,7 +327,7 @@ export function BudgetRowsFields({
return ( return (
<div <div
key={row.key} key={row.key}
className="flex items-center gap-1 sm:gap-2" className="flex items-center gap-2"
> >
<div <div
className={cn( className={cn(
@@ -340,7 +340,7 @@ export function BudgetRowsFields({
> >
<BudgetRowUnitSelect <BudgetRowUnitSelect
{...fields} {...fields}
className="h-full w-20 min-w-20 shrink-0 rounded-none border-0 px-2 shadow-none focus-visible:ring-0 sm:w-28 sm:min-w-28 max-sm:[&_svg]:hidden" className="h-full w-28 min-w-28 shrink-0 rounded-none border-0 px-2 shadow-none focus-visible:ring-0"
/> />
<div <div
className="w-px shrink-0 bg-border" className="w-px shrink-0 bg-border"
@@ -353,7 +353,7 @@ export function BudgetRowsFields({
</div> </div>
<BudgetRowPeriodSelect <BudgetRowPeriodSelect
{...fields} {...fields}
className="h-9 w-24 min-w-24 shrink-0 sm:w-32 sm:min-w-32 max-sm:[&_svg]:hidden" className="h-9 w-32 min-w-32 shrink-0"
/> />
<Button <Button
type="button" type="button"
+2 -9
View File
@@ -26,10 +26,6 @@ import { formatVirtualApiKeyPreview } from "@app/lib/virtualApiKeyFormat";
type UserVirtualApiKeysProps = { type UserVirtualApiKeysProps = {
orgId: string; orgId: string;
initialData: ListMyVirtualApiKeysResponse; initialData: ListMyVirtualApiKeysResponse;
/** The resource's niceId, when this page is scoped to a single resource. */
resourceNiceId?: string;
/** The resource's real access URL, when known; falls back to a placeholder otherwise. */
endpoint?: string;
}; };
function OwnedKeySecret({ function OwnedKeySecret({
@@ -167,9 +163,7 @@ function ManualKeyRow({
export default function UserVirtualApiKeys({ export default function UserVirtualApiKeys({
orgId, orgId,
initialData, initialData
resourceNiceId,
endpoint
}: UserVirtualApiKeysProps) { }: UserVirtualApiKeysProps) {
const t = useTranslations(); const t = useTranslations();
const resourceName = initialData.resourceName; const resourceName = initialData.resourceName;
@@ -223,12 +217,11 @@ export default function UserVirtualApiKeys({
<AiClientConfigSection <AiClientConfigSection
layout="wide" layout="wide"
endpoint={endpoint ?? t("aiClientConfigEndpointPlaceholder")} endpoint={t("aiClientConfigEndpointPlaceholder")}
auth={{ auth={{
mode: "keyed", mode: "keyed",
getKeyText: getKeyCopyText getKeyText: getKeyCopyText
}} }}
resourceNiceId={resourceNiceId}
/> />
</SettingsContainer> </SettingsContainer>
</> </>
@@ -27,11 +27,9 @@ import type {
} from "@app/lib/aiClientConfig"; } from "@app/lib/aiClientConfig";
import { buildAiClientGuide } from "@app/lib/aiClientConfig"; import { buildAiClientGuide } from "@app/lib/aiClientConfig";
import { cn } from "@app/lib/cn"; import { cn } from "@app/lib/cn";
import { ChevronDown, Loader2 } from "lucide-react"; import { ChevronDown, Loader2, type LucideIcon } from "lucide-react";
import { useTheme } from "next-themes";
import Image from "next/image";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useEffect, useMemo, useState } from "react"; import { useMemo, useState } from "react";
type AiClientConfigCardProps = { type AiClientConfigCardProps = {
clientId: AiClientId; clientId: AiClientId;
@@ -39,8 +37,7 @@ type AiClientConfigCardProps = {
endpoint: string; endpoint: string;
keyAuth: AiClientAuthInput; keyAuth: AiClientAuthInput;
description: string; description: string;
iconSrc: { light: string; dark: string }; icon: LucideIcon;
resourceNiceId?: string;
}; };
export function AiClientConfigCard({ export function AiClientConfigCard({
@@ -49,31 +46,15 @@ export function AiClientConfigCard({
endpoint, endpoint,
keyAuth, keyAuth,
description, description,
iconSrc, icon: Icon
resourceNiceId
}: AiClientConfigCardProps) { }: AiClientConfigCardProps) {
const t = useTranslations(); const t = useTranslations();
const { theme } = useTheme();
const [resolvedIconSrc, setResolvedIconSrc] = useState(iconSrc.light);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [presetId, setPresetId] = useState<AiClientPresetId>("default"); const [presetId, setPresetId] = useState<AiClientPresetId>("default");
const [revealedKey, setRevealedKey] = useState<string | null>(null); const [revealedKey, setRevealedKey] = useState<string | null>(null);
const [revealing, setRevealing] = useState(false); const [revealing, setRevealing] = useState(false);
const [revealError, setRevealError] = useState(false); const [revealError, setRevealError] = useState(false);
useEffect(() => {
let lightOrDark = theme;
if (theme === "system" || !theme) {
lightOrDark = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light";
}
setResolvedIconSrc(
lightOrDark === "dark" ? iconSrc.dark : iconSrc.light
);
}, [theme, iconSrc]);
const reveal = () => { const reveal = () => {
if (keyAuth.mode !== "keyed" || revealedKey !== null || revealing) { if (keyAuth.mode !== "keyed" || revealedKey !== null || revealing) {
return; return;
@@ -96,23 +77,16 @@ export function AiClientConfigCard({
const guide = useMemo(() => { const guide = useMemo(() => {
if (keyAuth.mode === "keyless") { if (keyAuth.mode === "keyless") {
return buildAiClientGuide( return buildAiClientGuide(clientId, endpoint, { mode: "keyless" });
clientId,
endpoint,
{ mode: "keyless" },
resourceNiceId
);
} }
if (revealedKey === null) { if (revealedKey === null) {
return null; return null;
} }
return buildAiClientGuide( return buildAiClientGuide(clientId, endpoint, {
clientId, mode: "keyed",
endpoint, key: revealedKey
{ mode: "keyed", key: revealedKey }, });
resourceNiceId }, [clientId, endpoint, keyAuth.mode, revealedKey]);
);
}, [clientId, endpoint, keyAuth.mode, revealedKey, resourceNiceId]);
const preset = const preset =
guide?.presets.find((p) => p.id === presetId) ?? guide?.presets[0]; guide?.presets.find((p) => p.id === presetId) ?? guide?.presets[0];
@@ -153,13 +127,7 @@ export function AiClientConfigCard({
className="min-w-0 rounded-md border bg-card" className="min-w-0 rounded-md border bg-card"
> >
<CollapsibleTrigger className="flex w-full items-center gap-3 px-4 py-3 text-left cursor-pointer"> <CollapsibleTrigger className="flex w-full items-center gap-3 px-4 py-3 text-left cursor-pointer">
<Image <Icon className="size-4 shrink-0 text-muted-foreground" />
src={resolvedIconSrc}
alt={name}
width={16}
height={16}
className="size-4 shrink-0 object-contain"
/>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<p className="font-medium truncate">{name}</p> <p className="font-medium truncate">{name}</p>
<p className="text-xs text-muted-foreground truncate"> <p className="text-xs text-muted-foreground truncate">
@@ -14,6 +14,7 @@ import {
type AiClientAuthInput type AiClientAuthInput
} from "@app/lib/aiClientConfig"; } from "@app/lib/aiClientConfig";
import { cn } from "@app/lib/cn"; import { cn } from "@app/lib/cn";
import { MousePointerClick, Sparkles, SquareTerminal, TerminalSquare } from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
type AiClientConfigSectionProps = { type AiClientConfigSectionProps = {
@@ -27,41 +28,20 @@ type AiClientConfigSectionProps = {
*/ */
layout?: "wide" | "compact"; layout?: "wide" | "compact";
className?: string; className?: string;
/**
* The resource's niceId, when known, so the displayed CLI commands can
* include `--resource <niceId>` and let `pangolin configure` skip its
* own resource picker.
*/
resourceNiceId?: string;
}; };
// Each logo file is named for its own color, not the theme it belongs on, so const CLIENT_ICONS = {
// the light-colored "-light" asset is what we show in dark mode and vice versa. claude: Sparkles,
const CLIENT_LOGOS = { codex: TerminalSquare,
claude: { opencode: SquareTerminal,
light: "/third-party/claude-dark.svg", cursor: MousePointerClick
dark: "/third-party/claude-light.svg"
},
codex: {
light: "/third-party/openai-dark.svg",
dark: "/third-party/openai-light.svg"
},
opencode: {
light: "/third-party/opencode-dark.svg",
dark: "/third-party/opencode-light.svg"
},
cursor: {
light: "/third-party/cursor-dark.svg",
dark: "/third-party/cursor-light.svg"
}
} as const; } as const;
export function AiClientConfigSection({ export function AiClientConfigSection({
endpoint, endpoint,
auth, auth,
layout = "compact", layout = "compact",
className, className
resourceNiceId
}: AiClientConfigSectionProps) { }: AiClientConfigSectionProps) {
const t = useTranslations(); const t = useTranslations();
const isWide = layout === "wide"; const isWide = layout === "wide";
@@ -87,7 +67,7 @@ export function AiClientConfigSection({
<div className={cn("@container min-w-0", className)}> <div className={cn("@container min-w-0", className)}>
<div <div
className={cn( className={cn(
"grid min-w-0 items-start gap-3", "grid min-w-0 gap-3",
isWide && "@3xl:grid-cols-2" isWide && "@3xl:grid-cols-2"
)} )}
> >
@@ -99,8 +79,7 @@ export function AiClientConfigSection({
endpoint={endpoint} endpoint={endpoint}
keyAuth={auth} keyAuth={auth}
description={descriptions[clientId]} description={descriptions[clientId]}
iconSrc={CLIENT_LOGOS[clientId]} icon={CLIENT_ICONS[clientId]}
resourceNiceId={resourceNiceId}
/> />
))} ))}
</div> </div>
@@ -3,11 +3,7 @@
import { useState } from "react"; import { useState } from "react";
import { cn } from "@app/lib/cn"; import { cn } from "@app/lib/cn";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { import { BarChart3, LineChart as LineChartIcon, LoaderIcon } from "lucide-react";
BarChart3,
LineChart as LineChartIcon,
LoaderIcon
} from "lucide-react";
import { import {
Bar, Bar,
BarChart, BarChart,
@@ -67,12 +63,7 @@ export function ToggleableTrendChart(props: ToggleableTrendChartProps) {
const hasData = props.data.length > 0; const hasData = props.data.length > 0;
return ( return (
<div <div className={cn("relative flex flex-col gap-2", props.className)}>
className={cn(
"relative flex min-w-0 flex-col gap-2",
props.className
)}
>
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<h3 className="font-semibold">{props.title}</h3> <h3 className="font-semibold">{props.title}</h3>
<div className="flex gap-1"> <div className="flex gap-1">
@@ -111,23 +102,17 @@ export function ToggleableTrendChart(props: ToggleableTrendChartProps) {
) : ( ) : (
<ChartContainer <ChartContainer
config={chartConfig} config={chartConfig}
className="aspect-auto min-h-50 h-64 w-full min-w-0 overflow-hidden" className="min-h-50 w-full h-64"
> >
{chartType === "bar" ? ( {chartType === "bar" ? (
<BarChart accessibilityLayer data={props.data}> <BarChart accessibilityLayer data={props.data}>
<ChartLegend <ChartLegend content={<ChartLegendContent />} />
content={
<ChartLegendContent className="flex-wrap" />
}
/>
<ChartTooltip <ChartTooltip
content={ content={
<ChartTooltipContent <ChartTooltipContent
indicator="dot" indicator="dot"
labelFormatter={(_value, payload) => labelFormatter={(_value, payload) =>
formatDay( formatDay(payload?.[0]?.payload?.day)
payload?.[0]?.payload?.day
)
} }
/> />
} }
@@ -158,19 +143,13 @@ export function ToggleableTrendChart(props: ToggleableTrendChartProps) {
</BarChart> </BarChart>
) : ( ) : (
<LineChart accessibilityLayer data={props.data}> <LineChart accessibilityLayer data={props.data}>
<ChartLegend <ChartLegend content={<ChartLegendContent />} />
content={
<ChartLegendContent className="flex-wrap" />
}
/>
<ChartTooltip <ChartTooltip
content={ content={
<ChartTooltipContent <ChartTooltipContent
indicator="line" indicator="line"
labelFormatter={(_value, payload) => labelFormatter={(_value, payload) =>
formatDay( formatDay(payload?.[0]?.payload?.day)
payload?.[0]?.payload?.day
)
} }
/> />
} }
@@ -351,7 +351,6 @@ function PublicResourceDetails({
mode: "keyed", mode: "keyed",
getKeyText: getAiKeyCopyText getKeyText: getAiKeyCopyText
}} }}
resourceNiceId={launcherResource.niceId}
/> />
) : null} ) : null}
</> </>
@@ -429,7 +428,6 @@ function PrivateResourceDetails({
<AiClientConfigSection <AiClientConfigSection
endpoint={launcherResource.accessUrl ?? ""} endpoint={launcherResource.accessUrl ?? ""}
auth={{ mode: "keyless" }} auth={{ mode: "keyless" }}
resourceNiceId={launcherResource.niceId}
/> />
</> </>
) : null} ) : null}
+1 -1
View File
@@ -51,7 +51,7 @@ const ChartContainer = React.forwardRef<
data-chart={chartId} data-chart={chartId}
ref={ref} ref={ref}
className={cn( className={cn(
"flex aspect-video w-full min-w-0 justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none", "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
className className
)} )}
{...props} {...props}
+14 -41
View File
@@ -59,17 +59,12 @@ function block(
function buildCli( function buildCli(
clientArg: "claude" | "codex" | "opencode", clientArg: "claude" | "codex" | "opencode",
auth: AiClientAuth, auth: AiClientAuth
resourceNiceId?: string
): AiCliCommands { ): AiCliCommands {
const resourceFlag = resourceNiceId
? ` --resource ${resourceNiceId}`
: "";
const configure: AiConfigBlock = { const configure: AiConfigBlock = {
id: `cli-configure-${clientArg}`, id: `cli-configure-${clientArg}`,
label: "Configure", label: "Configure",
displayText: `pangolin configure ${clientArg}${resourceFlag}` displayText: `pangolin configure ${clientArg}`
}; };
if (auth.mode !== "keyed") { if (auth.mode !== "keyed") {
@@ -81,16 +76,12 @@ function buildCli(
configureWithKey: { configureWithKey: {
id: `cli-configure-key-${clientArg}`, id: `cli-configure-key-${clientArg}`,
label: "Configure with an API key", label: "Configure with an API key",
displayText: `pangolin configure ${clientArg} ${auth.key}${resourceFlag}` displayText: `pangolin configure ${clientArg} ${auth.key}`
} }
}; };
} }
function buildClaudeGuide( function buildClaudeGuide(endpoint: string, auth: AiClientAuth): AiClientGuide {
endpoint: string,
auth: AiClientAuth,
resourceNiceId?: string
): AiClientGuide {
const defaultSettings = block( const defaultSettings = block(
"claude-default-settings", "claude-default-settings",
"~/.claude/settings.json", "~/.claude/settings.json",
@@ -177,7 +168,7 @@ function buildClaudeGuide(
return { return {
id: "claude", id: "claude",
name: AI_CLIENT_NAMES.claude, name: AI_CLIENT_NAMES.claude,
cli: buildCli("claude", auth, resourceNiceId), cli: buildCli("claude", auth),
presets: [ presets: [
{ {
id: "default", id: "default",
@@ -203,11 +194,7 @@ function buildClaudeGuide(
}; };
} }
function buildCodexGuide( function buildCodexGuide(endpoint: string, auth: AiClientAuth): AiClientGuide {
endpoint: string,
auth: AiClientAuth,
resourceNiceId?: string
): AiClientGuide {
const settings = block( const settings = block(
"codex-settings", "codex-settings",
"~/.codex/config.toml", "~/.codex/config.toml",
@@ -237,7 +224,7 @@ function buildCodexGuide(
return { return {
id: "codex", id: "codex",
name: AI_CLIENT_NAMES.codex, name: AI_CLIENT_NAMES.codex,
cli: buildCli("codex", auth, resourceNiceId), cli: buildCli("codex", auth),
presets: [ presets: [
{ {
id: "default", id: "default",
@@ -248,14 +235,10 @@ function buildCodexGuide(
}; };
} }
function buildOpencodeGuide( function buildOpencodeGuide(endpoint: string, auth: AiClientAuth): AiClientGuide {
endpoint: string,
auth: AiClientAuth,
resourceNiceId?: string
): AiClientGuide {
const config = block( const config = block(
"opencode-config", "opencode-config",
"Step 1: opencode.json", "opencode.json",
() => () =>
[ [
"{", "{",
@@ -266,11 +249,6 @@ function buildOpencodeGuide(
` "baseURL": "${endpoint}/v1"`, ` "baseURL": "${endpoint}/v1"`,
" }", " }",
" }", " }",
' "openai": {',
' "options": {',
` "baseURL": "${endpoint}/v1"`,
" }",
" }",
" }", " }",
"}" "}"
].join("\n"), ].join("\n"),
@@ -279,7 +257,7 @@ function buildOpencodeGuide(
const authFile = block( const authFile = block(
"opencode-auth", "opencode-auth",
"Step 2: auth.json", "auth.json",
(key) => (key) =>
["{", ' "anthropic": {', ' "type": "api",', ` "key": "${key}"`, " }", "}"].join( ["{", ' "anthropic": {', ' "type": "api",', ` "key": "${key}"`, " }", "}"].join(
"\n" "\n"
@@ -290,7 +268,7 @@ function buildOpencodeGuide(
return { return {
id: "opencode", id: "opencode",
name: AI_CLIENT_NAMES.opencode, name: AI_CLIENT_NAMES.opencode,
cli: buildCli("opencode", auth, resourceNiceId), cli: buildCli("opencode", auth),
presets: [ presets: [
{ {
id: "default", id: "default",
@@ -335,11 +313,7 @@ function buildCursorGuide(endpoint: string, auth: AiClientAuth): AiClientGuide {
const GUIDE_BUILDERS: Record< const GUIDE_BUILDERS: Record<
AiClientId, AiClientId,
( (endpoint: string, auth: AiClientAuth) => AiClientGuide
endpoint: string,
auth: AiClientAuth,
resourceNiceId?: string
) => AiClientGuide
> = { > = {
claude: buildClaudeGuide, claude: buildClaudeGuide,
codex: buildCodexGuide, codex: buildCodexGuide,
@@ -350,8 +324,7 @@ const GUIDE_BUILDERS: Record<
export function buildAiClientGuide( export function buildAiClientGuide(
clientId: AiClientId, clientId: AiClientId,
endpoint: string, endpoint: string,
auth: AiClientAuth, auth: AiClientAuth
resourceNiceId?: string
): AiClientGuide { ): AiClientGuide {
return GUIDE_BUILDERS[clientId](endpoint, auth, resourceNiceId); return GUIDE_BUILDERS[clientId](endpoint, auth);
} }
+398
View File
@@ -0,0 +1,398 @@
This is tailscale's documentation:
Use Claude Code with Aperture
Last validated: Jul 24, 2026
Copy as Markdown
View as Markdown
Aperture by Tailscale is currently in beta.
Configure Claude Code to send requests through Aperture by Tailscale so your organization gets centralized API key management, usage tracking, and session logging.
The Aperture CLI can set up this connection for you automatically. It sets the environment variables Claude Code requires and launches it, so you can skip the manual steps below.
Prerequisites
Before you begin, you need:
An Aperture instance with at least one configured AI provider (such as Anthropic), accessible from your device. Refer to get started with Aperture if you have not set this up.
The Aperture host URL accessible from your device. Use http://, not https://.
Claude Code installed on your device.
To avoid unexpected TLS issues, use http:// for the Aperture URL when configuring LLM clients. All connections remain encrypted using WireGuard, even when HTTPS is not used.
Aperture routes requests based on the model name, not the LLM client. Any LLM client configured to use Aperture can access any provider your admin has set up. Refer to the provider compatibility reference for the full list of supported providers and API formats.
Configure Claude Code
To configure Claude Code to use Aperture, create or edit the global Claude Code settings file (~/.claude/settings.json) and set ANTHROPIC_BASE_URL to your Aperture URL:
{
"apiKeyHelper": "echo '-'",
"env": {
"ANTHROPIC_BASE_URL": "http://<aperture-hostname>"
}
}
The apiKeyHelper setting returns a placeholder value because Aperture injects credentials automatically. You do not need to configure an API key on the client.
To route a Claude subscription account such as Claude Pro or Max through Aperture, the upstream Anthropic provider must use passthrough mode so Aperture forwards Claude Code's own OAuth token to Anthropic. In this case, set only ANTHROPIC_BASE_URL and do not set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN. Setting either makes Claude Code send that value instead of its OAuth token, which suppresses the credential you want to pass through. For more information, refer to Set up passthrough mode.
You can also configure Claude Code using an environment variable instead of a settings file. Set the following environment variable:
export ANTHROPIC_BASE_URL="http://<aperture-hostname>"
Claude Code v1.x requires additional configuration because the apiKeyHelper setting does not exist in earlier versions. You must provide a placeholder authentication token and explicitly specify the model.
{
"model": "claude-sonnet-4-6",
"env": {
"ANTHROPIC_AUTH_TOKEN": "bearer-managed",
"ANTHROPIC_BASE_URL": "http://<aperture-hostname>"
}
}
Claude Code with Amazon Bedrock
If your Aperture instance routes through Amazon Bedrock, use the following settings.json configuration instead of the default Anthropic configuration shown above:
{
"env": {
"ANTHROPIC_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_BEDROCK_BASE_URL": "http://<aperture-hostname>/bedrock",
"CLAUDE_CODE_USE_BEDROCK": "1",
"CLAUDE_CODE_SKIP_BEDROCK_AUTH": "1"
}
}
If you use Claude Code in VS Code with Bedrock, also add "claudeCode.disableLoginPrompt": true to your VS Code user settings JSON (open the Command Palette and select Preferences: Open User Settings (JSON)).
Claude Code with Vertex AI
If your Aperture instance routes through Vertex AI, use the following settings.json configuration instead of the default Anthropic configuration. Replace <project-id> with your Google Cloud project ID.
{
"env": {
"CLOUD_ML_REGION": "global",
"ANTHROPIC_VERTEX_PROJECT_ID": "<project-id>",
"CLAUDE_CODE_USE_VERTEX": "1",
"CLAUDE_CODE_SKIP_VERTEX_AUTH": "1",
"ANTHROPIC_VERTEX_BASE_URL": "http://<aperture-hostname>/v1"
}
}
If you use Claude Code in VS Code with Vertex, also add "claudeCode.disableLoginPrompt": true to your VS Code user settings JSON (open the Command Palette and select Preferences: Open User Settings (JSON)).
Verify the connection
To verify that Claude Code routes requests through Aperture:
Send a test message in Claude Code.
Open the Aperture dashboard at http://<aperture-hostname>/ui/ and confirm the request appears on the Logs page (admin only).
If the request does not appear, refer to the Aperture troubleshooting topic.
Next steps
Grant model access to users: Control which models each user or group can access through Aperture.
Review your usage dashboards: Monitor token consumption, costs, and session activity across your organization.
Set per-user spending limits: Configure quota buckets to control costs for individual users.
Use the Aperture CLI: Launch coding agents already configured for Aperture, without editing configuration by hand.
Use Codex with Aperture
Last validated: Jul 24, 2026
Copy as Markdown
View as Markdown
Aperture by Tailscale is currently in beta.
Configure Codex to send requests through Aperture by Tailscale so your organization gets centralized API key management, usage tracking, and session logging.
The Aperture CLI can set up this connection for you automatically. It writes the Codex configuration and launches it, so you can skip the manual steps below.
Prerequisites
Before you begin, you need:
An Aperture instance with at least one configured OpenAI-compatible provider, accessible from your device. Refer to get started with Aperture if you have not set this up.
The Aperture host URL accessible from your device. Use http://, not https://.
Codex installed on your device.
To avoid unexpected TLS issues, use http:// for the Aperture URL when configuring LLM clients. All connections remain encrypted using WireGuard, even when HTTPS is not used.
Aperture routes requests based on the model name, not the LLM client. Any LLM client configured to use Aperture can access any provider your admin has set up. Refer to the provider compatibility reference for the full list of supported providers and API formats.
Configure Codex
To configure Codex to use Aperture, create or edit the Codex configuration file (~/.codex/config.toml) to use the Aperture URL as the base_url and set the model to a Codex-compatible model:
model = "gpt-5.3-codex"
model_provider = "llm-ai-ts-net"
model_reasoning_effort = "high"
[
model_providers.llm-ai-ts-net
]
name = "Tailscale AI Gateway"
base_url = "http://<aperture-hostname>/v1" # Required: Aperture URL
# Required for Codex models
wire_api = "responses"
The wire_api = "responses" setting configures Codex to use the OpenAI Responses API format. You do not need to configure an API key because Aperture injects credentials automatically.
To forward each client's own credential instead of injecting a shared key, set the upstream provider to passthrough mode. Aperture then forwards the credential the client sends. A personal OpenAI platform API key uses the api.openai.com endpoint, while a ChatGPT subscription account such as Plus, Pro, or Team uses the ChatGPT backend endpoint and a different configuration. For more information, refer to Set up passthrough mode and Route a ChatGPT subscription through Aperture.
Verify the connection
Send a test message in Codex.
Open the Aperture dashboard at http://<aperture-hostname>/ui/ and confirm the request appears on the Logs page (admin only).
If the request does not appear, refer to the Aperture troubleshooting guide.
Use OpenCode with Aperture
Last validated: Jul 24, 2026
Copy as Markdown
View as Markdown
Aperture by Tailscale is currently in beta.
Configure OpenCode to send requests through Aperture by Tailscale so your organization gets centralized API key management, usage tracking, and session logging.
The Aperture CLI can set up this connection for you automatically. It writes the OpenCode configuration and launches it, so you can skip the manual steps below.
Prerequisites
Before you begin, you need:
An Aperture instance with at least one configured AI provider (such as Anthropic or OpenAI), accessible from your device. Refer to get started with Aperture if you have not set this up.
The Aperture host URL accessible from your device. Use http://, not https://.
OpenCode installed on your device.
To avoid unexpected TLS issues, use http:// for the Aperture URL when configuring LLM clients. All connections remain encrypted using WireGuard, even when HTTPS is not used.
Aperture routes requests based on the model name, not the LLM client. Any LLM client configured to use Aperture can access any provider your admin has set up. Refer to the provider compatibility reference for the full list of supported providers and API formats.
Configure OpenCode
OpenCode requires two configuration changes: set the base URL to point at Aperture, and provide a placeholder API key for each provider.
Set the base URL
Point OpenCode at your Aperture instance by setting the base URL for each provider in the OpenCode configuration file.
Open or create the OpenCode configuration file. The file is usually named opencode.json and located in your home directory or the directory where you run OpenCode. Refer to the OpenCode configuration documentation for details.
Add the following configuration to set Aperture as the base URL for your providers:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"anthropic": {
"options": {
"baseURL": "http://<aperture-hostname>/v1"
}
},
"openai": {
"options": {
"baseURL": "http://<aperture-hostname>/v1"
}
}
}
}
Replace <aperture-hostname> with your Aperture hostname. If you connect from outside your tailnet, use http://localhost:<port-number> instead.
Include only the providers you have configured in Aperture. For example, if you only have Anthropic configured, omit the openai block.
Save the file.
Configure authentication
OpenCode requires an authentication entry for each provider. Set a placeholder value for each provider you configured.
Aperture identifies users through Tailscale identity and injects the real API credentials when forwarding requests. The placeholder value is never sent to the provider.
Open or create the OpenCode authentication file.
Add the following placeholder keys:
{
"anthropic": {
"type": "api",
"key": "-"
},
"openai": {
"type": "api",
"key": "-"
}
}
The dash character (-) satisfies OpenCode's authentication check.
Save the file.
Alternatively, enter the placeholder key interactively by running /connect inside OpenCode and selecting Manually enter an API Key for each provider.
Verify the connection
Send a test message in OpenCode.
Open the Aperture dashboard at http://<aperture-hostname>/ui/ and confirm the request appears on the Logs page (admin only).
If the request does not appear, refer to the Aperture troubleshooting guide.
This is netbird's documentation:
Keyless Access to Claude Code
Updated July 26, 2026
Point Claude Code at your agent network endpoint as its Anthropic base URL. NetBird holds the Anthropic API key server-side, so no key lives on your machine.
Running Claude Code through Agent Network turns it from a tool that needs a shared Anthropic key into one your team reaches with their existing identity:
Keyless access through your IdP. No Anthropic API key is distributed to or stored on any developer's machine. Each person runs Claude Code over the NetBird tunnel, and the request is tied to their real identity from your identity provider (Okta, Microsoft Entra ID, Google, …). Onboarding and offboarding follow the same IdP groups you already manage. There's no key to hand out, copy, or revoke.
Usage tracking per developer and group. Every request is metered by identity, model, tokens, and cost, so you can see exactly who is using Claude Code and how much it costs, broken down per person and aggregated per IdP group (team, department, project) in Usage & Logs.
Budget and token limits. Attach per-user or per-group token and spend caps over a rolling window in your policies, so Claude Code usage stays within budget and a single user can't run up the whole account's bill.
The rest of this page walks through connecting the provider and pointing Claude Code at your endpoint.
Connect the Provider
Go to Agent Network → Providers and click Connect Provider.
Select Anthropic and paste your Anthropic API key.
Save the provider. The key is now held server-side, the next step authorizes who can use it.
connect Anthropic provider in NetBird Agent Network
See Providers for details.
Create a Policy
By default nothing is allowed: a policy must connect a source group to the Anthropic provider before anyone can route Claude Code through it.
Go to Agent Network → Policies and add a policy.
Set the Source to the users or agents who should be able to use Claude Code (for example your Engineering group from your IdP).
Set the Provider to the Anthropic provider you just connected.
Optionally attach per-user or per-group token and budget limits so Claude Code usage stays within budget, and guardrails such as a model allowlist.
create a NetBird Agent Network policy authorizing Claude Code
See Policies for details.
Configure with settings.json
Next to your agent network endpoint in the NetBird dashboard, click Agent Config. The modal shows tabs for Claude Code, Codex, OpenAI SDK, and cURL. Pick Claude Code and copy the pre-filled configuration.
NetBird Configure Your Agent modal showing Claude Code settings.json configuration
Add the following to ~/.claude/settings.json. The apiKeyHelper returns a dummy value so Claude Code doesn't prompt for a key. NetBird supplies the real one.
{
"apiKeyHelper": "echo '-'",
"env": {
"ANTHROPIC_BASE_URL": "https://<your-endpoint>"
}
}
Copy
Copied!
Configure with Shell Variables
Alternatively, export the variables before launching Claude Code:
export ANTHROPIC_BASE_URL=https://<your-endpoint>
export ANTHROPIC_API_KEY=none
claude
Copy
Copied!
That's it. Claude Code now sends every request over the NetBird tunnel, where it's tied to your identity, checked against your policies and limits, and recorded in Usage & Logs, broken down per developer and aggregated per IdP group.
NetBird Agent Network access logs showing per-request Claude Code identity, group, model, cost, and status
Use Claude on Vertex AI
If you reach Claude through Google Vertex AI instead of the Anthropic API, point Claude Code's Vertex backend at your agent network endpoint. NetBird holds the Google service account credential server-side and mints the Vertex access token, so Claude Code skips Google authentication entirely. The client stays keyless.
First connect a Google Vertex AI provider in NetBird. Set its upstream URL to the region-less host https://aiplatform.googleapis.com, not the <region>-aiplatform.googleapis.com form, so it matches CLOUD_ML_REGION=global below.
Then add the following to ~/.claude/settings.json:
{
"env": {
"CLOUD_ML_REGION": "global",
"ANTHROPIC_VERTEX_PROJECT_ID": "<your-gcp-project-id>",
"CLAUDE_CODE_USE_VERTEX": "1",
"CLAUDE_CODE_SKIP_VERTEX_AUTH": "1",
"ANTHROPIC_VERTEX_BASE_URL": "https://<your-endpoint>/v1"
}
}
Copy
Copied!
CLAUDE_CODE_USE_VERTEX=1 routes Claude Code through the Vertex backend.
CLAUDE_CODE_SKIP_VERTEX_AUTH=1 skips Google auth on the client: NetBird injects the OAuth token server-side.
ANTHROPIC_VERTEX_BASE_URL is your agent network endpoint with the /v1 suffix.
CLOUD_ML_REGION=global pairs with the region-less provider URL above.
Use Claude on AWS Bedrock
If you reach Claude through AWS Bedrock instead of the Anthropic API, point Claude Code's Bedrock backend at your agent network endpoint. NetBird holds the Bedrock API key server-side and injects it, so Claude Code skips AWS authentication entirely. The client stays keyless.
First connect an AWS Bedrock provider in NetBird. Then add the following to ~/.claude/settings.json:
{
"env": {
"ANTHROPIC_MODEL": "eu.anthropic.claude-sonnet-4-5-20250929-v1:0",
"ANTHROPIC_BEDROCK_BASE_URL": "https://<your-endpoint>/bedrock",
"CLAUDE_CODE_USE_BEDROCK": "1",
"CLAUDE_CODE_SKIP_BEDROCK_AUTH": "1"
}
}
Copy
Copied!
CLAUDE_CODE_USE_BEDROCK=1 routes Claude Code through the Bedrock backend.
CLAUDE_CODE_SKIP_BEDROCK_AUTH=1 skips AWS auth on the client: NetBird injects the Bedrock API key server-side.
ANTHROPIC_BEDROCK_BASE_URL is your agent network endpoint with the /bedrock suffix (the optional gateway-namespace prefix that disambiguates Bedrock from other providers).
ANTHROPIC_MODEL is the full Bedrock model ID including the region prefix (e.g. eu.anthropic.claude-sonnet-4-5-20250929-v1:0). Some models may not be available in all regions. If the model above doesn't work, switch to one in your provider's allowed list, or change it in Claude Code with /model <model-id>.
Use Kimi (Moonshot AI)
If you reach Claude through Moonshot AI instead of the Anthropic API, point Claude Code's Anthropic backend at your agent network endpoint. Moonshot serves the Anthropic Messages API under the /anthropic path, so Claude Code talks to the Kimi models through the same interface it uses for Claude. NetBird holds the Moonshot API key server-side and injects it, so the client stays keyless.
First connect a Kimi (Moonshot AI) provider in NetBird, keeping the default upstream URL https://api.moonshot.ai. Then add the following to ~/.claude/settings.json:
{
"apiKeyHelper": "echo '-'",
"env": {
"ANTHROPIC_BASE_URL": "https://<your-endpoint>/anthropic",
"ANTHROPIC_MODEL": "kimi-k3",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "kimi-k3",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "kimi-k3",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "kimi-k3",
"CLAUDE_CODE_SUBAGENT_MODEL": "kimi-k3",
"ENABLE_TOOL_SEARCH": "false"
}
}
Copy
Copied!
ANTHROPIC_BASE_URL is your agent network endpoint with the /anthropic suffix. That is the path Moonshot serves the Anthropic Messages API under, and NetBird rides it through to the bare https://api.moonshot.ai upstream.
The ANTHROPIC_DEFAULT_* and CLAUDE_CODE_SUBAGENT_MODEL variables pin every model tier (opus, sonnet, haiku, and subagents) to kimi-k3, so no Claude model names leak into requests Moonshot can't serve.
ENABLE_TOOL_SEARCH=false turns off tool search, whose tool_reference blocks Moonshot rejects.
In the Configure Your Agent modal, pick Kimi (Moonshot AI) on the Claude Code tab to copy this configuration with your endpoint filled in:
NetBird Configure Your Agent modal showing the Claude Code settings.json configuration for Kimi (Moonshot AI)
Keyless Access to Codex
Updated July 26, 2026
Configure Codex with a custom model provider that points at your agent network endpoint. NetBird injects the upstream key server-side, so the client stays keyless.
Running Codex through Agent Network turns it from a tool that needs a shared OpenAI key into one your team reaches with their existing identity:
Keyless access through your IdP. No OpenAI API key is distributed to or stored on any developer's machine. Each person runs Codex over the NetBird tunnel, and the request is tied to their real identity from your identity provider (Okta, Microsoft Entra ID, Google, …). Onboarding and offboarding follow the same IdP groups you already manage. There's no key to hand out, copy, or revoke.
Usage tracking per developer and group. Every request is metered by identity, model, tokens, and cost, so you can see exactly who is using Codex and how much it costs, broken down per person and aggregated per IdP group (team, department, project) in Usage & Logs.
Budget and token limits. Attach per-user or per-group token and spend caps over a rolling window in your policies, so Codex usage stays within budget and a single user can't run up the whole account's bill.
The rest of this page walks through connecting the provider and pointing Codex at your endpoint.
Connect the Provider
Go to Agent Network → Providers and click Connect Provider.
Select OpenAI (or another OpenAI-compatible provider or gateway) and paste its API key.
Save the provider. The key is now held server-side, the next step authorizes who can use it.
connect OpenAI provider in NetBird Agent Network
See Providers for details.
Create a Policy
By default nothing is allowed: a policy must connect a source group to the OpenAI provider before anyone can route Codex through it.
Go to Agent Network → Policies and add a policy.
Set the Source to the users or agents who should be able to use Codex (for example your Engineering group from your IdP).
Set the Provider to the OpenAI provider you just connected.
Optionally attach per-user or per-group token and budget limits so Codex usage stays within budget, and guardrails such as a model allowlist.
create a NetBird Agent Network policy authorizing Codex
See Policies for details.
Configure with config.toml
Add a model provider to ~/.codex/config.toml and select it as the default:
model_provider = "netbird"
[model_providers.netbird]
name = "NetBird"
base_url = "https://<your-endpoint>/v1"
wire_api = "responses"
Copy
Copied!
wire_api = "responses" tells Codex to use the OpenAI Responses API that it expects. The /v1 suffix is the OpenAI-compatible base path on your endpoint.
Once saved, Codex routes through NetBird, where each request is tied to your identity, evaluated against your policies and limits, and recorded in Usage & Logs.