Skip to content

Examples gallery

The manifests in the examples/ directory of the repository. Each entry shows the file as it is in the repository. The code cliffy generates for them is committed next to them in examples/generated/.

To try one, clone the repository and run the command shown from its root.

Files written in a custom spec, and the blueprints that turn them into CLIs, are in examples/blueprints/ and described in Blueprints.

Example Backend Requires Has tests
hello.yaml Typer nothing yes
nested-cli.yaml Typer nothing no
town.yaml Typer nothing no
template.yaml Typer nothing no
environ.yaml Typer nothing no
db.yaml Typer rich no
penv.yaml Typer nothing no
pydev.yaml Typer nothing no
taskmaster.yaml Typer tabulate>=0.9.0 yes
todo.yaml Typer tabulate, rich no
requires.yaml Typer requests >= 2.30.0, six < 1.0.0 no
callbacks.yaml Typer nothing yes
click_hello.yaml Click nothing yes
aiprompt.yaml Typer openai>=1.0.0, rich>=13.0.0, keyring>=24.0.0 no

hello

The smallest useful manifest: one shell command, one Python command and a tests section.

$ cli run examples/hello.yaml python
examples/hello.yaml
# yaml-language-server: $schema=cliffy_schema.json

name: hello
version: 0.1.0
help: Hello world!

commands:
  shell: $echo "hello from shell"
  python: print("hello from python")

examples:
  - command: hello shell
    description: Say hello from a shell command.
  - command: hello python
    description: Say hello from Python.

tests:
  - shell: assert result.exit_code == 0
  - python: assert result.output == "hello from python\n"
  - shell --help: assert "Show this message and exit" in result.output
  - -h: assert "Hello world!" in result.output
  - -h: assert "Examples:" in result.output and "$ hello shell" in result.output

nested-cli

Command groups nested two levels deep with dotted keys.

$ cli run examples/nested-cli.yaml group1 subgroup command1
examples/nested-cli.yaml
# yaml-language-server: $schema=cliffy_schema.json

name: nested-cli
version: 0.1.0
help: Demo nested command structure
commands:
  group1.subgroup.command1: $ echo "hello"
  group1.subgroup.command2: $ echo "world"
  group2.command3: $ echo "foo"
  command4: $ echo "bar"

town

A larger CLI: groups with help text, shared types, short option aliases, command aliases and a (*).list command that is added to every group.

$ cli run examples/town.yaml land list
examples/town.yaml
# yaml-language-server: $schema=cliffy_schema.json

manifestVersion: v3 # optional (default is the latest)
name: town
version: 0.1.1

imports: |
  import re
  import time

functions:
  - |
    def format_money(money: float):
        return "${:.2f}".format(money)

commands:
  people:
    help: Manage people
  shops:
    help: Manage shops
  home:
    help: Manage homes
  land:
    help: Manage land
  land.build:
    params: [name: Name!, address: str!, --value|-v: int = 100]
    run: |
      """Build land"""
      print(f"building land {name}")
  land.sell:
    params: [name: Name!, money: Money]
    run: |
      """Sell land"""
      print(f"selling land {name}")
  land.buy:
    params: [name: Name!, money: Money]
    run: |
      """Buy land"""
      print(f"buying land {name} for {format_money(money)}")
  people.add:
    params: [fullname: str!, age: int!, --home|-h: str]
    run: |
      """Add a person to town"""
      print(f"adding person {fullname}, {age}, {home}")
  people.remove:
    params: [fullname: str!]
    run: |
      """Remove person from town"""
      print(f"removing person {fullname}")
  shops.build:
    params: [name: Name!, land: str!, --type|-t: str]
    run: |
      """Build a shop"""
      print(f"building shop {name} ({type}) on land {land}")
  shops.sell:
    params: [name: Name!, money: Money]
    run: |
      """Sell a shop"""
      print(f"selling shop {name} for ${money}")
  shops.buy:
    params: [name: Name!, money: Money]
    run: |
      """Buy a shop"""
      print(f"buying shop {name} for ${money}")
  home.build|bu:
    params: [address: str!, land: str, owner: str]
    run: |
      """Build a home"""
      print(f"building home at {address} for {owner} on land {land}")
  home.sell:
    params: [address: str!, money: Money]
    aliases: ["s"]
    run: |
      """Sell a home"""
      print(f"selling home {address} for {format_money(money)}")
  home.buy:
    params: [address: str!, money: Money]
    aliases: ["b"]
    run: |
      """Buy a home"""
      print(f"buying home {address} for {money}")
  (*).list:
    params: [--limit|-l: int]
    run: |
      """Get a list of {(*)}"""
      print(f"listing {(*)}")

types:
  Money: float = typer.Option(..., help="Amount of money", min=0)
  Name: str = typer.Argument(..., help="Name")

# TODO
db: sqlite

template

A command template that gives two commands the same params, a run script written as a list, and per-command config.

$ cli run examples/template.yaml -- debug one --local-arg-2 two
examples/template.yaml
# yaml-language-server: $schema=cliffy_schema.json

name: template
version: 0.1.0

vars:
  GLOBAL_VAR: hello

command_templates:
  common_params:
    params:
      - local_arg: str!
      - --local_arg_2: str = ""

commands:
  hello:
    help: |
      Demo. global var and command args usage. 
      Runs hello command by default based on GLOBAL_VAR.
    template: common_params
    run:
      - $ echo "{{GLOBAL_VAR}} f{local_arg} --f{local_arg_2}"
    config:
      deprecated: True
      context_settings:
        help_option_names: ["-h", "--helpme"]

  debug:
    help: ""
    template: common_params
    run: |
      print(f"arg1: {local_arg}, arg2: --{local_arg_2}")

environ

Environment variables read when the manifest is loaded (vars with env) and when a command runs.

$ cli run examples/environ.yaml hello
examples/environ.yaml
# yaml-language-server: $schema=cliffy_schema.json

name: environ
description: Read environment variables
version: 0.1.0
help: Environment variable reader

imports:
  - import os

vars:
  # var setup on load time
  default_env_var: "{{ env['ENVIRON_LOAD_VAR'] or 'hello' }}"

commands:
  read: 
    params:
      - env-var: str!
    run: $ echo f{os.environ[env_var]}

  hello: $ echo {{ default_env_var }}
  bye: $ echo f{os.environ['ENVIRON_BYE_TEXT']} # on run time
  hello-bye: |
    $ echo {{ default_env_var }} bye  # the var was filled in on load time

db

Prompting options defined in types, YAML anchors to share params, pre_run, command aliases and cli_options.

$ cli run examples/db.yaml -- mk --name demo
examples/db.yaml
# yaml-language-server: $schema=cliffy_schema.json

manifestVersion: v3 # optional (Default will be the latest)
name: db
version: 0.1.0
help: Database CLI
cli_options:
  add_completion: False

requires:
  - rich

imports: |
  from rich.console import Console
  console = Console()

commands:
  create|mk:
    params: &db_args
      - name: DatabaseName
    run: |
      """Create a new database"""
      console.print(f"Creating database {name}", style="green")
    pre_run: |
      console.print("Running pre-run hook", style="green")
  delete|rm:
    params: *db_args
    run: |
      """Delete a database"""
      sure = typer.confirm("Are you really really really sure?")
      if sure:
          console.print(f"Deleting database {name}", style="red")
      else:
          console.print(f"Back to safety!", style="green")
  list|ls: |
    """List databases"""
    print("Listing all databases")
  view|v: 
    params:
    - <<: *db_args
    - table: TableName
    run: |
      """View database table"""
      console.print(f"Viewing {table} table for {name} DB")

types:
  DatabaseName: str = typer.Option(..., prompt="What is the name of the database?", confirmation_prompt=True)
  TableName: str = typer.Option(..., prompt="What is the name of the table?")

penv

A virtualenv manager: vars holding Python expressions, helper functions and shell lines that interpolate Python values with f{...}.

$ cli run examples/penv.yaml -- --help
examples/penv.yaml
# yaml-language-server: $schema=cliffy_schema.json

name: penv
version: 0.1.0
help: |
  ~ Virtualenv store ~
  Simplify virtualenvs

imports: |
  import os
  from pathlib import Path
  from shutil import rmtree

vars:
  HOME_PATH: {str(Path.home())}
  DEFAULT_VENV_STORE: {'f"{HOME_PATH}/venv-store/"'}

functions:
  - |
    def get_venv_path(name: str) -> str:
        return f"{DEFAULT_VENV_STORE}{name}"
  - |
    def venv_exists(name: str) -> bool:
        return os.path.exists(get_venv_path(name))

commands:
  ls:
    help: List venvs in the store
    run: $ ls f{DEFAULT_VENV_STORE}
  rm:
    help: Remove a venv
    params:
      - name: str!
    run: rmtree(get_venv_path(name))
  go:
    help: Activate a venv
    params:
      - name: str!
      - --interpreter | -i: str = "python"
    run: |
      if venv_exists(name):
          print(f"~ sourcing {name}")
      else:
          print(f"~ creating {name}")
          $ f{interpreter} -m venv f{os.path.join(DEFAULT_VENV_STORE, name)}

      os.system(f"""bash -c ". {get_venv_path(name)}/bin/activate; env PS1='\\[\\e[38;5;211m\\]({name})\\[\\e[\033[00m\\] \\w $ ' bash --norc\"""")

pydev

Developer chores as run-script lists that mix shell and Python lines, with helper functions.

$ cli run examples/pydev.yaml -- --help
examples/pydev.yaml
# yaml-language-server: $schema=cliffy_schema.json

manifestVersion: v3 # optional (default is the latest)
name: pydev
version: 0.1.0
help: Python developer utilities

imports: |
  import sys

functions:
  - |
    def run_cmd(cmd: str):
        """Helper function to run a command."""
        try:
            result = subprocess.run(cmd, shell=True, check=True,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE)
        except subprocess.CalledProcessError as e:
            print(f"Error running command: {cmd}")
            print(f"Exit code: {e.returncode}")
            print(f"Output: {e.output.decode('utf-8').strip()}")
            sys.exit(1)

        return result.stdout.decode('utf-8').strip()
  - |
    def get_file_contents(file_name: str):
        with open(file_name, 'r') as f:
            contents = f.read()
        return contents
  - |
    def write_to_file(file_name: str, contents: str):
        with open(file_name, 'w') as f:
            f.write(contents)

commands:
  clean:
    help: Clean pycache artifacts
    run:
    - $ find . -type f -name "*.pyc" -delete
    - $ find . -type d -name "__pycache__" -delete
    - $ echo Cleaned!

  format:
    help: Format code with black
    run:
    - $ black .
    - $ echo Formatted!

  lint:
    help: Run linters on the code
    run:
    - $ flake8 .
    - $ black --check .
    - $ mypy .
    - print("Linting successful!")

  test:
    help: Run tests
    run:
    - $ pytest
    - print("Tests passed!")

  bump: 
    help: Bump the package version
    params:
    - --major: VersionPart
    - --minor: VersionPart
    - --patch: VersionPart
    run: |
      current_version = run_cmd("python setup.py --version")
      print(f"Current version: {current_version}")
      parts = current_version.split('.')
      new_version = '.'.join(parts[:-1] + [str(int(parts[-1]) + 1)])
      print(f"New version: {new_version}")
      contents = get_file_contents("setup.py")
      contents = contents.replace(current_version, new_version)
      write_to_file("setup.py", contents)
      print(f"Bumped version from {current_version} to {new_version}.")

types:
  VersionPart: int = typer.Option(..., help="Version part", min=0)

taskmaster

A task manager with JSON storage, a vars placeholder used inside functions, shared types, nested groups and a full tests section.

$ cli test examples/taskmaster.yaml
examples/taskmaster.yaml
# yaml-language-server: $schema=cliffy_schema.json

name: taskmaster
version: 0.1.0
help: A task management CLI

requires:
  - tabulate>=0.9.0

imports: |
  import json
  from datetime import datetime
  from tabulate import tabulate

vars:
  data_file: "tasks.json"

functions:
  - |
    def load_data():
        try:
            with open("{{data_file}}", "r") as f:
                return json.load(f)
        except FileNotFoundError:
            return {"projects": [], "tasks": []}

  - |
    def save_data(data):
        with open("{{data_file}}", "w") as f:
            json.dump(data, f, indent=2)

  - |
    def format_date(date_str):
        return datetime.strptime(date_str, "%Y-%m-%d").strftime("%b %d, %Y")

types:
  ProjectName: str = typer.Argument(..., help="Name of the project")
  TaskName: str = typer.Argument(..., help="Name of the task")
  DueDate: str = typer.Option(None, "--due", "-d", help="Due date (YYYY-MM-DD)")
  Priority: int = typer.Option(1, "--priority", "-p", help="Priority (1-5)", min=1, max=5)

commands:
  project.add:
    params: [name: ProjectName]
    run: |
      data = load_data()
      if name not in data["projects"]:
          data["projects"].append(name)
          save_data(data)
          print(f"Project '{name}' added successfully.")
      else:
          print(f"Project '{name}' already exists.")

  project.list:
    run: |
      data = load_data()
      if data["projects"]:
          print("Projects:")
          for project in data["projects"]:
              print(f"- {project}")
      else:
          print("No projects found.")

  project.remove:
    params: [name: ProjectName]
    run: |
      data = load_data()
      if name in data["projects"]:
          data["projects"].remove(name)
          data["tasks"] = [task for task in data["tasks"] if task["project"] != name]
          save_data(data)
          print(f"Project '{name}' and its tasks removed successfully.")
      else:
          print(f"Project '{name}' not found.")

  task.add:
    params: [project: ProjectName, name: TaskName, due_date: DueDate, priority: Priority]
    run: |
      data = load_data()
      if project not in data["projects"]:
          print(f"Project '{project}' not found. Please create the project first.")
          return
      task = {
          "project": project,
          "name": name,
          "due_date": due_date,
          "priority": priority,
          "completed": False
      }
      data["tasks"].append(task)
      save_data(data)
      print(f"Task '{name}' added to project '{project}' successfully.")

  task.list:
    params: [project: ProjectName]
    run: |
      data = load_data()
      tasks = data["tasks"]
      if project:
          tasks = [task for task in tasks if task["project"] == project]

      if tasks:
          table_data = [
              [
                  task["project"],
                  task["name"],
                  format_date(task["due_date"]) if task["due_date"] else "N/A",
                  task["priority"],
                  "x" if task["completed"] else "o"
              ]
              for task in tasks
          ]
          headers = ["Project", "Task", "Due Date", "Priority", "Completed"]
          print(tabulate(table_data, headers=headers, tablefmt="grid"))
      else:
          print("No tasks found.")

  task.complete:
    params: [project: ProjectName, name: TaskName]
    run: |
      data = load_data()
      for task in data["tasks"]:
          if task["project"] == project and task["name"] == name:
              task["completed"] = True
              save_data(data)
              print(f"Task '{name}' in project '{project}' marked as completed.")
              return
      print(f"Task '{name}' in project '{project}' not found.")

  task.remove:
    params: [project: ProjectName, name: TaskName]
    run: |
      data = load_data()
      data["tasks"] = [task for task in data["tasks"] if task["project"] != project or task["name"] != name]
      save_data(data)
      print(f"Task '{name}' removed from project '{project}' successfully.")

  backup:
    help: Create a backup of the task data
    run: |
      $ cp {{data_file}} {{data_file}}.backup
      $ echo "Backup created: {{data_file}}.backup"


tests:
  - project add test1: assert result.exit_code == 0
  - project list: assert "test1" in result.output
  - project add test2: assert result.exit_code == 0
  - project list: assert "test1" in result.output and "test2" in result.output
  - task add test1 task1: assert result.exit_code == 0
  - task add test1 task2: assert result.exit_code == 0
  - task list test1: assert "task1" in result.output and "task2" in result.output
  - task remove test1 task1: assert "removed" in result.output
  - project remove test1: assert "removed" in result.output
  - task list test1: assert "No tasks found" in result.output
  - $ rm tasks.json

todo

A sqlite-backed todo list that depends on tabulate and rich.

$ cli run examples/todo.yaml -- --help
examples/todo.yaml
# yaml-language-server: $schema=cliffy_schema.json

name: todo
version: 1.0.0
requires:
  - tabulate  # For pretty table output
  - rich      # For colored terminal output
imports: |
  import sqlite3
  from pathlib import Path
  from tabulate import tabulate
  from rich import print

commands:
  create:
    help: Create a new database with tasks table
    params:
      - name: str = typer.Option(..., prompt=True, confirmation_prompt=True)
    run: |
      db_path = Path(f"{name}.db")
      conn = sqlite3.connect(db_path)
      conn.execute("CREATE TABLE tasks (id INTEGER PRIMARY KEY, task TEXT NOT NULL, done BOOLEAN NOT NULL)")

      # insert example tasks
      conn.execute("INSERT INTO tasks (task, done) VALUES ('Fight for your right!', 0)")
      conn.execute("INSERT INTO tasks (task, done) VALUES ('To party!', 1)")
      conn.commit()
      conn.close()
      print(f"✨ Created database {db_path} with tasks table")

  drop|rm:
    help: Drop a database
    params: [name: str!]
    run: |
      db_path = Path(f"{name}.db")
      db_path.unlink()
      print(f"🗑️ Removed database {db_path}")

  tasks:
    help: List tasks in database
    params: [name: str!]
    run: |
      conn = sqlite3.connect(f"{name}.db")
      cursor = conn.execute("SELECT * FROM tasks")
      tasks = cursor.fetchall()
      conn.close()
      print(tabulate(tasks, headers=['ID', 'Task', 'Done'], tablefmt='grid'))

  add:
    help: Add a new task
    params: [name: str!, task: str!]
    run: |
      conn = sqlite3.connect(f"{name}.db")
      conn.execute("INSERT INTO tasks (task, done) VALUES (?, 0)", (task,))
      conn.commit()
      conn.close()
      print(f"📝 Added task: {task}")

  complete:
    help: Mark a task as complete
    params: [name: str!, id: int!]
    run: |
      conn = sqlite3.connect(f"{name}.db")
      conn.execute("UPDATE tasks SET done = 1 WHERE id = ?", (id,))
      conn.commit()
      conn.close()
      print(f"🎉 Marked task {id} as complete")

requires

Version specifiers in requires. Loading fails on purpose unless requests>=2.30.0 and six<1.0.0 are installed, which is what the test suite checks.

$ cli validate examples/requires.yaml
examples/requires.yaml
# yaml-language-server: $schema=cliffy_schema.json

name: requires
version: 0.1.0

requires:
  - requests >= 2.30.0
  - six < 1.0.0

imports:
  - import six

commands:
  bash: $echo "hello from bash"
  python: print("hello from python")
  py: |
    if six.PY2:
        print("python 2")
    if six.PY3:
        print("python 3")

callbacks

Callbacks: code that runs before every command of the CLI or of one group, with params that become options of the CLI itself and access to the context as ctx.

$ cli run examples/callbacks.yaml -- --verbose db migrate
examples/callbacks.yaml
# yaml-language-server: $schema=cliffy_schema.json

name: callbacks
version: 0.1.0
help: Callbacks run before the commands of a CLI or of a command group

callbacks:
  # no `group`: runs before every command, and its params become options of the CLI itself
  - params:
      - name: --verbose
        type: bool
        default: false
        short: -v
        help: Print what is about to run
    invoke_without_command: true
    run: |
      ctx.obj = {"verbose": verbose}
      if ctx.invoked_subcommand is None:
          print("no command given, try --help")
      elif verbose:
          print(f"running {ctx.invoked_subcommand}")

  # runs before every command of the `db` group
  - group: db
    run: print("connecting to db")

commands:
  hello: print("hello")
  db.migrate: print("migrating")
  db.status: print("up to date")

tests:
  - hello: assert result.output == "hello\n"
  - --verbose hello: assert result.output == "running hello\nhello\n"
  - -v db migrate: assert result.output == "running db\nconnecting to db\nmigrating\n"
  - db status: assert result.output == "connecting to db\nup to date\n"
  - "": assert result.output == "no command given, try --help\n"
  - --version: assert result.output == "callbacks, 0.1.0\n"

clickhello

The Click backend (use_click: true) with a command alias. Needs the rich extra.

$ cli run examples/click_hello.yaml hello
examples/click_hello.yaml
name: clickhello
version: 0.1.0
use_click: true

help: Hello World with Click!
commands:
  hello | hl: $ echo "world"

examples:
  - command: clickhello hello
    description: Print "world".
  - clickhello hl

tests:
  - hello: assert result.exit_code == 0
  - --help: assert "Examples:" in result.output and "$ clickhello hl" in result.output

aiprompt

An OpenAI client written by an LLM: explicit params with help text, global_params and nested groups. Needs openai, rich and keyring.

$ cli render examples/aiprompt.yaml
examples/aiprompt.yaml
name: aiprompt
version: "1.0.0"
help: "Generated with Claude. Command-line interface for interacting with OpenAI's APIs."

requires:
  - "openai>=1.0.0"
  - "rich>=13.0.0"
  - "keyring>=24.0.0"

imports: |
  import openai
  import keyring
  import json
  from rich.console import Console
  from rich.markdown import Markdown
  from rich.table import Table
  from typing import Optional, List
  from pathlib import Path
  from datetime import datetime

functions: |
  def load_api_key():
      key = keyring.get_password("aiprompt", "openai_api_key")
      if not key:
          raise typer.BadParameter("API key not found. Please set it using 'aiprompt config set-key'")
      return key

  def format_chat_response(response):
      console = Console()
      content = response.choices[0].message.content
      console.print(Markdown(content))

  def format_completion_response(response):
      console = Console()
      content = response.choices[0].text
      console.print(content)

global_params:
  - name: "--verbose"
    type: "bool"
    help: "Enable verbose output"
    default: False

commands:
  "config.set-key":
    help: "Set OpenAI API key"
    params:
      - name: "api_key"
        type: "str"
        help: "Your OpenAI API key"
        required: true
    run: |
      keyring.set_password("aiprompt", "openai_api_key", api_key)
      typer.echo("API key saved successfully!")

  chat:
    help: "Start a chat completion with GPT models"
    params:
      - name: "prompt"
        type: "str"
        help: "The prompt to send to the model"
        required: true
      - name: "--model"
        type: "str"
        help: "GPT model to use"
        default: "gpt-3.5-turbo"
      - name: "--temperature"
        type: "float"
        help: "Sampling temperature (0-2)"
        default: 0.7
      - name: "--max-tokens"
        type: "int"
        help: "Maximum tokens in response"
        default: 1000
    run: |
      client = openai.OpenAI(api_key=load_api_key())
      response = client.chat.completions.create(
          model=model,
          messages=[{"role": "user", "content": prompt}],
          temperature=temperature,
          max_tokens=max_tokens
      )
      format_chat_response(response)

  complete:
    help: "Generate text completions"
    params:
      - name: "prompt"
        type: "str"
        help: "The prompt to complete"
        required: true
      - name: "--model"
        type: "str"
        help: "Model to use"
        default: "gpt-3.5-turbo-instruct"
      - name: "--temperature"
        type: "float"
        help: "Sampling temperature (0-2)"
        default: 0.7
      - name: "--max-tokens"
        type: "int"
        help: "Maximum tokens in response"
        default: 500
    run: |
      client = openai.OpenAI(api_key=load_api_key())
      response = client.completions.create(
          model=model,
          prompt=prompt,
          temperature=temperature,
          max_tokens=max_tokens
      )
      format_completion_response(response)

  "chat.stream":
    help: "Start a streaming chat completion"
    params:
      - name: "prompt"
        type: "str"
        help: "The prompt to send to the model"
        required: true
      - name: "--model"
        type: "str"
        help: "GPT model to use"
        default: "gpt-3.5-turbo"
      - name: "--temperature"
        type: "float"
        help: "Sampling temperature (0-2)"
        default: 0.7
    run: |
      console = Console()
      client = openai.OpenAI(api_key=load_api_key())
      stream = client.chat.completions.create(
          model=model,
          messages=[{"role": "user", "content": prompt}],
          temperature=temperature,
          stream=True
      )
      for chunk in stream:
          if chunk.choices[0].delta.content:
              console.print(chunk.choices[0].delta.content, end="")
      console.print()

  "chat.file":
    help: "Send a file's content as a chat prompt"
    params:
      - name: "file"
        type: "Path"
        help: "Path to the input file"
        required: true
      - name: "--model"
        type: "str"
        help: "GPT model to use"
        default: "gpt-3.5-turbo"
      - name: "--system-prompt"
        type: "str"
        help: "Optional system prompt"
        default: ""
    run: |
      client = openai.OpenAI(api_key=load_api_key())
      messages = []

      if system_prompt:
          messages.append({"role": "system", "content": system_prompt})

      with open(file, 'r') as f:
          content = f.read()
          messages.append({"role": "user", "content": content})

      response = client.chat.completions.create(
          model=model,
          messages=messages
      )
      format_chat_response(response)
  models:
    help: "List available OpenAI models"
    params:
      - name: "--type"
        type: "str"
        help: "Filter models by type (chat, completion, embedding, etc.)"
        default: ""
    run: |
      console = Console()
      client = openai.OpenAI(api_key=load_api_key())
      models = client.models.list()

      table = Table(title="Available OpenAI Models")
      table.add_column("ID", style="cyan")
      table.add_column("Created", style="green")
      table.add_column("Owner", style="blue")

      for model in sorted(models.data, key=lambda x: x.created):
          if type and type.lower() not in model.id.lower():
              continue
          created_date = datetime.fromtimestamp(model.created).strftime('%Y-%m-%d')
          table.add_row(model.id, created_date, model.owned_by)

      console.print(table)