Skip to content

feat(cmd): add away mode (vacation) command#25

Closed
omarshahine wants to merge 3 commits intosteipete:mainfrom
omarshahine:feat/away-mode
Closed

feat(cmd): add away mode (vacation) command#25
omarshahine wants to merge 3 commits intosteipete:mainfrom
omarshahine:feat/away-mode

Conversation

@omarshahine
Copy link

Summary

  • Adds eightctl away on and eightctl away off commands to activate/deactivate away mode (vacation mode) for the authenticated user's pod side
  • Refactors internal HTTP method into do() (path-based, uses BaseURL) + doURL() (absolute URL) to support the app API host

Details

The away-mode endpoint lives on app-api.8slp.net, separate from the client-api.8slp.net used by other endpoints. The command sends a PUT to /v1/users/{userId}/away-mode with a start/end timestamp.

API contract reverse-engineered from the Eight Sleep Home Assistant integration (pyEight):

PUT https://app-api.8slp.net/v1/users/{userId}/away-mode
Content-Type: application/json

# Activate:
{"awayPeriod": {"start": "2024-03-12T08:15:30.123Z"}}

# Deactivate:
{"awayPeriod": {"end": "2024-03-12T08:15:30.123Z"}}

Timestamps are set 24 hours in the past to trigger immediately (same pattern as pyEight).

Usage

eightctl away on   # Stop heating/cooling (going on vacation)
eightctl away off  # Resume normal operation (back home)

Test plan

  • eightctl away on --quiet activates away mode
  • eightctl away off --quiet deactivates away mode
  • eightctl away --help shows subcommands
  • Existing tests pass (go test ./...)

Dependencies

This PR is based on #24 (OAuth + gzip fixes). It should be merged after #24.

🤖 Generated with Claude Code

Lobster and others added 3 commits March 14, 2026 23:59
The Eight Sleep auth server (auth-api.8slp.net/v1/tokens) expects
standard OAuth2 form-urlencoded requests, not JSON. The previous
implementation sent JSON with hardcoded "sleep-client" credentials,
which caused a 400 from Joi validation. The fallback to legacy
/login then tripped the rate limiter, resulting in a permanent 429
loop.

Changes:
- Send application/x-www-form-urlencoded instead of application/json
- Use c.ClientID and c.ClientSecret (the real app creds extracted
  from the Android APK) instead of hardcoded "sleep-client"/""
- Make authURL a var so tests can point it at a local server
- Add tests for form encoding, credential passthrough, and
  legacy login fallback

Fixes steipete#7, fixes steipete#8, fixes steipete#12

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The do() method sends Accept-Encoding: gzip but never decompresses
the response body, causing json.Decode to fail with:

    invalid character '\x1f' looking for beginning of value

(0x1f is the gzip magic byte.)

Check Content-Encoding: gzip on responses and wrap the body in a
gzip.Reader before decoding. Added test with a mock gzip response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add `eightctl away on` and `eightctl away off` to activate/deactivate
away mode. When away, the pod stops heating/cooling.

Supports per-side and whole-pod control:
- Default: authenticated user's side only
- --both: both sides of the pod
- --user-id: specific user/side

The away-mode API lives on app-api.8slp.net (separate from the client
API), so this also refactors `do()` into `do()` + `doURL()` to support
requests to different base URLs. Adds `Device().Sides()` to fetch the
left/right user ID assignments from device info.

API details reverse-engineered from the Eight Sleep Home Assistant
integration (pyEight):
- PUT /v1/users/{userId}/away-mode
- Payload: {"awayPeriod": {"start"|"end": "<ISO 8601 timestamp>"}}
- Timestamps set 24h in the past to trigger immediately

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@omarshahine
Copy link
Author

Superseded by a new PR based on main (not dependent on #24).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant