cli/internal/orchclient/client.go
Alex Dunmow b5e39121d2 feat(cli): ninja plugin gallery commands (Phase 6)
Add `ninja plugin gallery list|add|remove|reorder|set-featured` for
author-side curation of a plugin/theme's registry screenshot gallery,
feeding the browse/detail surfaces shipped in Phases 4-5.

- Re-vendor plugin_registry.proto from orchestrator (adds
  PluginGalleryService) and regenerate the connect client.
- Wire the Gallery client into orchclient.Client.
- Target plugin defaults from plugin.mod with --scope/--name overrides;
  device-flow auth via resolveClient. list is a public read; add/remove/
  reorder/set-featured are RoleUser (scope/account membership enforced
  server-side). add derives content_type from the extension against the
  png/jpg/jpeg/webp allowlist and rejects >8MiB client-side.

Scope handling trims a leading @ client-side because ListScreenshots does
not trim server-side (GetPlugin does). Live-verified against the dev
orchestrator; go build + go test ./... + check-safety all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 12:35:09 +08:00

45 lines
1.5 KiB
Go

package orchclient
import (
"context"
"net/http"
"connectrpc.com/connect"
"git.dev.alexdunmow.com/block/cli/internal/api/orchestrator/v1/orchestratorv1connect"
)
type Client struct {
Host string
Token string
Auth orchestratorv1connect.PluginAuthServiceClient
Scope orchestratorv1connect.PluginScopeServiceClient
Reg orchestratorv1connect.PluginRegistryServiceClient
Pub orchestratorv1connect.PluginPublishServiceClient
Gallery orchestratorv1connect.PluginGalleryServiceClient
}
func New(host, token string) *Client {
httpClient := &http.Client{}
opts := []connect.ClientOption{
connect.WithInterceptors(bearerInterceptor(token)),
}
c := &Client{Host: host, Token: token}
c.Auth = orchestratorv1connect.NewPluginAuthServiceClient(httpClient, host, opts...)
c.Scope = orchestratorv1connect.NewPluginScopeServiceClient(httpClient, host, opts...)
c.Reg = orchestratorv1connect.NewPluginRegistryServiceClient(httpClient, host, opts...)
c.Pub = orchestratorv1connect.NewPluginPublishServiceClient(httpClient, host, opts...)
c.Gallery = orchestratorv1connect.NewPluginGalleryServiceClient(httpClient, host, opts...)
return c
}
func bearerInterceptor(token string) connect.Interceptor {
return connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc {
return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
if token != "" {
req.Header().Set("Authorization", "Bearer "+token)
}
return next(ctx, req)
}
})
}