Skip to content

Commit f4b59fd

Browse files
authored
feat: better handling of RUNLOOP_BASE_URL (#216)
1 parent b685b65 commit f4b59fd

22 files changed

Lines changed: 205 additions & 123 deletions

.github/workflows/e2e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ jobs:
3737
- name: Run e2e smoke tests
3838
env:
3939
RUNLOOP_API_KEY: ${{ inputs.environment == 'prod' && secrets.RUNLOOP_SMOKETEST_PROD_API_KEY || secrets.RUNLOOP_SMOKETEST_DEV_API_KEY }}
40-
RUNLOOP_ENV: ${{ inputs.environment }}
40+
RUNLOOP_BASE_URL: ${{ inputs.environment == 'prod' && 'https://api.runloop.ai' || 'https://api.runloop.pro' }}
4141
run: pnpm run test:e2e

CLAUDE_SETUP.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ Press Ctrl+C to stop it.
133133

134134
## Advanced Configuration
135135

136-
### Using Development Environment
136+
### Custom API endpoint
137137

138-
If you want to connect to Runloop's development environment:
138+
To connect to a non-default Runloop deployment, set `RUNLOOP_BASE_URL` to the full API URL (must be `https://api.<domain>`):
139139

140140
```json
141141
{
@@ -144,13 +144,15 @@ If you want to connect to Runloop's development environment:
144144
"command": "rli",
145145
"args": ["mcp", "start"],
146146
"env": {
147-
"RUNLOOP_ENV": "dev"
147+
"RUNLOOP_BASE_URL": "https://api.runloop.pro"
148148
}
149149
}
150150
}
151151
}
152152
```
153153

154+
See the repository [README](README.md) **Setup → Custom API endpoint (`RUNLOOP_BASE_URL`)** for the hostname table.
155+
154156
### Using a Specific Path
155157

156158
If `rli` isn't in your PATH, you can specify the full path:

MCP_COMMANDS.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ When you run `rli mcp install`, it creates this configuration in your Claude Des
6565
}
6666
```
6767

68-
### With Environment Variables
68+
### Custom API endpoint
6969

70-
To use the development environment:
70+
To point MCP at a different deployment, set `RUNLOOP_BASE_URL` (must be `https://api.<domain>`):
7171

7272
```json
7373
{
@@ -76,13 +76,15 @@ To use the development environment:
7676
"command": "rli",
7777
"args": ["mcp", "start"],
7878
"env": {
79-
"RUNLOOP_ENV": "dev"
79+
"RUNLOOP_BASE_URL": "https://api.runloop.pro"
8080
}
8181
}
8282
}
8383
}
8484
```
8585

86+
See [README.md](README.md) (Setup → Custom API endpoint) for the hostname table.
87+
8688
## Available Tools
8789

8890
Once configured, Claude can use these tools:

MCP_README.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,6 @@ If you prefer to configure manually, add this to your Claude configuration file:
102102
"runloop": {
103103
"command": "rli",
104104
"args": ["mcp", "start"],
105-
"env": {
106-
"RUNLOOP_ENV": "prod"
107-
}
108105
}
109106
}
110107
}
@@ -151,8 +148,8 @@ Claude will use the MCP tools to interact with your Runloop account and provide
151148

152149
## Environment Variables
153150

154-
- `RUNLOOP_ENV` - Set to `dev` for development environment, `prod` (or leave unset) for production
155-
- API key is read from the CLI configuration (~/.config/rli/config.json)
151+
- `RUNLOOP_BASE_URL` — Optional. Full API URL of the form `https://api.<domain>` (e.g. `https://api.runloop.pro`). Defaults to `https://api.runloop.ai`. See [README](README.md) **Setup → Custom API endpoint (`RUNLOOP_BASE_URL`)**.
152+
- API key is read from the CLI configuration (`~/.runloop/config.json`)
156153

157154
## Troubleshooting
158155

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,31 @@ pnpm add -g @runloop/rl-cli
4646

4747
## Setup
4848

49-
Configure your API key:
49+
### API key
5050

5151
```bash
5252
export RUNLOOP_API_KEY=your_api_key_here
5353
```
5454

5555
Get your API key from [https://runloop.ai/settings](https://runloop.ai/settings)
5656

57+
### Custom API endpoint (`RUNLOOP_BASE_URL`, optional)
58+
59+
By default the CLI and MCP server connect to `https://api.runloop.ai`. To use a different deployment, set `RUNLOOP_BASE_URL` to the full API URL:
60+
61+
```bash
62+
export RUNLOOP_BASE_URL=https://api.runloop.pro
63+
```
64+
65+
The URL must be of the form `https://api.<domain>`. The CLI derives other service hostnames from the domain portion:
66+
67+
| Service | Host |
68+
|----------|------|
69+
| API | `https://api.<domain>` (the value of `RUNLOOP_BASE_URL`) |
70+
| Platform | `https://platform.<domain>` |
71+
| SSH | `ssh.<domain>:443` |
72+
| Tunnels | `tunnel.<domain>` |
73+
5774
## Usage
5875

5976
### TUI (Interactive Mode)

src/cli.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { exitAlternateScreenBuffer } from "./utils/screen.js";
44
import { processUtils } from "./utils/processUtils.js";
55
import { createProgram } from "./utils/commands.js";
6-
import { getApiKeyErrorMessage } from "./utils/config.js";
6+
import { getApiKeyErrorMessage, checkBaseDomain } from "./utils/config.js";
77

88
// Global Ctrl+C handler to ensure it always exits
99
processUtils.on("SIGINT", () => {
@@ -21,6 +21,8 @@ const program = createProgram();
2121
const { initializeTheme } = await import("./utils/theme.js");
2222
await initializeTheme();
2323

24+
checkBaseDomain();
25+
2426
// Check if API key is configured (except for mcp commands)
2527
const args = process.argv.slice(2);
2628
if (!process.env.RUNLOOP_API_KEY) {

src/components/Breadcrumb.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
22
import { Box, Text, useStdout } from "ink";
33
import { colors } from "../utils/theme.js";
4+
import { runloopBaseDomain } from "../utils/config.js";
45
import { UpdateNotification } from "./UpdateNotification.js";
56

67
export interface BreadcrumbItem {
@@ -38,8 +39,8 @@ export const Breadcrumb = ({
3839
showVersionCheck = false,
3940
compactMode: compactModeProp,
4041
}: BreadcrumbProps) => {
41-
const env = process.env.RUNLOOP_ENV?.toLowerCase();
42-
const isDevEnvironment = env === "dev";
42+
const baseDomain = runloopBaseDomain();
43+
const isNonDefaultDomain = baseDomain !== "runloop.ai";
4344
const { stdout } = useStdout();
4445

4546
const [terminalWidth, setTerminalWidth] = React.useState(() =>
@@ -109,10 +110,10 @@ export const Breadcrumb = ({
109110
<Text color={colors.primary} bold>
110111
rl
111112
</Text>
112-
{isDevEnvironment && mode !== "minimal" && (
113-
<Text color={colors.error} bold>
113+
{isNonDefaultDomain && mode !== "minimal" && (
114+
<Text color={colors.warning} bold>
114115
{" "}
115-
(dev)
116+
({baseDomain})
116117
</Text>
117118
)}
118119
{displayItems.length > 0 && <Text color={colors.textDim}></Text>}

src/components/DevboxActionsMenu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ConfirmationPrompt } from "./ConfirmationPrompt.js";
1212
import { colors } from "../utils/theme.js";
1313
import { openInBrowser } from "../utils/browser.js";
1414
import { copyToClipboard } from "../utils/clipboard.js";
15+
import { sshGatewayHostname } from "../utils/config.js";
1516
import { useViewportHeight } from "../hooks/useViewportHeight.js";
1617
import { useNavigation } from "../store/navigationStore.js";
1718
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
@@ -661,8 +662,7 @@ export const DevboxActionsMenu = ({
661662

662663
const sshUser =
663664
devbox.launch_parameters?.user_parameters?.username || "user";
664-
const env = process.env.RUNLOOP_ENV?.toLowerCase();
665-
const sshHost = env === "dev" ? "ssh.runloop.pro" : "ssh.runloop.ai";
665+
const sshHost = sshGatewayHostname();
666666
// macOS openssl doesn't support -verify_quiet, use compatible flags
667667
// servername should be %h (target hostname) - SSH will replace %h with the actual hostname from the SSH command
668668
// This matches the reference implementation where servername is the target hostname

src/components/DevboxDetailPage.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
type DetailSection,
1313
type ResourceOperation,
1414
} from "./ResourceDetailPage.js";
15-
import { getDevboxUrl, getDevboxTunnelUrlPattern } from "../utils/url.js";
15+
import { getDevboxUrl, getTunnelUrl } from "../utils/url.js";
1616
import { colors } from "../utils/theme.js";
1717
import { formatTimeAgo } from "../utils/time.js";
1818
import { getMcpConfig } from "../services/mcpConfigService.js";
@@ -339,7 +339,7 @@ export const DevboxDetailPage = ({ devbox, onBack }: DevboxDetailPageProps) => {
339339
if (devbox.tunnel && devbox.tunnel.tunnel_key) {
340340
const tunnelKey = devbox.tunnel.tunnel_key;
341341
const authMode = devbox.tunnel.auth_mode;
342-
const tunnelUrl = getDevboxTunnelUrlPattern(tunnelKey);
342+
const tunnelUrl = getTunnelUrl("{port}", tunnelKey);
343343

344344
detailFields.push({
345345
label: "Tunnel",
@@ -651,7 +651,7 @@ export const DevboxDetailPage = ({ devbox, onBack }: DevboxDetailPageProps) => {
651651
</Text>,
652652
);
653653

654-
const tunnelUrl = getDevboxTunnelUrlPattern(devbox.tunnel.tunnel_key);
654+
const tunnelUrl = getTunnelUrl("{port}", devbox.tunnel.tunnel_key);
655655
lines.push(
656656
<Text key="tunnel-url" color={colors.success}>
657657
{" "}

src/components/HomeBaseUrlText.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from "react";
2+
import { Text } from "ink";
3+
import { runloopBaseDomain } from "../utils/config.js";
4+
import { colors } from "../utils/theme.js";
5+
6+
/** Shows the active domain in the home footer when it differs from the default. */
7+
export function HomeBaseUrlText() {
8+
const baseDomain = React.useMemo(() => runloopBaseDomain(), []);
9+
if (baseDomain === "runloop.ai") return null;
10+
return (
11+
<Text color={colors.textDim} dimColor>
12+
{"\n"}
13+
Domain: {baseDomain}
14+
</Text>
15+
);
16+
}

0 commit comments

Comments
 (0)