From e42c3c731c2bc408d625a79320af4e23c475dd2f Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Thu, 16 Jul 2026 09:26:11 -0700 Subject: [PATCH] chore: plan hltb CLI (HowLongToBeat search tool) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Milestone vibed-f05l and its seven subtasks laying out a new Rust CLI binary that queries howlongtobeat.com and prints completion times. Covers project init, request/response types, CLI arg parsing, the HTTP search call, output formatting, and a validation/smoke-test pass. No implementation yet — planning only. Co-Authored-By: Claude Sonnet 5 --- .beans/vibed-0t6e--add-api-request-types.md | 24 ++++++++++++++++++ .../vibed-54u4--add-cli-argument-parsing.md | 21 ++++++++++++++++ .beans/vibed-90il--validation-smoke-test.md | 25 +++++++++++++++++++ .beans/vibed-ca4q--initialize-rust-project.md | 16 ++++++++++++ ...05l--hltb-cli-howlongtobeat-search-tool.md | 10 ++++++++ ...ed-iq16--implement-http-search-function.md | 22 ++++++++++++++++ ...ibed-mlgv--implement-display-formatting.md | 24 ++++++++++++++++++ .beans/vibed-mn9a--add-api-response-types.md | 19 ++++++++++++++ 8 files changed, 161 insertions(+) create mode 100644 .beans/vibed-0t6e--add-api-request-types.md create mode 100644 .beans/vibed-54u4--add-cli-argument-parsing.md create mode 100644 .beans/vibed-90il--validation-smoke-test.md create mode 100644 .beans/vibed-ca4q--initialize-rust-project.md create mode 100644 .beans/vibed-f05l--hltb-cli-howlongtobeat-search-tool.md create mode 100644 .beans/vibed-iq16--implement-http-search-function.md create mode 100644 .beans/vibed-mlgv--implement-display-formatting.md create mode 100644 .beans/vibed-mn9a--add-api-response-types.md diff --git a/.beans/vibed-0t6e--add-api-request-types.md b/.beans/vibed-0t6e--add-api-request-types.md new file mode 100644 index 0000000..ad0d2d2 --- /dev/null +++ b/.beans/vibed-0t6e--add-api-request-types.md @@ -0,0 +1,24 @@ +--- +# vibed-0t6e +title: Add API request types +status: todo +type: task +created_at: 2026-04-11T22:34:39Z +updated_at: 2026-04-11T22:34:39Z +parent: vibed-f05l +blocked_by: + - vibed-ca4q +--- + +Implement the full `SearchRequest` struct tree for the HLTB POST body. + +## Todo +- [ ] `SearchRequest` with searchType, searchTerms, searchPage, size, searchOptions, useCache +- [ ] `SearchOptions` with games, users, lists, filter, sort, randomizer +- [ ] `GamesOptions` with userId, platform, sortCategory, rangeCategory, rangeTime, gameplay, rangeYear, modifier +- [ ] `RangeTime { min: Option, max: Option }` +- [ ] `Gameplay` with perspective, flow, genre, subGenre +- [ ] `RangeYear { min: &str, max: &str }` +- [ ] `UsersOptions` and `ListsOptions` with sortCategory +- [ ] All structs derive `serde::Serialize` with correct `#[serde(rename = ...)]` annotations +- [ ] `cargo check` passes diff --git a/.beans/vibed-54u4--add-cli-argument-parsing.md b/.beans/vibed-54u4--add-cli-argument-parsing.md new file mode 100644 index 0000000..1a82f02 --- /dev/null +++ b/.beans/vibed-54u4--add-cli-argument-parsing.md @@ -0,0 +1,21 @@ +--- +# vibed-54u4 +title: Add CLI argument parsing +status: todo +type: task +created_at: 2026-04-11T22:34:31Z +updated_at: 2026-04-11T22:34:31Z +parent: vibed-f05l +blocked_by: + - vibed-ca4q +--- + +Implement the `Args` struct using `clap` derive API. + +## Todo +- [ ] Add `use clap::Parser;` and derive `Args` struct +- [ ] Variadic positional `title: Vec` with `num_args = 1..` +- [ ] `--json` bool flag (output raw JSON) +- [ ] `--all` bool flag (show all results, not just top match) +- [ ] Call `Args::parse()` in `main` and join title words for display +- [ ] `cargo check` passes diff --git a/.beans/vibed-90il--validation-smoke-test.md b/.beans/vibed-90il--validation-smoke-test.md new file mode 100644 index 0000000..df0fb04 --- /dev/null +++ b/.beans/vibed-90il--validation-smoke-test.md @@ -0,0 +1,25 @@ +--- +# vibed-90il +title: Validation & smoke test +status: todo +type: task +created_at: 2026-04-11T22:35:32Z +updated_at: 2026-04-11T22:35:32Z +parent: vibed-f05l +blocked_by: + - vibed-iq16 + - vibed-mlgv +--- + +Run all validation commands and live smoke tests against the real API. + +## Todo +- [ ] `cargo fmt --check` passes +- [ ] `cargo check` passes +- [ ] `cargo clippy -- -D warnings` passes +- [ ] `cargo build --release` succeeds +- [ ] `./target/release/hltb halo` returns results +- [ ] `./target/release/hltb the witcher 3` works without quotes +- [ ] `./target/release/hltb --all zelda` shows numbered list +- [ ] `./target/release/hltb --json hollow knight` outputs valid JSON +- [ ] `./target/release/hltb xyzzy_no_game_zzzq` prints "No results found." diff --git a/.beans/vibed-ca4q--initialize-rust-project.md b/.beans/vibed-ca4q--initialize-rust-project.md new file mode 100644 index 0000000..b7dc49d --- /dev/null +++ b/.beans/vibed-ca4q--initialize-rust-project.md @@ -0,0 +1,16 @@ +--- +# vibed-ca4q +title: Initialize Rust project +status: todo +type: task +created_at: 2026-04-11T22:34:25Z +updated_at: 2026-04-11T22:34:25Z +parent: vibed-f05l +--- + +Create `Cargo.toml` and `src/main.rs` skeleton for the `hltb` binary. + +## Todo +- [ ] Write `Cargo.toml` with package metadata, [[bin]], dependencies (clap, reqwest, serde, serde_json, tokio), and release profile +- [ ] Write minimal `src/main.rs` with a stub `main()` +- [ ] Run `cargo check` and confirm it passes diff --git a/.beans/vibed-f05l--hltb-cli-howlongtobeat-search-tool.md b/.beans/vibed-f05l--hltb-cli-howlongtobeat-search-tool.md new file mode 100644 index 0000000..9830c93 --- /dev/null +++ b/.beans/vibed-f05l--hltb-cli-howlongtobeat-search-tool.md @@ -0,0 +1,10 @@ +--- +# vibed-f05l +title: hltb CLI — HowLongToBeat search tool +status: todo +type: milestone +created_at: 2026-04-11T22:33:37Z +updated_at: 2026-04-11T22:33:37Z +--- + +A Rust CLI binary that searches HowLongToBeat.com and prints completion times (Main Story / Main + Extras / Completionist) for any game. Single binary named `hltb`, uses reqwest + clap + serde + tokio. diff --git a/.beans/vibed-iq16--implement-http-search-function.md b/.beans/vibed-iq16--implement-http-search-function.md new file mode 100644 index 0000000..7baf0bd --- /dev/null +++ b/.beans/vibed-iq16--implement-http-search-function.md @@ -0,0 +1,22 @@ +--- +# vibed-iq16 +title: Implement HTTP search function +status: todo +type: task +created_at: 2026-04-11T22:35:13Z +updated_at: 2026-04-11T22:35:13Z +parent: vibed-f05l +blocked_by: + - vibed-0t6e + - vibed-mn9a +--- + +Implement `async fn search(query: &str) -> Result` using reqwest. + +## Todo +- [ ] Define `AppError { Network(reqwest::Error), Parse(serde_json::Error) }` with Display + From impls +- [ ] Implement `search()`: split query on whitespace for searchTerms, build full SearchRequest body +- [ ] POST to `https://howlongtobeat.com/api/search` with headers: User-Agent (Firefox), Referer, Content-Type +- [ ] Use `.json(&body)` for request and `.json::()` for response +- [ ] Wire into `main`: call search, match Ok/Err, print error to stderr and exit(1) on failure +- [ ] `cargo check` passes diff --git a/.beans/vibed-mlgv--implement-display-formatting.md b/.beans/vibed-mlgv--implement-display-formatting.md new file mode 100644 index 0000000..899ec86 --- /dev/null +++ b/.beans/vibed-mlgv--implement-display-formatting.md @@ -0,0 +1,24 @@ +--- +# vibed-mlgv +title: Implement display formatting +status: todo +type: task +created_at: 2026-04-11T22:35:25Z +updated_at: 2026-04-11T22:35:25Z +parent: vibed-f05l +blocked_by: + - vibed-mn9a + - vibed-54u4 +--- + +Implement output formatting functions and the main display dispatcher. + +## Todo +- [ ] `fn format_duration(seconds: u32) -> String`: return "N/A" for 0, else "Xh YYm" +- [ ] `fn print_game(game: &GameResult, prefix: Option<&str>)`: print name then 3 time lines with fixed-width labels +- [ ] `fn display(resp: SearchResponse, args: &Args)`: check for zero results, dispatch to --json / --all / default paths +- [ ] Default path: show only `data[0]` +- [ ] `--all` path: numbered list of all results +- [ ] `--json` path: `serde_json::to_string_pretty` of `data[0]` (or full vec with --all) +- [ ] Zero results: print "No results found." and return +- [ ] `cargo check` passes diff --git a/.beans/vibed-mn9a--add-api-response-types.md b/.beans/vibed-mn9a--add-api-response-types.md new file mode 100644 index 0000000..b5a7e39 --- /dev/null +++ b/.beans/vibed-mn9a--add-api-response-types.md @@ -0,0 +1,19 @@ +--- +# vibed-mn9a +title: Add API response types +status: todo +type: task +created_at: 2026-04-11T22:35:02Z +updated_at: 2026-04-11T22:35:02Z +parent: vibed-f05l +blocked_by: + - vibed-ca4q +--- + +Implement `SearchResponse` and `GameResult` deserialization types. + +## Todo +- [ ] `SearchResponse { count: u32, data: Vec }` with `Deserialize` +- [ ] `GameResult` with game_id, game_name, comp_main, comp_plus, comp_100, profile_steam, similarity — derive both `Deserialize` and `Serialize` (needed for --json output) +- [ ] Note: comp_main/plus/100 are in **seconds**; 0 means N/A +- [ ] `cargo check` passes