{"id":22222009,"url":"https://github.com/fabiospampinato/prask","last_synced_at":"2025-07-27T16:32:14.740Z","repository":{"id":187501227,"uuid":"677071748","full_name":"fabiospampinato/prask","owner":"fabiospampinato","description":"Lightweight prompting library for terminal apps.","archived":false,"fork":false,"pushed_at":"2024-03-22T19:29:30.000Z","size":9931,"stargazers_count":34,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-11T20:27:34.737Z","etag":null,"topics":["ask","input","prompt","question","select"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/fabiospampinato.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"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},"funding":{"github":"fabiospampinato","custom":"https://www.paypal.me/fabiospampinato"}},"created_at":"2023-08-10T17:05:51.000Z","updated_at":"2024-06-25T13:32:30.000Z","dependencies_parsed_at":null,"dependency_job_id":"2ab2c086-8662-4c7f-abb5-82c5c181409f","html_url":"https://github.com/fabiospampinato/prask","commit_stats":null,"previous_names":["fabiospampinato/prask"],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fabiospampinato%2Fprask","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fabiospampinato%2Fprask/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fabiospampinato%2Fprask/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fabiospampinato%2Fprask/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fabiospampinato","download_url":"https://codeload.github.com/fabiospampinato/prask/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227817017,"owners_count":17824200,"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","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","input","prompt","question","select"],"created_at":"2024-12-02T23:16:31.852Z","updated_at":"2024-12-02T23:16:32.515Z","avatar_url":"https://github.com/fabiospampinato.png","language":"TypeScript","funding_links":["https://github.com/sponsors/fabiospampinato","https://www.paypal.me/fabiospampinato"],"categories":[],"sub_categories":[],"readme":"# Prask\n\nLightweight prompting library for terminal apps.\n\n## Install\n\n```sh\nnpm install --save prask\n```\n\n## Prompts\n\n| Input                   | Selection                   | Others              |\n| ----------------------- | --------------------------- | ------------------- |\n| [string](#string)       | [multiselect](#multiselect) | [log](#log)         |\n| [invisible](#invisible) | [select](#select)           | [prompt](#prompt)   |\n| [password](#password)   | [boolean](#boolean)         | [spinner](#spinner) |\n| [number](#number)       | [toggle](#toggle)           |                     |\n|                         | [rating](#rating)           |                     |\n\n### `string`\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./resources/demo_string.gif\" alt=\"Demo\" width=\"662\"\u003e\n\u003c/p\u003e\n\nThis prompt can be used to ask the user for a string value.\n\n```ts\nimport {string} from 'prask';\nimport color from 'tiny-colors';\n\nconst result = await string ({\n  /* REQUIRED OPTIONS */\n  message: 'What is your name?', // The message that the user will read\n  /* OPTIONAL OPTIONS */\n  initial: 'John Doe', // Optional default value, to allow the user to quickly press Enter for it\n  required: true, // Whether a non-empty value for this prompt is required or not\n  format: ( value, settled ) =\u003e value.length \u003e= 2 ? colors.green ( value ) : colors.red ( value ), // Optional formatting function, the visual length of the output string must remain the same\n  validate: value =\u003e value.length \u003e= 2 // Optional validation function\n});\n```\n\nInteractions:\n\n| Trigger | Description                                                                      |\n| ------- | -------------------------------------------------------------------------------- |\n| `Esc`   | Quit the prompt, which will resolve to `undefined`.                              |\n| `Enter` | Submit the value, or the initial value if visible, checking if it's valid first. |\n| `Tab`   | Edit the initial value, if visible.                                              |\n\n### `invisible`\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./resources/demo_invisible.gif\" alt=\"Demo\" width=\"662\"\u003e\n\u003c/p\u003e\n\nThis prompt can be used to ask the user for a password, without visually leaking even the length of the password to the console.\n\n```ts\nimport {invisible} from 'prask';\n\nconst result = await invisible ({\n  /* REQUIRED OPTIONS */\n  message: 'What is your password?', // The message that the user will read\n  /* OPTIONAL OPTIONS */\n  initial: 'P@assword!', // Optional default value, to allow the user to quickly press Enter for it, better not to use it since it will be visible\n  required: true, // Whether a non-empty value for this prompt is required or not\n  validate: value =\u003e value.length \u003e= 8 // Optional validation function\n});\n```\n\nIt can be interacted with exactly like a `string` prompt, the only difference is that the value is invisible.\n\n### `password`\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./resources/demo_password.gif\" alt=\"Demo\" width=\"662\"\u003e\n\u003c/p\u003e\n\nThis prompt can be used to ask the user for a password, without visually leaking the characters that make it up to the console.\n\n```ts\nimport {password} from 'prask';\n\nconst result = await password ({\n  /* REQUIRED OPTIONS */\n  message: 'What is your password?', // The message that the user will read\n  /* OPTIONAL OPTIONS */\n  initial: 'P@assword!', // Optional default value, to allow the user to quickly press Enter for it, better not to use it since it will be visible\n  required: true, // Whether a non-empty value for this prompt is required or not\n  validate: value =\u003e value.length \u003e= 8 // Optional validation function\n});\n```\n\nIt can be interacted with exactly like a `string` prompt, the only difference is that every character in the value will be replaced with an asterisk.\n\n### `number`\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./resources/demo_number.gif\" alt=\"Demo\" width=\"662\"\u003e\n\u003c/p\u003e\n\nThis prompt can be used to ask the user for a numeric value.\n\n```ts\nimport {number} from 'prask';\nimport colors from 'tiny-colors';\n\nconst result = await number ({\n  /* REQUIRED OPTIONS */\n  message: 'What is your favorite even number?', // The message that the user will read\n  /* OPTIONAL OPTIONS */\n  initial: 42, // Optional default value, to allow the user to quickly press Enter for it\n  format: ( value, settled ) =\u003e ( value % 2 ) ? colors.red ( value ) : colors.green ( value ), // Optional formatting function, the visual length of the output string must remain the same\n  validate: value =\u003e ( value % 2 ) ? 'The number must be even' : true // Optional validation function\n});\n```\n\nIt can be interacted with exactly like a `string` prompt, the main difference is that the value of the input is also automatically validated to be a number, plus the following extra interactions are supported:\n\n| Trigger       | Description                       |\n| ------------- | --------------------------------- |\n| `Up`/`Right`  | Increment the current value by 1. |\n| `Down`/`Left` | Decrement the current value by 1. |\n\n### `multiselect`\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./resources/demo_multiselect.gif\" alt=\"Demo\" width=\"662\"\u003e\n\u003c/p\u003e\n\nThis prompt can be used to ask the user to pick from zero to many options between the provided ones.\n\n```ts\nimport {multiselect} from 'prask';\n\nconst result = await multiselect ({\n  /* REQUIRED OPTIONS */\n  message: 'Which countries would you like to visit?', // The message that the user will read\n  options: [\n    { title: 'Italy', value: 'it' },\n    { title: 'Turkey', value: 'tr' },\n    { title: 'United Kingdom', value: 'uk' }\n  ],\n  /* OPTIONAL OPTIONS */\n  limit: 10, // Limit to this number the maximum number of options visible at one time\n  min: 1, // Require at least this number of options to be selected\n  max: 10, // Require at most this number of options to be selected\n  searchable: false, // Turn off support for filtering the list of options\n  validate: values =\u003e ( values.length % 2 ) // Optional validation function\n});\n```\n\nEach option must have the following shape:\n\n```ts\ntype Option\u003cT\u003e = {\n  /* REQUIRED VALUES */\n  title: string, // The name of the option that will be showed to the user\n  value: T, // The value of the option that the prompt will return you when this option is selected\n  /* OPTIONAL VALUES */\n  disabled?: boolean, // Whether this option can be toggled or not\n  description?: string, // The description that will be showed next to the title\n  heading?: boolean, // Whether this option is an unselectable heading or not\n  hint?: string, // The description that will be showed next to the title, only when the item is focused\n  selected?: boolean // Whether this option is pre-selected or not\n};\n```\n\nOptions can be provided dynamically also, with a function that returns an array of options depending on the current query. That can also be used to customize how results are searched and ranked.\n\nInteractions:\n\n| Trigger | Description                                                                            |\n| ------- | -------------------------------------------------------------------------------------- |\n| `Esc`   | Quit the prompt, which will resolve to `undefined`.                                    |\n| `Enter` | Submit the selected values, checking if the selection is valid first.                  |\n| `Space` | Toggle the focused option.                                                             |\n| `Up`    | Move the focus to the previous item, and potentially scroll to the previous page.      |\n| `Down`  | Move the focus to the next item, and potentially scroll to the next page.              |\n| `*`     | All other keys will be used to edit the search query, which will filter down the list. |\n\n### `select`\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./resources/demo_select.gif\" alt=\"Demo\" width=\"662\"\u003e\n\u003c/p\u003e\n\nThis prompt can be used to ask the user to pick one option between the provided ones.\n\n```ts\nimport {select} from 'prask';\n\nconst result = await select ({\n  /* REQUIRED OPTIONS */\n  message: 'Which country would you like to visit first?', // The message that the user will read\n  options: [\n    { title: 'Italy', value: 'it' },\n    { title: 'Turkey', value: 'tr' },\n    { title: 'United Kingdom', value: 'uk' }\n  ],\n  /* OPTIONAL OPTIONS */\n  limit: 10, // Limit to this number the maximum number of options visible at one time\n  searchable: false, // Turn off support for filtering the list of options\n  validate: value =\u003e value !== 'Foo' // Optional validation function\n});\n```\n\nIt can be interacted with exactly like a `multiselect` prompt.\n\n### `boolean`\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./resources/demo_boolean.gif\" alt=\"Demo\" width=\"662\"\u003e\n\u003c/p\u003e\n\nThis prompt can be used to ask the user to pick between yes and no.\n\n```ts\nimport {boolean} from 'prask';\n\nconst result = await boolean ({\n  /* REQUIRED OPTIONS */\n  message: 'Do you like this library?', // The message that the user will read\n  /* OPTIONAL OPTIONS */\n  initial: true // Select this option by default\n});\n```\n\nIt can be interacted with exactly like a `select` prompt, except that search is turned off for it.\n\n### `toggle`\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./resources/demo_toggle.gif\" alt=\"Demo\" width=\"662\"\u003e\n\u003c/p\u003e\n\nThis prompt can be used to ask the user to pick between yes and no, using a single line of output, rather than the three that `boolean` needs.\n\n```ts\nimport {toggle} from 'prask';\n\nconst result = await toggle ({\n  /* REQUIRED OPTIONS */\n  message: 'Do you like this library?', // The message that the user will read\n  /* OPTIONAL OPTIONS */\n  initial: true // Select this option by default\n});\n```\n\nInteractions:\n\n| Trigger                          | Description                                         |\n| -------------------------------- | --------------------------------------------------- |\n| `Esc`                            | Quit the prompt, which will resolve to `undefined`. |\n| `Enter`                          | Submit the selected option.                         |\n| `Left`/`Right`/`Up`/`Down`/`Tab` | Select the other option.                            |\n\n### `rating`\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./resources/demo_rating.gif\" alt=\"Demo\" width=\"662\"\u003e\n\u003c/p\u003e\n\nThis prompt can be used to ask the user for a numeric rating between 1 and 5.\n\n```ts\nimport {rating} from 'prask';\n\nconst result = await rating ({\n  /* REQUIRED OPTIONS */\n  message: 'How would you rate this library?', // The message that the user will read\n  /* OPTIONAL OPTIONS */\n  initial: 5 // Select this option by default\n});\n```\n\nInteractions:\n\n| Trigger       | Description                                         |\n| ------------- | --------------------------------------------------- |\n| `Esc`         | Quit the prompt, which will resolve to `undefined`. |\n| `Enter`       | Submit the selected rating.                         |\n| `Right`/`Up`  | Increment the selected rating.                      |\n| `Left`/`Down` | Decrement the selected rating.                      |\n\n### `log`\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./resources/demo_log.gif\" alt=\"Demo\" width=\"662\"\u003e\n\u003c/p\u003e\n\nThis utility provides functions for logging a line of text to the console, with the same style that the built-in prompts use.\n\n```ts\nimport {log} from 'prask';\n\nlog.success ( 'Success message' );\nlog.error ( 'Error message' );\nlog.warning ( 'Warning message' );\nlog.question ( 'Question message' );\nlog.info ( 'Info message' );\n```\n\n### `prompt`\n\nThis is a low-level general prompt, which is used internally to implement the other higher-level prompts, and which you can use to implement your own custom prompts.\n\n```ts\nimport {prompt} from 'prask';\nimport color from 'tiny-colors';\n\n// Let's define the \"rating\" prompt, it returns a number between 1 and 5, which is the rating the user picked\n// This is the actual internal implementation for that component, so you can see exactly how it works\n\ntype Rating = 1 | 2 | 3 | 4 | 5;\n\ntype Options = {\n  message: string,\n  initial?: Rating\n};\n\n// A prompt could always resolve to \"undefined\", if the user escapes from it\nconst rating = ( options: Options ): Promise\u003cRating | undefined\u003e =\u003e {\n\n  // Let's define some internal constants we need for rendering\n\n  const STAR_ACTIVE = color.green ( '●' );\n  const STAR_INACTIVE = '○';\n  const STARS_DIVIDER = '─────';\n  const LABELS_DIVIDER = '     ';\n\n  // Let's define some internal state variables\n  // The status variable let's us keep track of whether this prompt was escaped (-1), submitted (1), or it's still pending (0)\n\n  let {message, initial = 3} = options;\n  let status: -1 | 0 | 1 = 0;\n  let current = initial;\n\n  // Let's define some components\n  // Basically each prompt re-renders its components in a loop, React-style\n  // Each prompt must return something to render, which can be a single component or an array of components\n  // A component is just a function that can either return a string, which will be printed to the console, or undefined, which will be ignored\n  // The \"prompt\" function takes care of cleaning up the previous output and replacing it with the new one\n  // That's basically how the entire library works, the \"prompt\" function tells you when a key is pressed, and you just tell it what to render next\n\n  const getStatusSymbol = ( status ): string =\u003e {\n    if ( status \u003c 0 ) return color.red ( '✖' );\n    if ( status \u003e 0 ) return color.green ( '✔' );\n    return color.cyan.bold ( '?' );\n  };\n\n  const question = (): string =\u003e {\n    const question = `${getStatusSymbol ()} ${color.bold ( message )}`;\n    const result = status === 1 ? color.cyan ( String ( current ) ) : '';\n    return [question, result].join ( ' ' );\n  };\n\n  const stars = (): string =\u003e {\n    const star1 = ( current === 1 ) ? STAR_ACTIVE : STAR_INACTIVE;\n    const star2 = ( current === 2 ) ? STAR_ACTIVE : STAR_INACTIVE;\n    const star3 = ( current === 3 ) ? STAR_ACTIVE : STAR_INACTIVE;\n    const star4 = ( current === 4 ) ? STAR_ACTIVE : STAR_INACTIVE;\n    const star5 = ( current === 5 ) ? STAR_ACTIVE : STAR_INACTIVE;\n    const stars = [star1, star2, star3, star4, star5].join ( STARS_DIVIDER );\n    return stars;\n  };\n\n  const labels = (): string =\u003e {\n    return [1, 2, 3, 4, 5].join ( LABELS_DIVIDER );\n  };\n\n  // Now let's handle keypresses\n\n  return prompt ( ( resolve, {key} ) =\u003e {\n    if ( key === '' ) { // Initial render, no key has been pressed yet\n      return [question, stars, labels];\n    } else if ( key === 'escape' ) { // Escape, let's bail out\n      status = -1;\n      resolve ( undefined );\n      return [question];\n    } else if ( key === 'return' ) { // Enter, let's return the current rating\n      status = 1;\n      resolve ( current );\n      return [question];\n    } else if ( key === 'left' || key === 'down' ) { // Left or Down, let's decrement the rating, if possible\n      current = Math.max ( 1, current - 1 );\n      return [question, stars, labels];\n    } else if ( key === 'right' || key === 'up' ) { // Right or Up, let's increment the rating, if possible\n      current = Math.min ( 5, current + 1 );\n      return [question, stars, labels];\n    } else { // Something else was pressed, let's just ignore it\n      return [question, stars, labels];\n    }\n  });\n\n};\n```\n\n### `spinner`\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./resources/demo_spinner.gif\" alt=\"Demo\" width=\"662\"\u003e\n\u003c/p\u003e\n\nThe spinner prompt is a convenience wrapper around [`tiny-spinner`](https://github.com/fabiospampinato/tiny-spinner), which you could also use directly if you prefer.\n\nIt allows to notify the user of in progress operations, and can resolve to a successful or an unsuccessful state.\n\n```ts\nimport {spinner} from 'prompts';\n\nconst result = await spinner ( async ({ update, resolve, reject }) =\u003e {\n  update ( 'Working' );\n  await delay ( 750 );\n  update ( 'Working hard' );\n  await delay ( 750 );\n  update ( 'Working harder' );\n  await delay ( 750 );\n  update ( 'Working real hard' );\n  await delay ( 750 );\n  let result = true; // Let's say this is the result of the operation\n  if ( result ) {\n    resolve ( 'Work succeeded' );\n  } else {\n    reject ( 'Work failed' );\n  }\n});\n```\n\n## License\n\nMIT © Fabio Spampinato\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffabiospampinato%2Fprask","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffabiospampinato%2Fprask","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffabiospampinato%2Fprask/lists"}