From 7af4d87fd0e5703a2252ef7eae8645337904aa41 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 23 Apr 2026 15:07:08 -0700 Subject: [PATCH 1/2] feat: add tailscale proxy param --- internal/api/client.gen.go | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/internal/api/client.gen.go b/internal/api/client.gen.go index 63c7c2d..5a34e95 100644 --- a/internal/api/client.gen.go +++ b/internal/api/client.gen.go @@ -2346,6 +2346,13 @@ type TabsData struct { Url string `json:"url"` } +// TailnetProxy defines model for TailnetProxy. +type TailnetProxy struct { + OauthClientId string `json:"oauth_client_id"` + OauthClientSecret *string `json:"oauth_client_secret,omitempty"` + Type *string `json:"type,omitempty"` +} + // UpdateFunctionRunResponse defines model for UpdateFunctionRunResponse. type UpdateFunctionRunResponse struct { // FunctionId The ID of the function @@ -5901,6 +5908,36 @@ func (t *ApiSessionStartRequest_Proxies_0_Item) MergeExternalProxy(v ExternalPro return err } +// AsTailnetProxy returns the union data inside the ApiSessionStartRequest_Proxies_0_Item as a TailnetProxy +func (t ApiSessionStartRequest_Proxies_0_Item) AsTailnetProxy() (TailnetProxy, error) { + var body TailnetProxy + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTailnetProxy overwrites any union data inside the ApiSessionStartRequest_Proxies_0_Item as the provided TailnetProxy +func (t *ApiSessionStartRequest_Proxies_0_Item) FromTailnetProxy(v TailnetProxy) error { + tmp := "tailnet" + v.Type = &tmp + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTailnetProxy performs a merge with any union data inside the ApiSessionStartRequest_Proxies_0_Item, using the provided TailnetProxy +func (t *ApiSessionStartRequest_Proxies_0_Item) MergeTailnetProxy(v TailnetProxy) error { + tmp := "tailnet" + v.Type = &tmp + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + func (t ApiSessionStartRequest_Proxies_0_Item) Discriminator() (string, error) { var discriminator struct { Discriminator string `json:"type"` @@ -5919,6 +5956,8 @@ func (t ApiSessionStartRequest_Proxies_0_Item) ValueByDiscriminator() (interface return t.AsExternalProxy() case "notte": return t.AsNotteProxy() + case "tailnet": + return t.AsTailnetProxy() default: return nil, errors.New("unknown discriminator value: " + discriminator) } @@ -6986,6 +7025,36 @@ func (t *GlobalScrapeRequest_Proxies_0_Item) MergeExternalProxy(v ExternalProxy) return err } +// AsTailnetProxy returns the union data inside the GlobalScrapeRequest_Proxies_0_Item as a TailnetProxy +func (t GlobalScrapeRequest_Proxies_0_Item) AsTailnetProxy() (TailnetProxy, error) { + var body TailnetProxy + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTailnetProxy overwrites any union data inside the GlobalScrapeRequest_Proxies_0_Item as the provided TailnetProxy +func (t *GlobalScrapeRequest_Proxies_0_Item) FromTailnetProxy(v TailnetProxy) error { + tmp := "tailnet" + v.Type = &tmp + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTailnetProxy performs a merge with any union data inside the GlobalScrapeRequest_Proxies_0_Item, using the provided TailnetProxy +func (t *GlobalScrapeRequest_Proxies_0_Item) MergeTailnetProxy(v TailnetProxy) error { + tmp := "tailnet" + v.Type = &tmp + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + func (t GlobalScrapeRequest_Proxies_0_Item) Discriminator() (string, error) { var discriminator struct { Discriminator string `json:"type"` @@ -7004,6 +7073,8 @@ func (t GlobalScrapeRequest_Proxies_0_Item) ValueByDiscriminator() (interface{}, return t.AsExternalProxy() case "notte": return t.AsNotteProxy() + case "tailnet": + return t.AsTailnetProxy() default: return nil, errors.New("unknown discriminator value: " + discriminator) } From 84560bebbf918124f3198180c9ceaa52edb48a09 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 23 Apr 2026 15:23:36 -0700 Subject: [PATCH 2/2] feat: expose external and tailnet proxies via typed CLI flags The previous commit regenerated the client with TailnetProxy but nothing surfaced it on `sessions start`. External proxies were never reachable from the CLI either. Adds typed flags for both kinds and rejects conflicting proxy flags since the union permits only one selection. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/cmd/sessions.go | 68 +++++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/internal/cmd/sessions.go b/internal/cmd/sessions.go index 274131c..c657efc 100644 --- a/internal/cmd/sessions.go +++ b/internal/cmd/sessions.go @@ -23,9 +23,14 @@ import ( // Manual flags for proxies and extra headers (union types not auto-generated) var ( - sessionsStartProxy bool - sessionsStartProxyCountry string - sessionsStartExtraHttpHeaders string + sessionsStartProxy bool + sessionsStartProxyCountry string + sessionsStartProxyExtServer string + sessionsStartProxyExtUsername string + sessionsStartProxyExtPassword string + sessionsStartProxyTailClientID string + sessionsStartProxyTailClientSecret string + sessionsStartExtraHttpHeaders string ) var ( @@ -339,6 +344,11 @@ func init() { // Manual flags for proxies (union type: bool | array of proxy objects) sessionsStartCmd.Flags().BoolVar(&sessionsStartProxy, "proxy", false, "Use default proxies") sessionsStartCmd.Flags().StringVar(&sessionsStartProxyCountry, "proxy-country", "", "Proxy country code (e.g. us, gb, fr). Implies --proxy") + sessionsStartCmd.Flags().StringVar(&sessionsStartProxyExtServer, "proxy-external-server", "", "External proxy server URL (e.g. http://proxy:8080). Enables external proxy") + sessionsStartCmd.Flags().StringVar(&sessionsStartProxyExtUsername, "proxy-external-username", "", "External proxy username") + sessionsStartCmd.Flags().StringVar(&sessionsStartProxyExtPassword, "proxy-external-password", "", "External proxy password") + sessionsStartCmd.Flags().StringVar(&sessionsStartProxyTailClientID, "proxy-tailnet-client-id", "", "Tailnet OAuth client ID. Enables Tailscale proxy") + sessionsStartCmd.Flags().StringVar(&sessionsStartProxyTailClientSecret, "proxy-tailnet-client-secret", "", "Tailnet OAuth client secret") // Manual flag for extra HTTP headers (map type not auto-generated) sessionsStartCmd.Flags().StringVar(&sessionsStartExtraHttpHeaders, "extra-http-headers", "", `Extra HTTP headers as JSON (e.g. '{"Authorization": "Bearer xxx"}')`) @@ -494,18 +504,60 @@ func runSessionsStart(cmd *cobra.Command, args []string) error { return err } - // Handle proxies manually (union type: bool | array of proxy objects) - // --proxy-country takes precedence over --proxy + // Handle proxies manually (union type: bool | array of proxy objects). + // At most one proxy kind may be selected per call. + var setProxyFlags []string + for _, name := range []string{"proxy", "proxy-country", "proxy-external-server", "proxy-tailnet-client-id"} { + if cmd.Flags().Changed(name) { + setProxyFlags = append(setProxyFlags, "--"+name) + } + } + if len(setProxyFlags) > 1 { + return fmt.Errorf("proxy flags are mutually exclusive, got: %s", strings.Join(setProxyFlags, ", ")) + } + + var proxyItems api.ApiSessionStartRequestProxies0 + if cmd.Flags().Changed("proxy-country") { country := api.ProxyGeolocationCountry(sessionsStartProxyCountry) notteProxy := api.NotteProxy{Country: &country} var item api.ApiSessionStartRequest_Proxies_0_Item if err := item.FromNotteProxy(notteProxy); err != nil { - return fmt.Errorf("failed to create proxy: %w", err) + return fmt.Errorf("failed to create notte proxy: %w", err) + } + proxyItems = append(proxyItems, item) + } + + if cmd.Flags().Changed("proxy-external-server") { + ext := api.ExternalProxy{Server: sessionsStartProxyExtServer} + if cmd.Flags().Changed("proxy-external-username") { + ext.Username = &sessionsStartProxyExtUsername + } + if cmd.Flags().Changed("proxy-external-password") { + ext.Password = &sessionsStartProxyExtPassword } - proxyList := api.ApiSessionStartRequestProxies0{item} + var item api.ApiSessionStartRequest_Proxies_0_Item + if err := item.FromExternalProxy(ext); err != nil { + return fmt.Errorf("failed to create external proxy: %w", err) + } + proxyItems = append(proxyItems, item) + } + + if cmd.Flags().Changed("proxy-tailnet-client-id") { + tail := api.TailnetProxy{OauthClientId: sessionsStartProxyTailClientID} + if cmd.Flags().Changed("proxy-tailnet-client-secret") { + tail.OauthClientSecret = &sessionsStartProxyTailClientSecret + } + var item api.ApiSessionStartRequest_Proxies_0_Item + if err := item.FromTailnetProxy(tail); err != nil { + return fmt.Errorf("failed to create tailnet proxy: %w", err) + } + proxyItems = append(proxyItems, item) + } + + if len(proxyItems) > 0 { var proxies api.ApiSessionStartRequest_Proxies - if err := proxies.FromApiSessionStartRequestProxies0(proxyList); err != nil { + if err := proxies.FromApiSessionStartRequestProxies0(proxyItems); err != nil { return fmt.Errorf("failed to set proxies: %w", err) } body.Proxies = &proxies