> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

> **Themelio compatibility reference.** This page describes the retained Themelio implementation or its historical design. Check the [parallel-operation guide](/compatibility/index.md) before applying it to the current platform.

# ADR-0021: Themelio Command-Line Interface

**Status:** Draft

**Date:** 2026-07-06

**Deciders:** TBD

**Relates to:** [ADR-0011 (Human Web UI and Agent Client Access)](/adr/0011-human-web-ui-and-agent-client-access.md) — the CLI is a new programmatic client alongside the web UI; [ADR-0012 (Job and Deployment Management System)](/adr/0012-job-deployment-management-system.md) and [ADR-0013 (actions.yaml Automation System)](/adr/0013-actions-yaml-automation-system.md) — the CLI drives and observes the jobs/deployments and actions those ADRs introduced.

## Context

Themelio today is reachable only through its Next.js web UI. Machine-learning practitioners, CI pipelines, and agent clients increasingly expect a terminal-driven, scriptable client — the way Hugging Face users reach the Hub through the `hf` CLI. We want a first-class `themelio` command-line tool that covers the resource lifecycle the platform already exposes (auth, repositories for models/datasets/spaces, running actions, and observing the resulting jobs and deployments) without inventing new backend architecture.

### Current State

<!-- Built from exploration of services/web, services/gitlab-root-actor, and services/k8s-root-actor during this decision. -->

- The **web service** (`services/web`) exposes an HTTP API under `/api/**` for models, datasets, spaces, repos, jobs, deployments, and settings. It backs onto GitLab (GraphQL/REST) and proxies jobs/deployments to `k8s-root-actor`.
- **Authentication** is a single `PRIVATE-TOKEN` header. In the browser flow, NextAuth (OIDC/Keycloak) mints a GitLab **impersonation token** stored in Valkey keyed by an opaque, `httpOnly` session cookie; middleware injects that token as `PRIVATE-TOKEN`. Users can also mint a **personal access token** in **Settings → Access Tokens** via `POST /api/settings/tokens`, fulfilled by `gitlab-root-actor` against GitLab's admin endpoint.
- **Content** (repository files, including large files) lives in GitLab and is transferred over git-HTTPS + LFS. The API's `POST …/commit` file-write endpoints are **stubs** (they return fabricated commits and persist nothing).
- **Compute** is never created directly: running an `actions.yaml` action (ADR-0013) yields a **job** (`slurm-job`) or a **deployment** (`k8s-deployment` / `k8s-inferenceservice`), persisted per ADR-0012. The server validates action **parameter values** at execution time but performs **no structural validation** of `actions.yaml`, and resolving a `ref:` requires the shipped references that live in `k8s-root-actor`.

### Current Problems

1. **No terminal/scriptable client.** Every operation requires the browser UI; there is no way to script repository, action, or job/deployment workflows, or to drive Themelio from CI or an agent.
2. **No documented headless authentication path.** The browser cookie flow is unusable outside a browser, and no client-facing token model is written down for programmatic use.
3. **No content-transfer story for a client.** With commit endpoints stubbed, a client has no API to push or pull repository bytes.
4. **No way to validate `actions.yaml` before running it.** Authors only discover structural mistakes when an action fails at execution time.

### Requirements

1. Authenticate non-interactively (CI) and interactively, using a credential that works for both the web API and git content transfer.
2. Cover the existing resource lifecycle: create/fork/list/inspect repositories; run actions; observe and destroy jobs and deployments.
3. Be scriptable: machine-readable output and stable exit codes.
4. Add the minimum viable backend surface; reuse existing services and semantics.

## Decision Drivers

- **Familiarity** — behave like `hf` where it fits, so ML users are immediately productive.
- **Thin client** — keep logic and validation on the server; the CLI orchestrates and formats.
- **Reuse over rebuild** — lean on the existing web API, `actions.yaml`, and jobs/deployments rather than new architecture.
- **One credential** — a single token for both API calls and git.
- **Scriptability** — first-class `--json` and predictable exit codes.

## Considered Options

### Option A: A Python `themelio` CLI over the web API, with git hand-off for content

A standalone Python 3.11+ CLI (`themelio`, distributed via `pipx`/`uv`, built on Typer + httpx) that is a thin client of the web API. Its design rests on five sub-decisions.

1. **Authentication — paste an `api`-scoped GitLab PAT.** The user creates a personal access token in **Settings → Access Tokens** with the **"Full API Access"** (`api`) scope and pastes it into `themelio auth login` (or supplies `THEMELIO_TOKEN`/`--token` for CI). One `api`-scoped token covers everything, because GitLab's `api` scope also authorizes git push/pull over HTTPS. To enable this, the Settings token UI was extended to offer the `api` scope (previously git read/write only). The CLI stores the token in the OS keyring by default (falling back to `~/.themelio/config`, mode `600`) and verifies it via `GET /api/whoami-v2`. The host defaults to `https://dev.themelio.pharos.grnet.gr` and is confirmed at login.

   Two alternatives were weighed and **rejected**: reusing the browser `session` cookie (it is opaque, `httpOnly`, browser-tethered, revoked on browser sign-out, and meaningless to GitLab, so it cannot authenticate git); and a browser OIDC **device-login** flow (nicer UX but requires new backend endpoints — deferred as a future enhancement, not v1).

2. **Content transfer — hand off to the user's `git`.** Because the commit endpoints are stubs and content lives in GitLab, the CLI moves no bytes itself. `themelio git-setup` prints (or, with `--apply`, writes) a git **credential-helper** configuration so `git clone`/`git push` over HTTPS transparently use the stored token via a hidden `themelio git-credential` subcommand. `<type> <ns/repo> download` and `upload` **print the exact `git`/`git-lfs` commands** for a repo rather than executing them, keeping the CLI thin and free of a git runtime dependency.

3. **Uniform command grammar.** Repo-scoped commands read `themelio <type> <ns/repo> <verb>` (the repo reference immediately after the type). Every resource type (`models`, `datasets`, `spaces`, `repos`) exposes the _same_ core verbs — `create`, `fork`, `ls`, `info`, `card`, `settings`, `ls-files`, `download`, `upload` — with no type-specific verbs.

4. **All compute flows through actions.** `models`, `spaces`, and `repos` carry an `actions` subgroup; `<type> <ns/repo> actions run` is the create-verb that yields a job or a deployment. `jobs` and `deployments` are observe-only (`ls`, `get`, `logs`, `wait`, `destroy`). This mirrors ADR-0012/0013 exactly: there are no ad-hoc "run a container" or per-type "deploy" verbs.

5. **A new server-side validation endpoint.** `<type> actions validate` posts a local `actions.yaml` to a **new `POST /api/actions/validate`** endpoint. It reuses the web executor's existing validation logic (`action-loader.ts` + `action-parameters.ts`) and delegates `ref:` resolution to `k8s-root-actor` (`populateRefs`), so the CLI and the web authoring UI share one source of truth and the CLI never reimplements rules it cannot fully evaluate locally.

**Pros:**

- Familiar, `hf`-like ergonomics with a uniform, predictable grammar.
- A single `api`-scoped token drives both the API and git; no bespoke credential store or new auth backend.
- Thin client: validation and orchestration stay on the server; the CLI formats and hands off.
- Reuses `actions.yaml`, jobs, and deployments unchanged; only one small additive endpoint.
- Python + `pipx`/`uv` matches the ML audience's tooling.

**Cons:**

- Content transfer depends on the user having `git`/`git-lfs` installed and configured (mitigated by `git-setup`).
- Pasting a PAT is less slick than a browser device-login (deferred).
- Some `hf`-familiar conveniences are intentionally absent (no local content cache; no `move`/`branch`/`tag`/`delete-files`) because the backend does not support them yet.
- One new endpoint (`POST /api/actions/validate`) must be built before `actions validate` works.

## Decision

We will build **Option A**: a Python `themelio` CLI that is a thin client of the web API, authenticated by a pasted `api`-scoped GitLab personal access token, using a uniform `themelio <type> <ns/repo> <verb>` grammar, driving compute exclusively through `actions.yaml` (yielding observe-only jobs and deployments), and handing repository content transfer off to the user's own `git`/`git-lfs` (configured via `themelio git-setup`). We will add one backend endpoint, `POST /api/actions/validate`, to back `actions validate`. The Settings token UI has already been extended to offer the `api` scope required by the CLI.

**See also:** [DD-0021: Themelio CLI — Implementation](/dd/0021-themelio-cli.md)

## Consequences

**Positive:**

- Themelio gains a scriptable, CI- and agent-friendly client without new architecture.
- One credential (`api` PAT) works across the web API and git; keyring storage by default.
- The action-driven command model keeps the CLI and platform semantics aligned (ADR-0012/0013), avoiding a divergent "run" surface.
- Server-side `actions.yaml` validation benefits the web authoring UI too.

**Negative:**

- Users must have `git`/`git-lfs` available for content transfer.
- Interactive login is a manual copy-paste until (and unless) a device-login flow is built.
- The CLI cannot offer repository delete, file-level commits, branches, or tags until the corresponding endpoints exist.

**Neutral:**

- The CLI is a separate deliverable (its own Python package), not part of the pnpm monorepo build.
- Jobs/deployments are addressed by id within a repo scope, so those commands take `--repo`.

### Migration

No migration of existing infrastructure is required. The work is additive: a new client package, one new additive endpoint (`POST /api/actions/validate`), and an already-shipped, backward-compatible addition of the `api` scope option to the Settings token UI. No data stores, schemas, or existing endpoints change.

## Open Questions

1. A browser **device-login** flow (reusing OIDC + impersonation-token machinery to remove the copy-paste step) is a candidate future enhancement, out of scope for v1.

## References

- [ADR-0011: Human Web UI and Agent Client Access](/adr/0011-human-web-ui-and-agent-client-access.md)
- [ADR-0012: Job and Deployment Management System](/adr/0012-job-deployment-management-system.md)
- [ADR-0013: actions.yaml Automation System](/adr/0013-actions-yaml-automation-system.md)
- [DD-0021: Themelio CLI — Implementation](/dd/0021-themelio-cli.md)
- Full command specification: `plans/themelio-cli/SPEC.md`
- [Hugging Face `hf` CLI](https://huggingface.co/docs/huggingface_hub/guides/cli) — prior art
- [git credential helper protocol](https://git-scm.com/docs/gitcredentials)

<details class="page-source"><summary>Page source</summary>

<a href="/source/docs/content/adr/0021-themelio-cli.md.txt">Source snapshot</a> · Revision `f928834c8`.

</details>
