Skip to content

Commit ea80ddf

Browse files
Add list subcommand for manifest accounts (#494)
closes #391 ## What changed - added a new `list` subcommand to print account names from `manifest.json` - wired the command into clap subcommands and runtime dispatch - added unit tests for list extraction behavior and kept CLI shape validation covered by existing `verify_cli` test ## Notes - `list` now runs before auto-upgrade logic so it does not mutate manifest/account files - if the manifest requires migration, `list` exits with a clear error instead of modifying data ## Testing - `cargo test list::tests::` - `cargo test commands::tests::verify_cli` - `cargo fmt --check` <!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ --> <sup><a href="https://pullfrog.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.com/logos/frog-white-full-18px.png"><img src="https://pullfrog.com/logos/frog-green-full-18px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a>&nbsp;&nbsp;| [View workflow run](https://github.com/dyc3/steamguard-cli/actions/runs/24413687679/job/71317067164) | Triggered by [Pullfrog](https://pullfrog.com) | Using `GPT Codex` | [𝕏](https://x.com/pullfrogai)</sup> --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
1 parent 2dae757 commit ea80ddf

3 files changed

Lines changed: 95 additions & 1 deletion

File tree

src/commands.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub mod debug;
1717
pub mod decrypt;
1818
pub mod encrypt;
1919
pub mod import;
20+
pub mod list;
2021
#[cfg(feature = "qr")]
2122
pub mod qr;
2223
pub mod qr_login;
@@ -32,6 +33,7 @@ pub use debug::DebugCommand;
3233
pub use decrypt::DecryptCommand;
3334
pub use encrypt::EncryptCommand;
3435
pub use import::ImportCommand;
36+
pub use list::ListCommand;
3537
#[cfg(feature = "qr")]
3638
pub use qr::QrCommand;
3739
pub use qr_login::QrLoginCommand;
@@ -172,6 +174,7 @@ pub(crate) enum Subcommands {
172174
Completion(CompletionsCommand),
173175
Setup(SetupCommand),
174176
Import(ImportCommand),
177+
List(ListCommand),
175178
#[clap(alias = "trade")]
176179
Confirm(ConfirmCommand),
177180
Remove(RemoveCommand),

src/commands/list.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use clap::Parser;
2+
use steamguard::transport::Transport;
3+
4+
use crate::{accountmanager::ManifestEntry, AccountManager};
5+
6+
use super::{GlobalArgs, ManifestCommand};
7+
8+
#[derive(Debug, Clone, Parser)]
9+
#[clap(about = "List all accounts from the manifest.")]
10+
pub struct ListCommand;
11+
12+
impl<T> ManifestCommand<T> for ListCommand
13+
where
14+
T: Transport,
15+
{
16+
fn execute(
17+
&self,
18+
_transport: T,
19+
manager: &mut AccountManager,
20+
_args: &GlobalArgs,
21+
) -> anyhow::Result<()> {
22+
let account_names = account_names(manager.iter());
23+
if account_names.is_empty() {
24+
println!("No accounts found in manifest.");
25+
return Ok(());
26+
}
27+
28+
for account_name in account_names {
29+
println!("{}", account_name);
30+
}
31+
32+
Ok(())
33+
}
34+
}
35+
36+
fn account_names<'a>(entries: impl Iterator<Item = &'a ManifestEntry>) -> Vec<&'a str> {
37+
entries.map(|entry| entry.account_name.as_str()).collect()
38+
}
39+
40+
#[cfg(test)]
41+
mod tests {
42+
use crate::accountmanager::{manifest::Manifest, ManifestEntry};
43+
44+
use super::account_names;
45+
46+
#[test]
47+
fn account_names_preserves_manifest_order() {
48+
let entries = [
49+
ManifestEntry {
50+
filename: String::from("beta.maFile"),
51+
steam_id: 2,
52+
account_name: String::from("beta"),
53+
encryption: None,
54+
},
55+
ManifestEntry {
56+
filename: String::from("alpha.maFile"),
57+
steam_id: 1,
58+
account_name: String::from("alpha"),
59+
encryption: None,
60+
},
61+
];
62+
63+
let names = account_names(entries.iter());
64+
65+
assert_eq!(names, vec!["beta", "alpha"]);
66+
}
67+
68+
#[test]
69+
fn account_names_empty_manifest() {
70+
let manifest = Manifest::default();
71+
let names = account_names(manifest.entries.iter());
72+
73+
assert!(names.is_empty());
74+
}
75+
}

src/main.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,16 @@ fn main() {
7878

7979
fn run(args: commands::Args) -> anyhow::Result<()> {
8080
let globalargs = args.global;
81+
let subcommand = args.sub.unwrap_or(Subcommands::Code(args.code));
82+
let is_list_command = matches!(&subcommand, Subcommands::List(_));
8183

82-
let cmd: CommandType<WebApiTransport> = match args.sub.unwrap_or(Subcommands::Code(args.code)) {
84+
let cmd: CommandType<WebApiTransport> = match subcommand {
8385
Subcommands::Approve(args) => CommandType::Account(Box::new(args)),
8486
Subcommands::Debug(args) => CommandType::Const(Box::new(args)),
8587
Subcommands::Completion(args) => CommandType::Const(Box::new(args)),
8688
Subcommands::Setup(args) => CommandType::Manifest(Box::new(args)),
8789
Subcommands::Import(args) => CommandType::Manifest(Box::new(args)),
90+
Subcommands::List(args) => CommandType::Manifest(Box::new(args)),
8891
Subcommands::Encrypt(args) => CommandType::Manifest(Box::new(args)),
8992
Subcommands::Decrypt(args) => CommandType::Manifest(Box::new(args)),
9093
Subcommands::Confirm(args) => CommandType::Account(Box::new(args)),
@@ -128,6 +131,11 @@ fn run(args: commands::Args) -> anyhow::Result<()> {
128131
manager = match accountmanager::AccountManager::load(path.as_path()) {
129132
Ok(m) => m,
130133
Err(ManifestLoadError::MigrationNeeded) => {
134+
if is_list_command {
135+
bail!(
136+
"Manifest migration is needed before running `list`. Run another command to migrate the manifest first."
137+
);
138+
}
131139
info!("Migrating manifest");
132140
let manifest;
133141
let accounts;
@@ -189,6 +197,14 @@ fn run(args: commands::Args) -> anyhow::Result<()> {
189197
}
190198

191199
manager.submit_passkey(passkey);
200+
if is_list_command {
201+
let http_client = reqwest::blocking::Client::builder().build()?;
202+
let transport = WebApiTransport::new(http_client);
203+
if let CommandType::Manifest(cmd) = cmd {
204+
cmd.execute(transport, &mut manager, &globalargs)?;
205+
return Ok(());
206+
}
207+
}
192208

193209
loop {
194210
match manager.auto_upgrade() {

0 commit comments

Comments
 (0)