Repository navigation
chore: sync dev with main - #809
Merged
Merged
Conversation
…tart-urls-and-broker-scope fix(mcp): correct hosted BASE_URL, add --scope user, drop broker_binding from public discovery
`nyxid service add --org X` now opens the wizard with the org pre-selected as owner instead of forcing the scripted terminal path. Update the prefill-args list and the scripted-flag bypass list to match.
feat(wizard): support --org in nyxid service add browser wizard
Users running `nyxid login` without `--base-url` or `NYXID_URL` now hit the hosted https://nyx-api.chrono-ai.fun endpoint instead of erroring. Override precedence is unchanged: --base-url flag > NYXID_URL env > default.
feat(cli): default login base URL to hosted NyxID instance
Document --passphrase-env and --no-pin-host-key for ssh-credentials add, and add an SSH error-code table (1011-1015) so agents have a complete reference for suggesting user-facing fixes.
feat(ssh): node-key authentication mode for credential nodes
The Authentication block (X-API-Key vs Bearer Token explanation) was gated on the curl example existing, so unknown bot/webhook services saw only the Base URL with no guidance on how to authenticate.
- Filter `none` and the OpenSSH ext-info-c / strict-kex markers from the "supported:" list in unknown-algorithm errors so users only see algorithms they can actually pass. - Reword the host-key error from "unknown" to "is not allowed" for algorithms that parse but are outside the Ed25519/RSA/ECDSA families. - Drop `num_args = 0..` on the eight allowlist flags so `--kex` without a value is rejected by clap instead of silently parsing as empty. - Reject `set-algos` invocations that specify no list and no reset flag to avoid no-op writes that print "updated".
Wire-up of the new TwitterOAuthGuidance component into the CLI pairing OAuth credential step changed the wizard source closure; rebuild the embedded bundle so the freshness check passes.
Add the new --kex / --host-key / --cipher / --mac flags on `ssh-credentials add` and the `set-algos` subcommand to the nyxid skill reference and the SSH node-key auth doc, with a worked RouterOS example. Update the 1013 SshNodeExecChannelClosed row to point at algorithm pinning as a debug step for legacy appliances that fail KEX init under russh defaults.
…deep-link actions (#761) * feat(frontend): make the dashboard interactive — URL-driven tabs and deep-link actions - Centralize tabbed-page state in lib/url-tabs.ts; settings / consents / integration-guide / ai-setup / keys / org-detail all read tab selection from search params. Tabs survive refresh, deep links are shareable, and browser back/forward navigates between tabs. - AccountPostureCard rows on the dashboard become real links pointing at the destination that completes the step (MFA -> /settings?tab=security, Connect a Service -> /keys?action=add-service, Create API key -> /keys?tab=nyxid&action=create-key). - CommandItem grows optional `search` and `onSelect`; the palette's three action entries ("Connect a Service", "Create API key", "Review approvals") now actually trigger the right dialog instead of just landing on a page. - Whitelist `action` on the /keys route so the deep links above survive TanStack search-param validation; add validators to settings / consents / integration-guide / ai-setup / org-detail for the same reason. Verified: type check clean, 567 unit tests pass, headless Chrome confirms the palette -> dialog round-trip for both add-service and create-key. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(frontend): mobile-nav search guards optional `to` on CommandItem The mobile-nav search reuses `ALL_ITEMS` from the command palette (`SEARCH_ITEMS`). After making `CommandItem.to` optional so action entries can run an `onSelect` callback instead of routing, two call sites in MobileNav still assumed `to` was a non-empty string and broke CI: - the filter dereferenced `item.to.toLowerCase()` (TS18048) - the click handler passed `item.to` directly to a `(to: string)` arg (TS2345) Mirror the palette's item-aware selection: optional-chain in the filter, prefer `onSelect` over navigation, and forward `item.search` so the action deep-links round-trip the same way the palette does. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…boarding chrome - Add shared ViewToggle component with sliding pill animation and per-page localStorage persistence via useViewMode(key) hook - Add grid/table view toggle to 5 pages: Services & Credentials, Organizations, Credential Nodes, Channel Bots, Access & Authorizations - Make each tab's view mode independent (keys-services vs keys-agent, etc.) - Mobile always shows card/grid view; toggle hidden below md breakpoint - Fix service card badges below title, subtitle truncation, info grid mt-auto - Fix consistent table row heights and badge ordering (Active before SSH) - Add profile dropdown and GitHub link to onboarding takeover top bar - Add auth-hero-4.png to login page image carousel rotation - Fix TabsList border width with w-fit - Fix Register Node button size to match standard CTA dimensions - Ensure cursor-pointer on all clickable toggle buttons and table rows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(frontend): UI consistency sweep v5 — view toggles, card fixes, onboarding
…de-grant feat(devices): device-code grant for headless provisioning
…6-device-code-grant Revert "feat(devices): device-code grant for headless provisioning"
…#776) * feat(oauth): multi-connection OAuth — additive, no migration Lets users add multiple codex / Lark services per provider instead of the second add silently aliasing onto the first. Every add mints its own connection_id; tokens are written and refreshed per-connection. Schema (additive, backward-compat — all Option fields, no migration): - connection_id on UserProviderToken, UserApiKey, OAuthState - partial unique index on UserApiKey.connection_id + startup dup audit New helpers (user_*_service): - write_oauth_tokens_to_key — callback writes the token straight onto the connection's UserApiKey, skipping user_provider_tokens; respects terminal statuses so a late/duplicate callback can't resurrect a key - refresh_user_api_key_in_place — per-connection refresh, BYO + provider- owned client creds, handles Lark JSON body + {code} error shape Flow wiring: - connection_id threaded initiate -> OAuthState -> callback - handle_oauth_callback + store_device_code_tokens branch on it - proxy_service refresh branches on it - silent-alias removal in unified_key_service::create_key (POST + PUT); fresh connection_id minted per add for oauth2 / device_code providers - key_id query param on the OAuth + device-code initiate handlers; the wizard passes the placeholder id so the callback scopes its write Isolation (a legacy refresh must not clobber a multi-connection key): - sync_provider_token_to_api_keys + fail_pending_placeholders_for_provider scoped to connection_id: null - reconcile_pending_oauth_placeholder — Pass 1 legacy-only, Pass 2 connection-scoped with a 60s fresh-placeholder grace guard CLI wizard bundle rebuilt to embed the key_id threading. Known gap (tracked in designs/codex-lark-multi-add-additive.md §4.11): per-Custom-App credentials for multi-connection Lark are not yet wired — all of a user's Lark connections currently share one Custom App and connection-scoped Lark refresh has no cred source. Requirements + fix plan discussed; follow-up PR. backend: 1890 tests pass, clippy clean, fmt clean. frontend type-checks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(oauth): wire multi-connection end-to-end — BYO Custom Apps, dashboard, CLI Lands the write side, surface side, and observability that the prior multi-connection scaffolding commit (a7938d4) deferred. After this patch a second `nyxid service add codex` / `add lark` actually creates an independent connection — no aliasing, no silent breakage at the first refresh ~2h later — across BOTH the dashboard "Add Service" dialog AND the `nyxid service add --oauth/--device-code` CLI commands. Strictly additive: no schema migrations, no index drops, no required- field additions, no breaking changes. Design source of truth: designs/multi-connection-custom-app-credentials.md (the BYO write side completing codex-lark- multi-add- additive.md §3.1 and §4.7) BYO Custom App credentials (Lark / Feishu / Twitter — credential_mode: "user"). `UserApiKey.user_oauth_client_*_encrypted` already existed on the branch but nothing populated them; refresh broke at first expiry. This commit: - Extends `CreateApiKeyParams` to accept `oauth_client_id` / `oauth_client_secret`; `create_api_key` encrypts both onto the new row. - `POST /keys` accepts three new optional fields: `oauth_client_id`, `oauth_client_secret`, `copy_oauth_client_from`. Mutual-exclusion + pair-completeness validated at the handler; provider compat (`supports_user_credentials`) gated in `create_key`; required-when- BYO-provider gated at the multi-connection mint site. - New `OauthClientCredentialsInput` enum + server-side resolver (`resolve_oauth_client_credentials_input`) decrypts the source key plaintext for the `CopyFrom` path (ownership-checked, returns NotFound for foreign owners so existence isn't leaked). - `user_credentials_service::resolve_connection_oauth_credentials` reads BYO off the connection's `UserApiKey`. `initiate_oauth_connect`, `request_device_code`, and `handle_oauth_callback` all consult it first when `connection_id` is set so authorize, exchange, and refresh use the SAME Custom App throughout the flow. API surfacing (so the FE / monitoring can tell connections apart): - `KeyView` / `KeyResponse` now include `connection_id` (plaintext) and `oauth_client_id` (async-decrypted via the new `enrich_view_with_oauth_client_id` helper). `list_keys` + `get_key` take `&EncryptionKeys` for the decrypt pass; all callers updated. - Proxy audit events (`event_data.connection_id`) and a `x-nyxid-connection-id` response header on all three proxy paths (direct HTTP, codex transport, node-routed) so dashboards can correlate "which Lark Custom App made this call" without a second DB read. - New `key_refresh_failed` audit event fired from `refresh_user_api_key_in_place` carrying `connection_id`, `provider_config_id`, `api_key_id`, and the truncated provider error. Dashboard + CLI multi-connection wiring (closing the gap so the non-wizard surfaces actually use the new path): - `useInitiateOAuth` / `useInitiateDeviceCode` accept an optional `keyId` that becomes `&key_id=…` on the initiate URL. Without it the OAuth state has no `connection_id` and the callback falls back to the legacy `user_provider_tokens` path, stranding the multi- connection placeholder. - Dashboard `AddKeyDialog` threads the placeholder id into both the OAuth and device-code initiate calls, and routes BYO Custom App credentials from `OAuthCredentialsStep` through parent state into the same `POST /keys` body. - CLI gains `--oauth-client-id`, `--oauth-client-secret` (hidden), and `--oauth-client-secret-env <VAR>` flags. `run_oauth_add` and `run_device_code_add` thread BYO into `POST /keys` and append `&key_id=…` to the initiate URL. - CLI rejects upfront: BYO + `--via-node` (node-routed flows manage creds locally), BYO + `--device-code` (all current device-code providers are admin-mode), unpaired BYO, and empty-but-set `--oauth-client-secret-env` (would surface as a generic backend 400 otherwise). Correctness fixes raised by the independent second-opinion review: - UTF-8 byte-boundary panic — `&body[..body.len().min(200)]` panics when a multibyte UTF-8 character straddles byte 200, which is the common case for Lark / Feishu Chinese error responses on refresh failure. Fixed at all four sites in `user_token_service.rs` with `.chars().take(200).collect::<String>()`. - Refresh-race CAS — failure writes in `refresh_user_api_key_in_place` used `update_one({_id: X})` with no precondition, so a losing concurrent refresh could clobber a sibling's freshly-active row with `status: "failed"` and lock the user out until manual re-auth. Failure writes now CAS on `(_id, updated_at, status $nin [revoked, failed])`; lost-race writes log and bail. The success write also gains the `status $nin [revoked, failed]` predicate so a sibling revoke can't be resurrected. - Legacy-table corruption — the dashboard's OAuthCredentialsStep and the wizard's `saveUserCredentials` were both calling `PUT /providers/{id}/credentials` alongside the new `POST /keys`. That table is single-row per `(user, provider)`; a user adding their second Lark Custom App via dashboard would overwrite their first connection's Custom App secret, silently breaking its legacy refresh path. Multi-connection refresh reads BYO directly off the `UserApiKey`, so the PUT is no longer needed for new adds. Removed it from both surfaces; the legacy endpoint stays available for admin tooling. - Partial unique index on `oauth_states.connection_id` (filter `{$gt: null}` since `connection_id: None` serializes as BSON null). Prevents duplicate in-flight states from a double-clicked "Connect" producing two successful callbacks that overwrite each other's freshly-rotated refresh tokens. Out of scope (tracked): - Provider-side identity capture (Codex email, Lark App identity) needs id_token / userinfo plumbing; deferred. - Real `copy_oauth_client_from` picker UX — backend supports it, frontend currently doesn't surface a picker; falls back to the re-paste form for v1. Tests + checks: - 1902 backend + 354 CLI + 1 wizard-freshness + 565 frontend tests pass; 0 failures. (12 new backend tests for create_api_key BYO write, resolve_connection_oauth_credentials, create_key BYO happy/sad paths, copy_oauth_client_from happy/sad paths.) - `cargo clippy --all-targets -- -D warnings` clean. - `cargo fmt --check` clean. - `npm run build` + `npm run lint` clean (0 errors, only pre- existing warnings). - Wizard bundle rebuilt (`cli/src/wizard/bundle-meta/index.hash`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(cli-wizard): allow BYO Custom App fields through wizard proxy The wizard's local HTTP proxy enforces a strict body-field allowlist for `POST /api/v1/keys` (cli/src/wizard/server.rs:1314). Commit 3604b4e added `oauth_client_id` / `oauth_client_secret` / `copy_oauth_client_from` to the backend `CreateKeyRequest` so a wizard add can store per-Custom-App credentials on the new UserApiKey, but the matching allowlist was never updated. Effect: every Lark / Feishu / Twitter wizard add through the new BYO path 400s with proxy: unexpected field 'oauth_client_id' for POST /api/v1/keys before the request reaches the backend. The placeholder is never created, so the rest of the multi-connection flow can't even start. Fix: add the three field names to the allowlist with a comment explaining what they're for and that `copy_oauth_client_from` is the alternative shape (clone an existing key's encrypted creds instead of retransmitting the secret). Server-side validation still enforces all type / length / shape rules — this only loosens the wizard's pre-flight filter, which exists to keep arbitrary client-supplied fields out of backend calls the wizard proxies. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(cli-wizard): re-apply OAuth polling cleanup fix (regression of #653) OAuthFlow makes ZERO network requests to GET /api/v1/keys/{id} after entering the "waiting" phase. The wizard's network tab shows only heartbeat traffic — no polling at all. Backend has the placeholder marked active; the wizard never notices, leaving the Lark / codex / Twitter wizard hung on "Waiting for provider authorization…" forever. This is the SAME bug as #653 / PR #723 (commit bdbb4ca). The fix was reverted by the UI consistency sweep (5f9a67e / PR #733), which even admitted it in its own diff: "Skip auth-flows polling regression test (depends on reverted #653 fix)" Root cause (unchanged from #723's diagnosis): OAuthFlow's main useEffect uses [phase] deps. Its cleanup sets cancelledRef.current = true. The async function inside the effect calls setPhase("waiting") and immediately awaits pollUntilActive(...). React's concurrent renderer flushes the queued setPhase during the polling loop's first `await sleepMs(2000)`, fires the useEffect cleanup, and flips cancelledRef = true. When the sleep resolves the polling loop's next `if (isCancelled()) return;` check exits without ever issuing the GET. DeviceCodeFlow doesn't have this bug because its main useEffect uses [] deps, so its cleanup only fires on actual unmount. Fix (verbatim re-application of bdbb4ca): - Remove `cancelledRef.current = true;` from the [phase]-deps useEffect cleanup. Keep the local `cancel` flag (still needed to abort the previous async run when the effect re-fires). - Add a separate useEffect with [] deps whose cleanup is the only place that flips cancelledRef = true. Mirrors DeviceCodeFlow's pattern, fires only on actual component unmount. - Expand the CRITICAL: do NOT comment block to call out the #733 regression explicitly so the next UI sweep is less likely to revert it a second time. Test: un-skip the regression guard in auth-flows.test.tsx: it("actually fires GET /keys/<id> while the placeholder is pending_auth (issue #653 root cause)") This test renders OAuthFlow with mocks that keep the placeholder in pending_auth (so the active short-circuit doesn't fire) and waits up to 5s for the polling loop to issue its first GET. Pre-fix it fails (zero GETs); post-fix it passes (~2.1s for the first poll cycle). Verification against the live database (this branch): Two Lark services added back-to-back through the wizard (api-lark-7 "lark test 01" + api-lark-8 "lark 002") each landed with distinct connection_ids, distinct encrypted access/refresh tokens, AND distinct encrypted BYO Custom App credentials — proving the full multi-connection BYO chain works end-to-end once polling actually fires. Bundle rebuilt: cli/src/wizard/assets/index.html + cli/src/wizard/bundle-meta/index.hash updated to match the fixed frontend source closure; wizard_bundle_freshness test passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(add-key-dialog): fix stale comment about legacy PUT call The parent-side comment on `OAuthCredentialsStep.onComplete` claimed "The legacy PUT to /providers/{id}/credentials still happens inside OAuthCredentialsStep so single-connection flows and admin tooling keep working." That's the OPPOSITE of reality post-3604b4ed — the PUT was removed precisely to avoid corrupting any pre-existing single-connection user's shared `user_provider_credentials` row when they add a SECOND Custom App connection via the dashboard (PR body correctness fix #3). The component-level doc on the `onComplete` prop itself (add-key-dialog.tsx:1798) already correctly explains the no-PUT invariant. This commit just brings the parent-side caller comment in line with it so a future contributor reading the call site doesn't infer they can rely on the PUT having happened. No behavioral change. No bundle rebuild needed (add-key-dialog.tsx is not in the wizard bundle closure; verified `cargo test -p nyxid-cli --test wizard_bundle_freshness` passes). Caught by the independent breaking-change audit on PR #776. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(devices): clean up pending_auth on device-code timeout/cancel CLI + dashboard device-code flows now DELETE the placeholder via ?only_if_pending=true on every abort path (expiry+declined renewal, denial, network errors, ctrl+C, dashboard Back/Cancel, dialog unmount), so `nyxid service list` no longer accumulates leftover services from abandoned attempts. Also adds a redrawn countdown timer and a [Y/n] renewal prompt in the CLI's TTY mode; non-TTY bails on first expiry without prompting. Closes #706. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(devices): wizard countdown + cleanup + responsive favicon Ports the device-code countdown + on-expiry/denied cleanup from the terminal-only and dashboard paths to the cli-wizard's DeviceCodeFlow (frontend/src/components/cli-wizard/auth-flows.tsx), which is the default path most users hit. The wizard now reads init.expires_in for its client-side deadline (was hardcoded 10min), shows an "Expires in MM:SS" chip beside the CODE label, and calls releaseServerState() on expired/denied so the placeholder doesn't linger if the user walks away. The "Request a new code" button continues to work via existing startSession() re-entry. CLI follow-ups in the same change: - Decouple countdown redraw from poll cadence: the inner loop now ticks every 1s for visible MM:SS movement while the HTTP poll is throttled to the provider's `interval` (5s+). - Add NYXID_DEVICE_CODE_DEADLINE_SECS env var that clamps the client-side deadline in both the terminal flow and (via a forwarded ?expires_in_override query param) the wizard, so QA can exercise expiry/renewal paths in seconds instead of waiting ~15min for a real provider expiry. Wizard now ships a responsive SVG favicon (nyxid-app-icon.svg) matching the dashboard's <link rel="icon"> — previously the wizard served only the static .ico fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Seed `google-cloud` provider with `access_type=offline` + `prompt=consent` extra_auth_params so Google returns a refresh_token. Without these the user gets a 1h access token and proxy calls fail when it expires. Matches the existing `google` provider seed. - Document the operator setup story in skills/nyxid/references/services.md: platform-default vs BYO OAuth client, the required redirect URI (`<BASE_URL>/api/v1/providers/callback`), and the `--oauth-client-id` / `--oauth-client-secret-env` flags. Without this, fresh deployments hit `--oauth` failures because the seeded provider has no client credentials.
feat(catalog): switch GCP to generic api-google-cloud OAuth service (#778)
…711) (#798) When the wizard server's request to the NyxID backend timed out, the proxy returned a body the shared frontend ApiError parser couldn't extract a `message` from, so the confirm panel's catch block silently set an empty error and the button just re-enabled — the user couldn't tell the request had failed. Backend (cli/src/wizard/server.rs): - New `proxy_error_response` helper emits ApiErrorResponse-shaped JSON (`error` + `error_code` + `message` + `detail`) with `Content-Type: application/json` on every proxy-error branch. - Distinguish timeout (504 + "upstream_timeout") from connection failure (502 + "upstream_unreachable") via `reqwest::Error::is_timeout()`. - CLI stderr prints a user-facing line on each branch instead of only the low-level transport error. Frontend: - New `UpstreamErrorBanner` — dismissible top-of-page banner with timeout/unreachable variants, mounted in the wizard shell. - Fetch shim (`client.ts`) sniffs upstream-error response bodies and dispatches a window event, so every flow that routes through `/api/v1/*` gets the banner without per-panel wiring. - `WizardApp` subscribes via `onUpstreamError`, gates the banner to the editable confirm phase, and clears on user dismiss. - AI-key confirm panel falls back to a default message when `ApiError.message` is empty — defense-in-depth against future shape drift. Tests: - 8 new vitest cases for `shouldShowUpstreamBanner` phase-gating. - Existing 48 Rust wizard-server tests and 576 frontend tests still pass. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous WARN claimed OAuth flows would fail until an admin configured platform-level credentials. That's wrong: with the seeded credential_mode=user, users can connect via BYO `--oauth-client-id` / `--oauth-client-secret-env` on `nyxid service add`. The platform default is just one of two valid paths. Downgrade to INFO and rewrite the message to surface both options.
The #778 cleanup removed the seed code for the gcp-cloud-billing / gcp-bigquery-billing catalog services and their gcp-billing / gcp-bigquery providers, but pre-existing rows from earlier deployments stay in MongoDB and still surface in the admin catalog UI -- with the now-defunct `gcp_service_account` auth_method that the proxy can no longer satisfy. Add a one-shot startup cleanup that deletes: - DownstreamService rows on the two removed service slugs - ProviderConfig rows on the two removed provider slugs - ServiceProviderRequirement rows referencing either side of the above Per-user data (UserService, UserProviderToken) is preserved -- the existing warning continues to nudge affected users to re-add via the new `api-google-cloud --oauth` flow.
Expand the #778 startup cleanup to remove user-side rows that were auto- provisioned from the now-defunct `gcp-cloud-billing` / `gcp-bigquery-billing` catalog entries. Without this, surviving UserService rows reference deleted DownstreamService rows and carry credentials for the gone `gcp_service_account` auth method — dead data the proxy can never use, and a credential leak waiting to happen. Cascade order (idempotent — no-op once clean): AgentServiceBinding (by user_service_id) UserService (by slug IN [gcp-cloud-billing, gcp-bigquery-billing]) UserEndpoint (by endpoint_id, only if not referenced by another svc) UserApiKey (by api_key_id, only if not referenced by another svc) UserProviderToken (by provider_config_id IN [gcp-billing, gcp-bigquery]) UserProviderCreds (same) ServiceProviderRequirement (either side) DownstreamService / ProviderConfig The orphan-check on UserEndpoint / UserApiKey matches the existing pattern in `unified_key_service::cleanup_orphan_endpoints_for_user` so a key shared with a non-GCP service isn't deleted out from under it. Affected users re-add via `api-google-cloud --oauth --endpoint-url ...`.
…p-warning fix(catalog): #778 follow-ups — drop stale GCP rows, downgrade warning
…789) Issue #651 stored an actionable account-mismatch message on the failed credential, but the two surfaces meant to display it were both intercepting it on the way to the user: - frontend/src/router.tsx: the /providers parent route's beforeLoad redirects non-admin users to /keys. That guard fires before the /providers/callback child route can render, so the browser-account user (typically not the CLI admin) lands on the AI Services list with the error query string discarded. Let /providers/callback past the guard so ProvidersCallbackPage renders status=error&message=... for any signed-in user. - cli/src/commands/service.rs: wait_for_authorized_key collapses every non-"active" status into a single "OAuth flow finished with unexpected key status: <other>" bail, dropping the credential's error_message field on the floor. Split "failed" out of the catch-all and bail with the stored error_message verbatim, falling back to a generic string only if it's empty. No backend changes -- the message already exists on the credential. This just connects the existing data to the surfaces the user is actually looking at. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Validate the saved session before any command does user-visible work (browser wizard, SSH socket, proxy request) so a dead/expired session prompts for re-login (interactive) or errors cleanly (headless) instead of opening a wizard/command that then 401s with "please log in". - auth::ensure_session: skip for explicit --access-token/NYXID_ACCESS_TOKEN; local JWT exp check (60s skew, zero network on the happy path); one silent /auth/refresh on expiry; on a dead session prompt-to-relogin when interactive (then exit 0 asking to re-run) or a clean error when headless. No auto-login. - ApiClient::from_auth_checked wraps the gate + client build; wired into session-authed commands, all 8 wizard entry points, and the SSH arms (gated before the raw WebSocket token is read). - Consolidate the three /auth/refresh implementations into one canonical auth::exchange_refresh_token (preflight, ApiClient in-flight retry, wizard server). 429/408 treated as retryable, not session-dead. Refs #801 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings dev up to date with main (6 commits since PR #799): - feat(cli): preflight session validation before authed commands (#803) - fix(oauth): surface account-mismatch error to browser and CLI (#695) (#789) - fix(catalog): drop stale GCP service-account rows + cascade-delete (#800) - fix(provider): downgrade google-cloud startup warning to info Merge resolves to main's tree exactly; dev has no unique content. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brings
devup to date withmain. Since the last sync (PR #799),maingained 6 commits thatdevwas missing. This PR closes that gap.The merge resolves to main's tree exactly — confirmed
devhas no unique content that would be lost (the only patch-id-unique commit on dev, the Vite port fix #243, is already content-equivalent in main).Commits being synced
8135f024feat(cli): preflight session validation before authed commands (feat(cli): preflight session validation before authed commands #803)098c09d5fix(oauth): surface account-mismatch error to browser and CLI ([Bug] [CLI Wizard OAuth] Account-mismatch callback redirects to the browser account's AI Services page instead of showing the mismatch error immediately #695) (fix(oauth): surface account-mismatch error to browser and CLI (#695) #789)b97b0138fix(catalog): cascade-delete user data tied to legacy GCP SA services (fix(catalog): #778 follow-ups — drop stale GCP rows, downgrade warning #800)9646a5c9fix(catalog): drop stale GCP service-account rows on startup (fix(catalog): #778 follow-ups — drop stale GCP rows, downgrade warning #800)83ae0458fix(provider): downgrade google-cloud startup warning to info (fix(catalog): #778 follow-ups — drop stale GCP rows, downgrade warning #800)Net diff: 31 files, +927 / −203 — concentrated in
cli/(auth preflight + arg plumbing),frontend/src/router.tsx, andbackendprovider/catalog cleanups.Note on history
The 31
add/addconflicts seen during the merge are artifacts of PR #799 being squash-merged intodev(which flattened main's history). They are not real content conflicts — every path was resolved to main's version, and the resulting tree is byte-identical tomain. Worth considering whether future syncs should fast-forward / merge (not squash) sodev's history stays a descendant ofmainand avoids this recurring noise.🤖 Generated with Claude Code