| # CLI Proxy SDK Guide |
|
|
| The `sdk/cliproxy` module exposes the proxy as a reusable Go library so external programs can embed the routing, authentication, hot‑reload, and translation layers without depending on the CLI binary. |
|
|
| ## Install & Import |
|
|
| ```bash |
| go get github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy |
| ``` |
|
|
| ```go |
| import ( |
| "context" |
| "errors" |
| "time" |
| |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy" |
| ) |
| ``` |
|
|
| Note the `/v6` module path. |
|
|
| ## Minimal Embed |
|
|
| ```go |
| cfg, err := config.LoadConfig("config.yaml") |
| if err != nil { panic(err) } |
| |
| svc, err := cliproxy.NewBuilder(). |
| WithConfig(cfg). |
| WithConfigPath("config.yaml"). // absolute or working-dir relative |
| Build() |
| if err != nil { panic(err) } |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| if err := svc.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { |
| panic(err) |
| } |
| ``` |
|
|
| The service manages config/auth watching, background token refresh, and graceful shutdown. Cancel the context to stop it. |
|
|
| ## Server Options (middleware, routes, logs) |
|
|
| The server accepts options via `WithServerOptions`: |
|
|
| ```go |
| svc, _ := cliproxy.NewBuilder(). |
| WithConfig(cfg). |
| WithConfigPath("config.yaml"). |
| WithServerOptions( |
| // Add global middleware |
| cliproxy.WithMiddleware(func(c *gin.Context) { c.Header("X-Embed", "1"); c.Next() }), |
| // Tweak gin engine early (CORS, trusted proxies, etc.) |
| cliproxy.WithEngineConfigurator(func(e *gin.Engine) { e.ForwardedByClientIP = true }), |
| // Add your own routes after defaults |
| cliproxy.WithRouterConfigurator(func(e *gin.Engine, _ *handlers.BaseAPIHandler, _ *config.Config) { |
| e.GET("/healthz", func(c *gin.Context) { c.String(200, "ok") }) |
| }), |
| // Override request log writer/dir |
| cliproxy.WithRequestLoggerFactory(func(cfg *config.Config, cfgPath string) logging.RequestLogger { |
| return logging.NewFileRequestLogger(true, "logs", filepath.Dir(cfgPath)) |
| }), |
| ). |
| Build() |
| ``` |
|
|
| These options mirror the internals used by the CLI server. |
|
|
| ## Management API (when embedded) |
|
|
| - Management endpoints are mounted only when `remote-management.secret-key` is set in `config.yaml`. |
| - Remote access additionally requires `remote-management.allow-remote: true`. |
| - See MANAGEMENT_API.md for endpoints. Your embedded server exposes them under `/v0/management` on the configured port. |
| |
| ## Using the Core Auth Manager |
| |
| The service uses a core `auth.Manager` for selection, execution, and auto‑refresh. When embedding, you can provide your own manager to customize transports or hooks: |
| |
| ```go |
| core := coreauth.NewManager(coreauth.NewFileStore(cfg.AuthDir), nil, nil) |
| core.SetRoundTripperProvider(myRTProvider) // per‑auth *http.Transport |
| |
| svc, _ := cliproxy.NewBuilder(). |
| WithConfig(cfg). |
| WithConfigPath("config.yaml"). |
| WithCoreAuthManager(core). |
| Build() |
| ``` |
| |
| Implement a custom per‑auth transport: |
|
|
| ```go |
| type myRTProvider struct{} |
| func (myRTProvider) RoundTripperFor(a *coreauth.Auth) http.RoundTripper { |
| if a == nil || a.ProxyURL == "" { return nil } |
| u, _ := url.Parse(a.ProxyURL) |
| return &http.Transport{ Proxy: http.ProxyURL(u) } |
| } |
| ``` |
|
|
| Programmatic execution is available on the manager: |
|
|
| ```go |
| // Non‑streaming |
| resp, err := core.Execute(ctx, []string{"gemini"}, req, opts) |
| |
| // Streaming |
| chunks, err := core.ExecuteStream(ctx, []string{"gemini"}, req, opts) |
| for ch := range chunks { /* ... */ } |
| ``` |
|
|
| Note: Built‑in provider executors are wired automatically when you run the `Service`. If you want to use `Manager` stand‑alone without the HTTP server, you must register your own executors that implement `auth.ProviderExecutor`. |
|
|
| ## Custom Client Sources |
|
|
| Replace the default loaders if your creds live outside the local filesystem: |
|
|
| ```go |
| type memoryTokenProvider struct{} |
| func (p *memoryTokenProvider) Load(ctx context.Context, cfg *config.Config) (*cliproxy.TokenClientResult, error) { |
| // Populate from memory/remote store and return counts |
| return &cliproxy.TokenClientResult{}, nil |
| } |
| |
| svc, _ := cliproxy.NewBuilder(). |
| WithConfig(cfg). |
| WithConfigPath("config.yaml"). |
| WithTokenClientProvider(&memoryTokenProvider{}). |
| WithAPIKeyClientProvider(cliproxy.NewAPIKeyClientProvider()). |
| Build() |
| ``` |
|
|
| ## Hooks |
|
|
| Observe lifecycle without patching internals: |
|
|
| ```go |
| hooks := cliproxy.Hooks{ |
| OnBeforeStart: func(cfg *config.Config) { log.Infof("starting on :%d", cfg.Port) }, |
| OnAfterStart: func(s *cliproxy.Service) { log.Info("ready") }, |
| } |
| svc, _ := cliproxy.NewBuilder().WithConfig(cfg).WithConfigPath("config.yaml").WithHooks(hooks).Build() |
| ``` |
|
|
| ## Shutdown |
|
|
| `Run` defers `Shutdown`, so cancelling the parent context is enough. To stop manually: |
|
|
| ```go |
| ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
| defer cancel() |
| _ = svc.Shutdown(ctx) |
| ``` |
|
|
| ## Notes |
|
|
| - Hot reload: changes to `config.yaml` and `auths/` are picked up automatically. |
| - Request logging can be toggled at runtime via the Management API. |
| - Gemini Web features (`gemini-web.*`) are honored in the embedded server. |
|
|