Metadata-Version: 2.4
Name: jartify
Version: 0.2.4
Summary: TUI and CLI for searching JFrog Artifactory artifacts (version, filename, sha256)
Author-email: Jakob Holst <code@jakobholst.dk>
License: MIT
Project-URL: Homepage, https://gitlab.com/jakobholst/jartify
Project-URL: Repository, https://gitlab.com/jakobholst/jartify
Keywords: artifactory,jfrog,tui,textual,sha256,search
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development
Classifier: Topic :: System :: Archiving :: Packaging
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.28
Requires-Dist: textual>=0.50
Requires-Dist: pyyaml>=6.0
Requires-Dist: packaging>=23.0

# jartify

`jartify` is a small toolkit for querying a JFrog Artifactory instance
from a terminal. It exposes two entry points:

| Command         | Purpose                                                      |
|-----------------|--------------------------------------------------------------|
| `jartify`       | One-shot CLI: find a file, print version / filename / sha256 |
| `jartify-tui`   | Textual TUI for interactive searching                        |
| `jartify-setup` | Interactive first-run: writes exports to `.zshrc`/`.bashrc`  |
| `jartify-repos` | List visible repositories (filter by type / package type)    |
| `jartify-locate`| Offline SQLite index + glob / fuzzy / substring search       |
| `jartify-reset` | Delete the jartify-locate index (asks for confirmation)      |

Both share the same `~/.local/etc/jartify/config.yaml` and
`$ARTIFACTORY_TOKEN` env var (name is configurable).

## Install

```bash
pip install jartify
```

## Configure

### Quickest: `jartify-setup`

```bash
jartify-setup
```

Prompts for URL, username, token (hidden), and the repository filter,
then writes a managed export block to your `.zshrc` or `.bashrc`:

```sh
# >>> jartify setup >>>
export ARTIFACTORY_URL="..."
export ARTIFACTORY_USER="..."
export ARTIFACTORY_TOKEN="..."
export ARTIFACTORY_REPOSITORIES="all"
# <<< jartify setup <<<
```

`ARTIFACTORY_REPOSITORIES` accepts three shapes:

| Value | Meaning |
|---|---|
| `all` (any case) | No filter — search every visible repo |
| `pypi-local,pypi-releases` | Comma-separated list |
| `["pypi-local","pypi-releases"]` | JSON array (handy for CI env files) |

The legacy singular `ARTIFACTORY_REPOSITORY` is still read as a
fallback with the same semantics.

Re-running updates the block in place (no duplicates). The rc file is
chmod'd to `600` since it now contains a token. Reload with
`source ~/.zshrc` (or open a new terminal).

### Alternative: config file

Env vars take precedence, but `~/.local/etc/jartify/config.yaml` works
too:

```yaml
artifactory:
  url: https://artifactory.example.com
  repos: ""                      # optional comma-separated allowlist

authentication:
  token_env: ARTIFACTORY_TOKEN   # name of env var holding your token
  # username: your.name          # uncomment for basic auth instead of bearer
```

## CLI usage

```bash
# Find all requests wheels and print version / filename / sha256:
jartify 'requests-*.whl'

# Restrict to specific repos (-R is a short alias):
jartify 'myapp-*.tar.gz' -R pypi-local -R pypi-releases

# Machine-readable output:
jartify 'myapp-*.whl' --json
```

Output:

```
VERSION  FILENAME                               SHA256
-------  -------------------------------------  ----------------------------------------------------------------
2.31.0   requests-2.31.0-py3-none-any.whl       4c491f82b8a8b4...
2.32.3   requests-2.32.3-py3-none-any.whl       70761cfe03c773...
```

Exit codes: `0` on at least one hit, `1` on no hits, `2` on config / auth errors.

## `jartify-locate` (offline index)

For big Artifactory instances where you want fast, repeated searches
without re-hitting the server:

```bash
# One-time (or periodic) scan via AQL — stores sha256 for every file.
jartify-locate --init

# Same, but with a live progress bar (paginated AQL, 5000/page, per-page
# commits so Ctrl+C keeps everything already indexed):
jartify-locate --init --status=progress

# Or one AQL per repo, printing the repo name as it goes (commits per repo):
jartify-locate --init --status=step

# Then search locally:
jartify-locate                       # list everything (respects --repo / --limit)
jartify-locate '*.json'              # glob
jartify-locate requests              # substring (no wildcards)
jartify-locate --fuzzy foojsn        # subsequence match
jartify-locate -R pypi-local         # all files in one repo
jartify-locate --init -R pypi-local  # refresh just one repo
jartify-locate --init --all          # scan every repo, ignoring env/config
jartify-locate --db                  # show index path + row count + last init
```

The index is stored at `$XDG_DATA_HOME/jartify/locate.db`
(defaults to `~/.local/share/jartify/locate.db`). `--init` uses one
AQL request; some Artifactory instances restrict AQL to admins — if
you get a 403 on `--init`, that's why.

**Schema and append semantics.** The index has two tables:

- `artifacts (sha256, filename, size, created, modified)` — primary key
  `(sha256, filename)`. One row per unique file.
- `locations (sha256, filename, repo, path)` — primary key `(repo, path)`.
  One row per physical copy. The same file in two repos shows up twice
  here but once in `artifacts`.

`--init` only ever `INSERT OR REPLACE`s. It never deletes, so running
`--init -R repo-a` and then `--init -R repo-b` leaves you with both
repos indexed. Files whose checksum was removed/renamed server-side
remain in the local index until you run `jartify-reset`.

## TUI usage

```bash
jartify-tui
```

Keys:

- Type a pattern → <kbd>Enter</kbd> runs the search.
- <kbd>Ctrl+R</kbd> focuses the Repo field. Comma-separate names to search
  multiple repos; leave blank to search all. <kbd>Enter</kbd> in the Repo
  field re-runs the last search with the new filter.
- <kbd>Ctrl+F</kbd> jumps back to the Search field.
- <kbd>r</kbd> repeats the last search, <kbd>y</kbd> copies the highlighted
  row's sha256, <kbd>q</kbd> quits.

## Releasing

### One-time: register the PyPI trusted publisher

PyPI has no API for this — the form is behind your login + 2FA. The
helper script opens the right page and prints the values to type:

```bash
python scripts/register-pypi-publisher.py
```

It reads the project name from `pyproject.toml` and the GitLab
namespace/repo from `git remote get-url origin`, so there's nothing to
look up. In PyPI's "Add a new pending publisher" form, pick the GitLab
tab and paste the printed values.

### Each release

Push to `main` with a bumped `version` in `pyproject.toml`. GitLab CI
runs `python -m build`, mints a short-lived PyPI token via OIDC
trusted publishing, and uploads with `twine upload --skip-existing`.
Pushes that do not bump the version are no-ops.
