Metadata-Version: 2.3
Name: pinexq-cli
Version: 0.3.12
Summary: pinexq command-line interface
Author: Sebastian Höfer, Sebastian Boerakker
Author-email: Sebastian Höfer <hoefer@data-cybernetics.com>, Sebastian Boerakker <boerakker@data-cybernetics.com>
License: MIT
Requires-Dist: typer>=0.12.5
Requires-Dist: copier>=9.10.2
Requires-Dist: docker>=7.1.0
Requires-Dist: dotenv>=0.9.9
Requires-Dist: pinexq-client>=1.10.0
Requires-Dist: pydantic>=2.12.0
Requires-Dist: rich>=14.2.0
Requires-Dist: tomli>=2.3.0
Requires-Dist: importlib>=1.0.4
Maintainer: Sebastian Höfer, Sebastian Boerakker
Maintainer-email: Sebastian Höfer <hoefer@data-cybernetics.com>, Sebastian Boerakker <boerakker@data-cybernetics.com>
Requires-Python: >=3.13
Description-Content-Type: text/markdown

# pinexq CLI

A minimal command-line interface for creating and running pinexq workers.

Install (editable):

- Using uv or pip, from the project root:
  - `uv pip install -e .` or `pip install -e .`

Usage:

- Global options must precede the subcommand.
- Authentication: Provide `--api-key` or set the `PINEXQ_API_KEY` environment variable. The CLI prefers the flag over the env var.

## Reading Resources (`list` and `show`)

The CLI can read every JMA Resource in your Context. Each Resource is a noun with
two verbs — `list` (many) and `show <ref>` (one):

| Noun | Alias | Resource |
| --- | --- | --- |
| `pinexq job` | | Jobs |
| `pinexq workdata` | `wd` | WorkData |
| `pinexq processing-step` | `ps` | ProcessingSteps |
| `pinexq template` | | Templates |
| `pinexq trigger` | | Triggers |
| `pinexq folder` | | Folders |

These commands run from any directory and do not need a Project.

### Connection

Every read command resolves its endpoint and API key in this order:

1. the `--endpoint` / `--api-key` flags,
2. the `PINEXQ_ENDPOINT` / `PINEXQ_API_KEY` environment variables,
3. for the endpoint only, `pinexq_endpoint` in a `pinexq.toml` in the working
   directory (there is no file fallback for the API key).

### Output

`-o, --output` selects `table` (default, human-readable), `json`, or `csv`.
`--wide` adds the extra columns to the table; `-q, --quiet` prints only ids, one
per line, for piping. JSON and CSV always carry the full column set and go to
plain stdout so they can be redirected safely.

### Pagination and sorting

`--limit` (page size, max 1000) and `--offset` page the result; `--all` fetches
every page and overrides both. `--sort FIELD` picks the sort field (the choices
differ per noun) and `--desc` / `--asc` the direction; each noun has a natural
default (Jobs, WorkData, Templates, and Triggers newest-first; ProcessingSteps by
title and Folders by path, ascending).

### Filters

Filter flags are grouped under a **Filters** panel in each command's `--help`.
The families a noun exposes follow its JMA query: tag filters (`--tag`,
`--tag-or`, `--tql`), a folder scope (`--folder`, `--recursive`), and date bounds
(`--created-after`, `--created-before`) where the Resource supports them, plus
noun-specific flags — for example `--state` for Jobs, `--kind` and `--media-type`
for WorkData, `--function` / `--version` / `--deployment-state` for
ProcessingSteps, `--enabled` / `--disabled` for Triggers, and `--path-prefix` for
Folders. Run `pinexq <noun> list --help` for the full set.

### Reference forms for `show`

`show` accepts a Resource's UUID or its full URL for every noun. Two nouns
accept a friendlier reference as well:

- `pinexq ps show <name>` or `pinexq ps show <name>@<version>` resolves a
  ProcessingStep by Function name (latest version, or the pinned one).
- `pinexq folder show </path>` resolves a Folder by its path; `folder show` also
  lists the Folder's direct child folders.

### One example per noun

```bash
pinexq job list --state completed --sort created_at --limit 20
pinexq wd list --kind clientupload --media-type text/csv -o json
pinexq ps show resize@1.2.0
pinexq template list --tag nightly --wide
pinexq trigger list --enabled
pinexq folder show /projects/foo
```

## Deploying on Docker Desktop (containerd image store)

Fresh Docker Desktop installs since version 4.34.0 default to the **containerd
image store**. This changes what a local image looks like, and both differences
are expected and supported by `pinexq deploy`:

- An image ID is the manifest (or index) digest rather than the config digest.
- A `docker build` produces an OCI image index that also carries provenance and
  SBOM attestation manifests. This is normal; the deploy pushes it and registers
  the digest the registry reports.

Check which store is active:

```bash
docker info --format '{{ .DriverStatus }}'   # containerd store shows "io.containerd..."
```

Note that `docker system prune` **without `-a`** does not remove tagged images —
so if a bad deploy is fixed by "prune and retry", it is the retry's fresh push
that fixes it, not the prune.

## Troubleshooting: `ImagePullBackOff` right after a deploy

If a function pod fails to pull the image you just deployed, work through this
checklist. The full analysis is in
[`docs/research/2026-08-28-imagepullbackoff-after-successful-push.md`](docs/research/2026-08-28-imagepullbackoff-after-successful-push.md).

1. **Re-run the deploy with `--verbose`.** The push stream is printed chunk by
   chunk, so a swallowed registry error (`denied`, `unauthorized`, `blob upload
   unknown`) becomes visible. Since the fix in this repo, a failed push aborts the
   deploy instead of registering an unpullable digest.

2. **Read the pod events and map the error text to a cause:**

   ```bash
   kubectl describe pod <pod>
   ```

   - `failed to resolve reference ... not found` → the registered digest is not a
     manifest the registry holds for this repository (the classic bug).
   - `failed to authorize ... 401 Unauthorized` → registry token / pull-secret
     problem, not a missing image.
   - `could not fetch content descriptor ... not found` / `httpReadSeeker: failed
     open` → the manifest exists but a referenced blob is missing.
   - A `localhost:6443` / mirror address in the message is the k3s Spegel mirror
     being tried first; the upstream registry error follows it.

3. **Inspect the local image digests** (compare against what was registered):

   ```bash
   docker image inspect --format '{{json .RepoDigests}}' <name:tag>
   docker image inspect --format '{{ .Id }}' <name:tag>
   docker buildx imagetools inspect <registry>/<context>/<function>:<version>
   ```

4. **Ask the registry directly** whether the digest is present:

   ```bash
   # with a token from the registry realm
   curl -sI -H "Authorization: Bearer $TOKEN" \
     https://<registry>/v2/<context>/<function>/manifests/<digest>
   # or, if available:
   skopeo inspect --raw docker://<registry>/<context>/<function>@<digest>
   crane manifest <registry>/<context>/<function>@<digest>
   ```

5. **Check the registry pod** for restarts or `manifest unknown` log lines around
   the time of the deploy:

   ```bash
   kubectl -n <ns> logs deploy/registry --since=1h | grep -iE 'manifest|blob|unknown'
   kubectl -n <ns> get pod -l app.kubernetes.io/name=registry
   ```