1use anyhow::Result;
2use apm_core::config::Config;
3use apm_core::wrapper::WrapperKind;
4use std::path::Path;
5use apm_core::start::AgentDiagnostic;
6
7pub fn run_list(root: &Path) -> Result<()> {
8 let config = Config::load(root)?;
9 let entries = apm_core::agents::list_wrappers(root, &config)?;
10
11 let name_w = entries.iter().map(|e| e.name.len()).max().unwrap_or(4).max(4);
12 let kind_w = "built-in".len();
13 let parser_w = "canonical".len();
14
15 println!(
16 "{:<name_w$} {:<kind_w$} {:<parser_w$} STATUS",
17 "NAME", "KIND", "PARSER"
18 );
19 for entry in &entries {
20 let kind_str = match &entry.kind {
21 WrapperKind::Builtin(_) => "built-in",
22 WrapperKind::Custom { .. } => "project",
23 };
24 let status = entry.configured_as.join(", ");
25 println!(
26 "{:<name_w$} {:<kind_w$} {:<parser_w$} {}",
27 entry.name, kind_str, entry.parser, status
28 );
29 }
30 Ok(())
31}
32
33pub fn run_new(root: &Path, name: &str, force: bool) -> Result<()> {
34 apm_core::agents::scaffold_wrapper(root, name, force)?;
35
36 let dir = root.join(".apm").join("agents").join(name);
37 println!("Created:");
38 println!(" {}", dir.join("wrapper.sh").display());
39 println!(" {}", dir.join("manifest.toml").display());
40 println!(" {}", dir.join("apm.worker.md").display());
41 println!(" {}", dir.join("apm.spec-writer.md").display());
42 println!();
43 println!("Next steps:");
44 println!(" 1. Edit {}/wrapper.sh to invoke your AI tool", dir.display());
45 println!(" 2. Run: apm agents test {name}");
46 Ok(())
47}
48
49pub fn run_test(root: &Path, name: &str) -> Result<()> {
50 let report = apm_core::agents::test_wrapper(root, name)?;
51
52 let label = if report.passed { "PASS" } else { "FAIL" };
53 println!(
54 "{} exit={} events={} non-canonical={} stderr={} wall={}ms",
55 label,
56 report.exit_code,
57 report.canonical_events,
58 report.non_canonical_lines,
59 report.stderr_lines,
60 report.wall_millis,
61 );
62
63 if !report.passed {
64 anyhow::bail!("wrapper test failed");
65 }
66 Ok(())
67}
68
69pub fn run_eject(root: &Path, name: &str) -> Result<()> {
70 apm_core::agents::eject_wrapper(root, name)?;
71
72 let script = root.join(".apm").join("agents").join(name).join("wrapper.sh");
73 println!("Ejected to: {}", script.display());
74 println!("Run: apm agents test {name}");
75 Ok(())
76}
77
78pub fn run_resolve(root: &Path, ticket_id: &str, json: bool) -> Result<()> {
79 let diag = apm_core::start::resolve_for_diagnostic(root, ticket_id)?;
80 if json {
81 print_resolve_json(&diag)
82 } else {
83 print_resolve_human(&diag)
84 }
85}
86
87fn print_resolve_human(d: &AgentDiagnostic) -> Result<()> {
88 println!("Agent assignment for {} (state: {}):", d.ticket_id, d.ticket_state);
89 if !d.dispatchable {
90 println!(" note: state {:?} has no worker dispatch; showing resolution for {:?}", d.ticket_state, d.transition_label);
91 }
92 if d.transition_label == "none" {
93 println!(" (no command:start transition defined in this workflow)");
94 return Ok(());
95 }
96
97 const W: usize = 14;
98 let model_val = d.model.as_deref().unwrap_or("—");
99 let container_val = d.container.as_deref().unwrap_or("—");
100 let manifest_status = if d.manifest_present { "[present]" } else { "[absent]" };
101
102 println!(" {:<W$} {} ({})", "agent", d.agent, d.agent_source);
103 println!(" {:<W$} {} ({})", "role", d.role, d.role_source);
104 println!(" {:<W$} {} ({})", "model", model_val, d.model_source);
105 println!(" {:<W$} {} ({})", "container", container_val, d.container_source);
106 println!(" {:<W$} {} {}", "manifest", d.manifest_path, manifest_status);
107
108 if d.env.is_empty() {
109 println!(" {:<W$} (none)", "env");
110 } else {
111 println!(" {:<W$}", "env");
112 for (k, v, src) in &d.env {
113 println!(" {k}={v} ({src})");
114 }
115 }
116
117 if d.keychain.is_empty() {
118 println!(" {:<W$} (none)", "keychain");
119 } else {
120 println!(" {:<W$}", "keychain");
121 let mut entries: Vec<_> = d.keychain.iter().collect();
122 entries.sort_by_key(|(k, _)| k.as_str());
123 for (k, v) in entries {
124 println!(" {k} → {v}");
125 }
126 }
127 Ok(())
128}
129
130fn print_resolve_json(d: &AgentDiagnostic) -> Result<()> {
131 let env_arr: Vec<serde_json::Value> = d.env.iter()
132 .map(|(k, v, src)| serde_json::json!({"key": k, "value": v, "source": src}))
133 .collect();
134 let keychain_obj: serde_json::Map<String, serde_json::Value> = d.keychain.iter()
135 .map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
136 .collect();
137 let obj = serde_json::json!({
138 "ticket_id": d.ticket_id,
139 "ticket_state": d.ticket_state,
140 "dispatchable": d.dispatchable,
141 "resolved_from_state": d.resolved_from_state,
142 "transition_label": d.transition_label,
143 "worker_profile_str": d.worker_profile_str,
144 "profile_source": d.profile_source,
145 "agent": d.agent,
146 "agent_source": d.agent_source,
147 "role": d.role,
148 "role_source": d.role_source,
149 "model": d.model,
150 "model_source": d.model_source,
151 "container": d.container,
152 "container_source": d.container_source,
153 "manifest_path": d.manifest_path,
154 "manifest_present": d.manifest_present,
155 "env": env_arr,
156 "keychain": keychain_obj,
157 });
158 println!("{}", serde_json::to_string_pretty(&obj)?);
159 Ok(())
160}