Skip to content
Merged
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
5 changes: 5 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,11 @@ run_agent_setup() {
local railway_bin="$1"
local yes=""

if [ -z "${RAILWAY_INSTALL_REQUEST_ID-}" ]; then
RAILWAY_INSTALL_REQUEST_ID="install_$(od -vAn -N16 -tx1 < /dev/urandom | tr -d ' \n')"
export RAILWAY_INSTALL_REQUEST_ID
fi

if [ -n "${FORCE-}" ] || [ ! -t 0 ]; then
yes="-y"
fi
Expand Down
5 changes: 5 additions & 0 deletions src/commands/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub async fn command(args: Args) -> Result<()> {
if let Ok(client) = GQLClient::new_authorized(&configs) {
match get_user(&client, &configs).await {
Ok(user) => {
let _ = configs.save_user_id(&user.id);
println!("{} found", token_name.bold());
print_user(user);
return Ok(());
Expand Down Expand Up @@ -85,6 +86,10 @@ pub async fn command(args: Args) -> Result<()> {
.await?
.me;

if let Err(e) = configs.save_user_id(&me.id) {
eprintln!("{}: {e}", "Warning: failed to persist user id".yellow());
}

if let Some(name) = me.name {
println!("Logged in as {} ({})", name.bold(), me.email);
} else {
Expand Down
47 changes: 44 additions & 3 deletions src/commands/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
consts::{RAILWAY_API_TOKEN_ENV, RAILWAY_TOKEN_ENV},
controllers::user::get_user,
macros::is_stdout_terminal,
telemetry::{self, SetupAgentPhase, SetupAgentTrackEvent},
};

const DOCS_URL: &str = "https://docs.railway.com/ai";
Expand Down Expand Up @@ -102,6 +103,44 @@ fn pick_mcp_choice(remote_flag: bool, non_interactive: bool) -> Result<McpChoice
}

async fn agent_setup(args: AgentArgs) -> Result<()> {
telemetry::send_setup_agent(SetupAgentTrackEvent {
phase: SetupAgentPhase::Start,
success: None,
error_message: None,
configured_clients: None,
})
.await;

match agent_setup_inner(args).await {
Ok(configured_clients) => {
telemetry::send_setup_agent(SetupAgentTrackEvent {
phase: SetupAgentPhase::Finish,
success: Some(true),
error_message: None,
configured_clients: Some(configured_clients),
})
.await;
Ok(())
}
Err(err) => {
let message = err.to_string();
telemetry::send_setup_agent(SetupAgentTrackEvent {
phase: SetupAgentPhase::Finish,
success: Some(false),
error_message: Some(if message.len() > 256 {
message[..256].to_string()
} else {
message
}),
configured_clients: None,
})
.await;
Err(err)
}
}
}

async fn agent_setup_inner(args: AgentArgs) -> Result<Vec<String>> {
let home = dirs::home_dir().context("could not determine home directory")?;
// Treat the run as non-interactive if the user passed -y, OR if stdout
// isn't a TTY (piped, CI, agent-driven). Matches the convention used by
Expand Down Expand Up @@ -145,7 +184,7 @@ async fn agent_setup(args: AgentArgs) -> Result<()> {

if picked.is_empty() {
println!("{}", "No editors selected. Nothing to do.".yellow());
return Ok(());
return Ok(Vec::new());
}
picked.iter().map(|c| c.slug.to_string()).collect()
};
Expand All @@ -155,9 +194,11 @@ async fn agent_setup(args: AgentArgs) -> Result<()> {
"{}",
"No editors detected. Re-run interactively to pick, or rerun in a TTY.".yellow()
);
return Ok(());
return Ok(Vec::new());
}

let configured_clients = selected_slugs.clone();

// Step 1: skills install
let missing_skills: Vec<String> = selected_slugs
.iter()
Expand Down Expand Up @@ -208,7 +249,7 @@ async fn agent_setup(args: AgentArgs) -> Result<()> {
eprintln!("{}: {e}", "Warning: failed to record agent setup".yellow());
}

Ok(())
Ok(configured_clients)
}

async fn install_missing_mcp(
Expand Down
7 changes: 7 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl LinkedProject {
#[serde_with::skip_serializing_none]
#[serde(rename_all = "camelCase")]
pub struct RailwayUser {
pub id: Option<String>,
pub token: Option<String>,
pub access_token: Option<String>,
pub refresh_token: Option<String>,
Expand Down Expand Up @@ -216,6 +217,12 @@ impl Configs {
self.write()
}

pub fn save_user_id(&mut self, id: &str) -> Result<()> {
anyhow::ensure!(!id.is_empty(), "user id cannot be empty");
self.root_config.user.id = Some(id.to_string());
self.write()
}

pub fn get_environment_id() -> Environment {
match std::env::var("RAILWAY_ENV")
.map(|env| env.to_lowercase())
Expand Down
3 changes: 3 additions & 0 deletions src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pub const RAILWAY_API_TOKEN_ENV: &str = "RAILWAY_API_TOKEN";
pub const RAILWAY_PROJECT_ID_ENV: &str = "RAILWAY_PROJECT_ID";
pub const RAILWAY_ENVIRONMENT_ID_ENV: &str = "RAILWAY_ENVIRONMENT_ID";
pub const RAILWAY_SERVICE_ID_ENV: &str = "RAILWAY_SERVICE_ID";
pub const RAILWAY_CALLER_ENV: &str = "RAILWAY_CALLER";
pub const RAILWAY_AGENT_SESSION_ENV: &str = "RAILWAY_AGENT_SESSION";
pub const RAILWAY_INSTALL_REQUEST_ID_ENV: &str = "RAILWAY_INSTALL_REQUEST_ID";
pub const RAILWAY_STAGE_UPDATE_ENV: &str = "_RAILWAY_STAGE_UPDATE";

pub const TICK_STRING: &str = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ ";
1 change: 1 addition & 0 deletions src/gql/mutations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type EnvironmentConfig = controllers::config::EnvironmentConfig;
query_path = "src/gql/mutations/strings/CliEventTrack.graphql",
response_derives = "Debug, Serialize, Clone"
)]
#[allow(dead_code)]
pub struct CliEventTrack;

#[derive(GraphQLQuery)]
Expand Down
1 change: 1 addition & 0 deletions src/gql/queries/strings/UserMeta.graphql
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
query UserMeta {
me {
id
name
email
}
Expand Down
Loading
Loading