Loha CLI
The Loha CLI is a single static binary for macOS, Linux and Windows. It drives the same
/api/v1 surface as the dashboard, so anything you do in the browser
you can script from a terminal, a CI job, or an agent.
Install
curl -fsSL https://s3th-api-a.inet.co.th/nopadolsooktiebqli1:loha-cli/install.sh | sh
loha version
The script detects your OS and architecture, downloads the latest release, verifies its
checksum, and installs loha to /usr/local/bin (or ~/.local/bin when that isn't
writable). Set LOHA_VERSION to pin a version or LOHA_INSTALL_DIR to choose the
directory. On Windows, download loha-windows-amd64.exe from
https://s3th-api-a.inet.co.th/nopadolsooktiebqli1:loha-cli/<version>/; the latest version number is in https://s3th-api-a.inet.co.th/nopadolsooktiebqli1:loha-cli/stable.txt.
Keep it current with:
loha update # replace the binary with the latest release
loha update --check # only report whether an update is available
Commands
Read commands (builds, build get, logs, repos, repos discover, connections list) accept --json for stable, machine-readable output: the path for scripting and agents.
loha login: save a workspace credential (a personal access token or service account key) as a named context. Members, admins and owners can create a personal access token in Settings → API Keys (guests cannot), where member self-service is enabled for the workspace.loha whoami: show the active token's workspace, identity, and scopesloha workspace list/loha workspace use <context>: list / switch contextsloha init(aliasloha scaffold): scaffold a starter.loha.ymlloha validate [file]: validate a.loha.ymlagainst the server parser (defaults to./.loha.yml)loha plan [--branch <b>] [--event <e>] [--json]: show what the cluster would run for a.loha.yml, without running anything (seeloha plan)loha run --local [--job <id>] [--secret NAME] [--env-file <path>] [--json]: run the plan's jobs in Docker or Podman containers on your machine, with logs and artifacts in.loha/local-runs/(see Run your pipeline locally)loha connections list: list the workspace's SCM connectionsloha connections add: store an SCM connection from a Git host tokenloha repos: list repositories in the active workspaceloha repos discover --connection <id>: browse remote repos reachable through a connectionloha repos add: import a repository into the workspace (with--connection+--full-name,--html-urland the default branch are resolved automatically via discovery)loha link --repo <id>: create a trigger for a repoloha onboard: import a repo, create its trigger, and optionally run the first build in one step (see One-shot onboarding)loha deploy [--repo <id>] [--watch]: fire a manual run for a trigger (repo inferred from the git remote when omitted; optionally wait for it)loha builds [filters] [--json]: list recent builds (filter by--repo/--status/--branch/--since/--until)loha build get <id> [--watch]: show a build's detail and step breakdownloha logs <id> [--follow]: fetch a build's logs (paginated;--followpolls until the build finishes)loha cancel <id>: cancel a PENDING/QUEUED/RUNNING buildloha releases [filters] [--json]: list releases: which commit produced which image, and its digest (filter by--repo/--project/--build)loha images verify <repository@sha256:digest>: verify an image's Loha signature and provenance with your localcosignloha images policy --engine sigstore [--mode audit|enforce]: download the workspace's admission policy to apply to your own clusterloha update: self-update to the latest released versionloha version: print the CLI version
Use a service account key instead of a personal token, and pass it via the LOHA_TOKEN environment variable: no loha login and no config file needed. See Using the CLI in CI/CD.
Authentication
On your laptop, authenticate once and the CLI remembers it. Create a token in the dashboard under Settings → API Keys, then log in with the API URL of this environment:
loha login --api-url https://app.loha.longtumlab.com --token loha_xxxxxxxxxxxxxxxxxxxx
# optionally name the context (handy if you use more than one workspace):
loha login --api-url https://app.loha.longtumlab.com --token loha_… --context work
loha whoami
--api-urlPass --api-url https://app.loha.longtumlab.com (or set LOHA_API_URL) on the first login. The CLI's
built-in default endpoint is not this environment, so a login without it fails. The
URL is saved with the context, so later commands don't need it.
The credential is written to a config file at ~/.config/loha/config.yaml (mode
0600, honoring XDG_CONFIG_HOME), not an OS keychain. Each context pairs a
workspace-scoped token with an API URL; loha workspace use <context> switches between
them.
In CI there's no config file: set LOHA_TOKEN and LOHA_API_URL=https://app.loha.longtumlab.com and every
command runs headless. See Using the CLI in CI/CD.
How commands map to the API
Each command is a thin wrapper over the /api/v1 surface. You can
drive the same flow with curl today.
| Command | Endpoint(s) | Scope |
|---|---|---|
loha login / loha whoami | GET /api/v1/whoami | — |
loha validate | POST /api/v1/pipelines/validate | pipelines:read |
loha plan | POST /api/v1/pipelines/plan | pipelines:read (+ variables:values:read for plain variable values) |
loha run --local | POST /api/v1/pipelines/plan (the only request; steps run locally) | pipelines:read (+ variables:values:read for plain variable values) |
loha connections list | GET /api/v1/scm-connections | connections:read |
loha connections add | POST /api/v1/scm-connections | connections:write |
loha repos | GET /api/v1/repos | repos:read |
loha repos discover | GET /api/v1/scm-connections/{id}/repos | repos:read |
loha repos add | POST /api/v1/repos | repos:write |
loha link | POST /api/v1/triggers/create | triggers:write |
loha onboard | POST /api/v1/repos, POST /api/v1/triggers/create, then a run | repos:read, repos:write, triggers:write (+ triggers:run with --deploy) |
loha deploy | GET /api/v1/triggers (with --repo), POST /api/v1/triggers/{id}/run | pipelines:read, triggers:run |
loha builds | GET /api/v1/builds | builds:read |
loha build get / --watch | GET /api/v1/builds/{id} | builds:read |
loha logs | GET /api/v1/builds/{id}/logs | builds:read |
loha cancel | DELETE /api/v1/builds/{id} | builds:write |
loha releases | GET /api/v1/releases | repos:read |
loha images policy | GET /api/v1/security/admission-policy | security:read |
(loha workspace …, loha init, loha update, and loha version are local: they don't call the API.)
Onboarding a repo from the terminal
Connecting a repo no longer requires the dashboard. The only manual step is creating the SCM token in your Git host (e.g. a GitLab PAT with read_repository); everything after is headless:
loha connections add --name "Company GitLab" --provider gitlab \
--api-url https://gitlab.example.com --token <pat> # connections:write
loha repos discover --connection <connection-id> # pick a repo
loha repos add --connection <connection-id> \
--full-name group/project # html-url + branch resolved via discovery
loha link --repo <repository-id> # create a trigger
loha deploy --repo <repository-id> --watch # ship and wait
One-shot onboarding
loha onboard collapses the import → link → run steps into a single command. Once you have an SCM connection, point it at a repo and (optionally) ship the first build in one go:
# fully headless: import, create a "deploy" trigger, run it, and wait
loha onboard --connection <connection-id> --repo group/project --deploy --watch
It resolves the repo's web URL and default branch through the connection, imports it, scaffolds a manual trigger that builds the repo's .loha.yml, and, with --deploy (or --watch, which implies --deploy), fires the first build. Each step prints as it completes (Repository imported: → Trigger created: → Build queued:) so a mid-flow failure is unambiguous about which step failed. Without --deploy, it ends with a Next: hint pointing at loha deploy --repo <id>.
Flags:
| Flag | Purpose |
|---|---|
--connection <id> | SCM connection to import through |
--repo group/project | repository path (full name) to import |
--name | trigger name (default deploy) |
--branch | branch to build (default: repo's default branch) |
--config-path | pipeline config path (default .loha.yml) |
--deploy | run the trigger after linking |
--watch | poll the build until it finishes (implies --deploy); exits non-zero on failure |
--var KEY=VALUE | runtime variable injected as step env, repeatable (only used when deploying) |
--json | print the created repo + trigger + run as a JSON summary |
Run interactively by omitting --connection/--repo on a terminal. It lists your connections and their discoverable repos and asks you to pick. For non-interactive use (a pipe, or --json), both flags are required and a clear error is returned instead of blocking on a prompt.
Watching builds & exit codes (agents / CI)
--watch (on deploy and build get) polls until the build reaches a terminal status and exits non-zero when it ends FAILED/CANCELLED/TIMEOUT, so a CI step or autonomous agent can branch on the exit code without parsing output. Combine with --json on the read commands for structured results, and loha logs <id> --follow to stream a running build's logs to completion.
Deploy semantics
loha deploy fires a manual run for a trigger, which builds from the .loha.yml
committed to your repository and ships through Loha's GitOps (ArgoCD) path, so
commit and push your pipeline first.
The trigger is resolved in three escalating steps:
--trigger <id>runs that trigger directly.--repo <id>resolves the repo's enabled, manually-runnable triggers. When exactly one matches, the CLI runs it directly: the common case (one repo, one deploy trigger) needs no extra typing. When more than one matches it stays explicit: the candidates are listed and you must re-run with--trigger <id>so it's always unambiguous which trigger fired.--yes/-yskips any confirmation on the single-trigger case (it already auto-runs by default).- Neither flag: run
loha deployfrom inside a repo's checkout and the CLI infers the repository from the local git remote (origin), then resolves its trigger as in step 2. If the cwd isn't a git repo, or the remote matches no onboarded Loha repo, the error tells you to pass--repo <id>explicitly or checkloha repos.
Optional --branch / --commit flags override the git ref; otherwise the trigger's own
configuration decides.
Deploying an uncommitted local working directory (uploading a build context,
vercel deploy-style) is a separate, larger capability and is not part of this flow.
Architecture (for contributors)
- Distribution: a single static Go binary (like
gh/vercel), no runtime dependency on the user's machine. - Client: a thin typed wrapper over the published
/api/v1surface; every request is bearer-token authenticated, so the CLI stays in lockstep with the API contract. - Auth:
loha loginsaves a token to~/.config/loha/config.yamland sends it as a bearer token; the CLI only ever talks to/api/v1.
On the roadmap
- Browser login:
loha loginwith no token opens the dashboard, lets you pick a workspace, and returns a scoped, expiring token (no manual paste).
Where to give feedback
CLI scope is still being shaped. If you have a use case we haven't covered, file an
issue in the public loha-apps repo or reach out to the team.