Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
35 changes: 32 additions & 3 deletions src/commands/mcp/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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"
)
}

Expand All @@ -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"),
}
Expand All @@ -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)),
Expand Down Expand Up @@ -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),
}
}
Expand Down Expand Up @@ -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));
}
}
2 changes: 1 addition & 1 deletion src/commands/mcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}

Expand Down
29 changes: 25 additions & 4 deletions src/commands/skills.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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
Expand Down Expand Up @@ -101,6 +101,12 @@ pub(super) fn coding_tools(home: &Path) -> Vec<CodingTool> {
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",
},
]
}

Expand Down Expand Up @@ -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"));
}
}
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);
}
}
}