@@ -233,6 +233,90 @@ pub async fn execute(
233233 }
234234}
235235
236+ /// Resolve a library:// URI to the actual file path
237+ /// Format: library://domain/agent-name (e.g., library://kubernetes/pod-doctor)
238+ fn resolve_library_uri ( uri : & str ) -> Result < std:: path:: PathBuf > {
239+ // Parse library://domain/agent-name
240+ let path = uri. strip_prefix ( "library://" )
241+ . ok_or_else ( || anyhow ! ( "Invalid library URI: {}" , uri) ) ?;
242+
243+ let parts: Vec < & str > = path. split ( '/' ) . collect ( ) ;
244+ if parts. len ( ) != 2 {
245+ return Err ( anyhow ! (
246+ "Invalid library URI format: {}\n Expected: library://domain/agent-name\n Example: library://kubernetes/pod-doctor" ,
247+ uri
248+ ) ) ;
249+ }
250+
251+ let domain = parts[ 0 ] ;
252+ let agent_name = parts[ 1 ] ;
253+
254+ // Find the library directory
255+ let library_path = find_library_path ( ) ?;
256+
257+ // Try both .yaml and .yml extensions
258+ for ext in & [ "yaml" , "yml" ] {
259+ let agent_path = library_path. join ( domain) . join ( format ! ( "{}.{}" , agent_name, ext) ) ;
260+ if agent_path. exists ( ) {
261+ return Ok ( agent_path) ;
262+ }
263+ }
264+
265+ // Agent not found - provide helpful error
266+ let available = list_library_agents ( & library_path, domain) ;
267+ Err ( anyhow ! (
268+ "Agent '{}' not found in library domain '{}'\n \n Available agents in '{}':\n {}\n \n Run 'aofctl get agents --library' to see all available agents." ,
269+ agent_name, domain, domain,
270+ available. join( ", " )
271+ ) )
272+ }
273+
274+ /// Find the library directory
275+ fn find_library_path ( ) -> Result < std:: path:: PathBuf > {
276+ let candidates = [
277+ std:: path:: PathBuf :: from ( "library" ) ,
278+ std:: path:: PathBuf :: from ( "./library" ) ,
279+ std:: env:: current_exe ( )
280+ . ok ( )
281+ . and_then ( |p| p. parent ( ) . map ( |p| p. join ( "library" ) ) )
282+ . unwrap_or_default ( ) ,
283+ std:: env:: current_exe ( )
284+ . ok ( )
285+ . and_then ( |p| p. parent ( ) . and_then ( |p| p. parent ( ) ) . map ( |p| p. join ( "library" ) ) )
286+ . unwrap_or_default ( ) ,
287+ ] ;
288+
289+ for candidate in candidates {
290+ if candidate. exists ( ) && candidate. is_dir ( ) {
291+ return Ok ( candidate) ;
292+ }
293+ }
294+
295+ Err ( anyhow ! (
296+ "Library directory not found. Make sure you're running from the project root or the library is installed."
297+ ) )
298+ }
299+
300+ /// List available agents in a library domain
301+ fn list_library_agents ( library_path : & std:: path:: Path , domain : & str ) -> Vec < String > {
302+ let domain_path = library_path. join ( domain) ;
303+ let mut agents = Vec :: new ( ) ;
304+
305+ if let Ok ( entries) = std:: fs:: read_dir ( & domain_path) {
306+ for entry in entries. flatten ( ) {
307+ let path = entry. path ( ) ;
308+ if path. extension ( ) . map_or ( false , |e| e == "yaml" || e == "yml" ) {
309+ if let Some ( stem) = path. file_stem ( ) {
310+ agents. push ( stem. to_string_lossy ( ) . to_string ( ) ) ;
311+ }
312+ }
313+ }
314+ }
315+
316+ agents. sort ( ) ;
317+ agents
318+ }
319+
236320/// Run an agent with configuration
237321async fn run_agent (
238322 config : & str ,
@@ -241,15 +325,23 @@ async fn run_agent(
241325 schema : Option < OutputSchema > ,
242326 context : Option < & AofContext > ,
243327) -> Result < ( ) > {
328+ // Resolve library:// URIs to actual file paths
329+ let config_path = if config. starts_with ( "library://" ) {
330+ resolve_library_uri ( config) ?
331+ } else {
332+ std:: path:: PathBuf :: from ( config)
333+ } ;
334+ let config_str = config_path. to_string_lossy ( ) ;
335+
244336 // Check if interactive mode should be enabled (when no input provided and stdin is a TTY)
245337 let interactive = input. is_none ( ) && io:: stdin ( ) . is_terminal ( ) ;
246338
247339 if interactive {
248340 // Load agent configuration
249- let config_content = fs:: read_to_string ( config )
250- . with_context ( || format ! ( "Failed to read config file: {}" , config ) ) ?;
341+ let config_content = fs:: read_to_string ( & config_path )
342+ . with_context ( || format ! ( "Failed to read config file: {}" , config_str ) ) ?;
251343
252- let agent_config = parse_agent_config ( & config_content, config ) ?;
344+ let agent_config = parse_agent_config ( & config_content, & config_str ) ?;
253345
254346 let agent_name = agent_config. name . clone ( ) ;
255347
@@ -266,18 +358,18 @@ async fn run_agent(
266358 }
267359
268360 // Non-interactive mode: normal logging to console
269- info ! ( "Loading agent config from: {}" , config ) ;
361+ info ! ( "Loading agent config from: {}" , config_str ) ;
270362 if let Some ( ctx) = context {
271363 info ! ( "Context: {} (approval required: {})" ,
272364 ctx. name( ) ,
273365 ctx. spec. approval. as_ref( ) . map( |a| a. required) . unwrap_or( false )
274366 ) ;
275367 }
276368
277- let config_content = fs:: read_to_string ( config )
278- . with_context ( || format ! ( "Failed to read config file: {}" , config ) ) ?;
369+ let config_content = fs:: read_to_string ( & config_path )
370+ . with_context ( || format ! ( "Failed to read config file: {}" , config_str ) ) ?;
279371
280- let agent_config = parse_agent_config ( & config_content, config ) ?;
372+ let agent_config = parse_agent_config ( & config_content, & config_str ) ?;
281373
282374 let agent_name = agent_config. name . clone ( ) ;
283375 info ! ( "Agent loaded: {}" , agent_name) ;
0 commit comments