diff --git a/crates/onyx-cli/src/commands/mod.rs b/crates/onyx-cli/src/commands/mod.rs index c681294..7731bc0 100644 --- a/crates/onyx-cli/src/commands/mod.rs +++ b/crates/onyx-cli/src/commands/mod.rs @@ -6,6 +6,7 @@ pub mod group; pub mod sync; use onyx_core::{AppConfig, TaskRepository}; +use onyx_core::config::WorkspaceConfig; use anyhow::{Context, Result}; use std::path::PathBuf; @@ -23,18 +24,29 @@ pub fn save_config(config: &AppConfig) -> Result<()> { config.save_to_file(&path).context("Failed to save config") } -pub fn get_repository(workspace_name: Option) -> Result<(TaskRepository, String)> { - let config = load_config()?; - - let (name, workspace_config) = if let Some(name) = workspace_name { - let workspace_config = config.get_workspace(&name) - .ok_or_else(|| anyhow::anyhow!("Workspace '{}' not found", name))?; - (name, workspace_config.clone()) +/// Resolve a user-supplied identifier to (id, WorkspaceConfig). Accepts either +/// the workspace's display name or its UUID. Falls back to the current +/// workspace when `identifier` is `None`. +pub fn resolve_workspace(config: &AppConfig, identifier: Option<&str>) -> Result<(String, WorkspaceConfig)> { + if let Some(s) = identifier { + // Try by UUID first (exact match on map key), then fall back to name lookup. + if let Some(ws) = config.get_workspace(s) { + return Ok((s.to_string(), ws.clone())); + } + let (id, ws) = config.find_by_name(s) + .ok_or_else(|| anyhow::anyhow!("Workspace '{}' not found", s))?; + Ok((id.clone(), ws.clone())) } else { - let (name, workspace_config) = config.get_current_workspace() - .context("No workspace set. Use 'onyx init' to create one.")?; - (name.clone(), workspace_config.clone()) - }; + let (id, ws) = config.get_current_workspace() + .context("No workspace set. Run 'onyx workspace add ' to create one, or 'onyx workspace switch ' to select one.")?; + Ok((id.clone(), ws.clone())) + } +} + +pub fn get_repository(workspace_identifier: Option) -> Result<(TaskRepository, String)> { + let config = load_config()?; + let (_id, workspace_config) = resolve_workspace(&config, workspace_identifier.as_deref())?; + let name = workspace_config.name.clone(); let repo = TaskRepository::new(workspace_config.path.clone()) .context(format!("Failed to open workspace '{}'", name))?; diff --git a/crates/onyx-cli/src/commands/sync.rs b/crates/onyx-cli/src/commands/sync.rs index c34d502..605eaee 100644 --- a/crates/onyx-cli/src/commands/sync.rs +++ b/crates/onyx-cli/src/commands/sync.rs @@ -2,22 +2,8 @@ use anyhow::{Context, Result}; use colored::Colorize; use onyx_core::sync::{SyncMode, sync_workspace, get_sync_status}; use onyx_core::webdav::{WebDavClient, store_credentials, load_credentials}; -use onyx_core::config::AppConfig; use crate::output; -use super::{load_config, save_config}; - -/// Resolve a workspace name to (id, config). Falls back to current workspace if name is None. -fn resolve_workspace(config: &AppConfig, name: Option<&str>) -> Result<(String, onyx_core::config::WorkspaceConfig)> { - if let Some(name) = name { - let (id, ws) = config.find_by_name(name) - .ok_or_else(|| anyhow::anyhow!("Workspace '{}' not found", name))?; - Ok((id.clone(), ws.clone())) - } else { - let (id, ws) = config.get_current_workspace() - .context("No workspace set. Use 'onyx init' to create one.")?; - Ok((id.clone(), ws.clone())) - } -} +use super::{load_config, save_config, resolve_workspace}; /// Run sync setup: prompt for URL, username, password, test connection, store credentials. pub fn setup(workspace_name: Option) -> Result<()> { diff --git a/crates/onyx-cli/src/commands/workspace.rs b/crates/onyx-cli/src/commands/workspace.rs index ac10a94..18b02ba 100644 --- a/crates/onyx-cli/src/commands/workspace.rs +++ b/crates/onyx-cli/src/commands/workspace.rs @@ -30,11 +30,21 @@ pub fn add(name: String, path: String) -> Result<()> { // Add workspace let id = config.add_workspace(WorkspaceConfig::new(name.clone(), path_buf.clone())); + // Select the new workspace as current when none was previously set, so the + // very next command doesn't fail with "No workspace set". + let made_current = config.current_workspace.is_none(); + if made_current { + config.set_current_workspace(id.clone())?; + } + // Save config save_config(&config)?; output::success(&format!("Added workspace \"{}\" ({}) at {}", name, &id[..8], path_buf.display())); output::success("Created default list \"My Tasks\""); + if made_current { + output::success(&format!("Set \"{}\" as the current workspace", name)); + } Ok(()) } @@ -64,15 +74,20 @@ pub fn list() -> Result<()> { Ok(()) } -/// Resolve a workspace name to its ID. Errors if not found or ambiguous. -fn resolve_name(config: &onyx_core::config::AppConfig, name: &str) -> Result { +/// Resolve a user-supplied identifier to a workspace ID. Accepts either the +/// display name or the UUID. Errors if not found or ambiguous. +fn resolve_name(config: &onyx_core::config::AppConfig, identifier: &str) -> Result { + // Direct UUID hit on the map key — unambiguous. + if config.workspaces.contains_key(identifier) { + return Ok(identifier.to_string()); + } let matches: Vec<_> = config.workspaces.iter() - .filter(|(_, ws)| ws.name == name) + .filter(|(_, ws)| ws.name == identifier) .collect(); match matches.len() { - 0 => anyhow::bail!("Workspace '{}' not found", name), + 0 => anyhow::bail!("Workspace '{}' not found", identifier), 1 => Ok(matches[0].0.clone()), - n => anyhow::bail!("Ambiguous: {} workspaces named '{}'. Use the workspace ID instead.", n, name), + n => anyhow::bail!("Ambiguous: {} workspaces named '{}'. Use the workspace ID instead.", n, identifier), } }