Skip to content

Commit 33238e1

Browse files
gouravjshahclaude
andcommitted
feat: Add --library flag to list built-in agents
- Add --library flag to `aofctl get agents` command - Scans library/ directory for all 30 production-ready agents - Displays domain (category), agent name, status, and model - Supports filtering by agent name: `aofctl get agents pod-doctor --library` - Supports all output formats: wide (default), json, yaml, name - Updated CLI reference documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 8f0fe6d commit 33238e1

File tree

4 files changed

+175
-13
lines changed

4 files changed

+175
-13
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Agent nodes now report input/output tokens
1515
- Flow completion summary shows total token usage
1616
- Script nodes correctly show 0 tokens (no LLM usage)
17+
- `--library` flag for `aofctl get agents` to list built-in agents
18+
- Shows all 30 production-ready agents from the library
19+
- Displays domain (category), agent name, status, and model
20+
- Supports filtering by agent name: `aofctl get agents pod-doctor --library`
21+
- Supports JSON/YAML output formats
1722

1823
### Fixed
1924
- Script node YAML field naming (`scriptConfig` camelCase)

crates/aofctl/src/cli.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ pub enum Commands {
7070
/// Show all namespaces
7171
#[arg(long)]
7272
all_namespaces: bool,
73+
74+
/// List resources from the built-in library
75+
#[arg(long)]
76+
library: bool,
7377
},
7478

7579
/// Apply configuration from file (verb-first: apply -f config.yaml)
@@ -248,8 +252,9 @@ impl Cli {
248252
name,
249253
output,
250254
all_namespaces,
255+
library,
251256
} => {
252-
commands::get::execute(&resource_type, name.as_deref(), &output, all_namespaces)
257+
commands::get::execute(&resource_type, name.as_deref(), &output, all_namespaces, library)
253258
.await
254259
}
255260
Commands::Apply { file, namespace } => {

crates/aofctl/src/commands/get.rs

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,21 @@ pub async fn execute(
77
name: Option<&str>,
88
output: &str,
99
all_namespaces: bool,
10+
library: bool,
1011
) -> Result<()> {
1112
// Parse resource type
1213
let rt = ResourceType::from_str(resource_type)
1314
.ok_or_else(|| anyhow::anyhow!("Unknown resource type: {}", resource_type))?;
1415

15-
// Build resource list (placeholder until persistent storage is implemented)
16-
let resources = get_mock_resources(&rt, name, all_namespaces);
16+
// Build resource list - either from library or mock data
17+
let resources = if library {
18+
get_library_resources(&rt, name)?
19+
} else {
20+
get_mock_resources(&rt, name, all_namespaces)
21+
};
22+
23+
// When listing library, always show domains (namespaces)
24+
let show_namespaces = all_namespaces || library;
1725

1826
// Format and display output
1927
match output {
@@ -45,16 +53,115 @@ pub async fn execute(
4553
}
4654
"wide" | _ => {
4755
// Table format (default)
48-
print_table_header(&rt, all_namespaces);
56+
print_table_header(&rt, show_namespaces);
4957
for resource in resources {
50-
print_table_row(&rt, &resource, all_namespaces);
58+
print_table_row(&rt, &resource, show_namespaces);
5159
}
5260
}
5361
}
5462

5563
Ok(())
5664
}
5765

66+
/// Get resources from the built-in library directory
67+
fn get_library_resources(
68+
rt: &ResourceType,
69+
name: Option<&str>,
70+
) -> Result<Vec<serde_json::Value>> {
71+
// Only agents are in the library currently
72+
if !matches!(rt, ResourceType::Agent) {
73+
return Ok(vec![]);
74+
}
75+
76+
// Find the library directory - check common locations
77+
let library_path = find_library_path()?;
78+
let mut resources = Vec::new();
79+
80+
// Scan all subdirectories (domains) for agent YAML files
81+
if let Ok(entries) = std::fs::read_dir(&library_path) {
82+
for entry in entries.flatten() {
83+
let path = entry.path();
84+
if path.is_dir() {
85+
let domain = path.file_name()
86+
.and_then(|n| n.to_str())
87+
.unwrap_or("unknown")
88+
.to_string();
89+
90+
// Scan for YAML files in each domain
91+
if let Ok(files) = std::fs::read_dir(&path) {
92+
for file in files.flatten() {
93+
let file_path = file.path();
94+
if file_path.extension().map_or(false, |e| e == "yaml" || e == "yml") {
95+
if let Ok(content) = std::fs::read_to_string(&file_path) {
96+
if let Ok(agent) = serde_yaml::from_str::<serde_json::Value>(&content) {
97+
let agent_name = agent
98+
.get("metadata")
99+
.and_then(|m| m.get("name"))
100+
.and_then(|n| n.as_str())
101+
.unwrap_or("unknown");
102+
103+
// Filter by name if provided
104+
if let Some(filter) = name {
105+
if agent_name != filter {
106+
continue;
107+
}
108+
}
109+
110+
// Add domain to metadata
111+
let mut enriched = agent.clone();
112+
if let Some(metadata) = enriched.get_mut("metadata") {
113+
if let Some(obj) = metadata.as_object_mut() {
114+
obj.insert("namespace".to_string(), serde_json::json!(domain));
115+
}
116+
}
117+
118+
// Add status for display
119+
if let Some(obj) = enriched.as_object_mut() {
120+
obj.insert("status".to_string(), serde_json::json!({
121+
"phase": "Available"
122+
}));
123+
}
124+
125+
resources.push(enriched);
126+
}
127+
}
128+
}
129+
}
130+
}
131+
}
132+
}
133+
}
134+
135+
Ok(resources)
136+
}
137+
138+
/// Find the library directory - check multiple possible locations
139+
fn find_library_path() -> Result<std::path::PathBuf> {
140+
// Check common locations
141+
let candidates = [
142+
std::path::PathBuf::from("library"),
143+
std::path::PathBuf::from("./library"),
144+
std::env::current_exe()
145+
.ok()
146+
.and_then(|p| p.parent().map(|p| p.join("library")))
147+
.unwrap_or_default(),
148+
std::env::current_exe()
149+
.ok()
150+
.and_then(|p| p.parent().and_then(|p| p.parent()).map(|p| p.join("library")))
151+
.unwrap_or_default(),
152+
];
153+
154+
for candidate in candidates {
155+
if candidate.exists() && candidate.is_dir() {
156+
return Ok(candidate);
157+
}
158+
}
159+
160+
Err(anyhow::anyhow!(
161+
"Library directory not found. Make sure you're running from the project root or the library is installed."
162+
))
163+
}
164+
58165
fn get_mock_resources(
59166
rt: &ResourceType,
60167
name: Option<&str>,
@@ -115,11 +222,11 @@ fn print_table_header(rt: &ResourceType, all_namespaces: bool) {
115222
match rt {
116223
ResourceType::Agent => {
117224
if all_namespaces {
118-
println!("\nNAMESPACE NAME STATUS MODEL AGE");
119-
println!("{}", "=".repeat(75));
225+
println!("\n{:<14} {:<24} {:<11} {:<24} {}", "DOMAIN", "NAME", "STATUS", "MODEL", "AGE");
226+
println!("{}", "=".repeat(80));
120227
} else {
121-
println!("\nNAME STATUS MODEL AGE");
122-
println!("{}", "=".repeat(60));
228+
println!("\n{:<24} {:<11} {:<24} {}", "NAME", "STATUS", "MODEL", "AGE");
229+
println!("{}", "=".repeat(65));
123230
}
124231
}
125232
ResourceType::Workflow => {
@@ -171,14 +278,28 @@ fn print_table_row(rt: &ResourceType, resource: &serde_json::Value, all_namespac
171278
.and_then(|p| p.as_str())
172279
.unwrap_or("Unknown");
173280

174-
let age = "5m"; // Placeholder
281+
// Get model from spec if available (for library agents)
282+
let model = resource
283+
.get("spec")
284+
.and_then(|s| s.get("model"))
285+
.and_then(|m| m.as_str())
286+
.unwrap_or("claude-sonnet-4");
287+
288+
// Truncate model name if too long
289+
let model_display = if model.len() > 24 {
290+
format!("{}...", &model[..21])
291+
} else {
292+
model.to_string()
293+
};
294+
295+
let age = "-"; // Library agents don't have age
175296

176297
match rt {
177298
ResourceType::Agent => {
178299
if all_namespaces {
179-
println!("{:<12} {:<16} {:<9} {:<18} {}", namespace, name, status, "claude-sonnet-4", age);
300+
println!("{:<14} {:<24} {:<11} {:<24} {}", namespace, name, status, model_display, age);
180301
} else {
181-
println!("{:<16} {:<9} {:<18} {}", name, status, "claude-sonnet-4", age);
302+
println!("{:<24} {:<11} {:<24} {}", name, status, model_display, age);
182303
}
183304
}
184305
ResourceType::Workflow => {

docs/reference/aofctl.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ aofctl get <resource_type> [name] [flags]
6363
**Flags:**
6464
- `-o, --output string` - Output format: json|yaml|wide|name (default: wide)
6565
- `--all-namespaces` - Show resources across all namespaces
66+
- `--library` - List resources from the built-in agent library
6667

6768
**Examples:**
6869
```bash
@@ -77,16 +78,46 @@ aofctl get agent my-agent -o yaml
7778

7879
# All namespaces
7980
aofctl get agent --all-namespaces
81+
82+
# List built-in library agents
83+
aofctl get agents --library
84+
85+
# Get specific library agent
86+
aofctl get agents pod-doctor --library
87+
88+
# Get library agents as JSON
89+
aofctl get agents --library -o json
8090
```
8191

82-
**Output:**
92+
**Output (default):**
8393
```
8494
NAME MODEL STATUS AGE
8595
k8s-helper google:gemini-2.5-flash Running 5d
8696
slack-bot anthropic:claude-3-5-sonnet-20241022 Running 2d
8797
incident-responder google:gemini-2.5-flash Running 1d
8898
```
8999

100+
**Output (--library):**
101+
```
102+
DOMAIN NAME STATUS MODEL AGE
103+
================================================================================
104+
kubernetes pod-doctor Available google:gemini-2.5-flash -
105+
kubernetes resource-optimizer Available google:gemini-2.5-flash -
106+
incident rca-agent Available google:gemini-2.5-flash -
107+
cicd pipeline-doctor Available google:gemini-2.5-flash -
108+
security security-scanner Available google:gemini-2.5-flash -
109+
observability alert-manager Available google:gemini-2.5-flash -
110+
cloud cost-optimizer Available google:gemini-2.5-flash -
111+
```
112+
113+
The library contains 30 production-ready agents across 6 domains:
114+
- **kubernetes** - Pod diagnostics, resource optimization, HPA tuning, etc.
115+
- **incident** - RCA, incident command, escalation, postmortems
116+
- **cicd** - Pipeline troubleshooting, build optimization, releases
117+
- **security** - Vulnerability scanning, secret rotation, compliance
118+
- **observability** - Alerts, logging, tracing, SLO monitoring
119+
- **cloud** - Cost optimization, drift detection, IAM auditing
120+
90121
---
91122

92123
## describe

0 commit comments

Comments
 (0)