Metadata-Version: 2.5
Name: opencosmo-portal
Version: 1.1.0
Summary: CLI and local MCP server for the OpenCosmo platform
Requires-Python: >=3.11
Requires-Dist: click>=8.1.0
Requires-Dist: filelock>=3
Requires-Dist: httpx>=0.28.1
Requires-Dist: joserfc>=1.6.1
Requires-Dist: jsonschema>=4.26.0
Requires-Dist: mcp<3,>=2
Requires-Dist: packaging>=25.0
Requires-Dist: pydantic>=2.0
Description-Content-Type: text/markdown

# OpenCosmo Portal CLI (`ocp`)

Command-line interface and local MCP server for the [OpenCosmo](../README.md) platform. Query cosmological simulation datasets at DOE Leadership Computing Facilities from your terminal.

## Installation

```bash
pip install opencosmo-portal
```

Or for development:

```bash
cd cli
uv sync
```

## Quick Start

The CLI ships with these profiles:

| Profile | API URL | Notes |
| ------- | ------- | ----- |
| `production` | `https://cosmoexplorer.alcf.anl.gov` | Default |
| `staging` | `https://opencosmo-test.cels.anl.gov` | Internal network only |
| `dev` | `http://localhost:8000` | Backend running locally without Docker |

Shipped profiles cannot be removed, but their URLs can be overwritten with
`ocp config add-profile`. When the CLI is upgraded, known former official URLs
automatically migrate to the latest shipped URL; user-overridden URLs remain
unchanged. Custom profiles can be added and removed normally.

To use production:

```bash
# Authenticate (Globus OAuth device code flow; --browser for redirect flow)
ocp auth login

# Browse available tasks
ocp task list
ocp task info <slug>

# Submit a task and monitor the run
ocp task run <slug>
ocp run status <run-id> --watch
ocp run results <run-id>
```

For development against a local backend, either pass `--profile dev` to each
command or make it the current profile with `ocp config set-profile dev`.

### Update Notifications

After an interactive command, the CLI checks PyPI at most once every 24 hours
and prints a package-neutral notice to stderr when a newer stable release is
available. The complete network check has a one-second wall-clock deadline.
Failures are silent, non-interactive and MCP sessions skip the check, and no
update is installed automatically. Set
`OPENCOSMO_DISABLE_UPDATE_CHECK=1` to disable the check. Cached update state is
stored in `~/.opencosmo/update-check.json`.

## Commands

### `ocp auth` — Authentication

| Command             | Description                                                        |
| ------------------- | ------------------------------------------------------------------ |
| `ocp auth login`    | Authenticate via Globus OAuth device code flow (default); works in SSH/headless sessions. `--browser` opens a local browser redirect flow instead (`--port` sets the callback port, default 8080) |
| `ocp auth logout`   | Clear stored tokens                                                |
| `ocp auth status`   | Show token status and expiry; refreshes an expired token unless `--no-refresh` |
| `ocp auth consent`  | Grant authorization for tasks that need it (e.g. to run computations on your behalf) |

Login requests the profile's API origin as the REST OAuth resource with
`api:access offline_access`; refresh requests stay bound to that same resource.
These fields are additive, so the CLI also accepts an older server response
that ignores them and does not echo a resource. All stored-token writes,
rotation, and deletion are serialized per profile and committed atomically.

### `ocp config` — Profile Management

| Command                                | Description              |
| -------------------------------------- | ------------------------ |
| `ocp config list`                      | List all profiles        |
| `ocp config add-profile <name> <url>`  | Add or update a profile  |
| `ocp config set-profile <name>`        | Set default profile      |
| `ocp config remove-profile <name>`     | Delete a custom profile  |
| `ocp config show [name]`               | Show profile details     |

### `ocp task` — Tasks

| Command                  | Description                                |
| ------------------------ | ------------------------------------------ |
| `ocp task list`          | List available tasks                       |
| `ocp task info <slug>`   | Show task details and input parameters     |
| `ocp task run <slug>`    | Submit a task (interactive or `--input`)   |

Interactive task entry keeps the task's nested JSON object intact while
presenting leaf fields in a numbered table. The `Constraints` column shows the
currently active numeric range or allowed values. Standard Draft 7 conditional
rules are recomputed after every edit. If a dependency change invalidates a
value, `ocp` first uses one valid active conditional default; otherwise it
clamps inclusive numeric bounds (or steps integers across exclusive bounds)
and repairs invalid enums with a valid ordinary default or the first allowed
value. Each automatic change is printed immediately with its field, old and
new values, and active constraint. Relaxing a rule preserves the current value
rather than restoring an earlier default.

Interactive entry also honors leaf-level `depends_on` visibility annotations
using the same parent-relative dot paths as the frontend. Hidden fields are
omitted from the parameter table and conditional repair, but their stored
values are preserved; revealing a field reactivates its constraints and repairs
an invalid value normally. Final submission still validates the complete value
object against the original schema. Explicit `--input` and `--file` payloads
are validated as supplied and do not receive visibility projection.

Exclusive floating-point bounds are not repaired with an invented epsilon;
the field remains invalid for manual correction unless a valid default exists.
Conflicting conditional defaults are reported as schema warnings, and
contradictory constraints are task-schema errors that prevent local
submission. Before leaving the editor, the complete nested input is validated
against the original Draft 7 schema; the backend repeats authoritative
validation on submission.

Non-interactive submissions (`--input`/`--file`) are validated against the
same original schema before any `--dry-run` output or submission, with no
automatic repair — scripted input fails with per-field messages instead of
being silently adjusted. `ocp task info` lists parameters by their nested
dotted paths (e.g. `filters.sod_halo_mass.minval`), matching the nested JSON
that `--input`/`--file` expect.

### `ocp run` — Runs

| Command                        | Description          |
| ------------------------------ | -------------------- |
| `ocp run list`                 | List your runs       |
| `ocp run status <id> [--watch]`| Check run status     |
| `ocp run logs <id>`            | View run logs        |
| `ocp run results <id>`         | Download results     |
| `ocp run cancel <id>`          | Cancel a run         |
| `ocp run archive <id>`         | Archive a run        |

`ocp run status` renders the server's shared diagnostics projection: source or
portal guidance, failure code and phase, warnings, portal context, bounded
traceback, diagnostic files, evidence state, and correlation identifiers. JSON
output returns the same detail response used by REST and MCP. When a current CLI
talks to an older server, it falls back to the summary response and reports that
structured diagnostics are unavailable.

### `ocp admin tasks install` — Adapter Installation

Install task adapter definitions into the backend database. Discovery is
explicit and non-recursive: directory mode uses the directory argument directly,
while archive modes require `--subdir`; only direct child `*.json` files in that
selected directory are loaded.

```bash
ocp admin tasks install dir ./artifact/build/tasks
ocp admin tasks install dir ./artifact/build/tasks --dry-run
ocp admin tasks install dir ./artifact/build/tasks --strict

OPENCOSMO_API_URL=https://portal.example.org \
OPENCOSMO_API_KEY="$OPENCOSMO_API_KEY" \
ocp admin tasks install dir build/tasks

ocp admin tasks install url "https://.../artifacts/download?file_type=archive" \
  --subdir build/tasks \
  --header "PRIVATE-TOKEN:${GITLAB_TOKEN}"

ocp admin tasks install gitlab \
  --host https://git.cels.anl.gov \
  --project hacc/hacc-compute-portal \
  --ref master \
  --job adapter-build \
  --subdir build/tasks \
  --gitlab-token-env GITLAB_TOKEN
```

Supported archives are `.zip`, `.tar`, `.tar.gz`, and `.tgz`. Protected URL
tokens are accepted through explicit token/header options or environment
variables and are not persisted by the CLI or backend.

For CI, provide an admin API key through `OPENCOSMO_API_KEY` and set
`OPENCOSMO_API_URL` to the target portal. The CLI automatically exchanges the
API key for a JWT, uses that JWT for backend requests, and does not persist the
API key or env-derived JWT. `OPENCOSMO_API_KEY` takes precedence over stored
profile tokens.

For non-dry-run installs, the CLI first sends a dry-run request for every
discovered task. If any prevalidation request fails, no real install requests
are sent. Once real installation starts, requests are committed one task at a
time.

Each response may include structured warnings when a task's `category` does not
exactly match a registered category slug. Without `--strict`, warnings are
displayed and installation continues. `--strict` is available for `dir`, `url`,
and `gitlab`; it gathers all dry-run responses first and exits 1 without real
task requests if any warning is present. If a warning first appears during the
real install requests (for example, the category registry changed after
prevalidation), the command still exits 1, but the tasks have already been
installed; installs are idempotent, so re-running after fixing the registry is
safe. JSON output remains valid on either nonzero exit.

### `ocp admin tasks categories sync` — Category Registry

Category files are complete desired-state JSON objects containing a
`categories` array. Sync always sends a server-side dry run first, displays the
create/update/unchanged/delete actions, and confirms before the real `PUT`
unless `--yes` is supplied. Confirmation defaults to no when deletions exist.

```bash
ocp admin tasks categories sync build/categories.json --dry-run
ocp admin tasks categories sync build/categories.json --yes
ocp admin tasks install dir build/tasks --strict
```

Run category sync before strict adapter installation. `slug` is the stable
identity used by each task's existing `category` field. Names and headings can
change without task reinstall; a slug change temporarily renders the old task
value as a fallback group until adapters are reinstalled.

### `ocp admin datasets sync` — Dataset Registry

Dataset files are complete desired-state JSON objects containing `vocabulary`
and `datasets` arrays (the exporter's `build/datasets.json`). Sync always
sends a server-side dry run first, displays the create/update/unchanged/delete
actions plus vocabulary changes and loose-reference warnings, and confirms
before the real `PUT` unless `--yes` is supplied. Confirmation defaults to no
when deletions exist.

```bash
ocp admin datasets sync build/datasets.json --dry-run
ocp admin datasets sync build/datasets.json --yes
```

CI ordering is categories → datasets → tasks: run this command after
`ocp admin tasks categories sync` and before `ocp admin tasks install`. Task
install validates binding tags against the synced vocabulary, so binding-task
installs fail on a fresh instance until `datasets sync` has run. Deleting a
dataset that installed tasks still match is allowed (task references are
deliberately loose) but produces a warning listing the affected task slugs.

### `ocp admin docs deploy` — Documentation Deployment

Deploy a complete Markdown documentation tree into the backend database.
Production docs are auto-deployed from the external documentation repository
(internal: `git.cels.anl.gov/hacc/hacc-compute-portal`). This command remains
the deployment interface for automation, recovery, and manual testing.

For `dir`, pass the docs root itself — the directory that directly contains the
root `index.md` (e.g. `content/documentation` inside a docs checkout or
extracted artifact), not the checkout or artifact root.

```bash
ocp admin docs deploy dir /path/to/docs-checkout/content/documentation --dry-run
ocp admin docs deploy dir /path/to/docs-checkout/content/documentation

ocp admin docs deploy url "https://.../artifacts/download?file_type=archive" \
  --subdir content/documentation \
  --header "PRIVATE-TOKEN:${GITLAB_TOKEN}"

ocp admin docs deploy gitlab \
  --host https://git.cels.anl.gov \
  --project hacc/hacc-compute-portal \
  --ref master \
  --job docs-build \
  --subdir content/documentation \
  --gitlab-token-env GITLAB_TOKEN
```

Only Markdown files at the selected docs root and one subdirectory level are
loaded. Non-Markdown files are ignored; deeper Markdown files fail validation.
Real deploys perform one backend dry-run prevalidation call, print the planned
actions, and ask for confirmation before the real deploy call (the prompt
defaults to no whenever pages would be removed). Pass `--yes`/`-y` to skip the
confirmation in CI pipelines. The backend requires root `index.md` and deletes
DB docs omitted from the submitted tree.

### `ocp admin service-accounts` — Service Accounts

Manage API-backed service accounts. All commands require admin permissions.

```bash
ocp --profile production admin service-accounts list
ocp --profile production admin service-accounts create ci-task-manager \
  --profile task-manager \
  --description "CI task adapter manager"
ocp --profile production admin service-accounts update ci-task-manager --profile task-manager
ocp --profile production admin service-accounts rotate-key ci-task-manager
ocp --profile production admin service-accounts revoke-key ci-task-manager KEY_ID --yes
ocp --profile production admin service-accounts disable ci-task-manager --yes
```

Create and rotate print the plaintext API key exactly once. Enabled service
accounts are automatically valid via `urn:opencosmo:service-account`; no
service-account entry is needed in `AUTH_VALID_GROUPS`. For `task-manager`, add
the printed profile-specific URN to backend admin authorization:

```bash
AUTH_ADMINS="urn:globus:groups:id:<admin-group>;urn:opencosmo:service-account:task-manager"
```

OpenCosmo JWTs store group URNs without an issuer field. Group provenance is
enforced before JWT minting through provider-owned namespace validation.
Identity providers declare the group URN prefixes they are allowed to mint;
Globus declares `urn:globus:groups:id:*`. Service accounts get the exact common
valid group `urn:opencosmo:service-account` at runtime and profile role groups
must use the `urn:opencosmo:service-account:` prefix. Callback, session,
auth-code, refresh-token, and device-code paths enforce validated effective
group authorization before accepting or minting tokens.

### `ocp whoami` — User

Show current user info.

### `ocp mcp` — MCP Server

Start a local stdio MCP server for AI assistants like Claude Desktop. This
stdio server supports both legacy and modern MCP handshake eras and calls the
portal REST API with the profile's REST-audience CLI token. It is distinct from
the hosted, modern-only remote endpoint at `<portal-origin>/mcp`, whose OAuth
audience is exactly that URL. If a remote MCP client was authorized before the
resource-bound OAuth migration, remove that client's existing token and
authorize it again; `ocp auth logout` only clears the CLI profile token.

```bash
ocp mcp start
```

Configure in Claude Desktop (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "opencosmo": {
      "command": "ocp",
      "args": ["mcp", "start"]
    }
  }
}
```

## Global Options

| Option             | Description                                |
| ------------------ | ------------------------------------------ |
| `--profile, -p`    | Use a specific profile                     |
| `--format, -f`     | Output format: `table` (default) or `json` |
| `--version`        | Show version                               |
| `--help`           | Show help for any command                  |

## Releasing

The CLI is published to PyPI as `opencosmo-portal` via GitHub Actions (trusted publisher / OIDC).

### Automated (recommended)

Use the **Release CLI** workflow in GitHub Actions:

1. Go to **Actions → Release CLI → Run workflow**
2. Select the bump type (`patch`, `minor`, or `major`)
3. The workflow bumps `cli/pyproject.toml`, commits, tags, and pushes — which
   triggers the publish workflow automatically

### Manual

1. Bump `version` in `cli/pyproject.toml` (or run `cd cli && uv version --bump patch`)
2. Commit: `git commit -am "release: CLI v0.2.0"`
3. Tag: `git tag cli-v0.2.0`
4. Push both: `git push origin main cli-v0.2.0`

The [`publish-cli.yml`](../.github/workflows/publish-cli.yml) workflow will:
- Run the full test suite
- Verify the tag version matches `pyproject.toml`
- Build and publish to PyPI

### First-time setup

Register a [trusted publisher](https://docs.pypi.org/trusted-publishers/) on pypi.org:

| Field        | Value                  |
| ------------ | ---------------------- |
| Package name | `opencosmo-portal`     |
| Owner        | `ArgonneCPAC`          |
| Repository   | `OpenCosmoPortal`      |
| Workflow     | `publish-cli.yml`      |
| Environment  | `pypi`                 |

Then create a `pypi` environment in GitHub repo settings → Environments.

## Configuration

Config and tokens are stored in `~/.opencosmo/`:

```
~/.opencosmo/
├── config.json          # Profiles (name → API URL)
└── tokens/
    └── <profile>.json   # OAuth tokens per profile
```
