Skip to content

Commit 8419b69

Browse files
alb-rlcursoragent
andauthored
feat: network policy create flags (#123)
## Description This PR updates the `rl-cli` to support the newly introduced `allow AI gateway` and `allow MCP gateway` flags for network policy creation, aligning the CLI with recent backend controller 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) - [x] 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 - **CLI Command:** Added `--allow-ai-gateway` and `--allow-mcp-gateway` flags to the `rl network-policy create` command. - **Type Definitions & Interfaces:** Updated `CreateOptions`, `CreateNetworkPolicyParams`, `UpdateNetworkPolicyParams`, and `ExtendedEgress` to include the new `allowAiGateway` and `allowMcpGateway` fields. - **Service Layer:** Modified `createNetworkPolicy`, `updateNetworkPolicy`, `getNetworkPolicy`, and `listNetworkPolicies` functions in `networkPolicyService` to handle the new gateway fields. - **UI Components:** Integrated form fields for `allowAiGateway` and `allowMcpGateway` into the `NetworkPolicyCreatePage` and added display for these settings in the `NetworkPolicyDetailScreen`. ## Testing <!-- Describe how you tested your changes --> - [x] I have tested locally - [ ] I have added/updated tests - [x] All existing tests pass Verified: - CLI help output for `network-policy create` displays the new flags. - TypeScript compilation is successful with no errors. - Functional testing of creating and viewing network policies via the CLI and UI, confirming the new gateway settings are correctly applied and displayed. ## Checklist - [x] My code follows the code style of this project - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [x] I have updated the documentation accordingly (CLI help output) - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published ## Screenshots (if applicable) <!-- Add screenshots to help explain your changes --> ## Additional Notes The new `allow AI gateway` and `allow MCP gateway` flags default to "No" in the UI. --- [Slack Thread](https://runloophq.slack.com/archives/C079G6098BH/p1772056925156089?thread_ts=1772056925.156089&cid=C079G6098BH) <p><a href="https://cursor.com/agents/bc-c2e84086-a2bf-5e94-9875-8f69a1de7263"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-c2e84086-a2bf-5e94-9875-8f69a1de7263"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</p> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent f014f2d commit 8419b69

9 files changed

Lines changed: 139 additions & 49 deletions

File tree

.github/workflows/pr-title.yml

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,83 @@ name: PR Title Check
33
on:
44
pull_request:
55
types: [opened, edited, synchronize, reopened]
6+
workflow_dispatch:
67

78
jobs:
89
pr-title-check:
910
runs-on: ubuntu-slim
1011
steps:
11-
- uses: amannn/action-semantic-pull-request@v6
12-
env:
13-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12+
- name: Validate PR title
13+
uses: actions/github-script@v7
1414
with:
15-
# Configure which types are allowed
16-
types: |
17-
feat
18-
fix
19-
docs
20-
style
21-
refactor
22-
perf
23-
test
24-
build
25-
ci
26-
chore
27-
revert
28-
# Require scope to be provided
29-
requireScope: false
30-
# Configure which scopes are allowed (empty = any scope allowed)
31-
scopes: |
32-
cli
33-
mcp
34-
devbox
35-
benchmark
36-
secret
37-
blueprint
38-
storage-object
39-
network-policy
40-
main
41-
snapshot
42-
config
43-
auth
44-
deps
45-
# Ensure the subject starts with a lowercase letter
46-
subjectPattern: ^(?![A-Z]).+$
47-
subjectPatternError: |
48-
The subject "{subject}" found in the pull request title "{title}"
49-
should start with a lowercase letter.
50-
51-
Examples:
52-
- feat: add new devbox command
53-
- fix(cli): resolve argument parsing issue
54-
- docs: update README with usage examples
15+
script: |
16+
// Fetch current PR to always get latest title, even on workflow re-runs
17+
const prNumber = context.issue.number || context.payload.pull_request?.number;
18+
if (!prNumber) {
19+
core.setFailed('Unable to determine PR number');
20+
return;
21+
}
22+
23+
const { data: pr } = await github.rest.pulls.get({
24+
owner: context.repo.owner,
25+
repo: context.repo.repo,
26+
pull_number: prNumber
27+
});
28+
29+
const title = pr.title;
30+
console.log(`Validating PR title: "${title}"`);
31+
32+
// Define allowed types
33+
const types = [
34+
'feat', 'fix', 'docs', 'style', 'refactor',
35+
'perf', 'test', 'build', 'ci', 'chore', 'revert'
36+
];
37+
38+
// Define allowed scopes (optional)
39+
const scopes = [
40+
'cli', 'mcp', 'devbox', 'benchmark', 'secret',
41+
'blueprint', 'storage-object', 'network-policy',
42+
'main', 'snapshot', 'config', 'auth', 'deps'
43+
];
44+
45+
// Regex pattern: type(scope)?: description
46+
// type is required, scope is optional, description must start with lowercase
47+
const pattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9-]+\))?:\s*[a-z].+$/;
48+
49+
if (!pattern.test(title)) {
50+
const errorMsg = [
51+
'❌ PR title does not follow the conventional commit format.',
52+
'',
53+
`Found: "${title}"`,
54+
'',
55+
'Expected format: type(scope)?: description',
56+
'',
57+
'Where:',
58+
`- type: one of ${types.join(', ')}`,
59+
`- scope (optional): one of ${scopes.join(', ')}`,
60+
'- description: starts with a lowercase letter',
61+
'',
62+
'Examples:',
63+
'- feat: add new devbox command',
64+
'- fix(cli): resolve argument parsing issue',
65+
'- docs: update README with usage examples',
66+
'- feat(network-policy): add support for gateway flags'
67+
].join('\n');
68+
69+
core.setFailed(errorMsg);
70+
return;
71+
}
72+
73+
// Extract and validate scope if present
74+
const scopeMatch = title.match(/\(([^)]+)\)/);
75+
if (scopeMatch) {
76+
const scope = scopeMatch[1];
77+
if (!scopes.includes(scope)) {
78+
core.setFailed(
79+
`❌ Invalid scope "${scope}".\n\nAllowed scopes: ${scopes.join(', ')}`
80+
);
81+
return;
82+
}
83+
}
84+
85+
console.log('✅ PR title is valid!');

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"dependencies": {
7272
"@js-temporal/polyfill": "^0.5.1",
7373
"@modelcontextprotocol/sdk": "^1.26.0",
74-
"@runloop/api-client": "1.6.0",
74+
"@runloop/api-client": "1.9.0",
7575
"@types/express": "^5.0.6",
7676
"chalk": "^5.6.2",
7777
"commander": "^14.0.2",

pnpm-lock.yaml

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/commands/network-policy/create.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ interface CreateOptions {
1010
description?: string;
1111
allowAll?: boolean;
1212
allowDevboxToDevbox?: boolean;
13+
allowAiGateway?: boolean;
14+
allowMcpGateway?: boolean;
1315
allowedHostnames?: string[];
1416
output?: string;
1517
}
@@ -23,6 +25,8 @@ export async function createNetworkPolicy(options: CreateOptions) {
2325
description: options.description,
2426
allow_all: options.allowAll ?? false,
2527
allow_devbox_to_devbox: options.allowDevboxToDevbox ?? false,
28+
allow_ai_gateway: options.allowAiGateway ?? false,
29+
allow_mcp_gateway: options.allowMcpGateway ?? false,
2630
allowed_hostnames: options.allowedHostnames ?? [],
2731
});
2832

src/commands/network-policy/list.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ interface NetworkPolicyListItem {
4040
egress: {
4141
allow_all: boolean;
4242
allow_devbox_to_devbox: boolean;
43+
allow_ai_gateway: boolean;
44+
allow_mcp_gateway: boolean;
4345
allowed_hostnames: string[];
4446
};
4547
}
@@ -165,6 +167,8 @@ const ListNetworkPoliciesUI = ({
165167
egress: {
166168
allow_all: p.egress.allow_all,
167169
allow_devbox_to_devbox: p.egress.allow_devbox_to_devbox,
170+
allow_ai_gateway: p.egress.allow_ai_gateway,
171+
allow_mcp_gateway: p.egress.allow_mcp_gateway,
168172
allowed_hostnames: [...(p.egress.allowed_hostnames || [])],
169173
},
170174
});

src/components/NetworkPolicyCreatePage.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,17 @@ type FormField =
3535
| "description"
3636
| "allow_all"
3737
| "allow_devbox_to_devbox"
38+
| "allow_ai_gateway"
39+
| "allow_mcp_gateway"
3840
| "allowed_hostnames";
3941

4042
interface FormData {
4143
name: string;
4244
description: string;
4345
allow_all: "Yes" | "No";
4446
allow_devbox_to_devbox: "Yes" | "No";
47+
allow_ai_gateway: "Yes" | "No";
48+
allow_mcp_gateway: "Yes" | "No";
4549
allowed_hostnames: string[];
4650
}
4751

@@ -63,6 +67,10 @@ export const NetworkPolicyCreatePage = ({
6367
allow_devbox_to_devbox: initialPolicy.egress.allow_devbox_to_devbox
6468
? "Yes"
6569
: "No",
70+
allow_ai_gateway: initialPolicy.egress.allow_ai_gateway ? "Yes" : "No",
71+
allow_mcp_gateway: initialPolicy.egress.allow_mcp_gateway
72+
? "Yes"
73+
: "No",
6674
allowed_hostnames: initialPolicy.egress.allowed_hostnames || [],
6775
};
6876
}
@@ -71,6 +79,8 @@ export const NetworkPolicyCreatePage = ({
7179
description: "",
7280
allow_all: "No",
7381
allow_devbox_to_devbox: "No",
82+
allow_ai_gateway: "No",
83+
allow_mcp_gateway: "No",
7484
allowed_hostnames: [],
7585
};
7686
});
@@ -100,6 +110,16 @@ export const NetworkPolicyCreatePage = ({
100110
label: "Allow Devbox-to-Devbox",
101111
type: "select",
102112
},
113+
{
114+
key: "allow_ai_gateway",
115+
label: "Allow AI Gateway",
116+
type: "select",
117+
},
118+
{
119+
key: "allow_mcp_gateway",
120+
label: "Allow MCP Gateway",
121+
type: "select",
122+
},
103123
{ key: "allowed_hostnames", label: "Allowed Hostnames", type: "list" },
104124
];
105125

@@ -233,6 +253,8 @@ export const NetworkPolicyCreatePage = ({
233253
description?: string;
234254
allow_all?: boolean;
235255
allow_devbox_to_devbox?: boolean;
256+
allow_ai_gateway?: boolean;
257+
allow_mcp_gateway?: boolean;
236258
allowed_hostnames?: string[];
237259
} = {};
238260

@@ -252,6 +274,8 @@ export const NetworkPolicyCreatePage = ({
252274

253275
params.allow_all = formData.allow_all === "Yes";
254276
params.allow_devbox_to_devbox = formData.allow_devbox_to_devbox === "Yes";
277+
params.allow_ai_gateway = formData.allow_ai_gateway === "Yes";
278+
params.allow_mcp_gateway = formData.allow_mcp_gateway === "Yes";
255279

256280
// For allowed_hostnames, always send the current list
257281
// (empty array means no hostnames allowed)

src/screens/NetworkPolicyDetailScreen.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,18 @@ export function NetworkPolicyDetailScreen({
332332
{np.egress.allow_devbox_to_devbox ? "Yes" : "No"}
333333
</Text>,
334334
);
335+
lines.push(
336+
<Text key="egress-ai-gateway" dimColor>
337+
{" "}
338+
Allow AI Gateway: {np.egress.allow_ai_gateway ? "Yes" : "No"}
339+
</Text>,
340+
);
341+
lines.push(
342+
<Text key="egress-mcp-gateway" dimColor>
343+
{" "}
344+
Allow MCP Gateway: {np.egress.allow_mcp_gateway ? "Yes" : "No"}
345+
</Text>,
346+
);
335347
lines.push(<Text key="egress-space"> </Text>);
336348

337349
// Allowed Hostnames

src/services/networkPolicyService.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export async function listNetworkPolicies(
6464
egress: {
6565
allow_all: p.egress.allow_all,
6666
allow_devbox_to_devbox: p.egress.allow_devbox_to_devbox,
67+
allow_ai_gateway: p.egress.allow_ai_gateway,
68+
allow_mcp_gateway: p.egress.allow_mcp_gateway,
6769
allowed_hostnames: p.egress.allowed_hostnames || [],
6870
},
6971
});
@@ -95,6 +97,8 @@ export async function getNetworkPolicy(id: string): Promise<NetworkPolicy> {
9597
egress: {
9698
allow_all: policy.egress.allow_all,
9799
allow_devbox_to_devbox: policy.egress.allow_devbox_to_devbox,
100+
allow_ai_gateway: policy.egress.allow_ai_gateway,
101+
allow_mcp_gateway: policy.egress.allow_mcp_gateway,
98102
allowed_hostnames: policy.egress.allowed_hostnames || [],
99103
},
100104
};
@@ -116,6 +120,8 @@ export interface CreateNetworkPolicyParams {
116120
description?: string;
117121
allow_all?: boolean;
118122
allow_devbox_to_devbox?: boolean;
123+
allow_ai_gateway?: boolean;
124+
allow_mcp_gateway?: boolean;
119125
allowed_hostnames?: string[];
120126
}
121127

@@ -134,6 +140,8 @@ export async function createNetworkPolicy(
134140
egress: {
135141
allow_all: policy.egress.allow_all,
136142
allow_devbox_to_devbox: policy.egress.allow_devbox_to_devbox,
143+
allow_ai_gateway: policy.egress.allow_ai_gateway,
144+
allow_mcp_gateway: policy.egress.allow_mcp_gateway,
137145
allowed_hostnames: policy.egress.allowed_hostnames || [],
138146
},
139147
};
@@ -147,6 +155,8 @@ export interface UpdateNetworkPolicyParams {
147155
description?: string;
148156
allow_all?: boolean;
149157
allow_devbox_to_devbox?: boolean;
158+
allow_ai_gateway?: boolean;
159+
allow_mcp_gateway?: boolean;
150160
allowed_hostnames?: string[];
151161
}
152162

@@ -166,6 +176,8 @@ export async function updateNetworkPolicy(
166176
egress: {
167177
allow_all: policy.egress.allow_all,
168178
allow_devbox_to_devbox: policy.egress.allow_devbox_to_devbox,
179+
allow_ai_gateway: policy.egress.allow_ai_gateway,
180+
allow_mcp_gateway: policy.egress.allow_mcp_gateway,
169181
allowed_hostnames: policy.egress.allowed_hostnames || [],
170182
},
171183
};

src/utils/commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,8 @@ export function createProgram(): Command {
712712
.option("--description <description>", "Policy description")
713713
.option("--allow-all", "Allow all egress traffic")
714714
.option("--allow-devbox-to-devbox", "Allow devbox-to-devbox communication")
715+
.option("--allow-ai-gateway", "Allow AI gateway access")
716+
.option("--allow-mcp-gateway", "Allow MCP gateway access")
715717
.option(
716718
"--allowed-hostnames <hostnames...>",
717719
"List of allowed hostnames for egress",

0 commit comments

Comments
 (0)