From e4b5f8fe285be69055292c2a14ea37beba593036 Mon Sep 17 00:00:00 2001 From: Paul Valladares <85648028+dreyfus92@users.noreply.github.com> Date: Tue, 3 Jun 2025 10:24:32 -0600 Subject: [PATCH] Update core and prompts documentation and examples (#14) * update: core, prompts and examples * chore: update @clack/core and @clack/prompts dependencies; enhance best practices and examples documentation * fix: restore @clack/core@0.4.2 entry in pnpm-lock.yaml and update prompts documentation formatting * docs(prompts): Enhance groupMultiselect and spinner documentation; add taskLog and internationalization examples --------- Signed-off-by: Paul Valladares <85648028+dreyfus92@users.noreply.github.com> --- package.json | 4 +- pnpm-lock.yaml | 30 +- .../docs/clack/guides/best-practices.mdx | 162 ++++++- src/content/docs/clack/guides/examples.mdx | 445 ++++++++++++++++-- src/content/docs/clack/packages/core.mdx | 163 +++++-- src/content/docs/clack/packages/prompts.mdx | 335 ++++++++++--- 6 files changed, 979 insertions(+), 160 deletions(-) diff --git a/package.json b/package.json index 803608a..beaf734 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,9 @@ }, "dependencies": { "@astrojs/starlight": "^0.32.2", + "@clack/core": "^0.4.2", + "@clack/prompts": "1.0.0-alpha.0", "@bomb.sh/args": "^0.3.1", - "@clack/core": "^0.4.1", - "@clack/prompts": "^0.10.0", "@types/node": "^22.13.11", "astro": "^5.1.5", "expressive-code-twoslash": "^0.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d79f914..b9cfb86 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,11 +15,11 @@ importers: specifier: ^0.3.1 version: 0.3.1 '@clack/core': - specifier: ^0.4.1 - version: 0.4.1 + specifier: ^0.4.2 + version: 0.4.2 '@clack/prompts': - specifier: ^0.10.0 - version: 0.10.0 + specifier: 1.0.0-alpha.0 + version: 1.0.0-alpha.0 '@types/node': specifier: ^22.13.11 version: 22.13.11 @@ -93,11 +93,14 @@ packages: '@bomb.sh/args@0.3.1': resolution: {integrity: sha512-CwxKrfgcorUPP6KfYD59aRdBYWBTsfsxT+GmoLVnKo5Tmyoqbpo0UNcjngRMyU+6tiPbd18RuIYxhgAn44wU/Q==} - '@clack/core@0.4.1': - resolution: {integrity: sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA==} + '@clack/core@0.4.2': + resolution: {integrity: sha512-NYQfcEy8MWIxrT5Fj8nIVchfRFA26yYKJcvBS7WlUIlw2OmQOY9DhGGXMovyI5J5PpxrCPGkgUi207EBrjpBvg==} - '@clack/prompts@0.10.0': - resolution: {integrity: sha512-H3rCl6CwW1NdQt9rE3n373t7o5cthPv7yUoxF2ytZvyvlJv89C5RYMJu83Hed8ODgys5vpBU0GKxIRG83jd8NQ==} + '@clack/core@1.0.0-alpha.0': + resolution: {integrity: sha512-Cp/bPW/pMUCkJ7Lr8VFixvFrlnJ4tQPDHqfTNQ51z50qwX1fSIAstQLfel2NquVHqbfjyrUkBsal8OJRBPJVjw==} + + '@clack/prompts@1.0.0-alpha.0': + resolution: {integrity: sha512-Aem7r4U2A4jdOh0PIv51Ugi+4vDgzJjGVMnuPUNVVHDGhFHEO//u6F/JY6NsZQFtXrd7ZmfePSiipikr/e5wWg==} '@ctrl/tinycolor@4.1.0': resolution: {integrity: sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==} @@ -1899,14 +1902,19 @@ snapshots: '@bomb.sh/args@0.3.1': {} - '@clack/core@0.4.1': + '@clack/core@0.4.2': + dependencies: + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@clack/core@1.0.0-alpha.0': dependencies: picocolors: 1.1.1 sisteransi: 1.0.5 - '@clack/prompts@0.10.0': + '@clack/prompts@1.0.0-alpha.0': dependencies: - '@clack/core': 0.4.1 + '@clack/core': 1.0.0-alpha.0 picocolors: 1.1.1 sisteransi: 1.0.5 diff --git a/src/content/docs/clack/guides/best-practices.mdx b/src/content/docs/clack/guides/best-practices.mdx index 2c89a1f..3f2a649 100644 --- a/src/content/docs/clack/guides/best-practices.mdx +++ b/src/content/docs/clack/guides/best-practices.mdx @@ -122,37 +122,24 @@ if (projectType === 'web') { Provide sensible defaults when appropriate: ```ts twoslash -import { text } from '@clack/prompts'; - -const configPath = await text({ - message: 'Configuration file path:', - defaultValue: './config.json', - placeholder: 'Enter path to config file', -}); -``` - -### 6. Type Safety - -Leverage TypeScript for better type safety: - -```ts twoslash -// @errors: 2322 2339 +// @errors: 2353 import { text, select, isCancel } from '@clack/prompts'; -// Define validation functions outside +// Define types for better type safety +interface ProjectConfig { + name: string; + type: 'web' | 'cli'; + framework?: 'react' | 'vue'; + port?: number; +} + +// Define validation functions function validateProjectName(value: string): string | undefined { if (value.length === 0) return 'Name is required'; if (!/^[a-z0-9-]+$/.test(value)) return 'Name can only contain lowercase letters, numbers, and hyphens'; return undefined; } -// Define types for better type safety -interface ProjectConfig { - name: string | symbol; - type: 'web' | 'cli'; - framework?: string; -} - // Collect project configuration with type safety async function collectProjectConfig(): Promise { const name = await text({ @@ -160,6 +147,11 @@ async function collectProjectConfig(): Promise { validate: validateProjectName, }); + if (isCancel(name)) { + console.log('Operation cancelled'); + process.exit(0); + } + const type = await select({ message: 'Project type:', options: [ @@ -173,10 +165,29 @@ async function collectProjectConfig(): Promise { process.exit(0); } - return { + const config: ProjectConfig = { name, type, }; + + if (type === 'web') { + const framework = await select({ + message: 'Choose a framework:', + options: [ + { value: 'react', label: 'React' }, + { value: 'vue', label: 'Vue' }, + ], + }); + + if (isCancel(framework)) { + console.log('Operation cancelled'); + process.exit(0); + } + + config.framework = framework; + } + + return config; } ``` @@ -285,4 +296,107 @@ const serverPort = await text({ return undefined; }, }); +``` + +## Advanced Patterns + +### 1. Conditional Prompts + +Use conditional logic to show relevant prompts: + +```ts twoslash +// @errors: 2339 +import { text, select, isCancel } from '@clack/prompts'; + +interface DatabaseConfig { + type: 'postgres' | 'mysql' | 'sqlite'; + host?: string; + port?: number; +} + +async function setupDatabase() { + const dbType = await select({ + message: 'Select database type:', + options: [ + { value: 'postgres', label: 'PostgreSQL' }, + { value: 'mysql', label: 'MySQL' }, + { value: 'sqlite', label: 'SQLite' }, + ], + }); + + if (isCancel(dbType)) { + console.log('Operation cancelled'); + process.exit(0); + } + + const config: DatabaseConfig = { + type: dbType, + }; + + // Only ask for host and port if not SQLite + if (dbType !== 'sqlite') { + const host = await text({ + message: 'Database host:', + defaultValue: 'localhost', + }); + + if (isCancel(host)) { + console.log('Operation cancelled'); + process.exit(0); + } + + const port = await text({ + message: 'Database port:', + defaultValue: dbType === 'postgres' ? '5432' : '3306', + validate: (value) => { + const num = parseInt(value); + if (isNaN(num)) return 'Please enter a valid number'; + if (num < 1 || num > 65535) return 'Port must be between 1 and 65535'; + return undefined; + }, + }); + + if (isCancel(port)) { + console.log('Operation cancelled'); + process.exit(0); + } + + config.host = host; + config.port = parseInt(port); + } + + return config; +} +``` + +### 2. Reusable Prompt Functions + +Create reusable functions for common prompt patterns: + +```ts twoslash +import { text, select, isCancel } from '@clack/prompts'; + +async function promptForPort(defaultPort: string) { + const port = await text({ + message: 'Enter port number:', + defaultValue: defaultPort, + validate: (value) => { + const num = parseInt(value); + if (isNaN(num)) return 'Please enter a valid number'; + if (num < 1 || num > 65535) return 'Port must be between 1 and 65535'; + return undefined; + }, + }); + + if (isCancel(port)) { + console.log('Operation cancelled'); + process.exit(0); + } + + return parseInt(port); +} + +// Use the reusable function +const serverPort = await promptForPort('3000'); +const dbPort = await promptForPort('5432'); ``` \ No newline at end of file diff --git a/src/content/docs/clack/guides/examples.mdx b/src/content/docs/clack/guides/examples.mdx index 5c00394..2747ff5 100644 --- a/src/content/docs/clack/guides/examples.mdx +++ b/src/content/docs/clack/guides/examples.mdx @@ -35,9 +35,9 @@ import { select, isCancel } from '@clack/prompts'; const framework = await select({ message: 'Choose a framework:', options: [ - { value: 'react', label: 'React' }, - { value: 'vue', label: 'Vue' }, - { value: 'svelte', label: 'Svelte' }, + { value: 'react', label: 'React', hint: 'A JavaScript library for building user interfaces' }, + { value: 'vue', label: 'Vue', hint: 'The Progressive JavaScript Framework' }, + { value: 'svelte', label: 'Svelte', hint: 'Cybernetically enhanced web apps' }, ], }); @@ -49,15 +49,80 @@ if (isCancel(framework)) { console.log(`You selected ${framework}`); ``` +### Autocomplete Selection + +```ts twoslash +import { autocomplete, isCancel } from '@clack/prompts'; + +const selectedPackage = await autocomplete({ + message: 'Search for a package:', + options: [ + { value: 'react', label: 'React', hint: 'A JavaScript library for building user interfaces' }, + { value: 'vue', label: 'Vue', hint: 'The Progressive JavaScript Framework' }, + { value: 'svelte', label: 'Svelte', hint: 'Cybernetically enhanced web apps' }, + { value: 'angular', label: 'Angular', hint: 'Platform for building mobile & desktop web apps' }, + { value: 'next', label: 'Next.js', hint: 'The React Framework for Production' }, + ], + placeholder: 'Type to search...', + maxItems: 5, +}); + +if (isCancel(selectedPackage)) { + console.log('Operation cancelled'); + process.exit(0); +} + +console.log(`Selected package: ${selectedPackage}`); +``` + +### Multi-select with Groups + +```ts twoslash +import { groupMultiselect, isCancel } from '@clack/prompts'; + +const tools = await groupMultiselect({ + message: 'Select development tools:', + options: { + 'Frontend': [ + { value: 'typescript', label: 'TypeScript', hint: 'JavaScript with syntax for types' }, + { value: 'eslint', label: 'ESLint', hint: 'Find and fix problems in your JavaScript code' }, + { value: 'prettier', label: 'Prettier', hint: 'Code formatter' }, + ], + 'Backend': [ + { value: 'node', label: 'Node.js', hint: 'JavaScript runtime' }, + { value: 'express', label: 'Express', hint: 'Web framework for Node.js' }, + { value: 'prisma', label: 'Prisma', hint: 'Next-generation ORM' }, + ], + 'Testing': [ + { value: 'jest', label: 'Jest', hint: 'JavaScript testing framework' }, + { value: 'cypress', label: 'Cypress', hint: 'End-to-end testing framework' }, + { value: 'vitest', label: 'Vitest', hint: 'Vite-native testing framework' }, + ], + }, +}); + +if (isCancel(tools)) { + console.log('Operation cancelled'); + process.exit(0); +} + +console.log('Selected tools:', tools); +``` + ### Confirmation Dialog ```ts twoslash -import { confirm } from '@clack/prompts'; +import { confirm, isCancel } from '@clack/prompts'; const shouldProceed = await confirm({ message: 'Do you want to continue?', }); +if (isCancel(shouldProceed)) { + console.log('Operation cancelled'); + process.exit(0); +} + if (shouldProceed) { console.log('Proceeding...'); } else { @@ -70,7 +135,7 @@ if (shouldProceed) { ### Project Setup Wizard ```ts twoslash -import { text, select, confirm, isCancel } from '@clack/prompts'; +import { text, select, confirm, groupMultiselect, isCancel } from '@clack/prompts'; async function setupProject() { // Get project details @@ -78,6 +143,7 @@ async function setupProject() { message: 'Project name:', validate: (value) => { if (value.length === 0) return 'Name is required'; + if (!/^[a-z0-9-]+$/.test(value)) return 'Name can only contain lowercase letters, numbers, and hyphens'; return undefined; }, }); @@ -90,9 +156,9 @@ async function setupProject() { const type = await select({ message: 'Project type:', options: [ - { value: 'web', label: 'Web Application' }, - { value: 'cli', label: 'CLI Tool' }, - { value: 'api', label: 'API Server' }, + { value: 'web', label: 'Web Application', hint: 'Full-stack web application' }, + { value: 'cli', label: 'CLI Tool', hint: 'Command-line interface tool' }, + { value: 'api', label: 'API Server', hint: 'REST/GraphQL API server' }, ], }); @@ -107,9 +173,9 @@ async function setupProject() { framework = await select({ message: 'Choose a framework:', options: [ - { value: 'react', label: 'React' }, - { value: 'vue', label: 'Vue' }, - { value: 'svelte', label: 'Svelte' }, + { value: 'next', label: 'Next.js', hint: 'React framework for production' }, + { value: 'nuxt', label: 'Nuxt', hint: 'Vue framework for production' }, + { value: 'sveltekit', label: 'SvelteKit', hint: 'Svelte framework for production' }, ], }); @@ -119,11 +185,41 @@ async function setupProject() { } } + // Select features + const features = await groupMultiselect({ + message: 'Select features:', + options: { + 'Development': [ + { value: 'typescript', label: 'TypeScript', hint: 'Type safety' }, + { value: 'eslint', label: 'ESLint', hint: 'Code linting' }, + { value: 'prettier', label: 'Prettier', hint: 'Code formatting' }, + ], + 'Testing': [ + { value: 'jest', label: 'Jest', hint: 'Unit testing' }, + { value: 'cypress', label: 'Cypress', hint: 'E2E testing' }, + ], + 'Deployment': [ + { value: 'docker', label: 'Docker', hint: 'Containerization' }, + { value: 'ci', label: 'CI/CD', hint: 'Continuous integration' }, + ], + }, + }); + + if (isCancel(features)) { + console.log('Operation cancelled'); + process.exit(0); + } + // Confirm setup const shouldProceed = await confirm({ message: `Create ${type} project "${name}"${framework ? ` with ${framework}` : ''}?`, }); + if (isCancel(shouldProceed)) { + console.log('Operation cancelled'); + process.exit(0); + } + if (shouldProceed) { // Project creation logic here console.log('Creating project...'); @@ -131,22 +227,26 @@ async function setupProject() { } ``` -### Configuration Setup +### Configuration Setup with Validation ```ts twoslash -import { text, select, confirm, isCancel } from '@clack/prompts'; +import { text, select, groupMultiselect, isCancel } from '@clack/prompts'; interface Config { port: number; - host: string | symbol; + host: string; mode: 'development' | 'production'; features: string[]; + database: { + type: 'postgres' | 'mysql' | 'mongodb'; + url: string; + }; } -async function setupConfig(): Promise { +async function setupConfig(): Promise { const port = await text({ message: 'Enter port number:', - defaultValue: '3000', + placeholder: '3000', validate: (value) => { const num = parseInt(value); if (isNaN(num)) return 'Please enter a valid number'; @@ -158,68 +258,126 @@ async function setupConfig(): Promise { if (isCancel(port)) { console.log('Operation cancelled'); process.exit(0); + return null; } const host = await text({ message: 'Enter host:', - defaultValue: 'localhost', + placeholder: 'localhost', + validate: (value) => { + if (!value) return 'Host is required'; + if (!/^[a-zA-Z0-9.-]+$/.test(value)) return 'Invalid host format'; + return undefined; + }, }); if (isCancel(host)) { console.log('Operation cancelled'); process.exit(0); + return null; } const mode = await select({ message: 'Select mode:', options: [ - { value: 'development', label: 'Development' }, - { value: 'production', label: 'Production' }, + { value: 'development', label: 'Development', hint: 'For local development' }, + { value: 'production', label: 'Production', hint: 'For production deployment' }, ], }); if (isCancel(mode)) { console.log('Operation cancelled'); process.exit(0); + return null; } - const features = await select({ - message: 'Select features:', + const database = await select({ + message: 'Select database:', options: [ - { value: 'auth', label: 'Authentication' }, - { value: 'logging', label: 'Logging' }, - { value: 'metrics', label: 'Metrics' }, + { value: 'postgres', label: 'PostgreSQL', hint: 'Advanced open source database' }, + { value: 'mysql', label: 'MySQL', hint: 'Most popular open source database' }, + { value: 'mongodb', label: 'MongoDB', hint: 'Document-oriented database' }, ], }); + if (isCancel(database)) { + console.log('Operation cancelled'); + process.exit(0); + return null; + } + + const dbUrl = await text({ + message: 'Enter database URL:', + placeholder: `postgresql://user:pass@localhost:5432/db`, + validate: (value) => { + if (!value) return 'Database URL is required'; + try { + new URL(value); + return undefined; + } catch { + return 'Invalid URL format'; + } + }, + }); + + if (isCancel(dbUrl)) { + console.log('Operation cancelled'); + process.exit(0); + return null; + } + + const features = await groupMultiselect({ + message: 'Select features:', + options: { + 'Security': [ + { value: 'auth', label: 'Authentication', hint: 'User authentication' }, + { value: 'cors', label: 'CORS', hint: 'Cross-origin resource sharing' }, + ], + 'Monitoring': [ + { value: 'logging', label: 'Logging', hint: 'Application logging' }, + { value: 'metrics', label: 'Metrics', hint: 'Performance metrics' }, + ], + 'Development': [ + { value: 'swagger', label: 'Swagger', hint: 'API documentation' }, + { value: 'debug', label: 'Debug', hint: 'Debugging tools' }, + ], + }, + }); + if (isCancel(features)) { console.log('Operation cancelled'); process.exit(0); + return null; } return { port: Number(port), host, mode: mode as 'development' | 'production', - features: [features], + features, + database: { + type: database as 'postgres' | 'mysql' | 'mongodb', + url: dbUrl, + }, }; } ``` -### Interactive CLI Tool +### Interactive CLI Tool with Tasks ```ts twoslash -import { text, select, confirm as confirmPrompt, isCancel } from '@clack/prompts'; +import { text, select, confirm, tasks, spinner, isCancel } from '@clack/prompts'; async function cliTool() { while (true) { const action = await select({ message: 'What would you like to do?', options: [ - { value: 'create', label: 'Create new item' }, - { value: 'list', label: 'List items' }, - { value: 'delete', label: 'Delete item' }, - { value: 'exit', label: 'Exit' }, + { value: 'create', label: 'Create new item', hint: 'Create a new resource' }, + { value: 'list', label: 'List items', hint: 'View all resources' }, + { value: 'delete', label: 'Delete item', hint: 'Remove a resource' }, + { value: 'update', label: 'Update item', hint: 'Modify a resource' }, + { value: 'exit', label: 'Exit', hint: 'Close the application' }, ], }); @@ -234,23 +392,65 @@ async function cliTool() { case 'create': { const name = await text({ message: 'Enter item name:', + validate: (value) => { + if (!value) return 'Name is required'; + return undefined; + }, }); if (isCancel(name)) { console.log('Operation cancelled'); break; } + + const spin = spinner(); + spin.start('Creating item...'); - console.log(`Created item: ${name}`); + await tasks([ + { + title: 'Validating input', + task: async () => { + // Validation logic + return 'Input validated'; + }, + }, + { + title: 'Creating resource', + task: async () => { + // Creation logic + return 'Resource created'; + }, + }, + { + title: 'Setting up permissions', + task: async () => { + // Permission setup + return 'Permissions configured'; + }, + }, + ]); + + spin.stop('Item created successfully'); break; } case 'list': { + const spin = spinner(); + spin.start('Fetching items...'); + + // Simulate API call + await new Promise(resolve => setTimeout(resolve, 1000)); + + spin.stop('Items fetched successfully'); console.log('Listing items...'); break; } case 'delete': { const item = await text({ message: 'Enter item to delete:', + validate: (value) => { + if (!value) return 'Item name is required'; + return undefined; + }, }); if (isCancel(item)) { @@ -258,31 +458,112 @@ async function cliTool() { break; } - const shouldDelete = await confirmPrompt({ - message: `Delete ${item}?`, + const shouldDelete = await confirm({ + message: `Are you sure you want to delete ${item}?`, }); + if (isCancel(shouldDelete)) { + console.log('Operation cancelled'); + break; + } + if (shouldDelete) { - console.log(`Deleted ${item}`); + const spin = spinner(); + spin.start('Deleting item...'); + + await tasks([ + { + title: 'Checking dependencies', + task: async () => { + // Check dependencies + return 'Dependencies checked'; + }, + }, + { + title: 'Removing item', + task: async () => { + // Delete logic + return 'Item removed'; + }, + }, + { + title: 'Cleaning up', + task: async () => { + // Cleanup logic + return 'Cleanup completed'; + }, + }, + ]); + + spin.stop('Item deleted successfully'); } break; } + case 'update': { + const item = await text({ + message: 'Enter item to update:', + validate: (value) => { + if (!value) return 'Item name is required'; + return undefined; + }, + }); + + if (isCancel(item)) { + console.log('Operation cancelled'); + break; + } + + const spin = spinner(); + spin.start('Updating item...'); + + await tasks([ + { + title: 'Fetching current state', + task: async () => { + // Fetch current state + return 'Current state fetched'; + }, + }, + { + title: 'Applying updates', + task: async () => { + // Update logic + return 'Updates applied'; + }, + }, + { + title: 'Verifying changes', + task: async () => { + // Verification logic + return 'Changes verified'; + }, + }, + ]); + + spin.stop('Item updated successfully'); + break; + } } } } ``` -### Form Validation +### Form Validation with Complex Types ```ts twoslash -// @errors: 2731 2322 2345 -import { text, select, isCancel } from '@clack/prompts'; +import { text, select, groupMultiselect, isCancel } from '@clack/prompts'; interface UserData { name: string; email: string; age: number; role: string; + skills: string[]; + preferences: { + theme: 'light' | 'dark' | 'system'; + notifications: boolean; + language: string; + }; } async function collectUserData(): Promise { @@ -335,9 +616,9 @@ async function collectUserData(): Promise { const role = await select({ message: 'Select role:', options: [ - { value: 'admin', label: 'Administrator' }, - { value: 'user', label: 'User' }, - { value: 'guest', label: 'Guest' }, + { value: 'admin', label: 'Administrator', hint: 'Full system access' }, + { value: 'user', label: 'User', hint: 'Standard user access' }, + { value: 'guest', label: 'Guest', hint: 'Limited access' }, ], }); @@ -347,11 +628,91 @@ async function collectUserData(): Promise { return null; } + const skills = await groupMultiselect({ + message: 'Select skills:', + options: { + 'Frontend': [ + { value: 'react', label: 'React', hint: 'UI library' }, + { value: 'vue', label: 'Vue', hint: 'Progressive framework' }, + { value: 'angular', label: 'Angular', hint: 'Platform' }, + ], + 'Backend': [ + { value: 'node', label: 'Node.js', hint: 'Runtime' }, + { value: 'python', label: 'Python', hint: 'Language' }, + { value: 'java', label: 'Java', hint: 'Language' }, + ], + 'Database': [ + { value: 'sql', label: 'SQL', hint: 'Query language' }, + { value: 'mongodb', label: 'MongoDB', hint: 'NoSQL database' }, + { value: 'redis', label: 'Redis', hint: 'Cache' }, + ], + }, + }); + + if (isCancel(skills)) { + console.log('Operation cancelled'); + process.exit(0); + return null; + } + + const theme = await select({ + message: 'Select theme:', + options: [ + { value: 'light', label: 'Light', hint: 'Light mode' }, + { value: 'dark', label: 'Dark', hint: 'Dark mode' }, + { value: 'system', label: 'System', hint: 'Follow system preference' }, + ], + }); + + if (isCancel(theme)) { + console.log('Operation cancelled'); + process.exit(0); + return null; + } + + const notifications = await select({ + message: 'Enable notifications?', + options: [ + { value: true, label: 'Yes', hint: 'Receive notifications' }, + { value: false, label: 'No', hint: 'No notifications' }, + ], + }); + + if (isCancel(notifications)) { + console.log('Operation cancelled'); + process.exit(0); + return null; + } + + const language = await select({ + message: 'Select language:', + options: [ + { value: 'en', label: 'English', hint: 'English' }, + { value: 'es', label: 'Spanish', hint: 'Español' }, + { value: 'fr', label: 'French', hint: 'Français' }, + { value: 'de', label: 'German', hint: 'Deutsch' }, + ], + }); + + if (isCancel(language)) { + console.log('Operation cancelled'); + process.exit(0); + return null; + } + return { name, email, age: parseInt(ageInput), role, + skills, + preferences: { + theme: theme as 'light' | 'dark' | 'system', + notifications: notifications as boolean, + language, + }, }; } -``` \ No newline at end of file +``` + +For more examples and best practices, check out our [GitHub repository](https://github.com/bombshell-dev/clack/tree/main/examples/basic). \ No newline at end of file diff --git a/src/content/docs/clack/packages/core.mdx b/src/content/docs/clack/packages/core.mdx index 1206972..e82477e 100644 --- a/src/content/docs/clack/packages/core.mdx +++ b/src/content/docs/clack/packages/core.mdx @@ -12,6 +12,45 @@ The `@clack/core` package provides the fundamental building blocks for creating - **Type-safe**: Built with TypeScript for better developer experience - **Flexible rendering**: Customizable rendering system for prompts - **Cancel handling**: Built-in support for handling user cancellation +- **Event system**: Comprehensive event handling for user interactions +- **Input validation**: Built-in support for input validation +- **Abort signal support**: Integration with AbortController for cancellation + +## Installation + +To start using the core package, first install it: + + + + ```bash + npm install @clack/core + ``` + + + ```bash + pnpm add @clack/core + ``` + + + ```bash + yarn add @clack/core + ``` + + + +Then import the components you need: + +```ts twoslash +import { + TextPrompt, + SelectPrompt, + ConfirmPrompt, + PasswordPrompt, + MultiSelectPrompt, + GroupMultiSelectPrompt, + SelectKeyPrompt +} from '@clack/core'; +``` ## Package Structure @@ -30,39 +69,109 @@ The `Prompt` class serves as the foundation for all prompt types. It provides: - Input handling - State management - Event system +- Validation support +- Abort signal integration -### Available Prompts +#### Key Methods -1. **TextPrompt**: For text input -2. **SelectPrompt**: For selection from options -3. **ConfirmPrompt**: For yes/no confirmations +```ts twoslash -## Getting Started +interface Prompt { + // Event handling + on(event: T, cb: (value: any) => void): void; + once(event: T, cb: (value: any) => void): void; + emit(event: T, ...data: any[]): void; -To start using the core package, first install it: + // Core functionality + prompt(): Promise; + close(): void; +} +``` - - - ```bash - npm install @clack/core - ``` - - - ```bash - pnpm add @clack/core - ``` - - - ```bash - yarn add @clack/core - ``` - - +#### Available Events -Then import the components you need: +The `Prompt` interface provides several events that can be handled: + +```ts twoslash +// @errors: 2304 7006 2353 2345 2459 +import { Prompt } from '@clack/core'; + +// Example usage +const p = new Prompt({ + render: () => 'Enter your name:' +}); + +// Handle value changes +p.on('value', (value?: string) => { + console.log('Value changed:', value); +}); + +// Handle submission +p.on('submit', (value?: string) => { + console.log('Submitted:', value); +}); + +// Handle cancellation +p.on('cancel', () => { + console.log('Operation cancelled'); +}); +``` -```ts -import { TextPrompt, SelectPrompt, ConfirmPrompt } from '@clack/core'; +### Available Prompts + +1. **TextPrompt**: For text input + - Supports validation + - Placeholder text + - Initial value + +2. **SelectPrompt**: For selection from options + - Multiple selection support + - Custom rendering + - Option filtering + +3. **ConfirmPrompt**: For yes/no confirmations + - Yes/No shortcuts + - Custom messages + +4. **AutocompletePrompt**: For searchable selection + - Type-ahead filtering + - Custom filtering logic + - Multiple selection support + +5. **PasswordPrompt**: For secure input + - Character masking + - Validation support + +6. **MultiSelectPrompt**: For multiple selections + - Checkbox interface + - Selection limits + - Custom rendering + +7. **GroupMultiSelectPrompt**: For grouped selections + - Hierarchical options + - Group selection + - Custom rendering + +8. **SelectKeyPrompt**: For key-based selection + - Custom key bindings + - Multiple selection support + +## Creating Custom Prompts + +You can create custom prompts by extending the base `Prompt` class: + +```ts twoslash +import { Prompt } from '@clack/core'; + +// Example of extending the base Prompt class +class CustomPrompt extends Prompt { + constructor(options: { message: string }) { + super({ + ...options, + render: () => `${options.message}\n${this.value || ''}` + }); + } +} ``` -For detailed examples and usage patterns, check out our [examples guide](/docs/guides/examples). +For detailed examples and usage patterns, check out our [examples guide](/docs/guides/examples). \ No newline at end of file diff --git a/src/content/docs/clack/packages/prompts.mdx b/src/content/docs/clack/packages/prompts.mdx index 5731f33..3593c85 100644 --- a/src/content/docs/clack/packages/prompts.mdx +++ b/src/content/docs/clack/packages/prompts.mdx @@ -2,8 +2,8 @@ title: Prompts description: Learn about the prompts package and its capabilities --- - import { Aside } from "@astrojs/starlight/components" +import { Tabs, TabItem } from '@astrojs/starlight/components'; The `@clack/prompts` package provides a collection of pre-built, high-level prompts that make it easy to create interactive command-line interfaces. It builds on top of the core package to provide a more developer-friendly experience. @@ -14,9 +14,37 @@ The `@clack/prompts` package provides a collection of pre-built, high-level prom - **Type-safe**: Full TypeScript support - **Customizable**: Easy to extend and modify +## Installation + + + + + ```bash + npm install @clack/prompts + ``` + + + ```bash + pnpm add @clack/prompts + ``` + + + ```bash + yarn add @clack/prompts + ``` + + + +## Usage + +The prompts package is designed to be intuitive and easy to use. Each prompt function returns a Promise that resolves to the user's input. + +For more detailed examples and advanced usage patterns, check out our [examples guide](/docs/guides/examples) and [best practices](/docs/guides/best-practices). + ## Available Prompts ### Text Input + ```ts twoslash // @errors: 2339 import { text } from '@clack/prompts'; @@ -37,6 +65,7 @@ const name = await text({ └ ### Password Input + ```ts twoslash import { password } from '@clack/prompts'; @@ -68,18 +97,19 @@ import { select } from '@clack/prompts'; const framework = await select({ message: 'Pick a framework', options: [ - { value: 'next', label: 'Next.js' }, - { value: 'astro', label: 'Astro' }, - { value: 'svelte', label: 'SvelteKit' }, + { value: 'next', label: 'Next.js', hint: 'React framework' }, + { value: 'astro', label: 'Astro', hint: 'Content-focused' }, + { value: 'svelte', label: 'SvelteKit', hint: 'Compile-time framework' }, ], + maxItems: 5, // Maximum number of items to display at once }); ```
│
 ◆  Pick a framework
-│  ● Next.js
-│  ○ Astro
-│  ○ SvelteKit
+│  ● Next.js (React framework)
+│  ○ Astro (Content-focused)
+│  ○ SvelteKit (Compile-time framework)
 └
#### Complex value @@ -90,19 +120,19 @@ import { select } from '@clack/prompts'; const framework = await select({ message: 'Pick a framework', options: [ - { value: { framework: 'Next', language: 'React' }, label: 'Next.js' }, - { value: { framework: null, language: 'Astro' }, label: 'Astro' }, - { value: { framework: 'Sveltekit', language: 'Svelte' }, label: 'SvelteKit' }, + { value: { framework: 'Next', language: 'React' }, label: 'Next.js', hint: 'React framework' }, + { value: { framework: null, language: 'Astro' }, label: 'Astro', hint: 'Content-focused' }, + { value: { framework: 'Sveltekit', language: 'Svelte' }, label: 'SvelteKit', hint: 'Compile-time framework' }, ], }); ```
│
-◆  Pick a framework
-│  ● Next.js
-│  ○ Astro
-│  ○ SvelteKit
-└
+│ Pick a framework +│ ● Next.js (React framework) +│ ○ Astro (Content-focused) +│ ○ SvelteKit (Compile-time framework) +└ #### Multiple values @@ -112,18 +142,77 @@ import { multiselect } from '@clack/prompts'; const framework = await multiselect({ message: 'Pick a framework', options: [ - { value: { framework: 'Next', language: 'React' }, label: 'Next.js' }, - { value: { framework: null, language: 'Astro' }, label: 'Astro' }, - { value: { framework: 'Sveltekit', language: 'Svelte' }, label: 'SvelteKit' }, + { value: { framework: 'Next', language: 'React' }, label: 'Next.js', hint: 'React framework' }, + { value: { framework: null, language: 'Astro' }, label: 'Astro', hint: 'Content-focused' }, + { value: { framework: 'Sveltekit', language: 'Svelte' }, label: 'SvelteKit', hint: 'Compile-time framework' }, ], + maxItems: 5, // Maximum number of items to display at once }); ```
│
 ◆  Pick a framework
-│  ◼ Next.js
-│  ◻ Astro
-│  ◻ SvelteKit
+│  ◼ Next.js (React framework)
+│  ◻ Astro (Content-focused)
+│  ◻ SvelteKit (Compile-time framework)
+└
+ +### Autocomplete + +The `autocomplete` prompt combines text input with a searchable list of options. It's perfect for when you have a large list of options and want to help users find what they're looking for quickly. + +```ts twoslash +import { autocomplete } from '@clack/prompts'; + +const framework = await autocomplete({ + message: 'Search for a framework', + options: [ + { value: 'next', label: 'Next.js', hint: 'React framework' }, + { value: 'astro', label: 'Astro', hint: 'Content-focused' }, + { value: 'svelte', label: 'SvelteKit', hint: 'Compile-time framework' }, + { value: 'remix', label: 'Remix', hint: 'Full stack framework' }, + { value: 'nuxt', label: 'Nuxt', hint: 'Vue framework' }, + ], + placeholder: 'Type to search...', + maxItems: 5, // Maximum number of items to display at once +}); +``` + +
│
+◆  Search for a framework
+│  Search: n
+│  (2 matches)
+│  ● Next.js (React framework)
+│  ○ Nuxt (Vue framework)
+└
+ +### Autocomplete Multiselect + +The `autocompleteMultiselect` combines the search functionality of autocomplete with the ability to select multiple options. + +```ts twoslash +import { autocompleteMultiselect } from '@clack/prompts'; + +const frameworks = await autocompleteMultiselect({ + message: 'Select frameworks', + options: [ + { value: 'next', label: 'Next.js', hint: 'React framework' }, + { value: 'astro', label: 'Astro', hint: 'Content-focused' }, + { value: 'svelte', label: 'SvelteKit', hint: 'Compile-time framework' }, + { value: 'remix', label: 'Remix', hint: 'Full stack framework' }, + { value: 'nuxt', label: 'Nuxt', hint: 'Vue framework' }, + ], + placeholder: 'Type to search...', + maxItems: 5, // Maximum number of items to display at once +}); +``` + +
│
+◆  Select frameworks
+│  Search: n
+│  (2 matches)
+│  ◼ Next.js (React framework)
+│  ◻ Nuxt (Vue framework)
 └
### Confirmation @@ -146,51 +235,63 @@ const shouldProceed = await confirm({ ### Group Multiselect ```ts twoslash +// @errors: 2353 import { groupMultiselect } from '@clack/prompts'; const projectOptions = await groupMultiselect({ message: 'Define your project', options: { 'Testing': [ - { value: 'Jest' }, - { value: 'Playwright' }, - { value: 'Vitest' }, + { value: 'Jest', hint: 'JavaScript testing framework' }, + { value: 'Playwright', hint: 'End-to-end testing' }, + { value: 'Vitest', hint: 'Vite-native testing' }, ], 'Language': [{ label: "Javascript", value: 'js', + hint: 'Dynamic typing' }, { label: 'TypeScript', value: 'ts', + hint: 'Static typing' }, { label: "CoffeeScript", value: 'coffee', + hint: 'JavaScript with Ruby-like syntax' }], 'Code quality': [ - { value: 'Prettier' }, - { value: 'ESLint' }, - { value: 'Biome.js' }, + { value: 'Prettier', hint: 'Code formatter' }, + { value: 'ESLint', hint: 'Linter' }, + { value: 'Biome.js', hint: 'Formatter and linter' }, ], }, + groupSpacing: 1, // Add one new line between each group + selectableGroups: false, // Disable selection of top-level groups }); ```
│
 ◆  Define your project
 │  ◼ Testing
-│  │ ◼ Jest
-│  │ ◼ Playwright
-│  └ ◼ Vitest
+│  │ ◼ Jest (JavaScript testing framework)
+│  │ ◼ Playwright (End-to-end testing)
+│  └ ◼ Vitest (Vite-native testing)
+│
 │  ◻ Language
-│  │ ◼ Javascript
-│  │ ◻ TypeScript
-│  └ ◻ CoffeeScript
+│  │ ◼ Javascript (Dynamic typing)
+│  │ ◻ TypeScript (Static typing)
+│  └ ◻ CoffeeScript (JavaScript with Ruby-like syntax)
+│
 │  ◻ Code quality
-│  │ ◻ Prettier
-│  │ ◻ ESLint
-│  └ ◼ Biome.js
+│  │ ◻ Prettier (Code formatter)
+│  │ ◻ ESLint (Linter)
+│  └ ◼ Biome.js (Formatter and linter)
 └
+The `groupMultiselect` prompt supports two additional options: +- `groupSpacing`: An integer that specifies how many new lines to add between each group. This helps improve readability when you have many groups. +- `selectableGroups`: A boolean that determines whether top-level groups can be selected. When set to `false`, only individual items within groups can be selected. + ### Group The `group` function provides a convenient API for combining a series of questions. @@ -322,6 +423,9 @@ cancel('Installation canceled');   ### Spinner + +The `spinner` function provides a loading indicator for long-running operations. + ```ts twoslash import { spinner } from '@clack/prompts'; @@ -332,14 +436,75 @@ spin.message('Finishing'); // Do more things spin.stop('Done'); ``` +
│
 ◒  Loading{`...`}
+You can customize the spinner's messages either per instance or globally: + +```ts twoslash +import { spinner, updateSettings } from '@clack/prompts'; + +// Global customization +updateSettings({ + messages: { + cancel: "Operation cancelled", + error: "An error occurred", + }, +}); + +// Per-instance customization +const spin = spinner({ + cancelMessage: "Process cancelled", + errorMessage: "Process failed", +}); + +spin.start('Loading'); +// Do something +spin.stop('Done'); +``` + The second parameter of `spinner.stop` (`code`) allow you to indicate the ending status of the spinner: - `0` (or no value) indicate a success and the symbol for a finished task will be used - `1` indicate a cancellation and the red square symbol will be used - Any other code indicate an error and the yellow triangle symbol will be used +You can also use custom frames for the spinner: + +```ts twoslash +import { spinner } from '@clack/prompts'; + +const spin = spinner({ + frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] +}); +spin.start('Loading'); +// Do something +spin.stop('Done'); +``` + +### Progress + +The `progress` prompt displays a progress bar for long-running operations. + +```ts twoslash +// @errors: 2339 +import { progress } from '@clack/prompts'; + +const prog = progress(); +prog.start('Processing files'); +// Update progress +prog.message('Halfway there'); +// Update again +prog.message('Complete'); +prog.stop('All files processed'); +``` + +
│
+◆  Processing files
+│  [████████████████████████████████████] 100%
+│  Complete
+◆  All files processed
+ ### Note The `note` function renders a box around a message to draw a user's attention. @@ -361,22 +526,55 @@ note( │ │ ├───────────────────────────────────────╯ -The second parameter (the title) is optional +The second parameter (the title) is optional. You can also provide a format function to customize how each line is displayed: + ```ts twoslash +// @errors: 2345 7006 import { note } from '@clack/prompts'; note( - 'All files have been created.', + 'Line 1\nLine 2\nLine 3', + 'Formatted steps', + { + format: (line: string) => `→ ${line}` + } ); ```
│
+◇  Formatted steps
 ◇   ─────────────────────────────╮
 │                                │
-│  All files have been created.  │
+│  → Line 1                      │
+│  → Line 2                      │
+│  → Line 3                      │
 │                                │
 ├────────────────────────────────╯
+### Task Log + +The `taskLog` prompt provides a way to display log output that is cleared on success. This is useful for showing progress or status updates that should be removed once the task is complete. + +```ts twoslash +// @errors: 2345 +import { taskLog } from '@clack/prompts'; + +const log = taskLog({ + title: 'Installing dependencies' +}); +log.message('Fetching package information...'); +// Do some work +log.message('Installing packages...'); +// Do more work +log.success('Installation complete'); +``` + +
│
+◆  Installing dependencies
+│  Fetching package information...
+│  Installing packages...
+◆  Installation complete
+ ### Logs The `log` utilities allow you to add semantic contextual information during an interaction. @@ -417,6 +615,47 @@ log.step('Check files'); │ ◇ Check files +### Internationalization + +The prompts package supports internationalization through the `updateSettings` function. You can customize the messages used by various prompts to match your preferred language. + +```ts twoslash +// @errors: 2353 +import { updateSettings, select, cancel } from '@clack/prompts'; + +// Update global messages +updateSettings({ + messages: { + cancel: "Operación cancelada", + error: "Se ha producido un error" + } +}); + +// Use the select prompt with translated content +const framework = await select({ + message: 'Selecciona un framework', + options: [ + { value: 'next', label: 'Next.js', hint: 'Framework de React' }, + { value: 'astro', label: 'Astro', hint: 'Enfocado en contenido' }, + { value: 'svelte', label: 'SvelteKit', hint: 'Framework de compilación' }, + ] +}); + +// If the user cancels, they'll see the translated message +if (!framework) { + cancel(); +} +``` + +
│
+◆  Selecciona un framework
+│  ● Next.js (Framework de React)
+│  ○ Astro (Enfocado en contenido)
+│  ○ SvelteKit (Framework de compilación)
+└
+└  Operación cancelada
+ 
+ ### Stream The `stream` utilities allow you, like the `log` utilities, to add semantic contextual information during an interaction, @@ -454,10 +693,10 @@ await stream.step([
│
 │
 │  ⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣶⣶⣿⣿⣿⣿⣿⣿⣶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
-│  ⠀⠀⠀⠀⠀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+│  ⠀⠀⠀⠀⠀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 │  ⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 │  ⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
-│  ⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀
+│  ⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀
 │  ⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⣿⣿⣿⡿⠀⠀⠀⠀⣀⠀
 │  ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⢠⣿⣿⣶⣤⣄⣻⣿⣿⣇⣠⣴⣶⣿⣿⡀
 │  ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⢾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡧
@@ -465,7 +704,7 @@ await stream.step([
 │  ⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⠟⢻⣿⣿⣿⣦⠀⠀⠀
 │  ⠀⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠐⠿⣿⣿⣿⠏⠀⠀⢻⣿⣿⣿⠷⠀⠀
 │  ⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠏⠀⠀⠀⠀⠹⠋⠁⠀⠀⠀
-│  ⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+│  ⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 │  ⠀⠀⠀⠀⠀⠙⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 │  ⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⠿⢿⣿⣿⣿⣿⡿⠿⠟⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 │
@@ -476,15 +715,3 @@ await stream.step([
 ◇  Job1{`...`} done
 │  Job2{`...`} done
 │  Job3{`...`} done
- -## Installation - -```bash -npm install @clack/prompts -``` - -## Usage - -The prompts package is designed to be intuitive and easy to use. Each prompt function returns a Promise that resolves to the user's input. - -For more detailed examples and advanced usage patterns, check out our [examples guide](/docs/guides/examples) and [best practices](/docs/guides/best-practices). -- 2.51.2