
Research
/Security News
Toptal’s GitHub Organization Hijacked: 10 Malicious Packages Published
Threat actors hijacked Toptal’s GitHub org, publishing npm packages with malicious payloads that steal tokens and attempt to wipe victim systems.
Build feature-rich CLIs quickly.
# hello.yaml
name: hello
version: 0.1.0
commands:
shell:
help: Print hello in shell
run: $echo "hello from shell"
python: print("hello from python")
$ cli load hello.yaml
Parses hello.yaml
to generate a Typer CLI and load it into the running Python environment.
hello -h
Simple todo CLI with sqlite3 + tabulate.
# todo.yaml
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")
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")
For more examples, check examples directory.
cli <command>
Command | Description |
---|---|
init <cli name> | Generate a template CLI manifest for a new CLI |
load <manifest> | Add a new CLI based on the manifest |
render <manifest> | View generated CLI script for a manifest |
list, ls | Output a list of loaded CLIs |
update <cli name> | Reload a loaded CLI |
remove <cli name>, rm <cli name> | Remove a loaded CLI |
run <manifest> -- \<args> | Runs a CLI manifest command in isolation |
build <cli name or manifest> | Build a CLI manifest or a loaded CLI into a self-contained zipapp |
info <cli name> | Display CLI metadata |
dev <manifest> | Start hot-reloader for a manifest for active development |
test <manifest> | Run tests defined in a manifest |
validate <manifest> | Validate the syntax and structure of a CLI manifest |
docs <cli name or manifest> | Generate documentation for a CLI |
ai generate <cli name> <description> | Generate a CLI manifest based on a description. |
ai ask <prompt> | Ask a question about cliffy or a specific CLI manifest. |
cli
commands to load, build, and manage CLIs$
will translate to subprocess calls via PyBashbuild
to generate portable zipapps built with ShivCliffy can be installed using either pip or uv package managers.
pip install "cliffy[rich]"
to include rich-click for colorful CLI help output formatted with rich.or
pip install cliffy
to use the default help output.cli init mycli
uvx cliffy init mycli
uvx cliffy load mycli.yaml
uvx --from cliffy mycli -h
Generated by cli init
. For a barebones template, run cli init --raw
manifestVersion: v3
# The name of the CLI, used when invoking from command line.
name: cliffy
# CLI version
version: 0.1.0
# Brief description of the CLI
help: A brief description of your CLI
# List of Python package dependencies for the CLI.Supports requirements specifier syntax.
requires: []
# - requests>=2.25.1
# - pyyaml~=5.4
# List of external CLI manifests to include.Performs a deep merge of manifests sequentially in the order given to assemble a merged manifest
# and finally, deep merges the merged manifest with this manifest.
includes: []
# - path/to/other/manifest.yaml
# Mapping defining manifest variables that can be referenced in any other blocks
# Environments variables can be used in this section with ${some_env_var} for dynamic parsing
# Supports jinja2 formatted expressions as values
# Interpolate defined vars in other blocks jinja2-styled {{ var_name }}.
vars:
data_file: "data.json"
debug_mode: "{{ env['DEBUG'] or 'False' }}"
# String block or list of strings containing any module imports
# These can be used to import any python modules that the CLI depends on.
imports: |
import json
import os
from pathlib import Path
# List of helper function definitions
# These functions should be defined as strings that can be executed by the Python interpreter.
functions:
- |
def load_data() -> dict:
data_path = Path("{{ data_file }}")
if data_path.exists():
with data_path.open() as f:
return json.load(f)
return {}
- |
def save_data(data):
with open("{{data_file}}", "w") as f:
json.dump(data, f, indent=2)
# A mapping containing any shared type definitions
# These types can be referenced by name in the args section to provide type annotations for params and options defined in the args section.
types:
Filename: str = typer.Argument(..., help="Name of the file to process")
Verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output")
# Arguments applied to all commands
global_params:
- verbose: Verbose
# Reusable command templates
command_templates:
with_confirmation:
params:
- "yes": bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt")
pre_run: |
if not yes:
typer.confirm("Are you sure you want to proceed?", abort=True)
# A mapping containing the command definitions for the CLI
# Each command should have a unique key- which can be either a group command or nested subcommands
# Nested subcommands are joined by '.' in between each level
# Aliases for commands can be separated in the key by '|'
# A special '(*)' wildcard can be used to spread the subcommand to all group-level commands
commands:
hello:
help: Greet the user
params:
- name: str = typer.Option("World", "--name", "-n", help="Name to greet")
run: |
print(f"Hello, {name}!")
$ echo "i can also mix-and-match this command script to run shell commands"
file.process:
help: Process a file
params:
- filename: Filename
run: |
data = load_data()
print(f"Processing {filename}")
if verbose:
print("Verbose output enabled")
data["processed"] = [filename]
# Process the file here
save_data(data)
delete|rm:
help: Delete a file
template: with_confirmation
params: [filename: Filename]
run: |
if verbose:
print(f"Deleting {filename}")
os.remove(filename)
print("File deleted successfully")
# Additional CLI configuration options
cli_options:
rich_help_panel: True
# Test cases for commands
tests:
- hello --name Alice: assert 'Hello, Alice!' in result.output
- file process test.txt: assert 'Processing test.txt' in result.output
FAQs
$ cli load from.yaml
We found that cliffy demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
/Security News
Threat actors hijacked Toptal’s GitHub org, publishing npm packages with malicious payloads that steal tokens and attempt to wipe victim systems.
Research
/Security News
Socket researchers investigate 4 malicious npm and PyPI packages with 56,000+ downloads that install surveillance malware.
Security News
The ongoing npm phishing campaign escalates as attackers hijack the popular 'is' package, embedding malware in multiple versions.