velvet v0.2.5

Define prompts in YAML (or use the lib/DSL in Crystal). Collect clean, typed input. Emit JSON. Your app never writes argparse again.

velvet

CI Release

The rope between your terminal and your app.

Define prompts in YAML (or use the lib/DSL in Crystal). Collect clean, typed input. Emit JSON. Your app never writes argparse again.


Install

git clone https://github.com/aristorap/velvet
cd velvet
shards install
shards build --release
# binary at bin/velvet

Usage

# Generate a new YAML wizard from shorthand field specs
velvet new "Deploy config" app_name replicas:int frontend@one_of=vanilla,vue dry_run@confirm

# Interactive wizard
velvet run deploy_config.yml

# Non-interactive — parse flags against schema
velvet parse deploy_config.yml -- --app-name myapp --replicas 3 --frontend vue --dry-run

# Validate a wizard file
velvet validate deploy_config.yml

# Run the DSL deploy example
crystal run examples/dsl.deploy.cr

Commands

command aliases short description
new [name] <shorthand>... n, init Scaffold a new velvet config
run [file] r Run an interactive wizard and emit JSON
parse [file] -- [flags] p Parse flags against a schema and emit JSON
validate [file] v, check Validate a wizard file

Long descriptions are available in command help:

velvet --help
velvet new --help
velvet run --help
velvet parse --help
velvet validate --help

Missing required positional arguments print a command-specific usage hint (including aliases):

Error: run requires a file argument
Usage: velvet run [file]
Alias: r

Alias examples:

velvet n "Deploy config" app_name replicas:int
velvet r deploy_config.yml
velvet p deploy_config.yml -- --app-name myapp --replicas 3
velvet v deploy_config.yml

The new command writes a normalized .yml file in the current directory.

Pipe into your app:

config=$(velvet run deploy.yml)
myapp <<< "$config"

# or
velvet run deploy.yml | myapp deploy

Your app just reads JSON from stdin — no argparse, no type coercion, no required-field checks.

Interactive prompt behavior:

  • Progress is shown inline in each prompt label as [step/total].
  • Completed values are shown as a dim, scrolling summary line above the current prompt.

Generator field shorthand

Supported shorthand format:

field       := id
            | id:cast
            | id@ui
            | id:cast@ui
            | id:cast@ui=val,val,...

cast        := str | string | int | float | bool
ui          := input | select | multi(select) | confirm
ui_alias    := one_of -> select | any_of -> multi
values      := csv list, valid only with select-family or multi-family ui

Examples:

app_name
app_name:str
replicas:int
replicas:int@select=1,2,4,8
frontend@select=vanilla,vue
dry_run@confirm
tags@multi=cache,metrics

Defaults and implications:

  • no :cast means string
  • no @ui means input
  • @confirm implies boolean semantics
  • @select or @multi without =values is valid and produces an empty options list

Built-in UI aliases:

  • @one_of is an alias for @select
  • @any_of is an alias for @multi
  • @multiselect is also accepted where @multi is used

Custom aliases can be registered from Crystal code while the API evolves:

Velvet::Generator.set_ui_alias("pick", "select")
Velvet::Generator.set_ui_alias("many", "multi")

Reset aliases back to defaults:

Velvet::Generator.reset_ui_aliases

DSL has matching user-facing aliases:

w.input "replicas", "Number of replicas", cast: Velvet::Cast::Int
w.one_of "environment", "Environment", ["dev", "prod"]
w.multi "tags", "Tags", ["cache", "metrics"]
w.any_of "tags", "Tags", ["cache", "metrics"]
w.field "tags", "Tags", ui: "multiselect", options: ["cache", "metrics"]

Wizard file

name: "Deploy config"

fields:
  - id: environment
    type: select
    label: "Target environment"
    options: [dev, staging, production]
    default: dev

  - id: replicas
    type: input
    label: "Number of replicas"
    cast: int
    required: true
    validate:
      min: 1
      max: 20

  - id: dry_run
    type: confirm
    label: "Dry run?"
    default: false

  - id: tags
    type: multiselect
    label: "Feature flags"
    options: [cache, metrics, tracing]
    required: false

Field types

type description
input free text, optionally cast and validated
select pick one from a list
multiselect pick many
confirm yes/no boolean

Cast types

string (default), int, float, bool

For select and multiselect fields, cast is also supported and is applied in both run and parse flows.

Validation

validate:
  min: 1
  max: 100
  pattern: "^[a-z-]+$"

Validation rules are cast-aware and checked when loading YAML or building via DSL:

  • min / max are only valid with cast: int or cast: float
  • pattern is only valid with cast: string (or omitted cast, which defaults to string)

Invalid combinations are rejected as schema/config errors (exit code 2) rather than being ignored.

Examples of invalid combinations:

# invalid: numeric bounds on string
cast: string
validate:
  min: 1

# invalid: pattern on int
cast: int
validate:
  pattern: "^[0-9]+$"

Output

Always clean JSON to stdout, errors to stderr.

{
  "environment": "staging",
  "replicas": 3,
  "dry_run": false,
  "tags": ["cache", "metrics"]
}

Types are cast before emission — consumers always get typed values.


Exit codes

code meaning
0 success
1 validation error
2 config/schema error
130 user aborted (Ctrl+C)
Repository

velvet

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 1
  • about 13 hours ago
  • May 5, 2026
License

MIT License

Links
Synced at

Wed, 06 May 2026 00:03:57 GMT

Languages