1use crate::core::find_repo_root;
10use crate::errors::LitError;
11use crate::response::AgentProfileResponse;
12use chrono::Utc;
13use serde::{Deserialize, Serialize};
14use std::fs;
15use std::path::Path;
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
21pub enum AgentDomain {
22 Software,
24 Cad,
26 Eda,
28 Writer,
30 Dba,
32 Reviewer,
34 Ci,
36 Security,
38 DataScience,
40 DevOps,
42 Qa,
44 ProjectManagement,
46 General,
48 Custom(String),
50}
51
52impl std::fmt::Display for AgentDomain {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 AgentDomain::Software => write!(f, "software"),
56 AgentDomain::Cad => write!(f, "cad"),
57 AgentDomain::Eda => write!(f, "eda"),
58 AgentDomain::Writer => write!(f, "writer"),
59 AgentDomain::Dba => write!(f, "dba"),
60 AgentDomain::Reviewer => write!(f, "reviewer"),
61 AgentDomain::Ci => write!(f, "ci"),
62 AgentDomain::Security => write!(f, "security"),
63 AgentDomain::DataScience => write!(f, "data-science"),
64 AgentDomain::DevOps => write!(f, "devops"),
65 AgentDomain::Qa => write!(f, "qa"),
66 AgentDomain::ProjectManagement => write!(f, "project-management"),
67 AgentDomain::General => write!(f, "general"),
68 AgentDomain::Custom(s) => write!(f, "custom:{}", s),
69 }
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
75pub enum Capability {
76 Read,
78 Write,
80 Branch,
82 Merge,
84 Review,
86 Deploy,
88 Test,
90 SecurityScan,
92 Diff,
94 Lfs,
96 Intent,
98 Converge,
100 ContentMetadata,
102 Orchestrate,
104 StructuralAnalysis,
106 SchemaManagement,
108 Custom(String),
110}
111
112impl std::fmt::Display for Capability {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 match self {
115 Capability::Read => write!(f, "read"),
116 Capability::Write => write!(f, "write"),
117 Capability::Branch => write!(f, "branch"),
118 Capability::Merge => write!(f, "merge"),
119 Capability::Review => write!(f, "review"),
120 Capability::Deploy => write!(f, "deploy"),
121 Capability::Test => write!(f, "test"),
122 Capability::SecurityScan => write!(f, "security-scan"),
123 Capability::Diff => write!(f, "diff"),
124 Capability::Lfs => write!(f, "lfs"),
125 Capability::Intent => write!(f, "intent"),
126 Capability::Converge => write!(f, "converge"),
127 Capability::ContentMetadata => write!(f, "content-metadata"),
128 Capability::Orchestrate => write!(f, "orchestrate"),
129 Capability::StructuralAnalysis => write!(f, "structural-analysis"),
130 Capability::SchemaManagement => write!(f, "schema-management"),
131 Capability::Custom(s) => write!(f, "custom:{}", s),
132 }
133 }
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
138pub enum TrustLevel {
139 Untrusted = 0,
141 Limited = 1,
143 Standard = 2,
145 Elevated = 3,
147 Admin = 4,
149}
150
151impl std::fmt::Display for TrustLevel {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 match self {
154 TrustLevel::Untrusted => write!(f, "untrusted"),
155 TrustLevel::Limited => write!(f, "limited"),
156 TrustLevel::Standard => write!(f, "standard"),
157 TrustLevel::Elevated => write!(f, "elevated"),
158 TrustLevel::Admin => write!(f, "admin"),
159 }
160 }
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct ResourceLimits {
166 pub max_file_size: u64,
168 pub max_total_storage: u64,
170 pub max_files_per_commit: u32,
172 pub max_concurrent_leases: u32,
174 pub max_branches: u32,
176 pub max_ops_per_minute: u32,
178 pub network_access: bool,
180 pub hook_execution: bool,
182}
183
184impl Default for ResourceLimits {
185 fn default() -> Self {
186 Self {
187 max_file_size: 100 * 1024 * 1024, max_total_storage: 0, max_files_per_commit: 1000,
190 max_concurrent_leases: 10,
191 max_branches: 5,
192 max_ops_per_minute: 60,
193 network_access: true,
194 hook_execution: false,
195 }
196 }
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct AgentProfile {
202 pub profile_id: String,
204 pub name: String,
206 pub domain: AgentDomain,
208 pub capabilities: Vec<Capability>,
210 pub supported_content_types: Vec<String>,
212 pub trust_level: TrustLevel,
214 pub resource_limits: ResourceLimits,
216 pub allowed_paths: Vec<String>,
218 pub denied_paths: Vec<String>,
220 pub description: String,
222 pub version: String,
224 pub registered_at: String,
226 pub inherits_from: Option<String>,
228 #[serde(default)]
230 pub metadata: serde_json::Value,
231}
232
233fn builtin_profiles() -> Vec<AgentProfile> {
236 let now = Utc::now().to_rfc3339();
237 vec![
238 AgentProfile {
239 profile_id: "swe-default".into(),
240 name: "Software Engineer".into(),
241 domain: AgentDomain::Software,
242 capabilities: vec![
243 Capability::Read, Capability::Write, Capability::Branch,
244 Capability::Merge, Capability::Test, Capability::Diff,
245 Capability::Intent, Capability::Converge,
246 ],
247 supported_content_types: vec![],
248 trust_level: TrustLevel::Standard,
249 resource_limits: ResourceLimits::default(),
250 allowed_paths: vec!["**/*.rs".into(), "**/*.py".into(), "**/*.ts".into(), "**/*.js".into(),
251 "**/*.go".into(), "**/*.java".into(), "**/*.c".into(), "**/*.cpp".into(),
252 "**/*.h".into(), "**/*.toml".into(), "**/*.json".into(), "**/*.yaml".into(),
253 "**/*.yml".into(), "**/*.md".into(), "**/*.txt".into()],
254 denied_paths: vec![],
255 description: "General-purpose software engineering agent".into(),
256 version: "1.0".into(),
257 registered_at: now.clone(),
258 inherits_from: None,
259 metadata: serde_json::json!({}),
260 },
261 AgentProfile {
262 profile_id: "cad-designer".into(),
263 name: "CAD Designer".into(),
264 domain: AgentDomain::Cad,
265 capabilities: vec![
266 Capability::Read, Capability::Write, Capability::Branch,
267 Capability::Lfs, Capability::Diff, Capability::Intent,
268 Capability::StructuralAnalysis, Capability::ContentMetadata,
269 ],
270 supported_content_types: vec![
271 "cad/step".into(), "cad/stl".into(), "cad/iges".into(), "cad/3mf".into(),
272 ],
273 trust_level: TrustLevel::Standard,
274 resource_limits: ResourceLimits {
275 max_file_size: 500 * 1024 * 1024, max_files_per_commit: 50,
277 max_concurrent_leases: 5,
278 ..Default::default()
279 },
280 allowed_paths: vec!["**/*.step".into(), "**/*.stp".into(), "**/*.stl".into(),
281 "**/*.igs".into(), "**/*.iges".into(), "**/*.3mf".into(),
282 "**/*.obj".into(), "**/*.dxf".into()],
283 denied_paths: vec!["src/**".into()],
284 description: "Mechanical/industrial CAD modeling agent with LFS and structural diff support".into(),
285 version: "1.0".into(),
286 registered_at: now.clone(),
287 inherits_from: None,
288 metadata: serde_json::json!({"tools": ["openscad", "freecad", "solidworks"]}),
289 },
290 AgentProfile {
291 profile_id: "eda-engineer".into(),
292 name: "EDA Engineer".into(),
293 domain: AgentDomain::Eda,
294 capabilities: vec![
295 Capability::Read, Capability::Write, Capability::Branch,
296 Capability::Diff, Capability::Intent, Capability::StructuralAnalysis,
297 Capability::ContentMetadata,
298 ],
299 supported_content_types: vec![
300 "eda/kicad-pcb".into(), "eda/kicad-sch".into(), "eda/gerber".into(), "eda/spice".into(),
301 ],
302 trust_level: TrustLevel::Standard,
303 resource_limits: ResourceLimits {
304 max_files_per_commit: 100,
305 max_concurrent_leases: 8,
306 ..Default::default()
307 },
308 allowed_paths: vec!["**/*.kicad_pcb".into(), "**/*.kicad_sch".into(),
309 "**/*.gbr".into(), "**/*.ger".into(), "**/*.spice".into(),
310 "**/*.lib".into(), "**/*.bom".into()],
311 denied_paths: vec![],
312 description: "Electronic design automation agent for PCB/schematic/FPGA work".into(),
313 version: "1.0".into(),
314 registered_at: now.clone(),
315 inherits_from: None,
316 metadata: serde_json::json!({"tools": ["kicad", "ltspice", "verilator"]}),
317 },
318 AgentProfile {
319 profile_id: "tech-writer".into(),
320 name: "Technical Writer".into(),
321 domain: AgentDomain::Writer,
322 capabilities: vec![
323 Capability::Read, Capability::Write, Capability::Branch,
324 Capability::Diff, Capability::Intent, Capability::ContentMetadata,
325 ],
326 supported_content_types: vec![
327 "manuscript/latex".into(), "manuscript/docx".into(),
328 "manuscript/typst".into(), "manuscript/asciidoc".into(),
329 ],
330 trust_level: TrustLevel::Standard,
331 resource_limits: ResourceLimits {
332 max_file_size: 50 * 1024 * 1024,
333 max_files_per_commit: 200,
334 ..Default::default()
335 },
336 allowed_paths: vec!["**/*.md".into(), "**/*.tex".into(), "**/*.typ".into(),
337 "**/*.adoc".into(), "**/*.rst".into(), "**/*.docx".into(),
338 "**/*.txt".into(), "docs/**".into()],
339 denied_paths: vec!["src/**".into()],
340 description: "Technical writing agent for documentation, manuscripts, and publications".into(),
341 version: "1.0".into(),
342 registered_at: now.clone(),
343 inherits_from: None,
344 metadata: serde_json::json!({"languages": ["en", "de", "fr", "ja"]}),
345 },
346 AgentProfile {
347 profile_id: "dba".into(),
348 name: "Database Administrator".into(),
349 domain: AgentDomain::Dba,
350 capabilities: vec![
351 Capability::Read, Capability::Write, Capability::Branch,
352 Capability::Diff, Capability::Intent, Capability::SchemaManagement,
353 Capability::ContentMetadata,
354 ],
355 supported_content_types: vec![
356 "db/sqlite".into(), "db/csv".into(), "db/parquet".into(), "db/sql-migration".into(),
357 ],
358 trust_level: TrustLevel::Elevated,
359 resource_limits: ResourceLimits {
360 max_file_size: 1024 * 1024 * 1024, max_files_per_commit: 50,
362 ..Default::default()
363 },
364 allowed_paths: vec!["**/*.sql".into(), "**/*.sqlite".into(), "**/*.db".into(),
365 "**/*.csv".into(), "**/*.parquet".into(), "migrations/**".into()],
366 denied_paths: vec![],
367 description: "Database administration agent for schema, migration, and data versioning".into(),
368 version: "1.0".into(),
369 registered_at: now.clone(),
370 inherits_from: None,
371 metadata: serde_json::json!({"tools": ["sqlite", "postgres", "duckdb"]}),
372 },
373 AgentProfile {
374 profile_id: "reviewer".into(),
375 name: "Code & Design Reviewer".into(),
376 domain: AgentDomain::Reviewer,
377 capabilities: vec![
378 Capability::Read, Capability::Review, Capability::Diff,
379 Capability::Converge, Capability::ContentMetadata,
380 ],
381 supported_content_types: vec![], trust_level: TrustLevel::Elevated,
383 resource_limits: ResourceLimits {
384 max_files_per_commit: 0,
385 max_file_size: 0,
386 network_access: false,
387 ..Default::default()
388 },
389 allowed_paths: vec![],
390 denied_paths: vec![],
391 description: "Read-only review agent that can approve and converge but not modify files".into(),
392 version: "1.0".into(),
393 registered_at: now.clone(),
394 inherits_from: None,
395 metadata: serde_json::json!({}),
396 },
397 AgentProfile {
398 profile_id: "ci-bot".into(),
399 name: "CI/CD Bot".into(),
400 domain: AgentDomain::Ci,
401 capabilities: vec![
402 Capability::Read, Capability::Test, Capability::Deploy,
403 Capability::SecurityScan, Capability::Diff,
404 ],
405 supported_content_types: vec![],
406 trust_level: TrustLevel::Elevated,
407 resource_limits: ResourceLimits {
408 network_access: true,
409 hook_execution: true,
410 ..Default::default()
411 },
412 allowed_paths: vec![],
413 denied_paths: vec![],
414 description: "CI/CD automation agent for build, test, and deploy pipelines".into(),
415 version: "1.0".into(),
416 registered_at: now.clone(),
417 inherits_from: None,
418 metadata: serde_json::json!({}),
419 },
420 AgentProfile {
421 profile_id: "security-auditor".into(),
422 name: "Security Auditor".into(),
423 domain: AgentDomain::Security,
424 capabilities: vec![
425 Capability::Read, Capability::Review, Capability::SecurityScan,
426 Capability::Diff,
427 ],
428 supported_content_types: vec![],
429 trust_level: TrustLevel::Elevated,
430 resource_limits: ResourceLimits {
431 max_file_size: 0,
432 max_files_per_commit: 0,
433 network_access: false,
434 hook_execution: false,
435 ..Default::default()
436 },
437 allowed_paths: vec![],
438 denied_paths: vec![],
439 description: "Security auditing agent — read-only scanning and compliance verification".into(),
440 version: "1.0".into(),
441 registered_at: now.clone(),
442 inherits_from: None,
443 metadata: serde_json::json!({}),
444 },
445 AgentProfile {
446 profile_id: "data-scientist".into(),
447 name: "Data Scientist".into(),
448 domain: AgentDomain::DataScience,
449 capabilities: vec![
450 Capability::Read, Capability::Write, Capability::Branch,
451 Capability::Lfs, Capability::Intent, Capability::ContentMetadata,
452 Capability::SchemaManagement,
453 ],
454 supported_content_types: vec![
455 "scientific/hdf5".into(), "scientific/jupyter".into(),
456 "db/parquet".into(), "db/csv".into(),
457 ],
458 trust_level: TrustLevel::Standard,
459 resource_limits: ResourceLimits {
460 max_file_size: 2 * 1024 * 1024 * 1024, max_concurrent_leases: 3,
462 ..Default::default()
463 },
464 allowed_paths: vec!["**/*.ipynb".into(), "**/*.h5".into(), "**/*.hdf5".into(),
465 "**/*.parquet".into(), "**/*.csv".into(), "**/*.py".into(),
466 "data/**".into(), "notebooks/**".into(), "models/**".into()],
467 denied_paths: vec![],
468 description: "Data science agent for notebooks, datasets, and ML pipeline versioning".into(),
469 version: "1.0".into(),
470 registered_at: now.clone(),
471 inherits_from: None,
472 metadata: serde_json::json!({"frameworks": ["pytorch", "tensorflow", "scikit-learn"]}),
473 },
474 AgentProfile {
475 profile_id: "orchestrator".into(),
476 name: "Multi-Agent Orchestrator".into(),
477 domain: AgentDomain::General,
478 capabilities: vec![
479 Capability::Read, Capability::Orchestrate, Capability::Intent,
480 Capability::Converge, Capability::Review,
481 ],
482 supported_content_types: vec![],
483 trust_level: TrustLevel::Admin,
484 resource_limits: ResourceLimits {
485 max_files_per_commit: 0,
486 max_file_size: 0,
487 network_access: true,
488 ..Default::default()
489 },
490 allowed_paths: vec![],
491 denied_paths: vec![],
492 description: "Meta-agent that orchestrates, coordinates, and routes work across domain-specific agents".into(),
493 version: "1.0".into(),
494 registered_at: now,
495 inherits_from: None,
496 metadata: serde_json::json!({}),
497 },
498 ]
499}
500
501fn profiles_dir(repo_root: &Path) -> std::path::PathBuf {
504 repo_root.join(".lit").join("agent-profiles")
505}
506
507fn save_profile(repo_root: &Path, profile: &AgentProfile) -> Result<(), LitError> {
508 let dir = profiles_dir(repo_root);
509 fs::create_dir_all(&dir).map_err(|e| LitError::io(e.to_string()))?;
510 let json = serde_json::to_string_pretty(profile)
511 .map_err(|e| LitError::general(format!("Serialize profile: {}", e)))?;
512 fs::write(dir.join(format!("{}.json", profile.profile_id)), json)
513 .map_err(|e| LitError::io(e.to_string()))?;
514 Ok(())
515}
516
517fn load_all_profiles(repo_root: &Path) -> Result<Vec<AgentProfile>, LitError> {
518 let dir = profiles_dir(repo_root);
519 let mut profiles = builtin_profiles();
520
521 if dir.exists() {
522 for entry in fs::read_dir(&dir).map_err(|e| LitError::io(e.to_string()))? {
523 let entry = entry.map_err(|e| LitError::io(e.to_string()))?;
524 if entry
525 .path()
526 .extension()
527 .map(|e| e == "json")
528 .unwrap_or(false)
529 {
530 let json =
531 fs::read_to_string(entry.path()).map_err(|e| LitError::io(e.to_string()))?;
532 if let Ok(p) = serde_json::from_str::<AgentProfile>(&json) {
533 profiles.retain(|b| b.profile_id != p.profile_id);
534 profiles.push(p);
535 }
536 }
537 }
538 }
539 Ok(profiles)
540}
541
542pub fn execute_list(domain_filter: Option<String>) -> Result<AgentProfileResponse, LitError> {
546 let repo_root = find_repo_root().unwrap_or_else(|_| std::path::PathBuf::from("."));
547 let mut profiles = load_all_profiles(&repo_root)?;
548
549 if let Some(ref domain) = domain_filter {
550 profiles.retain(|p| p.domain.to_string() == *domain);
551 }
552
553 let count = profiles.len();
554 let summary: Vec<serde_json::Value> = profiles
555 .iter()
556 .map(|p| {
557 serde_json::json!({
558 "profile_id": p.profile_id,
559 "name": p.name,
560 "domain": p.domain.to_string(),
561 "trust_level": p.trust_level.to_string(),
562 "capabilities": p.capabilities.iter().map(|c| c.to_string()).collect::<Vec<_>>(),
563 "content_types": p.supported_content_types,
564 })
565 })
566 .collect();
567
568 Ok(AgentProfileResponse {
569 action: "list".into(),
570 profile_id: None,
571 message: format!("{} agent profile(s)", count),
572 details: Some(serde_json::to_value(&summary).unwrap_or_default()),
573 })
574}
575
576pub fn execute_show(profile_id: String) -> Result<AgentProfileResponse, LitError> {
578 let repo_root = find_repo_root().unwrap_or_else(|_| std::path::PathBuf::from("."));
579 let profiles = load_all_profiles(&repo_root)?;
580
581 let profile = profiles
582 .iter()
583 .find(|p| p.profile_id == profile_id)
584 .ok_or_else(|| LitError::general(format!("Agent profile not found: {}", profile_id)))?;
585
586 Ok(AgentProfileResponse {
587 action: "show".into(),
588 profile_id: Some(profile.profile_id.clone()),
589 message: format!(
590 "{} ({}, trust={})",
591 profile.name, profile.domain, profile.trust_level
592 ),
593 details: Some(serde_json::to_value(profile).unwrap_or_default()),
594 })
595}
596
597#[allow(clippy::too_many_arguments)]
599pub fn execute_register(
600 profile_id: String,
601 name: String,
602 domain: String,
603 capabilities: Vec<String>,
604 trust_level: Option<String>,
605 content_types: Vec<String>,
606 allowed_paths: Vec<String>,
607 denied_paths: Vec<String>,
608) -> Result<AgentProfileResponse, LitError> {
609 let repo_root = find_repo_root()?;
610
611 let domain_enum = match domain.as_str() {
612 "software" => AgentDomain::Software,
613 "cad" => AgentDomain::Cad,
614 "eda" => AgentDomain::Eda,
615 "writer" => AgentDomain::Writer,
616 "dba" => AgentDomain::Dba,
617 "reviewer" => AgentDomain::Reviewer,
618 "ci" => AgentDomain::Ci,
619 "security" => AgentDomain::Security,
620 "data-science" => AgentDomain::DataScience,
621 "devops" => AgentDomain::DevOps,
622 "qa" => AgentDomain::Qa,
623 "project-management" => AgentDomain::ProjectManagement,
624 "general" => AgentDomain::General,
625 other => AgentDomain::Custom(other.to_string()),
626 };
627
628 let caps: Vec<Capability> = capabilities
629 .iter()
630 .map(|c| match c.as_str() {
631 "read" => Capability::Read,
632 "write" => Capability::Write,
633 "branch" => Capability::Branch,
634 "merge" => Capability::Merge,
635 "review" => Capability::Review,
636 "deploy" => Capability::Deploy,
637 "test" => Capability::Test,
638 "security-scan" => Capability::SecurityScan,
639 "diff" => Capability::Diff,
640 "lfs" => Capability::Lfs,
641 "intent" => Capability::Intent,
642 "converge" => Capability::Converge,
643 "content-metadata" => Capability::ContentMetadata,
644 "orchestrate" => Capability::Orchestrate,
645 "structural-analysis" => Capability::StructuralAnalysis,
646 "schema-management" => Capability::SchemaManagement,
647 other => Capability::Custom(other.to_string()),
648 })
649 .collect();
650
651 let trust = match trust_level.as_deref() {
652 Some("untrusted") => TrustLevel::Untrusted,
653 Some("limited") => TrustLevel::Limited,
654 Some("elevated") => TrustLevel::Elevated,
655 Some("admin") => TrustLevel::Admin,
656 _ => TrustLevel::Standard,
657 };
658
659 let profile = AgentProfile {
660 profile_id: profile_id.clone(),
661 name: name.clone(),
662 domain: domain_enum,
663 capabilities: caps,
664 supported_content_types: content_types,
665 trust_level: trust,
666 resource_limits: ResourceLimits::default(),
667 allowed_paths,
668 denied_paths,
669 description: format!("Custom agent profile: {}", name),
670 version: "1.0".into(),
671 registered_at: Utc::now().to_rfc3339(),
672 inherits_from: None,
673 metadata: serde_json::json!({}),
674 };
675
676 save_profile(&repo_root, &profile)?;
677
678 Ok(AgentProfileResponse {
679 action: "register".into(),
680 profile_id: Some(profile_id),
681 message: format!("Agent profile '{}' registered", name),
682 details: Some(serde_json::to_value(&profile).unwrap_or_default()),
683 })
684}
685
686pub fn execute_capabilities(domain: Option<String>) -> Result<AgentProfileResponse, LitError> {
688 let repo_root = find_repo_root().unwrap_or_else(|_| std::path::PathBuf::from("."));
689 let profiles = load_all_profiles(&repo_root)?;
690
691 let filtered: Vec<&AgentProfile> = if let Some(ref d) = domain {
692 profiles
693 .iter()
694 .filter(|p| p.domain.to_string() == *d)
695 .collect()
696 } else {
697 profiles.iter().collect()
698 };
699
700 let mut all_caps: Vec<String> = filtered
702 .iter()
703 .flat_map(|p| p.capabilities.iter().map(|c| c.to_string()))
704 .collect();
705 all_caps.sort();
706 all_caps.dedup();
707
708 let mut all_domains: Vec<String> = profiles.iter().map(|p| p.domain.to_string()).collect();
710 all_domains.sort();
711 all_domains.dedup();
712
713 Ok(AgentProfileResponse {
714 action: "capabilities".into(),
715 profile_id: None,
716 message: format!(
717 "{} unique capability/ies across {} domain(s)",
718 all_caps.len(),
719 all_domains.len()
720 ),
721 details: Some(serde_json::json!({
722 "capabilities": all_caps,
723 "domains": all_domains,
724 "profiles_by_domain": all_domains.iter().map(|d| {
725 let count = profiles.iter().filter(|p| p.domain.to_string() == *d).count();
726 serde_json::json!({"domain": d, "profile_count": count})
727 }).collect::<Vec<_>>(),
728 })),
729 })
730}
731
732pub fn execute_remove(profile_id: String) -> Result<AgentProfileResponse, LitError> {
734 let repo_root = find_repo_root()?;
735 let dir = profiles_dir(&repo_root);
736 let path = dir.join(format!("{}.json", profile_id));
737
738 if !path.exists() {
739 let builtins = builtin_profiles();
741 if builtins.iter().any(|p| p.profile_id == profile_id) {
742 return Err(LitError::general(format!(
743 "Cannot remove built-in profile: {}",
744 profile_id
745 )));
746 }
747 return Err(LitError::general(format!(
748 "Agent profile not found: {}",
749 profile_id
750 )));
751 }
752
753 fs::remove_file(&path).map_err(|e| LitError::io(e.to_string()))?;
754
755 Ok(AgentProfileResponse {
756 action: "remove".into(),
757 profile_id: Some(profile_id.clone()),
758 message: format!("Agent profile '{}' removed", profile_id),
759 details: None,
760 })
761}