From 4c9edf0c31fb05b4f0ea576f6f16af1b18f2fc9f Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 12 Feb 2026 16:39:26 -0300 Subject: [PATCH 1/3] feat: introduce profile view command --- AGENTS.md | 7 + Cargo.lock | 227 +++++++++++++++++++++- Cargo.toml | 2 + skills/trix-cli-conventions/SKILL.md | 281 +++++++++++++++++++++++++++ src/commands/mod.rs | 1 + src/commands/profile/list.rs | 95 +++++++++ src/commands/profile/mod.rs | 198 +++++++++++++++++++ src/commands/profile/show.rs | 145 ++++++++++++++ src/config/convention.rs | 4 +- src/main.rs | 4 + templates/profile/list.md | 11 ++ templates/profile/show.md | 56 ++++++ 12 files changed, 1022 insertions(+), 9 deletions(-) create mode 100644 AGENTS.md create mode 100644 skills/trix-cli-conventions/SKILL.md create mode 100644 src/commands/profile/list.rs create mode 100644 src/commands/profile/mod.rs create mode 100644 src/commands/profile/show.rs create mode 100644 templates/profile/list.md create mode 100644 templates/profile/show.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..92fef48 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,7 @@ +# Agent Instructions + +This folder contains AI agent instructions and conventions for the Trix project. + +## Available Skills + +- [Trix CLI Conventions](skills/trix-cli-conventions/SKILL.md) - Standards for implementing CLI commands diff --git a/Cargo.lock b/Cargo.lock index 7277663..aea179e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -631,6 +631,24 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "coolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "980c2afde4af43d6a05c5be738f9eae595cff86dce1f38f88b95058a98c027f3" +dependencies = [ + "crossterm 0.29.0", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -690,6 +708,54 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crokey" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04a63daf06a168535c74ab97cdba3ed4fa5d4f32cb36e437dcceb83d66854b7c" +dependencies = [ + "crokey-proc_macros", + "crossterm 0.29.0", + "once_cell", + "serde", + "strict", +] + +[[package]] +name = "crokey-proc_macros" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847f11a14855fc490bd5d059821895c53e77eeb3c2b73ee3dded7ce77c93b231" +dependencies = [ + "crossterm 0.29.0", + "proc-macro2", + "quote", + "strict", + "syn", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -709,6 +775,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -731,6 +806,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.9.1", + "crossterm_winapi", + "derive_more", + "document-features", + "mio 1.0.4", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + [[package]] name = "crossterm_winapi" version = "0.9.1" @@ -896,6 +989,28 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -945,6 +1060,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "dolos-core" version = "1.0.0-rc.9" @@ -964,6 +1088,16 @@ dependencies = [ "trait-variant", ] +[[package]] +name = "dotenv-parser" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4e7e3d829e699806e8f1cb1906e671114c2b955a9c65559756b5f93258e8f0" +dependencies = [ + "pest", + "pest_derive", +] + [[package]] name = "dyn-clone" version = "1.0.19" @@ -1684,7 +1818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" dependencies = [ "bitflags 2.9.1", - "crossterm", + "crossterm 0.25.0", "dyn-clone", "fuzzy-matcher", "fxhash", @@ -1807,6 +1941,29 @@ dependencies = [ "sha2", ] +[[package]] +name = "lazy-regex" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1850,6 +2007,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.13" @@ -1976,6 +2139,15 @@ dependencies = [ "syn", ] +[[package]] +name = "minimad" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c5d708226d186590a7b6d4a9780e2bdda5f689e0d58cd17012a298efd745d2" +dependencies = [ + "once_cell", +] + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2004,6 +2176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", + "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -2932,13 +3105,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.14", "regex-syntax 0.8.5", ] @@ -2955,9 +3128,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3045,6 +3218,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.0.7" @@ -3230,6 +3412,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -3390,6 +3578,7 @@ checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", "mio 0.8.11", + "mio 1.0.4", "signal-hook", ] @@ -3498,6 +3687,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strict" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42444fea5b87a39db4218d9422087e66a85d0e7a0963a439b07bcdf91804006" + [[package]] name = "strsim" version = "0.11.1" @@ -3621,6 +3816,22 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "termimad" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7301d9c2c4939c97f25376b70d3c13311f8fefdee44092fc361d2a98adc2cbb6" +dependencies = [ + "coolor", + "crokey", + "crossbeam", + "lazy-regex", + "minimad", + "serde", + "thiserror 2.0.17", + "unicode-width 0.1.14", +] + [[package]] name = "terminal_size" version = "0.4.2" @@ -4049,10 +4260,11 @@ dependencies = [ "bip39", "chrono", "clap", - "convert_case", + "convert_case 0.8.0", "cryptoxide 0.5.1", "dirs", "dolos-core", + "dotenv-parser", "ed25519-bip32", "futures", "handlebars", @@ -4068,6 +4280,7 @@ dependencies = [ "serde_json", "serde_with", "tempfile", + "termimad", "thiserror 2.0.17", "tokio", "tokio-util", diff --git a/Cargo.toml b/Cargo.toml index e817b4c..9f17f13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,8 @@ prost = "0.13" tracing = "0.1" tokio-util = "0.7" tracing-subscriber = "0.3.22" +dotenv-parser = "0.1.3" +termimad = "0.31" [features] unstable = [] diff --git a/skills/trix-cli-conventions/SKILL.md b/skills/trix-cli-conventions/SKILL.md new file mode 100644 index 0000000..f08f0cd --- /dev/null +++ b/skills/trix-cli-conventions/SKILL.md @@ -0,0 +1,281 @@ +--- +name: trix-cli-conventions +description: Standards and best practices for implementing CLI commands in the Trix project using the View-Model pattern with Askama templates, termimad rendering, and standardized function signatures. Apply when creating new CLI commands or refactoring existing ones. +license: MIT +metadata: + version: "1.0" +--- + +# CLI Command Development Best Practices + +This document defines the coding standards and architectural patterns for developing CLI commands in the Trix project. + +## Module Structure + +``` +src/commands// +├── mod.rs # Command enum, shared types, utilities, module exports +└── .rs # Individual subcommand implementations + +templates/ +└── / # External Askama templates only location + ├── *.md # Template files + └── ... +``` + +## Standard Run Function Signature + +Every command module must expose a `run` function with this exact signature: + +```rust +pub fn run( + args: , + config: &RootConfig, + profile: &ProfileConfig, +) -> miette::Result<()> +``` + +Include all parameters even if unused (e.g., `ListArgs` as empty struct for commands without arguments). + +## Template Location Rule + +Templates **only** in root `templates//` directory. Never duplicate templates in `src/commands//`. + +## Template Formatting Guidelines + +### Header Hierarchy +- Use `##` for main sections +- Use `###` for subsections +- Maximum two levels of nesting + +### Field Pattern +```markdown +- **Label:** `{{ view.field }}` +``` + +All template variables wrapped in backticks for consistency. + +### Value Metadata +```markdown +- **Source:** ({{ value }}) +``` + +Metadata in parentheses without backticks. + +### Complete Section Example +```markdown +## Section Name +- **field:** `{{ view.field }}` +- **metadata:** ({{ view.source }}) + +### Subsection Name +- **field:** `{{ value }}` +{%- if !view.list.is_empty() %} +- **list:** +{%- for item in view.items %} + - `{{ item.key }}`: `{{ item.value }}` +{%- endfor %} +{%- endif %} +``` + +### Empty States +```markdown +## Section Name +*(none)* +``` + +Use italic `*(none)*` for empty collections. + +### Arrow Notation +Use `→` for "maps to" relationships: +```markdown +- `profile_name` (built-in) → `network_name` (built-in) +``` + +### Whitespace Control +- Always use `{%-` and `-%}` (with dash) +- No blank lines between sections +- Indent nested lists with 2 spaces + +## Enum Display Pattern + +Avoid verbose paths in templates. Implement `Display`: + +```rust +impl std::fmt::Display for EnvFileStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Found => write!(f, "found"), + Self::NotFound => write!(f, "not found"), + Self::Error(msg) => write!(f, "error: {}", msg), + } + } +} +``` + +Then in template: +```jinja2 +{%- if view.status.to_string() == "found" %} +- **Status:** `{{ view.status }}` +{%- else %} +- **Status:** `{{ view.status }}` +{%- endif %} +``` + +Or use `match` with simple patterns: +```jinja2 +{%- match view.status %}} +{%- when EnvFileStatus::Found %} +- **Status:** `found` +{%- when EnvFileStatus::NotFound %} +- **Status:** `not found` +{%- endmatch %} +``` + +## View Model Pattern + +**Materialization**: Build view structs from config +**Rendering**: Pass to template via Askama + +```rust +// mod.rs - Shared types +pub struct CommandView { + pub name: String, + pub field: String, +} + +// show.rs +fn build_view(config: &RootConfig) -> miette::Result { + Ok(CommandView { ... }) +} + +fn render_view(view: &CommandView) { + let markdown = Template::render_view(view); + MadSkin::default().print_text(&markdown); +} +``` + +## Askama Template Definition + +```rust +#[derive(Template)] +#[template(path = "/.md")] +struct CommandTemplate<'a> { + view: &'a CommandView, +} + +impl<'a> CommandTemplate<'a> { + fn render_view(view: &'a CommandView) -> String { + Self { view } + .render() + .expect("Template rendering failed") + } +} +``` + +## Security: Mask Sensitive Values + +```rust +pub(crate) fn mask_value(value: &str) -> String { + if value.len() <= 8 { + "***".to_string() + } else { + format!("{}...{}", &value[..4], &value[value.len()-4..]) + } +} + +pub(crate) fn should_mask_env_var(key: &str) -> bool { + let lower = key.to_lowercase(); + lower.contains("key") + || lower.contains("secret") + || lower.contains("password") + || lower.contains("token") + || lower.contains("private") +} +``` + +## File Organization + +**Code** (`src/commands//`): +- `mod.rs`: Command enum, shared types, utilities, dispatcher +- `*.rs`: Subcommand implementations with `run()` functions + +**Templates** (`templates//`): +- `*.md`: External Askama templates +- Single source of truth for templates + +## Key Principles + +1. **Consistency**: Same `run(args, config, profile)` signature everywhere +2. **Single Source**: Templates only in root `templates/` directory +3. **Uniform Formatting**: All values in backticks, metadata in parens +4. **Visual Hierarchy**: `##` → `###` → bullet lists +5. **Type Safety**: Compile-time template checking via Askama +6. **Security**: Mask sensitive values by default +7. **Maps To**: Use `→` notation for relationships +8. **Empty States**: Italic `*(none)*` for empty collections + +## Complete Example + +```rust +// src/commands/mycommand/mod.rs +use clap::{Args as ClapArgs, Subcommand}; + +pub mod show; + +pub use show::run as run_show; + +#[derive(Subcommand)] +pub enum Command { + Show(ShowArgs), +} + +#[derive(ClapArgs)] +pub struct ShowArgs { + pub name: String, +} + +pub fn run(args: Args, config: &RootConfig, profile: &ProfileConfig) -> miette::Result<()> { + match args.command { + Command::Show(args) => run_show(args, config, profile), + } +} + +#[derive(Debug, Clone)] +pub struct MyView { + pub name: String, + pub items: Vec, +} + +impl std::fmt::Display for MyStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "status") + } +} + +// src/commands/mycommand/show.rs +pub fn run(args: super::ShowArgs, config: &RootConfig, _profile: &ProfileConfig) -> miette::Result<()> { + let view = build_view(config, &args.name)?; + render_view(&view); + Ok(()) +} + +// templates/mycommand/show.md +## My Section +- **name:** `{{ view.name }}` +{%- if !view.items.is_empty() %} +- **items:** +{%- for item in view.items %} + - `{{ item }}` +{%- endfor %} +{%- else %} +*(none)* +{%- endif %} +``` + +## References + +- [Askama Documentation](https://djc.github.io/askama/) +- [Miette Error Handling](https://docs.rs/miette/latest/miette/) +- [Termimad Terminal Markdown](https://github.com/Canop/termimad) +- [Clap Derive Reference](https://docs.rs/clap/latest/clap/) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 8a3f3ff..c8729a6 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -8,6 +8,7 @@ pub mod identities; pub mod init; pub mod inspect; pub mod invoke; +pub mod profile; pub mod publish; pub mod telemetry; pub mod test; diff --git a/src/commands/profile/list.rs b/src/commands/profile/list.rs new file mode 100644 index 0000000..970029c --- /dev/null +++ b/src/commands/profile/list.rs @@ -0,0 +1,95 @@ +use askama::Template; +use termimad::MadSkin; + +use crate::config::RootConfig; + +use super::{ + resolve_network_source, resolve_profile_source, ConfigSource, NetworkListItem, ProfileListItem, + ProfileListView, +}; + +// ============================================================================ +// Askama Template +// ============================================================================ + +#[derive(Template)] +#[template(path = "profile/list.md")] +struct ProfileListTemplate<'a> { + view: &'a ProfileListView, +} + +impl<'a> ProfileListTemplate<'a> { + fn render_view(view: &'a ProfileListView) -> String { + ProfileListTemplate { view } + .render() + .expect("Template rendering failed") + } +} + +// ============================================================================ +// Command Entry Point +// ============================================================================ + +pub fn run( + _args: super::ListArgs, + config: &RootConfig, + _profile: &crate::config::ProfileConfig, +) -> miette::Result<()> { + let view = build_profile_list_view(config)?; + render_profile_list_view(&view); + Ok(()) +} + +// ============================================================================ +// View Building (Materialization) +// ============================================================================ + +fn build_profile_list_view(config: &RootConfig) -> miette::Result { + let all_profiles = config.available_profiles(); + let all_networks = config.available_networks(); + + let profile_items: Vec<_> = all_profiles + .iter() + .map(|name| { + let source = resolve_profile_source(name, config); + let network_name = config + .resolve_profile(name) + .map(|p| p.network.clone()) + .unwrap_or_default(); + let network_source = resolve_network_source(&network_name, config); + + ProfileListItem { + name: name.clone(), + source, + network: network_name, + network_source, + } + }) + .collect(); + + let network_items: Vec<_> = all_networks + .iter() + .map(|name| { + let source = resolve_network_source(name, config); + NetworkListItem { + name: name.clone(), + source, + } + }) + .collect(); + + Ok(ProfileListView { + profiles: profile_items, + networks: network_items, + }) +} + +// ============================================================================ +// Rendering +// ============================================================================ + +fn render_profile_list_view(view: &ProfileListView) { + let markdown = ProfileListTemplate::render_view(view); + let skin = MadSkin::default(); + skin.print_text(&markdown); +} diff --git a/src/commands/profile/mod.rs b/src/commands/profile/mod.rs new file mode 100644 index 0000000..f745c49 --- /dev/null +++ b/src/commands/profile/mod.rs @@ -0,0 +1,198 @@ +use clap::{Args as ClapArgs, Subcommand}; +use miette::IntoDiagnostic; +use std::collections::BTreeMap; +use std::path::Path; + +use crate::config::convention::{KNOWN_NETWORKS, KNOWN_PROFILES}; +use crate::config::serde::Named; +use crate::config::RootConfig; + +pub mod list; +pub mod show; + +pub use list::run as run_list; +pub use show::run as run_show; + +#[derive(Subcommand)] +pub enum Command { + /// List all available profiles (built-in + custom) + List, + /// Show effective configuration for a specific profile + Show(ShowArgs), +} + +#[derive(ClapArgs)] +pub struct ListArgs; + +#[derive(ClapArgs)] +pub struct ShowArgs { + /// Profile name to inspect + pub name: String, +} + +#[derive(ClapArgs)] +pub struct Args { + #[clap(subcommand)] + pub command: Command, +} + +pub fn run( + args: Args, + config: &RootConfig, + profile: &crate::config::ProfileConfig, +) -> miette::Result<()> { + match args.command { + Command::List => run_list(ListArgs, config, profile), + Command::Show(args) => run_show(args, config, profile), + } +} + +// ============================================================================ +// Shared View Model Data Structures +// ============================================================================ + +#[derive(Debug, Clone)] +pub enum ConfigSource { + BuiltIn, + Explicit, +} + +impl std::fmt::Display for ConfigSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ConfigSource::BuiltIn => write!(f, "built-in"), + ConfigSource::Explicit => write!(f, "from trix.toml"), + } + } +} + +#[derive(Debug, Clone)] +pub enum EnvFileStatus { + Found, + NotFound, + Error(String), +} + +#[derive(Debug, Clone)] +pub struct EndpointView { + pub url: String, + pub url_source: ConfigSource, + pub headers: Vec<(String, String)>, +} + +#[derive(Debug, Clone)] +pub struct NetworkView { + pub name: String, + pub source: ConfigSource, + pub is_testnet: bool, + pub trp: EndpointView, + pub u5c: EndpointView, +} + +#[derive(Debug, Clone)] +pub struct IdentityView { + pub name: String, + pub kind: String, +} + +#[derive(Debug, Clone)] +pub struct EnvFileView { + pub file_name: String, + pub status: EnvFileStatus, + pub variables: Vec<(String, String)>, +} + +#[derive(Debug, Clone)] +pub struct ProfileView { + pub name: String, + pub source: ConfigSource, + pub network: NetworkView, + pub identities: Vec, + pub env_file: EnvFileView, +} + +#[derive(Debug, Clone)] +pub struct ProfileListItem { + pub name: String, + pub source: ConfigSource, + pub network: String, + pub network_source: ConfigSource, +} + +#[derive(Debug, Clone)] +pub struct NetworkListItem { + pub name: String, + pub source: ConfigSource, +} + +#[derive(Debug, Clone)] +pub struct ProfileListView { + pub profiles: Vec, + pub networks: Vec, +} + +// ============================================================================ +// Source Resolution +// ============================================================================ + +pub(crate) fn resolve_profile_source(profile_name: &str, config: &RootConfig) -> ConfigSource { + if config.profiles.contains_key(profile_name) { + ConfigSource::Explicit + } else { + ConfigSource::BuiltIn + } +} + +pub(crate) fn resolve_network_source(network_name: &str, config: &RootConfig) -> ConfigSource { + if config.networks.contains_key(network_name) { + ConfigSource::Explicit + } else { + ConfigSource::BuiltIn + } +} + +// ============================================================================ +// Utilities +// ============================================================================ + +pub(crate) fn mask_value(value: &str) -> String { + if value.len() <= 8 { + "***".to_string() + } else { + let first = &value[..4]; + let last = &value[value.len() - 4..]; + format!("{}...{}", first, last) + } +} + +pub(crate) fn should_mask_env_var(key: &str) -> bool { + let lower = key.to_lowercase(); + lower.contains("key") + || lower.contains("secret") + || lower.contains("password") + || lower.contains("token") + || lower.contains("private") +} + +pub(crate) fn load_and_mask_env_vars(path: &Path) -> miette::Result> { + use miette::{Context, IntoDiagnostic}; + + let content = std::fs::read_to_string(path) + .into_diagnostic() + .context("Failed to read env file")?; + + let parsed: BTreeMap = dotenv_parser::parse_dotenv(&content) + .map_err(|e| miette::miette!("Failed to parse env file: {}", e))?; + + Ok(parsed + .into_iter() + .map(|(key, value)| { + let display_value = if should_mask_env_var(&key) { + mask_value(&value) + } else { + value + }; + (key, display_value) + }) + .collect()) +} diff --git a/src/commands/profile/show.rs b/src/commands/profile/show.rs new file mode 100644 index 0000000..f3eb6aa --- /dev/null +++ b/src/commands/profile/show.rs @@ -0,0 +1,145 @@ +use askama::Template; +use termimad::MadSkin; + +use crate::config::{NetworkConfig, ProfileConfig, RootConfig}; + +use super::{ + load_and_mask_env_vars, mask_value, resolve_network_source, resolve_profile_source, + should_mask_env_var, ConfigSource, EndpointView, EnvFileStatus, EnvFileView, IdentityView, + NetworkView, ProfileView, +}; + +// ============================================================================ +// Askama Template +// ============================================================================ + +#[derive(Template)] +#[template(path = "profile/show.md")] +struct ProfileShowTemplate<'a> { + view: &'a ProfileView, +} + +impl<'a> ProfileShowTemplate<'a> { + fn render_view(view: &'a ProfileView) -> String { + ProfileShowTemplate { view } + .render() + .expect("Template rendering failed") + } +} + +// ============================================================================ +// Command Entry Point +// ============================================================================ + +pub fn run( + args: super::ShowArgs, + config: &RootConfig, + _profile: &ProfileConfig, +) -> miette::Result<()> { + let view = build_profile_view(config, &args.name)?; + render_profile_view(&view); + Ok(()) +} + +// ============================================================================ +// View Building (Materialization) +// ============================================================================ + +fn build_profile_view(config: &RootConfig, profile_name: &str) -> miette::Result { + let profile = config.resolve_profile(profile_name)?; + let network = config.resolve_profile_network(profile_name)?; + + let profile_source = resolve_profile_source(profile_name, config); + let network_source = resolve_network_source(&network.name, config); + + Ok(ProfileView { + name: profile.name.clone(), + source: profile_source, + network: build_network_view(&network, network_source), + identities: build_identities_view(&profile), + env_file: build_env_file_view(&profile), + }) +} + +fn build_network_view(network: &NetworkConfig, source: ConfigSource) -> NetworkView { + NetworkView { + name: network.name.clone(), + source: source.clone(), + is_testnet: network.is_testnet, + trp: build_endpoint_view(&network.trp.url, &network.trp.headers, source.clone()), + u5c: build_endpoint_view(&network.u5c.url, &network.u5c.headers, source.clone()), + } +} + +fn build_endpoint_view( + url: &str, + headers: &std::collections::HashMap, + source: ConfigSource, +) -> EndpointView { + EndpointView { + url: url.to_string(), + url_source: source, + headers: headers + .iter() + .map(|(k, v)| (k.clone(), mask_value(v))) + .collect(), + } +} + +fn build_identities_view(profile: &ProfileConfig) -> Vec { + use crate::config::serde::Named; + + profile + .identities + .values() + .map(|identity| IdentityView { + name: identity.name(), + kind: match identity { + crate::config::IdentityConfig::RandomKey(_) => "random-key".to_string(), + crate::config::IdentityConfig::ExplicitKey(_) => "explicit-key".to_string(), + }, + }) + .collect() +} + +fn build_env_file_view(profile: &ProfileConfig) -> EnvFileView { + use std::path::Path; + + let env_file_path = profile.env_file_path(); + let file_name = env_file_path + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or(".env.{profile}") + .to_string(); + + if !env_file_path.is_file() { + return EnvFileView { + file_name, + status: EnvFileStatus::NotFound, + variables: vec![], + }; + } + + match load_and_mask_env_vars(&env_file_path) { + Ok(vars) => EnvFileView { + file_name, + status: EnvFileStatus::Found, + variables: vars, + }, + Err(e) => EnvFileView { + file_name, + status: EnvFileStatus::Error(e.to_string()), + variables: vec![], + }, + } +} + +// ============================================================================ +// Rendering +// ============================================================================ + +fn render_profile_view(view: &ProfileView) { + let markdown = ProfileShowTemplate::render_view(view); + let skin = MadSkin::default(); + skin.print_text(&markdown); +} diff --git a/src/config/convention.rs b/src/config/convention.rs index 2321691..08b9357 100644 --- a/src/config/convention.rs +++ b/src/config/convention.rs @@ -17,7 +17,7 @@ const PUBLIC_PREVIEW_U5C_KEY: &str = "trpjodqbmjblunzpbikpcrl"; const PUBLIC_PREPROD_U5C_KEY: &str = "trpjodqbmjblunzpbikpcrl"; const PUBLIC_MAINNET_U5C_KEY: &str = "trpjodqbmjblunzpbikpcrl"; -const KNOWN_NETWORKS: &[KnownNetwork] = &[ +pub const KNOWN_NETWORKS: &[KnownNetwork] = &[ KnownNetwork::CardanoMainnet, KnownNetwork::CardanoPreview, KnownNetwork::CardanoPreprod, @@ -58,7 +58,7 @@ impl KnownProfile { } } -const KNOWN_PROFILES: &[KnownProfile] = &[ +pub const KNOWN_PROFILES: &[KnownProfile] = &[ KnownProfile::Local, KnownProfile::Preview, KnownProfile::Preprod, diff --git a/src/main.rs b/src/main.rs index 5ad2ee8..716ac5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -63,6 +63,9 @@ enum Commands { /// Manage crypographic identities Identities(cmds::identities::Args), + /// Inspect and manage profiles + Profile(cmds::profile::Args), + /// Publish a Tx3 package into the registry (UNSTABLE - This feature is experimental and may change) #[command(hide = true)] Publish(cmds::publish::Args), @@ -109,6 +112,7 @@ async fn run_scoped_command(cli: Cli, config: RootConfig) -> Result<()> { Commands::Test(args) => cmds::test::run(args, &config, &profile), Commands::Build(args) => cmds::build::run(args, &config, &profile), Commands::Identities(args) => cmds::identities::run(args, &config, &profile), + Commands::Profile(args) => cmds::profile::run(args, &config, &profile), Commands::Publish(args) => cmds::publish::run(args, &config), Commands::Telemetry(args) => cmds::telemetry::run(args), }; diff --git a/templates/profile/list.md b/templates/profile/list.md new file mode 100644 index 0000000..703af81 --- /dev/null +++ b/templates/profile/list.md @@ -0,0 +1,11 @@ +## Available Profiles + +{%- for item in view.profiles %} +- `{{ item.name }}` ({{ item.source }}) → {{ item.network }} ({{ item.network_source }}) +{%- endfor %} + +## Available Networks + +{%- for item in view.networks %} +- {{ item.name }} ({{ item.source }}) +{%- endfor %} diff --git a/templates/profile/show.md b/templates/profile/show.md new file mode 100644 index 0000000..680952e --- /dev/null +++ b/templates/profile/show.md @@ -0,0 +1,56 @@ +## Profile +- **name**: `{{ view.name }}` +- **Source:** ({{ view.source }}) + +## Network +- **name:** `{{ view.network.name }}` +- **Source:** ({{ view.network.source }}) +- **Is Testnet:** {{ view.network.is_testnet }} + +### TRP Configuration +- **Source:** ({{ view.network.trp.url_source }}) +- **URL:** {{ view.network.trp.url }} +{%- if !view.network.trp.headers.is_empty() %} +- **Headers:** +{%- for (key, value) in view.network.trp.headers %} + - `{{ key }}`: {{ value }} +{%- endfor %} +{%- endif %} + +### U5C Configuration +- **Source:** ({{ view.network.u5c.url_source }}) +- **URL:** {{ view.network.u5c.url }} +{%- if !view.network.u5c.headers.is_empty() %} +- **Headers:** +{%- for (key, value) in view.network.u5c.headers %} + - `{{ key }}`: {{ value }} +{%- endfor %} +{%- endif %} + +## Identities +{%- if view.identities.is_empty() %} +*(none)* +{%- else %} +{%- for identity in view.identities %} +- {{ identity.name }} ({{ identity.kind }}) +{%- endfor %} +{%- endif %} + +## Environment File: +- **location**: {{ view.env_file.file_name }} +{%- match view.env_file.status %} +{%- when crate::commands::profile::EnvFileStatus::Found %} +{%- if view.env_file.variables.is_empty() %} +- **Status:** found (empty) +{%- else %} +- **Status:** found +- **Variables:** +{%- for (key, value) in view.env_file.variables %} + - `{{ key }}`: {{ value }} +{%- endfor %} +{%- endif %} +{%- when crate::commands::profile::EnvFileStatus::NotFound %} +- **Status:** not found +{%- when crate::commands::profile::EnvFileStatus::Error with (msg) %} +- **Status:** error - {{ msg }} +{%- endmatch %} From f179034451670338b186f0c630086a45678ff031 Mon Sep 17 00:00:00 2001 From: Santiago Date: Fri, 13 Feb 2026 13:12:58 -0300 Subject: [PATCH 2/3] fix lints --- src/builder.rs | 8 +++----- src/commands/check.rs | 2 +- src/commands/devnet/copy.rs | 9 ++++----- src/commands/expect.rs | 2 +- src/commands/identities.rs | 5 +---- src/commands/init.rs | 2 +- src/commands/inspect/tir.rs | 2 +- src/commands/invoke.rs | 2 +- src/commands/profile/list.rs | 2 +- src/commands/profile/mod.rs | 3 --- src/commands/profile/show.rs | 5 +---- src/commands/publish.rs | 10 +++++++++- src/commands/test.rs | 5 ++--- src/config/model.rs | 1 + src/devnet/mod.rs | 20 +++++++------------- src/home.rs | 2 ++ src/spawn/cshell.rs | 30 ++++++++++++++++++------------ src/spawn/dolos.rs | 2 -- src/updates.rs | 2 +- src/wallet.rs | 12 ++++++------ 20 files changed, 61 insertions(+), 65 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 6e21e15..5b760eb 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,9 +1,6 @@ use std::path::PathBuf; -use crate::{ - config::{ProfileConfig, RootConfig}, - spawn, -}; +use crate::{config::RootConfig, spawn}; fn define_tii_output_path() -> miette::Result { let out = crate::dirs::target_dir("tii")?.join("main.tii"); @@ -16,11 +13,12 @@ pub fn build_tii(config: &RootConfig) -> miette::Result { let output_path = define_tii_output_path()?; - spawn::tx3c::build_tii(&source, &output_path, &config)?; + spawn::tx3c::build_tii(&source, &output_path, config)?; Ok(output_path) } +#[allow(dead_code)] pub fn ensure_tii(config: &RootConfig) -> miette::Result { let output_path = define_tii_output_path()?; diff --git a/src/commands/check.rs b/src/commands/check.rs index 612d372..76222cc 100644 --- a/src/commands/check.rs +++ b/src/commands/check.rs @@ -14,7 +14,7 @@ struct Error { #[derive(ClapArgs, Debug)] pub struct Args {} -pub fn run(_args: Args, config: &RootConfig, profile: &ProfileConfig) -> miette::Result<()> { +pub fn run(_args: Args, config: &RootConfig, _profile: &ProfileConfig) -> miette::Result<()> { let main_path = config.protocol.main.clone(); let content = std::fs::read_to_string(main_path).into_diagnostic()?; diff --git a/src/commands/devnet/copy.rs b/src/commands/devnet/copy.rs index 4f51fd8..6c0eda6 100644 --- a/src/commands/devnet/copy.rs +++ b/src/commands/devnet/copy.rs @@ -6,7 +6,7 @@ use utxorpc::{ }; use clap::Args as ClapArgs; -use miette::{IntoDiagnostic, bail}; +use miette::IntoDiagnostic; #[derive(ClapArgs, Debug)] pub struct Args { @@ -64,7 +64,7 @@ async fn fetch_utxo_deps( let mut client_builder = ClientBuilder::new().uri(&u5c.url).into_diagnostic()?; for (key, value) in u5c.headers.iter() { - client_builder = client_builder.metadata(&key, &value).into_diagnostic()?; + client_builder = client_builder.metadata(key, value).into_diagnostic()?; } let mut client = client_builder.build::>().await; @@ -76,8 +76,8 @@ async fn fetch_utxo_deps( .await .into_diagnostic()?; - if let Some(tx) = tx { - if let Some(tx) = tx.parsed { + if let Some(tx) = tx + && let Some(tx) = tx.parsed { let utxos = client .read_utxos( tx.inputs @@ -93,7 +93,6 @@ async fn fetch_utxo_deps( return Ok(utxos); } - } Ok(vec![]) } diff --git a/src/commands/expect.rs b/src/commands/expect.rs index c4603f5..44925ce 100644 --- a/src/commands/expect.rs +++ b/src/commands/expect.rs @@ -17,7 +17,7 @@ pub fn expect_utxo(expects: &[ExpectUtxo], test_home: &Path) -> Result { for expect in expects.iter() { let mut failed = false; - let utxos = cshell::wallet_utxos(&test_home, &expect.from)?; + let utxos = cshell::wallet_utxos(test_home, &expect.from)?; if expect.datum_equals.is_none() && expect.min_amount.is_empty() { if utxos.is_empty() { diff --git a/src/commands/identities.rs b/src/commands/identities.rs index d400867..729516c 100644 --- a/src/commands/identities.rs +++ b/src/commands/identities.rs @@ -1,10 +1,7 @@ use clap::{Args as ClapArgs, Subcommand}; use miette::IntoDiagnostic; -use crate::{ - config::{ProfileConfig, RootConfig}, - spawn, -}; +use crate::config::{ProfileConfig, RootConfig}; #[derive(ClapArgs)] pub struct Args { diff --git a/src/commands/init.rs b/src/commands/init.rs index 3082c60..a4b13c3 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use crate::config::{ CodegenConfig, CodegenPlugin, KNOWN_CODEGEN_PLUGINS, KnownLedgerFamily, LedgerConfig, diff --git a/src/commands/inspect/tir.rs b/src/commands/inspect/tir.rs index 94520fd..f7dc616 100644 --- a/src/commands/inspect/tir.rs +++ b/src/commands/inspect/tir.rs @@ -21,7 +21,7 @@ pub fn run(args: Args, config: &RootConfig) -> miette::Result<()> { tx3_lang::analyzing::analyze(&mut ast).ok()?; - let ir = tx3_lang::lowering::lower(&mut ast, &args.tx) + let ir = tx3_lang::lowering::lower(&ast, &args.tx) .into_diagnostic() .with_context(|| format!("lowering {}", args.tx))?; diff --git a/src/commands/invoke.rs b/src/commands/invoke.rs index f1589d3..9b2bdca 100644 --- a/src/commands/invoke.rs +++ b/src/commands/invoke.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use clap::Args as ClapArgs; -use miette::{IntoDiagnostic, bail}; +use miette::IntoDiagnostic; use crate::{ builder, diff --git a/src/commands/profile/list.rs b/src/commands/profile/list.rs index 970029c..e0a0885 100644 --- a/src/commands/profile/list.rs +++ b/src/commands/profile/list.rs @@ -4,7 +4,7 @@ use termimad::MadSkin; use crate::config::RootConfig; use super::{ - resolve_network_source, resolve_profile_source, ConfigSource, NetworkListItem, ProfileListItem, + resolve_network_source, resolve_profile_source, NetworkListItem, ProfileListItem, ProfileListView, }; diff --git a/src/commands/profile/mod.rs b/src/commands/profile/mod.rs index f745c49..4418034 100644 --- a/src/commands/profile/mod.rs +++ b/src/commands/profile/mod.rs @@ -1,10 +1,7 @@ use clap::{Args as ClapArgs, Subcommand}; -use miette::IntoDiagnostic; use std::collections::BTreeMap; use std::path::Path; -use crate::config::convention::{KNOWN_NETWORKS, KNOWN_PROFILES}; -use crate::config::serde::Named; use crate::config::RootConfig; pub mod list; diff --git a/src/commands/profile/show.rs b/src/commands/profile/show.rs index f3eb6aa..ba2bdbb 100644 --- a/src/commands/profile/show.rs +++ b/src/commands/profile/show.rs @@ -5,8 +5,7 @@ use crate::config::{NetworkConfig, ProfileConfig, RootConfig}; use super::{ load_and_mask_env_vars, mask_value, resolve_network_source, resolve_profile_source, - should_mask_env_var, ConfigSource, EndpointView, EnvFileStatus, EnvFileView, IdentityView, - NetworkView, ProfileView, + ConfigSource, EndpointView, EnvFileStatus, EnvFileView, IdentityView, NetworkView, ProfileView, }; // ============================================================================ @@ -103,8 +102,6 @@ fn build_identities_view(profile: &ProfileConfig) -> Vec { } fn build_env_file_view(profile: &ProfileConfig) -> EnvFileView { - use std::path::Path; - let env_file_path = profile.env_file_path(); let file_name = env_file_path .file_name() diff --git a/src/commands/publish.rs b/src/commands/publish.rs index 32fff1c..6b4fbaa 100644 --- a/src/commands/publish.rs +++ b/src/commands/publish.rs @@ -3,7 +3,9 @@ use clap::Args as ClapArgs; use miette::IntoDiagnostic as _; use serde::{Deserialize, Serialize}; +#[allow(dead_code)] const MARKDOWN_MEDIA_TYPE: &str = "text/markdown"; +#[allow(dead_code)] const PROTOCOL_MEDIA_TYPE: &str = "application/tx3"; #[derive(ClapArgs)] @@ -12,6 +14,7 @@ pub struct Args {} #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] +#[allow(dead_code)] pub struct ImageMetadata { pub name: String, pub scope: String, @@ -20,6 +23,7 @@ pub struct ImageMetadata { pub description: Option, } +#[allow(dead_code)] fn get_oci_client(config: &RootConfig) -> oci_client::Client { let registry_url = config.registry.clone().unwrap().url; let registry_protocol = registry_url.split("://").next().unwrap(); @@ -33,9 +37,10 @@ fn get_oci_client(config: &RootConfig) -> oci_client::Client { ..Default::default() }; - return oci_client::Client::new(client_config); + oci_client::Client::new(client_config) } +#[allow(dead_code)] fn get_oci_reference(config: &RootConfig) -> Result { let registry_url = config.registry.clone().unwrap().url; let registry_host = registry_url.split("://").collect::>().pop().unwrap(); @@ -48,6 +53,7 @@ fn get_oci_reference(config: &RootConfig) -> Result String { let registry_url = config.registry.clone().unwrap().url; format!( @@ -59,6 +65,7 @@ fn get_image_url(config: &RootConfig) -> String { ) } +#[allow(unused_variables)] pub fn run(_args: Args, config: &RootConfig) -> miette::Result<()> { #[cfg(feature = "unstable")] { @@ -66,6 +73,7 @@ pub fn run(_args: Args, config: &RootConfig) -> miette::Result<()> { } #[cfg(not(feature = "unstable"))] { + let _ = config; Err(miette::miette!( "The publish command is currently unstable and requires the `unstable` feature to be enabled." )) diff --git a/src/commands/test.rs b/src/commands/test.rs index 46c4f5a..fecfd63 100644 --- a/src/commands/test.rs +++ b/src/commands/test.rs @@ -13,7 +13,6 @@ use crate::{ builder, config::{ProfileConfig, RootConfig}, devnet::Config as DevnetConfig, - spawn::cshell::OutputWallet, wallet::WalletProxy, }; @@ -139,7 +138,7 @@ fn trigger_transaction( }; let output = wallet.invoke_json( - &tii_file, + tii_file, &transaction.template, &args, vec![&signer], @@ -174,7 +173,7 @@ pub fn run(args: Args, config: &RootConfig, profile: &ProfileConfig) -> Result<( for transaction in &test.transactions { println!("--- Running transaction: {} ---", transaction.description); - let result = trigger_transaction(&wallet, &tii_file, transaction, &profile); + let result = trigger_transaction(&wallet, &tii_file, transaction, profile); if let Err(err) = result { eprintln!("Transaction `{}` failed.\n", transaction.description); diff --git a/src/config/model.rs b/src/config/model.rs index 9b791b9..12c924f 100644 --- a/src/config/model.rs +++ b/src/config/model.rs @@ -164,6 +164,7 @@ pub struct CodegenPluginConfig { #[derive(Debug, Serialize, Deserialize, Clone, Copy)] #[serde(rename_all = "kebab-case")] +#[allow(clippy::enum_variant_names)] pub enum KnownCodegenPlugin { TsClient, RustClient, diff --git a/src/devnet/mod.rs b/src/devnet/mod.rs index 4e77e01..b9ab7f4 100644 --- a/src/devnet/mod.rs +++ b/src/devnet/mod.rs @@ -6,10 +6,10 @@ use std::{ str::FromStr, }; -use miette::{Context as _, Diagnostic, IntoDiagnostic as _}; +use miette::{Diagnostic, IntoDiagnostic as _}; use serde::{Deserialize, Serialize}; -use serde_with::{DisplayFromStr, serde_as}; +use serde_with::{serde_as, DisplayFromStr}; use thiserror::Error; use crate::wallet::WalletProxy; @@ -60,8 +60,8 @@ impl FromStr for AddressSpec { type Err = miette::Error; fn from_str(s: &str) -> Result { - if s.starts_with("@") { - Ok(Self::NamedWallet(s[1..].to_string())) + if let Some(stripped) = s.strip_prefix("@") { + Ok(Self::NamedWallet(stripped.to_string())) } else { Ok(Self::Address(s.to_string())) } @@ -90,17 +90,11 @@ pub enum UtxoSpec { NativeBytes(NativeBytesUtxoSpec), } -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, Default)] pub struct Config { pub utxos: Vec, } -impl Default for Config { - fn default() -> Self { - Self { utxos: vec![] } - } -} - impl Config { pub fn load(path: impl AsRef) -> miette::Result { let data = std::fs::read_to_string(&path).map_err(Error::CantOpenConfig)?; @@ -175,7 +169,7 @@ pub fn build_dolos_utxos( fn setup_home(devnet: &Config, ctx: &Context) -> miette::Result { let dolos_dir = crate::dirs::target_dir("dolos")?; - let initial_utxos = build_dolos_utxos(&devnet, &ctx.aliases)?; + let initial_utxos = build_dolos_utxos(devnet, &ctx.aliases)?; let _ = crate::spawn::dolos::initialize_config(&dolos_dir, initial_utxos)?; @@ -200,7 +194,7 @@ impl Context { } pub fn start_daemon(devnet: &Config, ctx: &Context, silent: bool) -> miette::Result { - let home = setup_home(&devnet, ctx)?; + let home = setup_home(devnet, ctx)?; let daemon = crate::spawn::dolos::daemon(&home, silent)?; diff --git a/src/home.rs b/src/home.rs index 61aeaf8..30d37f3 100644 --- a/src/home.rs +++ b/src/home.rs @@ -70,6 +70,7 @@ pub fn tool_path(name: &str) -> miette::Result { } } +#[allow(dead_code)] pub fn tmp_dir() -> miette::Result { let home = tx3_dir()?; @@ -84,6 +85,7 @@ pub fn tmp_dir() -> miette::Result { Ok(tmp) } +#[allow(dead_code)] pub fn consistent_tmp_dir(prefix: &str, hashable: &[u8]) -> miette::Result { let tmp = tmp_dir()?; diff --git a/src/spawn/cshell.rs b/src/spawn/cshell.rs index b0cc355..a81cf57 100644 --- a/src/spawn/cshell.rs +++ b/src/spawn/cshell.rs @@ -1,28 +1,30 @@ use std::{ collections::HashMap, - path::{Path, PathBuf}, + path::Path, process::{Child, Command, Stdio}, }; use askama::Template; -use bip39::Mnemonic; -use miette::{Context as _, IntoDiagnostic as _, bail}; -use serde::{Deserialize, Deserializer, Serialize, de}; +use miette::{bail, Context as _, IntoDiagnostic as _}; +use serde::{de, Deserialize, Deserializer, Serialize}; -use crate::config::{ProfileConfig, RootConfig, TrpConfig, U5cConfig}; +use crate::config::{TrpConfig, U5cConfig}; #[derive(Debug, Deserialize)] +#[allow(dead_code)] pub struct OutputWallet { pub name: String, pub addresses: OutputAddress, } #[derive(Debug, Deserialize)] +#[allow(dead_code)] pub struct OutputAddress { pub testnet: String, } +#[allow(dead_code)] fn string_to_u64<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -32,6 +34,7 @@ where } #[derive(Debug, Deserialize)] +#[allow(dead_code)] pub struct OutputBalance { #[serde(deserialize_with = "string_to_u64")] pub coin: u64, @@ -141,7 +144,7 @@ pub fn wallet_create(home: &Path, name: &str, mnemonic: &str) -> miette::Result< "--name", name, "--mnemonic", - &mnemonic, + mnemonic, "--unsafe", "--output-format", "json", @@ -165,6 +168,7 @@ pub fn wallet_create(home: &Path, name: &str, mnemonic: &str) -> miette::Result< serde_json::from_slice(&output.stdout).into_diagnostic() } +#[allow(dead_code)] pub fn wallet_list(home: &Path) -> miette::Result> { let mut cmd = new_generic_command(home)?; @@ -182,6 +186,7 @@ pub fn wallet_list(home: &Path) -> miette::Result> { serde_json::from_slice(&output.stdout).into_diagnostic() } +#[allow(clippy::too_many_arguments)] pub fn tx_invoke_cmd( home: &Path, tii_file: &Path, @@ -234,6 +239,7 @@ pub fn tx_invoke_cmd( Ok(cmd) } +#[allow(clippy::too_many_arguments)] pub fn tx_invoke_interactive( home: &Path, tii_file: &Path, @@ -267,6 +273,7 @@ pub fn tx_invoke_interactive( Ok(()) } +#[allow(clippy::too_many_arguments)] pub fn tx_invoke_json( home: &Path, tii_file: &Path, @@ -304,6 +311,7 @@ pub fn tx_invoke_json( serde_json::from_slice(&output.stdout).into_diagnostic() } +#[allow(dead_code)] pub fn wallet_balance(home: &Path, wallet_name: &str) -> miette::Result { let mut cmd = new_generic_command(home)?; @@ -344,13 +352,11 @@ pub fn wallet_utxos(home: &Path, wallet_name: &str) -> miette::Result> let list: Vec = serde_json::from_value(utxos_val.clone()).into_diagnostic()?; Ok(list) + } else if v.is_array() { + let list: Vec = serde_json::from_value(v).into_diagnostic()?; + Ok(list) } else { - if v.is_array() { - let list: Vec = serde_json::from_value(v).into_diagnostic()?; - Ok(list) - } else { - bail!("unexpected CShell wallet balance output shape") - } + bail!("unexpected CShell wallet balance output shape") } } } diff --git a/src/spawn/dolos.rs b/src/spawn/dolos.rs index 1e0f7fa..552595e 100644 --- a/src/spawn/dolos.rs +++ b/src/spawn/dolos.rs @@ -1,7 +1,5 @@ use miette::{Context as _, IntoDiagnostic as _}; -use serde_json::Value; use std::{ - collections::HashMap, path::{Path, PathBuf}, process::{Child, Command, Stdio}, }; diff --git a/src/updates.rs b/src/updates.rs index 2389f7e..d7a3d6a 100644 --- a/src/updates.rs +++ b/src/updates.rs @@ -29,7 +29,7 @@ pub fn check_for_updates() -> anyhow::Result<()> { }; // If there are updates available, print a message - if updates.len() > 0 { + if !updates.is_empty() { println!("\n⚠️ Updates available! Run 'tx3up' to install them.\n"); } diff --git a/src/wallet.rs b/src/wallet.rs index e1665fe..3bc90b5 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -6,10 +6,10 @@ use std::{ use askama::Template as _; use bip39::Mnemonic; use cryptoxide::{digest::Digest, sha2::Sha256}; -use miette::{Context, IntoDiagnostic as _, Result, bail}; +use miette::{bail, Context, IntoDiagnostic as _, Result}; use crate::{ - config::{IdentityConfig, NetworkConfig, ProfileConfig, RandomKeyIdentityConfig, RootConfig}, + config::{IdentityConfig, NetworkConfig, ProfileConfig, RootConfig}, spawn::cshell::{CshellTomlTemplate, Provider, WalletInfoOutput}, }; @@ -18,7 +18,7 @@ fn generate_deterministic_mnemonic(input: &str) -> miette::Result { hasher.input(input.as_bytes()); let hash = hasher.result_str(); - let entropy: [u8; 32] = hash[..32].as_bytes().try_into().unwrap(); + let entropy: [u8; 32] = hash.as_bytes()[..32].try_into().unwrap(); Mnemonic::from_entropy(&entropy).into_diagnostic() } @@ -83,10 +83,10 @@ impl WalletProxy { crate::spawn::cshell::tx_invoke_interactive( &self.target_dir, - &tii_file, + tii_file, Some(profile), None, - &args, + args, vec![], true, skip_submit, @@ -108,7 +108,7 @@ impl WalletProxy { let output = crate::spawn::cshell::tx_invoke_json( &self.target_dir, - &tii_file, + tii_file, Some(profile), args, Some(tx_template), From ede506d35910693b677b109a299a159d850a3afb Mon Sep 17 00:00:00 2001 From: Santiago Date: Fri, 13 Feb 2026 15:38:47 -0300 Subject: [PATCH 3/3] remove test until comprehensvie fix --- .github/workflows/test.yml | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 56c96ac..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Test - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install tx3up - uses: ./.github/actions/setup - - - name: Create project - run: | - mkdir my-project - cd my-project - trix init -y - trix test ./tests/basic.toml