Skip to content

Commit 1f7ddb6

Browse files
2witstudiosclaude
andcommitted
refactor: Split output.rs into output/ submodule
Decompose pu-cli output.rs (1,282 LoC) into focused modules: - output/mod.rs: check_response + print_response dispatch - output/formatters.rs: status coloring, duration, progress helpers - output/agents.rs: spawn, status, kill, suspend, resume printing - output/definitions.rs: template, agent def, swarm def printing - output/execution.rs: swarm, pulse, trigger, schedule printing - output/system.rs: health, init, config, error printing - output/tests.rs: output formatting tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2 parents 572e38c + 9e030b8 commit 1f7ddb6

8 files changed

Lines changed: 1544 additions & 1294 deletions

File tree

crates/pu-cli/src/output.rs

Lines changed: 0 additions & 1294 deletions
This file was deleted.

crates/pu-cli/src/output/agents.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
use owo_colors::OwoColorize;
2+
use pu_core::protocol::AgentStatusReport;
3+
use pu_core::types::{AgentStatus, WorktreeEntry};
4+
5+
use super::formatters::{status_colored, status_colored_with_suspended, trigger_progress};
6+
7+
pub(crate) fn print_spawn_result(
8+
worktree_id: &Option<String>,
9+
agent_id: &str,
10+
status: AgentStatus,
11+
) {
12+
println!(
13+
"Spawned agent {} ({})",
14+
agent_id.bold(),
15+
status_colored(status, None)
16+
);
17+
if let Some(wt) = worktree_id {
18+
println!(" Worktree: {wt}");
19+
}
20+
}
21+
22+
pub(crate) fn print_status_report(worktrees: &[WorktreeEntry], agents: &[AgentStatusReport]) {
23+
if worktrees.is_empty() && agents.is_empty() {
24+
println!("No active agents");
25+
return;
26+
}
27+
if !agents.is_empty() {
28+
println!(
29+
"{:<14} {:<16} {}",
30+
"ID".bold(),
31+
"NAME".bold(),
32+
"STATUS".bold()
33+
);
34+
for a in agents {
35+
println!(
36+
"{:<14} {:<16} {}{}",
37+
a.id.dimmed(),
38+
a.name,
39+
status_colored_with_suspended(a.status, a.exit_code, a.suspended),
40+
trigger_progress(a)
41+
);
42+
}
43+
}
44+
for wt in worktrees {
45+
println!(
46+
"\n{} {} ({}) — {:?}",
47+
"Worktree".bold(),
48+
wt.id.dimmed(),
49+
wt.branch,
50+
wt.status,
51+
);
52+
if !wt.agents.is_empty() {
53+
println!(
54+
" {:<14} {:<16} {}",
55+
"ID".bold(),
56+
"NAME".bold(),
57+
"STATUS".bold()
58+
);
59+
for a in wt.agents.values() {
60+
println!(
61+
" {:<14} {:<16} {}",
62+
a.id.dimmed(),
63+
a.name,
64+
status_colored_with_suspended(a.status, a.exit_code, a.suspended),
65+
);
66+
}
67+
}
68+
}
69+
}
70+
71+
pub(crate) fn print_agent_status(a: &AgentStatusReport) {
72+
println!(
73+
"{} {} {}{}",
74+
a.id.dimmed(),
75+
a.name.bold(),
76+
status_colored_with_suspended(a.status, a.exit_code, a.suspended),
77+
trigger_progress(a)
78+
);
79+
if let Some(pid) = a.pid {
80+
println!(" PID: {pid}");
81+
}
82+
if let Some(code) = a.exit_code {
83+
println!(" Exit: {code}");
84+
}
85+
if let Some(idle) = a.idle_seconds {
86+
println!(" Idle: {idle}s");
87+
}
88+
if let Some(ref wt) = a.worktree_id {
89+
println!(" Worktree: {wt}");
90+
}
91+
if let (Some(state), Some(idx), Some(total)) =
92+
(a.trigger_state, a.trigger_seq_index, a.trigger_total)
93+
{
94+
println!(" Trigger: {idx}/{total} ({state:?})");
95+
}
96+
}
97+
98+
pub(crate) fn print_kill_result(killed: &[String]) {
99+
println!("Killed {} agent(s)", killed.len());
100+
}
101+
102+
pub(crate) fn print_suspend_result(suspended: &[String]) {
103+
if suspended.is_empty() {
104+
println!("No agents to bench");
105+
} else {
106+
println!("Benched {} agent(s)", suspended.len());
107+
for id in suspended {
108+
println!(" {}", id.dimmed());
109+
}
110+
}
111+
}
112+
113+
pub(crate) fn print_resume_result(agent_id: &str, status: AgentStatus) {
114+
println!(
115+
"Back in play: {} ({})",
116+
agent_id.bold(),
117+
status_colored(status, None)
118+
);
119+
}
120+
121+
pub(crate) fn print_rename_result(agent_id: &str, name: &str) {
122+
println!("Renamed agent {} to {}", agent_id.bold(), name.green());
123+
}
124+
125+
pub(crate) fn print_assign_trigger_result(agent_id: &str, trigger_name: &str, sequence_len: u32) {
126+
println!(
127+
"Assigned trigger {} to agent {} ({} steps)",
128+
trigger_name.green(),
129+
agent_id.bold(),
130+
sequence_len
131+
);
132+
}
133+
134+
pub(crate) fn print_logs_result(agent_id: &str, data: &str) {
135+
println!("{}", format!("--- Logs for {agent_id} ---").dimmed());
136+
print!("{data}");
137+
}
138+
139+
pub(crate) fn print_create_worktree_result(worktree_id: &str) {
140+
println!("Created worktree {}", worktree_id.bold());
141+
}
142+
143+
pub(crate) fn print_delete_worktree_result(
144+
worktree_id: &str,
145+
killed_agents: &[String],
146+
branch_deleted: bool,
147+
remote_deleted: bool,
148+
) {
149+
println!("Deleted worktree {}", worktree_id.bold());
150+
if !killed_agents.is_empty() {
151+
println!(" Killed {} agent(s)", killed_agents.len());
152+
}
153+
println!(
154+
" Branch deleted: {}",
155+
if branch_deleted {
156+
"yes".green().to_string()
157+
} else {
158+
"no".dimmed().to_string()
159+
}
160+
);
161+
println!(
162+
" Remote deleted: {}",
163+
if remote_deleted {
164+
"yes".green().to_string()
165+
} else {
166+
"no".dimmed().to_string()
167+
}
168+
);
169+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
use owo_colors::OwoColorize;
2+
use pu_core::protocol::{AgentDefInfo, SwarmDefInfo, SwarmRosterEntryPayload, TemplateInfo};
3+
4+
pub(crate) fn print_template_list(templates: &[TemplateInfo]) {
5+
if templates.is_empty() {
6+
println!("No templates");
7+
return;
8+
}
9+
println!(
10+
"{:<20} {:<12} {:<10} {}",
11+
"NAME".bold(),
12+
"AGENT".bold(),
13+
"SOURCE".bold(),
14+
"VARIABLES".bold()
15+
);
16+
for t in templates {
17+
println!(
18+
"{:<20} {:<12} {:<10} {}",
19+
t.name,
20+
t.agent,
21+
t.source,
22+
t.variables.join(", ")
23+
);
24+
}
25+
}
26+
27+
pub(crate) fn print_template_detail(
28+
name: &str,
29+
description: &str,
30+
agent: &str,
31+
body: &str,
32+
source: &str,
33+
variables: &[String],
34+
command: &Option<String>,
35+
) {
36+
println!("{} ({})", name.bold(), source.dimmed());
37+
if !description.is_empty() {
38+
println!(" {description}");
39+
}
40+
println!(" Agent: {agent}");
41+
if let Some(cmd) = command {
42+
println!(" Command: {cmd}");
43+
}
44+
if !variables.is_empty() {
45+
println!(" Variables: {}", variables.join(", "));
46+
}
47+
println!("---");
48+
print!("{body}");
49+
}
50+
51+
#[allow(clippy::too_many_arguments)]
52+
pub(crate) fn print_agent_def_detail(
53+
name: &str,
54+
agent_type: &str,
55+
template: &Option<String>,
56+
inline_prompt: &Option<String>,
57+
tags: &[String],
58+
scope: &str,
59+
available_in_command_dialog: bool,
60+
icon: &Option<String>,
61+
command: &Option<String>,
62+
) {
63+
println!("{} ({})", name.bold(), scope.dimmed());
64+
println!(" Type: {agent_type}");
65+
if let Some(cmd) = command {
66+
println!(" Command: {cmd}");
67+
}
68+
if let Some(tpl) = template {
69+
println!(" Template: {tpl}");
70+
}
71+
if let Some(prompt) = inline_prompt {
72+
println!(" Inline prompt:");
73+
for line in prompt.lines() {
74+
println!(" {line}");
75+
}
76+
}
77+
if !tags.is_empty() {
78+
println!(" Tags: {}", tags.join(", "));
79+
}
80+
if let Some(ic) = icon {
81+
println!(" Icon: {ic}");
82+
}
83+
println!(" Command dialog: {available_in_command_dialog}");
84+
}
85+
86+
pub(crate) fn print_agent_def_list(agent_defs: &[AgentDefInfo]) {
87+
if agent_defs.is_empty() {
88+
println!("No agent definitions");
89+
return;
90+
}
91+
println!(
92+
"{:<20} {:<12} {:<10}",
93+
"NAME".bold(),
94+
"TYPE".bold(),
95+
"SCOPE".bold()
96+
);
97+
for d in agent_defs {
98+
println!("{:<20} {:<12} {:<10}", d.name, d.agent_type, d.scope);
99+
}
100+
}
101+
102+
pub(crate) fn print_swarm_def_detail(
103+
name: &str,
104+
worktree_count: u32,
105+
worktree_template: &str,
106+
roster: &[SwarmRosterEntryPayload],
107+
include_terminal: bool,
108+
scope: &str,
109+
) {
110+
println!("{} ({})", name.bold(), scope.dimmed());
111+
println!(" Worktrees: {worktree_count}");
112+
if !worktree_template.is_empty() {
113+
println!(" Template: {worktree_template}");
114+
}
115+
println!(" Terminal: {include_terminal}");
116+
if !roster.is_empty() {
117+
println!(" Roster:");
118+
for r in roster {
119+
println!(" {} ({}) x{}", r.agent_def, r.role, r.quantity);
120+
}
121+
}
122+
}
123+
124+
pub(crate) fn print_swarm_def_list(swarm_defs: &[SwarmDefInfo]) {
125+
if swarm_defs.is_empty() {
126+
println!("No swarm definitions");
127+
return;
128+
}
129+
println!(
130+
"{:<20} {:<10} {:<10} {}",
131+
"NAME".bold(),
132+
"WORKTREES".bold(),
133+
"SCOPE".bold(),
134+
"ROSTER".bold()
135+
);
136+
for d in swarm_defs {
137+
let roster_summary: Vec<String> = d
138+
.roster
139+
.iter()
140+
.map(|r| format!("{}x{}", r.agent_def, r.quantity))
141+
.collect();
142+
println!(
143+
"{:<20} {:<10} {:<10} {}",
144+
d.name,
145+
d.worktree_count,
146+
d.scope,
147+
roster_summary.join(", ")
148+
);
149+
}
150+
}

0 commit comments

Comments
 (0)