feat: add Huly cloud (huly.app) support#13
Conversation
teslakoile
left a comment
There was a problem hiding this comment.
Great feature — cloud support + OTP is valuable. A few things to address before this can merge:
Merge conflict (blocker): auth.py was significantly changed on master since this branch was cut. Needs a rebase before CI will run.
No tests: The new _accounts_url, _rpc, _complete_login, send_otp, login_otp paths and the login-otp command have no automated tests. See tests/test_auth.py and tests/test_auth_cmd.py for the existing patterns to follow.
Inline comments below on the specific code issues.
| resp.raise_for_status() | ||
| body = resp.json() | ||
| if "error" in body: | ||
| raise AuthError(f"{method} failed: {body['error']}") |
There was a problem hiding this comment.
If the server returns {"error": null} this raises AuthError: "login failed: None". Guard with if "error" in body and body["error"] is not None.
| "email": config.email, | ||
| "password": config.password, | ||
| }) | ||
| account_token: str = body["result"]["token"] |
There was a problem hiding this comment.
Bare body["result"] — KeyError if the API returns a 200 without a result key (version mismatch, proxy, etc). Check presence first and raise AuthError with a descriptive message.
| raise AuthError(f"Login failed: {body['error']}") | ||
| body = await _rpc(http, accounts_url, "login", { | ||
| "email": config.email, | ||
| "password": config.password, |
There was a problem hiding this comment.
Named params are used unconditionally here (and in selectWorkspace / getUserWorkspaces below). Self-hosted instances expect positional arrays: [email, password]. The branch description notes self-hosted wasn't verified — this is likely why. Consider detecting param format via _accounts_url / URL detection the same way you detect the endpoint.
| {"workspaceUrl": config.workspace, "kind": "external"}, | ||
| token=account_token, request_id="2", | ||
| ) | ||
| ws_result = body["result"] |
There was a problem hiding this comment.
Same bare body["result"] issue — KeyError if absent. Should raise a clean AuthError.
|
|
||
| # Get workspace UUID via getUserWorkspaces | ||
| body = await _rpc( | ||
| http, accounts_url, "getUserWorkspaces", {}, |
There was a problem hiding this comment.
Self-hosted getUserWorkspaces expects positional params ([]), not a named dict ({}). Same positional vs named params issue as above.
The CLI was built for self-hosted Huly instances and does not work with
Huly cloud (huly.app). This commit fixes three issues and adds OTP login:
1. **Accounts endpoint**: Cloud uses `account.huly.app` subdomain instead
of the `/_accounts` reverse-proxy path. Detect cloud URLs and route
accordingly.
2. **RPC param format**: Cloud expects named params (`{"email": ...}`)
instead of positional arrays (`[email, password]`). Also
`selectWorkspace` expects `workspaceUrl` key, not a positional slug.
3. **Transactor URL**: Cloud returns a per-region WebSocket endpoint in
the `selectWorkspace` response (e.g. `wss://europe-tr1.huly.app`).
The `/_transactor` path returns the SPA on cloud. Convert `wss://` to
`https://` for REST calls, and persist it in the auth cache.
4. **OTP login (`auth login-otp`)**: Huly cloud accounts that have only
used magic-link login have password login locked. Add an OTP flow
that sends a code to the user's email and validates it, which also
resets any failed-login lockout.
Self-hosted instances are unaffected — all changes are backward-compatible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8ddc5e8 to
3855cd8
Compare
|
Thanks for the review, Kyle — all points addressed in the force-push (commit 3855cd8): Rebased onto v0.1.4. Clean now.
Bare Positional vs named params — added Tests — added Let me know if you'd like anything restructured. |
The fixture value is a non-secret test literal, but GitGuardian's generic credential pattern flagged it. Rename to fake_test_password and add an allowlist pragma.
|
Thanks for the rework — all four prior blockers look properly addressed and the new test file is well-targeted. One ask before merge: Add command-level tests for Nits (fine in a follow-up):
|
The OTP code is a transient runtime value, not persistent config — pass it explicitly through the function signature instead of stashing it on the long-lived HulyConfig dataclass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ances Self-hosted instances reach the transactor through the nginx /_transactor reverse-proxy. If selectWorkspace ever started populating the `endpoint` field for a self-hosted server, we'd silently bypass that proxy. Gate the override on _is_cloud(config) and add a regression test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…loud match The login() docstring still listed the full step sequence even though steps 2-5 moved into the shared _complete_login helper. Replace the duplicated list with a pointer to the helper so the contract stays in one place. Also add a comment on _is_cloud explaining that the leading-dot match excludes bare huly.app by design. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the existing `auth login` command-level tests so the OTP flow has parity coverage: --code provided vs send-then-prompt, send_otp AuthError exit 2, login_otp AuthError exit 2, login_otp generic Exception exit 1, and missing email/workspace in non-TTY mode. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the review — pushed four commits addressing all four points:
Full suite green locally (264 passed). Ready for another look. |
Summary
huly-clicurrently only works with self-hosted Huly instances. This PR adds full support for Huly cloud (huly.app) and an OTP login command:account.huly.appsubdomain instead of/_accountsreverse-proxy path. Auto-detected from the configured URL.{"email": ...}) instead of positional arrays.selectWorkspaceusesworkspaceUrlkey instead of a positional slug.selectWorkspaceresponse (e.g.wss://europe-tr1.huly.app). The/_transactorpath returns the SPA on cloud. The transactor base is now derived from the response and persisted in the auth cache.auth login-otp): Cloud accounts using magic-link login have password login locked (PasswordLoginLocked). The new command sends an OTP code to the user's email and validates it — also resets any failed-login lockout.All changes are backward-compatible with self-hosted instances.
Files changed
auth.pysend_otp/login_otp/_complete_loginrefactorclient.pytransactor_basefrom auth instead of hardcoded/_transactorauth_cmd.pylogin-otpcommand with interactive code promptconfig.pyotp_codetoHulyConfig,transactor_basetoAuthCache(with persistence)Test plan
huly auth login-otpsends OTP, prompts for code, authenticates successfullyhuly auth statusshows authenticated statehuly projects listreturns projectshuly issues list --project <ID>returns issueshuly documents listreturns documentshuly --json projects listreturns valid JSON🤖 Generated with Claude Code