> ## Documentation Index
> Fetch the complete documentation index at: https://docs.crewship.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Admin

> Host-only operator commands that bypass the HTTP API and write directly to the local SQLite database. The recovery surface for when a user (typically you) cannot log in.

# crewship admin

`cmd/crewship/cmd_admin.go` exposes the **operator-on-the-host recovery surface**. Every subcommand under `admin` opens the local SQLite database directly (no HTTP, no auth token) — the server doesn't even need to be running. Use these when a user is locked out of the UI.

The "credential" for these commands is **shell access to the host**. That matches what GitLab (`gitlab-rake gitlab:password:reset`), Gitea (`gitea admin user change-password`), Nextcloud (`occ user:resetpassword`), and Mattermost (`mmctl user change-password`) all do — if you can SSH to the box, you ARE the admin.

```bash theme={null}
crewship admin <subcommand> [flags]
```

<Note>
  **The credential for these commands is shell access to the host** — there
  is no `crewship login` token. Requirements:

  * Read+write access to the data directory (default `~/.crewship`). The
    binary opens `~/.crewship/crewship.db` directly via `openAdminDB`.
  * The `crewship` binary must run on the **same host** as the data directory.
  * The host's filesystem permissions are the authentication.
</Note>

## Subcommands

| Command               | Purpose                                                                                                                                                           |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reset-password`      | Reset a user's password (interactive prompt or `--password` / `--password-stdin`).                                                                                |
| `list-users`          | List every user in the local database.                                                                                                                            |
| `promote`             | Promote a user to a workspace role (`OWNER`, `ADMIN`, `MANAGER`, `MEMBER`, `VIEWER`).                                                                             |
| `invalidate-sessions` | Force-logout every active session for a user (no password change).                                                                                                |
| `sessions list`       | Forensic read of `user_sessions` for one user — all rows, including revoked + expired.                                                                            |
| `prune-legacy`        | Remove orphaned pre-C1 crew docker resources that block agents from starting. **HTTP-backed — needs a running server + login** (the one exception in this group). |

***

## `crewship admin reset-password`

Rotate a user's password.

<Warning>
  The hashed value is written into `users.hashed_password`, the brute-force
  lockout state is cleared (`failed_login_count=0`, `locked_until=NULL`), and
  every active session row for that user is revoked as a side effect (the audit
  trail records `revoked_reason='password_change'`).
</Warning>

```bash theme={null}
# Interactive: stdin prompt with hidden input (no shell-history leak)
crewship admin reset-password --email admin@example.com

# Scripted: pipe the new password on stdin (CI-friendly)
echo 's3cret!' | crewship admin reset-password --email admin@example.com --password-stdin

# Argv (NOT recommended — visible in shell history and ps output)
crewship admin reset-password --email admin@example.com --password 's3cret!'
```

| Flag               | Required | Description                                                             |
| ------------------ | -------- | ----------------------------------------------------------------------- |
| `--email`          | yes      | Email of the user to reset. Normalised lowercase.                       |
| `--password`       | no       | New password as argv. Leaks to shell history; avoid in CI.              |
| `--password-stdin` | no       | Read the new password from stdin. Mutually exclusive with `--password`. |

<Tip>
  Omit both `--password` and `--password-stdin` to get an interactive prompt
  with hidden input — the right default for ad-hoc recovery from a host
  shell, and the only form that never leaks to shell history or `ps`.
</Tip>

***

## `crewship admin list-users`

Dump every row in the `users` table, ordered by `created_at ASC`. Output columns: EMAIL, NAME, CREATED (`created_at`), LOCKED (derived from `locked_until`), FAILS (`failed_login_count`), ROLES (`role@workspace-slug`, comma-joined across workspaces).

```bash theme={null}
crewship admin list-users
crewship admin list-users --locked-only
```

| Flag            | Default | Description                                                                                                  |
| --------------- | ------- | ------------------------------------------------------------------------------------------------------------ |
| `--locked-only` | `false` | Filter to currently locked-out accounts only. Useful when triaging "who's stuck?" after a brute-force probe. |

The lockout column reflects the `users.locked_until` timestamp set by the failed-login throttle — `list-users --locked-only` followed by `reset-password` is the canonical "unlock this user" pair.

***

## `crewship admin promote`

Grant a workspace role to an existing user. Roles ascend `VIEWER` \< `MEMBER` \< `MANAGER` \< `ADMIN` \< `OWNER`; the command updates the user's existing row in `workspace_members` (the user must already be a member — a non-member errors with `user is not a member of workspace`).

```bash theme={null}
crewship admin promote --email admin@example.com --role OWNER
crewship admin promote --email lead@example.com --role ADMIN --workspace acme
```

| Flag          | Required | Description                                                                                                                                    |
| ------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `--email`     | yes      | Email of the user to promote.                                                                                                                  |
| `--role`      | yes      | Target role — exactly one of `OWNER`, `ADMIN`, `MANAGER`, `MEMBER`, `VIEWER` (case-insensitive).                                               |
| `--workspace` | no       | Workspace slug. Defaults to the user's only workspace **when they have exactly one** — when ambiguous the command errors and asks you to pick. |

This is the post-`init` step for bootstrapping a second admin: the first user from `crewship init` is `OWNER` automatically; everyone else starts as a regular member and needs `admin promote` to gain elevated access without going through the UI.

***

## `crewship admin invalidate-sessions`

Force-logout every active session for one user **without** changing their password. The user can still sign in normally afterwards — they just have to re-authenticate on every device they were already on.

```bash theme={null}
crewship admin invalidate-sessions --email user@example.com
```

| Flag      | Required | Description                                 |
| --------- | -------- | ------------------------------------------- |
| `--email` | yes      | Email of the user whose sessions to revoke. |

Each revoked row is stamped `revoked_reason='admin_invalidate'` so the audit trail distinguishes this from the side-effect revokes that fire during `reset-password` (those carry `revoked_reason='password_change'`).

<Accordion title="When to reach for this instead of reset-password">
  * Laptop stolen / recovered, the password is believed safe but a cached cookie might still be on the device.
  * Suspected token leak via Slack screenshot, browser history dump, etc.
  * Periodic compliance sweep — "log everyone out of yesterday's sessions".
</Accordion>

***

## `crewship admin sessions list`

Forensic read of `user_sessions` for one user — mirrors [`crewship session list`](/cli/session) but for **arbitrary** users (the user-side command is self-only). Admin-only via direct DB access.

```bash theme={null}
crewship admin sessions list --email user@example.com
crewship admin sessions list --email user@example.com --active-only
crewship admin sessions list --email user@example.com --limit 200
```

| Flag            | Required | Default | Description                                                                                               |
| --------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------- |
| `--email`       | yes      |         | Email of the user whose sessions to list.                                                                 |
| `--active-only` | no       | `false` | Filter to non-revoked, non-expired rows (matches what `crewship session list` shows the user themselves). |
| `--limit`       | no       | `50`    | Row cap. Useful for users with hundreds of historic sessions.                                             |

Output columns: ID, STATUS (derived from `revoked_at` + `expires_at`; revoked rows show `revoked:<reason>`), CREATED, LAST USED (`last_used_at`), EXPIRES, IP, UA (`user_agent`). Sorted `created_at DESC`. A typo'd email reports `no user with email <addr>` rather than silently returning zero rows.

Pair with `admin invalidate-sessions` to act on what `sessions list` surfaces.

## `crewship admin prune-legacy`

Removes orphaned **pre-C1 (slug-only) crew docker resources** — volumes and
containers named `<prefix>-{home,tools,team}-<slug>` (e.g.
`crewship-3-tools-engineering`) left over from before the C1 naming change
(2026-06 audit, which re-keyed crew resources to include the crew id).

```bash theme={null}
crewship admin prune-legacy
```

<Warning>
  **Unlike the rest of `crewship admin`, this command is HTTP-backed.** The
  docker daemon lives behind the running server, not the local SQLite DB, so
  this command needs a reachable server and an authenticated session
  (`crewship login`) — it is **not** a host-only recovery command. It is filed
  here because it is an operator/admin maintenance action.
</Warning>

### Why this exists

A legacy slug-only volume **survives `crewship seed --nuke`**: the nuke clears
the database, and crew teardown removes the *id-scoped* volumes — but never the
orphaned slug-only ones, because no crew row references them. While they exist,
the runtime's legacy-resource guard refuses to start the crew's container, and
the failure reaches users only as a generic **"failed to start agent
container"**. Every agent in the affected crew fails; a `seed --smoke-test`
reports `N/N agents failed`.

`crewship doctor` surfaces this proactively: the `legacy crew resources` check
calls the authenticated `GET /api/v1/admin/legacy-resources` endpoint and
**WARN**s with this command as the remediation when orphaned resources are
present. (Detection runs on this admin endpoint, not on the unauthenticated
`/healthz` hot path, so a slow docker daemon can never stall health probes.)

The prune is **instance-wide**: legacy docker names carry no workspace or crew
id, so detection (what `crewship doctor` surfaces) and prune enumerate the same
full crew set — otherwise the doctor could WARN on a slug the prune can never
reach. It removes **only** the orphaned legacy names; the id-scoped resources
the live runtime uses are excluded (slug/id-collision safe) and never touched.
It reports each resource removed:

```text theme={null}
Removed 4 legacy resource(s):
  - crewship-3-tools-engineering
  - crewship-3-home-engineering
  - crewship-3-tools-quality
  - crewship-3-home-quality
```

Requires the `OWNER` or `ADMIN` role. Returns `503` when the server's container
provider is not docker (nothing to prune).

***

## See also

* [`crewship init`](/cli/init) — first-user bootstrap on a fresh database. After `init`, `admin promote` is how you add a second OWNER.
* [`crewship doctor`](/cli/doctor) — the `legacy crew resources` check WARNs when a prune is needed.
* [`crewship session`](/cli/session) — user-scoped self-service of the same `user_sessions` table.
* [`crewship doctor`](/cli/doctor) — adjacent host-side diagnostics (data dir, schema version, container runtime).
