Metadata-Version: 2.4
Name: hopper-cli
Version: 1.0.0
Summary: A git-like CLI for tutors on the hopper platform.
Author: hopper contributors
License-Expression: AGPL-3.0-or-later
Project-URL: Homepage, https://codeberg.org/hopper/cli
Project-URL: Repository, https://codeberg.org/hopper/cli
Project-URL: Issues, https://codeberg.org/hopper/cli/issues
Project-URL: Changelog, https://codeberg.org/hopper/cli/releases
Keywords: cli,grading,tutor,education,git
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Education
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Education
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# hopper

[![License: AGPL-3.0-or-later](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue.svg)](LICENSE)
[![PyPI](https://img.shields.io/pypi/v/hopper-cli)](https://pypi.org/project/hopper-cli/)
[![Python](https://img.shields.io/pypi/pyversions/hopper-cli)](https://pypi.org/project/hopper-cli/)
[![Forgejo](https://img.shields.io/badge/forge-codeberg.org-orange)](https://codeberg.org/hopper/cli)

A git-like CLI for tutors on the [hopper](https://codeberg.org/hopper/cli)
platform. It wraps `git` and the hopper orchestration services to safely
pull, grade, and push all of a tutor's assigned student repositories.

## Installation

Install via **pipx** or **pip**, or grab a prebuilt binary from the
[Forgejo releases page](https://codeberg.org/hopper/cli/releases). Do **not**
install via `go install` or build from source unless you are developing
hopper — see [Development](#development) for why.

**pipx** (recommended, installs an isolated executable):

```
pipx install hopper-cli
```

or with **pip**:

```
pip install hopper-cli
```

To upgrade later:

```
pipx upgrade hopper-cli    # or: pip install --upgrade hopper-cli
```

### Prebuilt binaries

Prebuilt binaries for recent releases are available on the
[Forgejo releases page](https://codeberg.org/hopper/cli/releases).

## Requirements

- [git](https://git-scm.com) — hopper drives `git` under the hood
- Python 3.8+ (only for the pip/pipx installation)
- An account as tutor/staff/admin on a hopper instance

## 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. |

Run `hopper <command> --help` for the full flag reference of each command.

### `auth`

```
hopper auth            # interactive setup (preset picker or custom URLs)
hopper auth --custom   # skip the preset picker and enter custom URLs
hopper auth list       # list configured profiles
hopper auth status     # show the current authentication state
hopper auth remove alu # remove a profile
```

Authorization happens in your browser: hopper opens the instance's authorize
page and receives the new API key via a local callback. If that fails, it
falls back to asking you to paste a key manually.

### `init`

```
hopper init 2025WS-EidP    # uses the only/default configured profile
hopper init alu/2025WS-EidP
hopper init alu/42         # numeric course ID also works
hopper init --force alu/2025WS-EidP   # overwrite an existing .hopper.yaml
```

### `pull`

```
hopper pull                 # sync all assigned student repos
hopper pull --student alice # pull a single student by username
hopper pull --only a,b,c    # pull only the listed usernames
hopper pull --prune         # remove local repos of students no longer assigned
hopper pull -j 8            # 8 concurrent git operations (default 4)
```

- `--prune` removes local repos of students no longer assigned to you, after
  showing a preview and asking for confirmation; `--no-prune` skips pruning.
- Per-repo failures are reported and skipped — one bad repo does not abort
  the whole run.

### `push` / `commit`

```
hopper push                     # commit */README.md changes and push (default msg: "Grade exercises")
hopper push "Grade exercise 3"  # custom commit message
hopper push --all               # stage all changes, not just */README.md
hopper push --allow-invalid     # push even when the README point schema is invalid
hopper commit "WIP grading"     # commit without pushing (recovery path)
```

Before pushing, hopper validates that each modified README's first line
contains exactly one point schema like `(12/15)`. Invalid pushes are blocked
unless `--allow-invalid` is given. A conflicting rebase surfaces the conflict
and halts that repo instead of destroying local work — hopper never runs
`git reset --hard`.

### `status` / `students` / `info`

```
hopper status    # table: student, branch, ahead/behind, dirty/clean
hopper students  # table: username, name, matrikel
hopper info      # course name/uid/id, profile, your user and role, exercises
```

## Version gate

On every command that touches the server (everything except `auth`,
`version`, and `help`), hopper asks the active profile's orchestration server
for the minimum supported CLI version:

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

## License

Licensed under [AGPL-3.0-or-later](LICENSE).

## Development

Requires [Go](https://go.dev) (see `go.mod` for the minimum version) and
[Task](https://taskfile.dev).

> ⚠️ **Note:** Building from source without injecting the version via
> ldflags produces a binary with a wrong/fallback version string
> (`v0.1.0` or `dev`). This breaks `hopper version`, the CLI's self-upgrade
> check, and the server-side minimum-version gate. End users should install
> via **pipx**/**pip** or the prebuilt release binaries instead — see
> [Installation](#installation). If you do build from source, always inject
> the version as shown below.

A plain `go install` works for quick local experiments only:

```
go install codeberg.org/hopper/cli/cmd/hopper@latest
```

Or clone the repository and build with Task (`task build` below produces
`bin/hopper`). Useful tasks:

```
task build       # build the CLI binary to bin/hopper
task test        # run unit tests
task lint        # run golangci-lint
task fmt         # format Go code and imports
task default     # fmt, lint, vet, test, build
task ci          # strict CI gate: fmt, lint, vet, test, build, clean tree
task hooks:install  # point git at the versioned githooks directory
```

The version is injected at build time via ldflags:

```
go build -ldflags "-X codeberg.org/hopper/cli/internal/cmd.Version=vX.Y.Z" -o hopper ./cmd/hopper/
```
