Metadata-Version: 2.4
Name: sleepyrat-cli
Version: 0.1.1
Summary: Secure command-line client for the SleepyRat V1 API
Author: SleepyRat
License-Expression: LicenseRef-Proprietary
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: click<9,>=8.1
Requires-Dist: httpx<1,>=0.27
Requires-Dist: keyring<26,>=25
Requires-Dist: platformdirs<5,>=4
Provides-Extra: test
Requires-Dist: pytest<9,>=8; extra == "test"

# SleepyRat CLI

This directory contains the independently installable command-line client for
the scoped SleepyRat V1 HTTP API. It never imports backend code, reads the
database, accesses server files, accepts passwords, or confirms an action
intent.

## Install

The distribution name is `sleepyrat-cli`; the installed command is
`sleepyrat`. The supported end-user install is:

```bash
pipx install sleepyrat-cli
sleepyrat --help
```

For repository development, install from this directory:

```bash
pipx install .
sleepyrat --help
```

For source development:

```bash
python -m pip install -e '.[test]'
pytest
```

Python 3.10 or newer is required.

Releases are built from the exact deployed, CI-green revision and use PyPI
Trusted Publishing. Do not upload distributions from a developer workstation
or store a long-lived PyPI token in the repository.

## Origins and profiles

Production is the default:

```bash
sleepyrat products list
```

It uses `https://sleepyrat.ai` and production refuses an HTTP origin. Local and
staging are explicit:

```bash
sleepyrat --profile local products list
sleepyrat --profile staging products list
sleepyrat --profile local \
  --api-origin http://127.0.0.1:5200 \
  --web-origin http://localhost:3200 \
  products list
```

Each profile has a separate API origin and trusted first-party web origin:

| Profile | API origin | Trusted web origin |
| --- | --- | --- |
| `production` | `https://sleepyrat.ai` | `https://sleepyrat.ai` |
| `staging` | `https://staging.sleepyrat.ai` | `https://staging.sleepyrat.ai` |
| `local` | `http://127.0.0.1:5100` | `http://localhost:3001` |

The local defaults match `scripts/dev.sh`, so `sleepyrat --profile local ...`
works with the canonical hot-reload frontend and backend without an origin
override. `--api-origin` changes only the HTTP API target; `--web-origin`
changes only the trusted browser target. `SLEEPYRAT_PROFILE`,
`SLEEPYRAT_API_ORIGIN` and `SLEEPYRAT_WEB_ORIGIN` provide the same non-secret
configuration.

Both values must be origins containing only a scheme, hostname and optional
port; paths, user information, query strings and fragments are rejected.
Non-local API and web origins require HTTPS. Browser verification and
action-confirmation URLs are accepted only
when their origin exactly matches the configured trusted web origin, including
scheme and effective port. An API response cannot nominate an arbitrary browser
host, and changing the API origin does not implicitly trust it for browser
navigation.

## Authentication and keychain behavior

```bash
sleepyrat auth login
sleepyrat auth status
sleepyrat auth logout
```

Login creates a device request, opens its first-party URL, and polls at the
server-provided interval. The one-time agent token is stored only in the
operating-system keychain and is never printed. If no secure keychain backend
is available, login fails; there is no plaintext fallback. Tests use an
in-memory credential store.

`auth logout` removes only the local keychain entry. To revoke the selected
credential on the server:

```bash
sleepyrat credentials list
sleepyrat credentials revoke
```

The CLI can introspect and revoke only its current token. Full credential
administration remains a first-party browser/Profile function. Non-secret
profile metadata such as origin, scopes and expiry is stored in the platform
configuration directory with user-only file permissions.

## Commands

The required command groups are implemented:

```text
sleepyrat auth login|status|logout
sleepyrat credentials list|revoke
sleepyrat products list
sleepyrat accounts list|show
sleepyrat projects list|show|create|update|delete
sleepyrat folders list|create|update|delete
sleepyrat submissions upload
sleepyrat jobs list|show|status|delete|delete-prepare
sleepyrat results download
sleepyrat reports create|show|download|compare|comparison-show|comparison-download
sleepyrat billing status|prepaid|postpaid|spend-cap|portal|ai-credits
sleepyrat permissions account-leave|membership-role-change|membership-remove
sleepyrat security password-change|email-change
sleepyrat terms status|accept
sleepyrat intents show|confirm|cancel|wait|resume
```

`profile show` and `billing subscribe` expose additional profile and required
SleepyRat-subscription capabilities. The CLI exposes only routes implemented by
the checked-in V1 OpenAPI contract.

Financial inputs use server-owned catalog identifiers and integer minor-unit
payloads. For example:

```bash
sleepyrat billing prepaid --account-id 10 --pack-code week
sleepyrat billing prepaid --account-id 10 --custom-amount-minor 5000
sleepyrat billing postpaid request --account-id 10 --spend-cap-chf 250.00
sleepyrat billing postpaid start --account-id 10
sleepyrat billing spend-cap --account-id 10 --amount-chf 400.00
sleepyrat billing ai-credits --package-code ai_credits_25
```

CHF values are parsed as decimals and converted exactly to integer
`spend_cap_minor` values; binary floating-point arithmetic is not used.

Every leaf command accepts `--json`. JSON mode emits one stable JSON document to
stdout. Human output goes to stdout and diagnostics go to stderr. Raw agent or
device tokens and hosted payment-provider URLs are filtered from all output.

Exit codes are:

- `0`: success, including a sensitive action successfully prepared for a human;
- `2`: local or API validation error;
- `3`: authentication missing or expired;
- `4`: authorization denied;
- `5`: missing/conflicting resource or rejected action;
- `6`: network, rate-limit, server, keychain, invalid-response or polling failure.

## Sensitive actions

Financial, security, legal, destructive and permission-changing commands use
the durable action-intent API:

```text
POST /api/v1/action-intents
GET  /api/v1/action-intents/{intent_id}
DELETE /api/v1/action-intents/{intent_id}
```

The product-specific deletion-preparation routes return that same safe action
intent directly.

They print/open the first-party confirmation URL and may poll with `--wait`.
`intents confirm` only reloads the agent-safe intent and opens that same
first-party human page; it never submits a confirmation request. `intents
resume` resumes polling the same durable intent. The CLI has no code path to a
confirmation endpoint. Password and replacement email values are entered only
on the human confirmation page, never as command arguments. Hosted
Stripe/provider URLs remain browser-only.

Product deletion can also be prepared through the product-specific route with
`jobs delete-prepare`. It still returns an action intent and has exactly the
same human-confirmation requirement as `jobs delete`.

Command-to-action mapping includes:

| Command | Action type |
| --- | --- |
| `projects delete` | `project_delete` |
| `projects update --billing-account-id` | `project_payer_rebind` |
| `folders delete` | `folder_delete` |
| `jobs delete --product sleepyrat` | `sleepyrat_submission_delete` |
| `jobs delete --product neurodecoder` | `neurodecoder_job_delete` |
| `billing subscribe` | `sleepyrat_subscription_start` |
| `billing prepaid` | `neurodecoder_prepaid_purchase` |
| `billing postpaid request` | `neurodecoder_postpaid_request` |
| `billing postpaid start` | `neurodecoder_postpaid_start` |
| `billing spend-cap` | `spend_cap_change` |
| `billing portal` | `billing_portal_open` |
| `billing ai-credits` | `ai_credit_purchase` |
| `security password-change` | `password_change` |
| `security email-change` | `email_change` |
| `terms accept` | `terms_acceptance` |
| `permissions account-leave` | `billing_account_leave` |
| `permissions membership-role-change` | `membership_role_change` |
| `permissions membership-remove` | `membership_remove` |

Permission commands target numeric account and user IDs only; they do not
accept usernames or email addresses:

```bash
sleepyrat permissions account-leave --account-id 10
sleepyrat permissions membership-role-change --account-id 10 --user-id 42 --role lab_admin
sleepyrat permissions membership-remove --account-id 10 --user-id 42
```

## Idempotency and retry behavior

The CLI generates one `Idempotency-Key` for each logical mutating request and
reuses it for every automatic retry. It retries only safe reads/downloads or
mutations carrying that key, and only for network errors, 408, 429, 502, 503 or
504. `Retry-After` is respected. Authentication, validation, authorization and
conflict responses are not retried.

Uploads reopen the input file for every retry, so an exhausted multipart stream
is never reused. The optional hidden `--idempotency-key` on mutating commands is
available for controlled integration/recovery testing; callers normally let the
CLI generate it.

## Product boundary

NeuroDecoder processing uses durable job endpoints. SleepyRat uses submission
resources and requires `--project-id` and `--folder-id`. The `jobs` command is a
CLI grouping only; it does not relabel a legacy SleepyRat filesystem submission
as a NeuroDecoder-style durable billing job.

## V1 API path mapping

The CLI centralizes the implemented, OpenAPI-documented paths in
`sleepyrat_cli/endpoints.py`:

| Capability | Assumed path |
| --- | --- |
| Current credential | `/api/v1/agent-credentials/current` |
| Billing status | `/api/v1/accounts/{account_id}/billing` |
| Terms status | `/api/v1/terms/{product_code}` |
| Projects | `/api/v1/projects[/project_id]` |
| Folders | `/api/v1/projects/{project_id}/folders[/folder_id]` |
| Upload | `/api/v1/products/{product_code}/projects/{project_id}/folders/{folder_id}/submissions` |
| NeuroDecoder jobs/results | `/api/v1/products/neurodecoder/jobs/{job_id}/...` |
| SleepyRat submissions/results | `/api/v1/products/sleepyrat/projects/{project_id}/folders/{folder_id}/submissions/{submission_id}/...` |
| SleepyRat reports | SleepyRat submission `/reports/...` and folder `/reports/comparisons/...` paths |

Reports are intentionally SleepyRat-only. `reports create` and `reports compare`
accept a bounded `--threshold` plus repeatable
`--analysis-setting NAME=SECONDS` options; status commands can `--wait`, and
download commands retrieve the ready Markdown attachment. NeuroDecoder uses its
own result schema and has no SleepyRat report adapter.

Financial and permission action payloads match the frozen WP4 adapter schemas:
prepaid uses `pack_code` or `custom_amount_minor`; cap operations use integer
`spend_cap_minor`; Postpaid activation accepts only `billing_account_id`; AI
credits use `package_code`; and membership actions use account/user IDs plus the
new role where applicable. The CLI sends only non-secret inputs. Server
normalization and confirmation-time revalidation remain authoritative.

Canonical API errors are surfaced with the documented exit code; the CLI never
falls back to legacy JWT endpoints.
