{"id":49109804,"url":"https://github.com/quonaro/lota","last_synced_at":"2026-07-01T04:01:03.388Z","repository":{"id":350493459,"uuid":"1197452134","full_name":"quonaro/Lota","owner":"quonaro","description":"🚀 Lota — configurable task runner for Go. Define commands in YAML, run from terminal. With variables, arguments, and hooks.","archived":false,"fork":false,"pushed_at":"2026-06-30T08:55:55.000Z","size":404,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-30T10:27:12.622Z","etag":null,"topics":["cli","cli-tool","config","go","golang","imperative","task-runner","taskrunner","yaml"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/quonaro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-31T15:37:14.000Z","updated_at":"2026-06-30T08:55:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/quonaro/Lota","commit_stats":null,"previous_names":["quonaro/lota"],"tags_count":44,"template":false,"template_full_name":null,"purl":"pkg:github/quonaro/Lota","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quonaro%2FLota","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quonaro%2FLota/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quonaro%2FLota/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quonaro%2FLota/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/quonaro","download_url":"https://codeload.github.com/quonaro/Lota/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quonaro%2FLota/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34992071,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","cli-tool","config","go","golang","imperative","task-runner","taskrunner","yaml"],"created_at":"2026-04-21T04:01:04.888Z","updated_at":"2026-07-01T04:01:03.377Z","avatar_url":"https://github.com/quonaro.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lota\n\nA configurable task runner for rapid development. Define commands in a YAML file and run them from the terminal.\n\n## Features\n\n- ✨ **Configurable tasks** - Define tasks in YAML, no code needed\n- 🔧 **Flexible arguments** - Positional, flags, wildcards, arrays with type validation\n- 🔄 **Variable interpolation** - Environment variables with hierarchical scoping\n- 🐚 **Shell-aware execution** - Auto-detects shell binary, overridable at any level\n- 👁️ **Dry-run mode** - Preview commands before execution\n- 🛡️ **Graceful shutdown** - Proper process management on interrupt signals\n- 📄 **Env file imports** - Load variables from .env files\n- 📊 **YAML config imports** - Import nested YAML configs with dot-notation access\n- 📂 **Nested groups** - Organize commands in hierarchical groups\n- 📁 **Working directory** - Set `dir` per command or group (relative to `lota.yml`)\n- � **Tee logging** - Write stdout/stderr to files while still printing to terminal\n- � **Command dependencies** - `depends` for automatic prerequisite execution with cycle detection\n- 🔍 **Upward config search** - Find `lota.yml` in parent directories up to the git root\n\n## 📦 Installation\n\n### Quick Install (Linux/macOS)\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/quonaro/lota/main/scripts/install.sh | bash\n```\n\nOr with specific version:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/quonaro/lota/main/scripts/install.sh | bash -s -- -V v0.1.0\n```\n\nScript verifies SHA256 checksum and installs to `~/.local/bin` (or `/usr/local/bin` with sudo).\n\n### Build from Source\n\nRequires Go 1.26+\n\n```bash\ngo install github.com/quonaro/lota@latest\n```\n\nOr manually:\n\n```bash\ngit clone https://github.com/quonaro/lota.git\ncd lota \u0026\u0026 go build -o lota . \u0026\u0026 sudo mv lota /usr/local/bin/\n```\n\n## 🚀 Quick Start\n\nInitialize a new configuration:\n\n```bash\nlota --init\n```\n\nThis creates a `lota.yml` in your current directory.\n\nOr create it manually:\n\n```yaml\nbuild:\n  desc: Build the application\n  script: go build -o bin/app .\n\ndev:\n  desc: Development commands\n  run:\n    desc: Run with hot reload\n    script: air\n  test:\n    desc: Run tests\n    script: go test ./...\n```\n\nRun a command:\n\n```bash\nlota build\nlota dev run\nlota dev test\n```\n\n## Comparison\n\n| Feature                   | Lota | Task | Just | npm scripts |\n| ------------------------- | ---- | ---- | ---- | ----------- |\n| Declarative YAML          | ✅   | ✅   | ✅   | ❌          |\n| Type-safe arguments       | ✅   | ✅   | ✅   | ❌          |\n| Variable interpolation    | ✅   | ✅   | ✅   | ✅          |\n| Nested groups             | ✅   | ✅   | ❌   | ❌          |\n| Working directory (`dir`) | ✅   | ✅   | ❌   | ❌          |\n| Command dependencies      | ✅   | ✅   | ✅   | ❌          |\n| Upward config search      | ✅   | ❌   | ❌   | ❌          |\n| Env file imports          | ✅   | ✅   | ❌   | ❌          |\n| Shell auto-detection      | ✅   | ❌   | ❌   | ❌          |\n\n### Syntax Comparison\n\n**Simple build command:**\n\n```yaml\n# Lota\nbuild:\n  script: go build -o app .\n\n# Task (Taskfile.yml)\nbuild:\n  cmds:\n    - go build -o app .\n\n# Just\nbuild:\n    go build -o app .\n\n# npm scripts\n\"build\": \"go build -o app .\"\n```\n\n**With arguments:**\n\n```yaml\n# Lota\ndev:\n  args:\n    - port|p:int=3000\n  script: npm start -- --port $port\n\n# Task (Taskfile.yml)\nbuild:\n  vars:\n    PORT: 3000\n  cmds:\n    - npm start -- --port {{.PORT}}\n\n# Just\nport := \"3000\"\ndev:\n    npm start -- --port {{port}}\n\n# npm scripts\n\"dev\": \"npm start -- --port ${PORT:-3000}\"\n```\n\n**With dependencies:**\n\n```yaml\n# Lota\ntest:\n  depends:\n    - build\n  script: go test ./...\n\n# Task (Taskfile.yml)\ntest:\n  deps: [build]\n  cmds:\n    - go test ./...\n\n# Just\nbuild:\n    go build -o app .\n\ntest: build\n    go test ./...\n\n# npm scripts\n\"test\": \"npm run build \u0026\u0026 go test ./...\"\n```\n\n## Examples\n\n### Simple Web Project\n\n```yaml\nshell: bash\n\nvars:\n  - import:env .env\n  - NODE_ENV=development\n\nargs:\n  - port|p:int=3000\n\ndev:\n  desc: Development commands\n  install:\n    desc: Install dependencies\n    script: npm install\n  start:\n    desc: Start development server\n    args:\n      - hot|h:bool\n    script: |\n      if [ \"$hot\" = \"true\" ]; then\n        npm run dev\n      else\n        npm start\n      fi\n\nbuild:\n  desc: Build for production\n  before: npm run clean\n  script: npm run build\n  after: echo \"Build completed successfully\"\n\ntest:\n  desc: Run tests\n  args:\n    - coverage|c:bool\n  script: |\n    if [ \"$coverage\" = \"true\" ]; then\n      npm test -- --coverage\n    else\n      npm test\n    fi\n```\n\n### DevOps / Infrastructure\n\n```yaml\nshell: bash\n\nvars:\n  - DOCKER_COMPOSE=docker-compose\n  - KUBECTL=kubectl\n\ninfra:\n  desc: Infrastructure management\n  docker:\n    desc: Docker operations\n    up:\n      desc: Start all services\n      script: $DOCKER_COMPOSE up -d\n    down:\n      desc: Stop all services\n      script: $DOCKER_COMPOSE down\n    logs:\n      desc: View logs\n      args:\n        - service:str\n        - ...tail\n      script: $DOCKER_COMPOSE logs -f \"$service\" \"$tail\"\n  k8s:\n    desc: Kubernetes operations\n    namespace:\n      desc: Namespace operations\n      create:\n        desc: Create namespace\n        args:\n          - name:str\n        script: $KUBECTL create namespace \"$name\"\n      delete:\n        desc: Delete namespace\n        args:\n          - name:str\n        script: $KUBECTL delete namespace \"$name\"\n    deploy:\n      desc: Deploy application\n      args:\n        - env|e:str=dev\n        - dry|d:bool\n      script: |\n        if [ \"$dry\" = \"true\" ]; then\n          $KUBECTL apply --dry-run=client -f \"k8s/$env/\"\n        else\n          $KUBECTL apply -f \"k8s/$env/\"\n        fi\n```\n\n### Go Project\n\n```yaml\nshell: bash\n\nvars:\n  - GOOS=linux\n  - GOARCH=amd64\n  - BINARY_NAME=app\n\nargs:\n  - target|t:str=linux/amd64\n\nbuild:\n  desc: Build the application\n  args:\n    - output|o:str=./bin\n    - race|r:bool\n  script: |\n    if [ \"$race\" = \"true\" ]; then\n      go build -race -o \"$output/$BINARY_NAME\" .\n    else\n      go build -o \"$output/$BINARY_NAME\" .\n    fi\n\ntest:\n  desc: Run tests\n  args:\n    - verbose|v:bool\n    - cover|c:bool\n  script: |\n    FLAGS=\"\"\n    if [ \"$verbose\" = \"true\" ]; then\n      FLAGS=\"$FLAGS -v\"\n    fi\n    if [ \"$cover\" = \"true\" ]; then\n      FLAGS=\"$FLAGS -cover\"\n    fi\n    go test $FLAGS ./...\n\nrelease:\n  desc: Build release binaries\n  before: echo \"Building release for $target\"\n  script: |\n    IFS=/ read -r GOOS GOARCH \u003c\u003c\u003c \"$target\"\n    go build -o ./dist/${BINARY_NAME}-${GOOS}-${GOARCH} .\n  after: ls -lh ./dist/\n```\n\n### Multi-Environment Project\n\n```yaml\nshell: bash\n\nvars:\n  - import:env .env.local\n  - import:env .env.shared\n  - import:yaml config/secrets.yaml@public app # Import public config section\n\nargs:\n  - environment|env:str=dev\n\ndb:\n  desc: Database operations\n  migrate:\n    desc: Run database migrations\n    script: |\n      case \"$environment\" in\n        dev)   npm run db:migrate:dev ;;\n        staging) npm run db:migrate:staging ;;\n        prod)  npm run db:migrate:prod ;;\n        *)     echo \"Unknown environment\" ;;\n      esac\n  seed:\n    desc: Seed database with test data\n    before: echo \"Seeding $environment database...\"\n    script: npm run \"db:seed:$environment\"\n    after: echo \"Database seeded successfully\"\n\ndeploy:\n  desc: Deployment operations\n  staging:\n    desc: Deploy to staging\n    script: |\n      npm run build:staging\n      npm run deploy:staging\n  production:\n    desc: Deploy to production\n    args:\n      - confirm|c:bool\n    script: |\n      if [ \"$confirm\" != \"true\" ]; then\n        echo \"Use --confirm to deploy to production\"\n        exit 1\n      fi\n      npm run build:prod\n      npm run deploy:prod\n```\n\n## ⚙️ Configuration\n\n### 📋 Structure\n\n```yaml\nshell: bash # Optional: default shell (auto-detected if omitted)\n\nvars: # global environment variables\n  - KEY=value\n  - import:env .env # Import from .env file\n\nargs: # global argument definitions\n  - name:type=default\n\ngroup-name: # command group\n  desc: ...\n  color: cyan # Optional: highlight group name in help\n  inherit_color: true # Optional: inherit color from parent group\n  shell: sh # Optional: override shell for this group\n  vars: # group-level variables\n    - KEY=value\n  args: # group-level arguments\n    - name:type=default\n  log: # Optional: group-level tee logging\n    path: group.log\n  command-name:\n    desc: ...\n    color: green # Optional: highlight command name in help\n    script: ...\n\ncommand-name: # top-level command\n  desc: ...\n  color: red # Optional: highlight command name in help\n  script: ...\n  log: # Optional: command-level tee logging\n    path: cmd.log\n```\n\n### 🔑 Variables (`vars`)\n\nVariables are exported as environment variables into scripts. Both `vars` and `args` share a unified environment pool — CLI args override vars on name collision. They support three scopes with priority: **app \u003c group \u003c command**.\n\n```yaml\nvars:\n  - DOCKER=docker compose # app-level\n\ndev:\n  vars:\n    - DOCKER=docker # overrides app-level for this group\n  run:\n    vars:\n      - DOCKER=podman # overrides group-level for this command\n    script: $DOCKER up\n```\n\n#### 📄 Import from .env files\n\nLoad variables from environment files:\n\n```yaml\nvars:\n  - import:env .env\n  - import:env config/prod.env\n```\n\n#### 📊 Import from YAML files\n\nImport nested YAML configurations with automatic flattening to dot-notation:\n\n```yaml\nvars:\n  - import:yaml config.yaml # Import all with original keys\n  - import:yaml config.yaml app # Import all with 'app.' prefix\n  - import:yaml config.yaml@public # Import only 'public' section\n  - import:yaml secrets.yaml@db cfg # Import 'db' section with 'cfg.' prefix\n```\n\n**Syntax:** `import:yaml \u003cfile\u003e[@\u003csection\u003e] [\u003cprefix\u003e]`\n\n\u003e **Note:** The old `!import:yaml` syntax is deprecated but still supported. Use `import:yaml` instead.\n\n- **file** - Path to YAML file\n- **section** (optional) - Import only specific top-level section via `@section`\n- **prefix** (optional) - Add prefix to all imported keys\n\n**Example YAML file:**\n\n```yaml\n# config.yaml\npublic:\n  app_name: MyApp\n  version: 1.0.0\n  database:\n    host: localhost\n    port: 5432\n\nprivate:\n  api_key: secret123\n```\n\n**Resulting variables:**\n\n```yaml\n# import:yaml config.yaml@public app\nvars:\n  app.app_name: \"MyApp\"\n  app.version: \"1.0.0\"\n  app.database.host: \"localhost\"\n  app.database.port: \"5432\"\n```\n\nAccess in scripts: `$app_app_name`, `$app_database_host`\n\n### 🎯 Arguments (`args`)\n\nArguments are passed from the CLI and exported as environment variables, accessible via `$name` in scripts.\n\n**Format:** `name|short:type=default`\n\n| Part       | Description              | Example                         |\n| ---------- | ------------------------ | ------------------------------- |\n| `name`     | Long name                | `output`                        |\n| `\\|short`  | Short alias (optional)   | `\\|o`                           |\n| `:type`    | Type (optional)          | `:str`, `:int`, `:bool`, `:arr` |\n| `=default` | Default value (optional) | `=./bin`                        |\n\n#### 📝 Argument Types\n\n**Positional** — passed by position, no flag needed:\n\n```yaml\nargs:\n  - filename:str\n  - count:int\nscript: process \"$filename\" \"$count\"\n```\n\n```bash\nlota cmd file.txt 5\n```\n\n**Flag** — passed by name using `--flag` or `-f`. Any arg with a short alias (`|short`) or type `bool` becomes a flag:\n\n```yaml\nargs:\n  - output|o:str=./bin\n  - verbose|v:bool\nscript: go build -o \"$output\"\n```\n\n```bash\nlota cmd --output ./dist\nlota cmd -o ./dist --verbose\n```\n\n**Wildcard** — captures all remaining positional arguments:\n\n```yaml\nargs:\n  - service:str\n  - ...cmd\nscript: docker exec \"$service\" \"$cmd\"\n```\n\n```bash\nlota cmd backend python manage.py shell\n# service=backend, cmd=\"python manage.py shell\"\n```\n\n**Array** — collects multiple consecutive positional values:\n\n```yaml\nargs:\n  - files:arr[5] # collect up to 5 values\nscript: lint $files\n```\n\n```bash\nlota cmd a.go b.go c.go\n```\n\n#### Boolean Flags\n\nBool args support negation via `--!name`:\n\n```bash\nlota cmd --verbose          # verbose=true\nlota cmd --!verbose         # verbose=false\nlota cmd --verbose=false    # verbose=false\n```\n\n#### Argument Scopes\n\nLike vars, args can be defined at app, group, or command level and are merged with the same priority (command wins):\n\n```yaml\nargs:\n  - env:str=dev # available to all commands\n\ndeploy:\n  args:\n    - env:str=prod # overrides app-level for this group\n  run:\n    script: ./deploy.sh --env=\"$env\"\n```\n\n\u003e **Deprecation:** Using `{{name}}` for variable and argument interpolation is deprecated. Use `$name` instead. `{{name}}` will be removed in a future version.\n\n\u003e **Reserved Variables:** System environment variable names (PATH, HOME, USER, SHELL, etc.) are reserved and cannot be overridden in `vars`.\n\n### 🐚 Shell Configuration\n\n**Important:** Lota selects the shell interpreter, but the script itself is **shell-specific**. Write scripts for the shell you target.\n\nLota auto-detects the shell binary from the system environment. If detection fails, it falls back to `bash`.\n\nOverride the shell at any level:\n\n```yaml\nshell: zsh # app-level\n\ndev:\n  shell: bash # group-level override\n  run:\n    shell: sh # command-level override\n    script: echo $0\n```\n\nSupported shells: bash, sh, zsh, dash, ksh, mksh, pdksh, ash, busybox, sash, tcsh, csh, fish\n\n### 📁 Working Directory (`dir`)\n\nSet the working directory for commands and groups. The path is resolved relative to the `lota.yml` file location.\n\n```yaml\nbackend:\n  dir: ./backend # group-level default\n  build:\n    desc: Build backend\n    script: go build .\n  test:\n    desc: Run backend tests\n    dir: ./backend/tests # command-level override\n    script: go test ./...\n```\n\nPriority: **command \u003e group \u003e cwd**. Useful in monorepos where different commands run in different subprojects.\n\n### 🔗 Command Dependencies (`depends`)\n\nReference other commands that must run before the current one. Dependencies are specified as full dot-separated paths.\n\n```yaml\nbuild:\n  desc: Build the application\n  script: go build -o bin/app .\n\ntest:\n  desc: Run tests\n  depends:\n    - build\n  script: go test ./...\n\ndeploy:\n  desc: Deploy to production\n  depends:\n    - build\n    - test\n  script: ./deploy.sh\n```\n\nDependencies execute with **their own context** (shell, vars, dir, default args). Circular dependencies are detected automatically and produce an error.\n\nIndependent dependencies run **in parallel by default**, with output prefixed by colored task name (like Docker Compose):\n\n```bash\n\\x1b[35m[build]\\x1b[0m  go build -o bin/app .\n\\x1b[36m[lint]\\x1b[0m   golangci-lint run\n\\x1b[35m[build]\\x1b[0m  ✓ done\n\\x1b[36m[lint]\\x1b[0m   ✓ done\n\\x1b[33m[deploy]\\x1b[0m ./deploy.sh\n```\n\nEach task gets its own color: either from its `color` field, inherited from a parent group, or a deterministic color derived from the task name.\n\nTo force sequential execution, set `parallel: false`:\n\n```yaml\nci:\n  depends:\n    - lint\n    - test\n  parallel: false\n  script: echo \"CI done\"\n```\n\n\u003e Shared dependencies are executed **once** (deduplication). If `build` is a dependency of both `test` and `deploy`, running `lota deploy` will execute `build` exactly once.\n\n\u003e **TODO:** A TUI for interactive task monitoring is under consideration for future versions.\n\n### ⚡ Hooks Tutorial\n\nLota provides five execution stages per command. You only use what you need — a simple `script` is enough for most tasks.\n\n```\nbefore → script → after → finally\n          ↓\n    fallback → finally\n```\n\n| Stage          | Purpose                                      | Runs on error?                      |\n| -------------- | -------------------------------------------- | ----------------------------------- |\n| **`before`**   | Preparation (compile, check env)             | Skips `script`, triggers `fallback` |\n| **`script`**   | Main command                                 | Triggers `fallback`                 |\n| **`after`**    | Post-success action (notify, log)            | Triggers `fallback`                 |\n| **`fallback`** | Recovery / alternative path (rollback, alert, degrade) | If succeeds, command returns `0`  |\n| **`finally`**  | Cleanup (stop containers, remove temp files) | Always runs                         |\n\n\u003e Return code: `0` if `before`+`script`+`after` succeeded, **or if `fallback` succeeded after a failure**. Otherwise the first error's exit code. `finally` errors are printed to stderr but do not change the return code.\n\n#### Example 1: Basic Pipeline\n\n```yaml\nbuild:\n  before: echo \"Compiling...\"\n  script: go build -o bin/app .\n  after: echo \"Build complete\"\n```\n\n**Happy path:** `before` → `script` → `after` → return 0\n\n**If `script` fails:** `before` → `script` (exit 1) → return 1. `after` is skipped.\n\n#### Example 2: Cleanup with `finally`\n\nUse `finally` for operations that must run regardless of success or failure.\n\n```yaml\ntest:\n  before: docker-compose up -d test-db\n  script: go test ./...\n  finally: docker-compose down test-db\n```\n\n**Any outcome:** `before` → `script` → `finally` → return 0 or 1. The database container is always stopped.\n\n#### Example 3: Error Handling with `fallback`\n\nUse `fallback` to react to failures — rollback, send alerts, write crash reports.\n\n```yaml\ndeploy:\n  before: echo \"Starting deploy...\"\n  script: ./deploy.sh\n  after: echo \"Deploy successful\"\n  fallback: ./rollback.sh\n  finally: echo \"Deploy finished\"\n```\n\n**Happy path:** before → script → after → finally → return 0\n**Script fails:** before → script (fail) → fallback → finally → return 1\n**After fails:** before → script → after (fail) → fallback → finally → return 1\n\n#### Example 4: Full Pipeline — Database Migration\n\n```yaml\ndb:\n  migrate:\n    before: |\n      echo \"Creating backup...\"\n      pg_dump mydb \u003e /tmp/backup.sql\n    script: |\n      echo \"Running migrations...\"\n      migrate -path ./migrations -database \"$DATABASE_URL\" up\n    after: echo \"Migration complete\"\n    fallback: |\n      echo \"Migration failed, restoring backup...\"\n      psql mydb \u003c /tmp/backup.sql\n    finally: rm -f /tmp/backup.sql\n```\n\n| Scenario        | Flow                                                                           |\n| --------------- | ------------------------------------------------------------------------------ |\n| Success         | before → script → after → finally (backup deleted)                             |\n| Migration fails | before → script (fail) → fallback (restore) → finally (backup deleted)         |\n| After fails     | before → script → after (fail) → fallback (restore) → finally (backup deleted) |\n\n### 📝 Tee Logging (`log`)\n\nWrite command output to log files while still printing to the terminal. Logs support **additive inheritance**: a command writes to its own log file **plus** all ancestor log files, unless `independent: true` breaks the chain.\n\n```yaml\nlog:\n  path: logs/all.log # app-level: all commands inherit this\n\nbuild:\n  desc: Build the application\n  script: go build -o bin/app .\n  log:\n    path: logs/build.log # writes to both all.log and build.log\n    truncate: true # overwrite on each run (default: append)\n\ntest:\n  desc: Run tests\n  script: go test ./...\n  log:\n    path: logs/test.log\n    independent: true # writes ONLY to test.log, skips all.log\n```\n\n| Field         | Type   | Default      | Description                                                                                     |\n| ------------- | ------ | ------------ | ----------------------------------------------------------------------------------------------- |\n| `path`        | string | **required** | Log file path (relative to `lota.yml`). Supports variable interpolation (`$var`).               |\n| `truncate`    | bool   | `false`      | If `true`, overwrite the file on each run. If `false`, append.                                  |\n| `independent` | bool   | `false`      | If `true`, discard all ancestor logs and write only to this file. **Not allowed at app level.** |\n\n**Inheritance behavior:**\n\n- `independent: false` (default): the command writes to its own `path` **plus** all ancestor `path`s.\n- `independent: true`: the command writes **only** to its own `path`; ancestor logs are skipped.\n- `truncate` applies **only** to the `path` declared on the same level.\n\n```yaml\nlog:\n  path: logs/global.log\n\ninfra:\n  desc: Infrastructure\n  log:\n    path: logs/infra.log\n    independent: true # infra commands skip global.log\n  docker:\n    desc: Docker ops\n    log:\n      path: logs/docker.log # writes to infra.log + docker.log\n    up:\n      script: docker-compose up -d\n```\n\nRuntime errors (missing parent dir, permission denied, path is a directory) are printed to stderr as `[log error]` but **do not fail the command**.\n\n### 📁 Nested Groups\n\nOrganize commands in hierarchical groups:\n\n```yaml\ninfra:\n  desc: Infrastructure commands\n  docker:\n    desc: Docker operations\n    up:\n      script: docker-compose up\n    down:\n      script: docker-compose down\n  k8s:\n    desc: Kubernetes operations\n    apply:\n      script: kubectl apply -f k8s/\n```\n\n```bash\nlota infra docker up\nlota infra k8s apply\n```\n\n### 🎨 Help Colors\n\nHighlight group and command names in `lota help` output using named ANSI colors or hex values:\n\n```yaml\ndev:\n  desc: Development commands\n  color: cyan\n  frontend:\n    desc: Frontend commands\n    inherit_color: true\n    start:\n      desc: Start dev server\n      inherit_color: true\n      script: npm run dev\n    build:\n      desc: Build frontend\n      color: yellow\n      script: npm run build\n```\n\n| Option          | Description                                                                                                                                              |\n| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `color`         | Named ANSI color (`black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, and `hi*` variants) or any `#RRGGBB` hex value (e.g. `#FF5733`) |\n| `inherit_color` | `true` to inherit the nearest ancestor `color`. Defaults to `null` (no inheritance)                                                                      |\n\nColor resolution priority: **direct `color` \u003e inherited `color` \u003e default**. `inherit_color: true` walks up the group chain and uses the first non-empty color found. Hex colors work in true-color capable terminals.\n\n## 🚩 Global Flags\n\n| Flag                                   | Description                                          |\n| -------------------------------------- | ---------------------------------------------------- |\n| `-h`, `--help`                         | Show help                                            |\n| `-V`                                   | Show version only (machine-friendly)                 |\n| `--version`                            | Show version with ASCII banner                       |\n| `-v`, `--verbose`                      | Enable verbose output                                |\n| `--dry-run`                            | Show commands without executing                      |\n| `--init`                               | Create a template lota.yml                           |\n| `--config`                             | Specify config file or directory                     |\n| `--install-completion`                 | Install shell completion script (auto-detects shell) |\n| `--install-completion zsh\\|bash\\|fish` | Install completion for a specific shell              |\n| `--completion-script zsh\\|bash\\|fish`  | Print completion script to stdout                    |\n\n## 🐚 Shell Completion\n\nLota provides built-in shell completion for **bash**, **zsh**, and **fish**.\n\n### Auto-install\n\n```bash\nlota --install-completion\n```\n\nLota detects your shell from `$SHELL` and writes the completion script to the standard location.\n\n### Install for a specific shell\n\n```bash\nlota --install-completion bash\nlota --install-completion zsh\nlota --install-completion fish\n```\n\n### Manual install (print to stdout)\n\n**Bash:**\n\n```bash\nlota --completion-script bash \u003e\u003e ~/.bashrc\n```\n\n**Zsh:**\n\n```bash\nlota --completion-script zsh \u003e ~/.config/zsh/completions/_lota\n```\n\n**Fish:**\n\n```bash\nlota --completion-script fish \u003e ~/.config/fish/completions/lota.fish\n```\n\n### Troubleshooting\n\nIf `lota` behaves like a completion engine instead of executing commands:\n\n```bash\nunset COMP_LINE COMP_POINT\n```\n\nThen regenerate and reinstall the completion script for your shell.\n\n## �� Upward Config Search\n\nIf `lota.yml` is not found in the current directory, Lota searches upward through parent directories until it finds one or reaches the git root (`.git` directory) or the filesystem root (`/`).\n\nThis is critical for monorepos and nested projects where you might run commands from subdirectories:\n\n```bash\ncd backend/src\nlota build    # finds lota.yml in project root\n```\n\nPass `--help` after a command to see its arguments:\n\n```bash\nlota dev run --help\n```\n\n## 👁️ Dry Run Mode\n\nPreview what would be executed without actually running it:\n\n```bash\nlota build --dry-run\n```\n\n## 👨‍💻 Development\n\n### Prerequisites\n\n- Go 1.26+\n- Python 3.8+ (for pre-commit)\n- cocogitto (cog) - for conventional commits\n\n### Setup Git Hooks\n\nInstall git hooks for commit validation and code quality:\n\n```bash\n# Install pre-commit\npip install pre-commit\n\n# Install git hooks\n./scripts/install-hooks.sh\n```\n\nThis installs:\n\n- **commit-msg hook** - validates conventional commits via cocogitto\n- **post-commit hook** - automatic version tagging\n- **pre-commit hooks** - runs go fmt, go vet, go test, and golangci-lint\n\n### Manual Pre-commit\n\nRun pre-commit manually without committing:\n\n```bash\n# Run all hooks\npre-commit run --all-files\n\n# Run specific hook\npre-commit run go-fmt --all-files\n```\n\n### Testing\n\n```bash\n# Run all tests\ngo test ./...\n\n# Run with race detector\ngo test -race ./...\n\n# Run with coverage\ngo test -cover ./...\n```\n\n### Linting\n\n```bash\n# Run golangci-lint\ngolangci-lint run\n\n# Run go vet\ngo vet ./...\n\n# Format code\ngofmt -w -s .\n```\n\n## 🏗️ Architecture\n\nLota follows a strict layered architecture:\n\n- **config/** - YAML parsing and configuration models\n- **runner/** - Command execution, argument parsing, interpolation\n- **cli/** - CLI orchestration (only orchestrates, doesn't import runner)\n- **shared/** - Constants and shared utilities\n\nKey design principles:\n\n- Stateless - no global variables\n- Context-aware execution with graceful shutdown\n- Pure functions for interpolation and parsing (testable)\n- Clean error handling with wrapped errors\n\n## 📜 License\n\n[Apache License 2.0](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquonaro%2Flota","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fquonaro%2Flota","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquonaro%2Flota/lists"}