Add durable Paseo Hub CLI login (#3022)
* feat(cli): add durable Hub login Keep organization-scoped human credentials separate from daemon relationship authority. Connect now exchanges explicit or stored CLI authority for a one-time enrollment token, while logout removes human access without implicitly disconnecting automation. * fix(cli): require TLS for remote Hub credentials Durable Hub credentials are bearer secrets, so remote origins must not receive them over cleartext HTTP. Keep literal loopback HTTP available for local Hub development. * fix(cli): make Hub destinations explicit Resolve optional Hub origins through one shared precedence policy so active logins remain the default context and credentials stay origin-bound. Report the selected destination before human-facing work, keep structured output auditable, and preserve login authority until requested daemon disconnection succeeds.
This commit is contained in:
+10
-2
@@ -196,10 +196,14 @@ Use `PASEO_HOME` to run multiple isolated daemon instances.
|
||||
## Hub
|
||||
|
||||
```bash
|
||||
paseo hub connect <url> # Enroll this daemon with a Paseo Hub
|
||||
paseo hub login [url] # Approve and store organization-scoped CLI access
|
||||
paseo hub connect [url] # Enroll this daemon using CLI access
|
||||
paseo hub projects # List projects in the authenticated organization
|
||||
paseo hub status # Show the current Hub relationship
|
||||
paseo hub disconnect # End it
|
||||
paseo hub deploy [file] # Install and activate a Hub configuration
|
||||
paseo hub deploy --dry-run # Validate without installing or activating
|
||||
paseo hub logout # Remove the active stored CLI login
|
||||
```
|
||||
|
||||
`file` defaults exactly to `.paseo/hub.yml` relative to the current directory. Pass a file to use another path. The CLI does not search parent directories or alternate filenames.
|
||||
@@ -208,7 +212,11 @@ Pass `-p, --project <slug>` to select the project, or add optional top-level `pr
|
||||
|
||||
Prompt `include` blocks are read from `.paseo/partials/` under the current directory, even when you pass an explicit configuration file. The CLI sends only the files referenced by the main YAML. Nested include-looking text inside a partial is content and is not resolved recursively. Inline-only configurations omit the partial bundle.
|
||||
|
||||
Deployment requires an explicit Hub origin and organization API key. `--hub <origin>` overrides `PASEO_HUB_URL`; `--api-key <secret>` overrides `PASEO_HUB_API_KEY`. The key's organization supplies organization scope. Durable Hub login and credential persistence are not implemented.
|
||||
`login` opens the Hub approval page and stores a durable organization-scoped CLI credential under `PASEO_HOME`. The stored login is separate from the daemon relationship created by `connect`. Interactive logout checks the same-origin daemon relationship and asks whether to disconnect before deleting the login. Declining removes only the login. JSON and noninteractive logout never prompt or disconnect implicitly; `--disconnect-daemon` is the explicit automation path, and `--force` applies to that daemon disconnection. If a requested disconnection fails, the login is preserved.
|
||||
|
||||
Every command resolves and normalizes its destination before Hub or daemon work. Origin precedence is an explicit command origin or `--hub`, then `PASEO_HUB_URL`, then the active stored login origin, then the hosted default `https://hub.paseo.sh`. The hosted default never overrides an active login. Credential precedence is `--api-key <secret>`, then `PASEO_HUB_API_KEY`, then a stored login for the exact resolved origin. A stored credential is never sent to a different origin. API keys passed through flags or the environment are not stored.
|
||||
|
||||
Human output reports the resolved destination before each action. JSON output keeps stdout machine-readable and includes the normalized Hub origin in command results where the destination would otherwise be lost.
|
||||
|
||||
See [Daemons in Hub](/docs/hub/daemons), [Hub configuration](/docs/hub/configuration), and the [Hub public API](/docs/hub/api).
|
||||
|
||||
|
||||
+52
-17
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Hub public API
|
||||
description: Use organization API keys to install configuration, dispatch manual runs, and enroll daemons.
|
||||
description: Use organization credentials to list projects, validate or install configuration, dispatch runs, and enroll daemons.
|
||||
nav: Public API
|
||||
order: 78
|
||||
category: Hub
|
||||
@@ -21,8 +21,9 @@ These are the canonical reference endpoints for the hosted Paseo Hub. A self-hos
|
||||
|
||||
## Authentication
|
||||
|
||||
Create an organization API key from the Hub dashboard under **API keys**. Send
|
||||
it as a bearer token on every API request:
|
||||
Run `paseo hub login [origin]` for interactive CLI access. Hub returns a durable, revocable organization-scoped CLI credential after browser approval, and Paseo stores it privately under `PASEO_HOME` for that exact normalized origin. Without an explicit origin, the CLI uses `PASEO_HUB_URL`, then the active stored login origin, then `https://hub.paseo.sh`.
|
||||
|
||||
For automation, create an organization API key from the Hub dashboard under **API keys**. Both credential types are bearer tokens:
|
||||
|
||||
```http
|
||||
Authorization: Bearer paseo_pk_...
|
||||
@@ -36,15 +37,19 @@ organization is not accessible through the key.
|
||||
|
||||
Each key has one or more selectable scopes:
|
||||
|
||||
| Scope | Operation |
|
||||
| ----------------------- | --------------------------------------------------- |
|
||||
| `configuration:install` | Replace and activate a project's configuration. |
|
||||
| `runs:dispatch` | Dispatch a configured manual trigger for a project. |
|
||||
| `daemons:enroll` | Issue a short-lived daemon enrollment token. |
|
||||
| Scope | Operation |
|
||||
| ------------------------ | --------------------------------------------------- |
|
||||
| `projects:read` | List active projects in the organization. |
|
||||
| `configuration:validate` | Validate configuration without changing Hub state. |
|
||||
| `configuration:install` | Replace and activate a project's configuration. |
|
||||
| `runs:dispatch` | Dispatch a configured manual trigger for a project. |
|
||||
| `daemons:enroll` | Issue a short-lived daemon enrollment token. |
|
||||
|
||||
API keys do not grant dashboard access. They cannot manage connections,
|
||||
projects, or organization members.
|
||||
|
||||
CLI credentials have the current CLI operation scopes and remain revocable independently of daemon relationships. `paseo hub logout` deletes the active local CLI credential; it does not revoke or disconnect the daemon identity.
|
||||
|
||||
API failures use RFC 9457 problem details. Missing, invalid, or revoked credentials return `401` with `application/problem+json`:
|
||||
|
||||
```json
|
||||
@@ -58,6 +63,37 @@ API failures use RFC 9457 problem details. Missing, invalid, or revoked credenti
|
||||
|
||||
A valid key without the scope required by an endpoint returns `403` in the same format.
|
||||
|
||||
## Project list
|
||||
|
||||
`GET /api/v1/projects` returns active projects in the bearer credential's organization. `paseo hub projects` renders the projects as a table. With `--json`, it returns `{ "origin": "...", "projects": [...] }` so even an empty result records the resolved Hub.
|
||||
|
||||
```json
|
||||
{
|
||||
"projects": [
|
||||
{
|
||||
"id": "00000000-0000-4000-8000-000000000000",
|
||||
"slug": "my-project",
|
||||
"name": "My project"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration validation
|
||||
|
||||
`POST /api/v1/configurations/validate` accepts the same `projectSlug`, `yaml`, and optional `partials` body as configuration install. It performs the same compilation and resource resolution without recording a revision or changing the active configuration.
|
||||
|
||||
On success, Hub returns `200`:
|
||||
|
||||
```json
|
||||
{
|
||||
"projectSlug": "my-project",
|
||||
"valid": true
|
||||
}
|
||||
```
|
||||
|
||||
`paseo hub deploy --dry-run` calls this endpoint with the identical locally resolved payload that a deployment would send.
|
||||
|
||||
## Configuration install
|
||||
|
||||
`configuration:install` replaces the project's current configuration with the
|
||||
@@ -107,7 +143,7 @@ curl --fail-with-body -sS -X POST "$PASEO_HUB_URL/api/v1/configurations/install"
|
||||
--data @configuration-install.json
|
||||
```
|
||||
|
||||
For a local YAML file, `paseo hub deploy [file]` calls this endpoint and preserves the file contents. See [Deploy from the CLI](/docs/hub/configuration#deploy-from-the-cli) for project precedence, flags, environment variables, and the current authentication limits.
|
||||
For a local YAML file, `paseo hub deploy [file]` calls this endpoint and preserves the file contents. The command uses an exact-origin stored login when flags and environment credentials are absent. See [Deploy from the CLI](/docs/hub/configuration#deploy-from-the-cli) for project precedence and credential precedence.
|
||||
|
||||
## Manual run dispatch
|
||||
|
||||
@@ -183,7 +219,7 @@ key mints this token; it is not the daemon's long-lived credential and must not
|
||||
be used as one.
|
||||
|
||||
```http
|
||||
POST /api/daemons/enrollment-tokens
|
||||
POST /api/v1/daemons/enrollment-tokens
|
||||
```
|
||||
|
||||
Send an empty JSON object as the request body. On success, Hub returns `201`:
|
||||
@@ -196,19 +232,18 @@ Send an empty JSON object as the request body. On success, Hub returns `201`:
|
||||
```
|
||||
|
||||
The token expires after 10 minutes and is consumed when the daemon enrolls.
|
||||
Pass it to the Paseo CLI, which exchanges it for the daemon's connection
|
||||
credential:
|
||||
`paseo hub connect [origin]` performs this request with `--api-key`, `PASEO_HUB_API_KEY`, or the matching stored login, then passes the returned one-time token to the daemon's existing enrollment operation. Without an origin, it uses `PASEO_HUB_URL`, the active stored login origin, then `https://hub.paseo.sh`. The daemon generates and retains its own relationship credential.
|
||||
|
||||
```bash
|
||||
ENROLLMENT_TOKEN="$(curl --fail-with-body -sS -X POST \
|
||||
"$HUB_URL/api/daemons/enrollment-tokens" \
|
||||
curl --fail-with-body -sS -X POST \
|
||||
"$HUB_URL/api/v1/daemons/enrollment-tokens" \
|
||||
-H "Authorization: Bearer $PASEO_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{}' | jq -r .token)"
|
||||
|
||||
paseo hub connect "$HUB_URL" --token "$ENROLLMENT_TOKEN"
|
||||
--data '{}'
|
||||
```
|
||||
|
||||
Direct API consumers can pass the returned token to the daemon enrollment protocol. The Paseo CLI intentionally does not accept raw enrollment tokens; `connect` owns the authenticated single-flow exchange.
|
||||
|
||||
An enrollment token cannot be reused. Revoking the API key immediately rejects
|
||||
future API requests and expires any unconsumed enrollment tokens that key
|
||||
issued. A race between revocation and issuance is resolved by Hub before a new
|
||||
|
||||
@@ -33,8 +33,8 @@ project: my-project
|
||||
Then deploy:
|
||||
|
||||
```sh
|
||||
PASEO_HUB_URL=https://hub.example.com \
|
||||
PASEO_HUB_API_KEY=paseo_pk_... \
|
||||
paseo hub login https://hub.example.com
|
||||
paseo hub deploy --dry-run
|
||||
paseo hub deploy
|
||||
```
|
||||
|
||||
@@ -42,9 +42,9 @@ The default path is exactly `.paseo/hub.yml` relative to the current directory.
|
||||
|
||||
For each prompt `include`, the CLI sends one `{ path, content }` entry whose path is relative to `.paseo/partials/`. It sends only files referenced by the main YAML; nested include-looking text inside a partial is not scanned. Missing, unsafe, duplicate, unreadable, non-file, or oversized inputs fail locally before the Hub request. A configuration with only inline prompt blocks sends no `partials` field.
|
||||
|
||||
Use `--hub <origin>` or `PASEO_HUB_URL` for the Hub origin, and `--api-key <secret>` or `PASEO_HUB_API_KEY` for the organization API key. A flag takes precedence over its environment variable. The key supplies organization scope and needs `configuration:install`. `project` only selects the deployment target; workflows cannot reference it.
|
||||
`--dry-run` sends the identical resolved YAML, project slug, and prompt-partial bundle to `POST /api/v1/configurations/validate`. Hub performs the same compilation and resource resolution as installation but records and activates nothing.
|
||||
|
||||
Durable Hub login and credential persistence are not implemented. Supply the origin and API key for each deployment through flags or the current process environment.
|
||||
Origin precedence is `--hub`, `PASEO_HUB_URL`, the active stored login, then `https://hub.paseo.sh`. Credential precedence is `--api-key`, `PASEO_HUB_API_KEY`, then an exact-origin stored login. API keys passed by flag or environment are not stored. A stored credential is organization-scoped and is never reused for a different Hub origin. Deploy and dry-run report the normalized destination before sending anything and include it in structured results.
|
||||
|
||||
## Sync
|
||||
|
||||
|
||||
@@ -12,24 +12,32 @@ A daemon is one of your machines running the Paseo daemon. Enroll it once with y
|
||||
|
||||
## Connect
|
||||
|
||||
On the machine:
|
||||
Log in from the machine first:
|
||||
|
||||
```sh
|
||||
paseo hub connect https://hub.example.com
|
||||
paseo hub login https://hub.example.com
|
||||
```
|
||||
|
||||
The CLI prints a URL and a verification code, and opens your browser if it can. In Hub, open **Daemons → Register a daemon**, enter the code, choose a friendly slug, and approve it.
|
||||
The CLI prints a URL and verification code, opens your browser when the terminal is interactive, and stores the approved organization-scoped CLI credential under `PASEO_HOME`. Then enroll the daemon:
|
||||
|
||||
```sh
|
||||
paseo hub connect
|
||||
```
|
||||
|
||||
`connect` resolves the active login's Hub origin and uses its credential to request a short-lived, single-use enrollment token. The daemon exchanges that token and retains its own independently generated relationship credential. The human CLI credential is never stored as daemon authority.
|
||||
|
||||
Each daemon has two identifiers: an immutable generated ID and a friendly slug. Hub normalizes the slug you enter with lowercase words joined by hyphens, so `Build Studio` becomes `build-studio`. The slug is what the dashboard shows and what configuration references.
|
||||
|
||||
You can rename the slug later without changing the daemon ID. Renaming after a configuration is active means updating that configuration.
|
||||
|
||||
For unattended setup, skip the browser with an enrollment token:
|
||||
For unattended setup, pass an organization API key without storing it:
|
||||
|
||||
```sh
|
||||
paseo hub connect https://hub.example.com --token <token>
|
||||
PASEO_HUB_URL=https://hub.example.com PASEO_HUB_API_KEY=paseo_pk_... paseo hub connect
|
||||
```
|
||||
|
||||
Origin precedence is explicit `[origin]`, `PASEO_HUB_URL`, active stored login, then `https://hub.paseo.sh`. An explicit `--api-key <secret>` takes precedence over the environment and an exact-origin stored login.
|
||||
|
||||
Check and undo:
|
||||
|
||||
```sh
|
||||
@@ -40,6 +48,8 @@ paseo hub disconnect --force # drop local authority when Hub is unreachable
|
||||
|
||||
One daemon has one Hub relationship. Connecting a daemon that already has one is refused.
|
||||
|
||||
`paseo hub logout` removes only the active human CLI credential. In an interactive terminal, it checks the relationship and offers to disconnect when the daemon uses the same Hub. It performs an accepted disconnect before deleting the login, so a failed disconnection preserves the human credential. Declining leaves the daemon connected and removes only the login. JSON and noninteractive logout never disconnect implicitly; use `paseo hub logout --disconnect-daemon` when automation intends to remove both identities. Add `--force` only when that daemon disconnection should remove local authority while Hub is unreachable.
|
||||
|
||||
## Reference it from configuration
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -42,6 +42,10 @@ Only the users listed in a trigger's `from_users`. It is required and cannot be
|
||||
|
||||
Dispatch fails and the event is recorded as failed. Nothing is queued, so trigger it again once the daemon is back.
|
||||
|
||||
## Does logging out disconnect my daemon?
|
||||
|
||||
No. The stored CLI login is a human organization credential; the enrolled daemon has its own relationship credential. Interactive `paseo hub logout` offers to disconnect a daemon related to the same Hub. Declining is normal, and JSON or noninteractive logout never disconnects unless you pass `--disconnect-daemon`.
|
||||
|
||||
## Can an agent reply back to Slack or Discord?
|
||||
|
||||
Yes, with `allow_outputs`. See the [`hub.yml` output capability reference](/docs/hub/configuration/hub-yml#output-capabilities) for reply limits and required outputs. On GitHub, agents reply through the scoped `GH_TOKEN` they already have, so `gh issue comment` works.
|
||||
|
||||
@@ -54,3 +54,12 @@ A project is one set of environments and triggers. Split your work into projects
|
||||
## Running it
|
||||
|
||||
Two ways: [hosted](/docs/hub/hosted) or [self-hosted](/docs/hub/self-hosting). Everything above is the same either way.
|
||||
|
||||
Once Hub is running, approve durable CLI access and inspect the organization:
|
||||
|
||||
```sh
|
||||
paseo hub login https://hub.example.com
|
||||
paseo hub projects
|
||||
```
|
||||
|
||||
The login is scoped to that exact Hub origin. Use it to connect a daemon or deploy configuration without copying an API key into each command.
|
||||
|
||||
@@ -25,10 +25,11 @@ The connection appears with a generated slug like `yourname-github`.
|
||||
On the machine that will run agents:
|
||||
|
||||
```sh
|
||||
paseo hub connect https://your-hub.example.com
|
||||
paseo hub login https://your-hub.example.com
|
||||
paseo hub connect
|
||||
```
|
||||
|
||||
The CLI prints a verification code. In Hub, open **Daemons → Register a daemon**, enter the code, and choose a friendly slug. Hub normalizes `Build Studio` to `build-studio`. See [Daemons](/docs/hub/daemons).
|
||||
Approve the CLI login in the browser. `connect` selects that active login's origin and enrolls the daemon using the stored organization credential. The CLI login and daemon relationship remain separate identities. See [Daemons](/docs/hub/daemons).
|
||||
|
||||
## 4. Create a project
|
||||
|
||||
@@ -79,15 +80,15 @@ Push to the default branch. Hub fetches the file at that commit, validates it, a
|
||||
|
||||
If the file is invalid, Hub records the failure and keeps the previous revision active.
|
||||
|
||||
To deploy the file directly instead, create an organization API key with the `configuration:install` scope, then run:
|
||||
To inspect the authenticated organization's projects and deploy the file directly, run:
|
||||
|
||||
```sh
|
||||
export PASEO_HUB_URL=https://your-hub.example.com
|
||||
export PASEO_HUB_API_KEY=paseo_pk_...
|
||||
paseo hub projects
|
||||
paseo hub deploy --dry-run
|
||||
paseo hub deploy
|
||||
```
|
||||
|
||||
The command reads exactly `.paseo/hub.yml` from the current directory. It does not search parent directories. Use `paseo hub deploy path/to/config.yml` for another file. `-p, --project <slug>` overrides the file's `project` metadata. See [Hub configuration](/docs/hub/configuration#deploy-from-the-cli) for every deploy option and the current authentication limits.
|
||||
The commands use the active stored login. Origin precedence is explicit command origin or `--hub`, `PASEO_HUB_URL`, active login, then `https://hub.paseo.sh`. `--dry-run` sends the same YAML, project, and partial bundle to Hub's authoritative validator without recording or activating a revision. The command reads exactly `.paseo/hub.yml` from the current directory. Use `paseo hub deploy path/to/config.yml` for another file, or `-p, --project <slug>` to override the file's project metadata. An explicit `--api-key` or `PASEO_HUB_API_KEY` overrides the stored credential without persisting the key.
|
||||
|
||||
## 7. Trigger it
|
||||
|
||||
|
||||
@@ -133,6 +133,16 @@ Paseo wraps agent CLIs (Claude Code, Codex, OpenCode) but does not manage their
|
||||
|
||||
Paseo never stores or transmits provider API keys. Agents run in your user context with your existing credentials.
|
||||
|
||||
## Hub identities and credentials
|
||||
|
||||
Hub CLI login and daemon enrollment are separate identities. `paseo hub login [origin]` stores a durable organization-scoped human credential in a private file under `PASEO_HOME`, keyed by the normalized Hub origin. A stored credential is never sent to another origin. Protect `PASEO_HOME` as sensitive local state.
|
||||
|
||||
Hub CLI credentials are bearer secrets. Remote Hub origins must use HTTPS; cleartext HTTP is accepted only for loopback development origins (`localhost`, `127.0.0.1`, and `[::1]`).
|
||||
|
||||
`paseo hub connect [origin]` uses that credential, or an explicit API key, only to request a short-lived one-time enrollment token. The daemon exchanges the token and retains its own independently generated relationship credential. Logging out of the CLI does not silently remove daemon authority. Interactive logout completes any accepted same-origin daemon disconnection before deleting the login. In JSON and noninteractive use, `logout` never prompts or disconnects; pass `--disconnect-daemon` only when automation intends to remove both identities.
|
||||
|
||||
`--api-key` and `PASEO_HUB_API_KEY` override stored login without being persisted. Prefer environment or secret-manager injection for automation, and avoid command-line flags when local process listings or shell history are visible to other users.
|
||||
|
||||
## Recommendations
|
||||
|
||||
- **Use the relay** for mobile access, it's the simplest option and all traffic is end-to-end encrypted
|
||||
|
||||
Reference in New Issue
Block a user