From 1532b070963e438d33fe2804b4d4c18629b09983 Mon Sep 17 00:00:00 2001 From: Arnav Gupta Date: Fri, 22 May 2026 18:50:16 +0100 Subject: [PATCH] feat(agent): add Pi support to setup tooling Add Pi (https://pi.dev) as a supported coding tool across Railway agent setup flows. This keeps the implementation minimal and follows the existing coding tool conventions: - detect Pi via ~/.pi/agent - install Railway skills into ~/.pi/agent/skills - install Railway MCP config into ~/.pi/agent/mcp.json - include Pi in MCP support checks and CLI parsing tests - update agent setup docs and examples to mention Pi Refs: - Pi: https://pi.dev --- README.md | 2 +- src/commands/mcp/install.rs | 35 ++++++++++++++++++++++++++++++++--- src/commands/mcp/mod.rs | 2 +- src/commands/skills.rs | 29 +++++++++++++++++++++++++---- src/main.rs | 2 ++ 5 files changed, 61 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ba9a61751..bc000ac4b 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Configure Railway agent support for AI coding tools: railway setup agent -y ``` -This installs Railway skills and configures the Railway MCP server for detected tools such as Claude Code, Cursor, Codex, OpenCode, GitHub Copilot, and Factory Droid. +This installs Railway skills and configures the Railway MCP server for detected tools such as Claude Code, Cursor, Codex, OpenCode, GitHub Copilot, Factory Droid, and Pi. Use the focused commands when you only need one part of the setup: diff --git a/src/commands/mcp/install.rs b/src/commands/mcp/install.rs index e120b5d5d..4bf0d7cce 100644 --- a/src/commands/mcp/install.rs +++ b/src/commands/mcp/install.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; const REMOTE_MCP_URL: &str = "https://mcp.railway.com"; -/// Install the Railway MCP server config into AI coding tools (Claude Code, Cursor, OpenAI Codex, OpenCode, GitHub Copilot, Factory Droid). +/// Install the Railway MCP server config into AI coding tools (Claude Code, Cursor, OpenAI Codex, OpenCode, GitHub Copilot, Factory Droid, Pi). /// /// Merges a `railway` server entry into each tool's MCP config file. Without `--agent`, only configures detected tools (those with their config dir present). #[derive(Parser)] @@ -96,7 +96,7 @@ pub(crate) async fn install_mcp(agent_filter: &[String], remote: bool) -> Result fn supports_mcp(slug: &str) -> bool { matches!( slug, - "claude-code" | "cursor" | "opencode" | "codex" | "copilot" | "factory-droid" + "claude-code" | "cursor" | "opencode" | "codex" | "copilot" | "factory-droid" | "pi" ) } @@ -108,6 +108,7 @@ fn config_path(slug: &str, home: &Path) -> PathBuf { "codex" => home.join(".codex").join("config.toml"), "copilot" => home.join(".copilot").join("mcp-config.json"), "factory-droid" => home.join(".factory").join("mcp.json"), + "pi" => home.join(".pi").join("agent").join("mcp.json"), // supports_mcp gates this; unreachable in practice. _ => home.join(".unsupported"), } @@ -117,7 +118,7 @@ pub(crate) fn mcp_configured_for_slug(home: &Path, slug: &str, remote: bool) -> let path = config_path(slug, home); match slug { - "claude-code" | "cursor" | "copilot" | "factory-droid" => read_json_or_empty(&path) + "claude-code" | "cursor" | "copilot" | "factory-droid" | "pi" => read_json_or_empty(&path) .ok() .and_then(|root| root.pointer("/mcpServers/railway").cloned()) .is_some_and(|entry| json_mcp_entry_matches(&entry, remote)), @@ -232,6 +233,14 @@ fn install_for(slug: &str, path: &Path, remote: bool) -> Result<()> { }; write_json_mcp_servers(path, entry) } + "pi" => { + let entry = if remote { + json!({ "url": REMOTE_MCP_URL }) + } else { + json!({ "command": "railway", "args": ["mcp"] }) + }; + write_json_mcp_servers(path, entry) + } _ => bail!("Unsupported MCP target: {}", slug), } } @@ -587,4 +596,24 @@ mod tests { ); assert!(mcp_configured_for_slug(home.path(), "factory-droid", true)); } + + #[test] + fn writes_pi_remote_mcp() { + let home = tempfile::tempdir().unwrap(); + let path = home.path().join(".pi").join("agent").join("mcp.json"); + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + + install_for("pi", &path, true).unwrap(); + + let written = std::fs::read_to_string(&path).unwrap(); + let root: JsonValue = serde_json::from_str(&written).unwrap(); + let railway = root.pointer("/mcpServers/railway").unwrap(); + + assert_eq!( + railway.get("url").and_then(JsonValue::as_str), + Some("https://mcp.railway.com") + ); + assert!(railway.get("type").is_none()); + assert!(mcp_configured_for_slug(home.path(), "pi", true)); + } } diff --git a/src/commands/mcp/mod.rs b/src/commands/mcp/mod.rs index c9ed82366..9e6b2c07f 100644 --- a/src/commands/mcp/mod.rs +++ b/src/commands/mcp/mod.rs @@ -16,7 +16,7 @@ pub struct Args { #[derive(Parser)] enum Commands { - /// Install Railway's MCP server config into AI coding tools (Claude Code, Cursor, OpenCode, Codex) + /// Install Railway's MCP server config into AI coding tools (Claude Code, Cursor, OpenCode, Codex, Pi) Install(install::Args), } diff --git a/src/commands/skills.rs b/src/commands/skills.rs index 25800e056..b721b8d34 100644 --- a/src/commands/skills.rs +++ b/src/commands/skills.rs @@ -10,9 +10,9 @@ const TARBALL_URL: &str = "https://github.com/railwayapp/railway-skills/archive/refs/heads/main.tar.gz"; const SKILLS_PATH_PREFIX: &str = "plugins/railway/skills/"; -/// Install Railway agent skills for AI coding tools (Claude Code, Cursor, Codex, OpenCode, GitHub Copilot, Factory Droid, and all tools that support .agents/skills) +/// Install Railway agent skills for AI coding tools (Claude Code, Cursor, Codex, OpenCode, GitHub Copilot, Factory Droid, Pi, and all tools that support .agents/skills) /// -/// Always installs to ~/.agents/skills. Additionally installs to any detected tool directories (e.g. ~/.claude/skills, ~/.cursor/skills). Use --agent to target specific tools instead of auto-detection. +/// Always installs to ~/.agents/skills. Additionally installs to any detected tool directories (e.g. ~/.claude/skills, ~/.cursor/skills, ~/.pi/agent/skills). Use --agent to target specific tools instead of auto-detection. #[derive(Parser)] pub struct Args { #[clap(subcommand)] @@ -25,9 +25,9 @@ pub struct Args { #[derive(Parser)] enum Commands { - /// Install Railway agent skills for AI coding tools (Claude Code, Cursor, Codex, OpenCode, GitHub Copilot, Factory Droid, and all tools that support .agents/skills) + /// Install Railway agent skills for AI coding tools (Claude Code, Cursor, Codex, OpenCode, GitHub Copilot, Factory Droid, Pi, and all tools that support .agents/skills) /// - /// Always installs to ~/.agents/skills. Additionally installs to any detected tool directories (e.g. ~/.claude/skills, ~/.cursor/skills). Use --agent to target specific tools instead of auto-detection. + /// Always installs to ~/.agents/skills. Additionally installs to any detected tool directories (e.g. ~/.claude/skills, ~/.cursor/skills, ~/.pi/agent/skills). Use --agent to target specific tools instead of auto-detection. #[clap(visible_alias = "update", visible_alias = "add")] Install, /// Remove Railway skills from all tools @@ -101,6 +101,12 @@ pub(super) fn coding_tools(home: &Path) -> Vec { global_parent: home.join(".cursor"), skills_dir_name: "skills", }, + CodingTool { + slug: "pi", + name: "Pi", + global_parent: home.join(".pi").join("agent"), + skills_dir_name: "skills", + }, ] } @@ -433,4 +439,19 @@ mod tests { assert!(skills_configured_for_slug(home.path(), "copilot")); assert!(skills_configured_for_slug(home.path(), "factory-droid")); } + + #[test] + fn detects_pi_skills() { + let home = tempfile::tempdir().unwrap(); + std::fs::create_dir_all( + home.path() + .join(".pi") + .join("agent") + .join("skills") + .join("use-railway"), + ) + .unwrap(); + + assert!(skills_configured_for_slug(home.path(), "pi")); + } } diff --git a/src/main.rs b/src/main.rs index cea70ce0b..891cc1a51 100644 --- a/src/main.rs +++ b/src/main.rs @@ -663,6 +663,8 @@ mod cli_tests { assert_parses(&["mcp", "install", "--remote"]); assert_parses(&["mcp", "install", "--remote", "--agent", "cursor"]); assert_parses(&["mcp", "install", "--remote", "--agent", "codex"]); + assert_parses(&["mcp", "install", "--agent", "pi"]); + assert_parses(&["mcp", "install", "--remote", "--agent", "pi"]); } } }