HTTP client/libcurl TUI front end in Rust, with request + key storage

Overview

image

image

Rust TUI HTTP Client with API Key Management

This project is still in active development and although it is useable, there may still be bugs and significant changes are still needed to both refactor the codebase and add new features.

I am distracted with work and a new project at the moment, so I am not able to put in as much time as I would like here and I am afraid that if I don't open source it now, it will end up in the side-project graveyard. Collaboration is welcome and encouraged, there is LOTS of low-hanging fruit here, while still being a useful tool.

image

Terminal user interface (TUI) HTTP client in Rust designed to simplify the process of making various types of HTTP requests while supporting various different kinds of Authentication (powered by libcURL), recursive downloading of directories (powered by GNU Wget), and storage + management of your previous requests + API keys.

This tool is for when you don't need something as complex as Postman, but you also don't want to have to remember the syntax for curl (or wget) commands.

Features

  • Interactive TUI Interface: The application offers an intuitive TUI interface that makes it easy to construct and execute HTTP requests without leaving the terminal.

  • Intuitive VIM keybindings: Vim keybindings are defaulted. Support to change them will eventually make it into the config file.

  • Multiple Request Types: Support for GET, POST, PUT, PATCH, HEAD, DELETE and custom requests.

  • API Key Management: Very simple sqlite based API key storage system. You can choose to save a Key from a request, or just add/edit/delete them manually.

  • Response Visualization: Pretty-print JSON responses in a human-readable format within the TUI, or allows you to choose to write the response to a file.

  • Cross Platform: This application builds and runs on Linux, MacOS and even Windows. Note Recursive downloading is powered by GNU Wget (not the fake wget command you get on windows), so this functionality is only available through Msys2 or WSL on Windows.

Why?

  • Have you even ran curl --help all ?

Installation

Prebuilt binaries for Windows and x86_64 Linux are available on the Releases page.

Install with Cargo:

  • Prerequisites: Make sure you have Rust and Cargo installed on your system.
  1. cargo install cute_tui

  2. make sure that your ~/.cargo/bin directory is in your PATH

  3. cute or cute --dump-config . # this will put a config.toml file in your cwd. You can edit this and place it in a dir CuTE in your ~/.config/ path (see below) to customize the colors of the application.

Build from source:

  1. Prerequisites: Make sure you have Rust and Cargo installed on your system.

  2. Clone the Repository: Clone this repository to your local machine using the following command:

    git clone https://github.com/PThorpe92/CuTE.git
    
  3. Navigate to Project Directory: Move into the project directory:

    cd CuTE
    
  4. Build and Run: Build and run the application using Cargo:

    cargo build --release 
    
  5. Move Binary: Move the binary to a location in your PATH of your choosing:

    sudo cp target/release/cute /usr/local/bin
    

Command Line Options

cute [OPTIONAL] '--dump-config ' or '--db-path <'/PATH/to/cute.db'>'
  • --dump-config: Dumps the default config.toml file to the specified path. If no path is specified, it will output it to the current working directory.

    • This config.toml file needs to be placed in ~/.config/CuTE/{config.toml} in order for the application to read it.
    • currently the config file can only specify basic colors of the application, and the path to the sqlite database. More options will be added in the future.
  • --db-path: Specify the path to the sqlite database. If no path is specified, it will default to data_local_dir working directory.(~/.local/share/CuTE/CuTE.db or the windows/macos equivalent)

Menus

  1. Main Menu: The main menu will provide options to create different types of HTTP requests and manage API keys.

  2. Request Type: Select the type of HTTP request you would like to make. The tool supports GET, POST, PUT, PATCH, HEAD, DELETE and custom requests.

  3. API Key Management: In the API key management section, you can add, edit, or delete API keys. Assign API keys to profiles and specific requests for easy integration.

  4. Viewing Responses: After executing a request, the tool will display the response in a readable format within the TUI, with the option to write it out to a file.

  5. Saved Commands: Much like the API keys, you can store and view past requests/commands for easy use later on.

Contributing

Contributions to this project are welcome and encouraged! If you encounter any bugs, have suggestions for improvements, or want to add a new feature, feel free to open an issue or submit a PR.

Before contributing, please review the Contribution Guidelines.

License

This project is licensed under the GPL3.0 License.


If you have any questions or need assistance, feel free to reach out

Fun fact:

This project was developed in the Maine State Prison system, where the author is currently incarcerated. I would like to bring whatever awareness possible to the importance of education and rehabilitation for those 2.2 million Americans currently incarcerated. I have a blog post if you are interested in reading about my story.

Disclaimer: This project is provided as-is, and its creators are not responsible for any misuse or potential security vulnerabilities resulting from the usage of API keys.

Comments
  • [CI] - Fix Issues With Build On Linux

    [CI] - Fix Issues With Build On Linux

    Yeah heres your problem

      = note: /usr/bin/ld: cannot find -lxcb-shape: No such file or directory
              /usr/bin/ld: cannot find -lxcb-xfixes: No such file or directory
              /usr/bin/ld: cannot find -lxcb-shape: No such file or directory
              /usr/bin/ld: cannot find -lxcb-xfixes: No such file or directory
              collect2: error: ld returned 1 exit status
    

    adding this in the build sequence should help

     sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
    
    opened by KMastroluca 17
  • [Bug/Windows] - Panic When Trying To Look At Response Headers When Response Was A HTTP Code 300.

    [Bug/Windows] - Panic When Trying To Look At Response Headers When Response Was A HTTP Code 300.

    I executed a basic request to google.com, which returns a status 300, page has moved or whatever because thats how they prevent people from scraping google. I was just testing all the different options, and when I tried to view the response headers we crash trying to unwrap what is an error. So we should be matching that result there and do some error checking.

    panic2

    opened by KMastroluca 13
  • Fixed Build Issues On Windows, Among Several Other Changes

    Fixed Build Issues On Windows, Among Several Other Changes

    • Fixed Issue Where We Had The Cursor Rolling Off The Screen If You Kept Hitting The Down Arrow.
    • Fixed Build Issues On Windows.
    • Put All Screen Code In Their Own Files
    • Added New Input Screens But Left The Old Ones In Tact
    • Added Debug Menu For Testing New Screens.

    closes #27

    opened by KMastroluca 13
  • Can't Open SQLite File On Windows

    Can't Open SQLite File On Windows

    Whatever the same issue that was being caused by App tests on our build before, is now happening locally on my machine after I pulled your latest code.

    Im getting an error related to the inability to open the SQLite3 file. When I check the directory that its in on Windows C:\Users\username\AppData\Local\CuTE\CuTE.db CuTE.db is showing up as a directory not a SQLite database file.

    
    thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: SqliteFailure(Error { code: CannotOpen, extended_code: 14 }, Some("unable to open database file: C:\\Users\\k.mastroluca-clark\\AppData\\Local\\CuTE\\CuTE.db"))', src\app.rs:72:36
    
    

    This is happening on startup before anything happens, its blocking my ability to open the app at all.

    opened by KMastroluca 11
  • Fixed Issue Where Options  And Response Body Would Persist In An Unreasonable Way

    Fixed Issue Where Options And Response Body Would Persist In An Unreasonable Way

    Issue

    When navigating back to the method screen from the request screen we didn't clear the options vector. I think it's reasonable for the opts vector to persist from the request page down to the view response page but not back upward, it should be cleared to make room for the user to change their mind and make another choice. Otherwise, you have situations where you go back to the home screen with your previous response body or request options stuck on the bottom of the page.

    Solution

    This is how I fix this in app.rs

    I add a previous_screen field to App with type Option which allows us to init with previous_screen == None which seems logically correct to me.

    pub struct App<'a> {
        /// toml config file
        pub config: Config,
        /// is the app running
        pub running: bool,
        /// cursor position (veritcal)
        pub cursor: usize,
        /// current screen (stack.pop)
        pub current_screen: Screen,
        /// screen stack
        pub screen_stack: Vec<Screen>,
        // Previous Screen 
        pub previous_screen: Option<Screen>,
        /// index of selected item
        pub selected: Option<usize>,
        /// command (curl or wget)
        pub command: Option<Box<dyn CMD>>,
        /// vec of applicable options
        pub opts: Vec<AppOptions>,
        /// Input struct for tui_input dependency
        pub input: Input,
        /// vec for user input to push into
        pub messages: Vec<String>,
        /// input mode (normal or editing)
        pub input_mode: InputMode,
        /// vec of list items to select from
        pub items: Vec<ListItem<'a>>,
        /// list state for tui
        pub state: Option<ListState>,
        /// http response from executed command
        pub response: Option<String>,
        /// database connection
        pub db: Box<DB>,
    }
    

    You can see we clearly do init with previous_screen == None here.

    impl<'a> Default for App<'a> {
        fn default() -> Self {
    
    
            Self {
                config: Config::default(),
                running: true,
                cursor: 0,
                screen_stack: vec![Screen::Home],
                previous_screen: None,
                selected: None,
                command: None,
                input_mode: InputMode::Normal,
                messages: Vec::new(),
                opts: Vec::new(),
                items: Screen::Home.get_opts(None),
                input: Input::default(),
                state: None,
                current_screen: Screen::Home,
                response: None,
                db: Box::new(DB::new().unwrap()),
            }
        }
    }
    

    Finally in App::go_to_screen() we take this approach, setting the previous screen, then matching on screen to see, if we have just navigated to the method screen, what was our previous screen? And if it was anything but Home, lets clear the opts vector. This might not be the best way to do this but it was what made sense to me i guess.

        pub fn goto_screen(&mut self, screen: Screen) {
    
            // Record Current Screen As Previous Screen 
            self.previous_screen = Some(self.current_screen.clone());
    
            // Push New/Next Screen Onto The Screen Stack
            self.screen_stack.push(screen.clone());
    
            // Set The Current Screen
            self.current_screen = screen.clone();
    
            self.cursor = 0;
            match screen {
                Screen::Method => {
                    // If The Screen We're Going To Is The Method Screen
                    // And Our Previous Screen Is Not The Home Screen, Meaning We're Backing Out  
                    // Not Moving Forward
                
                    if self.previous_screen != Some(Screen::Home) {
                        // Clear Our Options
                        self.remove_all_app_options();
                    }   
                }
    

    The return to home button on the response screen calls this remove_all_app_options() function as well, so this should cover our bases for now.

    opened by KMastroluca 9
  • [Bug] - This may be due to code I wrote, not yet merged, but if I navigate forwards and backwards  too much, moving backwards stops working

    [Bug] - This may be due to code I wrote, not yet merged, but if I navigate forwards and backwards too much, moving backwards stops working

    Like if I drill down all the way to URL input, then back all the way out to the main menu, then go all the way back down, and back up, after like 2 cycles I cant move back anymore. I think it has to do with unexpected behaviors related to hitting esc to go back on the home screen where there is nothing to go back to.

    bug 
    opened by KMastroluca 9
  • [GET REQUEST] - Adding Request Body

    [GET REQUEST] - Adding Request Body

    Most likely this is in JSON format. So we should think about possibly emulating the functionality of header input where we create a screen that allows adding and removing key/value pairs. The only thing is we should allow for raw text input as an option and if we do key/value pairs, we might have to figure out a way for a value to be another set of key/value pairs, which might get....complicated.

    enhancement 
    opened by KMastroluca 9
  • [UI] - Lets Create A Couple Different Kinds Of Screens We Will Reuse Over And Over.

    [UI] - Lets Create A Couple Different Kinds Of Screens We Will Reuse Over And Over.

    Im thinking the following:

    • 1 Line Text Input (URLs, Raw Body Input, Etc)
    • Multi-line Text Input (Not Sure What The Use Case Might Be Yet) ?
    • Key/Value Input, 2 Text Boxes Side By Side, i to begin input on the key, enter brings you to value input, enter again confirms the input, Escape brings you to the previous input and eventually backs out of the screen.
    • Data Add/Remove/Edit I see this one as kind of a list and you can hit a to add a new item, r to remove an item, and e to edit an existing item which would take you into one of the input/edit screens.
    enhancement 
    opened by KMastroluca 8
  • The name...

    The name...

    I think this will need a far more clever name before it gets released. Chat gippity has offered some pretty horrible suggestions. These are a few that came to mind

    RUSTfull    (lawsuit?)
    
    fetchMate
    
    tuiFetch
    
    Post-it
    
    Ehko
    
    Qwest
    
    opened by PThorpe92 8
  • Menu options

    Menu options

    I think offering HTTP requests with any of those 3 options (HTTP custom, cURL, or wget) is just pointless, as I cannot see any reason why someone would want to send a GET or POST request with wget as opposed to cURL.

    I think I am going to strip all the functionality of WGET aside from recursive downloads (the only thing it can do that curl cannot.) AND remove the option for custom HTTP requests (using reqwest) unless someone can provide me with a good reason not to. Because i just cannot think of any. Whether you use libcurl or some custom client.. it's all the same. and curl allows you to save the command and share it to run on servers or anything else you might want to do with it.

    opened by PThorpe92 7
  • [Fix/Feature] Restructured The Way Adding Display Opts Works | Added The Structure For A Sharable Command Feature

    [Fix/Feature] Restructured The Way Adding Display Opts Works | Added The Structure For A Sharable Command Feature

    Fix

    Before, when you tried to add a URL, and then add a second URL, literally you would have 2 URLs rendered next to each other in the Options window. That didnt make sense to me, so I restructured some key components of the options adding operation.

    The following is the new add_display_option() function.

        pub fn add_display_option(
            &mut self,
            mut opt: DisplayOpts, /* We Make This Mutable Because We Need To Compare It To A Mutable Reference Later */
        ) {
            if self.should_replace_or_add(opt.clone()) {
                // TRUE = We Should Add An Option
                // Adding The New Test Here. should_replace_or_add
                // The user has not yet added this command option yet, and so therefore, we push it to the opts vector.
                // I need a copy of the option before we move it
                let match_opt = opt.clone(); // Lets refactor all this later.
                self.opts.push(opt);
                // We also need to add it to the sharable command
                // but we need to know what kind of option it is
                // the best way I can think of right now is to match it but I'm sure there's a better way
                match match_opt {
                    // Im cloning this to shut the borrow checker up, there is probably a better way
                    DisplayOpts::Verbose => {
                        // Just add the verbose flag to the command
                        self.shareable_command.as_mut().unwrap().set_verbose(true);
                    }
                    DisplayOpts::Headers((key, value)) => {
                        // Push Header To Shareable Command
                        self.shareable_command
                            .as_mut()
                            .unwrap()
                            .push_header((key, value));
                    }
                    DisplayOpts::URL(url) => {
                        // Set URL To Sharable Command
                        self.shareable_command.as_mut().unwrap().set_url(url);
                    }
                    DisplayOpts::Outfile(outfile) => {
                        // Set Outfile To Shareable Command
                        self.shareable_command
                            .as_mut()
                            .unwrap()
                            .set_outfile(outfile);
                    }
                    _ => {
                        // Nothing
                        // This display opt does not factor into the sharable command.
                    }
                }
            } else {
                // FALSE = We Should Replace An Option
                // The user has already added this command option, and so therefore, we should replace the old value with the new value.
                //self.opts.retain(|x| x != &opt);
                // Sorry, this is my way to do this idk if its the right way, but this is what makes sense to me in my head
                for element in self.opts.iter_mut() {
                    // Same thing down here, I only care if its the same KIND of option, not the same value
                    // Again, this is annoying, I tried to do this an easier way
                    // but mem::discriminant doesnt like element as a comparison so I need to be particular
                    // Sorry lets refactor this
                    // TODO: Refactor This.
    
                    // We Want To Just Replace A URL
                    if let DisplayOpts::URL(_) = element {
                        *element = opt.clone(); // Copy The New URL Into The Old One
                        return;
                    }
    
                    // TODO: Headers Will Be Handled Differently.
    
                    // TODO: Outfile Will Be Handled Differently.
    
                    // TODO: Verbose & Save Command Will Be Handled Differently.
    
                    // TODO: Other Shit
                }
            }
        }
    

    It took me a while to figure out that, a lot of the ways that we were testing if options had already been added was kinda broken. It could be that I just didnt understand it, but It didnt make sense to me, and so to make some actual progress, I just implemented a process that made sense to me.

    These are the new tests for if we have a given option in the option vector.

    
        // Display option is some state that requires us to display the users
        // current selection on the screen so they know what they have selected
        // Lorenzo - Changing this because I dont think its doing what I want it to do.
        pub fn has_display_option(&self, opt: DisplayOpts) -> bool {
            // this is what we were doing before - self.opts.iter().any(|x| &x == &&opt)
            // I think this is what we want to do instead
            // We want to check if the option is in the vector
            // If it is, we want to return true
            // If it is not, we want to return false
            // We can do this by iterating over the vector and checking if the option is in the vector
            for element in self.opts.iter() {
                // I only care if its the same KIND of option, not the same value
                // This is annoying, I tried to do this an easier way
                // but mem::discriminant doesnt like element as a comparison so I need to be particular
                // im sorry i really dont like this
                // TODO: Lets refactor this.
                if let DisplayOpts::URL(_) = *element {
                    return true;
                }
    
                if let DisplayOpts::Headers(_) = *element {
                    return true;
                }
    
                if let DisplayOpts::Outfile(_) = *element {
                    return true;
                }
    
                if let DisplayOpts::Verbose = *element {
                    return true;
                }
    
                if let DisplayOpts::SaveCommand = *element {
                    return true;
                }
    
                if let DisplayOpts::Response(_) = *element {
                    return true;
                }
    
                if let DisplayOpts::ShareableCmd(_) = *element {
                    return true;
                }
            }
            // Otherwise, its not there.
            false
        }
    
        // Lorenzo - Im adding this function as a slightly more
        // robust version of has_display_option, to test if we should be replacing a value or adding a new one
        fn should_replace_or_add(&self, opt: DisplayOpts) -> bool {
            // Lets match the type of display option
            // We know that only 1 URL should ever be added,
            // So if we're adding a URL we should replace it if it already exists
            match opt {
                DisplayOpts::URL(_) => !self.has_display_option(opt.clone()), // URL Should Be Replaced If It Already Exists
                DisplayOpts::Headers(_) => true, // Headers Should Be "Pushed" or Added
                DisplayOpts::Outfile(_) => !self.has_display_option(opt.clone()), // Outfile Should Be Replaced
                DisplayOpts::Verbose => !self.has_display_option(opt.clone()), // Verbose Should Be Toggled
                DisplayOpts::SaveCommand => !self.has_display_option(opt.clone()), // Save Command Should Be Toggled
                DisplayOpts::Response(_) => !self.has_display_option(opt.clone()), // Response Should Be Replaced
                DisplayOpts::ShareableCmd(_) => !self.has_display_option(opt.clone()), // Shareable Command Should Be Replaced With The New Version
                _ => false, // Anything Else Should Be Replaced.
            }
        }
    
    

    My comments sort of explain myself as I go. I tried to do variant comparison with a function called std::mem::discriminant() but for whatever reason, it didnt like the element variable from self.opts.iter()? So this is kinda my hacky fix. I know theres a better way to do this, but for now, its verbose and clear what I mean. But its also kinda redundant and Im sure theres an easier way to do the same thing.


    As you can see, ive also added additional functionality for the sharable curl command. That has been implemented as follows, but ive yet to touch any UI code having anything to do that yet, so basically, the data structure is there, but I havent thoroughly tested it yet. It shouldnt interfere with the operation of the application tho.

    Sharable Command Structure / Implementation

    
    impl ShareableCommand {
        pub fn new() -> Self {
            Self {
                command: "".to_string(),
                url: "".to_string(),
                headers: Vec::new(),
                outfile: "".to_string(),
                verbose: false,
            }
        }
    
        pub fn set_command(&mut self, command: String) {
            self.command = command;
        }
    
        pub fn set_verbose(&mut self, verbose: bool) {
            self.verbose = verbose;
        }
    
        pub fn set_url(&mut self, url: String) {
            self.url = url;
        }
    
        pub fn set_headers(&mut self, headers: Vec<(String, String)>) {
            self.headers = headers;
        }
    
        pub fn push_header(&mut self, header: (String, String)) {
            self.headers.push(header);
        }
    
        pub fn set_outfile(&mut self, outfile: String) {
            self.outfile = outfile;
        }
    
        pub fn render_command_str(&self) -> Option<String> {
            if self.command.is_empty() {
                return None;
            }
    
            if self.url.is_empty() {
                return None;
            }
    
            // This assembles the simplest possible command string
            let mut command_str = self.command.clone();
    
            // Check For Verbose Flag
            if self.verbose {
                // Verbose Flag Including Whitespace
                command_str.push_str(" -v");
            }
    
            // Whitespace
            command_str.push_str(" ");
            // URL
            command_str.push_str(&self.url);
    
            // Next We Check For Headers
            if self.headers.len() > 0 {
                for (key, value) in &self.headers {
                    // Whitespace
                    command_str.push_str("");
                    // Header Flag
                    command_str.push_str("-H ");
                    // Open Quote
                    command_str.push_str("\"");
                    // Header Key
                    command_str.push_str(&key);
                    // Delimiter
                    command_str.push_str(":");
                    // Header Value
                    command_str.push_str(&value);
                    // Close Quote
                    command_str.push_str("\"");
                }
            }
    
            // Check For Outfile
            if !self.outfile.is_empty() {
                // Whitespace
                command_str.push_str(" ");
                // Outfile Flag
                command_str.push_str(" -o ");
                // Outfile Name
                command_str.push_str(&self.outfile);
            }
    
            // Return Command String
            Some(command_str)
        }
    }
    

    Ill make a note that this structure is somewhat similar to your existing Command structure, but it has the curl command string formatting function, which I imagine will be getting plenty more complicated as we add new features.

    enhancement 
    opened by KMastroluca 7
  • Authentication (Bearer) seems to be broken with recent commit

    Authentication (Bearer) seems to be broken with recent commit

    With the version of CuTE I have in my path, I can currently hit my companies ec2 instance that has Canvas LMS, which requires a Bearer token API Key.

    With the latest pull from main, I am getting an error immediately.

    bug 
    opened by PThorpe92 0
  • Better Error handling

    Better Error handling

    This project is full of horrendous practices due to still being in pre-0.1 release stage, many of those are examples of poor error handling, such as flagrant use of .unwrap() (although there are quite a few that are justified, like app.command.as_ref().unwrap().some_method() where there will always be a command field when those methods are called.

    However, there is still much to do in terms of error handling. This is simple stuff and I really wish this was hacktoberfest still so people could get some easy PR's merged (on a rust project no less :D )

    good first issue 
    opened by PThorpe92 0
  • Errors persist when backing up screens

    Errors persist when backing up screens

    If I go from screen A to screen B, and enter some invalid information (such as an invalid path to an SSL certificate), when I go back to screen A (the request screen) there will be an appropriate error message on the top of the screen letting me know I have entered an invalid path. If I then navigate to another page (such as the "More Options" page) and then navigate back to screen A.. I will be presented with the same error on the top of the screen letting me know I entered an invalid path, even though that path was never actually input, so I need not be reminded about that again.

    bug good first issue 
    opened by PThorpe92 0
  • Added Rust Recursive Downloading Function In New Utils Folder

    Added Rust Recursive Downloading Function In New Utils Folder

    I didn't really know where to put this, this fixes my issue of recursive downloading support on Windows, I just wrote it in rust. I didn't implement it yet, idk maybe we give the user a choice, depending on what they have available vs what they want to use. The point is , this will just work without issues on windows. Either way it's there.

    opened by KMastroluca 1
Releases(v0.1.0-b)
Owner
Preston Thorpe
Back-end/Systems Developer | Rustacean | /dev/tty inhabitant
Preston Thorpe
A TUI front-end for the Debug Adapter Protocol.

Pesticide A TUI front-end for the Debug Adapter Protocol. Motivation I am an avid kakoune user. Kak is a very niche text editor with few users. As suc

raiguard 6 Jun 17, 2022
This is a simple lnd poller and web front-end to see and read boosts and boostagrams.

Helipad This package will poll a Lightning LND node for invoices related to Podcasting 2.0 and display them in a web interface. It's intended for use

Podcastindex.org 26 Dec 29, 2022
A run-codes cli front end with some extra features

run-cli Run-cli A run-codes cli front end with some extra features Report Bug · Request Feature Table of Contents About The Project Built With Getting

Matheus Vieira 13 Nov 16, 2022
Command-line HTTP client for sending a POST request to specified URI on each stdin line.

line2httppost Simple tool to read lines from stdin and post each line as separate POST request to a specified URL (TCP connection is reused though). G

Vitaly Shukela 3 Jan 3, 2023
A curl(libcurl) mod for rust.

curl Master Dev A lightweight Curl-wrapper for using (mostly) HTTP from Rust. While there are a couple of Rust HTTP libraries like rust-http and its s

Valerii Hiora 2 Sep 14, 2016
Rust bindings to libcurl

curl-rust libcurl bindings for Rust Quick Start use std::io::{stdout, Write}; use curl::easy::Easy; // Print a web page onto stdout fn main() {

Alex Crichton 885 Jan 9, 2023
A template for bootstrapping a Rust TUI application with tui-rs & crossterm

rust-tui-template A template for bootstrapping a Rust TUI application with tui-rs & crossterm. tui-rs The library is based on the principle of immedia

Orhun Parmaksız 72 Dec 31, 2022
A reliable key-value storage for modern software

Quick-KV A reliable key-value storage for modern software Features Binary Based Data-Store Serde Supported Data Types Thread Safe Links Documentation

null 3 Oct 11, 2023
A user-friendly TUI for secure file transfers, with arrow-key and VIM-style navigation

gsftp SFTP with an interactive text-based user interface (TUI). Transfer files through an encrypted connection with a visual interface, so you can see

Ben Jiron 3 Jul 7, 2022
Rust TUI client for steamcmd

Steam TUI About Just a simple TUI client for steamcmd. Allows for the graphical launching, updating, and downloading of steam games through a simple t

Dylan Madisetti 599 Jan 9, 2023
A user-friendly TUI client for Matrix written in Rust!

Konoha A user-friendly TUI client for Matrix written in Rust! Notice: The client is currently not usable and is only hosted on GitHub for version cont

L3af 9 Jan 5, 2022
Tree-based TUI chat client

cove Cove is a TUI client for euphoria.io, a threaded real-time chat platform. It runs on Linux, Windows and macOS. Manual installation This section c

null 8 Nov 16, 2022
RedMaple offers an oppinionated yet extremely flexible data modeling system based on events for back-end applications.

RedMaple offers an oppinionated yet extremely flexible data modeling system based on events for back-end applications.

Amir Alesheikh 4 Mar 5, 2023
⚡ Blazing fast async/await HTTP client for Python written on Rust using reqwests

Reqsnaked Reqsnaked is a blazing fast async/await HTTP client for Python written on Rust using reqwests. Works 15% faster than aiohttp on average RAII

Yan Kurbatov 8 Mar 2, 2023
An event replay tool for the Trento storage backend.

photofinish - a little, handy tool to replay events This tiny CLI tool aims to fulfill the need to replay some events and get fixtures. Photofinish re

null 5 Nov 10, 2022
Single File Assets is a file storage format for images

SFA (Rust) Single File Assets is a file storage format for images. The packed images are not guaranteed to be of same format because the format while

null 1 Jan 23, 2022
Databento Binary Encoding (DBZ) - Fast message encoding and storage format for market data

dbz A library (dbz-lib) and CLI tool (dbz-cli) for working with Databento Binary Encoding (DBZ) files. Python bindings for dbz-lib are provided in the

Databento, Inc. 15 Nov 4, 2022
A simple command line program to upload file or directory to web3.storage with optional encryption and compression

w3s-cli A simple command line program to upload file or directory to web3.storage with optional encryption and compression. Features Uploads single fi

qdwang 5 Oct 22, 2022
An apocalypse-resistant data storage format for the truly paranoid.

Carbonado An apocalypse-resistant data storage format for the truly paranoid. Designed to keep encrypted, durable, compressed, provably replicated con

diba-io 30 Dec 29, 2022