1#![allow(clippy::print_stdout)]
6
7use crate::cli::CopilotCommand;
8use crate::client::Client;
9use crate::output::print_json;
10use anyhow::Context;
11
12pub fn run(cmd: CopilotCommand) -> anyhow::Result<()> {
18 match cmd {
19 CopilotCommand::Status { json, hostname } => status(json, hostname.as_deref()),
20 CopilotCommand::Usage {
21 org,
22 json,
23 hostname,
24 } => usage(org.as_deref(), json, hostname.as_deref()),
25 }
26}
27
28fn status(json: Option<Vec<String>>, hostname: Option<&str>) -> anyhow::Result<()> {
34 let host = hostname.unwrap_or("github.com");
35 let client = Client::new(host).context("failed to create HTTP client")?;
36
37 let response = client
38 .get("/user/copilot/subscription")
39 .context("failed to fetch Copilot subscription")?;
40
41 let status_code = response.status();
42 if status_code == reqwest::StatusCode::NOT_FOUND {
43 println!("Copilot is not enabled for your account.");
44 return Ok(());
45 }
46 if !status_code.is_success() {
47 anyhow::bail!("failed to fetch Copilot status: HTTP {status_code}");
48 }
49
50 let sub: serde_json::Value = response.json().context("failed to parse response")?;
51
52 if let Some(fields) = json {
53 let fields_ref: Option<&[String]> = if fields.is_empty() {
54 None
55 } else {
56 Some(&fields)
57 };
58 print_json(&sub, fields_ref);
59 return Ok(());
60 }
61
62 let plan = sub["plan"].as_str().unwrap_or("—");
63 let seat = sub["seat_status"].as_str().unwrap_or("—");
64 let renewal = sub["renewal_date"].as_str().unwrap_or("—");
65
66 println!("Copilot Status");
67 println!(" Plan: {plan}");
68 println!(" Seat: {seat}");
69 println!(" Renewal: {renewal}");
70
71 Ok(())
72}
73
74fn usage(
80 org: Option<&str>,
81 json: Option<Vec<String>>,
82 hostname: Option<&str>,
83) -> anyhow::Result<()> {
84 let host = hostname.unwrap_or("github.com");
85 let client = Client::new(host).context("failed to create HTTP client")?;
86
87 let org_name = org.ok_or_else(|| anyhow::anyhow!("--org is required for usage statistics"))?;
88
89 let path = format!("/orgs/{org_name}/copilot/usage");
90 let response = client.get(&path).context("failed to fetch Copilot usage")?;
91
92 let status_code = response.status();
93 if status_code == reqwest::StatusCode::NOT_FOUND {
94 println!("Copilot is not enabled for organization '{org_name}'.");
95 return Ok(());
96 }
97 if !status_code.is_success() {
98 anyhow::bail!("failed to fetch Copilot usage: HTTP {status_code}");
99 }
100
101 let usage_data: serde_json::Value = response.json().context("failed to parse response")?;
102
103 if let Some(fields) = json {
104 let fields_ref: Option<&[String]> = if fields.is_empty() {
105 None
106 } else {
107 Some(&fields)
108 };
109 print_json(&usage_data, fields_ref);
110 return Ok(());
111 }
112
113 let total = usage_data["total_active_users"].as_u64().unwrap_or(0);
115 let breakdown = usage_data["breakdown"]
116 .as_array()
117 .map_or_else(Vec::new, Clone::clone);
118
119 println!("Copilot Usage for {org_name}");
120 println!(" Total active users: {total}");
121
122 if !breakdown.is_empty() {
123 println!(" Breakdown:");
124 for entry in &breakdown {
125 let lang = entry["language"].as_str().unwrap_or("—");
126 let editors = entry["editor_count"].as_u64().unwrap_or(0);
127 println!(" {lang}: {editors} editors");
128 }
129 }
130
131 Ok(())
132}