use crate::api::editor::{update_profile, EditorProfilePatch};
use crate::cli::commands::shared::make_client;
use crate::cli::{EditorAction, GlobalArgs};
use crate::config;
use crate::error::{OlError, OL_4210_SCHEMA_MISMATCH};
use crate::ui::output::OutputConfig;
use clap::Args;
#[derive(Args, Debug)]
pub struct UpdateArgs {
#[arg(long)]
pub display_name: Option<String>,
#[arg(long)]
pub description: Option<String>,
#[arg(long)]
pub homepage_url: Option<String>,
#[arg(long)]
pub docs_url: Option<String>,
}
pub async fn run(g: &GlobalArgs, action: EditorAction) -> Result<(), OlError> {
match action {
EditorAction::Update(args) => update(g, args).await,
}
}
async fn update(g: &GlobalArgs, args: UpdateArgs) -> Result<(), OlError> {
let out = OutputConfig::resolve(g);
let mut patch = EditorProfilePatch {
display_name: args.display_name,
description: args.description,
homepage_url: args.homepage_url,
docs_url: args.docs_url,
};
let no_flags = patch.display_name.is_none()
&& patch.description.is_none()
&& patch.homepage_url.is_none()
&& patch.docs_url.is_none();
let prompted = no_flags && out.interactive;
if prompted {
crate::ui::header::print(&out, &["editor", "update"]);
print_context(&out, g);
let current = load_current_editor(g.profile.as_deref());
patch = prompt_editor_patch(&out, current)?;
}
if patch.display_name.is_none()
&& patch.description.is_none()
&& patch.homepage_url.is_none()
&& patch.docs_url.is_none()
{
if prompted {
out.print_info("");
out.print_info(
"No changes — every field matched the current value, so there's nothing \
to send. Re-run `editor update` and edit a field to push a change.",
);
} else {
out.print_info(
"Nothing to update — pass at least one of --display-name / --description / --homepage-url / --docs-url",
);
}
return Ok(());
}
if g.dry_run {
out.print_step("--dry-run: would PATCH /api/v1/editor/profile");
out.print_json(&patch);
return Ok(());
}
let client = make_client().await?;
let resp = update_profile(&client, &patch).await?;
if out.is_machine() {
out.print_json(&resp);
} else {
out.print_step("Editor profile updated");
}
Ok(())
}
fn print_context(out: &OutputConfig, g: &GlobalArgs) {
let profile = g.profile.as_deref().unwrap_or("default");
out.print_substep(&format!("Profile: {profile}"));
match config::active_manifest_path(g.profile.as_deref()) {
Ok(path) => out.print_substep(&format!("Manifest: {}", path.display())),
Err(_) => {
out.print_substep("Manifest: (none — run `openlatch-provider init` to scaffold one)")
}
}
}
#[derive(Debug, Default)]
struct CurrentEditor {
display_name: Option<String>,
description: Option<String>,
homepage_url: Option<String>,
docs_url: Option<String>,
}
fn load_current_editor(profile: Option<&str>) -> Option<CurrentEditor> {
let path = config::active_manifest_path(profile).ok()?;
let m = crate::manifest::load(&path).ok()?;
Some(CurrentEditor {
display_name: Some(m.editor.display_name.to_string()),
description: m.editor.description.as_ref().map(|d| d.to_string()),
homepage_url: m.editor.homepage_url.clone(),
docs_url: m.editor.docs_url.clone(),
})
}
fn prompt_editor_patch(
out: &OutputConfig,
current: Option<CurrentEditor>,
) -> Result<EditorProfilePatch, OlError> {
out.print_info("");
if current.is_some() {
out.print_info(
"Update your editor profile. Each field is pre-filled with its current \
value — edit it to change, or press Enter to keep it.",
);
} else {
out.print_info(
"Update your editor profile. Leave a field empty to keep its current value.",
);
}
let cur = current.unwrap_or_default();
let display_name = edit_text("Display name:", cur.display_name.as_deref())?;
let description = edit_text("Description:", cur.description.as_deref())?;
let homepage_url = edit_text("Homepage URL:", cur.homepage_url.as_deref())?;
let docs_url = edit_text("Docs URL:", cur.docs_url.as_deref())?;
Ok(EditorProfilePatch {
display_name: changed(cur.display_name.as_deref(), display_name.as_deref()),
description: changed(cur.description.as_deref(), description.as_deref()),
homepage_url: changed(cur.homepage_url.as_deref(), homepage_url.as_deref()),
docs_url: changed(cur.docs_url.as_deref(), docs_url.as_deref()),
})
}
fn edit_text(message: &str, current: Option<&str>) -> Result<Option<String>, OlError> {
use inquire::Text;
let mut prompt = Text::new(message);
if let Some(v) = current {
prompt = prompt.with_initial_value(v);
}
let answer = prompt
.prompt()
.map_err(|e| OlError::new(OL_4210_SCHEMA_MISMATCH, format!("prompt: {e}")))?;
let trimmed = answer.trim();
Ok(if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
})
}
fn changed(current: Option<&str>, new: Option<&str>) -> Option<String> {
match (current, new) {
(_, None) => None,
(Some(c), Some(n)) if c == n => None,
(_, Some(n)) => Some(n.to_string()),
}
}