Metadata-Version: 2.4
Name: hopper-cli
Version: 0.1.8
Summary: A git-like CLI for tutors on the hopper platform.
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# hopper

A git-like CLI for tutors on the hopper platform. It wraps `git` and the
hopper orchestration services to safely pull, grade, and push all of a
tutor's assigned student repositories.

## Install

```
pipx install hopper-cli
```

To update later:

```
pipx upgrade hopper-cli
```

## Getting started

```
hopper auth            # set up an instance profile + API key
hopper init <course>   # create a workspace directory bound to a course
cd <course-uid>
hopper pull            # clone/update all your assigned student repos
```

## Concepts

### Profiles

A **profile** is a set of service URLs plus your API key — typically one per
university. They live in the global config at `os.UserConfigDir()/hopper/config.yaml`
(`~/.config/hopper/` on Linux, `~/Library/Application Support/hopper/` on
macOS, `%AppData%\hopper\` on Windows).

`hopper auth` interactively creates a profile, offering known presets (e.g.
`alu` for Albert-Ludwigs-Universität Freiburg) or custom URLs. You can hold
several profiles and pick which one a workspace uses at `init` time.

### Workspaces

`hopper init [profile/]<course>` creates a `./<course-uid>/` directory with a
`.hopper.yaml` inside. `<course>` may be a UID (`2025WS-EidP`) or numeric ID.
With multiple profiles, prefix the profile: `hopper init alu/2025WS-EidP`.
All commands run **inside** that directory and never write outside it.

## Commands

| Command | Description |
|---|---|
| `hopper auth` | Interactively set up a profile (preset or custom). |
| `hopper auth list` / `status` / `remove <id>` | Manage profiles. |
| `hopper init [profile/]<course>` | Bind a new directory to a course. |
| `hopper pull` | Clone or `pull --rebase --autostash` every assigned student repo. |
| `hopper push [msg]` | Commit README changes, validate point schema, push. |
| `hopper commit <msg>` | Commit without pushing (recovery path). |
| `hopper status` | Per-student branch, ahead/behind, dirty state. |
| `hopper students` | List students assigned to you. |
| `hopper info` | Course info, your role, exercises. |
| `hopper version` | Print version. |

### `pull` flags

- `--prune` / `--no-prune` — remove local repos of students no longer assigned (default: prune, with preview + confirm).
- `--student <name>` — pull a single student.
- `--only a,b,c` — pull only the listed usernames.
- `--jobs N` / `-j` — concurrent git operations (default 4).

### Safety vs. the legacy CLI

- **Per-repo error isolation** — one failed clone/pull is reported and skipped, not fatal.
- **No shell** — git runs via `exec.Command` with argument slicing; no string interpolation.
- **No `git reset --hard`** — push rebases and surfaces conflicts instead of destroying local work.
- **No README mutation during pull** — the grader bot already injects build logs; pull is pure pull.
- **Point-schema validation blocks** invalid pushes (`--allow-invalid` to override).

## Minimum-version gate

On every command (except `auth`/`version`/`help`), hopper asks the active
profile's orchestration server for the minimum allowed CLI version
(`GET /api/cli/version`, returns `{ "minimum": "vX.Y.Z" }`):

- **running version < minimum** → the CLI refuses to run (upgrade via pip);
- **running version >= minimum** → proceeds;
- **server unreachable** → proceeds silently (can't check offline).

## Orchestration endpoints used

Existing (no server changes): `GET /api/users/me`, `GET /api/users/me/courses`,
`GET /api/courses/:id`, `GET /api/courses/:id/exercises`.

Required additions to orchestration:

- `GET /api/courses/:id/students/mine` — `RequireStaff`, scoped to
  `tutors.user_id = caller` (admins/owners get all). Returns students with
  `User.PreferredUsername` (the Forgejo repo name). *Used by `pull`.*
- `GET /api/cli/version` — public, returns `{ "minimum": "vX.Y.Z" }` from a
  Setting. *Used by the minimum-version gate.*

## Build (developers only)

```
go build -o hopper ./cmd/hopper/
```

The version is injected at build time:

```
go build -ldflags "-X codeberg.org/hopper/cli/internal/cmd.Version=v0.2.0" -o hopper ./cmd/hopper/
```
