The halton-meter cloud subgroup manages the optional connection from a
local daemon to a Halton Meter Cloud workspace. The daemon ships with
cloud.enabled = false; nothing flows to the cloud until you run
halton-meter cloud connect and approve the pairing in the browser.
For the conceptual model (what syncs, what doesn't, retention windows) see Halton Meter Cloud overview. For the pairing walk-through see Connect your meter.
Synopsis
$ halton-meter cloud <subcommand> [flags]
| Subcommand | Purpose |
|---|---|
connect | Pair this daemon to a Cloud workspace via a one-time code. Writes a hm_sync_… token to ~/.halton-meter/cloud-credentials.json (chmod 0600). |
disconnect | Revoke the workspace token at the backend, then wipe local credentials and flip [cloud].enabled to false. |
status | Health summary: ACTIVE / DEGRADED / PAUSED / NOT-CONFIGURED + last sync timestamp + unsynced row count + paused_reason if PAUSED. Add --json for the machine-readable shape. |
whoami | Print the bound workspace, machine id, hostname, and base URL. Confirms which workspace this daemon is pushing to. |
sync | Trigger a single drain pass and exit. Useful in CI to flush before reading a workspace report. Also exposed as the top-level alias halton-meter sync. |
reconcile | Re-cost the local cache against the latest provider pricing matrix and push the corrected totals. Takes --days N (default 1). |
pause | Manually pause sync. Sets paused_reason = "paused_manual". Existing rows stay on disk; no new uploads. |
resume | Clear a pause. On paused_manual clears immediately; on paused_unauthorised / paused_forbidden it whoami-probes the existing token first (so a transient blip doesn't burn a re-pair). |
privacy show | Render the current upload-privacy state: preset, per-field overrides, per-project overrides, body-sync state. |
privacy set … | Mutate the upload-privacy config in ~/.halton-meter/config.toml. See Privacy below. |
cloud connect
Pair this daemon to a workspace. Walks the operator through a one-time code:
$ halton-meter cloud connect ⠇ Requesting pairing code from api.haltonmeter.com… ● Pairing code: WXJ4-9KLM ● Approve at: https://app.haltonmeter.com/connect?code=WXJ4-9KLM ⠇ Waiting for approval… (Ctrl-C to cancel) ✓ Approved — workspace: acme-co ✓ Token written to ~/.halton-meter/cloud-credentials.json (chmod 0600) ✓ [cloud] block in config.toml: enabled=true, base_url=https://api.haltonmeter.com
| Flag | Purpose |
|---|---|
--base-url <URL> | Override the cloud endpoint. Default https://api.haltonmeter.com. Also overridable via the HALTON_METER_CLOUD_URL env var. As of v0.2.1, the value persists to config.toml; earlier versions required a manual edit afterward. |
--poll-interval-seconds <float> | How often the daemon polls POST /v1/pairing/poll while waiting for browser approval. Default 2.0. |
--timeout-seconds <float> | Hard cap on the total pairing wall-clock time. Default 600.0 (10 minutes, matching the cloud-side code TTL). |
Cancel a stuck pairing: Ctrl-C cancels the local long-poll. As of
v0.2.11, re-running halton-meter cloud connect after a prior pairing
has been left in flight cancels the previous handshake on the backend
so a re-run can't leave a zombie awaiting approval. The cloud-side
pairing code expires on its own 10-minute TTL if neither happens.
cloud disconnect
$ halton-meter cloud disconnect
Disconnect is symmetric to connect:
- The daemon calls
DELETE /v1/daemon/disconnectso the token is unusable immediately even if the local files survive (stolen-laptop case). ~/.halton-meter/cloud-credentials.jsonand~/.halton-meter/cloud.keyare removed.[cloud].enabledis flipped tofalse.- The
cloud_staterow in SQLite is cleared.
The local database, the captured row history, and the local report
command keep working exactly as before. To re-pair: run cloud connect
again. The machine appears as a new device unless you preserved
~/.halton-meter/machine.json (which carries the stable machine_id).
cloud status
$ halton-meter cloud status ● State: ACTIVE ● Workspace: acme-co ● Last sync: 3s ago ● Unsynced rows: 0 ● Last reconcile: never ● Last error: — $ halton-meter cloud status --json { "state": "ACTIVE", "paused_reason": null, "last_sync_at": "2026-05-24T13:59:14Z", ... }
The four possible state values are described in
Connect your meter: Verifying the link.
The decision tree for recovering from a PAUSED state lives in
Troubleshooting.
cloud resume
A real recovery command (v0.2.8+), not a no-op.
$ halton-meter cloud resume
Reads the current paused_reason:
paused_manual: clears the pause immediately.paused_unauthorised(401) orpaused_forbidden(403): hitsGET /v1/daemon/whoamionce with the stored token. If 200, clears the pause, the failure counters, andlast_error. The daemon unpauses without burning a re-pair.
Use cloud resume first whenever the daemon shows PAUSED. Only fall
back to cloud connect if resume reports the token is genuinely
invalid. Running cloud connect revokes the existing key, which is
wrong when a transient blip or a workspace-side membership flap caused
the pause.
cloud sync and the top-level alias halton-meter sync
$ halton-meter cloud sync ● POST /v1/requests/batch → 200 OK (1247 rows accepted) # Top-level alias — same behaviour, shorter to type: $ halton-meter sync ● POST /v1/requests/batch → 200 OK (0 rows pending)
A single drain pass, then exit. CI usage: run halton-meter sync after
your test suite so a workspace report fetched immediately afterward
includes the run's spend.
cloud reconcile
$ halton-meter cloud reconcile --days 7
Re-costs locally cached rows against the latest pricing matrix and pushes
the corrected totals up. --days N selects the window, default 1
(yesterday and today). Use this after a provider price change or after
upgrading the daemon to pick up new pricing-table coverage.
Privacy
The cloud upload posture is set by a preset (minimal | standard | full) plus per-field overrides and per-project rules. The default is
standard with source_workdir = false. The high-leak field (absolute
filesystem paths) is off by default.
$ halton-meter cloud privacy show ● Preset: standard ● Body sync: disabled ● Overrides: source_workdir=false (preset default) ● Per-project: none $ halton-meter cloud privacy set preset minimal $ halton-meter cloud privacy set field.source_workdir true # override one field $ halton-meter cloud privacy set upload false --project secret-client $ halton-meter cloud privacy set bodies.enabled true # opt in to body sync globally $ halton-meter cloud privacy set bodies.upload false --project secret-client
Targets accepted by privacy set:
preset <minimal|standard|full>: change the baseline preset.field.<name> <true|false>: override a single field (e.g.field.source_workdir,field.prompt_hash). Per-field overrides win over the preset default.upload <true|false> [--project SLUG]: flip the metadata-sync master switch, or drop one project's metadata uploads while leaving the global switch on.bodies.enabled <true|false>: master switch for body sync (v0.2.2+).bodies.upload <true|false> [--project SLUG]: per-project body override.
Body sync (v0.2.2+) is governed by [cloud.bodies], a separate switch
from metadata sync. Bodies do not leave the machine until
bodies.enabled = true. Per-project overrides win over the global
switch: if the global is true but bodies.upload = false --project foo,
project foo's bodies stay local.
For the full preset → field mapping see Sync and retention.
Exit codes
| Code | Meaning |
|---|---|
0 | Success. |
1 | Generic failure. The daemon prints the message; daemon.err.log has the structured event. |
2 | Daemon not running. Start with halton-meter start. |
3 | Not paired. Run halton-meter cloud connect. |
4 | Paired, but the token is rejected (401). Run halton-meter cloud resume. |
5 | Paired, but workspace removed the device (403). Owner re-invites; then cloud resume. |
See also
- Halton Meter Cloud overview: what cloud adds, what stays local
- Connect your meter: pairing walk-through
- Sync and retention: what syncs, retention windows, opt-in body sync
- Cloud security: token storage, encryption, auth model
- Troubleshooting → PAUSED states