# pyre CLI changelog

All notable changes to the `pyre` command-line client. Versions follow
semver; the tag is `cli-v<version>`.

## 1.0.40 — 2026-10-12 (CLI, Linux and macOS from one commit)

- macOS: a second PyreData volume could not be mounted while another was
  ("mount_smbfs: server rejected the connection: Authentication error").
  macOS keeps one SMB session per server NAME, and two volumes reached
  as `127.0.0.1` with different passwords collided -- across accounts
  too, which the release gate found (its volume beside the owner's).
  Every Mac volume's server is now `<container>.localhost` (resolves to
  this Mac with no setup): the mount line, Finder's text and the app's
  own mount. A saved Finder mount of an older volume keeps working; the
  app remounts under the new name.
- `pyre update` / `pyre uninstall` no longer replace or remove the copy of
  `pyre` that ships inside the PyreData app (macOS) or the `pyredata`
  package (Linux): they say to update the app or the package instead.
  Updating in place had left a 1.0.37 Mac app running a 1.0.30 `pyre`
  with a broken bundle signature.

## 1.0.39 — 2026-10-12 (macOS and Linux published; CLI superseded by 1.0.40: no gate report)

- From this release the CLI, the Linux package and the macOS app carry
  the same version number and are cut from the same commit; `pyre
  update` and the Download page agree.

- Linux app: the volume section fetched the restore points on every
  re-render (every agent event and status tick), so the app asked the API
  200 times in ten minutes and the header spinner never stopped; now once
  a minute per volume. Time travel re-added its entry and timeline to
  fresh rows on each render, which GTK refused ("gtk_box_append:
  assertion ... parent == NULL") and the row vanished; they are detached
  first. Daniel, 2026-10-12, on the Linux client.

## 1.0.38 — 2026-10-12 (macOS published; Linux and CLI superseded by 1.0.39)

- Export of a copy of an encrypted volume opens. A promoted copy's blocks
  are encrypted under the SOURCE volume's salt (its original prefix); the
  hub packed the copy with the copy's own path as salt, so the key was
  "accepted" and block 0 then failed ("incorrect block signature",
  Daniel 2026-10-12). The API now tells the hub the volume's salt prefix
  (`export-start ... [label [prefix [salt-prefix]]]`, `pyre export
  prepare --salt-prefix`), and `pyre export open|extract --salt-prefix
  X/` opens an export made before this fix.

- Linux: the engine could not start on a clean machine: Lima finds its
  guest agents two directories above its own binary under `share/lima`,
  and the package had limactl at `/usr/libexec/pyredata/limactl`, so it
  looked in `/usr/libexec/share/lima` ("guest agent binary could not be
  found for Linux-x86_64"). Every earlier test passed only because the
  test machine had a system Lima. limactl now lives at
  `/usr/libexec/pyredata/bin/limactl`; the package test checks the
  guest agent is where Lima looks. Daniel, 2026-10-12: "limactl not
  starting on s_pyreclient with 1.0.35".

## 1.0.37 — 2026-10-12 (macOS published; Linux tag desktop-v1.0.37) -- NOT published for Linux (the layout above)

- Linux CI: the arm64 package is install-tested in Debian trixie only (the
  emulated Ubuntu pass alone took 40 minutes); amd64 still runs both.

## 1.0.36 — 2026-10-12 (macOS only)

- Get a copy works. Request copy did nothing on the Mac because no volume
  was pre-selected (Daniel, 2026-10-12), and both apps asked the API for
  delivery "download", which it does not know (`link` or `media`). The
  first volume is selected; Download requests a link; Ship it to me shows
  the priced media options for the chosen region (`export.media`), takes
  a postal address and places the order; Recent copies read the job's
  real fields (`state`, `link`, `expires_at`, `retrieval_note`,
  `key_note`, `tracking`, `error`).
- A copy made from an encrypted volume reads with the SOURCE volume's key.
  The API now says whose (`VolumeOut.key_volume`, docs/CLI-API-CONTRACT.md
  §10a) and both apps' Encryption section points at that key file
  (`~/.pyre/pyre-<source>.key`) instead of a file named after the copy
  that never existed; the appliance start block already mounted the
  right one. Daniel, 2026-10-12: "which encryption key should be used in
  this volume? The original one or ??"
- A promoted copy showed "appliance off" until Mount was clicked; the
  state now reads "not started · Mount to start" and the new volume is
  selected after promotion. Ready copies are also listed on the Volumes
  board (Mac) with "Make it a volume", and the toasts say where to look.

## 1.0.35 — unreleased

- Time travel works end to end in both apps. "Open this second read-only"
  sent `at` where the API wants `timestamp` ("timestamp: Field required"
  in the Mac app, Daniel 2026-10-12), and "New volume from this second"
  demanded an open view and then called promote with the wrong key. New
  agent methods `views.open` (open the view, wait for it, mount its share
  as `<volume>-view-<name>`; verified live on a Mac) and `views.close`
  (unmount + release); `mount.attach` takes `view` to mount an open view's
  share. A new volume from a moment now goes the real way: `restore.start`
  prepares a writable copy, listed under "New volumes in the making" with
  its copy progress, and "Make it a volume" promotes it (`new_name`); the
  view list reads the API's `open` / `point_in_time` keys.
- Resize puts the mount back. A grow or shrink unmounts the share inside
  the appliance for its whole duration, and no client came back with the
  new size: a Mac dropped the SMB mount, a Linux cifs mount kept the old
  size, an iSCSI initiator kept the old LUN size. New agent method
  `volumes.resize` (used by both apps' Resize): grow/shrink through the
  API, wait for the appliance to finish (phases shown as toasts), then
  unmount and mount again on this machine; for a grown iSCSI volume the
  new helper verb `iscsi-grow` rescans the session and grows the ext4 /
  xfs / btrfs filesystem online instead (polkit action
  `io.pyredata.mount.iscsi-grow`). Daniel, 2026-10-12: "On volume resize,
  shrink or grow, the current mount is not auto updated."
- Linux app: the boards now follow the Linux mockups (docs/design/mockups):
  GNOME greys with the teal accent, white cards, protocol pills, a
  property table per volume, the plan card in the sidebar, a drawn
  time-travel timeline, History policy as the six tiers with the named
  plans (Light / Standard / Extended), Get a copy and Account & engine as
  cards. New volume is opened from the + button instead of a sidebar entry.
- Linux app: History policy reads and writes the real six-number policy
  (`history.show` → `output`, `history.set` → `policy`); before, the page
  showed the raw field and Save sent a shape the API rejected.
- Linux app: restore points are read from the appliance's list
  (`restore_points.list` → `raw.recovery_points`); the volume section and
  Time travel count them and show the oldest.
- Linux app: API timestamps without a zone (`…T22:41:14.393733`) are
  parsed as UTC; token rows read "Created Oct 12 · Expires Jan 10, 2027".
- Linux app: the plan card never shows the tenant's e-mail as the plan
  name; a section already open keeps its detail page when the sidebar
  re-emits its selection.

## 1.0.34 — 2026-10-12 (macOS only)

- macOS app rebuilt in SwiftUI to the design boards: Solarized palette,
  sidebar with the plan meter, Volumes table with the selected volume's
  section (restore points, oldest point, local cache, waiting to upload,
  mount path, encryption key), dark Time travel board with a draggable
  log-scale timeline over the real restore points, History policy as the
  six tiers plus the Light/Standard/Extended plans (wire format
  "720,24,7,4,6,1"; the previous page expected a tier list that the API
  never sent), Get a copy, Account & engine, Advanced, a menu-bar
  popover, and the New volume sheet.
- macOS app: restore points are read from the appliance's JSON
  (`restore_points.list` returns it as a string); API timestamps without
  a zone parse as UTC; boards refresh every 15 s; the New volume sheet
  no longer crashes when the plan figure has not arrived yet.


## 1.0.33 — 2026-10-12

- Agent: the control plane's run block and limactl get the bundled
  limactl's directory on their PATH. A login agent has the bare system
  PATH, and the package keeps limactl out of it, so "limactl: command not
  found" ended every deploy on a clean install (found on a Mac; Linux was
  saved only by a system Lima on the test machine). 1.0.32 was never
  published.

## 1.0.32 — 2026-10-12

- Desktop package: the arm64 build is part of the signed release (1.0.31's
  CI job still built amd64 only).
- macOS desktop client, first build: PyreData.app + installer for Apple
  silicon (`desktop/macos/`), signed with the Developer ID.

## 1.0.31 — 2026-10-11

- **Desktop package for arm64** (Apple-silicon virtual machines, Ampere,
  Raspberry Pi 5) beside amd64; the apt repository serves both, so
  `apt install pyredata` picks the right one.
- The app opens `pyredata://enroll?code=...` links and takes
  `pyredata --enroll <code|link>`: First run fills the code in and signs in.
- The app carries its own symbolic icons, so it renders the same under
  every desktop theme (XFCE's Tango had none of them).
- Inside an ssh session the app defaults to no accessibility bus, the
  cairo renderer and X11 (the first window took minutes over `ssh -Y`).
- Package: postinst reloads each user's systemd manager before starting
  the agent.

## 1.0.30 — 2026-10-11

- **Desktop client for Linux (package `pyredata`, Debian 13 / Ubuntu 24.04).**
  One package carries the GTK 4 + libadwaita app, the per-user agent
  `pyre-agent`, the polkit-guarded `pyre-mount-helper`, the engine runtime
  (limactl) and this CLI; it provides, conflicts with and replaces `pyre`.
  The app enrols the computer with a one-time code from the Download page,
  mounts SMB, NFS and iSCSI volumes under `/mnt/pyre/<volume>`, offers time
  travel, history policy, copies, the engine and the account, and lists
  every API operation under Advanced. `docs/DESKTOP-CLIENT.md`.
- `pyre auth enroll <code|link>`: sign a computer in with a one-time
  enrolment code (or the `pyredata://enroll` link); the token is saved as
  with `auth login`. `pyre auth enroll-code [--label]` mints one (24 h,
  single use) for another computer.
- `pyre auth logout --expire`: the one deliberate way to revoke this
  computer's own token. `pyre auth token revoke` refuses the token in use
  (exit 2) and names that command; the API answers 409 unless `?self=1`.

- One appliance runtime on every OS: the Pyre VM. `pyre appliance deploy`
  for macOS and Windows now prints the Pyre VM install step (Lima from
  pyredata.io; Apple's virtualisation framework on macOS, QEMU with the
  Windows Hypervisor Platform on Windows; the VM definition for that OS;
  the launch agent or logon task) and a run block that goes through
  `limactl shell pyre docker`, exactly as Linux has since 1.0.x. Docker
  Desktop is no longer installed, required or mentioned. `remove` and
  `cleanup` print the VM steps for every OS. The Windows path is
  unverified on a Windows machine at the time of writing (the testbed has
  none); macOS is verified on Apple silicon.

## 1.0.29 — 2026-10-11

- One unit, GiB. Purchases were always GiB and the dashboard always sent
  whole GiB while every label said "GB" and the capacity refusal printed
  raw bytes. `pyre volume create --size 100GB` now rounds UP to the next
  whole GiB and says so ("creating 94GiB (100.9 GB)"); the API refuses a
  create that is not a whole GiB (422 naming the two nearest), as grow
  and shrink already did; every capacity message says GiB with one
  decimal. New 409 code `hub_capacity`: a volume (or a grow) larger than
  the serving hub's free space is refused even when the purchase allows
  it -- the hub-capacity ceiling the fleet never had.

- The first hub registers itself. `pyre admin hubs bootstrap --address
  <host> --start --name <name>` builds the machine and, when the build
  finishes, the control plane registers it as the primary under that
  name (the address's first label without `--name`); `bootstrap-status`
  says so, and `hubs add` is no longer a step of the first-hub process
  (adding a machine that is registered already is refused by address,
  not only by name). A hub that reports itself primary while the
  registry has none is adopted at the next poll instead of flagged as
  drift.

## 1.0.28 — 2026-10-10

- `pyre restore <volume> <when> --as <name>` is writable in seconds. The
  recovery reads the blocks it has not written through the volume's
  history at that moment and copies the rest in behind you; `pyre
  recovery list` shows how much is its own ("own data", 0-99% then
  "all"), and `promote` waits for "all". Needs the appliance image from
  the same release; an older appliance keeps copying first.
- `pyre admin hubs list` says "ok, replication STALLED" in the health
  column when a replica's passes are being refused (the delete guard
  after a big deletion on the primary); `hubs replication` has the
  detail and `accept-deletions` the remedy.

## 1.0.27 — 2026-09-29

- `pyre export download <link>`: fetches every part of an export from
  its temporary download link (https://api.pyredata.io/dl/...), resumes
  a broken download, keeps a part that is already there and intact,
  checks every file against its SHA-256, and prints what to run next.
  No account, token or network beyond the link.
- `pyre volume export <volume>`: asks the hub for a copy of the volume
  behind such a link (48 hours; `--status`, `--remove`, `--email`).
  Works for a deleted volume whose data is still stored. The link is
  on the hub that holds the files (https://<hub>/x/<token>/).
- `pyre volume export <volume> --medium list|<key> --region --address`:
  the same copy shipped on a BD-R XL disc, an LTO-9 tape, an SSD or a
  hard disk, priced (media, work, shipping) and ordered from the CLI.
- `pyre export show|verify|extract|open` read zip parts (what a
  download is made of) as they are, in place, next to directories on
  media; a folder holding either kind is scanned.
- `pyre export prepare --archive`: each part as one stored zip file.
  The help now names the tenant's store credentials on a converged
  hub (`/etc/pyre/tenants/<bucket>.env`), not `/etc/default/minio`.
- iSCSI volumes: `extract` refuses (the filesystem is the customer's)
  and points at `open`; `open --vhd` appends a fixed-VHD footer for
  Windows Disk Management; the README and `open` print how to attach
  the image on Windows, macOS and Linux.
- `pyre admin exports list|create|remove|ship|setup-hub`: every export
  job, one for any tenant (a preserved account, a media order), the
  shipping of a media order (tracking number emailed to the customer),
  and a hub's HTTPS download endpoint.

## 1.0.26 — 2026-09-28

- `pyre appliance deploy`: a volume whose appliance is already deployed
  is refused with one sentence -- which computer, whether it is online,
  `pyre appliance remove <volume>` to move it, `--rebuild` if that
  computer is gone -- instead of a run block that could only end in
  docker's "Conflict. The container name ... is already in use". The
  run block itself (every OS and protocol) looks for the container
  before docker run and says the same. The Linux install step warns
  when two Pyre VM host agents are running (a mount then reaches an
  appliance from an earlier setup).
- `pyre admin hubs bootstrap` / `onboard-key`: the paste block makes ssh
  keys-only (root and password login off); help text says so.

## 1.0.25 — 2026-09-28

- `pyre appliance mount` / `remove`: the commands are for the OS the
  appliance was deployed on (recorded at deploy), not the OS running
  pyre; `--os` still overrides, and the first line says which OS and
  why. From a Mac, an NFS appliance on a Linux client had printed the
  macOS block. The `mount --help` example no longer shows a literal
  `\n`.
- Installer (`install.sh`): installs into `~/.local/bin` by default, no
  sudo and no password; `pyre update` and `pyre uninstall` then need
  none either, the same per-user model as the Windows installer.
  `PYRE_INSTALL_DIR=/usr/local/bin` (or a root shell) keeps the
  system-wide install. An older copy in `/usr/local/bin` is pointed out
  at the end of the install so it cannot shadow the new one unnoticed.

## 1.0.24 — 2026-09-27

- `pyre admin hubs list`: a SERVING column -- for the primary, how many of
  its WireGuard peers shook hands in the last 3 minutes over how many it
  has (`recent/peers`); `0/N NOBODY` plus a notice naming inbound UDP
  51820 when a primary serves nobody. A primary whose firewall dropped
  every handshake had looked healthy for 14 minutes.
- `pyre admin hubs poll <hub>`: prints the health detail (role, tenants,
  tunnel identity, and for the primary whom it serves).
- Gate: run 23, every check of the release gate against the live fleet.

## 1.0.23 — 2026-09-26

- `pyre admin hubs replication`: a table (name, role, lag s, measured,
  pending, speed, eta, data, last pass). The lag is the worse of the
  measured frontier delta and the time since the last successful pass,
  as the hub's own promote guard decides; a replica whose passes the hub
  refused shows `FAILED: delete guard ...` with the count.
- `pyre admin hubs accept-deletions <hub>`: let a replica's next data
  pass mirror a primary that shrank (the delete guard at 100% for one
  pass), the answer to a refused pass without a shell on the hub.
- `pyre view open`: exits 1 with the appliance's reason when the instant
  is no longer version-backed, naming the nearest archived instants.
- `pyre volume grow/shrink`: whole GiB, and the nearest sizes named when
  not; sizes need a unit and are at least 1 GB on create too.
- `pyre view list` and `pyre recovery list`: every column filled.
- Every API call errors out after 30 s with the status and the next
  step (`build-status`, `promote-status`, `volume status`, ...).
- Behind the CLI, found by the release gate and fixed in the appliance,
  the hubs and the control plane: a resize no longer restarts the SMB
  service (a reconnected Linux client could not write afterwards) and
  closes client connections again after the remount; a promoted
  recovery attaches at the copy's real size; a released NFS view drops
  its export block; a dead iSCSI target from a killed container is
  swept before the next appliance's; a refused replication pass is
  visible on the dashboard and in the CLI; the onboarding preflight no
  longer deletes a built hub's tunnel firewall rule, answers its probe
  only to real probes, runs in a fifth of the time, and a refused build
  leaves no stale "done"; an account from before a fleet rebuild can
  be deleted; a hub removed from the control plane has its ssh master
  closed.
- Gate: run 22, 370 checks against the live fleet, every CLI call under
  30 s.

## 1.0.22 — 2026-09-26

- The release gate (`cli/e2e/release-gate.sh`): one run of the whole
  product as a customer and an operator meet it, on the real fleet;
  `cli-publish` refuses a tag without its green report. First gated
  release: 242 checks.
- `pyre appliance status` converges after a deploy; views and
  recoveries list what the API sends; recovery listings reconcile
  behind the response; a demote starts the first replication pass at
  once.

## 1.0.21 — 2026-09-25

- `pyre appliance deploy`: asks which OS and prints that OS's install
  step (the Pyre VM on Linux) before the run block.

## 1.0.20 — 2026-09-25

- `pyre admin`: hidden from a customer's help and completion; a
  customer's token gets a plain refusal naming it.

## 1.0.19 — 2026-09-25

- `pyre admin hubs build`: the primary preflights the machine first and
  refuses with the FAIL lines; the CLI prints its own next step.

## 1.0.18 — 2026-09-25

- Admin CLI sanity run (`cli/smoke/admin-hubs.sh`): the real API and CLI
  through the whole hub lifecycle against a fake fleet, in CI.

## 1.0.17 — 2026-09-25

- `pyre admin hubs onboard-key`: prints the block the new machine must
  run (sudo, the primary's onboarding key, the management key, root
  login off), not the bare key.

## 1.0.16 — 2026-09-25

- `pyre admin hubs remove`: sends only the flags that are set (an empty
  `force=` was a 422).

## 1.0.15 — 2026-09-25

- `pyre admin hubs bootstrap`: the first hub of a fleet can be built
  from the CLI (`--start`, `bootstrap-status`, `--rebuild`).

## 1.0.14 — 2026-09-25

- `pyre export`: the physical export. `prepare` (operator, on the hub)
  copies a volume's blocks off the store into per-medium directories
  with an index, README and SHA256SUMS; an encrypted volume leaves as
  the appliance's own ciphertext, a volume that was never encrypted is
  sealed to a public key from `pyre export keygen`, so nobody who
  prepares or carries the media can read it. `show` and `verify` need
  no key; `extract` copies the files out of the media on Linux, macOS
  and Windows with no mount, no image and no Docker (a built-in
  read-only ext4 reader that handles sparse files); `open` writes the
  raw disk image. A wrong key is refused by fingerprint before a block
  is read. Format and runbook: docs/PHYSICAL-EXPORT.md.

## 1.0.13 — 2026-09-24

- `pyre volume rediscover` reports volumes whose deleted row was just
  marked encrypted: a volume older than the storage's own record is
  known to be encrypted by the key-check object beside its blocks, so
  its row now says so and deploy asks for the key file.

## 1.0.12 — 2026-09-24

- `pyre volume rediscover`: your storage now carries its own record of
  every volume (written at create, resize and every appliance attach),
  and this lists any volume it describes that the account has no
  record of -- storage attached with a recovery key from before
  records were kept, a volume lost with a database -- as a deleted
  volume, ready to restore. `pyre account reclaim` runs it by itself
  and names what it found. A volume older than that record is
  described from its first block, type unknown: listed as smb, and
  `pyre volume deleted restore <id> --type nfs|iscsi` says what it was.
- `pyre volume deleted list` shows each volume's type and whether it is
  encrypted (which rows need their key file).

## 1.0.11 — 2026-09-24

- `pyre account close --mode preserve` keeps every volume as a deleted
  volume (the control plane change of the same day) and names them;
  `pyre account reclaim` then says how many came back and points at
  `pyre volume deleted list` / `deleted restore <id>` /
  `appliance deploy <volume> --rebuild`, instead of "create a volume".
  A tenant preserved before 2026-09-24 has no volume records; the
  message then explains the recreate-by-name path.

## 1.0.10 — 2026-09-24

- `pyre account reclaim --key <recovery key>`: attach the storage of an
  account closed with `account close --mode preserve` to the account
  this token belongs to. That account must have nothing of its own yet
  (no storage, volumes, stores or appliances); the key is accepted in
  any spelling (case, dashes, spaces) and is spent on success, never
  by a refusal. Until now the key could only be entered on the
  password sign-up form, which a "Sign in with Google" account never
  sees; the dashboard has the same page under "Recover it with your
  key".

## 1.0.9 — 2026-09-23

- `pyre volume deleted restore` now says what comes next: `pyre
  appliance deploy <volume> --rebuild`. Deleting a volume revokes its
  appliance's identity, so a container started again from its old
  state comes up unable to connect; the rebuild form's fresh token
  re-enrols it and the state volume and key file can stay.

## 1.0.8 — 2026-09-23

- After `pyre volume delete` of a volume that had an appliance, the CLI
  says how to clear it from the computer (`pyre appliance cleanup
  --names <volume>`), as the dashboard's redirect to /cleanup does.
- A command given the name of a DELETED volume (`appliance remove`,
  `appliance disconnect`, `volume show`, ...) now says so: when it was
  deleted, the cleanup line for its appliance, and the `deleted restore`
  line that brings it back. Before, "no volume named" left the customer
  hunting for a way to remove a stale appliance.
- `pyre appliance cleanup` with no `--names` takes every deleted volume
  of the account (logged in). Logged out it still needs `--names`.

## 1.0.7 — 2026-09-23

- `pyre volume create --encryption customer` works and is documented
  (the 1.0.0–1.0.6 example said `customer-key`, which the API refuses;
  it is now accepted as an alias of `customer`, as are `none`/`off`).
  Any other value is refused with exit 2 and the two choices, before
  anything is sent. After creating an encrypted volume the CLI says
  where the key file will be made (`pyre appliance deploy`,
  `~/.pyre/<container>.key`) and that losing it makes the volume
  unrecoverable.
- Every input is checked against the API's own rule before a request
  is built: ids in paths are numeric; volume, recovery and view names,
  labels, policies, points in time, `--since`, `--type` (now also
  `nfs+smb`), `--os`, `--cache-mb`, `--store`, store fields, emails
  and URLs (`--api`, `api_base`, `download_base`, `--endpoint`) are
  validated; a bad value is a usage error (exit 2) naming the choices.
  Validated path segments are additionally percent-escaped.
- `pyre store probe|add`: the provider list is the API's (`aws`,
  `wasabi`, `minio`, `weka`, `other`; default `other`). The old
  default `s3` and the documented `backblaze` were refused by the
  server on every call.
- Exit codes: cobra's own parse errors (`unknown shorthand flag`,
  `flag needs an argument`) are usage errors (exit 2, was 1); a stray
  word after a group command (`pyre volume nosuch`) is a usage error
  (was help with exit 0).
- A server-side validation answer (HTTP 422) is rendered as
  `field: message`, not as a JSON dump.
- Tests: the customer command matrix (`internal/cmd/permutations_test.go`,
  over 220 rows: valid, invalid and injection cases against a fake control
  plane) and new smoke checks against the real API; design and
  pass/fail criteria in `docs/CLI-TESTING.md`.

## 1.0.6 — 2026-09-23

- Encryption is shown as "customer key" or "none" from the API's
  `encryption` field (server section 10); a server that does not send
  the field shows "unknown" instead of a false "none". Also fills the
  `pyre volume list` ENCRYPTION column.

## 1.0.5 — 2026-09-23

- `pyre update` says which file it replaces ("replacing /path/pyre"),
  checks that directory is writable BEFORE downloading and names it in
  the error (with `sudo pyre update` only when that path needs it), and
  after updating lists any other `pyre` on PATH it did not touch.
  `pyre version` shows the binary's path and any others on PATH.
- Sizes match the dashboard: decimal GB with one decimal
  (26843545600 bytes → 26.8 GB); appliance measurements that arrive
  as strings are formatted too (no more raw byte counts).
- `volume show` / `volume status`: the Offline line appears only while
  the appliance is offline; buffer fill and disk-low facts sit on the
  Sync line otherwise, and an empty buffer is not mentioned.

## 1.0.4 — 2026-09-23

- `pyre volume show` is readable: one line each for Volume, Appliance,
  Sync, Offline, Storage, Errors and Measured, sizes in the dashboard's
  units, times local. Nothing in table mode prints raw JSON any more
  (nested data becomes dotted lines); `-o json` is unchanged, `-o plain`
  flattens to `key<TAB>value`.
- New `pyre volume status <volume>`: the live block alone, with
  `--watch [--every 5s]` redrawing until Ctrl-C on macOS, Linux and
  Windows (no watch(1) needed).
- `pyre appliance status` uses the same rendering.
- `pyre completion` without a shell now says
  `pyre completion <bash|zsh|fish|powershell> — e.g. pyre completion zsh`.
- `pyre update` rewrites the completion files it finds (bash, zsh,
  fish, PowerShell) with the new binary, clears stale `~/.zcompdump*`,
  and says when zsh completion is installed but `~/.zshrc` does not
  load it.

## 1.0.3 — 2026-09-23

- Homebrew: the generated formula carries a top-level `url` so current
  Homebrew accepts the tap (it validates every platform, Linux arm64
  included, and refused 1.0.2's formula with "requires at least a URL").
  No change to the binary.

## 1.0.2 — 2026-09-23

(1.0.1 was tagged on the same day but never published: its installer
still had the PATH gap below. Everything listed here is new since 1.0.0.)

- install.sh: installs into /usr/local/bin through sudo (the password
  prompt works under `curl | sh`); the ~/.local/bin fallback now adds
  itself to PATH and the zsh completion dir to fpath in your shell rc,
  and ends with "Open a new terminal (or run: source ~/.zshrc)". Found
  by the first real install on a Mac: `pyre` was not on PATH.

- Token lifecycle: `pyre auth token create --expires 30d|1y|never`
  (server default 90 days), `list` shows state and expiry, `remove`
  deletes a revoked or expired row (`--force` revokes first), `prune`
  drops every such row. Operators: `pyre admin tokens
  list|revoke|remove|prune` across tenants.
- `pyre uninstall` now removes every man page it installed
  (`pyre-<command>.1.gz`), not only `pyre.1.gz`.
- Install docs: `brew trust pyredata/pyre` before `brew install pyre`
  (current Homebrew refuses formulae from an untrusted tap).

## 1.0.0 — 2026-09-23

First packaged release, a rewrite in Go of the 0.1 Python prototype.

- One static binary per platform: macOS (Apple Silicon and Intel),
  Linux x86-64, Windows x86-64.
- Install channels: apt repository (signed), Homebrew tap, `curl | sh`,
  PowerShell one-liner, Scoop manifest, plain archives.
- `pyre update` (signed releases, in-place replace; defers to apt /
  Homebrew / Scoop when they installed it) and `pyre uninstall`.
- Shell completion for bash, zsh, fish and PowerShell; man pages.
- Scripting: `-o json` / `-o plain`, `--yes`, stable exit codes,
  `pyre batch` to run a file of commands with one login.
- Profiles for more than one control plane (`--profile`, `pyre config`),
  `--ca-cert` / `--insecure` for self-hosted managers, every host a
  config value.
- Customer commands: auth (token per user), volume (incl. deleted
  volumes), restore-points, restore, recovery, view, history, snapshot,
  appliance (deploy/mount/remove/status/disconnect/flush/cleanup),
  store (bring your own S3), account.
- Operator commands (`pyre admin`): hubs (scan/add/preflight/build/
  promote/demote/remove/bootstrap/...), users (approve/reject/delete),
  tenants.
