Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 32 additions & 15 deletions src/commands/gateway-config/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

import { getClient } from "../../utils/client.js";
import { output, outputError } from "../../utils/output.js";
import { validateGatewayConfig } from "../../utils/gatewayConfigValidation.js";

interface CreateOptions {
name: string;
endpoint: string;
authType: string;
authKey?: string;
bearerAuth?: boolean;
headerAuth?: string;
description?: string;
output?: string;
}
Expand All @@ -18,32 +19,48 @@ export async function createGatewayConfig(options: CreateOptions) {
try {
const client = getClient();

// Validate auth type
const authType = options.authType.toLowerCase();
if (authType !== "bearer" && authType !== "header") {
outputError("Invalid auth type. Must be 'bearer' or 'header'");
// Validate that exactly one auth type is specified
if (options.bearerAuth && options.headerAuth) {
outputError(
"Cannot specify both --bearer-auth and --header-auth. Choose one.",
);
return;
}

// Validate auth key is provided for header type
if (authType === "header" && !options.authKey) {
outputError("--auth-key is required when auth-type is 'header'");
// Default to bearer if neither is specified
const authType = options.headerAuth ? "header" : "bearer";

// Validate all fields using shared validation
const validation = validateGatewayConfig(
{
name: options.name,
endpoint: options.endpoint,
authType,
authKey: options.headerAuth,
},
{ requireName: true, requireEndpoint: true },
);

if (!validation.valid) {
outputError(validation.errors.join("\n"));
return;
}

const { sanitized } = validation;

// Build auth mechanism
const authMechanism: { type: string; key?: string } = {
type: authType,
type: sanitized!.authType!,
};
if (authType === "header" && options.authKey) {
authMechanism.key = options.authKey;
if (sanitized!.authType === "header" && sanitized!.authKey) {
authMechanism.key = sanitized!.authKey;
}

const config = await client.gatewayConfigs.create({
name: options.name,
endpoint: options.endpoint,
name: sanitized!.name!,
endpoint: sanitized!.endpoint!,
auth_mechanism: authMechanism,
description: options.description,
description: options.description?.trim() || undefined,
});

// Default: just output the ID for easy scripting
Expand Down
11 changes: 7 additions & 4 deletions src/commands/gateway-config/get.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/**
* Get gateway config command
* Get gateway config command - supports lookup by ID or name
*/

import { getClient } from "../../utils/client.js";
import { getGatewayConfigByIdOrName } from "../../services/gatewayConfigService.js";
import { output, outputError } from "../../utils/output.js";

interface GetOptions {
Expand All @@ -12,9 +12,12 @@ interface GetOptions {

export async function getGatewayConfig(options: GetOptions) {
try {
const client = getClient();
const config = await getGatewayConfigByIdOrName(options.id);

const config = await client.gatewayConfigs.retrieve(options.id);
if (!config) {
outputError(`Gateway config not found: ${options.id}`);
return;
}

output(config, { format: options.output, defaultFormat: "json" });
} catch (error) {
Expand Down
36 changes: 20 additions & 16 deletions src/commands/gateway-config/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,13 @@ const ListGatewayConfigsUI = ({
},
{
key: "edit",
label: "Edit Gateway Config",
label: "Edit AI Gateway Config",
color: colors.warning,
icon: figures.pointer,
},
{
key: "delete",
label: "Delete Gateway Config",
label: "Delete AI Gateway Config",
color: colors.error,
icon: figures.cross,
},
Expand Down Expand Up @@ -319,7 +319,7 @@ const ListGatewayConfigsUI = ({
case "delete":
await client.gatewayConfigs.delete(config.id);
setOperationResult(
`Gateway config "${config.name}" deleted successfully`,
`AI gateway config "${config.name}" deleted successfully`,
);
break;
}
Expand Down Expand Up @@ -481,11 +481,11 @@ const ListGatewayConfigsUI = ({
if (showDeleteConfirm && selectedConfig) {
return (
<ConfirmationPrompt
title="Delete Gateway Config"
title="Delete AI Gateway Config"
message={`Are you sure you want to delete "${selectedConfig.name}"?`}
details="This action cannot be undone. Any devboxes using this gateway config will no longer have access to it."
details="This action cannot be undone. Any devboxes using this AI gateway config will no longer have access to it."
breadcrumbItems={[
{ label: "Gateway Configs" },
{ label: "AI Gateway Configs" },
{ label: selectedConfig.name || selectedConfig.id },
{ label: "Delete", active: true },
]}
Expand All @@ -511,7 +511,7 @@ const ListGatewayConfigsUI = ({
<>
<Breadcrumb
items={[
{ label: "Gateway Configs" },
{ label: "AI Gateway Configs" },
{
label: selectedConfig?.name || selectedConfig?.id || "Config",
},
Expand All @@ -534,13 +534,13 @@ const ListGatewayConfigsUI = ({
operations.find((o) => o.key === executingOperation)?.label ||
"Operation";
const messages: Record<string, string> = {
delete: "Deleting gateway config...",
delete: "Deleting AI gateway config...",
};
return (
<>
<Breadcrumb
items={[
{ label: "Gateway Configs" },
{ label: "AI Gateway Configs" },
{ label: selectedConfig.name || selectedConfig.id },
{ label: operationLabel, active: true },
]}
Expand Down Expand Up @@ -589,8 +589,8 @@ const ListGatewayConfigsUI = ({
if (loading && configs.length === 0) {
return (
<>
<Breadcrumb items={[{ label: "Gateway Configs", active: true }]} />
<SpinnerComponent message="Loading gateway configs..." />
<Breadcrumb items={[{ label: "AI Gateway Configs", active: true }]} />
<SpinnerComponent message="Loading AI gateway configs..." />
</>
);
}
Expand All @@ -599,16 +599,19 @@ const ListGatewayConfigsUI = ({
if (error) {
return (
<>
<Breadcrumb items={[{ label: "Gateway Configs", active: true }]} />
<ErrorMessage message="Failed to list gateway configs" error={error} />
<Breadcrumb items={[{ label: "AI Gateway Configs", active: true }]} />
<ErrorMessage
message="Failed to list AI gateway configs"
error={error}
/>
</>
);
}

// Main list view
return (
<>
<Breadcrumb items={[{ label: "Gateway Configs", active: true }]} />
<Breadcrumb items={[{ label: "AI Gateway Configs", active: true }]} />

{/* Search bar */}
<SearchBar
Expand All @@ -618,7 +621,7 @@ const ListGatewayConfigsUI = ({
resultCount={totalCount}
onSearchChange={search.setSearchQuery}
onSearchSubmit={search.submitSearch}
placeholder="Search gateway configs..."
placeholder="Search AI gateway configs..."
/>

{/* Table - hide when popup is shown */}
Expand All @@ -631,7 +634,8 @@ const ListGatewayConfigsUI = ({
columns={columns}
emptyState={
<Text color={colors.textDim}>
{figures.info} No gateway configs found. Press [c] to create one.
{figures.info} No AI gateway configs found. Press [c] to create
one.
</Text>
}
/>
Expand Down
77 changes: 48 additions & 29 deletions src/commands/gateway-config/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

import { getClient } from "../../utils/client.js";
import { output, outputError } from "../../utils/output.js";
import { validateGatewayConfig } from "../../utils/gatewayConfigValidation.js";

interface UpdateOptions {
id: string;
name?: string;
endpoint?: string;
authType?: string;
authKey?: string;
bearerAuth?: boolean;
headerAuth?: string;
description?: string;
output?: string;
}
Expand All @@ -19,47 +20,65 @@ export async function updateGatewayConfig(options: UpdateOptions) {
try {
const client = getClient();

// Validate that at most one auth type is specified
if (options.bearerAuth && options.headerAuth) {
outputError(
"Cannot specify both --bearer-auth and --header-auth. Choose one.",
);
return;
}

// Determine auth type if specified
const authType = options.bearerAuth
? "bearer"
: options.headerAuth
? "header"
: undefined;

// Validate provided fields using shared validation
const validation = validateGatewayConfig(
{
name: options.name,
endpoint: options.endpoint,
authType,
authKey: options.headerAuth,
},
{ requireName: false, requireEndpoint: false },
);

if (!validation.valid) {
outputError(validation.errors.join("\n"));
return;
}

const { sanitized } = validation;

// Build update params - only include fields that are provided
const updateParams: Record<string, unknown> = {};

if (options.name) {
updateParams.name = options.name;
if (sanitized!.name) {
updateParams.name = sanitized!.name;
}
if (options.endpoint) {
updateParams.endpoint = options.endpoint;
if (sanitized!.endpoint) {
updateParams.endpoint = sanitized!.endpoint;
}
if (options.description !== undefined) {
updateParams.description = options.description;
updateParams.description = options.description.trim() || undefined;
}

// Handle auth mechanism update
if (options.authType) {
const authType = options.authType.toLowerCase();
if (authType !== "bearer" && authType !== "header") {
outputError("Invalid auth type. Must be 'bearer' or 'header'");
return;
}

const authMechanism: { type: string; key?: string } = {
type: authType,
if (sanitized!.authType === "bearer") {
updateParams.auth_mechanism = { type: "bearer" };
} else if (sanitized!.authType === "header" && sanitized!.authKey) {
updateParams.auth_mechanism = {
type: "header",
key: sanitized!.authKey,
};
if (authType === "header") {
if (!options.authKey) {
outputError("--auth-key is required when auth-type is 'header'");
return;
}
authMechanism.key = options.authKey;
}
updateParams.auth_mechanism = authMechanism;
} else if (options.authKey) {
// If only auth key is provided without auth type, we need the type
outputError("--auth-type is required when updating --auth-key");
return;
}

if (Object.keys(updateParams).length === 0) {
outputError(
"No update options provided. Use --name, --endpoint, --auth-type, --auth-key, or --description",
"No update options provided. Use --name, --endpoint, --bearer-auth, --header-auth, or --description",
);
return;
}
Expand Down
Loading
Loading