Files
Kai (Tam Nhu) Tran 30708ae79d feat(providers): support Codex OAuth pools with inherited routing defaults
* feat(auth): support named chatgpt oauth providers

- add provider-scoped ChatGPT OAuth routes and CLI support

- persist refresh tokens per provider and reject provider-type collisions

- wire provider OAuth setup flows in the dashboard and setup UI

Refs #448

* feat(agent): add chatgpt oauth account routing

- add agent other_config routing for manual and round-robin selection

- reuse routed provider resolution across resolver and pending loaders

- add router, parser, and agent advanced dialog coverage for multi-account use

Refs #448

* docs(api): describe chatgpt oauth routing

- document named-provider ChatGPT OAuth auth routes

- describe agent-side account routing and round-robin behavior

- update OpenAPI agent config schema and provider type enum

Refs #448

* fix(store): add missing agent key context helpers

* feat(ui): clarify chatgpt oauth account setup and routing

* docs(providers): align chatgpt oauth alias examples

* feat(agent): add codex pool activity dashboard

* fix(providers): harden codex oauth alias setup

* feat(codex-pool): improve routing dashboard UX

- redesign the Codex/OpenAI pool page around saved-pool checkpoints and live evidence

- add clearer selection, attention, and recent-proof states for pool members

- make the lower panels fill the remaining desktop viewport while staying responsive

* fix(store): resolve context helper merge duplication

* feat(oauth): add codex pool quota and observation APIs

- add quota inspection and observation endpoints for ChatGPT Subscription (OAuth) providers

- teach codex routing to surface pool activity, observation metadata, and quota-aware readiness

- extend tests and HTTP docs/OpenAPI for the new pool monitoring flows

* feat(web): add codex pool quota monitor and controls

- add provider quota fetching, readiness badges, and live routing evidence on the account pool page

- redesign pool setup and activity panels for multi-account management with localized copy updates

- keep the live monitor internally scrollable and compact the account cards for better viewport fit

* fix(web): clarify pool routing labels

- rename the recent request badge from Direct to Selected

- restore compact quota bars in the live pool cards

* feat(codex-pool): add runtime health dashboard

- derive per-provider success and failure health from routed Codex traces

- surface routing, quota, and recent request evidence in the pool UI

- align provider alias guidance and owner access with the dashboard role model

* docs(auth): document tenant scoping and key roles

* fix(auth): harden tenant and codex pool access control

* fix(providers): align codex pool runtime defaults

* feat(ui): tighten codex pool responsive layout

* feat(chatgpt-oauth): refine codex pool management UX

* feat(chatgpt-oauth): surface quota bars on provider pages

- add compact quota bars to Codex provider rows and provider detail

- fetch quota only for ready visible provider rows and ready detail aliases

- fix managed-member detail visibility and tighten provider locale copy
2026-03-27 09:35:57 +07:00

134 lines
3.6 KiB
Go

package cmd
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"github.com/nextlevelbuilder/goclaw/internal/oauth"
"github.com/spf13/cobra"
)
func authCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "auth",
Short: "Authenticate named ChatGPT OAuth accounts",
Long: "Manage ChatGPT OAuth authentication via the running gateway. Requires the gateway to be running.",
}
cmd.AddCommand(authStatusCmd())
cmd.AddCommand(authLogoutCmd())
return cmd
}
// gatewayURL returns the base URL for the running gateway.
func gatewayURL() string {
if u := os.Getenv("GOCLAW_GATEWAY_URL"); u != "" {
return strings.TrimRight(u, "/")
}
host := os.Getenv("GOCLAW_HOST")
if host == "" {
host = "127.0.0.1"
}
port := os.Getenv("GOCLAW_PORT")
if port == "" {
port = "3577"
}
return fmt.Sprintf("http://%s:%s", host, port)
}
// gatewayRequest sends an authenticated request to the running gateway.
func gatewayRequest(method, path string) (map[string]any, error) {
url := gatewayURL() + path
req, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, err
}
if token := os.Getenv("GOCLAW_TOKEN"); token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot reach gateway at %s: %w", gatewayURL(), err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
var result map[string]any
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("invalid response from gateway: %s", string(body))
}
if resp.StatusCode >= 400 {
if msg, ok := result["error"].(string); ok {
return nil, fmt.Errorf("gateway error: %s", msg)
}
return nil, fmt.Errorf("gateway returned status %d", resp.StatusCode)
}
return result, nil
}
func authStatusCmd() *cobra.Command {
return &cobra.Command{
Use: "status [provider]",
Short: "Show OAuth authentication status",
Long: "Check if a named ChatGPT OAuth account is authenticated on the running gateway.",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
provider := resolveOAuthProviderArg(args)
result, err := gatewayRequest("GET", fmt.Sprintf("/v1/auth/chatgpt/%s/status", url.PathEscape(provider)))
if err != nil {
return err
}
if auth, _ := result["authenticated"].(bool); auth {
name, _ := result["provider_name"].(string)
if name == "" {
name = provider
}
fmt.Printf("ChatGPT OAuth account: active (alias: %s)\n", name)
fmt.Printf("Use model prefix '%s/' in agent config (e.g. %s/gpt-5.4).\n", name, name)
} else {
fmt.Printf("No ChatGPT OAuth tokens found for alias '%s'.\n", provider)
fmt.Println("Use the web UI to authenticate this ChatGPT OAuth account.")
}
return nil
},
}
}
func authLogoutCmd() *cobra.Command {
return &cobra.Command{
Use: "logout [provider]",
Short: "Disconnect stored ChatGPT OAuth tokens",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
provider := resolveOAuthProviderArg(args)
_, err := gatewayRequest("POST", fmt.Sprintf("/v1/auth/chatgpt/%s/logout", url.PathEscape(provider)))
if err != nil {
return err
}
fmt.Printf("ChatGPT OAuth account disconnected for alias '%s'.\n", provider)
return nil
},
}
}
func resolveOAuthProviderArg(args []string) string {
if len(args) == 0 {
return oauth.DefaultProviderName
}
provider := strings.TrimSpace(args[0])
if provider == "" || provider == "openai" {
return oauth.DefaultProviderName
}
return provider
}