Initial commit
This commit is contained in:
318
skills/inquirer-patterns/README.md
Normal file
318
skills/inquirer-patterns/README.md
Normal file
@@ -0,0 +1,318 @@
|
||||
# Inquirer Patterns Skill
|
||||
|
||||
Comprehensive interactive prompt patterns for building CLI tools with rich user input capabilities.
|
||||
|
||||
## Overview
|
||||
|
||||
This skill provides templates, examples, and utilities for implementing interactive CLI prompts in both **Node.js** (using `inquirer`) and **Python** (using `questionary`). It covers all major prompt types with validation, conditional logic, and real-world examples.
|
||||
|
||||
## Prompt Types Covered
|
||||
|
||||
### 1. Text Input
|
||||
- Simple string input
|
||||
- Email, URL, and path validation
|
||||
- Numeric input with range validation
|
||||
- Multi-line text
|
||||
|
||||
### 2. List Selection
|
||||
- Single choice from options
|
||||
- Categorized options with separators
|
||||
- Options with descriptions and shortcuts
|
||||
- Dynamic choices based on context
|
||||
|
||||
### 3. Checkbox
|
||||
- Multiple selections
|
||||
- Pre-selected defaults
|
||||
- Grouped options
|
||||
- Validation (min/max selections)
|
||||
|
||||
### 4. Password
|
||||
- Hidden input with mask characters
|
||||
- Password confirmation
|
||||
- Strength validation
|
||||
- API key and token input
|
||||
|
||||
### 5. Autocomplete
|
||||
- Type-ahead search
|
||||
- Fuzzy matching
|
||||
- Large option lists
|
||||
- Dynamic filtering
|
||||
|
||||
### 6. Conditional Questions
|
||||
- Skip logic based on answers
|
||||
- Dynamic question flow
|
||||
- Branching paths
|
||||
- Context-dependent validation
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
inquirer-patterns/
|
||||
├── SKILL.md # Main skill documentation
|
||||
├── README.md # This file
|
||||
├── templates/
|
||||
│ ├── nodejs/
|
||||
│ │ ├── text-prompt.js # Text input examples
|
||||
│ │ ├── list-prompt.js # List selection examples
|
||||
│ │ ├── checkbox-prompt.js # Checkbox examples
|
||||
│ │ ├── password-prompt.js # Password/secure input
|
||||
│ │ ├── autocomplete-prompt.js # Autocomplete examples
|
||||
│ │ ├── conditional-prompt.js # Conditional logic
|
||||
│ │ └── comprehensive-example.js # Complete wizard
|
||||
│ └── python/
|
||||
│ ├── text_prompt.py
|
||||
│ ├── list_prompt.py
|
||||
│ ├── checkbox_prompt.py
|
||||
│ ├── password_prompt.py
|
||||
│ ├── autocomplete_prompt.py
|
||||
│ └── conditional_prompt.py
|
||||
├── scripts/
|
||||
│ ├── install-nodejs-deps.sh # Install Node.js packages
|
||||
│ ├── install-python-deps.sh # Install Python packages
|
||||
│ ├── validate-prompts.sh # Validate skill structure
|
||||
│ └── generate-prompt.sh # Generate boilerplate code
|
||||
└── examples/
|
||||
├── nodejs/
|
||||
│ └── project-init-wizard.js # Full project setup wizard
|
||||
└── python/
|
||||
└── project_init_wizard.py # Full project setup wizard
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Node.js
|
||||
|
||||
1. **Install dependencies:**
|
||||
```bash
|
||||
./scripts/install-nodejs-deps.sh
|
||||
```
|
||||
|
||||
2. **Run an example:**
|
||||
```bash
|
||||
node templates/nodejs/text-prompt.js
|
||||
node templates/nodejs/comprehensive-example.js
|
||||
```
|
||||
|
||||
3. **Generate boilerplate:**
|
||||
```bash
|
||||
./scripts/generate-prompt.sh --type checkbox --lang js --output my-prompt.js
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
1. **Install dependencies:**
|
||||
```bash
|
||||
./scripts/install-python-deps.sh
|
||||
```
|
||||
|
||||
2. **Run an example:**
|
||||
```bash
|
||||
python3 templates/python/text_prompt.py
|
||||
python3 templates/python/conditional_prompt.py
|
||||
```
|
||||
|
||||
3. **Generate boilerplate:**
|
||||
```bash
|
||||
./scripts/generate-prompt.sh --type list --lang py --output my_prompt.py
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### Validation Patterns
|
||||
|
||||
All templates include comprehensive validation examples:
|
||||
- **Required fields**: Ensure input is not empty
|
||||
- **Format validation**: Email, URL, regex patterns
|
||||
- **Range validation**: Numeric min/max values
|
||||
- **Custom validation**: Business logic rules
|
||||
- **Cross-field validation**: Compare multiple answers
|
||||
|
||||
### Error Handling
|
||||
|
||||
Examples demonstrate proper error handling:
|
||||
- Graceful Ctrl+C handling
|
||||
- TTY detection for non-interactive environments
|
||||
- User-friendly error messages
|
||||
- Validation feedback
|
||||
|
||||
### Best Practices
|
||||
|
||||
Templates follow CLI best practices:
|
||||
- Clear, descriptive prompts
|
||||
- Sensible defaults
|
||||
- Keyboard shortcuts
|
||||
- Progressive disclosure
|
||||
- Accessibility considerations
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Text Input with Validation
|
||||
|
||||
**Node.js:**
|
||||
```javascript
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
const answer = await inquirer.prompt([{
|
||||
type: 'input',
|
||||
name: 'email',
|
||||
message: 'Enter your email:',
|
||||
validate: (input) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(input) || 'Invalid email address';
|
||||
}
|
||||
}]);
|
||||
```
|
||||
|
||||
**Python:**
|
||||
```python
|
||||
import questionary
|
||||
|
||||
email = questionary.text(
|
||||
"Enter your email:",
|
||||
validate=lambda text: bool(re.match(r'^[^\s@]+@[^\s@]+\.[^\s@]+$', text))
|
||||
or "Invalid email address"
|
||||
).ask()
|
||||
```
|
||||
|
||||
### Conditional Questions
|
||||
|
||||
**Node.js:**
|
||||
```javascript
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useDatabase',
|
||||
message: 'Use database?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'dbType',
|
||||
message: 'Database type:',
|
||||
choices: ['PostgreSQL', 'MySQL', 'MongoDB'],
|
||||
when: (answers) => answers.useDatabase
|
||||
}
|
||||
]);
|
||||
```
|
||||
|
||||
**Python:**
|
||||
```python
|
||||
use_database = questionary.confirm("Use database?", default=True).ask()
|
||||
|
||||
if use_database:
|
||||
db_type = questionary.select(
|
||||
"Database type:",
|
||||
choices=['PostgreSQL', 'MySQL', 'MongoDB']
|
||||
).ask()
|
||||
```
|
||||
|
||||
### Multiple Selections
|
||||
|
||||
**Node.js:**
|
||||
```javascript
|
||||
const answers = await inquirer.prompt([{
|
||||
type: 'checkbox',
|
||||
name: 'features',
|
||||
message: 'Select features:',
|
||||
choices: ['Auth', 'Database', 'API Docs', 'Testing'],
|
||||
validate: (choices) => choices.length > 0 || 'Select at least one'
|
||||
}]);
|
||||
```
|
||||
|
||||
**Python:**
|
||||
```python
|
||||
features = questionary.checkbox(
|
||||
"Select features:",
|
||||
choices=['Auth', 'Database', 'API Docs', 'Testing'],
|
||||
validate=lambda c: len(c) > 0 or "Select at least one"
|
||||
).ask()
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
Run the validation script to check skill structure:
|
||||
|
||||
```bash
|
||||
./scripts/validate-prompts.sh
|
||||
```
|
||||
|
||||
This checks:
|
||||
- ✅ SKILL.md structure and frontmatter
|
||||
- ✅ Required templates exist
|
||||
- ✅ Scripts are executable
|
||||
- ✅ No hardcoded secrets
|
||||
- ✅ Basic syntax validation
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Node.js
|
||||
- `inquirer@^9.0.0` - Core prompting library
|
||||
- `inquirer-autocomplete-prompt@^3.0.0` - Autocomplete support
|
||||
- `chalk@^5.0.0` - Terminal colors (optional)
|
||||
|
||||
### Python
|
||||
- `questionary>=2.0.0` - Core prompting library
|
||||
- `prompt_toolkit>=3.0.0` - Terminal UI toolkit
|
||||
- `colorama` - Windows color support (optional)
|
||||
|
||||
## Real-World Use Cases
|
||||
|
||||
### Project Initialization
|
||||
See `examples/nodejs/project-init-wizard.js` and `examples/python/project_init_wizard.py` for complete project setup wizards.
|
||||
|
||||
### Configuration Management
|
||||
Templates show how to build interactive config generators for:
|
||||
- Database connections
|
||||
- API credentials
|
||||
- Deployment settings
|
||||
- Feature flags
|
||||
- CI/CD pipelines
|
||||
|
||||
### Interactive Installers
|
||||
Examples demonstrate building user-friendly installers with:
|
||||
- Dependency selection
|
||||
- Environment setup
|
||||
- Credential collection
|
||||
- Validation and verification
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Node.js: "Error [ERR_REQUIRE_ESM]"
|
||||
**Solution**: Use `import` instead of `require`, or add `"type": "module"` to package.json
|
||||
|
||||
### Python: "No module named 'questionary'"
|
||||
**Solution**: Run `./scripts/install-python-deps.sh` or `pip install questionary`
|
||||
|
||||
### Autocomplete not working
|
||||
**Solution**: Install the autocomplete plugin:
|
||||
- Node.js: `npm install inquirer-autocomplete-prompt`
|
||||
- Python: Built into questionary
|
||||
|
||||
### Terminal rendering issues
|
||||
**Solution**: Ensure terminal supports ANSI escape codes. On Windows, install `colorama`.
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding new patterns:
|
||||
1. Add template to both `templates/nodejs/` and `templates/python/`
|
||||
2. Include comprehensive validation examples
|
||||
3. Add real-world usage examples
|
||||
4. Update this README
|
||||
5. Run validation: `./scripts/validate-prompts.sh`
|
||||
|
||||
## License
|
||||
|
||||
Part of the CLI Builder plugin.
|
||||
|
||||
## Resources
|
||||
|
||||
- **Inquirer.js**: https://github.com/SBoudrias/Inquirer.js
|
||||
- **Questionary**: https://github.com/tmbo/questionary
|
||||
- **Prompt Toolkit**: https://github.com/prompt-toolkit/python-prompt-toolkit
|
||||
- **CLI Best Practices**: https://clig.dev/
|
||||
|
||||
---
|
||||
|
||||
**Created**: 2025
|
||||
**Maintained by**: CLI Builder Plugin
|
||||
**Skill Version**: 1.0.0
|
||||
453
skills/inquirer-patterns/SKILL.md
Normal file
453
skills/inquirer-patterns/SKILL.md
Normal file
@@ -0,0 +1,453 @@
|
||||
---
|
||||
name: inquirer-patterns
|
||||
description: Interactive prompt patterns for CLI tools with text, list, checkbox, password, autocomplete, and conditional questions. Use when building CLIs with user input, creating interactive prompts, implementing questionnaires, or when user mentions inquirer, prompts, interactive input, CLI questions, user prompts.
|
||||
allowed-tools: Read, Write, Bash
|
||||
---
|
||||
|
||||
# Inquirer Patterns
|
||||
|
||||
Comprehensive interactive prompt patterns for building CLI tools with rich user input capabilities. Provides templates for text, list, checkbox, password, autocomplete, and conditional questions in both Node.js and Python.
|
||||
|
||||
## Instructions
|
||||
|
||||
### When Building Interactive CLI Prompts
|
||||
|
||||
1. **Identify prompt type needed:**
|
||||
- Text input: Simple string input
|
||||
- List selection: Single choice from options
|
||||
- Checkbox: Multiple selections
|
||||
- Password: Secure input (hidden)
|
||||
- Autocomplete: Type-ahead suggestions
|
||||
- Conditional: Questions based on previous answers
|
||||
|
||||
2. **Choose language:**
|
||||
- **Node.js**: Use templates in `templates/nodejs/`
|
||||
- **Python**: Use templates in `templates/python/`
|
||||
|
||||
3. **Select appropriate template:**
|
||||
- `text-prompt.js/py` - Basic text input
|
||||
- `list-prompt.js/py` - Single selection list
|
||||
- `checkbox-prompt.js/py` - Multiple selections
|
||||
- `password-prompt.js/py` - Secure password input
|
||||
- `autocomplete-prompt.js/py` - Type-ahead suggestions
|
||||
- `conditional-prompt.js/py` - Dynamic questions based on answers
|
||||
- `comprehensive-example.js/py` - All patterns combined
|
||||
|
||||
4. **Install required dependencies:**
|
||||
- **Node.js**: Run `scripts/install-nodejs-deps.sh`
|
||||
- **Python**: Run `scripts/install-python-deps.sh`
|
||||
|
||||
5. **Test prompts:**
|
||||
- Use examples in `examples/nodejs/` or `examples/python/`
|
||||
- Run validation script: `scripts/validate-prompts.sh`
|
||||
|
||||
6. **Customize for your CLI:**
|
||||
- Copy relevant template sections
|
||||
- Modify questions, choices, validation
|
||||
- Add custom conditional logic
|
||||
|
||||
### Node.js Implementation
|
||||
|
||||
**Library**: `inquirer` (v9.x)
|
||||
|
||||
**Installation**:
|
||||
```bash
|
||||
npm install inquirer
|
||||
```
|
||||
|
||||
**Basic Usage**:
|
||||
```javascript
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'username',
|
||||
message: 'Enter your username:',
|
||||
validate: (input) => input.length > 0 || 'Username required'
|
||||
}
|
||||
]);
|
||||
|
||||
console.log(`Hello, ${answers.username}!`);
|
||||
```
|
||||
|
||||
### Python Implementation
|
||||
|
||||
**Library**: `questionary` (v2.x)
|
||||
|
||||
**Installation**:
|
||||
```bash
|
||||
pip install questionary
|
||||
```
|
||||
|
||||
**Basic Usage**:
|
||||
```python
|
||||
import questionary
|
||||
|
||||
username = questionary.text(
|
||||
"Enter your username:",
|
||||
validate=lambda text: len(text) > 0 or "Username required"
|
||||
).ask()
|
||||
|
||||
print(f"Hello, {username}!")
|
||||
```
|
||||
|
||||
## Available Templates
|
||||
|
||||
### Node.js Templates (`templates/nodejs/`)
|
||||
|
||||
1. **text-prompt.js** - Text input with validation
|
||||
2. **list-prompt.js** - Single selection from list
|
||||
3. **checkbox-prompt.js** - Multiple selections
|
||||
4. **password-prompt.js** - Secure password input with confirmation
|
||||
5. **autocomplete-prompt.js** - Type-ahead with fuzzy search
|
||||
6. **conditional-prompt.js** - Dynamic questions based on answers
|
||||
7. **comprehensive-example.js** - Complete CLI questionnaire
|
||||
|
||||
### Python Templates (`templates/python/`)
|
||||
|
||||
1. **text_prompt.py** - Text input with validation
|
||||
2. **list_prompt.py** - Single selection from list
|
||||
3. **checkbox_prompt.py** - Multiple selections
|
||||
4. **password_prompt.py** - Secure password input with confirmation
|
||||
5. **autocomplete_prompt.py** - Type-ahead with fuzzy search
|
||||
6. **conditional_prompt.py** - Dynamic questions based on answers
|
||||
7. **comprehensive_example.py** - Complete CLI questionnaire
|
||||
|
||||
## Prompt Types Reference
|
||||
|
||||
### Text Input
|
||||
- **Use for**: Names, emails, URLs, paths, free-form text
|
||||
- **Features**: Validation, default values, transform
|
||||
- **Node.js**: `{ type: 'input' }`
|
||||
- **Python**: `questionary.text()`
|
||||
|
||||
### List Selection
|
||||
- **Use for**: Single choice from predefined options
|
||||
- **Features**: Arrow key navigation, search filtering
|
||||
- **Node.js**: `{ type: 'list' }`
|
||||
- **Python**: `questionary.select()`
|
||||
|
||||
### Checkbox
|
||||
- **Use for**: Multiple selections from options
|
||||
- **Features**: Space to toggle, Enter to confirm
|
||||
- **Node.js**: `{ type: 'checkbox' }`
|
||||
- **Python**: `questionary.checkbox()`
|
||||
|
||||
### Password
|
||||
- **Use for**: Sensitive input (credentials, tokens)
|
||||
- **Features**: Hidden input, confirmation, validation
|
||||
- **Node.js**: `{ type: 'password' }`
|
||||
- **Python**: `questionary.password()`
|
||||
|
||||
### Autocomplete
|
||||
- **Use for**: Large option lists with search
|
||||
- **Features**: Type-ahead, fuzzy matching, suggestions
|
||||
- **Node.js**: `inquirer-autocomplete-prompt` plugin
|
||||
- **Python**: `questionary.autocomplete()`
|
||||
|
||||
### Conditional Questions
|
||||
- **Use for**: Dynamic forms based on previous answers
|
||||
- **Features**: Skip logic, dependent questions, branching
|
||||
- **Node.js**: `when` property in question config
|
||||
- **Python**: Conditional logic with if statements
|
||||
|
||||
## Validation Patterns
|
||||
|
||||
### Email Validation
|
||||
```javascript
|
||||
// Node.js
|
||||
validate: (input) => {
|
||||
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return regex.test(input) || 'Invalid email address';
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
def validate_email(text):
|
||||
import re
|
||||
regex = r'^[^\s@]+@[^\s@]+\.[^\s@]+$'
|
||||
return bool(re.match(regex, text)) or "Invalid email address"
|
||||
|
||||
questionary.text("Email:", validate=validate_email).ask()
|
||||
```
|
||||
|
||||
### Non-Empty Validation
|
||||
```javascript
|
||||
// Node.js
|
||||
validate: (input) => input.length > 0 || 'This field is required'
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
questionary.text("Name:", validate=lambda t: len(t) > 0 or "Required").ask()
|
||||
```
|
||||
|
||||
### Numeric Range Validation
|
||||
```javascript
|
||||
// Node.js
|
||||
validate: (input) => {
|
||||
const num = parseInt(input);
|
||||
return (num >= 1 && num <= 100) || 'Enter number between 1-100';
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
def validate_range(text):
|
||||
try:
|
||||
num = int(text)
|
||||
return 1 <= num <= 100 or "Enter number between 1-100"
|
||||
except ValueError:
|
||||
return "Invalid number"
|
||||
|
||||
questionary.text("Number:", validate=validate_range).ask()
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Project Initialization Wizard
|
||||
|
||||
**Use case**: Interactive CLI for scaffolding new projects
|
||||
|
||||
```javascript
|
||||
// Node.js - See examples/nodejs/project-init.js
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'projectName',
|
||||
message: 'Project name:',
|
||||
validate: (input) => /^[a-z0-9-]+$/.test(input) || 'Invalid project name'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'framework',
|
||||
message: 'Choose framework:',
|
||||
choices: ['React', 'Vue', 'Angular', 'Svelte']
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'features',
|
||||
message: 'Select features:',
|
||||
choices: ['TypeScript', 'ESLint', 'Prettier', 'Testing']
|
||||
}
|
||||
]);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python - See examples/python/project_init.py
|
||||
import questionary
|
||||
|
||||
project_name = questionary.text(
|
||||
"Project name:",
|
||||
validate=lambda t: bool(re.match(r'^[a-z0-9-]+$', t)) or "Invalid name"
|
||||
).ask()
|
||||
|
||||
framework = questionary.select(
|
||||
"Choose framework:",
|
||||
choices=['React', 'Vue', 'Angular', 'Svelte']
|
||||
).ask()
|
||||
|
||||
features = questionary.checkbox(
|
||||
"Select features:",
|
||||
choices=['TypeScript', 'ESLint', 'Prettier', 'Testing']
|
||||
).ask()
|
||||
```
|
||||
|
||||
### Example 2: Conditional Question Flow
|
||||
|
||||
**Use case**: Dynamic questions based on previous answers
|
||||
|
||||
```javascript
|
||||
// Node.js - See examples/nodejs/conditional-flow.js
|
||||
const questions = [
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useDatabase',
|
||||
message: 'Use database?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'dbType',
|
||||
message: 'Database type:',
|
||||
choices: ['PostgreSQL', 'MySQL', 'MongoDB', 'SQLite'],
|
||||
when: (answers) => answers.useDatabase
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'dbHost',
|
||||
message: 'Database host:',
|
||||
default: 'localhost',
|
||||
when: (answers) => answers.useDatabase && answers.dbType !== 'SQLite'
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
```python
|
||||
# Python - See examples/python/conditional_flow.py
|
||||
use_database = questionary.confirm("Use database?", default=True).ask()
|
||||
|
||||
if use_database:
|
||||
db_type = questionary.select(
|
||||
"Database type:",
|
||||
choices=['PostgreSQL', 'MySQL', 'MongoDB', 'SQLite']
|
||||
).ask()
|
||||
|
||||
if db_type != 'SQLite':
|
||||
db_host = questionary.text(
|
||||
"Database host:",
|
||||
default="localhost"
|
||||
).ask()
|
||||
```
|
||||
|
||||
### Example 3: Password with Confirmation
|
||||
|
||||
**Use case**: Secure password input with validation and confirmation
|
||||
|
||||
```javascript
|
||||
// Node.js - See examples/nodejs/password-confirm.js
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
message: 'Enter password:',
|
||||
validate: (input) => input.length >= 8 || 'Password must be 8+ characters'
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'confirmPassword',
|
||||
message: 'Confirm password:',
|
||||
validate: (input, answers) => {
|
||||
return input === answers.password || 'Passwords do not match';
|
||||
}
|
||||
}
|
||||
]);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python - See examples/python/password_confirm.py
|
||||
password = questionary.password(
|
||||
"Enter password:",
|
||||
validate=lambda t: len(t) >= 8 or "Password must be 8+ characters"
|
||||
).ask()
|
||||
|
||||
confirm = questionary.password(
|
||||
"Confirm password:",
|
||||
validate=lambda t: t == password or "Passwords do not match"
|
||||
).ask()
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
**Node.js**:
|
||||
```bash
|
||||
./scripts/install-nodejs-deps.sh
|
||||
# Installs: inquirer, inquirer-autocomplete-prompt
|
||||
```
|
||||
|
||||
**Python**:
|
||||
```bash
|
||||
./scripts/install-python-deps.sh
|
||||
# Installs: questionary, prompt_toolkit
|
||||
```
|
||||
|
||||
### Validate Prompts
|
||||
|
||||
```bash
|
||||
./scripts/validate-prompts.sh [nodejs|python]
|
||||
# Tests all templates and examples
|
||||
```
|
||||
|
||||
### Generate Prompt Code
|
||||
|
||||
```bash
|
||||
./scripts/generate-prompt.sh --type [text|list|checkbox|password] --lang [js|py]
|
||||
# Generates boilerplate prompt code
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always validate user input** - Prevent invalid data early
|
||||
2. **Provide clear messages** - Use descriptive prompt text
|
||||
3. **Set sensible defaults** - Reduce friction for common cases
|
||||
4. **Use conditional logic** - Skip irrelevant questions
|
||||
5. **Group related questions** - Keep context together
|
||||
6. **Handle Ctrl+C gracefully** - Catch interrupts and exit cleanly
|
||||
7. **Test interactively** - Run examples to verify UX
|
||||
8. **Provide help text** - Add descriptions for complex prompts
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### CLI Configuration Generator
|
||||
```javascript
|
||||
const config = await inquirer.prompt([
|
||||
{ type: 'input', name: 'appName', message: 'App name:' },
|
||||
{ type: 'input', name: 'version', message: 'Version:', default: '1.0.0' },
|
||||
{ type: 'list', name: 'env', message: 'Environment:', choices: ['dev', 'prod'] },
|
||||
{ type: 'confirm', name: 'debug', message: 'Enable debug?', default: false }
|
||||
]);
|
||||
```
|
||||
|
||||
### Multi-Step Installation Wizard
|
||||
```python
|
||||
# Step 1: Choose components
|
||||
components = questionary.checkbox(
|
||||
"Select components:",
|
||||
choices=['Core', 'CLI', 'Web UI', 'API']
|
||||
).ask()
|
||||
|
||||
# Step 2: Configure each component
|
||||
for component in components:
|
||||
print(f"\nConfiguring {component}...")
|
||||
# Component-specific questions
|
||||
```
|
||||
|
||||
### Error Recovery
|
||||
```javascript
|
||||
try {
|
||||
const answers = await inquirer.prompt(questions);
|
||||
// Process answers
|
||||
} catch (error) {
|
||||
if (error.isTtyError) {
|
||||
console.error('Prompt could not be rendered in this environment');
|
||||
} else {
|
||||
console.error('User interrupted prompt');
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Node.js**: v14+ with ESM support
|
||||
- **Python**: 3.7+ with pip
|
||||
- **Dependencies**:
|
||||
- Node.js: `inquirer@^9.0.0`, `inquirer-autocomplete-prompt@^3.0.0`
|
||||
- Python: `questionary@^2.0.0`, `prompt_toolkit@^3.0.0`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Node.js Issues
|
||||
|
||||
**Problem**: `Error [ERR_REQUIRE_ESM]`
|
||||
**Solution**: Use `import` instead of `require`, or add `"type": "module"` to package.json
|
||||
|
||||
**Problem**: Autocomplete not working
|
||||
**Solution**: Install `inquirer-autocomplete-prompt` plugin
|
||||
|
||||
### Python Issues
|
||||
|
||||
**Problem**: No module named 'questionary'
|
||||
**Solution**: Run `pip install questionary`
|
||||
|
||||
**Problem**: Prompt rendering issues
|
||||
**Solution**: Ensure terminal supports ANSI escape codes
|
||||
|
||||
---
|
||||
|
||||
**Purpose**: Provide reusable interactive prompt patterns for CLI development
|
||||
**Load when**: Building CLIs with user input, creating interactive questionnaires, implementing wizards
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Example: Complete Project Initialization Wizard
|
||||
*
|
||||
* Demonstrates combining multiple prompt types to create
|
||||
* a comprehensive CLI tool for project setup
|
||||
*/
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
async function projectInitWizard() {
|
||||
console.log('\n╔════════════════════════════════════════╗');
|
||||
console.log('║ 🚀 Project Initialization Wizard 🚀 ║');
|
||||
console.log('╚════════════════════════════════════════╝\n');
|
||||
|
||||
const config = await inquirer.prompt([
|
||||
// Project basics
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: 'Project name:',
|
||||
validate: (input) => {
|
||||
if (!/^[a-z0-9-]+$/.test(input)) {
|
||||
return 'Use lowercase letters, numbers, and hyphens only';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'description',
|
||||
message: 'Description:',
|
||||
validate: (input) => input.length > 0 || 'Description required'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'language',
|
||||
message: 'Programming language:',
|
||||
choices: ['TypeScript', 'JavaScript', 'Python', 'Go', 'Rust']
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'framework',
|
||||
message: 'Framework:',
|
||||
choices: (answers) => {
|
||||
const frameworks = {
|
||||
TypeScript: ['Next.js', 'Nest.js', 'Express', 'Fastify'],
|
||||
JavaScript: ['React', 'Vue', 'Express', 'Koa'],
|
||||
Python: ['FastAPI', 'Django', 'Flask'],
|
||||
Go: ['Gin', 'Echo', 'Fiber'],
|
||||
Rust: ['Actix', 'Rocket', 'Axum']
|
||||
};
|
||||
return frameworks[answers.language] || ['None'];
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'features',
|
||||
message: 'Select features:',
|
||||
choices: [
|
||||
{ name: 'Database', value: 'database', checked: true },
|
||||
{ name: 'Authentication', value: 'auth' },
|
||||
{ name: 'API Documentation', value: 'docs' },
|
||||
{ name: 'Testing', value: 'testing', checked: true },
|
||||
{ name: 'Logging', value: 'logging', checked: true }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useDocker',
|
||||
message: 'Use Docker?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'setupCI',
|
||||
message: 'Setup CI/CD?',
|
||||
default: true
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\n✅ Configuration complete!\n');
|
||||
console.log(JSON.stringify(config, null, 2));
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
projectInitWizard()
|
||||
.then(() => process.exit(0))
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
export { projectInitWizard };
|
||||
100
skills/inquirer-patterns/examples/python/project_init_wizard.py
Normal file
100
skills/inquirer-patterns/examples/python/project_init_wizard.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""
|
||||
Example: Complete Project Initialization Wizard
|
||||
|
||||
Demonstrates combining multiple prompt types to create
|
||||
a comprehensive CLI tool for project setup
|
||||
"""
|
||||
|
||||
import questionary
|
||||
from questionary import Choice
|
||||
import json
|
||||
|
||||
|
||||
def project_init_wizard():
|
||||
"""Complete project initialization wizard"""
|
||||
|
||||
print('\n╔════════════════════════════════════════╗')
|
||||
print('║ 🚀 Project Initialization Wizard 🚀 ║')
|
||||
print('╚════════════════════════════════════════╝\n')
|
||||
|
||||
# Project basics
|
||||
name = questionary.text(
|
||||
"Project name:",
|
||||
validate=lambda text: (
|
||||
text and text.replace('-', '').isalnum()
|
||||
or "Use lowercase letters, numbers, and hyphens only"
|
||||
)
|
||||
).ask()
|
||||
|
||||
description = questionary.text(
|
||||
"Description:",
|
||||
validate=lambda text: len(text) > 0 or "Description required"
|
||||
).ask()
|
||||
|
||||
# Language selection
|
||||
language = questionary.select(
|
||||
"Programming language:",
|
||||
choices=['TypeScript', 'JavaScript', 'Python', 'Go', 'Rust']
|
||||
).ask()
|
||||
|
||||
# Framework selection (based on language)
|
||||
frameworks = {
|
||||
'TypeScript': ['Next.js', 'Nest.js', 'Express', 'Fastify'],
|
||||
'JavaScript': ['React', 'Vue', 'Express', 'Koa'],
|
||||
'Python': ['FastAPI', 'Django', 'Flask'],
|
||||
'Go': ['Gin', 'Echo', 'Fiber'],
|
||||
'Rust': ['Actix', 'Rocket', 'Axum']
|
||||
}
|
||||
|
||||
framework = questionary.select(
|
||||
"Framework:",
|
||||
choices=frameworks.get(language, ['None'])
|
||||
).ask()
|
||||
|
||||
# Feature selection
|
||||
features = questionary.checkbox(
|
||||
"Select features:",
|
||||
choices=[
|
||||
Choice('Database', value='database', checked=True),
|
||||
Choice('Authentication', value='auth'),
|
||||
Choice('API Documentation', value='docs'),
|
||||
Choice('Testing', value='testing', checked=True),
|
||||
Choice('Logging', value='logging', checked=True)
|
||||
]
|
||||
).ask()
|
||||
|
||||
# Docker
|
||||
use_docker = questionary.confirm(
|
||||
"Use Docker?",
|
||||
default=True
|
||||
).ask()
|
||||
|
||||
# CI/CD
|
||||
setup_ci = questionary.confirm(
|
||||
"Setup CI/CD?",
|
||||
default=True
|
||||
).ask()
|
||||
|
||||
# Build configuration object
|
||||
config = {
|
||||
'name': name,
|
||||
'description': description,
|
||||
'language': language,
|
||||
'framework': framework,
|
||||
'features': features,
|
||||
'useDocker': use_docker,
|
||||
'setupCI': setup_ci
|
||||
}
|
||||
|
||||
print('\n✅ Configuration complete!\n')
|
||||
print(json.dumps(config, indent=2))
|
||||
|
||||
return config
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
project_init_wizard()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n❌ Cancelled by user")
|
||||
exit(1)
|
||||
423
skills/inquirer-patterns/scripts/generate-prompt.sh
Executable file
423
skills/inquirer-patterns/scripts/generate-prompt.sh
Executable file
@@ -0,0 +1,423 @@
|
||||
#!/bin/bash
|
||||
|
||||
# generate-prompt.sh
|
||||
# Generate boilerplate prompt code for Node.js or Python
|
||||
|
||||
set -e
|
||||
|
||||
# Default values
|
||||
PROMPT_TYPE=""
|
||||
LANGUAGE=""
|
||||
OUTPUT_FILE=""
|
||||
|
||||
# Color codes
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Usage information
|
||||
usage() {
|
||||
echo "Usage: $0 --type <prompt-type> --lang <language> [--output <file>]"
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " --type Prompt type: text, list, checkbox, password, autocomplete, conditional"
|
||||
echo " --lang Language: js (Node.js) or py (Python)"
|
||||
echo " --output Output file path (optional)"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " $0 --type text --lang js --output my-prompt.js"
|
||||
echo " $0 --type checkbox --lang py"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--type)
|
||||
PROMPT_TYPE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--lang)
|
||||
LANGUAGE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--output)
|
||||
OUTPUT_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate arguments
|
||||
if [ -z "$PROMPT_TYPE" ] || [ -z "$LANGUAGE" ]; then
|
||||
echo "Error: --type and --lang are required"
|
||||
usage
|
||||
fi
|
||||
|
||||
# Validate prompt type
|
||||
VALID_TYPES=("text" "list" "checkbox" "password" "autocomplete" "conditional")
|
||||
if [[ ! " ${VALID_TYPES[@]} " =~ " ${PROMPT_TYPE} " ]]; then
|
||||
echo "Error: Invalid prompt type '$PROMPT_TYPE'"
|
||||
echo "Valid types: ${VALID_TYPES[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate language
|
||||
if [ "$LANGUAGE" != "js" ] && [ "$LANGUAGE" != "py" ]; then
|
||||
echo "Error: Language must be 'js' or 'py'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set default output file if not specified
|
||||
if [ -z "$OUTPUT_FILE" ]; then
|
||||
if [ "$LANGUAGE" == "js" ]; then
|
||||
OUTPUT_FILE="${PROMPT_TYPE}-prompt.js"
|
||||
else
|
||||
OUTPUT_FILE="${PROMPT_TYPE}_prompt.py"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}🔧 Generating $PROMPT_TYPE prompt for $LANGUAGE...${NC}"
|
||||
echo
|
||||
|
||||
# Generate Node.js code
|
||||
if [ "$LANGUAGE" == "js" ]; then
|
||||
case $PROMPT_TYPE in
|
||||
text)
|
||||
cat > "$OUTPUT_FILE" << 'EOF'
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
async function textPrompt() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'fieldName',
|
||||
message: 'Enter value:',
|
||||
validate: (input) => input.length > 0 || 'This field is required'
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('Answer:', answers.fieldName);
|
||||
return answers;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
textPrompt().then(() => process.exit(0)).catch(console.error);
|
||||
}
|
||||
|
||||
export { textPrompt };
|
||||
EOF
|
||||
;;
|
||||
list)
|
||||
cat > "$OUTPUT_FILE" << 'EOF'
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
async function listPrompt() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'selection',
|
||||
message: 'Choose an option:',
|
||||
choices: ['Option 1', 'Option 2', 'Option 3']
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('Selected:', answers.selection);
|
||||
return answers;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
listPrompt().then(() => process.exit(0)).catch(console.error);
|
||||
}
|
||||
|
||||
export { listPrompt };
|
||||
EOF
|
||||
;;
|
||||
checkbox)
|
||||
cat > "$OUTPUT_FILE" << 'EOF'
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
async function checkboxPrompt() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'selections',
|
||||
message: 'Select options:',
|
||||
choices: ['Option 1', 'Option 2', 'Option 3'],
|
||||
validate: (choices) => choices.length > 0 || 'Select at least one option'
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('Selected:', answers.selections);
|
||||
return answers;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
checkboxPrompt().then(() => process.exit(0)).catch(console.error);
|
||||
}
|
||||
|
||||
export { checkboxPrompt };
|
||||
EOF
|
||||
;;
|
||||
password)
|
||||
cat > "$OUTPUT_FILE" << 'EOF'
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
async function passwordPrompt() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
message: 'Enter password:',
|
||||
mask: '*',
|
||||
validate: (input) => input.length >= 8 || 'Password must be at least 8 characters'
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'confirm',
|
||||
message: 'Confirm password:',
|
||||
mask: '*',
|
||||
validate: (input, answers) => input === answers.password || 'Passwords do not match'
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('Password set successfully');
|
||||
return answers;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
passwordPrompt().then(() => process.exit(0)).catch(console.error);
|
||||
}
|
||||
|
||||
export { passwordPrompt };
|
||||
EOF
|
||||
;;
|
||||
autocomplete)
|
||||
cat > "$OUTPUT_FILE" << 'EOF'
|
||||
import inquirer from 'inquirer';
|
||||
import inquirerAutocomplete from 'inquirer-autocomplete-prompt';
|
||||
|
||||
inquirer.registerPrompt('autocomplete', inquirerAutocomplete);
|
||||
|
||||
const choices = ['Option 1', 'Option 2', 'Option 3', 'Option 4', 'Option 5'];
|
||||
|
||||
function searchChoices(input) {
|
||||
if (!input) return choices;
|
||||
return choices.filter(choice => choice.toLowerCase().includes(input.toLowerCase()));
|
||||
}
|
||||
|
||||
async function autocompletePrompt() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'autocomplete',
|
||||
name: 'selection',
|
||||
message: 'Search for an option:',
|
||||
source: (answersSoFar, input) => Promise.resolve(searchChoices(input))
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('Selected:', answers.selection);
|
||||
return answers;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
autocompletePrompt().then(() => process.exit(0)).catch(console.error);
|
||||
}
|
||||
|
||||
export { autocompletePrompt };
|
||||
EOF
|
||||
;;
|
||||
conditional)
|
||||
cat > "$OUTPUT_FILE" << 'EOF'
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
async function conditionalPrompt() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'continue',
|
||||
message: 'Do you want to continue?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'details',
|
||||
message: 'Enter details:',
|
||||
when: (answers) => answers.continue,
|
||||
validate: (input) => input.length > 0 || 'Details required'
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('Answers:', answers);
|
||||
return answers;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
conditionalPrompt().then(() => process.exit(0)).catch(console.error);
|
||||
}
|
||||
|
||||
export { conditionalPrompt };
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Generate Python code
|
||||
if [ "$LANGUAGE" == "py" ]; then
|
||||
case $PROMPT_TYPE in
|
||||
text)
|
||||
cat > "$OUTPUT_FILE" << 'EOF'
|
||||
import questionary
|
||||
|
||||
def text_prompt():
|
||||
answer = questionary.text(
|
||||
"Enter value:",
|
||||
validate=lambda text: len(text) > 0 or "This field is required"
|
||||
).ask()
|
||||
|
||||
print(f"Answer: {answer}")
|
||||
return answer
|
||||
|
||||
if __name__ == "__main__":
|
||||
text_prompt()
|
||||
EOF
|
||||
;;
|
||||
list)
|
||||
cat > "$OUTPUT_FILE" << 'EOF'
|
||||
import questionary
|
||||
|
||||
def list_prompt():
|
||||
answer = questionary.select(
|
||||
"Choose an option:",
|
||||
choices=['Option 1', 'Option 2', 'Option 3']
|
||||
).ask()
|
||||
|
||||
print(f"Selected: {answer}")
|
||||
return answer
|
||||
|
||||
if __name__ == "__main__":
|
||||
list_prompt()
|
||||
EOF
|
||||
;;
|
||||
checkbox)
|
||||
cat > "$OUTPUT_FILE" << 'EOF'
|
||||
import questionary
|
||||
|
||||
def checkbox_prompt():
|
||||
answers = questionary.checkbox(
|
||||
"Select options:",
|
||||
choices=['Option 1', 'Option 2', 'Option 3'],
|
||||
validate=lambda choices: len(choices) > 0 or "Select at least one option"
|
||||
).ask()
|
||||
|
||||
print(f"Selected: {answers}")
|
||||
return answers
|
||||
|
||||
if __name__ == "__main__":
|
||||
checkbox_prompt()
|
||||
EOF
|
||||
;;
|
||||
password)
|
||||
cat > "$OUTPUT_FILE" << 'EOF'
|
||||
import questionary
|
||||
|
||||
def password_prompt():
|
||||
password = questionary.password(
|
||||
"Enter password:",
|
||||
validate=lambda text: len(text) >= 8 or "Password must be at least 8 characters"
|
||||
).ask()
|
||||
|
||||
confirm = questionary.password(
|
||||
"Confirm password:",
|
||||
validate=lambda text: text == password or "Passwords do not match"
|
||||
).ask()
|
||||
|
||||
print("Password set successfully")
|
||||
return password
|
||||
|
||||
if __name__ == "__main__":
|
||||
password_prompt()
|
||||
EOF
|
||||
;;
|
||||
autocomplete)
|
||||
cat > "$OUTPUT_FILE" << 'EOF'
|
||||
import questionary
|
||||
|
||||
def autocomplete_prompt():
|
||||
choices = ['Option 1', 'Option 2', 'Option 3', 'Option 4', 'Option 5']
|
||||
|
||||
answer = questionary.autocomplete(
|
||||
"Search for an option:",
|
||||
choices=choices
|
||||
).ask()
|
||||
|
||||
print(f"Selected: {answer}")
|
||||
return answer
|
||||
|
||||
if __name__ == "__main__":
|
||||
autocomplete_prompt()
|
||||
EOF
|
||||
;;
|
||||
conditional)
|
||||
cat > "$OUTPUT_FILE" << 'EOF'
|
||||
import questionary
|
||||
|
||||
def conditional_prompt():
|
||||
continue_prompt = questionary.confirm(
|
||||
"Do you want to continue?",
|
||||
default=True
|
||||
).ask()
|
||||
|
||||
details = None
|
||||
if continue_prompt:
|
||||
details = questionary.text(
|
||||
"Enter details:",
|
||||
validate=lambda text: len(text) > 0 or "Details required"
|
||||
).ask()
|
||||
|
||||
result = {
|
||||
'continue': continue_prompt,
|
||||
'details': details
|
||||
}
|
||||
|
||||
print(f"Answers: {result}")
|
||||
return result
|
||||
|
||||
if __name__ == "__main__":
|
||||
conditional_prompt()
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Make Python files executable
|
||||
if [ "$LANGUAGE" == "py" ]; then
|
||||
chmod +x "$OUTPUT_FILE"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Generated: $OUTPUT_FILE${NC}"
|
||||
echo
|
||||
echo -e "${YELLOW}📝 Next steps:${NC}"
|
||||
echo " 1. Edit the generated file to customize your prompt"
|
||||
if [ "$LANGUAGE" == "js" ]; then
|
||||
echo " 2. Run: node $OUTPUT_FILE"
|
||||
else
|
||||
echo " 2. Run: python3 $OUTPUT_FILE"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo -e "${BLUE}💡 Tip: Check out the templates directory for more advanced examples${NC}"
|
||||
72
skills/inquirer-patterns/scripts/install-nodejs-deps.sh
Executable file
72
skills/inquirer-patterns/scripts/install-nodejs-deps.sh
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
# install-nodejs-deps.sh
|
||||
# Install Node.js dependencies for inquirer patterns
|
||||
|
||||
set -e
|
||||
|
||||
echo "📦 Installing Node.js dependencies for inquirer patterns..."
|
||||
echo
|
||||
|
||||
# Check if npm is installed
|
||||
if ! command -v npm &> /dev/null; then
|
||||
echo "❌ Error: npm is not installed"
|
||||
echo "Please install Node.js and npm first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Node.js version
|
||||
NODE_VERSION=$(node --version | cut -d'v' -f2 | cut -d'.' -f1)
|
||||
if [ "$NODE_VERSION" -lt 14 ]; then
|
||||
echo "⚠️ Warning: Node.js 14 or higher is recommended"
|
||||
echo "Current version: $(node --version)"
|
||||
fi
|
||||
|
||||
# Create package.json if it doesn't exist
|
||||
if [ ! -f "package.json" ]; then
|
||||
echo "📝 Creating package.json..."
|
||||
cat > package.json << 'EOF'
|
||||
{
|
||||
"name": "inquirer-patterns-examples",
|
||||
"version": "1.0.0",
|
||||
"description": "Interactive prompt patterns for CLI tools",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": ["inquirer", "cli", "prompts", "interactive"],
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
}
|
||||
EOF
|
||||
echo "✅ package.json created"
|
||||
fi
|
||||
|
||||
# Install core dependencies
|
||||
echo "📥 Installing inquirer..."
|
||||
npm install inquirer@^9.0.0
|
||||
|
||||
echo "📥 Installing inquirer-autocomplete-prompt..."
|
||||
npm install inquirer-autocomplete-prompt@^3.0.0
|
||||
|
||||
# Optional: Install chalk for colored output
|
||||
echo "📥 Installing chalk (optional, for colored output)..."
|
||||
npm install chalk@^5.0.0
|
||||
|
||||
echo
|
||||
echo "✅ All Node.js dependencies installed successfully!"
|
||||
echo
|
||||
echo "📚 Installed packages:"
|
||||
echo " - inquirer@^9.0.0"
|
||||
echo " - inquirer-autocomplete-prompt@^3.0.0"
|
||||
echo " - chalk@^5.0.0"
|
||||
echo
|
||||
echo "🚀 You can now run the examples:"
|
||||
echo " node templates/nodejs/text-prompt.js"
|
||||
echo " node templates/nodejs/list-prompt.js"
|
||||
echo " node templates/nodejs/checkbox-prompt.js"
|
||||
echo " node templates/nodejs/password-prompt.js"
|
||||
echo " node templates/nodejs/autocomplete-prompt.js"
|
||||
echo " node templates/nodejs/conditional-prompt.js"
|
||||
echo " node templates/nodejs/comprehensive-example.js"
|
||||
echo
|
||||
65
skills/inquirer-patterns/scripts/install-python-deps.sh
Executable file
65
skills/inquirer-patterns/scripts/install-python-deps.sh
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
# install-python-deps.sh
|
||||
# Install Python dependencies for questionary patterns
|
||||
|
||||
set -e
|
||||
|
||||
echo "📦 Installing Python dependencies for questionary patterns..."
|
||||
echo
|
||||
|
||||
# Check if python3 is installed
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
echo "❌ Error: python3 is not installed"
|
||||
echo "Please install Python 3.7 or higher first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Python version
|
||||
PYTHON_VERSION=$(python3 --version | cut -d' ' -f2 | cut -d'.' -f1,2)
|
||||
MAJOR=$(echo $PYTHON_VERSION | cut -d'.' -f1)
|
||||
MINOR=$(echo $PYTHON_VERSION | cut -d'.' -f2)
|
||||
|
||||
if [ "$MAJOR" -lt 3 ] || ([ "$MAJOR" -eq 3 ] && [ "$MINOR" -lt 7 ]); then
|
||||
echo "⚠️ Warning: Python 3.7 or higher is recommended"
|
||||
echo "Current version: $(python3 --version)"
|
||||
fi
|
||||
|
||||
# Check if pip is installed
|
||||
if ! command -v pip3 &> /dev/null; then
|
||||
echo "❌ Error: pip3 is not installed"
|
||||
echo "Please install pip3 first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Upgrade pip
|
||||
echo "🔄 Upgrading pip..."
|
||||
python3 -m pip install --upgrade pip
|
||||
|
||||
# Install core dependencies
|
||||
echo "📥 Installing questionary..."
|
||||
pip3 install "questionary>=2.0.0"
|
||||
|
||||
echo "📥 Installing prompt_toolkit..."
|
||||
pip3 install "prompt_toolkit>=3.0.0"
|
||||
|
||||
# Optional: Install colorama for colored output on Windows
|
||||
echo "📥 Installing colorama (optional, for Windows support)..."
|
||||
pip3 install colorama
|
||||
|
||||
echo
|
||||
echo "✅ All Python dependencies installed successfully!"
|
||||
echo
|
||||
echo "📚 Installed packages:"
|
||||
echo " - questionary>=2.0.0"
|
||||
echo " - prompt_toolkit>=3.0.0"
|
||||
echo " - colorama"
|
||||
echo
|
||||
echo "🚀 You can now run the examples:"
|
||||
echo " python3 templates/python/text_prompt.py"
|
||||
echo " python3 templates/python/list_prompt.py"
|
||||
echo " python3 templates/python/checkbox_prompt.py"
|
||||
echo " python3 templates/python/password_prompt.py"
|
||||
echo " python3 templates/python/autocomplete_prompt.py"
|
||||
echo " python3 templates/python/conditional_prompt.py"
|
||||
echo
|
||||
206
skills/inquirer-patterns/scripts/validate-prompts.sh
Executable file
206
skills/inquirer-patterns/scripts/validate-prompts.sh
Executable file
@@ -0,0 +1,206 @@
|
||||
#!/bin/bash
|
||||
|
||||
# validate-prompts.sh
|
||||
# Validate prompt templates and check for common issues
|
||||
|
||||
set -e
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
|
||||
echo "🔍 Validating inquirer-patterns skill..."
|
||||
echo
|
||||
|
||||
# Color codes
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
error() {
|
||||
echo -e "${RED}❌ ERROR: $1${NC}"
|
||||
((ERRORS++))
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo -e "${YELLOW}⚠️ WARNING: $1${NC}"
|
||||
((WARNINGS++))
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
# Check SKILL.md exists
|
||||
echo "📄 Checking SKILL.md..."
|
||||
if [ ! -f "$SKILL_DIR/SKILL.md" ]; then
|
||||
error "SKILL.md not found"
|
||||
else
|
||||
success "SKILL.md exists"
|
||||
|
||||
# Check frontmatter starts at line 1
|
||||
FIRST_LINE=$(head -n 1 "$SKILL_DIR/SKILL.md")
|
||||
if [ "$FIRST_LINE" != "---" ]; then
|
||||
error "SKILL.md frontmatter must start at line 1"
|
||||
else
|
||||
success "Frontmatter starts at line 1"
|
||||
fi
|
||||
|
||||
# Check for required frontmatter fields
|
||||
if ! grep -q "^name:" "$SKILL_DIR/SKILL.md"; then
|
||||
error "Missing 'name' field in frontmatter"
|
||||
else
|
||||
success "Frontmatter has 'name' field"
|
||||
fi
|
||||
|
||||
if ! grep -q "^description:" "$SKILL_DIR/SKILL.md"; then
|
||||
error "Missing 'description' field in frontmatter"
|
||||
else
|
||||
success "Frontmatter has 'description' field"
|
||||
fi
|
||||
|
||||
# Check for "Use when" context
|
||||
if ! grep -qi "use when" "$SKILL_DIR/SKILL.md"; then
|
||||
warning "Missing 'Use when' trigger context in description"
|
||||
else
|
||||
success "'Use when' context found"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
|
||||
# Check Node.js templates
|
||||
echo "📁 Checking Node.js templates..."
|
||||
NODEJS_DIR="$SKILL_DIR/templates/nodejs"
|
||||
|
||||
if [ ! -d "$NODEJS_DIR" ]; then
|
||||
error "Node.js templates directory not found"
|
||||
else
|
||||
success "Node.js templates directory exists"
|
||||
|
||||
REQUIRED_NODEJS_TEMPLATES=(
|
||||
"text-prompt.js"
|
||||
"list-prompt.js"
|
||||
"checkbox-prompt.js"
|
||||
"password-prompt.js"
|
||||
"autocomplete-prompt.js"
|
||||
"conditional-prompt.js"
|
||||
"comprehensive-example.js"
|
||||
)
|
||||
|
||||
for template in "${REQUIRED_NODEJS_TEMPLATES[@]}"; do
|
||||
if [ ! -f "$NODEJS_DIR/$template" ]; then
|
||||
error "Missing Node.js template: $template"
|
||||
else
|
||||
# Check for basic syntax
|
||||
if ! grep -q "import inquirer from 'inquirer'" "$NODEJS_DIR/$template"; then
|
||||
if ! grep -q "inquirer" "$NODEJS_DIR/$template"; then
|
||||
warning "$template might be missing inquirer import"
|
||||
fi
|
||||
fi
|
||||
success "Node.js template exists: $template"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo
|
||||
|
||||
# Check Python templates
|
||||
echo "📁 Checking Python templates..."
|
||||
PYTHON_DIR="$SKILL_DIR/templates/python"
|
||||
|
||||
if [ ! -d "$PYTHON_DIR" ]; then
|
||||
error "Python templates directory not found"
|
||||
else
|
||||
success "Python templates directory exists"
|
||||
|
||||
REQUIRED_PYTHON_TEMPLATES=(
|
||||
"text_prompt.py"
|
||||
"list_prompt.py"
|
||||
"checkbox_prompt.py"
|
||||
"password_prompt.py"
|
||||
"autocomplete_prompt.py"
|
||||
"conditional_prompt.py"
|
||||
)
|
||||
|
||||
for template in "${REQUIRED_PYTHON_TEMPLATES[@]}"; do
|
||||
if [ ! -f "$PYTHON_DIR/$template" ]; then
|
||||
error "Missing Python template: $template"
|
||||
else
|
||||
# Check for basic syntax
|
||||
if ! grep -q "import questionary" "$PYTHON_DIR/$template"; then
|
||||
warning "$template might be missing questionary import"
|
||||
fi
|
||||
success "Python template exists: $template"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo
|
||||
|
||||
# Check scripts
|
||||
echo "📁 Checking scripts..."
|
||||
SCRIPTS_DIR="$SKILL_DIR/scripts"
|
||||
|
||||
REQUIRED_SCRIPTS=(
|
||||
"install-nodejs-deps.sh"
|
||||
"install-python-deps.sh"
|
||||
"validate-prompts.sh"
|
||||
"generate-prompt.sh"
|
||||
)
|
||||
|
||||
for script in "${REQUIRED_SCRIPTS[@]}"; do
|
||||
if [ ! -f "$SCRIPTS_DIR/$script" ]; then
|
||||
error "Missing script: $script"
|
||||
else
|
||||
if [ ! -x "$SCRIPTS_DIR/$script" ]; then
|
||||
warning "Script not executable: $script"
|
||||
else
|
||||
success "Script exists and is executable: $script"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
|
||||
# Check for hardcoded API keys or secrets (security check)
|
||||
echo "🔒 Security check: Scanning for hardcoded secrets..."
|
||||
|
||||
# Patterns to search for
|
||||
SECRET_PATTERNS=(
|
||||
"sk-[a-zA-Z0-9]{32,}"
|
||||
"pk-[a-zA-Z0-9]{32,}"
|
||||
"AIza[0-9A-Za-z_-]{35}"
|
||||
"password.*=.*['\"][^'\"]{8,}['\"]"
|
||||
)
|
||||
|
||||
SECRETS_FOUND=0
|
||||
for pattern in "${SECRET_PATTERNS[@]}"; do
|
||||
if grep -rE "$pattern" "$SKILL_DIR/templates" 2>/dev/null | grep -v "your_.*_key_here" | grep -v "example" > /dev/null; then
|
||||
error "Potential hardcoded secret found matching pattern: $pattern"
|
||||
((SECRETS_FOUND++))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $SECRETS_FOUND -eq 0 ]; then
|
||||
success "No hardcoded secrets found"
|
||||
fi
|
||||
|
||||
echo
|
||||
|
||||
# Summary
|
||||
echo "═══════════════════════════════════════════════════════════"
|
||||
echo "Validation Summary"
|
||||
echo "═══════════════════════════════════════════════════════════"
|
||||
|
||||
if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ All validations passed!${NC}"
|
||||
exit 0
|
||||
elif [ $ERRORS -eq 0 ]; then
|
||||
echo -e "${YELLOW}⚠️ Validation passed with $WARNINGS warning(s)${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ Validation failed with $ERRORS error(s) and $WARNINGS warning(s)${NC}"
|
||||
exit 1
|
||||
fi
|
||||
259
skills/inquirer-patterns/templates/nodejs/autocomplete-prompt.js
Normal file
259
skills/inquirer-patterns/templates/nodejs/autocomplete-prompt.js
Normal file
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* Autocomplete Prompt Template
|
||||
*
|
||||
* Use for: Large option lists with search
|
||||
* Features: Type-ahead, fuzzy matching, suggestions
|
||||
*
|
||||
* Note: Requires inquirer-autocomplete-prompt plugin
|
||||
* Install: npm install inquirer-autocomplete-prompt
|
||||
*/
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
import inquirerAutocomplete from 'inquirer-autocomplete-prompt';
|
||||
|
||||
// Register the autocomplete prompt type
|
||||
inquirer.registerPrompt('autocomplete', inquirerAutocomplete);
|
||||
|
||||
// Example: Countries list for autocomplete
|
||||
const countries = [
|
||||
'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola',
|
||||
'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan',
|
||||
'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus',
|
||||
'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia',
|
||||
'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi',
|
||||
'Cambodia', 'Cameroon', 'Canada', 'Cape Verde', 'Chad',
|
||||
'Chile', 'China', 'Colombia', 'Comoros', 'Congo',
|
||||
'Costa Rica', 'Croatia', 'Cuba', 'Cyprus', 'Czech Republic',
|
||||
'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic',
|
||||
'Ecuador', 'Egypt', 'El Salvador', 'Estonia', 'Ethiopia',
|
||||
'Fiji', 'Finland', 'France', 'Gabon', 'Gambia',
|
||||
'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada',
|
||||
'Guatemala', 'Guinea', 'Guyana', 'Haiti', 'Honduras',
|
||||
'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran',
|
||||
'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica',
|
||||
'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kuwait',
|
||||
'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia',
|
||||
'Libya', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi',
|
||||
'Malaysia', 'Maldives', 'Mali', 'Malta', 'Mexico',
|
||||
'Moldova', 'Monaco', 'Mongolia', 'Morocco', 'Mozambique',
|
||||
'Myanmar', 'Namibia', 'Nepal', 'Netherlands', 'New Zealand',
|
||||
'Nicaragua', 'Niger', 'Nigeria', 'Norway', 'Oman',
|
||||
'Pakistan', 'Panama', 'Paraguay', 'Peru', 'Philippines',
|
||||
'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia',
|
||||
'Rwanda', 'Saudi Arabia', 'Senegal', 'Serbia', 'Singapore',
|
||||
'Slovakia', 'Slovenia', 'Somalia', 'South Africa', 'South Korea',
|
||||
'Spain', 'Sri Lanka', 'Sudan', 'Sweden', 'Switzerland',
|
||||
'Syria', 'Taiwan', 'Tanzania', 'Thailand', 'Togo',
|
||||
'Tunisia', 'Turkey', 'Uganda', 'Ukraine', 'United Arab Emirates',
|
||||
'United Kingdom', 'United States', 'Uruguay', 'Uzbekistan',
|
||||
'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe'
|
||||
];
|
||||
|
||||
// Example: NPM packages for autocomplete
|
||||
const popularPackages = [
|
||||
'express', 'react', 'vue', 'angular', 'next', 'nuxt',
|
||||
'axios', 'lodash', 'moment', 'dayjs', 'uuid', 'dotenv',
|
||||
'typescript', 'eslint', 'prettier', 'jest', 'mocha', 'chai',
|
||||
'webpack', 'vite', 'rollup', 'babel', 'esbuild',
|
||||
'socket.io', 'redis', 'mongodb', 'mongoose', 'sequelize',
|
||||
'prisma', 'typeorm', 'knex', 'pg', 'mysql2',
|
||||
'bcrypt', 'jsonwebtoken', 'passport', 'helmet', 'cors',
|
||||
'multer', 'sharp', 'puppeteer', 'playwright', 'cheerio'
|
||||
];
|
||||
|
||||
// Fuzzy search function
|
||||
function fuzzySearch(input, choices) {
|
||||
if (!input) return choices;
|
||||
|
||||
const searchTerm = input.toLowerCase();
|
||||
return choices.filter(choice => {
|
||||
const item = typeof choice === 'string' ? choice : choice.name;
|
||||
return item.toLowerCase().includes(searchTerm);
|
||||
});
|
||||
}
|
||||
|
||||
async function autocompletePromptExample() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'autocomplete',
|
||||
name: 'country',
|
||||
message: 'Select your country:',
|
||||
source: (answersSoFar, input) => {
|
||||
return Promise.resolve(fuzzySearch(input, countries));
|
||||
},
|
||||
pageSize: 10
|
||||
},
|
||||
{
|
||||
type: 'autocomplete',
|
||||
name: 'package',
|
||||
message: 'Search for an npm package:',
|
||||
source: (answersSoFar, input) => {
|
||||
const filtered = fuzzySearch(input, popularPackages);
|
||||
return Promise.resolve(filtered);
|
||||
},
|
||||
pageSize: 8,
|
||||
validate: (input) => {
|
||||
return input.length > 0 || 'Please select a package';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'autocomplete',
|
||||
name: 'city',
|
||||
message: 'Select city:',
|
||||
source: async (answersSoFar, input) => {
|
||||
// Example: Cities based on selected country
|
||||
const citiesByCountry = {
|
||||
'United States': ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'],
|
||||
'United Kingdom': ['London', 'Manchester', 'Birmingham', 'Glasgow', 'Liverpool'],
|
||||
'Canada': ['Toronto', 'Vancouver', 'Montreal', 'Calgary', 'Ottawa'],
|
||||
'Australia': ['Sydney', 'Melbourne', 'Brisbane', 'Perth', 'Adelaide'],
|
||||
'Germany': ['Berlin', 'Munich', 'Hamburg', 'Frankfurt', 'Cologne']
|
||||
};
|
||||
|
||||
const cities = citiesByCountry[answersSoFar.country] || ['Capital City', 'Major City'];
|
||||
return fuzzySearch(input, cities);
|
||||
},
|
||||
when: (answers) => ['United States', 'United Kingdom', 'Canada', 'Australia', 'Germany'].includes(answers.country)
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\n✅ Selections:');
|
||||
console.log(JSON.stringify(answers, null, 2));
|
||||
|
||||
return answers;
|
||||
}
|
||||
|
||||
// Example: Framework/Library search
|
||||
async function frameworkSearchExample() {
|
||||
const frameworks = [
|
||||
{ name: 'React - UI library by Facebook', value: 'react' },
|
||||
{ name: 'Vue.js - Progressive JavaScript framework', value: 'vue' },
|
||||
{ name: 'Angular - Platform for building web apps', value: 'angular' },
|
||||
{ name: 'Svelte - Cybernetically enhanced web apps', value: 'svelte' },
|
||||
{ name: 'Next.js - React framework with SSR', value: 'next' },
|
||||
{ name: 'Nuxt.js - Vue.js framework with SSR', value: 'nuxt' },
|
||||
{ name: 'Remix - Full stack web framework', value: 'remix' },
|
||||
{ name: 'SvelteKit - Svelte framework', value: 'sveltekit' },
|
||||
{ name: 'Express - Fast Node.js web framework', value: 'express' },
|
||||
{ name: 'Fastify - Fast and low overhead web framework', value: 'fastify' },
|
||||
{ name: 'NestJS - Progressive Node.js framework', value: 'nestjs' },
|
||||
{ name: 'Koa - Expressive middleware for Node.js', value: 'koa' }
|
||||
];
|
||||
|
||||
const answer = await inquirer.prompt([
|
||||
{
|
||||
type: 'autocomplete',
|
||||
name: 'framework',
|
||||
message: 'Search for a framework:',
|
||||
source: (answersSoFar, input) => {
|
||||
const filtered = fuzzySearch(input, frameworks);
|
||||
return Promise.resolve(filtered);
|
||||
},
|
||||
pageSize: 10
|
||||
}
|
||||
]);
|
||||
|
||||
console.log(`\n✅ Selected: ${answer.framework}`);
|
||||
return answer;
|
||||
}
|
||||
|
||||
// Example: Command search with categories
|
||||
async function commandSearchExample() {
|
||||
const commands = [
|
||||
{ name: '📦 install - Install dependencies', value: 'install' },
|
||||
{ name: '🚀 start - Start development server', value: 'start' },
|
||||
{ name: '🏗️ build - Build for production', value: 'build' },
|
||||
{ name: '🧪 test - Run tests', value: 'test' },
|
||||
{ name: '🔍 lint - Check code quality', value: 'lint' },
|
||||
{ name: '✨ format - Format code', value: 'format' },
|
||||
{ name: '📝 generate - Generate files', value: 'generate' },
|
||||
{ name: '🔄 update - Update dependencies', value: 'update' },
|
||||
{ name: '🧹 clean - Clean build artifacts', value: 'clean' },
|
||||
{ name: '🚢 deploy - Deploy application', value: 'deploy' },
|
||||
{ name: '📊 analyze - Analyze bundle size', value: 'analyze' },
|
||||
{ name: '🐛 debug - Start debugger', value: 'debug' }
|
||||
];
|
||||
|
||||
const answer = await inquirer.prompt([
|
||||
{
|
||||
type: 'autocomplete',
|
||||
name: 'command',
|
||||
message: 'Search for a command:',
|
||||
source: (answersSoFar, input) => {
|
||||
return Promise.resolve(fuzzySearch(input, commands));
|
||||
},
|
||||
pageSize: 12
|
||||
}
|
||||
]);
|
||||
|
||||
console.log(`\n✅ Running: ${answer.command}`);
|
||||
return answer;
|
||||
}
|
||||
|
||||
// Example: Dynamic API search (simulated)
|
||||
async function apiSearchExample() {
|
||||
console.log('\n🔍 API Endpoint Search\n');
|
||||
|
||||
const answer = await inquirer.prompt([
|
||||
{
|
||||
type: 'autocomplete',
|
||||
name: 'endpoint',
|
||||
message: 'Search API endpoints:',
|
||||
source: async (answersSoFar, input) => {
|
||||
// Simulate API call
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
const endpoints = [
|
||||
{ name: 'GET /users - List all users', value: '/users' },
|
||||
{ name: 'GET /users/:id - Get user by ID', value: '/users/:id' },
|
||||
{ name: 'POST /users - Create new user', value: '/users' },
|
||||
{ name: 'PUT /users/:id - Update user', value: '/users/:id' },
|
||||
{ name: 'DELETE /users/:id - Delete user', value: '/users/:id' },
|
||||
{ name: 'GET /posts - List all posts', value: '/posts' },
|
||||
{ name: 'GET /posts/:id - Get post by ID', value: '/posts/:id' },
|
||||
{ name: 'POST /posts - Create new post', value: '/posts' },
|
||||
{ name: 'GET /comments - List comments', value: '/comments' },
|
||||
{ name: 'POST /auth/login - User login', value: '/auth/login' },
|
||||
{ name: 'POST /auth/register - User registration', value: '/auth/register' },
|
||||
{ name: 'POST /auth/logout - User logout', value: '/auth/logout' }
|
||||
];
|
||||
|
||||
return fuzzySearch(input, endpoints);
|
||||
},
|
||||
pageSize: 10
|
||||
}
|
||||
]);
|
||||
|
||||
console.log(`\n✅ Selected endpoint: ${answer.endpoint}`);
|
||||
return answer;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
(async () => {
|
||||
console.log('=== Autocomplete Examples ===\n');
|
||||
|
||||
console.log('1. Country & Package Selection');
|
||||
await autocompletePromptExample();
|
||||
|
||||
console.log('\n2. Framework Search');
|
||||
await frameworkSearchExample();
|
||||
|
||||
console.log('\n3. Command Search');
|
||||
await commandSearchExample();
|
||||
|
||||
console.log('\n4. API Endpoint Search');
|
||||
await apiSearchExample();
|
||||
|
||||
process.exit(0);
|
||||
})().catch((error) => {
|
||||
if (error.isTtyError) {
|
||||
console.error('❌ Prompt could not be rendered in this environment');
|
||||
} else {
|
||||
console.error('❌ User interrupted prompt');
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { autocompletePromptExample, frameworkSearchExample, commandSearchExample, apiSearchExample };
|
||||
140
skills/inquirer-patterns/templates/nodejs/checkbox-prompt.js
Normal file
140
skills/inquirer-patterns/templates/nodejs/checkbox-prompt.js
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Checkbox Prompt Template
|
||||
*
|
||||
* Use for: Multiple selections from options
|
||||
* Features: Space to toggle, Enter to confirm
|
||||
*/
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
async function checkboxPromptExample() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'features',
|
||||
message: 'Select features to include:',
|
||||
choices: [
|
||||
'Authentication',
|
||||
'Authorization',
|
||||
'Database Integration',
|
||||
'API Documentation',
|
||||
'Testing Suite',
|
||||
'CI/CD Pipeline',
|
||||
'Monitoring',
|
||||
'Logging'
|
||||
],
|
||||
validate: (choices) => {
|
||||
if (choices.length === 0) {
|
||||
return 'You must select at least one feature';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'tools',
|
||||
message: 'Select development tools:',
|
||||
choices: [
|
||||
{ name: 'ESLint (Linting)', value: 'eslint', checked: true },
|
||||
{ name: 'Prettier (Formatting)', value: 'prettier', checked: true },
|
||||
{ name: 'Jest (Testing)', value: 'jest' },
|
||||
{ name: 'Husky (Git Hooks)', value: 'husky' },
|
||||
{ name: 'TypeDoc (Documentation)', value: 'typedoc' },
|
||||
{ name: 'Webpack (Bundling)', value: 'webpack' }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'plugins',
|
||||
message: 'Select plugins to install:',
|
||||
choices: [
|
||||
new inquirer.Separator('=== Essential ==='),
|
||||
{ name: 'dotenv - Environment variables', value: 'dotenv', checked: true },
|
||||
{ name: 'axios - HTTP client', value: 'axios', checked: true },
|
||||
new inquirer.Separator('=== Utilities ==='),
|
||||
{ name: 'lodash - Utility functions', value: 'lodash' },
|
||||
{ name: 'dayjs - Date manipulation', value: 'dayjs' },
|
||||
{ name: 'uuid - Unique IDs', value: 'uuid' },
|
||||
new inquirer.Separator('=== Validation ==='),
|
||||
{ name: 'joi - Schema validation', value: 'joi' },
|
||||
{ name: 'zod - TypeScript-first validation', value: 'zod' },
|
||||
new inquirer.Separator('=== Advanced ==='),
|
||||
{ name: 'bull - Job queues', value: 'bull' },
|
||||
{ name: 'socket.io - WebSockets', value: 'socket.io' }
|
||||
],
|
||||
pageSize: 15,
|
||||
validate: (choices) => {
|
||||
if (choices.length > 10) {
|
||||
return 'Please select no more than 10 plugins to avoid bloat';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'permissions',
|
||||
message: 'Grant the following permissions:',
|
||||
choices: [
|
||||
{ name: '📁 Read files', value: 'read', checked: true },
|
||||
{ name: '✏️ Write files', value: 'write' },
|
||||
{ name: '🗑️ Delete files', value: 'delete' },
|
||||
{ name: '🌐 Network access', value: 'network', checked: true },
|
||||
{ name: '🖥️ System commands', value: 'system' },
|
||||
{ name: '🔒 Keychain access', value: 'keychain' }
|
||||
],
|
||||
validate: (choices) => {
|
||||
if (choices.includes('delete') && !choices.includes('write')) {
|
||||
return 'Delete permission requires write permission';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'environments',
|
||||
message: 'Select deployment environments:',
|
||||
choices: [
|
||||
{ name: 'Development', value: 'dev', checked: true },
|
||||
{ name: 'Staging', value: 'staging' },
|
||||
{ name: 'Production', value: 'prod' },
|
||||
{ name: 'Testing', value: 'test', checked: true }
|
||||
],
|
||||
validate: (choices) => {
|
||||
if (!choices.includes('dev')) {
|
||||
return 'Development environment is required';
|
||||
}
|
||||
if (choices.includes('prod') && !choices.includes('staging')) {
|
||||
return 'Staging environment is recommended before production';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\n✅ Selected options:');
|
||||
console.log(JSON.stringify(answers, null, 2));
|
||||
|
||||
// Example: Process selections
|
||||
console.log('\n📦 Installing selected features...');
|
||||
answers.features.forEach(feature => {
|
||||
console.log(` - ${feature}`);
|
||||
});
|
||||
|
||||
return answers;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
checkboxPromptExample()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
if (error.isTtyError) {
|
||||
console.error('❌ Prompt could not be rendered in this environment');
|
||||
} else {
|
||||
console.error('❌ User interrupted prompt');
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { checkboxPromptExample };
|
||||
@@ -0,0 +1,456 @@
|
||||
/**
|
||||
* Comprehensive CLI Example
|
||||
*
|
||||
* Complete project initialization wizard combining all prompt types:
|
||||
* - Text input with validation
|
||||
* - List selections
|
||||
* - Checkbox selections
|
||||
* - Password input
|
||||
* - Autocomplete (optional)
|
||||
* - Conditional logic
|
||||
*/
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
async function projectInitWizard() {
|
||||
console.log(`
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🚀 Project Initialization Wizard 🚀 ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
`);
|
||||
|
||||
const config = await inquirer.prompt([
|
||||
// === PROJECT BASICS ===
|
||||
{
|
||||
type: 'input',
|
||||
name: 'projectName',
|
||||
message: '📦 Project name:',
|
||||
validate: (input) => {
|
||||
if (input.length === 0) return 'Project name is required';
|
||||
if (!/^[a-z0-9-_]+$/.test(input)) {
|
||||
return 'Use lowercase letters, numbers, hyphens, and underscores only';
|
||||
}
|
||||
if (input.length < 3) return 'Project name must be at least 3 characters';
|
||||
return true;
|
||||
},
|
||||
transformer: (input) => input.toLowerCase()
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'description',
|
||||
message: '📝 Project description:',
|
||||
validate: (input) => input.length > 0 || 'Description is required'
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'version',
|
||||
message: '🏷️ Initial version:',
|
||||
default: '0.1.0',
|
||||
validate: (input) => {
|
||||
return /^\d+\.\d+\.\d+$/.test(input) || 'Use semantic versioning (e.g., 0.1.0)';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'author',
|
||||
message: '👤 Author name:',
|
||||
default: process.env.USER || ''
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'email',
|
||||
message: '📧 Author email:',
|
||||
validate: (input) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(input) || 'Invalid email address';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'license',
|
||||
message: '📜 License:',
|
||||
choices: ['MIT', 'Apache-2.0', 'GPL-3.0', 'BSD-3-Clause', 'ISC', 'Unlicensed'],
|
||||
default: 'MIT'
|
||||
},
|
||||
|
||||
// === TECHNOLOGY STACK ===
|
||||
{
|
||||
type: 'list',
|
||||
name: 'projectType',
|
||||
message: '🛠️ Project type:',
|
||||
choices: [
|
||||
'Web Application',
|
||||
'CLI Tool',
|
||||
'API/Backend',
|
||||
'Library/Package',
|
||||
'Desktop Application',
|
||||
'Mobile Application'
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'language',
|
||||
message: '💻 Programming language:',
|
||||
choices: [
|
||||
{ name: 'TypeScript (Recommended)', value: 'typescript', short: 'TS' },
|
||||
{ name: 'JavaScript', value: 'javascript', short: 'JS' },
|
||||
{ name: 'Python', value: 'python', short: 'Py' },
|
||||
{ name: 'Go', value: 'go', short: 'Go' },
|
||||
{ name: 'Rust', value: 'rust', short: 'Rust' }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'framework',
|
||||
message: '🎨 Framework/Runtime:',
|
||||
choices: (answers) => {
|
||||
const frameworks = {
|
||||
typescript: ['Next.js', 'Remix', 'Nest.js', 'Express', 'Fastify', 'Node.js'],
|
||||
javascript: ['React', 'Vue', 'Svelte', 'Express', 'Fastify', 'Node.js'],
|
||||
python: ['FastAPI', 'Django', 'Flask', 'Tornado', 'Sanic'],
|
||||
go: ['Gin', 'Echo', 'Fiber', 'Standard library'],
|
||||
rust: ['Actix', 'Rocket', 'Axum', 'Warp']
|
||||
};
|
||||
return frameworks[answers.language] || ['None'];
|
||||
}
|
||||
},
|
||||
|
||||
// === FEATURES & TOOLS ===
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'features',
|
||||
message: '✨ Select features:',
|
||||
choices: (answers) => {
|
||||
const baseFeatures = [
|
||||
{ name: 'Environment variables (.env)', value: 'env', checked: true },
|
||||
{ name: 'Configuration management', value: 'config', checked: true },
|
||||
{ name: 'Logging', value: 'logging', checked: true },
|
||||
{ name: 'Error handling', value: 'error-handling', checked: true }
|
||||
];
|
||||
|
||||
if (answers.projectType === 'Web Application' || answers.projectType === 'API/Backend') {
|
||||
baseFeatures.push(
|
||||
{ name: 'Authentication', value: 'auth' },
|
||||
{ name: 'Database integration', value: 'database' },
|
||||
{ name: 'API documentation', value: 'api-docs' },
|
||||
{ name: 'CORS handling', value: 'cors' }
|
||||
);
|
||||
}
|
||||
|
||||
if (answers.projectType === 'CLI Tool') {
|
||||
baseFeatures.push(
|
||||
{ name: 'Command-line arguments parser', value: 'cli-parser', checked: true },
|
||||
{ name: 'Interactive prompts', value: 'prompts', checked: true },
|
||||
{ name: 'Progress bars', value: 'progress' }
|
||||
);
|
||||
}
|
||||
|
||||
return baseFeatures;
|
||||
},
|
||||
validate: (choices) => choices.length > 0 || 'Select at least one feature'
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'devTools',
|
||||
message: '🔧 Development tools:',
|
||||
choices: (answers) => {
|
||||
const tools = [];
|
||||
|
||||
if (['typescript', 'javascript'].includes(answers.language)) {
|
||||
tools.push(
|
||||
{ name: 'ESLint - Linting', value: 'eslint', checked: true },
|
||||
{ name: 'Prettier - Code formatting', value: 'prettier', checked: true },
|
||||
{ name: 'Husky - Git hooks', value: 'husky' },
|
||||
{ name: 'Jest - Testing framework', value: 'jest', checked: true },
|
||||
{ name: 'TypeDoc/JSDoc - Documentation', value: 'docs' }
|
||||
);
|
||||
} else if (answers.language === 'python') {
|
||||
tools.push(
|
||||
{ name: 'Black - Code formatting', value: 'black', checked: true },
|
||||
{ name: 'Flake8 - Linting', value: 'flake8', checked: true },
|
||||
{ name: 'mypy - Type checking', value: 'mypy' },
|
||||
{ name: 'pytest - Testing framework', value: 'pytest', checked: true },
|
||||
{ name: 'Sphinx - Documentation', value: 'sphinx' }
|
||||
);
|
||||
}
|
||||
|
||||
return tools;
|
||||
},
|
||||
default: ['eslint', 'prettier', 'jest']
|
||||
},
|
||||
|
||||
// === DATABASE CONFIGURATION ===
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useDatabase',
|
||||
message: '🗄️ Use database?',
|
||||
default: (answers) => {
|
||||
return answers.features.includes('database') ||
|
||||
['Web Application', 'API/Backend'].includes(answers.projectType);
|
||||
},
|
||||
when: (answers) => ['Web Application', 'API/Backend', 'CLI Tool'].includes(answers.projectType)
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'databaseType',
|
||||
message: '📊 Database type:',
|
||||
choices: [
|
||||
{ name: '🐘 PostgreSQL (Relational)', value: 'postgresql' },
|
||||
{ name: '🐬 MySQL (Relational)', value: 'mysql' },
|
||||
{ name: '🍃 MongoDB (Document)', value: 'mongodb' },
|
||||
{ name: '⚡ Redis (Key-Value)', value: 'redis' },
|
||||
{ name: '📁 SQLite (Embedded)', value: 'sqlite' },
|
||||
{ name: '🔥 Supabase (PostgreSQL + APIs)', value: 'supabase' }
|
||||
],
|
||||
when: (answers) => answers.useDatabase
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'databaseORM',
|
||||
message: '🔗 ORM/Database client:',
|
||||
choices: (answers) => {
|
||||
const orms = {
|
||||
typescript: {
|
||||
postgresql: ['Prisma', 'TypeORM', 'Kysely', 'Drizzle'],
|
||||
mysql: ['Prisma', 'TypeORM', 'Kysely', 'Drizzle'],
|
||||
mongodb: ['Mongoose', 'Prisma', 'TypeORM'],
|
||||
sqlite: ['Prisma', 'TypeORM', 'Better-SQLite3'],
|
||||
supabase: ['Supabase Client', 'Prisma']
|
||||
},
|
||||
python: {
|
||||
postgresql: ['SQLAlchemy', 'Django ORM', 'Tortoise ORM'],
|
||||
mysql: ['SQLAlchemy', 'Django ORM', 'Tortoise ORM'],
|
||||
mongodb: ['Motor', 'PyMongo', 'MongoEngine'],
|
||||
sqlite: ['SQLAlchemy', 'Django ORM']
|
||||
}
|
||||
};
|
||||
|
||||
const lang = answers.language;
|
||||
const db = answers.databaseType;
|
||||
return orms[lang]?.[db] || ['None'];
|
||||
},
|
||||
when: (answers) => answers.useDatabase && answers.databaseType !== 'redis'
|
||||
},
|
||||
|
||||
// === TESTING CONFIGURATION ===
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'setupTesting',
|
||||
message: '🧪 Setup testing?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'testTypes',
|
||||
message: '🔬 Test types:',
|
||||
choices: [
|
||||
{ name: 'Unit tests', value: 'unit', checked: true },
|
||||
{ name: 'Integration tests', value: 'integration', checked: true },
|
||||
{ name: 'E2E tests', value: 'e2e' },
|
||||
{ name: 'Performance tests', value: 'performance' }
|
||||
],
|
||||
when: (answers) => answers.setupTesting
|
||||
},
|
||||
|
||||
// === CI/CD CONFIGURATION ===
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'setupCICD',
|
||||
message: '⚙️ Setup CI/CD?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'cicdProvider',
|
||||
message: '🔄 CI/CD provider:',
|
||||
choices: ['GitHub Actions', 'GitLab CI', 'CircleCI', 'Jenkins', 'None'],
|
||||
when: (answers) => answers.setupCICD
|
||||
},
|
||||
|
||||
// === DEPLOYMENT CONFIGURATION ===
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'setupDeployment',
|
||||
message: '🚀 Setup deployment?',
|
||||
default: (answers) => answers.projectType !== 'Library/Package'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'deploymentPlatform',
|
||||
message: '☁️ Deployment platform:',
|
||||
choices: (answers) => {
|
||||
if (answers.projectType === 'Web Application') {
|
||||
return ['Vercel', 'Netlify', 'AWS', 'Google Cloud', 'Azure', 'Self-hosted'];
|
||||
} else if (answers.projectType === 'API/Backend') {
|
||||
return ['AWS', 'Google Cloud', 'Azure', 'DigitalOcean', 'Heroku', 'Self-hosted'];
|
||||
} else if (answers.projectType === 'CLI Tool') {
|
||||
return ['npm', 'PyPI', 'Homebrew', 'Binary releases', 'Docker'];
|
||||
}
|
||||
return ['AWS', 'Google Cloud', 'Azure', 'Self-hosted'];
|
||||
},
|
||||
when: (answers) => answers.setupDeployment
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useDocker',
|
||||
message: '🐳 Use Docker?',
|
||||
default: true,
|
||||
when: (answers) => {
|
||||
return answers.setupDeployment &&
|
||||
!['Vercel', 'Netlify'].includes(answers.deploymentPlatform);
|
||||
}
|
||||
},
|
||||
|
||||
// === MONITORING & OBSERVABILITY ===
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'setupMonitoring',
|
||||
message: '📊 Setup monitoring & observability?',
|
||||
default: (answers) => answers.projectType !== 'Library/Package'
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'monitoringTools',
|
||||
message: '📈 Monitoring tools:',
|
||||
choices: [
|
||||
{ name: 'Sentry - Error tracking', value: 'sentry' },
|
||||
{ name: 'DataDog - Full observability', value: 'datadog' },
|
||||
{ name: 'Prometheus - Metrics', value: 'prometheus' },
|
||||
{ name: 'Grafana - Dashboards', value: 'grafana' },
|
||||
{ name: 'New Relic - APM', value: 'newrelic' }
|
||||
],
|
||||
when: (answers) => answers.setupMonitoring
|
||||
},
|
||||
|
||||
// === DOCUMENTATION ===
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'generateDocs',
|
||||
message: '📚 Generate documentation?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'docTypes',
|
||||
message: '📖 Documentation types:',
|
||||
choices: [
|
||||
{ name: 'README.md', value: 'readme', checked: true },
|
||||
{ name: 'API documentation', value: 'api', checked: true },
|
||||
{ name: 'Contributing guidelines', value: 'contributing' },
|
||||
{ name: 'Code of conduct', value: 'coc' },
|
||||
{ name: 'Changelog', value: 'changelog', checked: true }
|
||||
],
|
||||
when: (answers) => answers.generateDocs
|
||||
},
|
||||
|
||||
// === SECURITY ===
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'securitySetup',
|
||||
message: '🔒 Setup security features?',
|
||||
default: true,
|
||||
when: (answers) => ['Web Application', 'API/Backend'].includes(answers.projectType)
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'securityFeatures',
|
||||
message: '🛡️ Security features:',
|
||||
choices: [
|
||||
{ name: 'Dependency scanning', value: 'dep-scan', checked: true },
|
||||
{ name: 'Secret scanning', value: 'secret-scan', checked: true },
|
||||
{ name: 'HTTPS enforcement', value: 'https' },
|
||||
{ name: 'Rate limiting', value: 'rate-limit' },
|
||||
{ name: 'Input validation', value: 'validation', checked: true },
|
||||
{ name: 'Security headers', value: 'headers' }
|
||||
],
|
||||
when: (answers) => answers.securitySetup
|
||||
},
|
||||
|
||||
// === FINAL CONFIRMATION ===
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: '✅ Initialize project with these settings?',
|
||||
default: true
|
||||
}
|
||||
]);
|
||||
|
||||
if (!config.confirm) {
|
||||
console.log('\n❌ Project initialization cancelled.\n');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Display configuration summary
|
||||
console.log('\n' + '═'.repeat(60));
|
||||
console.log('📋 PROJECT CONFIGURATION SUMMARY');
|
||||
console.log('═'.repeat(60) + '\n');
|
||||
|
||||
console.log(`📦 Project: ${config.projectName} v${config.version}`);
|
||||
console.log(`📝 Description: ${config.description}`);
|
||||
console.log(`👤 Author: ${config.author} <${config.email}>`);
|
||||
console.log(`📜 License: ${config.license}\n`);
|
||||
|
||||
console.log(`💻 Language: ${config.language}`);
|
||||
console.log(`🎨 Framework: ${config.framework}`);
|
||||
console.log(`🛠️ Type: ${config.projectType}\n`);
|
||||
|
||||
if (config.useDatabase) {
|
||||
console.log(`🗄️ Database: ${config.databaseType}`);
|
||||
if (config.databaseORM) {
|
||||
console.log(`🔗 ORM: ${config.databaseORM}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.features.length > 0) {
|
||||
console.log(`✨ Features: ${config.features.join(', ')}`);
|
||||
}
|
||||
|
||||
if (config.devTools.length > 0) {
|
||||
console.log(`🔧 Dev Tools: ${config.devTools.join(', ')}\n`);
|
||||
}
|
||||
|
||||
if (config.setupDeployment) {
|
||||
console.log(`🚀 Deployment: ${config.deploymentPlatform}`);
|
||||
if (config.useDocker) console.log(`🐳 Docker: Enabled`);
|
||||
}
|
||||
|
||||
if (config.setupCICD) {
|
||||
console.log(`⚙️ CI/CD: ${config.cicdProvider}`);
|
||||
}
|
||||
|
||||
console.log('\n' + '═'.repeat(60) + '\n');
|
||||
|
||||
console.log('🎉 Configuration complete! Initializing project...\n');
|
||||
|
||||
// Here you would actually create the project files
|
||||
// This is just a demonstration
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
projectInitWizard()
|
||||
.then((config) => {
|
||||
if (config) {
|
||||
console.log('✅ Project initialized successfully!\n');
|
||||
console.log('Next steps:');
|
||||
console.log(` 1. cd ${config.projectName}`);
|
||||
console.log(' 2. Install dependencies');
|
||||
console.log(' 3. Start development');
|
||||
}
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.isTtyError) {
|
||||
console.error('❌ Prompt could not be rendered in this environment');
|
||||
} else {
|
||||
console.error('❌ User interrupted prompt');
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { projectInitWizard };
|
||||
460
skills/inquirer-patterns/templates/nodejs/conditional-prompt.js
Normal file
460
skills/inquirer-patterns/templates/nodejs/conditional-prompt.js
Normal file
@@ -0,0 +1,460 @@
|
||||
/**
|
||||
* Conditional Prompt Template
|
||||
*
|
||||
* Use for: Dynamic forms based on previous answers
|
||||
* Features: Skip logic, dependent questions, branching
|
||||
*/
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
async function conditionalPromptExample() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useDatabase',
|
||||
message: 'Do you want to use a database?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'databaseType',
|
||||
message: 'Select database type:',
|
||||
choices: ['PostgreSQL', 'MySQL', 'MongoDB', 'SQLite', 'Redis'],
|
||||
when: (answers) => answers.useDatabase
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'databaseHost',
|
||||
message: 'Database host:',
|
||||
default: 'localhost',
|
||||
when: (answers) => {
|
||||
return answers.useDatabase && answers.databaseType !== 'SQLite';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'databasePort',
|
||||
message: 'Database port:',
|
||||
default: (answers) => {
|
||||
const ports = {
|
||||
'PostgreSQL': '5432',
|
||||
'MySQL': '3306',
|
||||
'MongoDB': '27017',
|
||||
'Redis': '6379'
|
||||
};
|
||||
return ports[answers.databaseType] || '5432';
|
||||
},
|
||||
when: (answers) => {
|
||||
return answers.useDatabase && answers.databaseType !== 'SQLite';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'databaseName',
|
||||
message: 'Database name:',
|
||||
when: (answers) => answers.useDatabase,
|
||||
validate: (input) => input.length > 0 || 'Database name required'
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useAuthentication',
|
||||
message: 'Do you want to use authentication?',
|
||||
default: true,
|
||||
when: (answers) => answers.useDatabase
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'databaseUsername',
|
||||
message: 'Database username:',
|
||||
when: (answers) => answers.useDatabase && answers.useAuthentication,
|
||||
validate: (input) => input.length > 0 || 'Username required'
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'databasePassword',
|
||||
message: 'Database password:',
|
||||
mask: '*',
|
||||
when: (answers) => answers.useDatabase && answers.useAuthentication
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useSSL',
|
||||
message: 'Use SSL connection?',
|
||||
default: false,
|
||||
when: (answers) => {
|
||||
return answers.useDatabase &&
|
||||
answers.databaseType !== 'SQLite' &&
|
||||
answers.databaseHost !== 'localhost';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'sslCertPath',
|
||||
message: 'Path to SSL certificate:',
|
||||
when: (answers) => answers.useSSL,
|
||||
validate: (input) => input.length > 0 || 'SSL certificate path required'
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\n✅ Configuration:');
|
||||
console.log(JSON.stringify(answers, null, 2));
|
||||
|
||||
return answers;
|
||||
}
|
||||
|
||||
// Example: Deployment configuration wizard
|
||||
async function deploymentWizard() {
|
||||
console.log('\n🚀 Deployment Configuration Wizard\n');
|
||||
|
||||
const config = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'environment',
|
||||
message: 'Select deployment environment:',
|
||||
choices: ['Development', 'Staging', 'Production']
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useDocker',
|
||||
message: 'Deploy using Docker?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'dockerImage',
|
||||
message: 'Docker image name:',
|
||||
when: (answers) => answers.useDocker,
|
||||
default: 'myapp:latest',
|
||||
validate: (input) => /^[a-z0-9-_/:.]+$/.test(input) || 'Invalid Docker image name'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'registry',
|
||||
message: 'Container registry:',
|
||||
choices: ['Docker Hub', 'GitHub Container Registry', 'AWS ECR', 'Google Artifact Registry'],
|
||||
when: (answers) => answers.useDocker
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'platform',
|
||||
message: 'Deployment platform:',
|
||||
choices: ['AWS', 'Google Cloud', 'Azure', 'DigitalOcean', 'Vercel', 'Netlify', 'Self-hosted']
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'awsService',
|
||||
message: 'AWS service:',
|
||||
choices: ['ECS', 'EKS', 'Lambda', 'Elastic Beanstalk', 'EC2'],
|
||||
when: (answers) => answers.platform === 'AWS'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'gcpService',
|
||||
message: 'Google Cloud service:',
|
||||
choices: ['Cloud Run', 'GKE', 'App Engine', 'Compute Engine'],
|
||||
when: (answers) => answers.platform === 'Google Cloud'
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'autoScale',
|
||||
message: 'Enable auto-scaling?',
|
||||
default: true,
|
||||
when: (answers) => {
|
||||
const scalableServices = ['ECS', 'EKS', 'Cloud Run', 'GKE'];
|
||||
return scalableServices.includes(answers.awsService) ||
|
||||
scalableServices.includes(answers.gcpService);
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'minInstances',
|
||||
message: 'Minimum instances:',
|
||||
default: '1',
|
||||
when: (answers) => answers.autoScale,
|
||||
validate: (input) => {
|
||||
const num = parseInt(input);
|
||||
return num > 0 || 'Must be at least 1';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'maxInstances',
|
||||
message: 'Maximum instances:',
|
||||
default: '10',
|
||||
when: (answers) => answers.autoScale,
|
||||
validate: (input, answers) => {
|
||||
const num = parseInt(input);
|
||||
const min = parseInt(answers.minInstances);
|
||||
return num >= min || `Must be at least ${min}`;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useCDN',
|
||||
message: 'Use CDN for static assets?',
|
||||
default: true,
|
||||
when: (answers) => answers.environment === 'Production'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'cdnProvider',
|
||||
message: 'CDN provider:',
|
||||
choices: ['CloudFlare', 'AWS CloudFront', 'Google Cloud CDN', 'Azure CDN'],
|
||||
when: (answers) => answers.useCDN
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'setupMonitoring',
|
||||
message: 'Setup monitoring?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'monitoringTools',
|
||||
message: 'Select monitoring tools:',
|
||||
choices: ['Prometheus', 'Grafana', 'Datadog', 'New Relic', 'Sentry'],
|
||||
when: (answers) => answers.setupMonitoring,
|
||||
validate: (choices) => choices.length > 0 || 'Select at least one tool'
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\n✅ Deployment configuration complete!');
|
||||
console.log(JSON.stringify(config, null, 2));
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// Example: Feature flag configuration
|
||||
async function featureFlagWizard() {
|
||||
console.log('\n🎛️ Feature Flag Configuration\n');
|
||||
|
||||
const config = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'featureName',
|
||||
message: 'Feature name:',
|
||||
validate: (input) => /^[a-z-_]+$/.test(input) || 'Use lowercase, hyphens, underscores only'
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'enabledByDefault',
|
||||
message: 'Enabled by default?',
|
||||
default: false
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'rolloutStrategy',
|
||||
message: 'Rollout strategy:',
|
||||
choices: [
|
||||
'All users',
|
||||
'Percentage rollout',
|
||||
'User targeting',
|
||||
'Beta users only',
|
||||
'Manual control'
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'rolloutPercentage',
|
||||
message: 'Rollout percentage (0-100):',
|
||||
when: (answers) => answers.rolloutStrategy === 'Percentage rollout',
|
||||
default: '10',
|
||||
validate: (input) => {
|
||||
const num = parseInt(input);
|
||||
return (num >= 0 && num <= 100) || 'Must be between 0 and 100';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'targetUserGroups',
|
||||
message: 'Target user groups:',
|
||||
choices: ['Beta testers', 'Premium users', 'Internal team', 'Early adopters', 'Specific regions'],
|
||||
when: (answers) => answers.rolloutStrategy === 'User targeting',
|
||||
validate: (choices) => choices.length > 0 || 'Select at least one group'
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'targetRegions',
|
||||
message: 'Target regions:',
|
||||
choices: ['North America', 'Europe', 'Asia Pacific', 'South America', 'Africa'],
|
||||
when: (answers) => {
|
||||
return answers.rolloutStrategy === 'User targeting' &&
|
||||
answers.targetUserGroups.includes('Specific regions');
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'enableMetrics',
|
||||
message: 'Track feature usage metrics?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'metrics',
|
||||
message: 'Select metrics to track:',
|
||||
choices: [
|
||||
'Usage count',
|
||||
'User adoption rate',
|
||||
'Performance impact',
|
||||
'Error rate',
|
||||
'User feedback'
|
||||
],
|
||||
when: (answers) => answers.enableMetrics
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'addExpirationDate',
|
||||
message: 'Set feature flag expiration?',
|
||||
default: false
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'expirationDate',
|
||||
message: 'Expiration date (YYYY-MM-DD):',
|
||||
when: (answers) => answers.addExpirationDate,
|
||||
validate: (input) => {
|
||||
const date = new Date(input);
|
||||
if (isNaN(date.getTime())) return 'Invalid date format';
|
||||
if (date < new Date()) return 'Date must be in the future';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\n✅ Feature flag configured!');
|
||||
console.log(JSON.stringify(config, null, 2));
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// Example: CI/CD pipeline setup
|
||||
async function cicdPipelineWizard() {
|
||||
console.log('\n⚙️ CI/CD Pipeline Configuration\n');
|
||||
|
||||
const config = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'provider',
|
||||
message: 'CI/CD provider:',
|
||||
choices: ['GitHub Actions', 'GitLab CI', 'CircleCI', 'Jenkins', 'Travis CI']
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'triggers',
|
||||
message: 'Pipeline triggers:',
|
||||
choices: [
|
||||
'Push to main/master',
|
||||
'Pull request',
|
||||
'Tag creation',
|
||||
'Manual trigger',
|
||||
'Scheduled (cron)'
|
||||
],
|
||||
default: ['Push to main/master', 'Pull request']
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'cronSchedule',
|
||||
message: 'Cron schedule:',
|
||||
when: (answers) => answers.triggers.includes('Scheduled (cron)'),
|
||||
default: '0 2 * * *',
|
||||
validate: (input) => {
|
||||
// Basic cron validation
|
||||
const parts = input.split(' ');
|
||||
return parts.length === 5 || 'Invalid cron format (5 parts required)';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'stages',
|
||||
message: 'Pipeline stages:',
|
||||
choices: ['Build', 'Test', 'Lint', 'Security scan', 'Deploy'],
|
||||
default: ['Build', 'Test', 'Deploy'],
|
||||
validate: (choices) => choices.length > 0 || 'Select at least one stage'
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'testTypes',
|
||||
message: 'Test types to run:',
|
||||
choices: ['Unit tests', 'Integration tests', 'E2E tests', 'Performance tests'],
|
||||
when: (answers) => answers.stages.includes('Test')
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'securityTools',
|
||||
message: 'Security scanning tools:',
|
||||
choices: ['Snyk', 'Dependabot', 'SonarQube', 'OWASP Dependency Check'],
|
||||
when: (answers) => answers.stages.includes('Security scan')
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'deployEnvironments',
|
||||
message: 'Deployment environments:',
|
||||
choices: ['Development', 'Staging', 'Production'],
|
||||
when: (answers) => answers.stages.includes('Deploy'),
|
||||
default: ['Staging', 'Production']
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'requireApproval',
|
||||
message: 'Require manual approval for production?',
|
||||
default: true,
|
||||
when: (answers) => {
|
||||
return answers.stages.includes('Deploy') &&
|
||||
answers.deployEnvironments?.includes('Production');
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'enableNotifications',
|
||||
message: 'Enable build notifications?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'notificationChannels',
|
||||
message: 'Notification channels:',
|
||||
choices: ['Email', 'Slack', 'Discord', 'Microsoft Teams'],
|
||||
when: (answers) => answers.enableNotifications
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\n✅ CI/CD pipeline configured!');
|
||||
console.log(JSON.stringify(config, null, 2));
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
(async () => {
|
||||
console.log('=== Conditional Prompt Examples ===\n');
|
||||
|
||||
console.log('1. Database Configuration');
|
||||
await conditionalPromptExample();
|
||||
|
||||
console.log('\n2. Deployment Wizard');
|
||||
await deploymentWizard();
|
||||
|
||||
console.log('\n3. Feature Flag Configuration');
|
||||
await featureFlagWizard();
|
||||
|
||||
console.log('\n4. CI/CD Pipeline Setup');
|
||||
await cicdPipelineWizard();
|
||||
|
||||
process.exit(0);
|
||||
})().catch((error) => {
|
||||
if (error.isTtyError) {
|
||||
console.error('❌ Prompt could not be rendered in this environment');
|
||||
} else {
|
||||
console.error('❌ User interrupted prompt');
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
conditionalPromptExample,
|
||||
deploymentWizard,
|
||||
featureFlagWizard,
|
||||
cicdPipelineWizard
|
||||
};
|
||||
104
skills/inquirer-patterns/templates/nodejs/list-prompt.js
Normal file
104
skills/inquirer-patterns/templates/nodejs/list-prompt.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* List Selection Prompt Template
|
||||
*
|
||||
* Use for: Single choice from predefined options
|
||||
* Features: Arrow key navigation, search filtering
|
||||
*/
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
async function listPromptExample() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'framework',
|
||||
message: 'Choose your preferred framework:',
|
||||
choices: [
|
||||
'React',
|
||||
'Vue',
|
||||
'Angular',
|
||||
'Svelte',
|
||||
'Next.js',
|
||||
'Nuxt.js'
|
||||
],
|
||||
default: 'React'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'language',
|
||||
message: 'Choose programming language:',
|
||||
choices: [
|
||||
{ name: 'JavaScript', value: 'js' },
|
||||
{ name: 'TypeScript', value: 'ts' },
|
||||
{ name: 'Python', value: 'py' },
|
||||
{ name: 'Ruby', value: 'rb' },
|
||||
{ name: 'Go', value: 'go' }
|
||||
],
|
||||
default: 'ts'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'packageManager',
|
||||
message: 'Choose package manager:',
|
||||
choices: [
|
||||
{ name: 'npm (Node Package Manager)', value: 'npm', short: 'npm' },
|
||||
{ name: 'yarn (Fast, reliable package manager)', value: 'yarn', short: 'yarn' },
|
||||
{ name: 'pnpm (Fast, disk space efficient)', value: 'pnpm', short: 'pnpm' },
|
||||
{ name: 'bun (All-in-one toolkit)', value: 'bun', short: 'bun' }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'environment',
|
||||
message: 'Select deployment environment:',
|
||||
choices: [
|
||||
new inquirer.Separator('--- Cloud Platforms ---'),
|
||||
'AWS',
|
||||
'Google Cloud',
|
||||
'Azure',
|
||||
new inquirer.Separator('--- Serverless ---'),
|
||||
'Vercel',
|
||||
'Netlify',
|
||||
'Cloudflare Workers',
|
||||
new inquirer.Separator('--- Self-hosted ---'),
|
||||
'Docker',
|
||||
'Kubernetes'
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'database',
|
||||
message: 'Choose database:',
|
||||
choices: [
|
||||
{ name: '🐘 PostgreSQL (Relational)', value: 'postgresql' },
|
||||
{ name: '🐬 MySQL (Relational)', value: 'mysql' },
|
||||
{ name: '🍃 MongoDB (Document)', value: 'mongodb' },
|
||||
{ name: '⚡ Redis (Key-Value)', value: 'redis' },
|
||||
{ name: '📊 SQLite (Embedded)', value: 'sqlite' },
|
||||
{ name: '🔥 Supabase (PostgreSQL + APIs)', value: 'supabase' }
|
||||
],
|
||||
pageSize: 10
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\n✅ Selections:');
|
||||
console.log(JSON.stringify(answers, null, 2));
|
||||
|
||||
return answers;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
listPromptExample()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
if (error.isTtyError) {
|
||||
console.error('❌ Prompt could not be rendered in this environment');
|
||||
} else {
|
||||
console.error('❌ User interrupted prompt');
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { listPromptExample };
|
||||
208
skills/inquirer-patterns/templates/nodejs/password-prompt.js
Normal file
208
skills/inquirer-patterns/templates/nodejs/password-prompt.js
Normal file
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Password Prompt Template
|
||||
*
|
||||
* Use for: Sensitive input (credentials, tokens)
|
||||
* Features: Hidden input, confirmation, validation
|
||||
*/
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
async function passwordPromptExample() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
message: 'Enter your password:',
|
||||
mask: '*',
|
||||
validate: (input) => {
|
||||
if (input.length < 8) {
|
||||
return 'Password must be at least 8 characters';
|
||||
}
|
||||
if (!/[A-Z]/.test(input)) {
|
||||
return 'Password must contain at least one uppercase letter';
|
||||
}
|
||||
if (!/[a-z]/.test(input)) {
|
||||
return 'Password must contain at least one lowercase letter';
|
||||
}
|
||||
if (!/[0-9]/.test(input)) {
|
||||
return 'Password must contain at least one number';
|
||||
}
|
||||
if (!/[!@#$%^&*(),.?":{}|<>]/.test(input)) {
|
||||
return 'Password must contain at least one special character';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'confirmPassword',
|
||||
message: 'Confirm your password:',
|
||||
mask: '*',
|
||||
validate: (input, answers) => {
|
||||
if (input !== answers.password) {
|
||||
return 'Passwords do not match';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'apiKey',
|
||||
message: 'Enter your API key:',
|
||||
mask: '•',
|
||||
validate: (input) => {
|
||||
if (input.length === 0) {
|
||||
return 'API key is required';
|
||||
}
|
||||
// Example: Validate API key format (e.g., sk-...)
|
||||
if (!input.startsWith('sk-') && !input.startsWith('pk-')) {
|
||||
return 'API key must start with "sk-" or "pk-"';
|
||||
}
|
||||
if (input.length < 32) {
|
||||
return 'API key appears to be too short';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'oldPassword',
|
||||
message: 'Enter your old password (for password change):',
|
||||
mask: '*',
|
||||
when: (answers) => {
|
||||
// Only ask for old password if changing password
|
||||
return answers.password && answers.confirmPassword;
|
||||
},
|
||||
validate: (input) => {
|
||||
if (input.length === 0) {
|
||||
return 'Old password is required';
|
||||
}
|
||||
// In real app, you'd verify against stored password
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'encryptionKey',
|
||||
message: 'Enter encryption key (optional):',
|
||||
mask: '#',
|
||||
validate: (input) => {
|
||||
if (input.length === 0) return true; // Optional
|
||||
if (input.length < 16) {
|
||||
return 'Encryption key must be at least 16 characters';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
// Don't log actual passwords!
|
||||
console.log('\n✅ Credentials received (not displayed for security)');
|
||||
console.log('Password strength:', calculatePasswordStrength(answers.password));
|
||||
console.log('API key format:', answers.apiKey.substring(0, 6) + '...');
|
||||
|
||||
// In real app, you'd:
|
||||
// - Hash the password before storing
|
||||
// - Encrypt the API key
|
||||
// - Store securely (not in plain text)
|
||||
|
||||
return {
|
||||
passwordHash: hashPassword(answers.password),
|
||||
apiKeyEncrypted: encryptApiKey(answers.apiKey)
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to calculate password strength
|
||||
function calculatePasswordStrength(password) {
|
||||
let strength = 0;
|
||||
|
||||
if (password.length >= 8) strength++;
|
||||
if (password.length >= 12) strength++;
|
||||
if (/[a-z]/.test(password)) strength++;
|
||||
if (/[A-Z]/.test(password)) strength++;
|
||||
if (/[0-9]/.test(password)) strength++;
|
||||
if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) strength++;
|
||||
|
||||
const levels = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong', 'Very Strong'];
|
||||
return levels[Math.min(strength, levels.length - 1)];
|
||||
}
|
||||
|
||||
// Placeholder for password hashing (use bcrypt in production)
|
||||
function hashPassword(password) {
|
||||
return `[HASHED:${password.length}_chars]`;
|
||||
}
|
||||
|
||||
// Placeholder for API key encryption (use proper encryption in production)
|
||||
function encryptApiKey(apiKey) {
|
||||
return `[ENCRYPTED:${apiKey.substring(0, 6)}...]`;
|
||||
}
|
||||
|
||||
// Example: Secure credential storage
|
||||
async function securePasswordExample() {
|
||||
console.log('\n🔐 Secure Password Setup\n');
|
||||
|
||||
const credentials = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'username',
|
||||
message: 'Username:',
|
||||
validate: (input) => input.length > 0 || 'Username required'
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
message: 'Password:',
|
||||
mask: '*',
|
||||
validate: (input) => {
|
||||
const issues = [];
|
||||
if (input.length < 12) issues.push('at least 12 characters');
|
||||
if (!/[A-Z]/.test(input)) issues.push('an uppercase letter');
|
||||
if (!/[a-z]/.test(input)) issues.push('a lowercase letter');
|
||||
if (!/[0-9]/.test(input)) issues.push('a number');
|
||||
if (!/[!@#$%^&*]/.test(input)) issues.push('a special character (!@#$%^&*)');
|
||||
|
||||
if (issues.length > 0) {
|
||||
return `Password must contain: ${issues.join(', ')}`;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'confirm',
|
||||
message: 'Confirm password:',
|
||||
mask: '*',
|
||||
validate: (input, answers) => {
|
||||
return input === answers.password || 'Passwords do not match';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'remember',
|
||||
message: 'Remember credentials? (stored securely)',
|
||||
default: false
|
||||
}
|
||||
]);
|
||||
|
||||
console.log(`\n✅ Account created for: ${credentials.username}`);
|
||||
console.log(`🔒 Password strength: ${calculatePasswordStrength(credentials.password)}`);
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
passwordPromptExample()
|
||||
.then(() => securePasswordExample())
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
if (error.isTtyError) {
|
||||
console.error('❌ Prompt could not be rendered in this environment');
|
||||
} else {
|
||||
console.error('❌ User interrupted prompt');
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { passwordPromptExample, securePasswordExample };
|
||||
105
skills/inquirer-patterns/templates/nodejs/text-prompt.js
Normal file
105
skills/inquirer-patterns/templates/nodejs/text-prompt.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Text Input Prompt Template
|
||||
*
|
||||
* Use for: Names, emails, URLs, paths, free-form text
|
||||
* Features: Validation, default values, transform
|
||||
*/
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
async function textPromptExample() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'username',
|
||||
message: 'Enter your username:',
|
||||
default: '',
|
||||
validate: (input) => {
|
||||
if (input.length === 0) {
|
||||
return 'Username is required';
|
||||
}
|
||||
if (input.length < 3) {
|
||||
return 'Username must be at least 3 characters';
|
||||
}
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
|
||||
return 'Username can only contain letters, numbers, hyphens, and underscores';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
transformer: (input) => {
|
||||
return input.toLowerCase();
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'email',
|
||||
message: 'Enter your email:',
|
||||
validate: (input) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(input) || 'Invalid email address';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'website',
|
||||
message: 'Enter your website (optional):',
|
||||
default: '',
|
||||
validate: (input) => {
|
||||
if (input.length === 0) return true; // Optional field
|
||||
const urlRegex = /^https?:\/\/.+/;
|
||||
return urlRegex.test(input) || 'Must be a valid URL (http:// or https://)';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'age',
|
||||
message: 'Enter your age:',
|
||||
validate: (input) => {
|
||||
const age = parseInt(input);
|
||||
if (isNaN(age)) {
|
||||
return 'Please enter a valid number';
|
||||
}
|
||||
if (age < 18) {
|
||||
return 'You must be at least 18 years old';
|
||||
}
|
||||
if (age > 120) {
|
||||
return 'Please enter a realistic age';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
filter: (input) => parseInt(input)
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'bio',
|
||||
message: 'Enter a short bio:',
|
||||
validate: (input) => {
|
||||
if (input.length > 200) {
|
||||
return 'Bio must be 200 characters or less';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\n✅ Answers received:');
|
||||
console.log(JSON.stringify(answers, null, 2));
|
||||
|
||||
return answers;
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
textPromptExample()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
if (error.isTtyError) {
|
||||
console.error('❌ Prompt could not be rendered in this environment');
|
||||
} else {
|
||||
console.error('❌ User interrupted prompt');
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { textPromptExample };
|
||||
361
skills/inquirer-patterns/templates/python/autocomplete_prompt.py
Normal file
361
skills/inquirer-patterns/templates/python/autocomplete_prompt.py
Normal file
@@ -0,0 +1,361 @@
|
||||
"""
|
||||
Autocomplete Prompt Template
|
||||
|
||||
Use for: Large option lists with search
|
||||
Features: Type-ahead, fuzzy matching, suggestions
|
||||
"""
|
||||
|
||||
import questionary
|
||||
from questionary import Choice
|
||||
|
||||
|
||||
# Example: Countries list for autocomplete
|
||||
COUNTRIES = [
|
||||
'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola',
|
||||
'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan',
|
||||
'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus',
|
||||
'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia',
|
||||
'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi',
|
||||
'Cambodia', 'Cameroon', 'Canada', 'Cape Verde', 'Chad',
|
||||
'Chile', 'China', 'Colombia', 'Comoros', 'Congo',
|
||||
'Costa Rica', 'Croatia', 'Cuba', 'Cyprus', 'Czech Republic',
|
||||
'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic',
|
||||
'Ecuador', 'Egypt', 'El Salvador', 'Estonia', 'Ethiopia',
|
||||
'Fiji', 'Finland', 'France', 'Gabon', 'Gambia',
|
||||
'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada',
|
||||
'Guatemala', 'Guinea', 'Guyana', 'Haiti', 'Honduras',
|
||||
'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran',
|
||||
'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica',
|
||||
'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kuwait',
|
||||
'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia',
|
||||
'Libya', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi',
|
||||
'Malaysia', 'Maldives', 'Mali', 'Malta', 'Mexico',
|
||||
'Moldova', 'Monaco', 'Mongolia', 'Morocco', 'Mozambique',
|
||||
'Myanmar', 'Namibia', 'Nepal', 'Netherlands', 'New Zealand',
|
||||
'Nicaragua', 'Niger', 'Nigeria', 'Norway', 'Oman',
|
||||
'Pakistan', 'Panama', 'Paraguay', 'Peru', 'Philippines',
|
||||
'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia',
|
||||
'Rwanda', 'Saudi Arabia', 'Senegal', 'Serbia', 'Singapore',
|
||||
'Slovakia', 'Slovenia', 'Somalia', 'South Africa', 'South Korea',
|
||||
'Spain', 'Sri Lanka', 'Sudan', 'Sweden', 'Switzerland',
|
||||
'Syria', 'Taiwan', 'Tanzania', 'Thailand', 'Togo',
|
||||
'Tunisia', 'Turkey', 'Uganda', 'Ukraine', 'United Arab Emirates',
|
||||
'United Kingdom', 'United States', 'Uruguay', 'Uzbekistan',
|
||||
'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe'
|
||||
]
|
||||
|
||||
# Example: Popular packages
|
||||
POPULAR_PACKAGES = [
|
||||
'express', 'react', 'vue', 'angular', 'next', 'nuxt',
|
||||
'axios', 'lodash', 'moment', 'dayjs', 'uuid', 'dotenv',
|
||||
'typescript', 'eslint', 'prettier', 'jest', 'mocha', 'chai',
|
||||
'webpack', 'vite', 'rollup', 'babel', 'esbuild',
|
||||
'socket.io', 'redis', 'mongodb', 'mongoose', 'sequelize',
|
||||
'prisma', 'typeorm', 'knex', 'pg', 'mysql2',
|
||||
'bcrypt', 'jsonwebtoken', 'passport', 'helmet', 'cors',
|
||||
'multer', 'sharp', 'puppeteer', 'playwright', 'cheerio'
|
||||
]
|
||||
|
||||
|
||||
def autocomplete_prompt_example():
|
||||
"""Example autocomplete prompts"""
|
||||
|
||||
print("\n🔍 Autocomplete Example\n")
|
||||
|
||||
# Country selection with autocomplete
|
||||
country = questionary.autocomplete(
|
||||
"Select your country:",
|
||||
choices=COUNTRIES,
|
||||
validate=lambda text: len(text) > 0 or "Please select a country"
|
||||
).ask()
|
||||
|
||||
# Package selection
|
||||
package = questionary.autocomplete(
|
||||
"Search for an npm package:",
|
||||
choices=POPULAR_PACKAGES
|
||||
).ask()
|
||||
|
||||
# Cities based on country (conditional)
|
||||
city = None
|
||||
cities_by_country = {
|
||||
'United States': ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'],
|
||||
'United Kingdom': ['London', 'Manchester', 'Birmingham', 'Glasgow', 'Liverpool'],
|
||||
'Canada': ['Toronto', 'Vancouver', 'Montreal', 'Calgary', 'Ottawa'],
|
||||
'Australia': ['Sydney', 'Melbourne', 'Brisbane', 'Perth', 'Adelaide'],
|
||||
'Germany': ['Berlin', 'Munich', 'Hamburg', 'Frankfurt', 'Cologne']
|
||||
}
|
||||
|
||||
if country in cities_by_country:
|
||||
city = questionary.autocomplete(
|
||||
"Select city:",
|
||||
choices=cities_by_country[country]
|
||||
).ask()
|
||||
|
||||
answers = {
|
||||
'country': country,
|
||||
'package': package,
|
||||
'city': city
|
||||
}
|
||||
|
||||
print("\n✅ Selections:")
|
||||
import json
|
||||
print(json.dumps(answers, indent=2))
|
||||
|
||||
return answers
|
||||
|
||||
|
||||
def framework_search_example():
|
||||
"""Example: Framework/library search with descriptions"""
|
||||
|
||||
print("\n🔎 Framework Search Example\n")
|
||||
|
||||
frameworks = [
|
||||
'React - UI library by Facebook',
|
||||
'Vue.js - Progressive JavaScript framework',
|
||||
'Angular - Platform for building web apps',
|
||||
'Svelte - Cybernetically enhanced web apps',
|
||||
'Next.js - React framework with SSR',
|
||||
'Nuxt.js - Vue.js framework with SSR',
|
||||
'Remix - Full stack web framework',
|
||||
'SvelteKit - Svelte framework',
|
||||
'Express - Fast Node.js web framework',
|
||||
'Fastify - Fast and low overhead web framework',
|
||||
'NestJS - Progressive Node.js framework',
|
||||
'Koa - Expressive middleware for Node.js'
|
||||
]
|
||||
|
||||
framework = questionary.autocomplete(
|
||||
"Search for a framework:",
|
||||
choices=frameworks
|
||||
).ask()
|
||||
|
||||
# Extract value (remove description)
|
||||
framework_name = framework.split(' - ')[0] if ' - ' in framework else framework
|
||||
|
||||
print(f"\n✅ Selected: {framework_name}")
|
||||
|
||||
return {'framework': framework_name}
|
||||
|
||||
|
||||
def command_search_example():
|
||||
"""Example: Command search with emojis and categories"""
|
||||
|
||||
print("\n⌨️ Command Search Example\n")
|
||||
|
||||
commands = [
|
||||
'📦 install - Install dependencies',
|
||||
'🚀 start - Start development server',
|
||||
'🏗️ build - Build for production',
|
||||
'🧪 test - Run tests',
|
||||
'🔍 lint - Check code quality',
|
||||
'✨ format - Format code',
|
||||
'📝 generate - Generate files',
|
||||
'🔄 update - Update dependencies',
|
||||
'🧹 clean - Clean build artifacts',
|
||||
'🚢 deploy - Deploy application',
|
||||
'📊 analyze - Analyze bundle size',
|
||||
'🐛 debug - Start debugger'
|
||||
]
|
||||
|
||||
command = questionary.autocomplete(
|
||||
"Search for a command:",
|
||||
choices=commands
|
||||
).ask()
|
||||
|
||||
# Extract command name
|
||||
command_name = command.split(' - ')[0].split(' ', 1)[1] if ' - ' in command else command
|
||||
|
||||
print(f"\n✅ Running: {command_name}")
|
||||
|
||||
return {'command': command_name}
|
||||
|
||||
|
||||
def api_endpoint_search():
|
||||
"""Example: API endpoint search"""
|
||||
|
||||
print("\n🔍 API Endpoint Search\n")
|
||||
|
||||
endpoints = [
|
||||
'GET /users - List all users',
|
||||
'GET /users/:id - Get user by ID',
|
||||
'POST /users - Create new user',
|
||||
'PUT /users/:id - Update user',
|
||||
'DELETE /users/:id - Delete user',
|
||||
'GET /posts - List all posts',
|
||||
'GET /posts/:id - Get post by ID',
|
||||
'POST /posts - Create new post',
|
||||
'GET /comments - List comments',
|
||||
'POST /auth/login - User login',
|
||||
'POST /auth/register - User registration',
|
||||
'POST /auth/logout - User logout'
|
||||
]
|
||||
|
||||
endpoint = questionary.autocomplete(
|
||||
"Search API endpoints:",
|
||||
choices=endpoints
|
||||
).ask()
|
||||
|
||||
# Extract endpoint path
|
||||
endpoint_path = endpoint.split(' - ')[0] if ' - ' in endpoint else endpoint
|
||||
|
||||
print(f"\n✅ Selected endpoint: {endpoint_path}")
|
||||
|
||||
return {'endpoint': endpoint_path}
|
||||
|
||||
|
||||
def technology_stack_selection():
|
||||
"""Example: Building technology stack with multiple autocomplete prompts"""
|
||||
|
||||
print("\n🛠️ Technology Stack Selection\n")
|
||||
|
||||
# Programming languages
|
||||
languages = [
|
||||
'JavaScript', 'TypeScript', 'Python', 'Go', 'Rust',
|
||||
'Java', 'C++', 'Ruby', 'PHP', 'Swift', 'Kotlin'
|
||||
]
|
||||
|
||||
language = questionary.autocomplete(
|
||||
"Choose programming language:",
|
||||
choices=languages
|
||||
).ask()
|
||||
|
||||
# Frameworks based on language
|
||||
frameworks_by_language = {
|
||||
'JavaScript': ['React', 'Vue', 'Angular', 'Svelte', 'Express', 'Fastify'],
|
||||
'TypeScript': ['Next.js', 'Nest.js', 'Angular', 'Remix', 'tRPC'],
|
||||
'Python': ['Django', 'Flask', 'FastAPI', 'Tornado', 'Sanic'],
|
||||
'Go': ['Gin', 'Echo', 'Fiber', 'Chi', 'Gorilla'],
|
||||
'Rust': ['Actix', 'Rocket', 'Axum', 'Warp', 'Tide'],
|
||||
'Java': ['Spring', 'Micronaut', 'Quarkus', 'Vert.x'],
|
||||
'Ruby': ['Ruby on Rails', 'Sinatra', 'Hanami']
|
||||
}
|
||||
|
||||
framework_choices = frameworks_by_language.get(language, ['None'])
|
||||
framework = questionary.autocomplete(
|
||||
f"Choose {language} framework:",
|
||||
choices=framework_choices
|
||||
).ask()
|
||||
|
||||
# Databases
|
||||
databases = [
|
||||
'PostgreSQL', 'MySQL', 'MongoDB', 'Redis', 'SQLite',
|
||||
'Cassandra', 'DynamoDB', 'CouchDB', 'Neo4j', 'InfluxDB'
|
||||
]
|
||||
|
||||
database = questionary.autocomplete(
|
||||
"Choose database:",
|
||||
choices=databases
|
||||
).ask()
|
||||
|
||||
# Cloud providers
|
||||
cloud_providers = [
|
||||
'AWS', 'Google Cloud', 'Azure', 'DigitalOcean',
|
||||
'Heroku', 'Vercel', 'Netlify', 'Cloudflare'
|
||||
]
|
||||
|
||||
cloud = questionary.autocomplete(
|
||||
"Choose cloud provider:",
|
||||
choices=cloud_providers
|
||||
).ask()
|
||||
|
||||
stack = {
|
||||
'language': language,
|
||||
'framework': framework,
|
||||
'database': database,
|
||||
'cloud': cloud
|
||||
}
|
||||
|
||||
print("\n✅ Technology Stack:")
|
||||
import json
|
||||
print(json.dumps(stack, indent=2))
|
||||
|
||||
return stack
|
||||
|
||||
|
||||
def file_path_autocomplete():
|
||||
"""Example: File path autocomplete (simulated)"""
|
||||
|
||||
print("\n📁 File Path Autocomplete Example\n")
|
||||
|
||||
# Common project directories
|
||||
directories = [
|
||||
'/home/user/projects/web-app',
|
||||
'/home/user/projects/api-server',
|
||||
'/home/user/projects/cli-tool',
|
||||
'/var/www/html',
|
||||
'/opt/applications',
|
||||
'~/Documents/code',
|
||||
'~/workspace/nodejs',
|
||||
'~/workspace/python'
|
||||
]
|
||||
|
||||
project_path = questionary.autocomplete(
|
||||
"Select project directory:",
|
||||
choices=directories
|
||||
).ask()
|
||||
|
||||
# Common config files
|
||||
config_files = [
|
||||
'package.json',
|
||||
'tsconfig.json',
|
||||
'jest.config.js',
|
||||
'webpack.config.js',
|
||||
'.env',
|
||||
'.gitignore',
|
||||
'README.md',
|
||||
'Dockerfile',
|
||||
'docker-compose.yml'
|
||||
]
|
||||
|
||||
config_file = questionary.autocomplete(
|
||||
"Select config file:",
|
||||
choices=config_files
|
||||
).ask()
|
||||
|
||||
result = {
|
||||
'projectPath': project_path,
|
||||
'configFile': config_file
|
||||
}
|
||||
|
||||
print("\n✅ Selected:")
|
||||
import json
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
"""Run autocomplete prompt examples"""
|
||||
try:
|
||||
print("=== Autocomplete Examples ===")
|
||||
|
||||
# Example 1: Basic autocomplete
|
||||
autocomplete_prompt_example()
|
||||
|
||||
# Example 2: Framework search
|
||||
framework_search_example()
|
||||
|
||||
# Example 3: Command search
|
||||
command_search_example()
|
||||
|
||||
# Example 4: API endpoint search
|
||||
api_endpoint_search()
|
||||
|
||||
# Example 5: Technology stack
|
||||
technology_stack_selection()
|
||||
|
||||
# Example 6: File path autocomplete
|
||||
file_path_autocomplete()
|
||||
|
||||
print("\n✅ Autocomplete examples complete!")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n❌ User interrupted prompt")
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n\n❌ Error: {e}")
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
310
skills/inquirer-patterns/templates/python/checkbox_prompt.py
Normal file
310
skills/inquirer-patterns/templates/python/checkbox_prompt.py
Normal file
@@ -0,0 +1,310 @@
|
||||
"""
|
||||
Checkbox Prompt Template
|
||||
|
||||
Use for: Multiple selections from options
|
||||
Features: Space to toggle, Enter to confirm
|
||||
"""
|
||||
|
||||
import questionary
|
||||
from questionary import Choice, Separator, ValidationError, Validator
|
||||
|
||||
|
||||
class MinimumChoicesValidator(Validator):
|
||||
"""Validator to ensure minimum number of choices selected"""
|
||||
|
||||
def __init__(self, minimum=1, message=None):
|
||||
self.minimum = minimum
|
||||
self.message = message or f"You must select at least {minimum} option(s)"
|
||||
|
||||
def validate(self, document):
|
||||
# document.text contains selected choices as list
|
||||
if len(document.text) < self.minimum:
|
||||
raise ValidationError(
|
||||
message=self.message,
|
||||
cursor_position=0
|
||||
)
|
||||
|
||||
|
||||
class MaximumChoicesValidator(Validator):
|
||||
"""Validator to ensure maximum number of choices selected"""
|
||||
|
||||
def __init__(self, maximum=10, message=None):
|
||||
self.maximum = maximum
|
||||
self.message = message or f"Please select no more than {maximum} options"
|
||||
|
||||
def validate(self, document):
|
||||
if len(document.text) > self.maximum:
|
||||
raise ValidationError(
|
||||
message=self.message,
|
||||
cursor_position=0
|
||||
)
|
||||
|
||||
|
||||
def checkbox_prompt_example():
|
||||
"""Example checkbox prompts"""
|
||||
|
||||
print("\n☑️ Checkbox Selection Example\n")
|
||||
|
||||
# Simple checkbox
|
||||
features = questionary.checkbox(
|
||||
"Select features to include:",
|
||||
choices=[
|
||||
'Authentication',
|
||||
'Authorization',
|
||||
'Database Integration',
|
||||
'API Documentation',
|
||||
'Testing Suite',
|
||||
'CI/CD Pipeline',
|
||||
'Monitoring',
|
||||
'Logging'
|
||||
],
|
||||
validate=lambda choices: len(choices) > 0 or "You must select at least one feature"
|
||||
).ask()
|
||||
|
||||
# Checkbox with default selections
|
||||
dev_tools = questionary.checkbox(
|
||||
"Select development tools:",
|
||||
choices=[
|
||||
Choice('ESLint (Linting)', value='eslint', checked=True),
|
||||
Choice('Prettier (Formatting)', value='prettier', checked=True),
|
||||
Choice('Jest (Testing)', value='jest'),
|
||||
Choice('Husky (Git Hooks)', value='husky'),
|
||||
Choice('TypeDoc (Documentation)', value='typedoc'),
|
||||
Choice('Webpack (Bundling)', value='webpack')
|
||||
]
|
||||
).ask()
|
||||
|
||||
# Checkbox with separators and checked defaults
|
||||
plugins = questionary.checkbox(
|
||||
"Select plugins to install:",
|
||||
choices=[
|
||||
Separator('=== Essential ==='),
|
||||
Choice('dotenv - Environment variables', value='dotenv', checked=True),
|
||||
Choice('axios - HTTP client', value='axios', checked=True),
|
||||
Separator('=== Utilities ==='),
|
||||
Choice('lodash - Utility functions', value='lodash'),
|
||||
Choice('dayjs - Date manipulation', value='dayjs'),
|
||||
Choice('uuid - Unique IDs', value='uuid'),
|
||||
Separator('=== Validation ==='),
|
||||
Choice('joi - Schema validation', value='joi'),
|
||||
Choice('zod - TypeScript-first validation', value='zod'),
|
||||
Separator('=== Advanced ==='),
|
||||
Choice('bull - Job queues', value='bull'),
|
||||
Choice('socket.io - WebSockets', value='socket.io')
|
||||
],
|
||||
validate=MaximumChoicesValidator(maximum=10)
|
||||
).ask()
|
||||
|
||||
# Checkbox with emojis
|
||||
permissions = questionary.checkbox(
|
||||
"Grant the following permissions:",
|
||||
choices=[
|
||||
Choice('📁 Read files', value='read', checked=True),
|
||||
Choice('✏️ Write files', value='write'),
|
||||
Choice('🗑️ Delete files', value='delete'),
|
||||
Choice('🌐 Network access', value='network', checked=True),
|
||||
Choice('🖥️ System commands', value='system'),
|
||||
Choice('🔒 Keychain access', value='keychain')
|
||||
]
|
||||
).ask()
|
||||
|
||||
# Validate permissions logic
|
||||
if 'delete' in permissions and 'write' not in permissions:
|
||||
print("\n⚠️ Warning: Delete permission requires write permission")
|
||||
permissions.append('write')
|
||||
|
||||
# Checkbox with validation
|
||||
environments = questionary.checkbox(
|
||||
"Select deployment environments:",
|
||||
choices=[
|
||||
Choice('Development', value='dev', checked=True),
|
||||
Choice('Staging', value='staging'),
|
||||
Choice('Production', value='prod'),
|
||||
Choice('Testing', value='test', checked=True)
|
||||
],
|
||||
validate=lambda choices: (
|
||||
'dev' in choices or "Development environment is required"
|
||||
)
|
||||
).ask()
|
||||
|
||||
# Additional validation
|
||||
if 'prod' in environments and 'staging' not in environments:
|
||||
print("\n⚠️ Warning: Staging environment is recommended before production")
|
||||
|
||||
answers = {
|
||||
'features': features,
|
||||
'devTools': dev_tools,
|
||||
'plugins': plugins,
|
||||
'permissions': permissions,
|
||||
'environments': environments
|
||||
}
|
||||
|
||||
print("\n✅ Selected options:")
|
||||
import json
|
||||
print(json.dumps(answers, indent=2))
|
||||
|
||||
# Example: Process selections
|
||||
print("\n📦 Installing selected features...")
|
||||
for feature in features:
|
||||
print(f" - {feature}")
|
||||
|
||||
return answers
|
||||
|
||||
|
||||
def grouped_checkbox_example():
|
||||
"""Example with logically grouped checkboxes"""
|
||||
|
||||
print("\n📂 Grouped Checkbox Example\n")
|
||||
|
||||
security_features = questionary.checkbox(
|
||||
"Select security features:",
|
||||
choices=[
|
||||
Separator('=== Authentication ==='),
|
||||
Choice('JWT Tokens', value='jwt', checked=True),
|
||||
Choice('OAuth 2.0', value='oauth'),
|
||||
Choice('Session Management', value='session'),
|
||||
Choice('Two-Factor Auth', value='2fa'),
|
||||
Separator('=== Authorization ==='),
|
||||
Choice('Role-Based Access Control', value='rbac', checked=True),
|
||||
Choice('Permission System', value='permissions', checked=True),
|
||||
Choice('API Key Management', value='api-keys'),
|
||||
Separator('=== Security ==='),
|
||||
Choice('Rate Limiting', value='rate-limit', checked=True),
|
||||
Choice('CORS Configuration', value='cors', checked=True),
|
||||
Choice('Input Sanitization', value='sanitization', checked=True),
|
||||
Choice('SQL Injection Prevention', value='sql-prevent', checked=True),
|
||||
Choice('XSS Protection', value='xss-protect', checked=True),
|
||||
Separator('=== Encryption ==='),
|
||||
Choice('Data Encryption at Rest', value='encrypt-rest'),
|
||||
Choice('SSL/TLS', value='ssl', checked=True),
|
||||
Choice('Password Hashing', value='hash', checked=True)
|
||||
],
|
||||
validate=MinimumChoicesValidator(minimum=3, message="Select at least 3 security features")
|
||||
).ask()
|
||||
|
||||
print(f"\n✅ Selected {len(security_features)} security features")
|
||||
return {'securityFeatures': security_features}
|
||||
|
||||
|
||||
def dependent_checkbox_example():
|
||||
"""Example with checkboxes that depend on previous selections"""
|
||||
|
||||
print("\n🔗 Dependent Checkbox Example\n")
|
||||
|
||||
# First checkbox: Select cloud providers
|
||||
cloud_providers = questionary.checkbox(
|
||||
"Select cloud providers:",
|
||||
choices=[
|
||||
Choice('☁️ AWS', value='aws'),
|
||||
Choice('☁️ Google Cloud', value='gcp'),
|
||||
Choice('☁️ Azure', value='azure'),
|
||||
Choice('☁️ DigitalOcean', value='do')
|
||||
],
|
||||
validate=lambda c: len(c) > 0 or "Select at least one cloud provider"
|
||||
).ask()
|
||||
|
||||
# Second checkbox: AWS services (only if AWS selected)
|
||||
aws_services = []
|
||||
if 'aws' in cloud_providers:
|
||||
aws_services = questionary.checkbox(
|
||||
"Select AWS services:",
|
||||
choices=[
|
||||
Choice('EC2 - Virtual Servers', value='ec2'),
|
||||
Choice('Lambda - Serverless', value='lambda'),
|
||||
Choice('S3 - Object Storage', value='s3', checked=True),
|
||||
Choice('RDS - Databases', value='rds'),
|
||||
Choice('CloudFront - CDN', value='cloudfront')
|
||||
]
|
||||
).ask()
|
||||
|
||||
# Third checkbox: GCP services (only if GCP selected)
|
||||
gcp_services = []
|
||||
if 'gcp' in cloud_providers:
|
||||
gcp_services = questionary.checkbox(
|
||||
"Select GCP services:",
|
||||
choices=[
|
||||
Choice('Compute Engine', value='compute'),
|
||||
Choice('Cloud Functions', value='functions'),
|
||||
Choice('Cloud Storage', value='storage', checked=True),
|
||||
Choice('Cloud SQL', value='sql'),
|
||||
Choice('Cloud CDN', value='cdn')
|
||||
]
|
||||
).ask()
|
||||
|
||||
result = {
|
||||
'cloudProviders': cloud_providers,
|
||||
'awsServices': aws_services,
|
||||
'gcpServices': gcp_services
|
||||
}
|
||||
|
||||
print("\n✅ Configuration complete:")
|
||||
import json
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def conditional_validation_example():
|
||||
"""Example with conditional validation logic"""
|
||||
|
||||
print("\n🔍 Conditional Validation Example\n")
|
||||
|
||||
database_features = questionary.checkbox(
|
||||
"Select database features:",
|
||||
choices=[
|
||||
Choice('Connection Pooling', value='pool', checked=True),
|
||||
Choice('Migrations', value='migrations', checked=True),
|
||||
Choice('Transactions', value='transactions'),
|
||||
Choice('Replication', value='replication'),
|
||||
Choice('Sharding', value='sharding'),
|
||||
Choice('Caching', value='caching')
|
||||
]
|
||||
).ask()
|
||||
|
||||
# Conditional logic: Sharding requires replication
|
||||
if 'sharding' in database_features and 'replication' not in database_features:
|
||||
print("\n⚠️ Sharding requires replication. Adding replication...")
|
||||
database_features.append('replication')
|
||||
|
||||
# Conditional logic: Caching works best with pooling
|
||||
if 'caching' in database_features and 'pool' not in database_features:
|
||||
add_pooling = questionary.confirm(
|
||||
"Caching works best with connection pooling. Add it?",
|
||||
default=True
|
||||
).ask()
|
||||
if add_pooling:
|
||||
database_features.append('pool')
|
||||
|
||||
print(f"\n✅ Selected {len(database_features)} database features")
|
||||
return {'databaseFeatures': database_features}
|
||||
|
||||
|
||||
def main():
|
||||
"""Run checkbox prompt examples"""
|
||||
try:
|
||||
print("=== Checkbox Prompt Examples ===")
|
||||
|
||||
# Example 1: Basic checkbox selections
|
||||
checkbox_prompt_example()
|
||||
|
||||
# Example 2: Grouped checkboxes
|
||||
grouped_checkbox_example()
|
||||
|
||||
# Example 3: Dependent checkboxes
|
||||
dependent_checkbox_example()
|
||||
|
||||
# Example 4: Conditional validation
|
||||
conditional_validation_example()
|
||||
|
||||
print("\n✅ Checkbox examples complete!")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n❌ User interrupted prompt")
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n\n❌ Error: {e}")
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
501
skills/inquirer-patterns/templates/python/conditional_prompt.py
Normal file
501
skills/inquirer-patterns/templates/python/conditional_prompt.py
Normal file
@@ -0,0 +1,501 @@
|
||||
"""
|
||||
Conditional Prompt Template
|
||||
|
||||
Use for: Dynamic forms based on previous answers
|
||||
Features: Skip logic, dependent questions, branching
|
||||
"""
|
||||
|
||||
import questionary
|
||||
from questionary import Choice, Separator
|
||||
|
||||
|
||||
def conditional_prompt_example():
|
||||
"""Example conditional prompts"""
|
||||
|
||||
print("\n🔀 Conditional Prompt Example\n")
|
||||
|
||||
# First question: Use database?
|
||||
use_database = questionary.confirm(
|
||||
"Do you want to use a database?",
|
||||
default=True
|
||||
).ask()
|
||||
|
||||
database_config = {}
|
||||
|
||||
if use_database:
|
||||
# Database type
|
||||
database_type = questionary.select(
|
||||
"Select database type:",
|
||||
choices=['PostgreSQL', 'MySQL', 'MongoDB', 'SQLite', 'Redis']
|
||||
).ask()
|
||||
|
||||
database_config['databaseType'] = database_type
|
||||
|
||||
# Host and port (not for SQLite)
|
||||
if database_type != 'SQLite':
|
||||
database_host = questionary.text(
|
||||
"Database host:",
|
||||
default="localhost"
|
||||
).ask()
|
||||
|
||||
# Default ports based on database type
|
||||
default_ports = {
|
||||
'PostgreSQL': '5432',
|
||||
'MySQL': '3306',
|
||||
'MongoDB': '27017',
|
||||
'Redis': '6379'
|
||||
}
|
||||
|
||||
database_port = questionary.text(
|
||||
"Database port:",
|
||||
default=default_ports.get(database_type, '5432')
|
||||
).ask()
|
||||
|
||||
database_config['host'] = database_host
|
||||
database_config['port'] = database_port
|
||||
|
||||
# Database name
|
||||
database_name = questionary.text(
|
||||
"Database name:",
|
||||
validate=lambda text: len(text) > 0 or "Database name required"
|
||||
).ask()
|
||||
|
||||
database_config['databaseName'] = database_name
|
||||
|
||||
# Authentication
|
||||
use_authentication = questionary.confirm(
|
||||
"Do you want to use authentication?",
|
||||
default=True
|
||||
).ask()
|
||||
|
||||
if use_authentication:
|
||||
database_username = questionary.text(
|
||||
"Database username:",
|
||||
validate=lambda text: len(text) > 0 or "Username required"
|
||||
).ask()
|
||||
|
||||
database_password = questionary.password(
|
||||
"Database password:"
|
||||
).ask()
|
||||
|
||||
database_config['useAuthentication'] = True
|
||||
database_config['username'] = database_username
|
||||
# Don't store actual password in answers
|
||||
|
||||
# SSL (only for remote hosts)
|
||||
if database_type != 'SQLite' and database_config.get('host') != 'localhost':
|
||||
use_ssl = questionary.confirm(
|
||||
"Use SSL connection?",
|
||||
default=False
|
||||
).ask()
|
||||
|
||||
if use_ssl:
|
||||
ssl_cert_path = questionary.text(
|
||||
"Path to SSL certificate:",
|
||||
validate=lambda text: len(text) > 0 or "SSL certificate path required"
|
||||
).ask()
|
||||
|
||||
database_config['useSSL'] = True
|
||||
database_config['sslCertPath'] = ssl_cert_path
|
||||
|
||||
answers = {
|
||||
'useDatabase': use_database,
|
||||
'databaseConfig': database_config if use_database else None
|
||||
}
|
||||
|
||||
print("\n✅ Configuration:")
|
||||
import json
|
||||
print(json.dumps(answers, indent=2))
|
||||
|
||||
return answers
|
||||
|
||||
|
||||
def deployment_wizard():
|
||||
"""Example: Deployment configuration wizard"""
|
||||
|
||||
print("\n🚀 Deployment Configuration Wizard\n")
|
||||
|
||||
# Environment
|
||||
environment = questionary.select(
|
||||
"Select deployment environment:",
|
||||
choices=['Development', 'Staging', 'Production']
|
||||
).ask()
|
||||
|
||||
config = {'environment': environment}
|
||||
|
||||
# Docker
|
||||
use_docker = questionary.confirm(
|
||||
"Deploy using Docker?",
|
||||
default=True
|
||||
).ask()
|
||||
|
||||
config['useDocker'] = use_docker
|
||||
|
||||
if use_docker:
|
||||
docker_image = questionary.text(
|
||||
"Docker image name:",
|
||||
default="myapp:latest",
|
||||
validate=lambda text: len(text) > 0 or "Docker image name required"
|
||||
).ask()
|
||||
|
||||
config['dockerImage'] = docker_image
|
||||
|
||||
registry = questionary.select(
|
||||
"Container registry:",
|
||||
choices=[
|
||||
'Docker Hub',
|
||||
'GitHub Container Registry',
|
||||
'AWS ECR',
|
||||
'Google Artifact Registry'
|
||||
]
|
||||
).ask()
|
||||
|
||||
config['registry'] = registry
|
||||
|
||||
# Platform
|
||||
platform = questionary.select(
|
||||
"Deployment platform:",
|
||||
choices=[
|
||||
'AWS', 'Google Cloud', 'Azure',
|
||||
'DigitalOcean', 'Vercel', 'Netlify', 'Self-hosted'
|
||||
]
|
||||
).ask()
|
||||
|
||||
config['platform'] = platform
|
||||
|
||||
# Platform-specific configuration
|
||||
if platform == 'AWS':
|
||||
aws_service = questionary.select(
|
||||
"AWS service:",
|
||||
choices=['ECS', 'EKS', 'Lambda', 'Elastic Beanstalk', 'EC2']
|
||||
).ask()
|
||||
config['awsService'] = aws_service
|
||||
|
||||
# Auto-scaling (only for certain services)
|
||||
if aws_service in ['ECS', 'EKS']:
|
||||
auto_scale = questionary.confirm(
|
||||
"Enable auto-scaling?",
|
||||
default=True
|
||||
).ask()
|
||||
|
||||
config['autoScale'] = auto_scale
|
||||
|
||||
if auto_scale:
|
||||
min_instances = questionary.text(
|
||||
"Minimum instances:",
|
||||
default="1",
|
||||
validate=lambda text: text.isdigit() and int(text) > 0 or "Must be at least 1"
|
||||
).ask()
|
||||
|
||||
max_instances = questionary.text(
|
||||
"Maximum instances:",
|
||||
default="10",
|
||||
validate=lambda text: text.isdigit() and int(text) >= int(min_instances) or f"Must be at least {min_instances}"
|
||||
).ask()
|
||||
|
||||
config['minInstances'] = int(min_instances)
|
||||
config['maxInstances'] = int(max_instances)
|
||||
|
||||
elif platform == 'Google Cloud':
|
||||
gcp_service = questionary.select(
|
||||
"Google Cloud service:",
|
||||
choices=['Cloud Run', 'GKE', 'App Engine', 'Compute Engine']
|
||||
).ask()
|
||||
config['gcpService'] = gcp_service
|
||||
|
||||
# CDN (only for production)
|
||||
if environment == 'Production':
|
||||
use_cdn = questionary.confirm(
|
||||
"Use CDN for static assets?",
|
||||
default=True
|
||||
).ask()
|
||||
|
||||
config['useCDN'] = use_cdn
|
||||
|
||||
if use_cdn:
|
||||
cdn_provider = questionary.select(
|
||||
"CDN provider:",
|
||||
choices=['CloudFlare', 'AWS CloudFront', 'Google Cloud CDN', 'Azure CDN']
|
||||
).ask()
|
||||
config['cdnProvider'] = cdn_provider
|
||||
|
||||
# Monitoring
|
||||
setup_monitoring = questionary.confirm(
|
||||
"Setup monitoring?",
|
||||
default=True
|
||||
).ask()
|
||||
|
||||
if setup_monitoring:
|
||||
monitoring_tools = questionary.checkbox(
|
||||
"Select monitoring tools:",
|
||||
choices=['Prometheus', 'Grafana', 'Datadog', 'New Relic', 'Sentry'],
|
||||
validate=lambda choices: len(choices) > 0 or "Select at least one tool"
|
||||
).ask()
|
||||
|
||||
config['monitoringTools'] = monitoring_tools
|
||||
|
||||
print("\n✅ Deployment configuration complete!")
|
||||
import json
|
||||
print(json.dumps(config, indent=2))
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def feature_flag_wizard():
|
||||
"""Example: Feature flag configuration"""
|
||||
|
||||
print("\n🎛️ Feature Flag Configuration\n")
|
||||
|
||||
# Feature name
|
||||
feature_name = questionary.text(
|
||||
"Feature name:",
|
||||
validate=lambda text: text and text.replace('-', '').replace('_', '').islower() or "Use lowercase, hyphens, underscores only"
|
||||
).ask()
|
||||
|
||||
# Enabled by default
|
||||
enabled_by_default = questionary.confirm(
|
||||
"Enabled by default?",
|
||||
default=False
|
||||
).ask()
|
||||
|
||||
config = {
|
||||
'featureName': feature_name,
|
||||
'enabledByDefault': enabled_by_default
|
||||
}
|
||||
|
||||
# Rollout strategy
|
||||
rollout_strategy = questionary.select(
|
||||
"Rollout strategy:",
|
||||
choices=[
|
||||
'All users',
|
||||
'Percentage rollout',
|
||||
'User targeting',
|
||||
'Beta users only',
|
||||
'Manual control'
|
||||
]
|
||||
).ask()
|
||||
|
||||
config['rolloutStrategy'] = rollout_strategy
|
||||
|
||||
# Percentage rollout
|
||||
if rollout_strategy == 'Percentage rollout':
|
||||
rollout_percentage = questionary.text(
|
||||
"Rollout percentage (0-100):",
|
||||
default="10",
|
||||
validate=lambda text: text.isdigit() and 0 <= int(text) <= 100 or "Must be between 0 and 100"
|
||||
).ask()
|
||||
|
||||
config['rolloutPercentage'] = int(rollout_percentage)
|
||||
|
||||
# User targeting
|
||||
if rollout_strategy == 'User targeting':
|
||||
target_user_groups = questionary.checkbox(
|
||||
"Target user groups:",
|
||||
choices=[
|
||||
'Beta testers',
|
||||
'Premium users',
|
||||
'Internal team',
|
||||
'Early adopters',
|
||||
'Specific regions'
|
||||
],
|
||||
validate=lambda choices: len(choices) > 0 or "Select at least one group"
|
||||
).ask()
|
||||
|
||||
config['targetUserGroups'] = target_user_groups
|
||||
|
||||
# Specific regions
|
||||
if 'Specific regions' in target_user_groups:
|
||||
target_regions = questionary.checkbox(
|
||||
"Target regions:",
|
||||
choices=[
|
||||
'North America',
|
||||
'Europe',
|
||||
'Asia Pacific',
|
||||
'South America',
|
||||
'Africa'
|
||||
]
|
||||
).ask()
|
||||
|
||||
config['targetRegions'] = target_regions
|
||||
|
||||
# Metrics
|
||||
enable_metrics = questionary.confirm(
|
||||
"Track feature usage metrics?",
|
||||
default=True
|
||||
).ask()
|
||||
|
||||
if enable_metrics:
|
||||
metrics = questionary.checkbox(
|
||||
"Select metrics to track:",
|
||||
choices=[
|
||||
'Usage count',
|
||||
'User adoption rate',
|
||||
'Performance impact',
|
||||
'Error rate',
|
||||
'User feedback'
|
||||
]
|
||||
).ask()
|
||||
|
||||
config['metrics'] = metrics
|
||||
|
||||
# Expiration
|
||||
add_expiration_date = questionary.confirm(
|
||||
"Set feature flag expiration?",
|
||||
default=False
|
||||
).ask()
|
||||
|
||||
if add_expiration_date:
|
||||
expiration_date = questionary.text(
|
||||
"Expiration date (YYYY-MM-DD):",
|
||||
validate=lambda text: len(text) == 10 and text.count('-') == 2 or "Use format YYYY-MM-DD"
|
||||
).ask()
|
||||
|
||||
config['expirationDate'] = expiration_date
|
||||
|
||||
print("\n✅ Feature flag configured!")
|
||||
import json
|
||||
print(json.dumps(config, indent=2))
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def cicd_pipeline_wizard():
|
||||
"""Example: CI/CD pipeline setup"""
|
||||
|
||||
print("\n⚙️ CI/CD Pipeline Configuration\n")
|
||||
|
||||
# Provider
|
||||
provider = questionary.select(
|
||||
"CI/CD provider:",
|
||||
choices=['GitHub Actions', 'GitLab CI', 'CircleCI', 'Jenkins', 'Travis CI']
|
||||
).ask()
|
||||
|
||||
config = {'provider': provider}
|
||||
|
||||
# Triggers
|
||||
triggers = questionary.checkbox(
|
||||
"Pipeline triggers:",
|
||||
choices=[
|
||||
'Push to main/master',
|
||||
'Pull request',
|
||||
'Tag creation',
|
||||
'Manual trigger',
|
||||
'Scheduled (cron)'
|
||||
],
|
||||
default=['Push to main/master', 'Pull request']
|
||||
).ask()
|
||||
|
||||
config['triggers'] = triggers
|
||||
|
||||
# Cron schedule
|
||||
if 'Scheduled (cron)' in triggers:
|
||||
cron_schedule = questionary.text(
|
||||
"Cron schedule:",
|
||||
default="0 2 * * *",
|
||||
validate=lambda text: len(text.split()) == 5 or "Invalid cron format (5 parts required)"
|
||||
).ask()
|
||||
|
||||
config['cronSchedule'] = cron_schedule
|
||||
|
||||
# Stages
|
||||
stages = questionary.checkbox(
|
||||
"Pipeline stages:",
|
||||
choices=['Build', 'Test', 'Lint', 'Security scan', 'Deploy'],
|
||||
default=['Build', 'Test', 'Deploy'],
|
||||
validate=lambda choices: len(choices) > 0 or "Select at least one stage"
|
||||
).ask()
|
||||
|
||||
config['stages'] = stages
|
||||
|
||||
# Test types
|
||||
if 'Test' in stages:
|
||||
test_types = questionary.checkbox(
|
||||
"Test types to run:",
|
||||
choices=[
|
||||
'Unit tests',
|
||||
'Integration tests',
|
||||
'E2E tests',
|
||||
'Performance tests'
|
||||
]
|
||||
).ask()
|
||||
|
||||
config['testTypes'] = test_types
|
||||
|
||||
# Security tools
|
||||
if 'Security scan' in stages:
|
||||
security_tools = questionary.checkbox(
|
||||
"Security scanning tools:",
|
||||
choices=['Snyk', 'Dependabot', 'SonarQube', 'OWASP Dependency Check']
|
||||
).ask()
|
||||
|
||||
config['securityTools'] = security_tools
|
||||
|
||||
# Deploy environments
|
||||
if 'Deploy' in stages:
|
||||
deploy_environments = questionary.checkbox(
|
||||
"Deployment environments:",
|
||||
choices=['Development', 'Staging', 'Production'],
|
||||
default=['Staging', 'Production']
|
||||
).ask()
|
||||
|
||||
config['deployEnvironments'] = deploy_environments
|
||||
|
||||
# Approval for production
|
||||
if 'Production' in deploy_environments:
|
||||
require_approval = questionary.confirm(
|
||||
"Require manual approval for production?",
|
||||
default=True
|
||||
).ask()
|
||||
|
||||
config['requireApproval'] = require_approval
|
||||
|
||||
# Notifications
|
||||
enable_notifications = questionary.confirm(
|
||||
"Enable build notifications?",
|
||||
default=True
|
||||
).ask()
|
||||
|
||||
if enable_notifications:
|
||||
notification_channels = questionary.checkbox(
|
||||
"Notification channels:",
|
||||
choices=['Email', 'Slack', 'Discord', 'Microsoft Teams']
|
||||
).ask()
|
||||
|
||||
config['notificationChannels'] = notification_channels
|
||||
|
||||
print("\n✅ CI/CD pipeline configured!")
|
||||
import json
|
||||
print(json.dumps(config, indent=2))
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def main():
|
||||
"""Run conditional prompt examples"""
|
||||
try:
|
||||
print("=== Conditional Prompt Examples ===")
|
||||
|
||||
# Example 1: Database configuration
|
||||
conditional_prompt_example()
|
||||
|
||||
# Example 2: Deployment wizard
|
||||
deployment_wizard()
|
||||
|
||||
# Example 3: Feature flag configuration
|
||||
feature_flag_wizard()
|
||||
|
||||
# Example 4: CI/CD pipeline setup
|
||||
cicd_pipeline_wizard()
|
||||
|
||||
print("\n✅ Conditional prompt examples complete!")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n❌ User interrupted prompt")
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n\n❌ Error: {e}")
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
221
skills/inquirer-patterns/templates/python/list_prompt.py
Normal file
221
skills/inquirer-patterns/templates/python/list_prompt.py
Normal file
@@ -0,0 +1,221 @@
|
||||
"""
|
||||
List Selection Prompt Template
|
||||
|
||||
Use for: Single choice from predefined options
|
||||
Features: Arrow key navigation, search filtering
|
||||
"""
|
||||
|
||||
import questionary
|
||||
from questionary import Choice, Separator
|
||||
|
||||
|
||||
def list_prompt_example():
|
||||
"""Example list selection prompts"""
|
||||
|
||||
print("\n📋 List Selection Example\n")
|
||||
|
||||
# Simple list
|
||||
framework = questionary.select(
|
||||
"Choose your preferred framework:",
|
||||
choices=[
|
||||
'React',
|
||||
'Vue',
|
||||
'Angular',
|
||||
'Svelte',
|
||||
'Next.js',
|
||||
'Nuxt.js'
|
||||
],
|
||||
default='React'
|
||||
).ask()
|
||||
|
||||
# List with values
|
||||
language = questionary.select(
|
||||
"Choose programming language:",
|
||||
choices=[
|
||||
Choice('JavaScript', value='js'),
|
||||
Choice('TypeScript', value='ts'),
|
||||
Choice('Python', value='py'),
|
||||
Choice('Ruby', value='rb'),
|
||||
Choice('Go', value='go')
|
||||
],
|
||||
default='ts'
|
||||
).ask()
|
||||
|
||||
# List with descriptions
|
||||
package_manager = questionary.select(
|
||||
"Choose package manager:",
|
||||
choices=[
|
||||
Choice('npm - Node Package Manager', value='npm', shortcut_key='n'),
|
||||
Choice('yarn - Fast, reliable package manager', value='yarn', shortcut_key='y'),
|
||||
Choice('pnpm - Fast, disk space efficient', value='pnpm', shortcut_key='p'),
|
||||
Choice('bun - All-in-one toolkit', value='bun', shortcut_key='b')
|
||||
]
|
||||
).ask()
|
||||
|
||||
# List with separators
|
||||
environment = questionary.select(
|
||||
"Select deployment environment:",
|
||||
choices=[
|
||||
Separator('--- Cloud Platforms ---'),
|
||||
'AWS',
|
||||
'Google Cloud',
|
||||
'Azure',
|
||||
Separator('--- Serverless ---'),
|
||||
'Vercel',
|
||||
'Netlify',
|
||||
'Cloudflare Workers',
|
||||
Separator('--- Self-hosted ---'),
|
||||
'Docker',
|
||||
'Kubernetes'
|
||||
]
|
||||
).ask()
|
||||
|
||||
# List with emojis and styling
|
||||
database = questionary.select(
|
||||
"Choose database:",
|
||||
choices=[
|
||||
Choice('🐘 PostgreSQL (Relational)', value='postgresql'),
|
||||
Choice('🐬 MySQL (Relational)', value='mysql'),
|
||||
Choice('🍃 MongoDB (Document)', value='mongodb'),
|
||||
Choice('⚡ Redis (Key-Value)', value='redis'),
|
||||
Choice('📊 SQLite (Embedded)', value='sqlite'),
|
||||
Choice('🔥 Supabase (PostgreSQL + APIs)', value='supabase')
|
||||
],
|
||||
use_shortcuts=True,
|
||||
use_arrow_keys=True
|
||||
).ask()
|
||||
|
||||
answers = {
|
||||
'framework': framework,
|
||||
'language': language,
|
||||
'packageManager': package_manager,
|
||||
'environment': environment,
|
||||
'database': database
|
||||
}
|
||||
|
||||
print("\n✅ Selections:")
|
||||
import json
|
||||
print(json.dumps(answers, indent=2))
|
||||
|
||||
return answers
|
||||
|
||||
|
||||
def dynamic_list_example():
|
||||
"""Example with dynamic choices based on context"""
|
||||
|
||||
print("\n🔄 Dynamic List Example\n")
|
||||
|
||||
# First selection
|
||||
project_type = questionary.select(
|
||||
"Project type:",
|
||||
choices=['Web Application', 'CLI Tool', 'API/Backend', 'Library']
|
||||
).ask()
|
||||
|
||||
# Dynamic framework choices based on project type
|
||||
framework_choices = {
|
||||
'Web Application': ['React', 'Vue', 'Angular', 'Svelte', 'Next.js'],
|
||||
'CLI Tool': ['Commander.js', 'Yargs', 'Click', 'Typer', 'Cobra'],
|
||||
'API/Backend': ['Express', 'Fastify', 'Flask', 'FastAPI', 'Gin'],
|
||||
'Library': ['TypeScript', 'JavaScript', 'Python', 'Go', 'Rust']
|
||||
}
|
||||
|
||||
framework = questionary.select(
|
||||
f"Choose framework for {project_type}:",
|
||||
choices=framework_choices.get(project_type, ['None'])
|
||||
).ask()
|
||||
|
||||
print(f"\n✅ Selected: {project_type} with {framework}")
|
||||
|
||||
return {'projectType': project_type, 'framework': framework}
|
||||
|
||||
|
||||
def categorized_list_example():
|
||||
"""Example with categorized options"""
|
||||
|
||||
print("\n📂 Categorized List Example\n")
|
||||
|
||||
cloud_service = questionary.select(
|
||||
"Choose cloud service:",
|
||||
choices=[
|
||||
Separator('=== Compute ==='),
|
||||
Choice('EC2 - Virtual Servers', value='ec2'),
|
||||
Choice('Lambda - Serverless Functions', value='lambda'),
|
||||
Choice('ECS - Container Service', value='ecs'),
|
||||
Choice('EKS - Kubernetes Service', value='eks'),
|
||||
Separator('=== Storage ==='),
|
||||
Choice('S3 - Object Storage', value='s3'),
|
||||
Choice('EBS - Block Storage', value='ebs'),
|
||||
Choice('EFS - File System', value='efs'),
|
||||
Separator('=== Database ==='),
|
||||
Choice('RDS - Relational Database', value='rds'),
|
||||
Choice('DynamoDB - NoSQL Database', value='dynamodb'),
|
||||
Choice('ElastiCache - In-Memory Cache', value='elasticache'),
|
||||
Separator('=== Other ==='),
|
||||
Choice('CloudFront - CDN', value='cloudfront'),
|
||||
Choice('Route53 - DNS', value='route53'),
|
||||
Choice('SQS - Message Queue', value='sqs')
|
||||
],
|
||||
use_indicator=True
|
||||
).ask()
|
||||
|
||||
print(f"\n✅ Selected: {cloud_service}")
|
||||
|
||||
return {'cloudService': cloud_service}
|
||||
|
||||
|
||||
def numbered_list_example():
|
||||
"""Example with numbered choices for easier selection"""
|
||||
|
||||
print("\n🔢 Numbered List Example\n")
|
||||
|
||||
languages = [
|
||||
'Python', 'JavaScript', 'TypeScript', 'Go', 'Rust',
|
||||
'Java', 'C++', 'Ruby', 'PHP', 'Swift'
|
||||
]
|
||||
|
||||
# Add numbers to choices for easier reference
|
||||
numbered_choices = [
|
||||
Choice(f"{i+1}. {lang}", value=lang)
|
||||
for i, lang in enumerate(languages)
|
||||
]
|
||||
|
||||
language = questionary.select(
|
||||
"Choose programming language:",
|
||||
choices=numbered_choices,
|
||||
use_shortcuts=False # Disable letter shortcuts when using numbers
|
||||
).ask()
|
||||
|
||||
print(f"\n✅ Selected: {language}")
|
||||
|
||||
return {'language': language}
|
||||
|
||||
|
||||
def main():
|
||||
"""Run list prompt examples"""
|
||||
try:
|
||||
print("=== List Selection Examples ===")
|
||||
|
||||
# Example 1: Basic list selections
|
||||
list_prompt_example()
|
||||
|
||||
# Example 2: Dynamic choices
|
||||
dynamic_list_example()
|
||||
|
||||
# Example 3: Categorized options
|
||||
categorized_list_example()
|
||||
|
||||
# Example 4: Numbered list
|
||||
numbered_list_example()
|
||||
|
||||
print("\n✅ List selection examples complete!")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n❌ User interrupted prompt")
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n\n❌ Error: {e}")
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
366
skills/inquirer-patterns/templates/python/password_prompt.py
Normal file
366
skills/inquirer-patterns/templates/python/password_prompt.py
Normal file
@@ -0,0 +1,366 @@
|
||||
"""
|
||||
Password Prompt Template
|
||||
|
||||
Use for: Sensitive input (credentials, tokens)
|
||||
Features: Hidden input, confirmation, validation
|
||||
"""
|
||||
|
||||
import questionary
|
||||
import re
|
||||
from questionary import ValidationError, Validator
|
||||
|
||||
|
||||
class PasswordStrengthValidator(Validator):
|
||||
"""Validator for password strength requirements"""
|
||||
|
||||
def __init__(self, min_length=8, require_uppercase=True, require_lowercase=True,
|
||||
require_digit=True, require_special=True):
|
||||
self.min_length = min_length
|
||||
self.require_uppercase = require_uppercase
|
||||
self.require_lowercase = require_lowercase
|
||||
self.require_digit = require_digit
|
||||
self.require_special = require_special
|
||||
|
||||
def validate(self, document):
|
||||
password = document.text
|
||||
issues = []
|
||||
|
||||
if len(password) < self.min_length:
|
||||
issues.append(f"at least {self.min_length} characters")
|
||||
|
||||
if self.require_uppercase and not re.search(r'[A-Z]', password):
|
||||
issues.append("an uppercase letter")
|
||||
|
||||
if self.require_lowercase and not re.search(r'[a-z]', password):
|
||||
issues.append("a lowercase letter")
|
||||
|
||||
if self.require_digit and not re.search(r'[0-9]', password):
|
||||
issues.append("a number")
|
||||
|
||||
if self.require_special and not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
|
||||
issues.append("a special character")
|
||||
|
||||
if issues:
|
||||
raise ValidationError(
|
||||
message=f"Password must contain: {', '.join(issues)}",
|
||||
cursor_position=len(password)
|
||||
)
|
||||
|
||||
|
||||
class APIKeyValidator(Validator):
|
||||
"""Validator for API key format"""
|
||||
|
||||
def validate(self, document):
|
||||
api_key = document.text
|
||||
|
||||
if len(api_key) == 0:
|
||||
raise ValidationError(
|
||||
message="API key is required",
|
||||
cursor_position=0
|
||||
)
|
||||
|
||||
if not (api_key.startswith('sk-') or api_key.startswith('pk-')):
|
||||
raise ValidationError(
|
||||
message='API key must start with "sk-" or "pk-"',
|
||||
cursor_position=len(api_key)
|
||||
)
|
||||
|
||||
if len(api_key) < 32:
|
||||
raise ValidationError(
|
||||
message="API key appears to be too short",
|
||||
cursor_position=len(api_key)
|
||||
)
|
||||
|
||||
|
||||
def calculate_password_strength(password):
|
||||
"""Calculate password strength score"""
|
||||
strength = 0
|
||||
|
||||
if len(password) >= 8:
|
||||
strength += 1
|
||||
if len(password) >= 12:
|
||||
strength += 1
|
||||
if re.search(r'[a-z]', password):
|
||||
strength += 1
|
||||
if re.search(r'[A-Z]', password):
|
||||
strength += 1
|
||||
if re.search(r'[0-9]', password):
|
||||
strength += 1
|
||||
if re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
|
||||
strength += 1
|
||||
|
||||
levels = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong', 'Very Strong']
|
||||
return levels[min(strength, len(levels) - 1)]
|
||||
|
||||
|
||||
def password_prompt_example():
|
||||
"""Example password prompts with validation"""
|
||||
|
||||
print("\n🔒 Password Prompt Example\n")
|
||||
|
||||
# Password with strength validation
|
||||
password = questionary.password(
|
||||
"Enter your password:",
|
||||
validate=PasswordStrengthValidator(min_length=8)
|
||||
).ask()
|
||||
|
||||
# Confirm password
|
||||
confirm_password = questionary.password(
|
||||
"Confirm your password:",
|
||||
validate=lambda text: text == password or "Passwords do not match"
|
||||
).ask()
|
||||
|
||||
print(f"\n✅ Password strength: {calculate_password_strength(password)}")
|
||||
|
||||
# API key input
|
||||
api_key = questionary.password(
|
||||
"Enter your API key:",
|
||||
validate=APIKeyValidator()
|
||||
).ask()
|
||||
|
||||
print(f"✅ API key format: {api_key[:6]}...")
|
||||
|
||||
# Optional encryption key
|
||||
encryption_key = questionary.password(
|
||||
"Enter encryption key (optional, press Enter to skip):",
|
||||
validate=lambda text: len(text) == 0 or len(text) >= 16 or "Encryption key must be at least 16 characters"
|
||||
).ask()
|
||||
|
||||
# Don't log actual passwords!
|
||||
answers = {
|
||||
'passwordSet': True,
|
||||
'passwordStrength': calculate_password_strength(password),
|
||||
'apiKeyPrefix': api_key[:6],
|
||||
'encryptionKeySet': len(encryption_key) > 0
|
||||
}
|
||||
|
||||
print("\n✅ Credentials received (not displayed for security)")
|
||||
import json
|
||||
print(json.dumps(answers, indent=2))
|
||||
|
||||
return answers
|
||||
|
||||
|
||||
def secure_account_setup():
|
||||
"""Example: Complete secure account setup"""
|
||||
|
||||
print("\n🔐 Secure Account Setup\n")
|
||||
|
||||
# Username
|
||||
username = questionary.text(
|
||||
"Username:",
|
||||
validate=lambda text: len(text) > 0 or "Username required"
|
||||
).ask()
|
||||
|
||||
# Strong password
|
||||
print("\n📝 Password requirements:")
|
||||
print(" • At least 12 characters")
|
||||
print(" • Uppercase and lowercase letters")
|
||||
print(" • Numbers")
|
||||
print(" • Special characters (!@#$%^&*)")
|
||||
print()
|
||||
|
||||
password = questionary.password(
|
||||
"Password:",
|
||||
validate=PasswordStrengthValidator(
|
||||
min_length=12,
|
||||
require_uppercase=True,
|
||||
require_lowercase=True,
|
||||
require_digit=True,
|
||||
require_special=True
|
||||
)
|
||||
).ask()
|
||||
|
||||
# Confirm password
|
||||
confirm = questionary.password(
|
||||
"Confirm password:",
|
||||
validate=lambda text: text == password or "Passwords do not match"
|
||||
).ask()
|
||||
|
||||
# Optional: Remember credentials
|
||||
remember = questionary.confirm(
|
||||
"Remember credentials? (stored securely)",
|
||||
default=False
|
||||
).ask()
|
||||
|
||||
strength = calculate_password_strength(password)
|
||||
|
||||
print(f"\n✅ Account created for: {username}")
|
||||
print(f"🔒 Password strength: {strength}")
|
||||
|
||||
if remember:
|
||||
print("💾 Credentials will be stored securely")
|
||||
|
||||
return {
|
||||
'username': username,
|
||||
'passwordStrength': strength,
|
||||
'remember': remember
|
||||
}
|
||||
|
||||
|
||||
def database_credentials_setup():
|
||||
"""Example: Database connection credentials"""
|
||||
|
||||
print("\n🗄️ Database Credentials Setup\n")
|
||||
|
||||
# Database username
|
||||
db_user = questionary.text(
|
||||
"Database username:",
|
||||
default="postgres",
|
||||
validate=lambda text: len(text) > 0 or "Username required"
|
||||
).ask()
|
||||
|
||||
# Database password
|
||||
db_password = questionary.password(
|
||||
"Database password:",
|
||||
validate=lambda text: len(text) >= 8 or "Password must be at least 8 characters"
|
||||
).ask()
|
||||
|
||||
# Admin password (if needed)
|
||||
is_admin = questionary.confirm(
|
||||
"Create admin user?",
|
||||
default=False
|
||||
).ask()
|
||||
|
||||
admin_password = None
|
||||
if is_admin:
|
||||
admin_password = questionary.password(
|
||||
"Admin password:",
|
||||
validate=PasswordStrengthValidator(min_length=12)
|
||||
).ask()
|
||||
|
||||
admin_confirm = questionary.password(
|
||||
"Confirm admin password:",
|
||||
validate=lambda text: text == admin_password or "Passwords do not match"
|
||||
).ask()
|
||||
|
||||
print(f"\n✅ Admin password strength: {calculate_password_strength(admin_password)}")
|
||||
|
||||
credentials = {
|
||||
'dbUser': db_user,
|
||||
'dbPasswordSet': True,
|
||||
'adminConfigured': is_admin
|
||||
}
|
||||
|
||||
print("\n✅ Database credentials configured")
|
||||
import json
|
||||
print(json.dumps(credentials, indent=2))
|
||||
|
||||
return credentials
|
||||
|
||||
|
||||
def api_token_setup():
|
||||
"""Example: API token and secret key setup"""
|
||||
|
||||
print("\n🔑 API Token Setup\n")
|
||||
|
||||
# API key
|
||||
api_key = questionary.password(
|
||||
"Enter API key:",
|
||||
validate=lambda text: len(text) > 0 or "API key required"
|
||||
).ask()
|
||||
|
||||
# Secret key
|
||||
secret_key = questionary.password(
|
||||
"Enter secret key:",
|
||||
validate=lambda text: len(text) >= 32 or "Secret key must be at least 32 characters"
|
||||
).ask()
|
||||
|
||||
# Webhook secret (optional)
|
||||
use_webhooks = questionary.confirm(
|
||||
"Configure webhook authentication?",
|
||||
default=False
|
||||
).ask()
|
||||
|
||||
webhook_secret = None
|
||||
if use_webhooks:
|
||||
webhook_secret = questionary.password(
|
||||
"Webhook secret:",
|
||||
validate=lambda text: len(text) >= 16 or "Webhook secret must be at least 16 characters"
|
||||
).ask()
|
||||
|
||||
# Environment
|
||||
environment = questionary.select(
|
||||
"Environment:",
|
||||
choices=['Development', 'Staging', 'Production']
|
||||
).ask()
|
||||
|
||||
config = {
|
||||
'apiKeySet': True,
|
||||
'secretKeySet': True,
|
||||
'webhookConfigured': use_webhooks,
|
||||
'environment': environment
|
||||
}
|
||||
|
||||
print("\n✅ API credentials configured")
|
||||
print(f"Environment: {environment}")
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def password_change_flow():
|
||||
"""Example: Password change with old password verification"""
|
||||
|
||||
print("\n🔄 Change Password\n")
|
||||
|
||||
# Old password (in real app, verify against stored hash)
|
||||
old_password = questionary.password(
|
||||
"Enter current password:",
|
||||
validate=lambda text: len(text) > 0 or "Current password required"
|
||||
).ask()
|
||||
|
||||
# New password
|
||||
new_password = questionary.password(
|
||||
"Enter new password:",
|
||||
validate=PasswordStrengthValidator(min_length=8)
|
||||
).ask()
|
||||
|
||||
# Ensure new password is different
|
||||
if new_password == old_password:
|
||||
print("\n❌ New password must be different from current password")
|
||||
return password_change_flow()
|
||||
|
||||
# Confirm new password
|
||||
confirm_password = questionary.password(
|
||||
"Confirm new password:",
|
||||
validate=lambda text: text == new_password or "Passwords do not match"
|
||||
).ask()
|
||||
|
||||
print(f"\n✅ Password changed successfully")
|
||||
print(f"🔒 New password strength: {calculate_password_strength(new_password)}")
|
||||
|
||||
return {'passwordChanged': True}
|
||||
|
||||
|
||||
def main():
|
||||
"""Run password prompt examples"""
|
||||
try:
|
||||
print("=== Password Prompt Examples ===")
|
||||
|
||||
# Example 1: Basic password prompts
|
||||
password_prompt_example()
|
||||
|
||||
# Example 2: Secure account setup
|
||||
secure_account_setup()
|
||||
|
||||
# Example 3: Database credentials
|
||||
database_credentials_setup()
|
||||
|
||||
# Example 4: API token setup
|
||||
api_token_setup()
|
||||
|
||||
# Example 5: Password change
|
||||
password_change_flow()
|
||||
|
||||
print("\n✅ Password prompt examples complete!")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n❌ User interrupted prompt")
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n\n❌ Error: {e}")
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
220
skills/inquirer-patterns/templates/python/text_prompt.py
Normal file
220
skills/inquirer-patterns/templates/python/text_prompt.py
Normal file
@@ -0,0 +1,220 @@
|
||||
"""
|
||||
Text Input Prompt Template
|
||||
|
||||
Use for: Names, emails, URLs, paths, free-form text
|
||||
Features: Validation, default values, transform
|
||||
"""
|
||||
|
||||
import questionary
|
||||
import re
|
||||
from questionary import ValidationError, Validator
|
||||
|
||||
|
||||
class UsernameValidator(Validator):
|
||||
"""Validate username format"""
|
||||
|
||||
def validate(self, document):
|
||||
text = document.text
|
||||
if len(text) == 0:
|
||||
raise ValidationError(
|
||||
message='Username is required',
|
||||
cursor_position=len(text)
|
||||
)
|
||||
if len(text) < 3:
|
||||
raise ValidationError(
|
||||
message='Username must be at least 3 characters',
|
||||
cursor_position=len(text)
|
||||
)
|
||||
if not re.match(r'^[a-zA-Z0-9_-]+$', text):
|
||||
raise ValidationError(
|
||||
message='Username can only contain letters, numbers, hyphens, and underscores',
|
||||
cursor_position=len(text)
|
||||
)
|
||||
|
||||
|
||||
class EmailValidator(Validator):
|
||||
"""Validate email format"""
|
||||
|
||||
def validate(self, document):
|
||||
text = document.text
|
||||
email_regex = r'^[^\s@]+@[^\s@]+\.[^\s@]+$'
|
||||
if not re.match(email_regex, text):
|
||||
raise ValidationError(
|
||||
message='Invalid email address',
|
||||
cursor_position=len(text)
|
||||
)
|
||||
|
||||
|
||||
class URLValidator(Validator):
|
||||
"""Validate URL format"""
|
||||
|
||||
def validate(self, document):
|
||||
text = document.text
|
||||
if len(text) == 0:
|
||||
return # Optional field
|
||||
url_regex = r'^https?://.+'
|
||||
if not re.match(url_regex, text):
|
||||
raise ValidationError(
|
||||
message='Must be a valid URL (http:// or https://)',
|
||||
cursor_position=len(text)
|
||||
)
|
||||
|
||||
|
||||
class AgeValidator(Validator):
|
||||
"""Validate age range"""
|
||||
|
||||
def validate(self, document):
|
||||
text = document.text
|
||||
try:
|
||||
age = int(text)
|
||||
if age < 18:
|
||||
raise ValidationError(
|
||||
message='You must be at least 18 years old',
|
||||
cursor_position=len(text)
|
||||
)
|
||||
if age > 120:
|
||||
raise ValidationError(
|
||||
message='Please enter a realistic age',
|
||||
cursor_position=len(text)
|
||||
)
|
||||
except ValueError:
|
||||
raise ValidationError(
|
||||
message='Please enter a valid number',
|
||||
cursor_position=len(text)
|
||||
)
|
||||
|
||||
|
||||
class BioValidator(Validator):
|
||||
"""Validate bio length"""
|
||||
|
||||
def validate(self, document):
|
||||
text = document.text
|
||||
if len(text) > 200:
|
||||
raise ValidationError(
|
||||
message='Bio must be 200 characters or less',
|
||||
cursor_position=len(text)
|
||||
)
|
||||
|
||||
|
||||
def text_prompt_example():
|
||||
"""Example text input prompts with validation"""
|
||||
|
||||
print("\n📝 Text Input Example\n")
|
||||
|
||||
# Username input
|
||||
username = questionary.text(
|
||||
"Enter your username:",
|
||||
validate=UsernameValidator
|
||||
).ask()
|
||||
|
||||
# Email input
|
||||
email = questionary.text(
|
||||
"Enter your email:",
|
||||
validate=EmailValidator
|
||||
).ask()
|
||||
|
||||
# Website input (optional)
|
||||
website = questionary.text(
|
||||
"Enter your website (optional):",
|
||||
default="",
|
||||
validate=URLValidator
|
||||
).ask()
|
||||
|
||||
# Age input with conversion
|
||||
age_str = questionary.text(
|
||||
"Enter your age:",
|
||||
validate=AgeValidator
|
||||
).ask()
|
||||
age = int(age_str)
|
||||
|
||||
# Bio input
|
||||
bio = questionary.text(
|
||||
"Enter a short bio:",
|
||||
validate=BioValidator,
|
||||
multiline=False
|
||||
).ask()
|
||||
|
||||
answers = {
|
||||
'username': username.lower(),
|
||||
'email': email,
|
||||
'website': website,
|
||||
'age': age,
|
||||
'bio': bio
|
||||
}
|
||||
|
||||
print("\n✅ Answers received:")
|
||||
import json
|
||||
print(json.dumps(answers, indent=2))
|
||||
|
||||
return answers
|
||||
|
||||
|
||||
# Alternative: Using lambda validators
|
||||
def lambda_validator_example():
|
||||
"""Example using lambda validators for simpler cases"""
|
||||
|
||||
print("\n📝 Lambda Validator Example\n")
|
||||
|
||||
# Simple required field
|
||||
name = questionary.text(
|
||||
"Name:",
|
||||
validate=lambda text: len(text) > 0 or "Name is required"
|
||||
).ask()
|
||||
|
||||
# Email validation
|
||||
email = questionary.text(
|
||||
"Email:",
|
||||
validate=lambda text: bool(re.match(r'^[^\s@]+@[^\s@]+\.[^\s@]+$', text)) or "Invalid email"
|
||||
).ask()
|
||||
|
||||
# Numeric validation
|
||||
port = questionary.text(
|
||||
"Port number:",
|
||||
default="8000",
|
||||
validate=lambda text: text.isdigit() and 1 <= int(text) <= 65535 or "Invalid port (1-65535)"
|
||||
).ask()
|
||||
|
||||
# Path validation
|
||||
path = questionary.text(
|
||||
"Project path:",
|
||||
default="./my-project",
|
||||
validate=lambda text: len(text) > 0 or "Path is required"
|
||||
).ask()
|
||||
|
||||
answers = {
|
||||
'name': name,
|
||||
'email': email,
|
||||
'port': int(port),
|
||||
'path': path
|
||||
}
|
||||
|
||||
print("\n✅ Answers received:")
|
||||
import json
|
||||
print(json.dumps(answers, indent=2))
|
||||
|
||||
return answers
|
||||
|
||||
|
||||
def main():
|
||||
"""Run text prompt examples"""
|
||||
try:
|
||||
print("=== Text Prompt Examples ===")
|
||||
|
||||
# Example 1: Class-based validators
|
||||
text_prompt_example()
|
||||
|
||||
# Example 2: Lambda validators
|
||||
lambda_validator_example()
|
||||
|
||||
print("\n✅ Text prompt examples complete!")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n❌ User interrupted prompt")
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n\n❌ Error: {e}")
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user