A CLI tool and API server for defining, publishing, and running shell-based command specs. Author commands as YAML or JSON specs, push them to a server, sync them locally, and run them from anywhere.
Prerequisites: Go 1.25+, Docker (for PostgreSQL)
# Start the database
docker compose up -d
# Build
make build-cli
make build-api
# Run the API server (dev mode)
make dev
# Log in and create your first command
./bin/my cli login
./bin/my cli init hello
# edit command.yaml
./bin/my cli push
./bin/my cli run hello
# or run directly from a file
./bin/my cli run -f command.yamlOr use libraries without an account:
make build-cli
./bin/my library add https://github.com/user/example-library.git
./bin/my <library> <command>All management subcommands live under cli:
| Command | Description |
|---|---|
cli login |
Log in via email magic link (auto-syncs after login) |
cli logout |
Clear stored credentials |
cli whoami |
Show current user info (ID, email) |
| Command | Description |
|---|---|
cli init [name] |
Scaffold a command.yaml file (with name: creates subdirectory) |
cli push |
Validate and publish a command spec to the server |
cli show <slug> |
Show details of a cached command |
cli run [-f file | slug] [args...] |
Run a cached command (or directly from a spec file with -f) |
cli status |
Show API URL, login state, last sync time, cached command count |
cli history |
Show run history |
| Command | Description |
|---|---|
library search <query> |
Search public libraries |
library add <identifier> |
Install a library from the registry or a git URL |
library remove <name> |
Remove an installed library |
library list |
List installed libraries |
library update [name] |
Update all or a specific library |
library info <identifier> |
Show library details |
library explore |
Interactive TUI for browsing libraries |
library release <tag> |
Create a versioned release from a git tag |
library has alias lib (e.g., my lib add kubernetes).
Installed library commands are available as top-level subcommands: my <library> <command>.
Global flags:
--api-url— Override the API server URL--config— Override the config file path
Command-specific flags:
| Command | Flag | Description |
|---|---|---|
cli push |
-f, --file |
Spec file to push (default: command.yaml, auto-detects) |
cli push |
-m, --message |
Version message |
cli push |
--dir |
Push all spec files found in directory tree |
cli show |
--raw |
Output raw JSON spec |
cli run |
-y, --yes |
Skip confirmation prompts |
cli run |
-f, --file |
Run directly from a spec file |
cli history |
-n, --last |
Number of entries to show (default: 20) |
cli init |
--force |
Overwrite existing spec file |
library add |
--ref |
Git branch or tag to checkout |
library add |
--name |
Alias for the library (defaults to repo name) |
library list |
--json |
Output as JSON |
Commands are defined as YAML or JSON files validated against a JSON Schema.
{
"schemaVersion": 1,
"kind": "command",
"metadata": {
"name": "greet",
"slug": "greet",
"description": "Greet someone",
"tags": ["example"],
"aliases": ["hi"]
},
"dependencies": ["curl"],
"args": {
"positional": [
{ "name": "name", "description": "Who to greet", "required": true }
],
"flags": [
{ "name": "loud", "short": "l", "type": "bool", "description": "Shout it" }
]
},
"defaults": {
"shell": "/bin/bash",
"timeout": "30s",
"env": { "GREETING": "Hello" }
},
"steps": [
{
"name": "greet",
"run": "echo '{{.env.GREETING}}, {{.args.name}}!'"
}
],
"policy": {
"requireConfirmation": true,
"allowedExecutables": ["/bin/echo"]
}
}| Field | Required | Description |
|---|---|---|
schemaVersion |
yes | Must be 1 |
kind |
yes | Must be "command" |
metadata |
yes | name, slug (required); description, tags, aliases (optional). Slugs must match ^[a-z][a-z0-9-]*$ |
dependencies |
no | Array of required binary dependencies (e.g., ["curl", "jq"]). Checked before execution |
args |
no | positional (ordered list) and flags (named options with optional short, type, default) |
defaults |
no | Default shell, timeout, and env vars applied to all steps |
steps |
yes | Array of steps (min 1). Each has name, run (required); env, timeout, shell, continueOnError (optional) |
policy |
no | requireConfirmation prompts before running; allowedExecutables restricts which binaries can be invoked |
Flags support string (default), bool, and int types.
Step run lines and env values are rendered as Go templates:
| Variable | Description |
|---|---|
{{.args.X}} |
Argument value (positional or flag) by name |
{{.env.X}} |
Environment variable value |
{{.cwd}} |
Current working directory |
{{.home}} |
User's home directory |
A library repo is a git repository that provides one or more command libraries. Library repos let you share and use curated sets of commands without needing an account or API server — just a git URL.
my library add <url>clones the repo and reads its shelf manifest (shelf.yaml)- Each library in the manifest maps to a directory of command spec files
- Commands become available as
my <library> <slug>(e.g.,my ops deploy) my library updatepulls the latest changes from all library repos
Every library repo must have a shelf.yaml at its root (shelf.yml and shelf.json are also accepted for backward compatibility):
shelfVersion: 1
name: my-shelf
description: A collection of useful commands
libraries:
ops:
name: Operations
description: Deployment and operations commands
path: ops
k8s:
name: Kubernetes
description: Kubernetes helper commands
path: k8sshelfVersionmust be1- Each key in
librariesis the library slug (must match^[a-z][a-z0-9-]*$) pathpoints to a directory containing command spec files- Each
.yaml,.yml, or.jsonfile in a library directory must be a valid command spec whosemetadata.slugmatches the filename (minus extension)
my-library/
shelf.yaml # Manifest
ops/ # Library directory
deploy.yaml # Command spec (slug: "deploy")
status.yaml # Command spec (slug: "status")
k8s/
logs.yaml # Command spec (slug: "logs")
- Library repos are cloned to
~/.my/libraries/repos/(path derived from URL) - The library registry lives at
~/.my/libraries/libraries.json
See examples/shelf/ for a complete working example.
docker compose up -d # PostgreSQL on localhost:5432
make migrate # Run database migrations
make dev # Build and start on :8080| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgres://mycli:mycli@localhost:5432/mycli_dev?sslmode=disable |
PostgreSQL connection string |
PORT |
8080 |
Server listen port |
JWT_SECRET |
dev-secret-change-me |
Secret for signing JWTs |
BASE_URL |
http://localhost:8080 |
Public base URL (used in emails) |
RESEND_API_KEY |
(empty) | Resend API key for sending emails. When unset, emails are printed to stdout |
EMAIL_FROM |
mycli@updates.mycli.sh |
Sender address for emails |
Public (no auth):
| Method | Path | Description |
|---|---|---|
POST |
/v1/auth/device/start |
Start device auth flow |
POST |
/v1/auth/device/token |
Poll for device token |
POST |
/v1/auth/device/resend |
Resend OTP |
POST |
/v1/auth/verify-code |
Verify OTP code |
POST |
/v1/auth/refresh |
Refresh access token |
GET |
/v1/auth/verify |
Verify magic link |
POST |
/v1/auth/web/login |
Start web auth flow |
POST |
/v1/auth/web/verify |
Verify web auth |
GET |
/v1/usernames/{username}/available |
Check username availability |
GET |
/device |
Device verification page |
POST |
/device |
Device verification submit |
GET |
/health |
Health check |
Library browsing (optional auth):
| Method | Path | Description |
|---|---|---|
GET |
/v1/libraries |
Search libraries |
GET |
/v1/libraries/{owner}/{slug} |
Library detail |
GET |
/v1/libraries/{owner}/{slug}/releases |
List releases |
GET |
/v1/libraries/{owner}/{slug}/releases/{version} |
Get a release |
GET |
/v1/libraries/{owner}/{slug}/commands/{commandSlug} |
Get a command |
GET |
/v1/libraries/{owner}/{slug}/commands/{commandSlug}/versions |
List command versions |
Authenticated (no username required):
| Method | Path | Description |
|---|---|---|
GET |
/v1/me |
Current user info |
PATCH |
/v1/me/username |
Set username |
GET |
/v1/sessions |
List sessions |
DELETE |
/v1/sessions/{id} |
Revoke a session |
DELETE |
/v1/sessions |
Revoke all sessions |
Authenticated (username required):
| Method | Path | Description |
|---|---|---|
GET |
/v1/me/sync-summary |
Sync summary |
POST |
/v1/commands |
Create a command |
GET |
/v1/commands |
List commands |
GET |
/v1/commands/{id} |
Get a command |
DELETE |
/v1/commands/{id} |
Delete a command (soft delete) |
POST |
/v1/commands/{id}/versions |
Publish a version |
GET |
/v1/commands/{id}/versions/{version} |
Get a specific version |
GET |
/v1/catalog |
Get synced catalog (supports ETag) |
POST |
/v1/libraries/{slug}/releases |
Create a release |
POST |
/v1/libraries/{owner}/{slug}/install |
Install a library |
DELETE |
/v1/libraries/{owner}/{slug}/install |
Uninstall a library |
make build-cli # Build CLI to bin/my
make build-api # Build API to bin/api
make build-web # Build web frontend
make build-site # Build marketing site
make build-docs # Build documentation site
make test # go test ./...
make lint # golangci-lint run ./...
make dev # Build and run API on :8080
make dev-web # Run web frontend dev server
make dev-site # Run marketing site dev server
make dev-docs # Run docs dev server
make migrate # Run DB migrations
make sqlc-generate # Regenerate sqlc query code
make dev-reset # Reset dev environmentRun tests for a single package:
go test ./pkg/spec/...docker compose up -d # PostgreSQL 18 (mycli:mycli@localhost:5432/mycli_dev)
docker compose down # StopTwo binaries sharing one Go module (mycli.sh):
cli/cmd/my/main.go CLI entry point (Cobra)
cli/internal/commands/ Subcommand implementations
cli/internal/engine/ Template rendering + shell execution
cli/internal/cache/ Local spec cache (~/.my/cache/)
cli/internal/auth/ Token storage (OS keyring + file fallback)
cli/internal/history/ Run history (JSONL at ~/.my/history.jsonl)
cli/internal/library/ Library registry management (~/.my/libraries/)
cli/internal/shelf/ Git operations, manifest parsing, spec discovery
cli/internal/client/ API client
cli/internal/config/ Config loading
cli/internal/termui/ Terminal UI helpers (Bubble Tea)
api/cmd/api/main.go API entry point (Chi)
api/internal/handler/ HTTP handlers
api/internal/store/ PostgreSQL stores (pgx, no ORM)
api/internal/middleware/ Auth + rate limiting
api/internal/config/ Env-based configuration
api/internal/database/ Connection + migrations
api/internal/email/ Email sender (Resend / dev log)
pkg/spec/ Shared: CommandSpec types, JSON Schema validation, parsing, hashing
Config and credentials are stored under ~/.my/. Database IDs use prefixed UUIDs (usr_, cmd_, cv_, ml_, lib_).