feat: add milestone 6 CLI polish
Add shell completion generation, improve first-run device messaging, and include HTTP endpoint details in daemon startup output.
This commit is contained in:
Generated
+10
@@ -223,6 +223,15 @@ dependencies = [
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff7a1dccbdd8b078c2bdebff47e404615151534d5043da397ec50286816f9cb"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.0"
|
||||
@@ -1686,6 +1695,7 @@ dependencies = [
|
||||
"axum",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"libc",
|
||||
"md5",
|
||||
"reqwest",
|
||||
|
||||
@@ -8,6 +8,7 @@ anyhow = "1.0"
|
||||
axum = { version = "0.8", features = ["multipart"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
clap_complete = "4.5"
|
||||
libc = "0.2"
|
||||
md5 = "0.7"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["charset", "http2", "json", "multipart", "rustls-tls"] }
|
||||
|
||||
@@ -229,6 +229,16 @@ tvctl config reset Reset to defaults
|
||||
tvctl config reload Hot-reload config into running daemon
|
||||
```
|
||||
|
||||
### completion
|
||||
|
||||
Generate shell completions to stdout.
|
||||
|
||||
```bash
|
||||
tvctl completion bash > ~/.local/share/bash-completion/completions/tvctl
|
||||
tvctl completion zsh > ~/.zfunc/_tvctl
|
||||
tvctl completion fish > ~/.config/fish/completions/tvctl.fish
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTTP API Reference
|
||||
@@ -462,6 +472,9 @@ tvctl state
|
||||
|
||||
# Scripting example
|
||||
tvctl state --json | jq '.data.active_app.name'
|
||||
|
||||
# Optional shell completions
|
||||
tvctl completion zsh > ~/.zfunc/_tvctl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
+12
-10
@@ -1,20 +1,20 @@
|
||||
# ROADMAP.md
|
||||
# tvctl — Feature Roadmap and Milestone Tracker
|
||||
# Agents: update this file as work is completed. See AGENT.md for instructions.
|
||||
# Last updated: 2026-04-14
|
||||
# Last updated: 2026-04-15
|
||||
|
||||
---
|
||||
|
||||
## Current Focus
|
||||
|
||||
**Milestone 5 — HTTP API**
|
||||
HTTP route parity with the daemon is now in progress. Finish automated API validation and close any remaining transport gaps.
|
||||
**Milestone 6 — Polish and Release Prep**
|
||||
Close the low-risk polish items that make the CLI easier to install, discover, and use day to day.
|
||||
|
||||
---
|
||||
|
||||
## In Progress
|
||||
|
||||
- Milestone 5 is in progress; the `/v1` Axum server and core route surface are implemented, but automated HTTP validation is still missing
|
||||
- Milestone 6 is in progress; shell completions and first-run CLI polish are landing first while heavier release work stays pending
|
||||
|
||||
---
|
||||
|
||||
@@ -116,14 +116,14 @@ _Goal: Full /v1/ API running on 127.0.0.1:7272._
|
||||
## Milestone 6 — Polish and Release Prep
|
||||
_Goal: Ready for real use._
|
||||
|
||||
- [ ] Shell completions (bash, zsh, fish) via clap
|
||||
- [x] 2026-04-15 — Shell completions (bash, zsh, fish) via clap
|
||||
- [ ] `tvctl daemon install` generates correct systemd unit file
|
||||
- [ ] First-run experience: helpful output when no devices discovered yet
|
||||
- [ ] Daemon startup message with socket path and HTTP port
|
||||
- [x] 2026-04-15 — First-run experience: helpful output when no devices discovered yet
|
||||
- [x] 2026-04-15 — Daemon startup message with socket path and HTTP port
|
||||
- [ ] Log output via `tracing` (respects `log_level` config)
|
||||
- [ ] README accuracy pass (verify all examples work)
|
||||
- [ ] `cargo clippy` clean
|
||||
- [ ] `cargo test` passing
|
||||
- [x] 2026-04-15 — README accuracy pass (verify all examples work)
|
||||
- [x] 2026-04-15 — `cargo clippy` clean
|
||||
- [x] 2026-04-15 — `cargo test` passing
|
||||
- [ ] Cross-compile test (x86_64 + aarch64)
|
||||
- [ ] GitHub Actions CI (build + clippy + test)
|
||||
- [ ] First binary release
|
||||
@@ -164,6 +164,8 @@ out of scope until Milestone 6 is complete and stable.
|
||||
- [x] 2026-04-14 — Add daemon-backed app control, remote input, state queries, and dev workflows with live Roku CLI validation
|
||||
- [x] 2026-04-14 — Add config list/get/set/reset/reload plus systemd user-service install/uninstall commands
|
||||
- [x] 2026-04-14 — Finish Milestone 4 with help, JSON-mode, config socket-reload, and secret-redaction polish
|
||||
- [x] 2026-04-15 — Add HTTP API route parity and integration coverage against an isolated running daemon
|
||||
- [x] 2026-04-15 — Add shell completions and first-run CLI polish for Milestone 6
|
||||
|
||||
---
|
||||
|
||||
|
||||
+44
-49
@@ -18,7 +18,8 @@ use tokio::{
|
||||
use crate::{
|
||||
adapters::{Device, TvKey, parse_normalized_tv_key},
|
||||
daemon::{
|
||||
SharedDaemon, config::TvctlConfig,
|
||||
SharedDaemon,
|
||||
config::TvctlConfig,
|
||||
ipc::{
|
||||
AppListResult, ConfigReloadResult, DaemonRequest, DaemonResponse, DaemonStatus,
|
||||
DevLogsResult, DiscoveryResult, StateResult,
|
||||
@@ -115,13 +116,7 @@ async fn delete_device(Path(id): Path<String>, State(daemon): State<SharedDaemon
|
||||
}
|
||||
|
||||
async fn get_state(Path(id): Path<String>, State(daemon): State<SharedDaemon>) -> Response {
|
||||
execute_json::<StateResult>(
|
||||
daemon,
|
||||
DaemonRequest::GetState {
|
||||
device: Some(id),
|
||||
},
|
||||
)
|
||||
.await
|
||||
execute_json::<StateResult>(daemon, DaemonRequest::GetState { device: Some(id) }).await
|
||||
}
|
||||
|
||||
async fn list_apps(Path(id): Path<String>, State(daemon): State<SharedDaemon>) -> Response {
|
||||
@@ -151,13 +146,7 @@ async fn launch_app(
|
||||
}
|
||||
|
||||
async fn stop_app(Path(id): Path<String>, State(daemon): State<SharedDaemon>) -> Response {
|
||||
execute_json_value(
|
||||
daemon,
|
||||
DaemonRequest::StopApp {
|
||||
device: Some(id),
|
||||
},
|
||||
)
|
||||
.await
|
||||
execute_json_value(daemon, DaemonRequest::StopApp { device: Some(id) }).await
|
||||
}
|
||||
|
||||
async fn refresh_apps(
|
||||
@@ -182,7 +171,7 @@ async fn send_key(
|
||||
) -> Response {
|
||||
let key = match parse_key(&body.key) {
|
||||
Ok(key) => key,
|
||||
Err(response) => return response,
|
||||
Err(response) => return *response,
|
||||
};
|
||||
execute_json_value(
|
||||
daemon,
|
||||
@@ -203,7 +192,7 @@ async fn send_sequence(
|
||||
for key in body.keys {
|
||||
match parse_key(&key) {
|
||||
Ok(key) => parsed.push(key),
|
||||
Err(response) => return response,
|
||||
Err(response) => return *response,
|
||||
}
|
||||
}
|
||||
execute_json_value(
|
||||
@@ -277,23 +266,11 @@ async fn dev_install(
|
||||
}
|
||||
|
||||
async fn dev_reload(Path(id): Path<String>, State(daemon): State<SharedDaemon>) -> Response {
|
||||
execute_json_value(
|
||||
daemon,
|
||||
DaemonRequest::DevReload {
|
||||
device: Some(id),
|
||||
},
|
||||
)
|
||||
.await
|
||||
execute_json_value(daemon, DaemonRequest::DevReload { device: Some(id) }).await
|
||||
}
|
||||
|
||||
async fn dev_logs(Path(id): Path<String>, State(daemon): State<SharedDaemon>) -> Response {
|
||||
execute_json::<DevLogsResult>(
|
||||
daemon,
|
||||
DaemonRequest::DevLogs {
|
||||
device: Some(id),
|
||||
},
|
||||
)
|
||||
.await
|
||||
execute_json::<DevLogsResult>(daemon, DaemonRequest::DevLogs { device: Some(id) }).await
|
||||
}
|
||||
|
||||
async fn daemon_status(State(daemon): State<SharedDaemon>) -> Response {
|
||||
@@ -342,7 +319,9 @@ async fn patch_config(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"invalid_config_value",
|
||||
format!("Config value for '{key}' must be a string, number, boolean, or null."),
|
||||
Some("Use flat key/value JSON such as {\"daemon.http_port\": 7272}.".to_string()),
|
||||
Some(
|
||||
"Use flat key/value JSON such as {\"daemon.http_port\": 7272}.".to_string(),
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -365,8 +344,7 @@ async fn patch_config(
|
||||
);
|
||||
}
|
||||
|
||||
let response = execute_json_value(daemon, DaemonRequest::ReloadConfig).await;
|
||||
response
|
||||
execute_json_value(daemon, DaemonRequest::ReloadConfig).await
|
||||
}
|
||||
|
||||
async fn reload_config(State(daemon): State<SharedDaemon>) -> Response {
|
||||
@@ -395,7 +373,12 @@ where
|
||||
T: Serialize + DeserializeOwned,
|
||||
{
|
||||
if let Some(error) = response.error {
|
||||
return api_error(status_for_error(&error.code), error.code, error.message, error.hint);
|
||||
return api_error(
|
||||
status_for_error(&error.code),
|
||||
error.code,
|
||||
error.message,
|
||||
error.hint,
|
||||
);
|
||||
}
|
||||
|
||||
let data = response.data.unwrap_or(Value::Null);
|
||||
@@ -437,14 +420,17 @@ fn api_error(
|
||||
.into_response()
|
||||
}
|
||||
|
||||
fn parse_key(input: &str) -> Result<TvKey, Response> {
|
||||
fn parse_key(input: &str) -> Result<TvKey, Box<Response>> {
|
||||
parse_normalized_tv_key(input).map_err(|_| {
|
||||
api_error(
|
||||
Box::new(api_error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"invalid_key",
|
||||
format!("Unknown key '{input}'."),
|
||||
Some("Use a normalized key like `home`, `down`, `volume-up`, or `literal:text`.".to_string()),
|
||||
)
|
||||
Some(
|
||||
"Use a normalized key like `home`, `down`, `volume-up`, or `literal:text`."
|
||||
.to_string(),
|
||||
),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -486,8 +472,14 @@ async fn send_daemon_request(
|
||||
api_error(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"daemon_socket_unreachable",
|
||||
format!("Unable to reach tvctld at {}: {error}", socket_path.display()),
|
||||
Some("Check whether the daemon socket is writable and the daemon is running.".to_string()),
|
||||
format!(
|
||||
"Unable to reach tvctld at {}: {error}",
|
||||
socket_path.display()
|
||||
),
|
||||
Some(
|
||||
"Check whether the daemon socket is writable and the daemon is running."
|
||||
.to_string(),
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -517,14 +509,17 @@ async fn send_daemon_request(
|
||||
})?;
|
||||
|
||||
let mut response_bytes = Vec::new();
|
||||
stream.read_to_end(&mut response_bytes).await.map_err(|error| {
|
||||
api_error(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"daemon_read_failed",
|
||||
format!("Failed to read the daemon response: {error}"),
|
||||
Some("Retry the request after restarting the daemon.".to_string()),
|
||||
)
|
||||
})?;
|
||||
stream
|
||||
.read_to_end(&mut response_bytes)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
api_error(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"daemon_read_failed",
|
||||
format!("Failed to read the daemon response: {error}"),
|
||||
Some("Retry the request after restarting the daemon.".to_string()),
|
||||
)
|
||||
})?;
|
||||
|
||||
serde_json::from_slice::<DaemonResponse>(&response_bytes).map_err(|error| {
|
||||
api_error(
|
||||
|
||||
+68
-15
@@ -1,6 +1,7 @@
|
||||
use std::{collections::BTreeMap, net::IpAddr, path::PathBuf, process::Stdio, time::Duration};
|
||||
|
||||
use clap::{Args, CommandFactory, Parser, Subcommand};
|
||||
use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum};
|
||||
use clap_complete::{Generator, Shell, generate};
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
use tokio::{
|
||||
@@ -97,11 +98,23 @@ pub enum Command {
|
||||
#[command(subcommand)]
|
||||
command: ConfigCommand,
|
||||
},
|
||||
/// Generate shell completion scripts.
|
||||
Completion {
|
||||
#[arg(value_enum)]
|
||||
shell: CompletionShell,
|
||||
},
|
||||
/// Internal daemon entry point used by `tvctl daemon start`.
|
||||
#[command(hide = true, name = "__daemon_serve")]
|
||||
InternalDaemonServe,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum CompletionShell {
|
||||
Bash,
|
||||
Zsh,
|
||||
Fish,
|
||||
}
|
||||
|
||||
/// Manage the tvctld lifecycle.
|
||||
#[derive(Debug, Clone, Subcommand)]
|
||||
pub enum DaemonCommand {
|
||||
@@ -320,9 +333,29 @@ pub async fn run() -> Result<(), CliError> {
|
||||
Command::State => handle_state_command(&cli).await,
|
||||
Command::Dev { command } => handle_dev_command(&cli, command).await,
|
||||
Command::Config { command } => handle_config_command(&cli, command).await,
|
||||
Command::Completion { shell } => handle_completion_command(shell),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_completion_command(shell: CompletionShell) -> Result<(), CliError> {
|
||||
let mut command = Cli::command();
|
||||
match shell {
|
||||
CompletionShell::Bash => print_completions(Shell::Bash, &mut command),
|
||||
CompletionShell::Zsh => print_completions(Shell::Zsh, &mut command),
|
||||
CompletionShell::Fish => print_completions(Shell::Fish, &mut command),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_completions<G: Generator>(generator: G, command: &mut clap::Command) {
|
||||
generate(
|
||||
generator,
|
||||
command,
|
||||
command.get_name().to_string(),
|
||||
&mut std::io::stdout(),
|
||||
);
|
||||
}
|
||||
|
||||
async fn handle_daemon_command(cli: &Cli, command: DaemonCommand) -> Result<(), CliError> {
|
||||
match command {
|
||||
DaemonCommand::Start => daemon_start(cli).await,
|
||||
@@ -626,10 +659,7 @@ async fn daemon_start(cli: &Cli) -> Result<(), CliError> {
|
||||
if let Some(status) = daemon_status_payload().await {
|
||||
if service_owns_daemon(&service, &status) {
|
||||
return render(cli, &status, || {
|
||||
format!(
|
||||
"tvctld user service is already running on {}",
|
||||
status.socket
|
||||
)
|
||||
render_daemon_started("tvctld user service is already running.", &status)
|
||||
});
|
||||
}
|
||||
stop_ad_hoc_daemon(&status).await?;
|
||||
@@ -638,13 +668,13 @@ async fn daemon_start(cli: &Cli) -> Result<(), CliError> {
|
||||
run_systemctl(&["--user", "start", TVCTLD_SYSTEMD_UNIT]).await?;
|
||||
let status = wait_for_daemon_ready().await?;
|
||||
return render(cli, &status, || {
|
||||
format!("Started tvctld user service on {}", status.socket)
|
||||
render_daemon_started("Started tvctld user service.", &status)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(status) = daemon_status_payload().await {
|
||||
return render(cli, &status, || {
|
||||
format!("tvctld is already running on {}", status.socket)
|
||||
render_daemon_started("tvctld is already running.", &status)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -670,7 +700,7 @@ async fn daemon_start(cli: &Cli) -> Result<(), CliError> {
|
||||
|
||||
let status = wait_for_daemon_ready().await?;
|
||||
render(cli, &status, || {
|
||||
format!("tvctld started on {}", status.socket)
|
||||
render_daemon_started("tvctld started.", &status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -725,15 +755,15 @@ async fn daemon_stop(cli: &Cli) -> Result<(), CliError> {
|
||||
async fn daemon_restart(cli: &Cli) -> Result<(), CliError> {
|
||||
let service = systemd_service_status().await?;
|
||||
if service.installed {
|
||||
if let Some(status) = daemon_status_payload().await {
|
||||
if !service_owns_daemon(&service, &status) {
|
||||
stop_ad_hoc_daemon(&status).await?;
|
||||
}
|
||||
if let Some(status) = daemon_status_payload().await
|
||||
&& !service_owns_daemon(&service, &status)
|
||||
{
|
||||
stop_ad_hoc_daemon(&status).await?;
|
||||
}
|
||||
run_systemctl(&["--user", "restart", TVCTLD_SYSTEMD_UNIT]).await?;
|
||||
let status = wait_for_daemon_ready().await?;
|
||||
return render(cli, &status, || {
|
||||
format!("Restarted tvctld user service on {}", status.socket)
|
||||
render_daemon_started("Restarted tvctld user service.", &status)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1166,7 +1196,11 @@ fn render_action_response(cli: &Cli, response: DaemonResponse) -> Result<(), Cli
|
||||
|
||||
fn render_device_list(devices: &[Device]) -> String {
|
||||
if devices.is_empty() {
|
||||
return "No devices are registered yet.".to_string();
|
||||
return [
|
||||
"No devices are registered yet.".to_string(),
|
||||
"Run `tvctl device discover` to scan the network or `tvctl device add --platform roku --address <ip>` to add one manually.".to_string(),
|
||||
]
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
devices
|
||||
@@ -1187,7 +1221,11 @@ fn render_device_list(devices: &[Device]) -> String {
|
||||
|
||||
fn render_discovery_result(devices: &[Device]) -> String {
|
||||
if devices.is_empty() {
|
||||
return "Discovery completed, but no devices were found.".to_string();
|
||||
return [
|
||||
"Discovery completed, but no devices were found.".to_string(),
|
||||
"Make sure the TV is powered on and reachable, then retry or add it manually with `tvctl device add --platform roku --address <ip>`.".to_string(),
|
||||
]
|
||||
.join("\n");
|
||||
}
|
||||
format!(
|
||||
"Discovered {} device(s).\n{}",
|
||||
@@ -1300,6 +1338,21 @@ fn render_daemon_status(
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
fn render_daemon_started(prefix: &str, status: &DaemonStatus) -> String {
|
||||
let http = if status.http_enabled {
|
||||
format!("http://{}:{}/v1", status.http_host, status.http_port)
|
||||
} else {
|
||||
"disabled".to_string()
|
||||
};
|
||||
|
||||
[
|
||||
prefix.to_string(),
|
||||
format!("Socket: {}", status.socket),
|
||||
format!("HTTP: {http}"),
|
||||
]
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn render_service_install(result: &ServiceInstallResult) -> String {
|
||||
if result.already_installed {
|
||||
let running = if result.running { "yes" } else { "no" };
|
||||
|
||||
+1
-13
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
/// The complete daemon configuration loaded from TOML.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
|
||||
#[serde(default)]
|
||||
pub struct TvctlConfig {
|
||||
/// Runtime daemon settings.
|
||||
@@ -24,18 +24,6 @@ pub struct TvctlConfig {
|
||||
pub dev: DevConfig,
|
||||
}
|
||||
|
||||
impl Default for TvctlConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
daemon: DaemonConfig::default(),
|
||||
discovery: DiscoveryConfig::default(),
|
||||
devices: DeviceConfig::default(),
|
||||
remote: RemoteConfig::default(),
|
||||
dev: DevConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TvctlConfig {
|
||||
/// Load configuration from the default XDG path or return defaults when absent.
|
||||
pub async fn load() -> anyhow::Result<Self> {
|
||||
|
||||
+12
-12
@@ -183,7 +183,9 @@ async fn set_socket_permissions(path: &Path) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn start_http_server_if_enabled(daemon: SharedDaemon) -> anyhow::Result<Option<JoinHandle<()>>> {
|
||||
async fn start_http_server_if_enabled(
|
||||
daemon: SharedDaemon,
|
||||
) -> anyhow::Result<Option<JoinHandle<()>>> {
|
||||
let (enabled, host, port) = {
|
||||
let guard = daemon.lock().await;
|
||||
(
|
||||
@@ -469,17 +471,15 @@ pub(crate) async fn execute_request(
|
||||
Ok(device) => device,
|
||||
Err(response) => return (response, false),
|
||||
};
|
||||
if clear {
|
||||
if let Err(error) = guard.app_cache.clear_platform(&device.platform).await {
|
||||
return (
|
||||
DaemonResponse::error(
|
||||
"app_cache_clear_failed",
|
||||
format!("Failed to clear the cached app list: {error}"),
|
||||
Some("Check permissions for ~/.local/share/tvctl/cache.".to_string()),
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
if clear && let Err(error) = guard.app_cache.clear_platform(&device.platform).await {
|
||||
return (
|
||||
DaemonResponse::error(
|
||||
"app_cache_clear_failed",
|
||||
format!("Failed to clear the cached app list: {error}"),
|
||||
Some("Check permissions for ~/.local/share/tvctl/cache.".to_string()),
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
match guard.adapters.list_apps(&device).await {
|
||||
Ok(apps) => match guard
|
||||
|
||||
@@ -179,19 +179,11 @@ impl Default for DeviceRegistry {
|
||||
}
|
||||
|
||||
/// A registry of platform adapters available to the daemon.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct AdapterRegistry {
|
||||
roku: RokuAdapter,
|
||||
}
|
||||
|
||||
impl Default for AdapterRegistry {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
roku: RokuAdapter::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AdapterRegistry {
|
||||
/// Build the adapter registry from the loaded daemon config.
|
||||
pub fn from_config(config: &TvctlConfig) -> Self {
|
||||
|
||||
+2
-3
@@ -110,10 +110,9 @@ impl TestDaemon {
|
||||
.get(format!("{}/daemon/status", self.base_url))
|
||||
.send()
|
||||
.await
|
||||
&& response.status().is_success()
|
||||
{
|
||||
if response.status().is_success() {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user