Skip to content

Commit ad33a02

Browse files
authored
fix: gateway cli improvements (#113)
## Description <!-- Provide a brief description of your changes --> **Note:** PR titles should follow [Conventional Commits](https://www.conventionalcommits.org/) format (e.g., `feat(devbox): add support for custom env vars` or `fix(snapshot): resolve pagination issue`) as they are used for automatic release notes generation. ## Type of Change <!-- Mark the relevant option with an 'x' --> - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Documentation update - [ ] Code refactoring - [ ] Performance improvement - [ ] Test updates ## Related Issues <!-- Link to related issues using #issue-number --> Closes # ## Changes Made <!-- Describe the changes in detail --> ## Testing <!-- Describe how you tested your changes --> - [ ] I have tested locally - [ ] I have added/updated tests - [ ] All existing tests pass ## Checklist - [ ] My code follows the code style of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have updated the documentation accordingly - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published ## Screenshots (if applicable) <!-- Add screenshots to help explain your changes --> ## Additional Notes <!-- Any additional information that reviewers should know -->
1 parent 557aab0 commit ad33a02

14 files changed

Lines changed: 1008 additions & 354 deletions

src/commands/gateway-config/create.ts

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44

55
import { getClient } from "../../utils/client.js";
66
import { output, outputError } from "../../utils/output.js";
7+
import { validateGatewayConfig } from "../../utils/gatewayConfigValidation.js";
78

89
interface CreateOptions {
910
name: string;
1011
endpoint: string;
11-
authType: string;
12-
authKey?: string;
12+
bearerAuth?: boolean;
13+
headerAuth?: string;
1314
description?: string;
1415
output?: string;
1516
}
@@ -18,32 +19,48 @@ export async function createGatewayConfig(options: CreateOptions) {
1819
try {
1920
const client = getClient();
2021

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

28-
// Validate auth key is provided for header type
29-
if (authType === "header" && !options.authKey) {
30-
outputError("--auth-key is required when auth-type is 'header'");
30+
// Default to bearer if neither is specified
31+
const authType = options.headerAuth ? "header" : "bearer";
32+
33+
// Validate all fields using shared validation
34+
const validation = validateGatewayConfig(
35+
{
36+
name: options.name,
37+
endpoint: options.endpoint,
38+
authType,
39+
authKey: options.headerAuth,
40+
},
41+
{ requireName: true, requireEndpoint: true },
42+
);
43+
44+
if (!validation.valid) {
45+
outputError(validation.errors.join("\n"));
3146
return;
3247
}
3348

49+
const { sanitized } = validation;
50+
3451
// Build auth mechanism
3552
const authMechanism: { type: string; key?: string } = {
36-
type: authType,
53+
type: sanitized!.authType!,
3754
};
38-
if (authType === "header" && options.authKey) {
39-
authMechanism.key = options.authKey;
55+
if (sanitized!.authType === "header" && sanitized!.authKey) {
56+
authMechanism.key = sanitized!.authKey;
4057
}
4158

4259
const config = await client.gatewayConfigs.create({
43-
name: options.name,
44-
endpoint: options.endpoint,
60+
name: sanitized!.name!,
61+
endpoint: sanitized!.endpoint!,
4562
auth_mechanism: authMechanism,
46-
description: options.description,
63+
description: options.description?.trim() || undefined,
4764
});
4865

4966
// Default: just output the ID for easy scripting

src/commands/gateway-config/get.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
2-
* Get gateway config command
2+
* Get gateway config command - supports lookup by ID or name
33
*/
44

5-
import { getClient } from "../../utils/client.js";
5+
import { getGatewayConfigByIdOrName } from "../../services/gatewayConfigService.js";
66
import { output, outputError } from "../../utils/output.js";
77

88
interface GetOptions {
@@ -12,9 +12,12 @@ interface GetOptions {
1212

1313
export async function getGatewayConfig(options: GetOptions) {
1414
try {
15-
const client = getClient();
15+
const config = await getGatewayConfigByIdOrName(options.id);
1616

17-
const config = await client.gatewayConfigs.retrieve(options.id);
17+
if (!config) {
18+
outputError(`Gateway config not found: ${options.id}`);
19+
return;
20+
}
1821

1922
output(config, { format: options.output, defaultFormat: "json" });
2023
} catch (error) {

src/commands/gateway-config/list.tsx

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,13 @@ const ListGatewayConfigsUI = ({
209209
},
210210
{
211211
key: "edit",
212-
label: "Edit Gateway Config",
212+
label: "Edit AI Gateway Config",
213213
color: colors.warning,
214214
icon: figures.pointer,
215215
},
216216
{
217217
key: "delete",
218-
label: "Delete Gateway Config",
218+
label: "Delete AI Gateway Config",
219219
color: colors.error,
220220
icon: figures.cross,
221221
},
@@ -319,7 +319,7 @@ const ListGatewayConfigsUI = ({
319319
case "delete":
320320
await client.gatewayConfigs.delete(config.id);
321321
setOperationResult(
322-
`Gateway config "${config.name}" deleted successfully`,
322+
`AI gateway config "${config.name}" deleted successfully`,
323323
);
324324
break;
325325
}
@@ -481,11 +481,11 @@ const ListGatewayConfigsUI = ({
481481
if (showDeleteConfirm && selectedConfig) {
482482
return (
483483
<ConfirmationPrompt
484-
title="Delete Gateway Config"
484+
title="Delete AI Gateway Config"
485485
message={`Are you sure you want to delete "${selectedConfig.name}"?`}
486-
details="This action cannot be undone. Any devboxes using this gateway config will no longer have access to it."
486+
details="This action cannot be undone. Any devboxes using this AI gateway config will no longer have access to it."
487487
breadcrumbItems={[
488-
{ label: "Gateway Configs" },
488+
{ label: "AI Gateway Configs" },
489489
{ label: selectedConfig.name || selectedConfig.id },
490490
{ label: "Delete", active: true },
491491
]}
@@ -511,7 +511,7 @@ const ListGatewayConfigsUI = ({
511511
<>
512512
<Breadcrumb
513513
items={[
514-
{ label: "Gateway Configs" },
514+
{ label: "AI Gateway Configs" },
515515
{
516516
label: selectedConfig?.name || selectedConfig?.id || "Config",
517517
},
@@ -534,13 +534,13 @@ const ListGatewayConfigsUI = ({
534534
operations.find((o) => o.key === executingOperation)?.label ||
535535
"Operation";
536536
const messages: Record<string, string> = {
537-
delete: "Deleting gateway config...",
537+
delete: "Deleting AI gateway config...",
538538
};
539539
return (
540540
<>
541541
<Breadcrumb
542542
items={[
543-
{ label: "Gateway Configs" },
543+
{ label: "AI Gateway Configs" },
544544
{ label: selectedConfig.name || selectedConfig.id },
545545
{ label: operationLabel, active: true },
546546
]}
@@ -589,8 +589,8 @@ const ListGatewayConfigsUI = ({
589589
if (loading && configs.length === 0) {
590590
return (
591591
<>
592-
<Breadcrumb items={[{ label: "Gateway Configs", active: true }]} />
593-
<SpinnerComponent message="Loading gateway configs..." />
592+
<Breadcrumb items={[{ label: "AI Gateway Configs", active: true }]} />
593+
<SpinnerComponent message="Loading AI gateway configs..." />
594594
</>
595595
);
596596
}
@@ -599,16 +599,19 @@ const ListGatewayConfigsUI = ({
599599
if (error) {
600600
return (
601601
<>
602-
<Breadcrumb items={[{ label: "Gateway Configs", active: true }]} />
603-
<ErrorMessage message="Failed to list gateway configs" error={error} />
602+
<Breadcrumb items={[{ label: "AI Gateway Configs", active: true }]} />
603+
<ErrorMessage
604+
message="Failed to list AI gateway configs"
605+
error={error}
606+
/>
604607
</>
605608
);
606609
}
607610

608611
// Main list view
609612
return (
610613
<>
611-
<Breadcrumb items={[{ label: "Gateway Configs", active: true }]} />
614+
<Breadcrumb items={[{ label: "AI Gateway Configs", active: true }]} />
612615

613616
{/* Search bar */}
614617
<SearchBar
@@ -618,7 +621,7 @@ const ListGatewayConfigsUI = ({
618621
resultCount={totalCount}
619622
onSearchChange={search.setSearchQuery}
620623
onSearchSubmit={search.submitSearch}
621-
placeholder="Search gateway configs..."
624+
placeholder="Search AI gateway configs..."
622625
/>
623626

624627
{/* Table - hide when popup is shown */}
@@ -631,7 +634,8 @@ const ListGatewayConfigsUI = ({
631634
columns={columns}
632635
emptyState={
633636
<Text color={colors.textDim}>
634-
{figures.info} No gateway configs found. Press [c] to create one.
637+
{figures.info} No AI gateway configs found. Press [c] to create
638+
one.
635639
</Text>
636640
}
637641
/>

src/commands/gateway-config/update.ts

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
import { getClient } from "../../utils/client.js";
66
import { output, outputError } from "../../utils/output.js";
7+
import { validateGatewayConfig } from "../../utils/gatewayConfigValidation.js";
78

89
interface UpdateOptions {
910
id: string;
1011
name?: string;
1112
endpoint?: string;
12-
authType?: string;
13-
authKey?: string;
13+
bearerAuth?: boolean;
14+
headerAuth?: string;
1415
description?: string;
1516
output?: string;
1617
}
@@ -19,47 +20,65 @@ export async function updateGatewayConfig(options: UpdateOptions) {
1920
try {
2021
const client = getClient();
2122

23+
// Validate that at most one auth type is specified
24+
if (options.bearerAuth && options.headerAuth) {
25+
outputError(
26+
"Cannot specify both --bearer-auth and --header-auth. Choose one.",
27+
);
28+
return;
29+
}
30+
31+
// Determine auth type if specified
32+
const authType = options.bearerAuth
33+
? "bearer"
34+
: options.headerAuth
35+
? "header"
36+
: undefined;
37+
38+
// Validate provided fields using shared validation
39+
const validation = validateGatewayConfig(
40+
{
41+
name: options.name,
42+
endpoint: options.endpoint,
43+
authType,
44+
authKey: options.headerAuth,
45+
},
46+
{ requireName: false, requireEndpoint: false },
47+
);
48+
49+
if (!validation.valid) {
50+
outputError(validation.errors.join("\n"));
51+
return;
52+
}
53+
54+
const { sanitized } = validation;
55+
2256
// Build update params - only include fields that are provided
2357
const updateParams: Record<string, unknown> = {};
2458

25-
if (options.name) {
26-
updateParams.name = options.name;
59+
if (sanitized!.name) {
60+
updateParams.name = sanitized!.name;
2761
}
28-
if (options.endpoint) {
29-
updateParams.endpoint = options.endpoint;
62+
if (sanitized!.endpoint) {
63+
updateParams.endpoint = sanitized!.endpoint;
3064
}
3165
if (options.description !== undefined) {
32-
updateParams.description = options.description;
66+
updateParams.description = options.description.trim() || undefined;
3367
}
3468

3569
// Handle auth mechanism update
36-
if (options.authType) {
37-
const authType = options.authType.toLowerCase();
38-
if (authType !== "bearer" && authType !== "header") {
39-
outputError("Invalid auth type. Must be 'bearer' or 'header'");
40-
return;
41-
}
42-
43-
const authMechanism: { type: string; key?: string } = {
44-
type: authType,
70+
if (sanitized!.authType === "bearer") {
71+
updateParams.auth_mechanism = { type: "bearer" };
72+
} else if (sanitized!.authType === "header" && sanitized!.authKey) {
73+
updateParams.auth_mechanism = {
74+
type: "header",
75+
key: sanitized!.authKey,
4576
};
46-
if (authType === "header") {
47-
if (!options.authKey) {
48-
outputError("--auth-key is required when auth-type is 'header'");
49-
return;
50-
}
51-
authMechanism.key = options.authKey;
52-
}
53-
updateParams.auth_mechanism = authMechanism;
54-
} else if (options.authKey) {
55-
// If only auth key is provided without auth type, we need the type
56-
outputError("--auth-type is required when updating --auth-key");
57-
return;
5877
}
5978

6079
if (Object.keys(updateParams).length === 0) {
6180
outputError(
62-
"No update options provided. Use --name, --endpoint, --auth-type, --auth-key, or --description",
81+
"No update options provided. Use --name, --endpoint, --bearer-auth, --header-auth, or --description",
6382
);
6483
return;
6584
}

0 commit comments

Comments
 (0)