From e4974678c968f38aa1899295096af735a531cee4 Mon Sep 17 00:00:00 2001 From: Tony Deng Date: Wed, 22 Apr 2026 13:35:46 -0700 Subject: [PATCH 1/4] add baseURL option + RUNLOOP_BASE_URL --- CLAUDE_SETUP.md | 20 +++++++ MCP_COMMANDS.md | 18 ++++++ MCP_README.md | 7 ++- README.md | 28 ++++++++- src/components/DevboxActionsMenu.tsx | 4 +- src/components/HomeBaseUrlText.tsx | 15 +++++ src/components/MainMenu.tsx | 13 ++++ src/mcp/server.ts | 15 +---- src/utils/client.ts | 23 +------- src/utils/config.ts | 88 +++++++++++++++++++++++++++- src/utils/ssh.ts | 6 +- src/utils/url.ts | 22 +++---- 12 files changed, 200 insertions(+), 59 deletions(-) create mode 100644 src/components/HomeBaseUrlText.tsx diff --git a/CLAUDE_SETUP.md b/CLAUDE_SETUP.md index bbcf7f28..4d188974 100644 --- a/CLAUDE_SETUP.md +++ b/CLAUDE_SETUP.md @@ -151,6 +151,26 @@ If you want to connect to Runloop's development environment: } ``` +### Custom deployment domain + +To use a non-default Runloop deployment, set `RUNLOOP_BASE_URL` to a **bare domain suffix** (e.g. customer vanity domain). The CLI builds `api.`, `platform.`, `ssh.`, and `tunnel.` hostnames from it; invalid or full-URL values are ignored. + +```json +{ + "mcpServers": { + "runloop": { + "command": "rli", + "args": ["mcp", "start"], + "env": { + "RUNLOOP_BASE_URL": "example.com" + } + } + } +} +``` + +See the repository [README](README.md) **Setup → API base URL** for validation and the hostname table. + ### Using a Specific Path If `rli` isn't in your PATH, you can specify the full path: diff --git a/MCP_COMMANDS.md b/MCP_COMMANDS.md index 80d9cdbe..a66c9a0e 100644 --- a/MCP_COMMANDS.md +++ b/MCP_COMMANDS.md @@ -83,6 +83,24 @@ To use the development environment: } ``` +To point MCP at a **custom deployment** (bare domain suffix; overrides hostnames implied by `RUNLOOP_ENV`): + +```json +{ + "mcpServers": { + "runloop": { + "command": "rli", + "args": ["mcp", "start"], + "env": { + "RUNLOOP_BASE_URL": "example.com" + } + } + } +} +``` + +See [README.md](README.md) (Setup → API base URL) for validation rules and the `api.` / `platform.` / `ssh.` / `tunnel.` prefix behavior. + ## Available Tools Once configured, Claude can use these tools: diff --git a/MCP_README.md b/MCP_README.md index 4aa5c1fe..52dde252 100644 --- a/MCP_README.md +++ b/MCP_README.md @@ -151,8 +151,11 @@ Claude will use the MCP tools to interact with your Runloop account and provide ## Environment Variables -- `RUNLOOP_ENV` - Set to `dev` for development environment, `prod` (or leave unset) for production -- API key is read from the CLI configuration (~/.config/rli/config.json) +- `RUNLOOP_API_KEY` — Required unless the key is stored in the CLI config under `~/.runloop`. +- `RUNLOOP_ENV` — `prod` or unset uses production (`https://api.runloop.ai`). +- `RUNLOOP_BASE_URL` — Optional bare domain suffix (e.g. `runloop.ai`). The MCP server builds `api.`, `platform.`, `ssh.`, and `tunnel.` hostnames from it the same way as the CLI. Full URLs and invalid values are ignored; see [README](README.md) **Setup → API base URL**. + +See the main [README](README.md) **Setup → API base URL** for details and TUI behavior. ## Troubleshooting diff --git a/README.md b/README.md index 94134f1e..172ba70c 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ pnpm add -g @runloop/rl-cli ## Setup -Configure your API key: +### API key ```bash export RUNLOOP_API_KEY=your_api_key_here @@ -54,6 +54,32 @@ export RUNLOOP_API_KEY=your_api_key_here Get your API key from [https://runloop.ai/settings](https://runloop.ai/settings) +### API base URL (optional) + +The CLI and MCP server use the Runloop HTTP API. By default: + +- **`RUNLOOP_ENV`** — `prod` or unset uses `https://api.runloop.ai` and `dev` uses `https://api.runloop.pro`. +- **`RUNLOOP_BASE_URL`** — Optional **bare domain suffix** only: e.g. `runloop.ai`, `runloop.pro`, or `runloop.customer.com`. Do **not** include a scheme (`https://`), path, port, or leading `api.` label. Leading/trailing whitespace is trimmed; the value is treated case-insensitively. + +When set and valid, the CLI and MCP use these hosts (all HTTPS on 443 except SSH, which uses TLS to `ssh.:443`): + +| Service | Host | +|----------|------| +| API | `https://api.` | +| Platform | `https://platform.` | +| SSH | `ssh.:443` | +| Tunnels | `tunnel.` (hostname for tunnel URLs) | + +If the value is empty, contains `://`, `/`, whitespace, `:`, or is not a valid multi-label hostname, it is **ignored** and **`RUNLOOP_ENV`** defaults apply (same as unset). + +Example: + +```bash +export RUNLOOP_BASE_URL=runloop.customer.com +``` + +In **interactive (TUI) mode**, the home screen shows the resolved API base URL at the bottom (below the keyboard hints) so you can confirm which API you are using. + ## Usage ### TUI (Interactive Mode) diff --git a/src/components/DevboxActionsMenu.tsx b/src/components/DevboxActionsMenu.tsx index 3299183f..e135d669 100644 --- a/src/components/DevboxActionsMenu.tsx +++ b/src/components/DevboxActionsMenu.tsx @@ -12,6 +12,7 @@ import { ConfirmationPrompt } from "./ConfirmationPrompt.js"; import { colors } from "../utils/theme.js"; import { openInBrowser } from "../utils/browser.js"; import { copyToClipboard } from "../utils/clipboard.js"; +import { sshGatewayHostname } from "../utils/config.js"; import { useViewportHeight } from "../hooks/useViewportHeight.js"; import { useNavigation } from "../store/navigationStore.js"; import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js"; @@ -661,8 +662,7 @@ export const DevboxActionsMenu = ({ const sshUser = devbox.launch_parameters?.user_parameters?.username || "user"; - const env = process.env.RUNLOOP_ENV?.toLowerCase(); - const sshHost = env === "dev" ? "ssh.runloop.pro" : "ssh.runloop.ai"; + const sshHost = sshGatewayHostname(); // macOS openssl doesn't support -verify_quiet, use compatible flags // servername should be %h (target hostname) - SSH will replace %h with the actual hostname from the SSH command // This matches the reference implementation where servername is the target hostname diff --git a/src/components/HomeBaseUrlText.tsx b/src/components/HomeBaseUrlText.tsx new file mode 100644 index 00000000..94849542 --- /dev/null +++ b/src/components/HomeBaseUrlText.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { Text } from "ink"; +import { baseUrl } from "../utils/config.js"; +import { colors } from "../utils/theme.js"; + +/** Compact API origin (`https://api.` when set) for home footer. */ +export function HomeBaseUrlText() { + const apiBase = React.useMemo(() => baseUrl(), []); + return ( + + {"\n"} + Base URL: {apiBase} + + ); +} diff --git a/src/components/MainMenu.tsx b/src/components/MainMenu.tsx index 14df4a8e..0b7cdcda 100644 --- a/src/components/MainMenu.tsx +++ b/src/components/MainMenu.tsx @@ -13,6 +13,7 @@ import { useVerticalLayout } from "../hooks/useVerticalLayout.js"; import { useBetaFeatures } from "../store/betaFeatureStore.js"; import type { BetaFeature } from "../store/betaFeatureStore.js"; import { useMenuStore } from "../store/menuStore.js"; +import { HomeBaseUrlText } from "./HomeBaseUrlText.js"; interface MenuItem { key: string; @@ -284,6 +285,9 @@ export const MainMenu = ({ onSelect }: MainMenuProps) => { })} {navTips} + + + ); } @@ -332,6 +336,9 @@ export const MainMenu = ({ onSelect }: MainMenuProps) => { })} {navTips} + + + ); } @@ -390,6 +397,9 @@ export const MainMenu = ({ onSelect }: MainMenuProps) => { })} {navTips} + + + ); } @@ -494,6 +504,9 @@ export const MainMenu = ({ onSelect }: MainMenuProps) => { {navTips} + + + ); }; diff --git a/src/mcp/server.ts b/src/mcp/server.ts index d5755cc2..5c75c723 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -10,6 +10,7 @@ import Runloop from "@runloop/api-client"; import { VERSION } from "@runloop/api-client/version.js"; import Conf from "conf"; import { processUtils } from "../utils/processUtils.js"; +import { baseUrl as getApiBaseUrl } from "../utils/config.js"; // Client configuration interface Config { @@ -45,18 +46,6 @@ function getConfig(): Config { } } -function getBaseUrl(): string { - const env = process.env.RUNLOOP_ENV?.toLowerCase(); - - switch (env) { - case "dev": - return "https://api.runloop.pro"; - case "prod": - default: - return "https://api.runloop.ai"; - } -} - function getClient(): Runloop { const config = getConfig(); @@ -66,7 +55,7 @@ function getClient(): Runloop { ); } - const baseURL = getBaseUrl(); + const baseURL = getApiBaseUrl(); return new Runloop({ bearerToken: config.apiKey, diff --git a/src/utils/client.ts b/src/utils/client.ts index b5003d2b..04f12fcf 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -1,23 +1,6 @@ -import Runloop from "@runloop/api-client"; +import { Runloop } from "@runloop/api-client"; import { VERSION } from "@runloop/api-client/version.js"; -import { getConfig } from "./config.js"; - -/** - * Get the base URL based on RUNLOOP_ENV environment variable - * - dev: https://api.runloop.pro - * - prod or unset: https://api.runloop.ai (default) - */ -function getBaseUrl(): string { - const env = process.env.RUNLOOP_ENV?.toLowerCase(); - - switch (env) { - case "dev": - return "https://api.runloop.pro"; - case "prod": - default: - return "https://api.runloop.ai"; - } -} +import { getConfig, baseUrl as getApiBaseUrl } from "./config.js"; export function getClient(): Runloop { const config = getConfig(); @@ -28,7 +11,7 @@ export function getClient(): Runloop { ); } - const baseURL = getBaseUrl(); + const baseURL = getApiBaseUrl(); return new Runloop({ bearerToken: config.apiKey, diff --git a/src/utils/config.ts b/src/utils/config.ts index 80dac7e5..e9ac2731 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -31,16 +31,98 @@ export function clearConfig(): void { config.clear(); } +/** + * Bare domain suffix from `RUNLOOP_BASE_URL`, e.g. `runloop.ai` or `example.com`. + * Full URLs, `api.*` hostnames, paths, ports, or invalid hostnames → null (use RUNLOOP_ENV). + */ +function runloopBaseDomainOrNull(): string | null { + const raw = process.env.RUNLOOP_BASE_URL?.trim(); + if (!raw) return null; + if (/:\/\//.test(raw) || /\s/.test(raw) || raw.includes("/")) { + return null; + } + if (raw.includes(":")) { + return null; + } + const domain = raw.toLowerCase(); + if (!isValidBareDomain(domain)) { + return null; + } + return domain; +} + +function isValidBareDomain(domain: string): boolean { + if (domain.length === 0 || domain.length > 253) return false; + if (domain.startsWith(".") || domain.endsWith(".")) return false; + if (!domain.includes(".")) return false; + const labels = domain.split("."); + for (const label of labels) { + if (label.length === 0 || label.length > 63) return false; + if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(label)) return false; + } + return true; +} + +function prefixedHost(prefix: string, domain: string): string { + return `${prefix}.${domain}`; +} + +/** + * HTTP base URL for the Runloop API (used by the CLI client and MCP). + * + * - If `RUNLOOP_BASE_URL` is a valid bare domain, uses `https://api.`. + * - Else `RUNLOOP_ENV=dev` → https://api.runloop.pro; otherwise production. + */ export function baseUrl(): string { + const domain = runloopBaseDomainOrNull(); + if (domain) { + return `https://${prefixedHost("api", domain)}`; + } return process.env.RUNLOOP_ENV === "dev" ? "https://api.runloop.pro" : "https://api.runloop.ai"; } -export function sshUrl(): string { +/** + * Web platform origin for deep links (settings, devbox pages in the browser). + */ +export function platformBaseUrl(): string { + const domain = runloopBaseDomainOrNull(); + if (domain) { + return `https://${prefixedHost("platform", domain)}`; + } + return process.env.RUNLOOP_ENV === "dev" + ? "https://platform.runloop.pro" + : "https://platform.runloop.ai"; +} + +/** Hostname for devbox tunnel URLs (`{port}-{key}.`). */ +export function tunnelBaseHostname(): string { + const domain = runloopBaseDomainOrNull(); + if (domain) { + return prefixedHost("tunnel", domain); + } + return process.env.RUNLOOP_ENV === "dev" + ? "tunnel.runloop.pro" + : "tunnel.runloop.ai"; +} + +/** SSH gateway hostname (TLS/SNI), without port. */ +export function sshGatewayHostname(): string { + const domain = runloopBaseDomainOrNull(); + if (domain) { + return prefixedHost("ssh", domain); + } return process.env.RUNLOOP_ENV === "dev" - ? "ssh.runloop.pro:443" - : "ssh.runloop.ai:443"; + ? "ssh.runloop.pro" + : "ssh.runloop.ai"; +} + +/** + * `host:443` for `openssl s_client -connect` (SSH over HTTPS). + */ +export function sshUrl(): string { + return `${sshGatewayHostname()}:443`; } export function getCacheDir(): string { diff --git a/src/utils/ssh.ts b/src/utils/ssh.ts index 641997e2..bb4e7fcf 100644 --- a/src/utils/ssh.ts +++ b/src/utils/ssh.ts @@ -6,6 +6,7 @@ import { homedir } from "os"; import { getClient } from "./client.js"; import { cliStatus } from "./cliStatus.js"; import { processUtils } from "./processUtils.js"; +import { sshUrl as sshTlsConnectEndpoint } from "./config.js"; const execAsync = promisify(exec); @@ -148,11 +149,10 @@ export async function waitForReady( } /** - * Get SSH URL based on environment + * Get SSH TLS proxy target (`ssh.:443`) from RUNLOOP_BASE_URL domain suffix or RUNLOOP_ENV. */ export function getSSHUrl(): string { - const env = processUtils.env.RUNLOOP_ENV?.toLowerCase(); - return env === "dev" ? "ssh.runloop.pro:443" : "ssh.runloop.ai:443"; + return sshTlsConnectEndpoint(); } /** diff --git a/src/utils/url.ts b/src/utils/url.ts index d856f190..3ea87bd2 100644 --- a/src/utils/url.ts +++ b/src/utils/url.ts @@ -2,21 +2,14 @@ * Utility functions for generating URLs */ +import { platformBaseUrl, tunnelBaseHostname } from "./config.js"; + /** - * Get the base URL for the Runloop platform based on environment - * - dev: https://platform.runloop.pro - * - prod or unset: https://platform.runloop.ai (default) + * Web platform base URL (browser). With `RUNLOOP_BASE_URL=customer.example.com`, + * uses `https://platform.customer.example.com`; otherwise `RUNLOOP_ENV` picks .pro vs .ai. */ export function getBaseUrl(): string { - const env = process.env.RUNLOOP_ENV?.toLowerCase(); - - switch (env) { - case "dev": - return "https://platform.runloop.pro"; - case "prod": - default: - return "https://platform.runloop.ai"; - } + return platformBaseUrl(); } /** @@ -44,11 +37,10 @@ export function getSettingsUrl(): string { } /** - * Hostname for V2 devbox tunnel URLs (matches RUNLOOP_ENV / API host). + * Hostname for V2 devbox tunnel URLs (`tunnel.` when set). */ export function getTunnelBaseHost(): string { - const env = process.env.RUNLOOP_ENV?.toLowerCase(); - return env === "dev" ? "tunnel.runloop.pro" : "tunnel.runloop.ai"; + return tunnelBaseHostname(); } /** From 31f427539a40dac302903ab72566dfdc9c9a6ef2 Mon Sep 17 00:00:00 2001 From: Tony Deng Date: Wed, 22 Apr 2026 13:45:15 -0700 Subject: [PATCH 2/4] update naming --- CLAUDE_SETUP.md | 6 +++--- MCP_COMMANDS.md | 4 ++-- MCP_README.md | 4 ++-- README.md | 6 +++--- src/components/HomeBaseUrlText.tsx | 2 +- src/utils/config.ts | 6 +++--- src/utils/ssh.ts | 2 +- src/utils/url.ts | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CLAUDE_SETUP.md b/CLAUDE_SETUP.md index 4d188974..a2768969 100644 --- a/CLAUDE_SETUP.md +++ b/CLAUDE_SETUP.md @@ -153,7 +153,7 @@ If you want to connect to Runloop's development environment: ### Custom deployment domain -To use a non-default Runloop deployment, set `RUNLOOP_BASE_URL` to a **bare domain suffix** (e.g. customer vanity domain). The CLI builds `api.`, `platform.`, `ssh.`, and `tunnel.` hostnames from it; invalid or full-URL values are ignored. +To use a non-default Runloop deployment, set `RUNLOOP_BASE_DOMAIN` to a **bare domain suffix** (e.g. customer vanity domain). The CLI builds `api.`, `platform.`, `ssh.`, and `tunnel.` hostnames from it; invalid or full-URL values are ignored. ```json { @@ -162,14 +162,14 @@ To use a non-default Runloop deployment, set `RUNLOOP_BASE_URL` to a **bare doma "command": "rli", "args": ["mcp", "start"], "env": { - "RUNLOOP_BASE_URL": "example.com" + "RUNLOOP_BASE_DOMAIN": "example.com" } } } } ``` -See the repository [README](README.md) **Setup → API base URL** for validation and the hostname table. +See the repository [README](README.md) **Setup → Custom domain (`RUNLOOP_BASE_DOMAIN`)** for validation and the hostname table. ### Using a Specific Path diff --git a/MCP_COMMANDS.md b/MCP_COMMANDS.md index a66c9a0e..468e6518 100644 --- a/MCP_COMMANDS.md +++ b/MCP_COMMANDS.md @@ -92,14 +92,14 @@ To point MCP at a **custom deployment** (bare domain suffix; overrides hostnames "command": "rli", "args": ["mcp", "start"], "env": { - "RUNLOOP_BASE_URL": "example.com" + "RUNLOOP_BASE_DOMAIN": "example.com" } } } } ``` -See [README.md](README.md) (Setup → API base URL) for validation rules and the `api.` / `platform.` / `ssh.` / `tunnel.` prefix behavior. +See [README.md](README.md) (Setup → Custom domain) for validation rules and the `api.` / `platform.` / `ssh.` / `tunnel.` prefix behavior. ## Available Tools diff --git a/MCP_README.md b/MCP_README.md index 52dde252..6db8a014 100644 --- a/MCP_README.md +++ b/MCP_README.md @@ -153,9 +153,9 @@ Claude will use the MCP tools to interact with your Runloop account and provide - `RUNLOOP_API_KEY` — Required unless the key is stored in the CLI config under `~/.runloop`. - `RUNLOOP_ENV` — `prod` or unset uses production (`https://api.runloop.ai`). -- `RUNLOOP_BASE_URL` — Optional bare domain suffix (e.g. `runloop.ai`). The MCP server builds `api.`, `platform.`, `ssh.`, and `tunnel.` hostnames from it the same way as the CLI. Full URLs and invalid values are ignored; see [README](README.md) **Setup → API base URL**. +- `RUNLOOP_BASE_DOMAIN` — Optional bare domain suffix (e.g. `runloop.ai`). The MCP server builds `api.`, `platform.`, `ssh.`, and `tunnel.` hostnames from it the same way as the CLI. Full URLs and invalid values are ignored; see [README](README.md) **Setup → Custom domain (`RUNLOOP_BASE_DOMAIN`)**. -See the main [README](README.md) **Setup → API base URL** for details and TUI behavior. +See the main [README](README.md) **Setup → Custom domain (`RUNLOOP_BASE_DOMAIN`)** for details and TUI behavior. ## Troubleshooting diff --git a/README.md b/README.md index 172ba70c..a7c02f21 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,12 @@ export RUNLOOP_API_KEY=your_api_key_here Get your API key from [https://runloop.ai/settings](https://runloop.ai/settings) -### API base URL (optional) +### Custom domain (`RUNLOOP_BASE_DOMAIN`, optional) The CLI and MCP server use the Runloop HTTP API. By default: - **`RUNLOOP_ENV`** — `prod` or unset uses `https://api.runloop.ai` and `dev` uses `https://api.runloop.pro`. -- **`RUNLOOP_BASE_URL`** — Optional **bare domain suffix** only: e.g. `runloop.ai`, `runloop.pro`, or `runloop.customer.com`. Do **not** include a scheme (`https://`), path, port, or leading `api.` label. Leading/trailing whitespace is trimmed; the value is treated case-insensitively. +- **`RUNLOOP_BASE_DOMAIN`** — Optional **bare domain suffix** only: e.g. `runloop.ai`, `runloop.pro`, or `runloop.customer.com`. Do **not** include a scheme (`https://`), path, port, or leading `api.` label. Leading/trailing whitespace is trimmed; the value is treated case-insensitively. When set and valid, the CLI and MCP use these hosts (all HTTPS on 443 except SSH, which uses TLS to `ssh.:443`): @@ -75,7 +75,7 @@ If the value is empty, contains `://`, `/`, whitespace, `:`, or is not a valid m Example: ```bash -export RUNLOOP_BASE_URL=runloop.customer.com +export RUNLOOP_BASE_DOMAIN=runloop.customer.com ``` In **interactive (TUI) mode**, the home screen shows the resolved API base URL at the bottom (below the keyboard hints) so you can confirm which API you are using. diff --git a/src/components/HomeBaseUrlText.tsx b/src/components/HomeBaseUrlText.tsx index 94849542..b1cd74d7 100644 --- a/src/components/HomeBaseUrlText.tsx +++ b/src/components/HomeBaseUrlText.tsx @@ -3,7 +3,7 @@ import { Text } from "ink"; import { baseUrl } from "../utils/config.js"; import { colors } from "../utils/theme.js"; -/** Compact API origin (`https://api.` when set) for home footer. */ +/** Compact API origin (`https://api.` when set) for home footer. */ export function HomeBaseUrlText() { const apiBase = React.useMemo(() => baseUrl(), []); return ( diff --git a/src/utils/config.ts b/src/utils/config.ts index e9ac2731..40defa6b 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -32,11 +32,11 @@ export function clearConfig(): void { } /** - * Bare domain suffix from `RUNLOOP_BASE_URL`, e.g. `runloop.ai` or `example.com`. + * Bare domain suffix from `RUNLOOP_BASE_DOMAIN`, e.g. `runloop.ai` or `example.com`. * Full URLs, `api.*` hostnames, paths, ports, or invalid hostnames → null (use RUNLOOP_ENV). */ function runloopBaseDomainOrNull(): string | null { - const raw = process.env.RUNLOOP_BASE_URL?.trim(); + const raw = process.env.RUNLOOP_BASE_DOMAIN?.trim(); if (!raw) return null; if (/:\/\//.test(raw) || /\s/.test(raw) || raw.includes("/")) { return null; @@ -70,7 +70,7 @@ function prefixedHost(prefix: string, domain: string): string { /** * HTTP base URL for the Runloop API (used by the CLI client and MCP). * - * - If `RUNLOOP_BASE_URL` is a valid bare domain, uses `https://api.`. + * - If `RUNLOOP_BASE_DOMAIN` is a valid bare domain, uses `https://api.`. * - Else `RUNLOOP_ENV=dev` → https://api.runloop.pro; otherwise production. */ export function baseUrl(): string { diff --git a/src/utils/ssh.ts b/src/utils/ssh.ts index bb4e7fcf..af5ffaa2 100644 --- a/src/utils/ssh.ts +++ b/src/utils/ssh.ts @@ -149,7 +149,7 @@ export async function waitForReady( } /** - * Get SSH TLS proxy target (`ssh.:443`) from RUNLOOP_BASE_URL domain suffix or RUNLOOP_ENV. + * Get SSH TLS proxy target (`ssh.:443`) from `RUNLOOP_BASE_DOMAIN` or `RUNLOOP_ENV`. */ export function getSSHUrl(): string { return sshTlsConnectEndpoint(); diff --git a/src/utils/url.ts b/src/utils/url.ts index 3ea87bd2..341e9380 100644 --- a/src/utils/url.ts +++ b/src/utils/url.ts @@ -5,7 +5,7 @@ import { platformBaseUrl, tunnelBaseHostname } from "./config.js"; /** - * Web platform base URL (browser). With `RUNLOOP_BASE_URL=customer.example.com`, + * Web platform base URL (browser). With `RUNLOOP_BASE_DOMAIN=customer.example.com`, * uses `https://platform.customer.example.com`; otherwise `RUNLOOP_ENV` picks .pro vs .ai. */ export function getBaseUrl(): string { @@ -37,7 +37,7 @@ export function getSettingsUrl(): string { } /** - * Hostname for V2 devbox tunnel URLs (`tunnel.` when set). + * Hostname for V2 devbox tunnel URLs (`tunnel.` when set). */ export function getTunnelBaseHost(): string { return tunnelBaseHostname(); From 7236f02cbec1c167d13a0620ec4f08a0151bb5eb Mon Sep 17 00:00:00 2001 From: Tony Deng Date: Wed, 22 Apr 2026 14:03:01 -0700 Subject: [PATCH 3/4] fix string --- README.md | 4 ++-- src/utils/url.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a7c02f21..454ed1f8 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Get your API key from [https://runloop.ai/settings](https://runloop.ai/settings) The CLI and MCP server use the Runloop HTTP API. By default: - **`RUNLOOP_ENV`** — `prod` or unset uses `https://api.runloop.ai` and `dev` uses `https://api.runloop.pro`. -- **`RUNLOOP_BASE_DOMAIN`** — Optional **bare domain suffix** only: e.g. `runloop.ai`, `runloop.pro`, or `runloop.customer.com`. Do **not** include a scheme (`https://`), path, port, or leading `api.` label. Leading/trailing whitespace is trimmed; the value is treated case-insensitively. +- **`RUNLOOP_BASE_DOMAIN`** — Optional **bare domain suffix** only: e.g. `runloop.ai`, `runloop.pro`, or `example.com`. Do **not** include a scheme (`https://`), path, port, or leading `api.` label. Leading/trailing whitespace is trimmed; the value is treated case-insensitively. When set and valid, the CLI and MCP use these hosts (all HTTPS on 443 except SSH, which uses TLS to `ssh.:443`): @@ -75,7 +75,7 @@ If the value is empty, contains `://`, `/`, whitespace, `:`, or is not a valid m Example: ```bash -export RUNLOOP_BASE_DOMAIN=runloop.customer.com +export RUNLOOP_BASE_DOMAIN=example.com ``` In **interactive (TUI) mode**, the home screen shows the resolved API base URL at the bottom (below the keyboard hints) so you can confirm which API you are using. diff --git a/src/utils/url.ts b/src/utils/url.ts index 341e9380..612db1e9 100644 --- a/src/utils/url.ts +++ b/src/utils/url.ts @@ -5,8 +5,8 @@ import { platformBaseUrl, tunnelBaseHostname } from "./config.js"; /** - * Web platform base URL (browser). With `RUNLOOP_BASE_DOMAIN=customer.example.com`, - * uses `https://platform.customer.example.com`; otherwise `RUNLOOP_ENV` picks .pro vs .ai. + * Web platform base URL (browser). With `RUNLOOP_BASE_DOMAIN=example.com`, + * uses `https://platform.example.com`; otherwise `RUNLOOP_ENV` picks .pro vs .ai. */ export function getBaseUrl(): string { return platformBaseUrl(); From 42ffcd0a57f172b3c6ff94cbc4df73ee645e53e3 Mon Sep 17 00:00:00 2001 From: Tony Deng Date: Wed, 22 Apr 2026 14:11:22 -0700 Subject: [PATCH 4/4] cp --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 454ed1f8..63248fa6 100644 --- a/README.md +++ b/README.md @@ -78,8 +78,6 @@ Example: export RUNLOOP_BASE_DOMAIN=example.com ``` -In **interactive (TUI) mode**, the home screen shows the resolved API base URL at the bottom (below the keyboard hints) so you can confirm which API you are using. - ## Usage ### TUI (Interactive Mode)