From 495b806407b1c57be8da0cda68381d3a7c522db1 Mon Sep 17 00:00:00 2001 From: Alexander Dines Date: Wed, 7 Jan 2026 16:40:20 -0800 Subject: [PATCH 1/6] cp dines --- CLAUDE_SETUP.md | 4 +- MCP_COMMANDS.md | 2 +- MCP_README.md | 12 +- README.md | 70 +-------- package.json | 1 - src/cli.ts | 45 +----- src/commands/auth.tsx | 65 -------- src/commands/config.tsx | 300 ------------------------------------ src/commands/mcp-install.ts | 2 +- src/mcp/server.ts | 2 +- src/utils/client.ts | 2 +- src/utils/config.ts | 3 +- 12 files changed, 22 insertions(+), 486 deletions(-) delete mode 100644 src/commands/auth.tsx delete mode 100644 src/commands/config.tsx diff --git a/CLAUDE_SETUP.md b/CLAUDE_SETUP.md index d7f84103..ebda34e6 100644 --- a/CLAUDE_SETUP.md +++ b/CLAUDE_SETUP.md @@ -5,7 +5,7 @@ This guide will walk you through connecting the Runloop MCP server to Claude Des ## Prerequisites 1. Make sure you have Claude Desktop installed -2. Authenticate with Runloop: `rli auth` +2. Set your API key: `export RUNLOOP_API_KEY=your_api_key_here` 3. Make sure `rli` is installed globally and in your PATH ## Quick Setup (Automatic) @@ -108,7 +108,7 @@ If not found: ### "API key not configured" -Run `rli auth` to configure your API key before using the MCP server. +Set the `RUNLOOP_API_KEY` environment variable before using the MCP server. ### Claude doesn't show Runloop tools diff --git a/MCP_COMMANDS.md b/MCP_COMMANDS.md index f35f721e..1dc1c43f 100644 --- a/MCP_COMMANDS.md +++ b/MCP_COMMANDS.md @@ -148,7 +148,7 @@ If you get "command not found: rli": ### API key not configured -Run `rli auth` before using the MCP server. +Set the `RUNLOOP_API_KEY` environment variable before using the MCP server. ### Port already in use diff --git a/MCP_README.md b/MCP_README.md index 09e7620a..4aa5c1fe 100644 --- a/MCP_README.md +++ b/MCP_README.md @@ -131,14 +131,12 @@ Example for Claude Code or other MCP clients supporting HTTP: ## Authentication -The MCP server uses the same API key configuration as the CLI. Make sure you've authenticated first: +The MCP server uses the same API key configuration as the CLI. Set your API key: ```bash -rli auth +export RUNLOOP_API_KEY=your_api_key_here ``` -The server will automatically use your stored API credentials. - ## Example Usage with Claude Once configured, you can ask Claude to perform Runloop operations: @@ -162,7 +160,7 @@ Claude will use the MCP tools to interact with your Runloop account and provide If the stdio MCP server isn't working: -1. Make sure you've run `rli auth` to configure your API key +1. Make sure `RUNLOOP_API_KEY` environment variable is set 2. Check that the `rli` command is in your PATH 3. Restart Claude Desktop after updating the configuration 4. Check Claude's logs for any error messages @@ -171,7 +169,7 @@ If the stdio MCP server isn't working: If the HTTP MCP server isn't working: -1. Make sure you've run `rli auth` to configure your API key +1. Make sure `RUNLOOP_API_KEY` environment variable is set 2. Check that the port isn't already in use 3. Verify the server is running: `curl http://localhost:3000/sse` 4. Check your firewall settings if connecting remotely @@ -179,7 +177,7 @@ If the HTTP MCP server isn't working: ### Common Issues -- **"API key not configured"**: Run `rli auth` to set up your credentials +- **"API key not configured"**: Set `RUNLOOP_API_KEY` environment variable - **Port already in use**: Stop other services or use a different port with `--port` - **Connection refused**: Make sure the server is running and accessible diff --git a/README.md b/README.md index 2dd5fd39..24e23561 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ A beautiful, interactive CLI for managing Runloop devboxes built with Ink and Ty - 🎨 Beautiful terminal UI with colors and gradients - ⚑ Fast and responsive with pagination -- πŸ” Secure API key management - πŸ“¦ Manage devboxes, snapshots, and blueprints - πŸš€ Execute commands in devboxes - πŸ“€ Upload files to devboxes @@ -33,90 +32,31 @@ npm link ## Setup -Configure your API key using either method: - -### Option 1: Environment Variable (Recommended for CI/CD) +Configure your API key: ```bash export RUNLOOP_API_KEY=your_api_key_here ``` -### Option 2: Interactive Setup - -```bash -rli auth -``` - Get your API key from [https://runloop.ai/settings](https://runloop.ai/settings) ## Usage -### Authentication - -```bash -# Interactive setup (stores API key locally) -rli auth - -# Or use environment variable -export RUNLOOP_API_KEY=your_api_key_here -``` - -The CLI will automatically use `RUNLOOP_API_KEY` if set, otherwise it will use the stored configuration. - ### Theme Configuration -The CLI supports both light and dark terminal themes with automatic detection: +The CLI supports both light and dark terminal themes. Set the theme via environment variable: ```bash -# Interactive theme selector with live preview -rli config theme - -# Or set theme directly -rli config theme auto # Auto-detect terminal background (default) -rli config theme light # Force light mode (dark text on light background) -rli config theme dark # Force dark mode (light text on dark background) - -# Or use environment variable -export RUNLOOP_THEME=light +export RUNLOOP_THEME=light # Force light mode (dark text on light background) +export RUNLOOP_THEME=dark # Force dark mode (light text on dark background) ``` -**Interactive Mode:** - -- When you run `rli config theme` without arguments, you get an interactive selector -- Use arrow keys to navigate between auto/light/dark options -- See live preview of colors as you navigate -- Press Enter to save, Esc to cancel - **How it works:** -- **auto** (default): Uses dark mode by default (theme detection is disabled to prevent terminal flashing) +- **auto** (default): Uses dark mode by default - **light**: Optimized for light-themed terminals (uses dark text colors) - **dark**: Optimized for dark-themed terminals (uses light text colors) -**Terminal Compatibility:** - -- Works with all modern terminals (iTerm2, Terminal.app, VS Code integrated terminal, tmux) -- The CLI defaults to dark mode for the best experience -- You can manually set light or dark mode based on your terminal theme - -**Note on Auto-Detection:** - -- Auto theme detection is **disabled by default** to prevent screen flashing -- To enable it, set `RUNLOOP_ENABLE_THEME_DETECTION=1` -- If you use a light terminal, we recommend setting: `rli config theme light` -- The result is cached, so subsequent runs are instant (no flashing!) -- If you change your terminal theme, you can re-detect by running: - - ```bash - rli config theme auto - ``` -- To manually set your theme without detection: - ```bash - export RUNLOOP_THEME=dark # or light - # Or disable auto-detection entirely: - export RUNLOOP_DISABLE_THEME_DETECTION=1 - ``` - ### Devbox Commands ```bash diff --git a/package.json b/package.json index 966aa709..783e06b0 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.19.1", "@runloop/api-client": "^1.0.0", - "@runloop/rl-cli": "^0.1.2", "@types/express": "^5.0.3", "chalk": "^5.3.0", "commander": "^14.0.1", diff --git a/src/cli.ts b/src/cli.ts index d7c26ecf..dbcb918e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -26,43 +26,6 @@ program .description("Beautiful CLI for Runloop devbox management") .version(VERSION); -program - .command("auth") - .description("Configure API authentication") - .action(async () => { - const { default: auth } = await import("./commands/auth.js"); - auth(); - }); - -// Config commands -const config = program - .command("config") - .description("Configure CLI settings") - .action(async () => { - const { showThemeConfig } = await import("./commands/config.js"); - showThemeConfig(); - }); - -config - .command("theme [mode]") - .description("Get or set theme mode (auto|light|dark)") - .action(async (mode?: string) => { - const { showThemeConfig, setThemeConfig } = await import( - "./commands/config.js" - ); - - if (!mode) { - showThemeConfig(); - } else if (mode === "auto" || mode === "light" || mode === "dark") { - setThemeConfig(mode); - } else { - console.error( - `\n❌ Invalid theme mode: ${mode}\nValid options: auto, light, dark\n`, - ); - processUtils.exit(1); - } - }); - // Devbox commands const devbox = program .command("devbox") @@ -642,11 +605,9 @@ program const { initializeTheme } = await import("./utils/theme.js"); await initializeTheme(); - // Check if API key is configured (except for auth, config, and mcp commands) + // Check if API key is configured (except for mcp commands) const args = process.argv.slice(2); if ( - args[0] !== "auth" && - args[0] !== "config" && args[0] !== "mcp" && args[0] !== "mcp-server" && args[0] !== "--help" && @@ -655,7 +616,9 @@ program ) { const config = getConfig(); if (!config.apiKey) { - console.error("\n❌ API key not configured. Run: rli auth\n"); + console.error( + "\n❌ API key not configured. Set RUNLOOP_API_KEY environment variable.\n", + ); processUtils.exit(1); } } diff --git a/src/commands/auth.tsx b/src/commands/auth.tsx deleted file mode 100644 index 5e8d7364..00000000 --- a/src/commands/auth.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from "react"; -import { render, Box, Text, useInput } from "ink"; -import TextInput from "ink-text-input"; -import { setApiKey } from "../utils/config.js"; -import { Header } from "../components/Header.js"; -import { Banner } from "../components/Banner.js"; -import { SuccessMessage } from "../components/SuccessMessage.js"; -import { getSettingsUrl } from "../utils/url.js"; -import { colors } from "../utils/theme.js"; -import { processUtils } from "../utils/processUtils.js"; - -const AuthUI = () => { - const [apiKey, setApiKeyInput] = React.useState(""); - const [saved, setSaved] = React.useState(false); - - useInput((input, key) => { - if (key.return && apiKey.trim()) { - setApiKey(apiKey.trim()); - setSaved(true); - setTimeout(() => processUtils.exit(0), 1000); - } - }); - - if (saved) { - return ( - <> - -
- - - ); - } - - return ( - - -
- - Get your key: - {getSettingsUrl()} - - - API Key: - - - - - Press Enter to save - - - - ); -}; - -export default function auth() { - render(); -} diff --git a/src/commands/config.tsx b/src/commands/config.tsx deleted file mode 100644 index 9ffb1812..00000000 --- a/src/commands/config.tsx +++ /dev/null @@ -1,300 +0,0 @@ -import React from "react"; -import { render, Box, Text, useInput, useApp } from "ink"; -import figures from "figures"; -import { - setThemePreference, - getThemePreference, - clearDetectedTheme, -} from "../utils/config.js"; -import { Header } from "../components/Header.js"; -import { SuccessMessage } from "../components/SuccessMessage.js"; -import { colors, getCurrentTheme, setThemeMode } from "../utils/theme.js"; -import { processUtils } from "../utils/processUtils.js"; - -interface ThemeOption { - value: "auto" | "light" | "dark"; - label: string; - description: string; -} - -const themeOptions: ThemeOption[] = [ - { - value: "auto", - label: "Auto-detect", - description: "Automatically detect terminal background color", - }, - { - value: "dark", - label: "Dark mode", - description: "Light text on dark background", - }, - { - value: "light", - label: "Light mode", - description: "Dark text on light background", - }, -]; - -interface InteractiveThemeSelectorProps { - initialTheme: "auto" | "light" | "dark"; -} - -const InteractiveThemeSelector = ({ - initialTheme, -}: InteractiveThemeSelectorProps) => { - const { exit } = useApp(); - const [selectedIndex, setSelectedIndex] = React.useState(() => - themeOptions.findIndex((opt) => opt.value === initialTheme), - ); - const [saved, setSaved] = React.useState(false); - const [detectedTheme] = React.useState<"light" | "dark">(getCurrentTheme()); - - // Update theme preview when selection changes - React.useEffect(() => { - const newTheme = themeOptions[selectedIndex].value; - let targetTheme: "light" | "dark"; - - if (newTheme === "auto") { - // For auto mode, show the detected theme - targetTheme = detectedTheme; - } else { - // For explicit light/dark, set directly without detection - targetTheme = newTheme; - } - - // Apply theme change for preview - setThemeMode(targetTheme); - }, [selectedIndex, detectedTheme]); - - useInput((input, key) => { - if (saved) { - exit(); - return; - } - - if (key.upArrow && selectedIndex > 0) { - setSelectedIndex(selectedIndex - 1); - } else if (key.downArrow && selectedIndex < themeOptions.length - 1) { - setSelectedIndex(selectedIndex + 1); - } else if (key.return) { - // Save the selected theme to config - const selectedTheme = themeOptions[selectedIndex].value; - setThemePreference(selectedTheme); - - // If setting to 'auto', clear cached detection for re-run - if (selectedTheme === "auto") { - clearDetectedTheme(); - } - - setSaved(true); - setTimeout(() => exit(), 1500); - } else if (key.escape || input === "q") { - // Restore original theme without re-running detection - setThemePreference(initialTheme); - if (initialTheme === "auto") { - setThemeMode(detectedTheme); - } else { - setThemeMode(initialTheme); - } - exit(); - } - }); - - if (saved) { - return ( - <> -
- - - ); - } - - return ( - -
- - - - Current preference: - - {themeOptions[selectedIndex].label} - - {themeOptions[selectedIndex].value === "auto" && ( - (detected: {detectedTheme}) - )} - - - - - - Select theme mode: - - - {themeOptions.map((option, index) => { - const isSelected = index === selectedIndex; - return ( - - - {isSelected ? figures.pointer : " "}{" "} - - - {option.label} - - - {option.description} - - ); - })} - - - - - - {figures.play} Live Preview: - - - - - {figures.tick} Primary - - - - {figures.star} Secondary - - - - {figures.tick} Success - - {figures.warning} Warning - - {figures.cross} Error - - - Normal text - - Dim text - - - - - - - {figures.arrowUp} - {figures.arrowDown} Navigate β€’ [Enter] Save β€’ [Esc] Cancel - - - - ); -}; - -interface StaticConfigUIProps { - action?: "get" | "set"; - value?: "auto" | "light" | "dark"; -} - -const StaticConfigUI = ({ action, value }: StaticConfigUIProps) => { - const [saved, setSaved] = React.useState(false); - - React.useEffect(() => { - if (action === "set" && value) { - setThemePreference(value); - - // If setting to 'auto', clear the cached detection so it re-runs on next start - if (value === "auto") { - clearDetectedTheme(); - } - - setSaved(true); - setTimeout(() => processUtils.exit(0), 1500); - } else if (action === "get" || !action) { - setTimeout(() => processUtils.exit(0), 2000); - } - }, [action, value]); - - const currentPreference = getThemePreference(); - const activeTheme = getCurrentTheme(); - - if (saved) { - return ( - <> -
- - - ); - } - - return ( - -
- - - - Current preference: - - {currentPreference} - - - - Active theme: - - {activeTheme} - - - - - - - Available options: - - - - β€’ auto - Detect terminal - background automatically - - - β€’ light - Force light mode (dark - text on light background) - - - β€’ dark - Force dark mode (light - text on dark background) - - - - - - - Usage: rli config theme [auto|light|dark] - - - Environment variable: RUNLOOP_THEME - - - - ); -}; - -export function showThemeConfig() { - const currentTheme = getThemePreference(); - render(); -} - -export function setThemeConfig(theme: "auto" | "light" | "dark") { - render(); -} diff --git a/src/commands/mcp-install.ts b/src/commands/mcp-install.ts index 080e55c1..94ecdfa7 100644 --- a/src/commands/mcp-install.ts +++ b/src/commands/mcp-install.ts @@ -138,7 +138,7 @@ export async function installMcpConfig() { '2. Ask Claude: "List my devboxes" or "What Runloop tools do you have?"', ); console.log( - '\nπŸ’‘ Tip: Make sure you\'ve run "rli auth" to configure your API key first!', + "\nπŸ’‘ Tip: Make sure RUNLOOP_API_KEY environment variable is set!", ); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 4ae89b3c..60e792dd 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -62,7 +62,7 @@ function getClient(): Runloop { if (!config.apiKey) { throw new Error( - "API key not configured. Please set RUNLOOP_API_KEY environment variable or run: rli auth", + "API key not configured. Please set RUNLOOP_API_KEY environment variable.", ); } diff --git a/src/utils/client.ts b/src/utils/client.ts index 6d7a1748..c6bf3967 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -23,7 +23,7 @@ export function getClient(): Runloop { const config = getConfig(); if (!config.apiKey) { - throw new Error("API key not configured. Run: rli auth"); + throw new Error("API key not configured. Set RUNLOOP_API_KEY environment variable."); } const baseURL = getBaseUrl(); diff --git a/src/utils/config.ts b/src/utils/config.ts index e0ec1f52..1d60cd7c 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -11,6 +11,7 @@ interface Config { const config = new Conf({ projectName: "runloop-cli", + cwd: join(homedir(), ".runloop"), }); export function getConfig(): Config { @@ -43,7 +44,7 @@ export function sshUrl(): string { } export function getCacheDir(): string { - return join(homedir(), ".cache", "rl-cli"); + return join(homedir(), ".runloop", "rl-cli"); } export function shouldCheckForUpdates(): boolean { From 59c986802ca02227cba9838846aa3dcb1fdf4cd0 Mon Sep 17 00:00:00 2001 From: Alexander Dines Date: Wed, 7 Jan 2026 16:54:14 -0800 Subject: [PATCH 2/6] cp dines --- OUTPUT_FORMAT_SUMMARY.md | 127 ------- TESTING.md | 175 --------- TESTING_GUIDE.md | 322 ---------------- package-lock.json | 562 +--------------------------- src/components/ResourceListView.tsx | 6 +- src/hooks/useCursorPagination.ts | 6 +- src/utils/client.ts | 4 +- 7 files changed, 12 insertions(+), 1190 deletions(-) delete mode 100644 OUTPUT_FORMAT_SUMMARY.md delete mode 100644 TESTING.md delete mode 100644 TESTING_GUIDE.md diff --git a/OUTPUT_FORMAT_SUMMARY.md b/OUTPUT_FORMAT_SUMMARY.md deleted file mode 100644 index 59d3d4b3..00000000 --- a/OUTPUT_FORMAT_SUMMARY.md +++ /dev/null @@ -1,127 +0,0 @@ -# Output Format Support - Implementation Summary - -All CLI commands now support `-o` or `--output` flags for controlling output format. - -## Supported Formats - -- **`text`** (default): Interactive UI with tables, colors, and navigation -- **`json`**: Non-interactive JSON output for scripting and automation -- **`yaml`**: Non-interactive YAML output for configuration files and readability - -## Reusable Output Utility - -Created `/src/utils/output.ts` with the following functions: - -- `shouldUseNonInteractiveOutput(options)` - Check if non-interactive mode -- `outputList(items, options)` - Output array of items -- `outputResult(result, options)` - Output single result -- `outputError(error, options)` - Handle errors consistently -- `validateOutputFormat(format)` - Validate format option - -## Commands Updated - -### List Commands (with `-o json`) -- βœ… `rli devbox list [-s ] -o json` -- βœ… `rli blueprint list -o json` -- βœ… `rli snapshot list [-d ] -o json` - -### Create Commands (with `-o json`) -- βœ… `rli devbox create [-n ] [-t