{"id":16295154,"url":"https://github.com/wadackel/promptuity","last_synced_at":"2025-08-21T10:31:33.556Z","repository":{"id":216064169,"uuid":"740354212","full_name":"wadackel/promptuity","owner":"wadackel","description":"Promptuity is a library that provides interactive prompts.","archived":false,"fork":false,"pushed_at":"2025-08-17T12:47:06.000Z","size":3005,"stargazers_count":81,"open_issues_count":6,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-17T14:38:07.104Z","etag":null,"topics":["ask","cli","command-line","command-line-tool","console","interactive","prompt","rust"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/promptuity","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wadackel.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"wadackel"}},"created_at":"2024-01-08T07:10:49.000Z","updated_at":"2025-06-28T16:12:18.000Z","dependencies_parsed_at":"2024-03-20T03:43:11.094Z","dependency_job_id":"f8347623-37e5-4231-8f52-f14ba794e7f9","html_url":"https://github.com/wadackel/promptuity","commit_stats":null,"previous_names":["wadackel/promptuity"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/wadackel/promptuity","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wadackel%2Fpromptuity","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wadackel%2Fpromptuity/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wadackel%2Fpromptuity/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wadackel%2Fpromptuity/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wadackel","download_url":"https://codeload.github.com/wadackel/promptuity/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wadackel%2Fpromptuity/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270862916,"owners_count":24658703,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-17T02:00:09.016Z","response_time":129,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ask","cli","command-line","command-line-tool","console","interactive","prompt","rust"],"created_at":"2024-10-10T20:17:49.536Z","updated_at":"2025-08-21T10:31:33.200Z","avatar_url":"https://github.com/wadackel.png","language":"Rust","funding_links":["https://github.com/sponsors/wadackel"],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003ePromptuity\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\u003csmall\u003ePromptuity = Prompt + Ingenuity\u003c/small\u003e\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/wadackel/promptuity/actions/workflows/ci.yml?query=branch%3Amain\"\u003e\u003cimg alt=\"GitHub Actions Workflow Status\" src=\"https://img.shields.io/github/actions/workflow/status/wadackel/promptuity/ci.yml?branch=main\u0026style=flat-square\u0026logo=GitHub%20Actions\u0026logoColor=white\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://crates.io/crates/promptuity/\"\u003e\u003cimg alt=\"Crates.io Version\" src=\"https://img.shields.io/crates/v/promptuity?style=flat-square\u0026logo=Rust\u0026logoColor=white\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://docs.rs/promptuity\"\u003e\u003cimg alt=\"docs.rs\" src=\"https://img.shields.io/docsrs/promptuity?style=flat-square\u0026logo=Rust\u0026logoColor=white\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/wadackel/promptuity/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/wadackel/promptuity?label=license\u0026style=flat-square\" alt=\"MIT LICENSE\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003ePromptuity is a library that provides interactive prompts. It is highly extensible, allowing you to build your original prompts from scratch. It brings \u003cstrong\u003eingenuity\u003c/strong\u003e to various projects.\u003c/p\u003e\n\n## Table Of Contents\n\n- [Concept](#concept)\n- [Quick Start](#quick-start)\n- [Examples](#examples)\n- [Documentation](#documentation)\n- [Prompts](#prompts)\n    - [Input](#input)\n    - [Password](#password)\n    - [Number](#number)\n    - [Select](#select)\n    - [MultiSelect](#multiselect)\n    - [Confirm](#confirm)\n    - [Autocomplete](#autocomplete)\n- [Themes](#themes)\n    - [MinimalTheme](#minimaltheme)\n    - [FancyTheme](#fancytheme)\n- [Customize](#customize)\n    - [Build your own Prompt](#build-your-own-prompt)\n    - [Build your own Theme](#build-your-own-theme)\n- [Error Handling](#error-handling)\n- [Testing](#testing)\n- [Alternatives](#alternatives)\n    - [Inspired](#inspired)\n- [Contributing](#contributing)\n- [CHANGELOG](#changelog)\n- [License](#license)\n\n## Concept\n\n- :zap: **Not easy, But simple**\n    - Avoids APIs with implicit behavior, aiming to provide as transparent APIs as possible.\n    - The amount of code required to start a prompt may be more compared to other libraries.\n- :hammer: **Extensible**\n    - You can customize built-in prompts or build your prompts from scratch.\n    - The built-in prompts are minimal, assuming that prompt requirements vary by project.\n- :nail_care: **Beautiful**\n    - Offers two types of built-in Themes.\n    - Themes can also be fully customized to fit your ideal.\n\n## Quick Start\n\n![Quick Start DEMO](./assets/quick_start.gif)\n\nThe basic usage is as follows.\n\n```rust\nuse promptuity::prompts::{Confirm, Input, Select, SelectOption};\nuse promptuity::themes::FancyTheme;\nuse promptuity::{Error, Promptuity, Term};\n\nfn main() -\u003e Result\u003c(), Error\u003e {\n    let mut term = Term::default();\n    let mut theme = FancyTheme::default();\n    let mut p = Promptuity::new(\u0026mut term, \u0026mut theme);\n\n    p.term().clear()?;\n\n    p.with_intro(\"Survey\").begin()?;\n\n    let name = p.prompt(Input::new(\"Please enter your username\").with_placeholder(\"username\"))?;\n\n    let _ = p.prompt(Confirm::new(\"Are you a full-time software developer?\").with_default(true))?;\n\n    let _ = p.prompt(\n        Select::new(\n            \"Select your primary programming language\",\n            vec![\n                SelectOption::new(\"Rust\", \"rust\"),\n                SelectOption::new(\"Go\", \"go\"),\n                SelectOption::new(\"C++\", \"cpp\"),\n                SelectOption::new(\"C\", \"c\"),\n                SelectOption::new(\"TypeScript\", \"typescript\"),\n                SelectOption::new(\"JavaScript\", \"javascript\"),\n                SelectOption::new(\"Deno\", \"deno\"),\n                SelectOption::new(\"Python\", \"python\"),\n                SelectOption::new(\"Java\", \"java\"),\n                SelectOption::new(\"Dart\", \"dart\"),\n                SelectOption::new(\"Other\", \"other\"),\n            ],\n        )\n        .with_hint(\"Submit with Space or Enter.\"),\n    )?;\n\n    p.with_outro(format!(\"Thank you for your response, {}!\", name))\n        .finish()?;\n\n    Ok(())\n}\n```\n\n## Examples\n\nIf you want to see more examples, please refer to the [examples](./examples/) directory.\n\n## Documentation\n\nPlease refer to the [documentation](https://docs.rs/promptuity).\n\n## Prompts\n\n[`promptuity::prompts`](https://docs.rs/promptuity/latest/promptuity/prompts/index.html) offers five built-in prompts.  \nTo implement your original prompt, please see the [Build your own Prompt](#build-your-own-prompt) section.\n\n### Input\n\n![Input Demo](./assets/prompt_input.gif)\n\nA prompt for general text input.\n\n```rust\nlet name = p.prompt(\n    Input::new(\"What is your accout name?\")\n        .with_placeholder(\"username\")\n        .with_hint(\"Only alphanumeric characters are allowed.\")\n        .with_validator(|value: \u0026String| {\n            if value.chars().all(|c| c.is_alphanumeric()) {\n                Ok(())\n            } else {\n                Err(\"Invalid format\".into())\n            }\n        }),\n)?;\n```\n\n### Password\n\n![Password Demo](./assets/prompt_password.gif)\n\nA text input prompt where the input is not displayed.\n\n```rust\nlet secret = p.prompt(\n    Password::new(\"Set a password for your account\")\n        .with_hint(\"Please enter more than 6 alphanumeric characters.\")\n        .with_validator(|value: \u0026String| {\n            if value.len() \u003c 6 {\n                Err(\"Password must be at least 6 characters long\".into())\n            } else {\n                Ok(())\n            }\n        }),\n)?;\n```\n\n### Number\n\n![Number Demo](./assets/prompt_number.gif)\n\nA prompt for inputting only integer values.\n\n```rust\nlet age = p.prompt(Number::new(\"How old are you?\").with_min(0).with_max(120))?;\n```\n\n### Select\n\n![Select Demo](./assets/prompt_select.gif)\n\nA prompt for selecting a single element from a list of options.\n\n```rust\nlet color = p.prompt(\n    Select::new(\n        \"What is your favorite color?\",\n        vec![\n            SelectOption::new(\"Red\", \"#ff0000\"),\n            SelectOption::new(\"Green\", \"#00ff00\").with_hint(\"recommended\"),\n            SelectOption::new(\"Blue\", \"#0000ff\"),\n        ],\n    )\n    .as_mut(),\n)?;\n```\n\n### MultiSelect\n\n![MultiSelect Demo](./assets/prompt_multi_select.gif)\n\nA prompt for selecting multiple elements from a list of options.\n\n```rust\nlet color = p.prompt(\n    MultiSelect::new(\n        \"What are your favorite colors?\",\n        vec![\n            MultiSelectOption::new(\"Red\", \"#ff0000\"),\n            MultiSelectOption::new(\"Green\", \"#00ff00\").with_hint(\"recommended\"),\n            MultiSelectOption::new(\"Blue\", \"#0000ff\"),\n        ],\n    )\n    .as_mut(),\n)?;\n```\n\n### Confirm\n\n![Confirm Demo](./assets/prompt_confirm.gif)\n\nA prompt for inputting a Yes/No choice.\n\n```rust\nlet like = p.prompt(\n    Confirm::new(\"Do you like dogs?\")\n        .with_hint(\"This is just a sample prompt :)\")\n        .with_default(true),\n)?;\n```\n\n### Autocomplete\n\n\u003e [!NOTE]\n\u003e Autocomplete is not provided as a built-in feature. This is because the optimal behavior for Fuzzy Match and key bindings varies by project.  \n\u003e While not provided as a built-in, a reference implementation is available in [examples/autocomplete.rs](./examples/autocomplete.rs). Please adapt this to suit your project's needs.\n\n## Themes\n\nPromptuity offers two different built-in themes.  \nTo implement your original Theme, please see the [Build your own Theme](#build-your-own-theme) section.\n\n### MinimalTheme\n\nMinimalTheme is similar to [Inquirer](https://github.com/SBoudrias/Inquirer.js). It provides a compact UI.\n\n![MinimalTheme Screenshot](./assets/theme_minimal.png)\n\n```rust\nuse promptuity::themes::MinimalTheme;\n\nfn main() {\n    let mut theme = MinimalTheme::default();\n    // ...\n}\n```\n\n### FancyTheme\n\nFancyTheme is similar to [clack](https://github.com/natemoo-re/clack). It provides a rich UI.\n\n![FancyTheme Screenshot](./assets/theme_fancy.png)\n\n```rust\nuse promptuity::themes::FancyTheme;\n\nfn main() {\n    let mut theme = FancyTheme::default();\n    // ...\n}\n```\n\n## Customize\n\nThis section provides guidance on how to construct original prompts and Themes.\n\n### Build your own Prompt\n\nCreating an original prompt can be achieved by implementing the [`Prompt`](https://docs.rs/promptuity/latest/promptuity/trait.Prompt.html) trait. By implementing three lifecycle methods, you can build prompts that are usable with [`Promptuity::prompt`](https://docs.rs/promptuity/latest/promptuity/struct.Promptuity.html#method.prompt).\n\nPromptuity prompts consist of the following elements:\n\n| Item | Description |\n| :-- | :-- |\n| **Message** | Displays the question content of the prompt. |\n| **Input** | A single-line item that accepts user key inputs. |\n| **Body** | A multi-line item that accepts user key inputs. |\n| **Hint** | Displays a message to assist with prompt input. |\n\n- Prompts that accept single-line inputs, like `Input` or `Password`, do not utilize **Body**.\n- Prompts that do not accept inputs, like `Select` or `MultiSelect`, do not utilize **Input**.\n\nKeep these points in mind when building your prompts.\n\n#### 0. Setting Up a Custom Prompt\n\nLet's use the implementation of a custom prompt similar to `Confirm` as an example.\n\n```rust\nuse promptuity::Prompt;\n\nstruct CustomConfirm {\n    message: String,\n    hint: Option\u003cString\u003e,\n    value: bool,\n}\n\nimpl Prompt for CustomConfirm {\n    type Output = bool;\n\n    // TODO\n}\n```\n\nDefine a struct with a message, hint, and value. Specify the final result type in `Output`.\n\nFirst, let's implement the reception of key inputs.\n\n#### 1. Receiving Key Input\n\nHandle key inputs in the [`Prompt::handle`](https://docs.rs/promptuity/latest/promptuity/trait.Prompt.html#tymethod.handle) method.\n\nFor example, let's implement it so that pressing \u003ckbd\u003ey\u003c/kbd\u003e for Yes and \u003ckbd\u003en\u003c/kbd\u003e for No finalizes the result.\n\n```rust\nuse promptuity::event::{KeyCode, KeyModifiers};\nuse promptuity::{Prompt, PromptState};\n\n// ...\n\nimpl Prompt for CustomConfirm {\n    // ...\n\n    fn handle(\u0026mut self, code: KeyCode, modifiers: KeyModifiers) -\u003e PromptState {\n        match (code, modifiers) {\n            (KeyCode::Enter, _) =\u003e PromptState::Submit,\n            (KeyCode::Esc, _) | (KeyCode::Char('c'), KeyModifiers::CONTROL) =\u003e PromptState::Cancel,\n            (KeyCode::Char('y'), KeyModifiers::NONE) | (KeyCode::Char('Y'), KeyModifiers::NONE) =\u003e {\n                self.value = true;\n                PromptState::Submit\n            }\n            (KeyCode::Char('n'), KeyModifiers::NONE) | (KeyCode::Char('N'), KeyModifiers::NONE) =\u003e {\n                self.value = false;\n                PromptState::Submit\n            }\n            _ =\u003e PromptState::Active,\n        }\n    }\n}\n```\n\nYou can freely combine key codes and modifiers, allowing the construction of complex prompts tailored to specific requirements.\n\n\u003e [!IMPORTANT]\n\u003e Commonly, prompts are interrupted with \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003eC\u003c/kbd\u003e, but Promptuity does not automatically handle this.  \n\u003e If the implementation is omitted, it results in a prompt that cannot be interrupted, leading to poor usability. Therefore, when building an original prompt, you must explicitly implement the interruption process yourself.\n\n#### 2. Rendering the Prompt\n\nConstruct the rendering content in the [`Prompt::render`](https://docs.rs/promptuity/latest/promptuity/trait.Prompt.html#tymethod.render) method. Here's a simple example using only **Input** without a **Body**.\n\n```rust\nuse promptuity::event::{KeyCode, KeyModifiers};\nuse promptuity::{Prompt, PromptState, RenderPayload};\n\n// ...\n\nimpl Prompt for CustomConfirm {\n    // ...\n\n    fn render(\u0026mut self, state: \u0026PromptState) -\u003e Result\u003cRenderPayload, String\u003e {\n        let payload = RenderPayload::new(self.message.clone(), self.hint.clone(), None);\n\n        match state {\n            PromptState::Submit =\u003e {\n                let raw = if self.value { \"Yes\" } else { \"No\" };\n                Ok(payload.input(PromptInput::Raw(raw.into())))\n            }\n\n            PromptState::Cancel =\u003e Ok(payload),\n\n            _ =\u003e Ok(payload.input(PromptInput::Raw(\"Y/n\"))),\n        }\n    }\n}\n```\n\nDetermine the appropriate rendering content based on the [`PromptState`](https://docs.rs/promptuity/latest/promptuity/enum.PromptState.html) returned by [`Prompt::handle`](https://docs.rs/promptuity/latest/promptuity/trait.Prompt.html#tymethod.handle). The above implementation achieves the following requirements:\n\n- The result displays either `Yes` or `No`.\n- If the prompt is interrupted, only the message is displayed.\n- During user input reception, it displays `Y/n`.\n\n#### 3. Returning Submission Results\n\nThis is the final step in constructing a custom prompt.\n\nImplement the [`Prompt::submit`](https://docs.rs/promptuity/latest/promptuity/trait.Prompt.html#tymethod.submit) method, which returns the final value for the received key input.\n\n```rust\nimpl Prompt for CustomConfirm {\n    // ...\n\n    fn submit(\u0026mut self) -\u003e Self::Output {\n        self.value\n    }\n}\n```\n\n`Prompt::submit` is a lifecycle method called immediately after `Prompt::handle` returns `PromptState::Submit`.\n\n---\n\nHandling key inputs and rendering based on input state form the foundation of prompt construction.\n\nFor building more complex prompts, [examples/autocomplete.rs](./examples/autocomplete.rs) should serve as a useful reference.\n\n### Build your own Theme\n\nJust like prompts, you can build an original Theme by implementing the `Theme` trait.\n\nFor a complete example, please refer to [examples/custom_theme.rs](./examples/custom_theme.rs).\n\n## Error Handling\n\nAll errors are consolidated into [`promptuity::Error`](https://docs.rs/promptuity/latest/promptuity/enum.Error.html).\n\nIn many cases, prompt interruptions will need to be handled individually. Interruptions occur during user input reception, typically through inputs like \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003eC\u003c/kbd\u003e or \u003ckbd\u003eESC\u003c/kbd\u003e.\n\n```rust\nuse promptuity::prompts::Input;\nuse promptuity::themes::MinimalTheme;\nuse promptuity::{Error, Promptuity, Term};\n\nfn ask() -\u003e Result\u003cString, Error\u003e {\n    let mut term = Term::default();\n    let mut theme = MinimalTheme::default();\n    let mut p = Promptuity::new(\u0026mut term, \u0026mut theme);\n\n    p.begin()?;\n    let name = p.prompt(Input::new(\"Please enter your username\").with_placeholder(\"username\"))?;\n    p.finish()?;\n\n    Ok(name)\n}\n\nfn main() {\n    match ask() {\n        Ok(name) =\u003e println!(\"Hello, {}!\", name),\n        Err(Error::Cancel) =\u003e {}\n        Err(e) =\u003e eprintln!(\"Error: {}\", e),\n    }\n}\n```\n\nPrompt interruptions can be handled as `Error::Cancel`. In the above examples, no message is displayed in the event of an interruption.\n\n## Testing\n\nGenerally, validations involving user input are costly. Since Promptuity implements terminal behaviors as the [`Terminal`](https://docs.rs/promptuity/latest/promptuity/trait.Terminal.html) trait, it's easy to replace with a Fake.\n\nThe `Terminal` that simulates key inputs, used in Promptuity's integration tests, can be referenced in [`Term`](./tests/fake_term.rs).\n\nBelow is an example of testing prompts using a Fake `Terminal`.\n\n```rust\n#[test]\nfn test_prompts() {\n    let mut term = fake_term::Term::new(\u0026[\n        (KeyCode::Char('a'), KeyModifiers::NONE),\n        (KeyCode::Char('b'), KeyModifiers::NONE),\n        (KeyCode::Char('c'), KeyModifiers::NONE),\n        (KeyCode::Enter, KeyModifiers::NONE),\n    ]);\n\n    let mut theme = MinimalTheme::default();\n\n    let result = {\n        let mut p = Promptuity::new(\u0026mut term, \u0026mut theme);\n        p.prompt(Input::new(\"Input Message\").as_mut()).unwrap()\n    };\n\n    let output = term.output();\n\n    assert_eq!(result, String::from(\"abc\"));\n\n    // This is an example of performing snapshots on outputs.\n    insta::with_settings!({ omit_expression =\u003e true }, {\n        insta::assert_snapshot!(output);\n    });\n}\n```\n\n## Alternatives\n\nThe Rust ecosystem contains many wonderful crates.\n\n- [console-rs/dialoguer](https://github.com/console-rs/dialoguer)\n- [axelvc/asky](https://github.com/axelvc/asky/)\n- [mikaelmello/inquire](https://github.com/mikaelmello/inquire)\n- [fadeevab/cliclack](https://github.com/fadeevab/cliclack)\n\n### Inspired\n\nPromptuity's various prompts and design have been greatly inspired by these projects. We are very grateful for their development.\n\n- [SBoudrias/Inquirer.js](https://github.com/SBoudrias/Inquirer.js)\n- [natemoo-re/clack](https://github.com/natemoo-re/clack)\n- [terkelg/prompts](https://github.com/terkelg/prompts)\n\n## Contributing\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md).\n\n## CHANGELOG\n\nSee [CHANGELOG.md](./CHANGELOG.md).\n\n## License\n\n[MIT © wadackel](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwadackel%2Fpromptuity","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwadackel%2Fpromptuity","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwadackel%2Fpromptuity/lists"}