feat(cli): ninja CLI with login, plugin init/publish/status

Cobra scaffold, credentials store, Connect client, device-flow login,
whoami/logout, plugin init (creates + adds git remote), publish (tag + push
+ registry RPC), and status (list scopes/plugins). Proto copied from
orchestrator with buf codegen for client stubs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-06-01 23:55:01 +08:00
parent 7ff326ef25
commit 7f4bce79c9
14 changed files with 3455 additions and 7 deletions

15
buf.gen.yaml Normal file
View File

@ -0,0 +1,15 @@
version: v2
managed:
enabled: true
override:
- file_option: go_package_prefix
value: git.dev.alexdunmow.com/block/core/internal/api
plugins:
- local: protoc-gen-go
out: internal/api
opt:
- paths=source_relative
- local: protoc-gen-connect-go
out: internal/api
opt:
- paths=source_relative

12
buf.yaml Normal file
View File

@ -0,0 +1,12 @@
version: v2
modules:
- path: proto
lint:
use:
- STANDARD
except:
- PACKAGE_SAME_GO_PACKAGE
- RPC_REQUEST_RESPONSE_UNIQUE
breaking:
use:
- FILE

114
cmd/ninja/cmd/login.go Normal file
View File

@ -0,0 +1,114 @@
package cmd
import (
"context"
"fmt"
"time"
"connectrpc.com/connect"
"github.com/spf13/cobra"
"git.dev.alexdunmow.com/block/core/cmd/ninja/internal/creds"
"git.dev.alexdunmow.com/block/core/cmd/ninja/internal/orchclient"
v1 "git.dev.alexdunmow.com/block/core/internal/api/orchestrator/v1"
)
func newLoginCmd() *cobra.Command {
var host string
cmd := &cobra.Command{
Use: "login",
Short: "Authenticate against the orchestrator using device flow",
RunE: func(c *cobra.Command, _ []string) error {
if host == "" {
host, _ = c.Flags().GetString("host")
}
if host == "" {
host = "https://my.blockninjacms.com"
}
cli := orchclient.New(host, "")
ctx := context.Background()
start, err := cli.Auth.StartDevice(ctx, connect.NewRequest(&v1.StartDeviceRequest{
Scopes: []string{"plugin:read", "plugin:publish", "scope:admin"},
}))
if err != nil {
return fmt.Errorf("start device: %w", err)
}
fmt.Printf("Visit %s and enter code: %s\n", start.Msg.VerificationUri, start.Msg.UserCode)
interval := time.Duration(start.Msg.IntervalSeconds) * time.Second
deadline := time.Now().Add(time.Duration(start.Msg.ExpiresInSeconds) * time.Second)
for time.Now().Before(deadline) {
time.Sleep(interval)
poll, err := cli.Auth.PollDevice(ctx, connect.NewRequest(&v1.PollDeviceRequest{DeviceCode: start.Msg.DeviceCode}))
if err != nil {
return err
}
switch poll.Msg.Status {
case "pending":
continue
case "approved":
cr, err := creds.Load()
if err != nil {
return err
}
cr.DefaultHost = host
if cr.Hosts == nil {
cr.Hosts = map[string]creds.HostCreds{}
}
cr.Hosts[host] = creds.HostCreds{Token: poll.Msg.AccessToken}
if err := cr.Save(); err != nil {
return err
}
fmt.Println("Logged in.")
return nil
case "expired":
return fmt.Errorf("device code expired; try again")
}
}
return fmt.Errorf("login timed out")
},
}
cmd.Flags().StringVar(&host, "host", "", "Orchestrator base URL")
return cmd
}
func newWhoamiCmd() *cobra.Command {
return &cobra.Command{
Use: "whoami",
Short: "Show the currently logged-in user",
RunE: func(c *cobra.Command, _ []string) error {
host, _ := c.Flags().GetString("host")
cr, err := creds.Load()
if err != nil {
return err
}
resolvedHost, hc, err := cr.Resolve(host)
if err != nil {
return err
}
cli := orchclient.New(resolvedHost, hc.Token)
r, err := cli.Auth.Whoami(context.Background(), connect.NewRequest(&v1.WhoamiRequest{}))
if err != nil {
return err
}
fmt.Printf("%s <%s> at %s\n", r.Msg.DisplayName, r.Msg.Email, resolvedHost)
return nil
},
}
}
func newLogoutCmd() *cobra.Command {
return &cobra.Command{
Use: "logout",
Short: "Remove stored credentials",
RunE: func(c *cobra.Command, _ []string) error {
host, _ := c.Flags().GetString("host")
cr, err := creds.Load()
if err != nil {
return err
}
resolvedHost, _, _ := cr.Resolve(host)
delete(cr.Hosts, resolvedHost)
return cr.Save()
},
}
}

236
cmd/ninja/cmd/plugin.go Normal file
View File

@ -0,0 +1,236 @@
package cmd
import (
"context"
"fmt"
"os"
"os/exec"
"strings"
"connectrpc.com/connect"
"github.com/spf13/cobra"
core "git.dev.alexdunmow.com/block/core/plugin"
"git.dev.alexdunmow.com/block/core/cmd/ninja/internal/creds"
"git.dev.alexdunmow.com/block/core/cmd/ninja/internal/orchclient"
v1 "git.dev.alexdunmow.com/block/core/internal/api/orchestrator/v1"
)
func newPluginCmd() *cobra.Command {
c := &cobra.Command{Use: "plugin", Short: "Manage BlockNinja plugins"}
c.AddCommand(newPluginInitCmd(), newPluginPublishCmd(), newPluginStatusCmd())
return c
}
func newPluginInitCmd() *cobra.Command {
var scope, name string
cmd := &cobra.Command{
Use: "init",
Short: "Create a plugin in the registry and add a git remote",
RunE: func(c *cobra.Command, _ []string) error {
host, _ := c.Flags().GetString("host")
cr, err := creds.Load()
if err != nil {
return err
}
resolvedHost, hc, err := cr.Resolve(host)
if err != nil {
return err
}
cli := orchclient.New(resolvedHost, hc.Token)
scope = strings.TrimPrefix(scope, "@")
if scope == "" || name == "" {
return fmt.Errorf("usage: ninja plugin init --scope ACME --name foo")
}
ctx := context.Background()
resp, err := cli.Reg.CreatePlugin(ctx, connect.NewRequest(&v1.CreatePluginRequest{
ScopeSlug: scope, Name: name, Description: "",
}))
if err != nil {
return err
}
fmt.Printf("Created @%s/%s\n", scope, name)
fmt.Printf(" Git remote: %s\n", resp.Msg.GitRemoteUrl)
if _, err := os.Stat(".git"); err == nil {
_ = runCmd("git", "remote", "remove", "ninja")
if err := runCmd("git", "remote", "add", "ninja", resp.Msg.GitRemoteUrl); err != nil {
return fmt.Errorf("git remote add: %w", err)
}
fmt.Println("Added git remote 'ninja'")
} else {
fmt.Println("Not in a git repo - skipped adding remote")
}
if err := upsertPluginMod(scope, name); err != nil {
return err
}
fmt.Println("plugin.mod updated")
return nil
},
}
cmd.Flags().StringVar(&scope, "scope", "", "Scope slug (with or without @)")
cmd.Flags().StringVar(&name, "name", "", "Plugin name")
return cmd
}
func newPluginPublishCmd() *cobra.Command {
var channel string
var allowDirty bool
cmd := &cobra.Command{
Use: "publish",
Short: "Push the current commit as a new version and notify the registry",
RunE: func(c *cobra.Command, _ []string) error {
host, _ := c.Flags().GetString("host")
cr, err := creds.Load()
if err != nil {
return err
}
resolvedHost, hc, err := cr.Resolve(host)
if err != nil {
return err
}
cli := orchclient.New(resolvedHost, hc.Token)
modBytes, err := os.ReadFile("plugin.mod")
if err != nil {
return fmt.Errorf("read plugin.mod: %w", err)
}
mod, err := core.ParseModFull(modBytes)
if err != nil {
return err
}
if mod.Plugin.Scope == "" || mod.Plugin.Name == "" || mod.Plugin.Version == "" {
return fmt.Errorf("plugin.mod must have scope, name, and version")
}
if !allowDirty {
out, _ := exec.Command("git", "status", "--porcelain").Output()
if len(strings.TrimSpace(string(out))) > 0 {
return fmt.Errorf("working tree dirty; commit or pass --allow-dirty")
}
}
tag := "v" + mod.Plugin.Version
if err := runCmd("git", "tag", "-a", tag, "-m", tag); err != nil {
fmt.Fprintf(os.Stderr, "tag %s already exists or could not be created (%v); attempting push\n", tag, err)
}
if err := runCmd("git", "push", "ninja", tag); err != nil {
return fmt.Errorf("git push ninja %s: %w", tag, err)
}
fmt.Printf("Pushed %s to ninja remote\n", tag)
ctx := context.Background()
pr, err := cli.Reg.GetPlugin(ctx, connect.NewRequest(&v1.GetPluginRequest{
ScopeSlug: mod.Plugin.Scope, Name: mod.Plugin.Name,
}))
if err != nil {
return fmt.Errorf("get plugin: %w", err)
}
readme, _ := os.ReadFile("README.md")
changelog, _ := os.ReadFile("CHANGELOG.md")
pubResp, err := cli.Pub.PublishVersion(ctx, connect.NewRequest(&v1.PublishVersionRequest{
PluginId: pr.Msg.Plugin.Id,
GitRef: "refs/tags/" + tag,
Channel: channel,
ReadmeMd: string(readme),
ChangelogMd: string(changelog),
}))
if err != nil {
return fmt.Errorf("publish: %w", err)
}
fmt.Printf("Published version_id=%s\n", pubResp.Msg.Version.Id)
for _, w := range pubResp.Msg.Warnings {
fmt.Printf(" warning: %s\n", w)
}
fmt.Printf(" Archive: %s\n", pubResp.Msg.ArchiveUrl)
return nil
},
}
cmd.Flags().StringVar(&channel, "channel", "latest", "Channel to point at this version")
cmd.Flags().BoolVar(&allowDirty, "allow-dirty", false, "Skip clean-working-tree check")
return cmd
}
func newPluginStatusCmd() *cobra.Command {
return &cobra.Command{
Use: "status",
Short: "List owned scopes and their plugins",
RunE: func(c *cobra.Command, _ []string) error {
host, _ := c.Flags().GetString("host")
cr, err := creds.Load()
if err != nil {
return err
}
resolvedHost, hc, err := cr.Resolve(host)
if err != nil {
return err
}
cli := orchclient.New(resolvedHost, hc.Token)
ctx := context.Background()
scopes, err := cli.Scope.ListMyScopes(ctx, connect.NewRequest(&v1.ListMyScopesRequest{}))
if err != nil {
return err
}
if len(scopes.Msg.Scopes) == 0 {
fmt.Println("No scopes yet. Create one in the orchestrator UI or via a CreateScope call.")
return nil
}
for _, s := range scopes.Msg.Scopes {
fmt.Printf("@%s - %s\n", s.Slug, s.DisplayName)
gs, err := cli.Scope.GetScope(ctx, connect.NewRequest(&v1.GetScopeRequest{Slug: s.Slug}))
if err != nil {
fmt.Printf(" (error: %v)\n", err)
continue
}
for _, p := range gs.Msg.Plugins {
fmt.Printf(" @%s/%s [%s]\n", s.Slug, p.Name, p.Visibility)
}
}
return nil
},
}
}
func runCmd(name string, args ...string) error {
c := exec.Command(name, args...)
c.Stderr = os.Stderr
return c.Run()
}
func upsertPluginMod(scope, name string) error {
const file = "plugin.mod"
existing, _ := os.ReadFile(file)
mod, _ := core.ParseModFull(existing)
if mod == nil {
mod = &core.ModFile{}
}
if mod.Plugin.Version == "" {
mod.Plugin.Version = "0.1.0"
}
mod.Plugin.Scope = scope
mod.Plugin.Name = name
return writeMod(file, mod)
}
func writeMod(path string, m *core.ModFile) error {
var b strings.Builder
b.WriteString("[plugin]\n")
b.WriteString(fmt.Sprintf("name = %q\n", m.Plugin.Name))
b.WriteString(fmt.Sprintf("scope = %q\n", m.Plugin.Scope))
b.WriteString(fmt.Sprintf("version = %q\n", m.Plugin.Version))
if m.Compatibility != nil {
b.WriteString("\n[compatibility]\n")
b.WriteString(fmt.Sprintf("block_core = %q\n", m.Compatibility.BlockCore))
}
for _, r := range m.Requires {
b.WriteString("\n[[requires]]\n")
b.WriteString(fmt.Sprintf("name = %q\n", r.Name))
b.WriteString(fmt.Sprintf("version = %q\n", r.Version))
}
return os.WriteFile(path, []byte(b.String()), 0o644)
}

18
cmd/ninja/cmd/root.go Normal file
View File

@ -0,0 +1,18 @@
package cmd
import "github.com/spf13/cobra"
func NewRoot() *cobra.Command {
root := &cobra.Command{
Use: "ninja",
Short: "BlockNinja developer CLI",
Long: "ninja is the developer-facing CLI for BlockNinja. First subcommand group: plugin.",
}
root.PersistentFlags().String("host", "", "Orchestrator base URL (default: from credentials or https://my.blockninjacms.com)")
root.AddCommand(newVersionCmd())
root.AddCommand(newLoginCmd())
root.AddCommand(newLogoutCmd())
root.AddCommand(newWhoamiCmd())
root.AddCommand(newPluginCmd())
return root
}

19
cmd/ninja/cmd/version.go Normal file
View File

@ -0,0 +1,19 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var Version = "dev"
func newVersionCmd() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Print ninja version",
Run: func(_ *cobra.Command, _ []string) {
fmt.Println("ninja", Version)
},
}
}

View File

@ -0,0 +1,77 @@
package creds
import (
"encoding/json"
"errors"
"os"
"path/filepath"
)
type Credentials struct {
DefaultHost string `json:"default_host"`
Hosts map[string]HostCreds `json:"hosts"`
}
type HostCreds struct {
Token string `json:"token"`
User string `json:"user,omitempty"`
}
func filePath() (string, error) {
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "ninja", "credentials.json"), nil
}
func Load() (*Credentials, error) {
p, err := filePath()
if err != nil {
return nil, err
}
b, err := os.ReadFile(p)
if errors.Is(err, os.ErrNotExist) {
return &Credentials{Hosts: map[string]HostCreds{}}, nil
}
if err != nil {
return nil, err
}
c := &Credentials{Hosts: map[string]HostCreds{}}
if err := json.Unmarshal(b, c); err != nil {
return nil, err
}
return c, nil
}
func (c *Credentials) Save() error {
p, err := filePath()
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(p), 0o700); err != nil {
return err
}
b, err := json.MarshalIndent(c, "", " ")
if err != nil {
return err
}
return os.WriteFile(p, b, 0o600)
}
func (c *Credentials) Resolve(host string) (string, HostCreds, error) {
if host == "" {
host = c.DefaultHost
}
if host == "" {
host = "https://my.blockninjacms.com"
}
if t := os.Getenv("NINJA_TOKEN"); t != "" {
return host, HostCreds{Token: t}, nil
}
hc, ok := c.Hosts[host]
if !ok || hc.Token == "" {
return host, HostCreds{}, errors.New("not logged in; run `ninja login`")
}
return host, hc, nil
}

View File

@ -0,0 +1,42 @@
package orchclient
import (
"context"
"net/http"
"connectrpc.com/connect"
"git.dev.alexdunmow.com/block/core/internal/api/orchestrator/v1/orchestratorv1connect"
)
type Client struct {
Host string
Token string
Auth orchestratorv1connect.PluginAuthServiceClient
Scope orchestratorv1connect.PluginScopeServiceClient
Reg orchestratorv1connect.PluginRegistryServiceClient
Pub orchestratorv1connect.PluginPublishServiceClient
}
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...)
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)
}
})
}

15
cmd/ninja/main.go Normal file
View File

@ -0,0 +1,15 @@
package main
import (
"fmt"
"os"
"git.dev.alexdunmow.com/block/core/cmd/ninja/cmd"
)
func main() {
if err := cmd.NewRoot().Execute(); err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
}

8
go.mod
View File

@ -3,18 +3,20 @@ module git.dev.alexdunmow.com/block/core
go 1.26 go 1.26
require ( require (
connectrpc.com/connect v1.19.2 connectrpc.com/connect v1.20.0
github.com/BurntSushi/toml v1.6.0 github.com/BurntSushi/toml v1.6.0
github.com/a-h/templ v0.3.1001 github.com/a-h/templ v0.3.1001
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.9.2 github.com/jackc/pgx/v5 v5.9.2
github.com/spf13/cobra v1.10.2
golang.org/x/mod v0.34.0 golang.org/x/mod v0.34.0
google.golang.org/protobuf v1.36.11
) )
require ( require (
github.com/google/go-cmp v0.7.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/spf13/pflag v1.0.9 // indirect
golang.org/x/text v0.36.0 // indirect golang.org/x/text v0.36.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
) )

17
go.sum
View File

@ -1,9 +1,10 @@
connectrpc.com/connect v1.19.2 h1:McQ83FGdzL+t60peksi0gXC7MQ/iLKgLduAnThbM0mo= connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ=
connectrpc.com/connect v1.19.2/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/a-h/templ v0.3.1001 h1:yHDTgexACdJttyiyamcTHXr2QkIeVF1MukLy44EAhMY= github.com/a-h/templ v0.3.1001 h1:yHDTgexACdJttyiyamcTHXr2QkIeVF1MukLy44EAhMY=
github.com/a-h/templ v0.3.1001/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= github.com/a-h/templ v0.3.1001/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -11,6 +12,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@ -21,19 +24,25 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -0,0 +1,597 @@
// Code generated by protoc-gen-connect-go. DO NOT EDIT.
//
// Source: orchestrator/v1/plugin_registry.proto
package orchestratorv1connect
import (
connect "connectrpc.com/connect"
context "context"
errors "errors"
v1 "git.dev.alexdunmow.com/block/core/internal/api/orchestrator/v1"
http "net/http"
strings "strings"
)
// This is a compile-time assertion to ensure that this generated file and the connect package are
// compatible. If you get a compiler error that this constant is not defined, this code was
// generated with a version of connect newer than the one compiled into your binary. You can fix the
// problem by either regenerating this code with an older version of connect or updating the connect
// version compiled into your binary.
const _ = connect.IsAtLeastVersion1_13_0
const (
// PluginScopeServiceName is the fully-qualified name of the PluginScopeService service.
PluginScopeServiceName = "orchestrator.v1.PluginScopeService"
// PluginRegistryServiceName is the fully-qualified name of the PluginRegistryService service.
PluginRegistryServiceName = "orchestrator.v1.PluginRegistryService"
// PluginPublishServiceName is the fully-qualified name of the PluginPublishService service.
PluginPublishServiceName = "orchestrator.v1.PluginPublishService"
// PluginAuthServiceName is the fully-qualified name of the PluginAuthService service.
PluginAuthServiceName = "orchestrator.v1.PluginAuthService"
)
// These constants are the fully-qualified names of the RPCs defined in this package. They're
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
//
// Note that these are different from the fully-qualified method names used by
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
// period.
const (
// PluginScopeServiceCreateScopeProcedure is the fully-qualified name of the PluginScopeService's
// CreateScope RPC.
PluginScopeServiceCreateScopeProcedure = "/orchestrator.v1.PluginScopeService/CreateScope"
// PluginScopeServiceListMyScopesProcedure is the fully-qualified name of the PluginScopeService's
// ListMyScopes RPC.
PluginScopeServiceListMyScopesProcedure = "/orchestrator.v1.PluginScopeService/ListMyScopes"
// PluginScopeServiceGetScopeProcedure is the fully-qualified name of the PluginScopeService's
// GetScope RPC.
PluginScopeServiceGetScopeProcedure = "/orchestrator.v1.PluginScopeService/GetScope"
// PluginRegistryServiceCreatePluginProcedure is the fully-qualified name of the
// PluginRegistryService's CreatePlugin RPC.
PluginRegistryServiceCreatePluginProcedure = "/orchestrator.v1.PluginRegistryService/CreatePlugin"
// PluginRegistryServiceGetPluginProcedure is the fully-qualified name of the
// PluginRegistryService's GetPlugin RPC.
PluginRegistryServiceGetPluginProcedure = "/orchestrator.v1.PluginRegistryService/GetPlugin"
// PluginRegistryServiceListPluginsProcedure is the fully-qualified name of the
// PluginRegistryService's ListPlugins RPC.
PluginRegistryServiceListPluginsProcedure = "/orchestrator.v1.PluginRegistryService/ListPlugins"
// PluginRegistryServiceGetVersionProcedure is the fully-qualified name of the
// PluginRegistryService's GetVersion RPC.
PluginRegistryServiceGetVersionProcedure = "/orchestrator.v1.PluginRegistryService/GetVersion"
// PluginRegistryServiceResolveInstallProcedure is the fully-qualified name of the
// PluginRegistryService's ResolveInstall RPC.
PluginRegistryServiceResolveInstallProcedure = "/orchestrator.v1.PluginRegistryService/ResolveInstall"
// PluginPublishServicePublishVersionProcedure is the fully-qualified name of the
// PluginPublishService's PublishVersion RPC.
PluginPublishServicePublishVersionProcedure = "/orchestrator.v1.PluginPublishService/PublishVersion"
// PluginAuthServiceStartDeviceProcedure is the fully-qualified name of the PluginAuthService's
// StartDevice RPC.
PluginAuthServiceStartDeviceProcedure = "/orchestrator.v1.PluginAuthService/StartDevice"
// PluginAuthServicePollDeviceProcedure is the fully-qualified name of the PluginAuthService's
// PollDevice RPC.
PluginAuthServicePollDeviceProcedure = "/orchestrator.v1.PluginAuthService/PollDevice"
// PluginAuthServiceApproveDeviceProcedure is the fully-qualified name of the PluginAuthService's
// ApproveDevice RPC.
PluginAuthServiceApproveDeviceProcedure = "/orchestrator.v1.PluginAuthService/ApproveDevice"
// PluginAuthServiceWhoamiProcedure is the fully-qualified name of the PluginAuthService's Whoami
// RPC.
PluginAuthServiceWhoamiProcedure = "/orchestrator.v1.PluginAuthService/Whoami"
)
// PluginScopeServiceClient is a client for the orchestrator.v1.PluginScopeService service.
type PluginScopeServiceClient interface {
CreateScope(context.Context, *connect.Request[v1.CreateScopeRequest]) (*connect.Response[v1.CreateScopeResponse], error)
ListMyScopes(context.Context, *connect.Request[v1.ListMyScopesRequest]) (*connect.Response[v1.ListMyScopesResponse], error)
GetScope(context.Context, *connect.Request[v1.GetScopeRequest]) (*connect.Response[v1.GetScopeResponse], error)
}
// NewPluginScopeServiceClient constructs a client for the orchestrator.v1.PluginScopeService
// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for
// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply
// the connect.WithGRPC() or connect.WithGRPCWeb() options.
//
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
// http://api.acme.com or https://acme.com/grpc).
func NewPluginScopeServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) PluginScopeServiceClient {
baseURL = strings.TrimRight(baseURL, "/")
pluginScopeServiceMethods := v1.File_orchestrator_v1_plugin_registry_proto.Services().ByName("PluginScopeService").Methods()
return &pluginScopeServiceClient{
createScope: connect.NewClient[v1.CreateScopeRequest, v1.CreateScopeResponse](
httpClient,
baseURL+PluginScopeServiceCreateScopeProcedure,
connect.WithSchema(pluginScopeServiceMethods.ByName("CreateScope")),
connect.WithClientOptions(opts...),
),
listMyScopes: connect.NewClient[v1.ListMyScopesRequest, v1.ListMyScopesResponse](
httpClient,
baseURL+PluginScopeServiceListMyScopesProcedure,
connect.WithSchema(pluginScopeServiceMethods.ByName("ListMyScopes")),
connect.WithClientOptions(opts...),
),
getScope: connect.NewClient[v1.GetScopeRequest, v1.GetScopeResponse](
httpClient,
baseURL+PluginScopeServiceGetScopeProcedure,
connect.WithSchema(pluginScopeServiceMethods.ByName("GetScope")),
connect.WithClientOptions(opts...),
),
}
}
// pluginScopeServiceClient implements PluginScopeServiceClient.
type pluginScopeServiceClient struct {
createScope *connect.Client[v1.CreateScopeRequest, v1.CreateScopeResponse]
listMyScopes *connect.Client[v1.ListMyScopesRequest, v1.ListMyScopesResponse]
getScope *connect.Client[v1.GetScopeRequest, v1.GetScopeResponse]
}
// CreateScope calls orchestrator.v1.PluginScopeService.CreateScope.
func (c *pluginScopeServiceClient) CreateScope(ctx context.Context, req *connect.Request[v1.CreateScopeRequest]) (*connect.Response[v1.CreateScopeResponse], error) {
return c.createScope.CallUnary(ctx, req)
}
// ListMyScopes calls orchestrator.v1.PluginScopeService.ListMyScopes.
func (c *pluginScopeServiceClient) ListMyScopes(ctx context.Context, req *connect.Request[v1.ListMyScopesRequest]) (*connect.Response[v1.ListMyScopesResponse], error) {
return c.listMyScopes.CallUnary(ctx, req)
}
// GetScope calls orchestrator.v1.PluginScopeService.GetScope.
func (c *pluginScopeServiceClient) GetScope(ctx context.Context, req *connect.Request[v1.GetScopeRequest]) (*connect.Response[v1.GetScopeResponse], error) {
return c.getScope.CallUnary(ctx, req)
}
// PluginScopeServiceHandler is an implementation of the orchestrator.v1.PluginScopeService service.
type PluginScopeServiceHandler interface {
CreateScope(context.Context, *connect.Request[v1.CreateScopeRequest]) (*connect.Response[v1.CreateScopeResponse], error)
ListMyScopes(context.Context, *connect.Request[v1.ListMyScopesRequest]) (*connect.Response[v1.ListMyScopesResponse], error)
GetScope(context.Context, *connect.Request[v1.GetScopeRequest]) (*connect.Response[v1.GetScopeResponse], error)
}
// NewPluginScopeServiceHandler builds an HTTP handler from the service implementation. It returns
// the path on which to mount the handler and the handler itself.
//
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression.
func NewPluginScopeServiceHandler(svc PluginScopeServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
pluginScopeServiceMethods := v1.File_orchestrator_v1_plugin_registry_proto.Services().ByName("PluginScopeService").Methods()
pluginScopeServiceCreateScopeHandler := connect.NewUnaryHandler(
PluginScopeServiceCreateScopeProcedure,
svc.CreateScope,
connect.WithSchema(pluginScopeServiceMethods.ByName("CreateScope")),
connect.WithHandlerOptions(opts...),
)
pluginScopeServiceListMyScopesHandler := connect.NewUnaryHandler(
PluginScopeServiceListMyScopesProcedure,
svc.ListMyScopes,
connect.WithSchema(pluginScopeServiceMethods.ByName("ListMyScopes")),
connect.WithHandlerOptions(opts...),
)
pluginScopeServiceGetScopeHandler := connect.NewUnaryHandler(
PluginScopeServiceGetScopeProcedure,
svc.GetScope,
connect.WithSchema(pluginScopeServiceMethods.ByName("GetScope")),
connect.WithHandlerOptions(opts...),
)
return "/orchestrator.v1.PluginScopeService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case PluginScopeServiceCreateScopeProcedure:
pluginScopeServiceCreateScopeHandler.ServeHTTP(w, r)
case PluginScopeServiceListMyScopesProcedure:
pluginScopeServiceListMyScopesHandler.ServeHTTP(w, r)
case PluginScopeServiceGetScopeProcedure:
pluginScopeServiceGetScopeHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
})
}
// UnimplementedPluginScopeServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedPluginScopeServiceHandler struct{}
func (UnimplementedPluginScopeServiceHandler) CreateScope(context.Context, *connect.Request[v1.CreateScopeRequest]) (*connect.Response[v1.CreateScopeResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginScopeService.CreateScope is not implemented"))
}
func (UnimplementedPluginScopeServiceHandler) ListMyScopes(context.Context, *connect.Request[v1.ListMyScopesRequest]) (*connect.Response[v1.ListMyScopesResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginScopeService.ListMyScopes is not implemented"))
}
func (UnimplementedPluginScopeServiceHandler) GetScope(context.Context, *connect.Request[v1.GetScopeRequest]) (*connect.Response[v1.GetScopeResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginScopeService.GetScope is not implemented"))
}
// PluginRegistryServiceClient is a client for the orchestrator.v1.PluginRegistryService service.
type PluginRegistryServiceClient interface {
CreatePlugin(context.Context, *connect.Request[v1.CreatePluginRequest]) (*connect.Response[v1.CreatePluginResponse], error)
GetPlugin(context.Context, *connect.Request[v1.GetPluginRequest]) (*connect.Response[v1.GetPluginResponse], error)
ListPlugins(context.Context, *connect.Request[v1.ListPluginsRequest]) (*connect.Response[v1.ListPluginsResponse], error)
GetVersion(context.Context, *connect.Request[v1.GetVersionRequest]) (*connect.Response[v1.GetVersionResponse], error)
ResolveInstall(context.Context, *connect.Request[v1.ResolveInstallRequest]) (*connect.Response[v1.ResolveInstallResponse], error)
}
// NewPluginRegistryServiceClient constructs a client for the orchestrator.v1.PluginRegistryService
// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for
// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply
// the connect.WithGRPC() or connect.WithGRPCWeb() options.
//
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
// http://api.acme.com or https://acme.com/grpc).
func NewPluginRegistryServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) PluginRegistryServiceClient {
baseURL = strings.TrimRight(baseURL, "/")
pluginRegistryServiceMethods := v1.File_orchestrator_v1_plugin_registry_proto.Services().ByName("PluginRegistryService").Methods()
return &pluginRegistryServiceClient{
createPlugin: connect.NewClient[v1.CreatePluginRequest, v1.CreatePluginResponse](
httpClient,
baseURL+PluginRegistryServiceCreatePluginProcedure,
connect.WithSchema(pluginRegistryServiceMethods.ByName("CreatePlugin")),
connect.WithClientOptions(opts...),
),
getPlugin: connect.NewClient[v1.GetPluginRequest, v1.GetPluginResponse](
httpClient,
baseURL+PluginRegistryServiceGetPluginProcedure,
connect.WithSchema(pluginRegistryServiceMethods.ByName("GetPlugin")),
connect.WithClientOptions(opts...),
),
listPlugins: connect.NewClient[v1.ListPluginsRequest, v1.ListPluginsResponse](
httpClient,
baseURL+PluginRegistryServiceListPluginsProcedure,
connect.WithSchema(pluginRegistryServiceMethods.ByName("ListPlugins")),
connect.WithClientOptions(opts...),
),
getVersion: connect.NewClient[v1.GetVersionRequest, v1.GetVersionResponse](
httpClient,
baseURL+PluginRegistryServiceGetVersionProcedure,
connect.WithSchema(pluginRegistryServiceMethods.ByName("GetVersion")),
connect.WithClientOptions(opts...),
),
resolveInstall: connect.NewClient[v1.ResolveInstallRequest, v1.ResolveInstallResponse](
httpClient,
baseURL+PluginRegistryServiceResolveInstallProcedure,
connect.WithSchema(pluginRegistryServiceMethods.ByName("ResolveInstall")),
connect.WithClientOptions(opts...),
),
}
}
// pluginRegistryServiceClient implements PluginRegistryServiceClient.
type pluginRegistryServiceClient struct {
createPlugin *connect.Client[v1.CreatePluginRequest, v1.CreatePluginResponse]
getPlugin *connect.Client[v1.GetPluginRequest, v1.GetPluginResponse]
listPlugins *connect.Client[v1.ListPluginsRequest, v1.ListPluginsResponse]
getVersion *connect.Client[v1.GetVersionRequest, v1.GetVersionResponse]
resolveInstall *connect.Client[v1.ResolveInstallRequest, v1.ResolveInstallResponse]
}
// CreatePlugin calls orchestrator.v1.PluginRegistryService.CreatePlugin.
func (c *pluginRegistryServiceClient) CreatePlugin(ctx context.Context, req *connect.Request[v1.CreatePluginRequest]) (*connect.Response[v1.CreatePluginResponse], error) {
return c.createPlugin.CallUnary(ctx, req)
}
// GetPlugin calls orchestrator.v1.PluginRegistryService.GetPlugin.
func (c *pluginRegistryServiceClient) GetPlugin(ctx context.Context, req *connect.Request[v1.GetPluginRequest]) (*connect.Response[v1.GetPluginResponse], error) {
return c.getPlugin.CallUnary(ctx, req)
}
// ListPlugins calls orchestrator.v1.PluginRegistryService.ListPlugins.
func (c *pluginRegistryServiceClient) ListPlugins(ctx context.Context, req *connect.Request[v1.ListPluginsRequest]) (*connect.Response[v1.ListPluginsResponse], error) {
return c.listPlugins.CallUnary(ctx, req)
}
// GetVersion calls orchestrator.v1.PluginRegistryService.GetVersion.
func (c *pluginRegistryServiceClient) GetVersion(ctx context.Context, req *connect.Request[v1.GetVersionRequest]) (*connect.Response[v1.GetVersionResponse], error) {
return c.getVersion.CallUnary(ctx, req)
}
// ResolveInstall calls orchestrator.v1.PluginRegistryService.ResolveInstall.
func (c *pluginRegistryServiceClient) ResolveInstall(ctx context.Context, req *connect.Request[v1.ResolveInstallRequest]) (*connect.Response[v1.ResolveInstallResponse], error) {
return c.resolveInstall.CallUnary(ctx, req)
}
// PluginRegistryServiceHandler is an implementation of the orchestrator.v1.PluginRegistryService
// service.
type PluginRegistryServiceHandler interface {
CreatePlugin(context.Context, *connect.Request[v1.CreatePluginRequest]) (*connect.Response[v1.CreatePluginResponse], error)
GetPlugin(context.Context, *connect.Request[v1.GetPluginRequest]) (*connect.Response[v1.GetPluginResponse], error)
ListPlugins(context.Context, *connect.Request[v1.ListPluginsRequest]) (*connect.Response[v1.ListPluginsResponse], error)
GetVersion(context.Context, *connect.Request[v1.GetVersionRequest]) (*connect.Response[v1.GetVersionResponse], error)
ResolveInstall(context.Context, *connect.Request[v1.ResolveInstallRequest]) (*connect.Response[v1.ResolveInstallResponse], error)
}
// NewPluginRegistryServiceHandler builds an HTTP handler from the service implementation. It
// returns the path on which to mount the handler and the handler itself.
//
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression.
func NewPluginRegistryServiceHandler(svc PluginRegistryServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
pluginRegistryServiceMethods := v1.File_orchestrator_v1_plugin_registry_proto.Services().ByName("PluginRegistryService").Methods()
pluginRegistryServiceCreatePluginHandler := connect.NewUnaryHandler(
PluginRegistryServiceCreatePluginProcedure,
svc.CreatePlugin,
connect.WithSchema(pluginRegistryServiceMethods.ByName("CreatePlugin")),
connect.WithHandlerOptions(opts...),
)
pluginRegistryServiceGetPluginHandler := connect.NewUnaryHandler(
PluginRegistryServiceGetPluginProcedure,
svc.GetPlugin,
connect.WithSchema(pluginRegistryServiceMethods.ByName("GetPlugin")),
connect.WithHandlerOptions(opts...),
)
pluginRegistryServiceListPluginsHandler := connect.NewUnaryHandler(
PluginRegistryServiceListPluginsProcedure,
svc.ListPlugins,
connect.WithSchema(pluginRegistryServiceMethods.ByName("ListPlugins")),
connect.WithHandlerOptions(opts...),
)
pluginRegistryServiceGetVersionHandler := connect.NewUnaryHandler(
PluginRegistryServiceGetVersionProcedure,
svc.GetVersion,
connect.WithSchema(pluginRegistryServiceMethods.ByName("GetVersion")),
connect.WithHandlerOptions(opts...),
)
pluginRegistryServiceResolveInstallHandler := connect.NewUnaryHandler(
PluginRegistryServiceResolveInstallProcedure,
svc.ResolveInstall,
connect.WithSchema(pluginRegistryServiceMethods.ByName("ResolveInstall")),
connect.WithHandlerOptions(opts...),
)
return "/orchestrator.v1.PluginRegistryService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case PluginRegistryServiceCreatePluginProcedure:
pluginRegistryServiceCreatePluginHandler.ServeHTTP(w, r)
case PluginRegistryServiceGetPluginProcedure:
pluginRegistryServiceGetPluginHandler.ServeHTTP(w, r)
case PluginRegistryServiceListPluginsProcedure:
pluginRegistryServiceListPluginsHandler.ServeHTTP(w, r)
case PluginRegistryServiceGetVersionProcedure:
pluginRegistryServiceGetVersionHandler.ServeHTTP(w, r)
case PluginRegistryServiceResolveInstallProcedure:
pluginRegistryServiceResolveInstallHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
})
}
// UnimplementedPluginRegistryServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedPluginRegistryServiceHandler struct{}
func (UnimplementedPluginRegistryServiceHandler) CreatePlugin(context.Context, *connect.Request[v1.CreatePluginRequest]) (*connect.Response[v1.CreatePluginResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginRegistryService.CreatePlugin is not implemented"))
}
func (UnimplementedPluginRegistryServiceHandler) GetPlugin(context.Context, *connect.Request[v1.GetPluginRequest]) (*connect.Response[v1.GetPluginResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginRegistryService.GetPlugin is not implemented"))
}
func (UnimplementedPluginRegistryServiceHandler) ListPlugins(context.Context, *connect.Request[v1.ListPluginsRequest]) (*connect.Response[v1.ListPluginsResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginRegistryService.ListPlugins is not implemented"))
}
func (UnimplementedPluginRegistryServiceHandler) GetVersion(context.Context, *connect.Request[v1.GetVersionRequest]) (*connect.Response[v1.GetVersionResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginRegistryService.GetVersion is not implemented"))
}
func (UnimplementedPluginRegistryServiceHandler) ResolveInstall(context.Context, *connect.Request[v1.ResolveInstallRequest]) (*connect.Response[v1.ResolveInstallResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginRegistryService.ResolveInstall is not implemented"))
}
// PluginPublishServiceClient is a client for the orchestrator.v1.PluginPublishService service.
type PluginPublishServiceClient interface {
PublishVersion(context.Context, *connect.Request[v1.PublishVersionRequest]) (*connect.Response[v1.PublishVersionResponse], error)
}
// NewPluginPublishServiceClient constructs a client for the orchestrator.v1.PluginPublishService
// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for
// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply
// the connect.WithGRPC() or connect.WithGRPCWeb() options.
//
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
// http://api.acme.com or https://acme.com/grpc).
func NewPluginPublishServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) PluginPublishServiceClient {
baseURL = strings.TrimRight(baseURL, "/")
pluginPublishServiceMethods := v1.File_orchestrator_v1_plugin_registry_proto.Services().ByName("PluginPublishService").Methods()
return &pluginPublishServiceClient{
publishVersion: connect.NewClient[v1.PublishVersionRequest, v1.PublishVersionResponse](
httpClient,
baseURL+PluginPublishServicePublishVersionProcedure,
connect.WithSchema(pluginPublishServiceMethods.ByName("PublishVersion")),
connect.WithClientOptions(opts...),
),
}
}
// pluginPublishServiceClient implements PluginPublishServiceClient.
type pluginPublishServiceClient struct {
publishVersion *connect.Client[v1.PublishVersionRequest, v1.PublishVersionResponse]
}
// PublishVersion calls orchestrator.v1.PluginPublishService.PublishVersion.
func (c *pluginPublishServiceClient) PublishVersion(ctx context.Context, req *connect.Request[v1.PublishVersionRequest]) (*connect.Response[v1.PublishVersionResponse], error) {
return c.publishVersion.CallUnary(ctx, req)
}
// PluginPublishServiceHandler is an implementation of the orchestrator.v1.PluginPublishService
// service.
type PluginPublishServiceHandler interface {
PublishVersion(context.Context, *connect.Request[v1.PublishVersionRequest]) (*connect.Response[v1.PublishVersionResponse], error)
}
// NewPluginPublishServiceHandler builds an HTTP handler from the service implementation. It returns
// the path on which to mount the handler and the handler itself.
//
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression.
func NewPluginPublishServiceHandler(svc PluginPublishServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
pluginPublishServiceMethods := v1.File_orchestrator_v1_plugin_registry_proto.Services().ByName("PluginPublishService").Methods()
pluginPublishServicePublishVersionHandler := connect.NewUnaryHandler(
PluginPublishServicePublishVersionProcedure,
svc.PublishVersion,
connect.WithSchema(pluginPublishServiceMethods.ByName("PublishVersion")),
connect.WithHandlerOptions(opts...),
)
return "/orchestrator.v1.PluginPublishService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case PluginPublishServicePublishVersionProcedure:
pluginPublishServicePublishVersionHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
})
}
// UnimplementedPluginPublishServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedPluginPublishServiceHandler struct{}
func (UnimplementedPluginPublishServiceHandler) PublishVersion(context.Context, *connect.Request[v1.PublishVersionRequest]) (*connect.Response[v1.PublishVersionResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginPublishService.PublishVersion is not implemented"))
}
// PluginAuthServiceClient is a client for the orchestrator.v1.PluginAuthService service.
type PluginAuthServiceClient interface {
StartDevice(context.Context, *connect.Request[v1.StartDeviceRequest]) (*connect.Response[v1.StartDeviceResponse], error)
PollDevice(context.Context, *connect.Request[v1.PollDeviceRequest]) (*connect.Response[v1.PollDeviceResponse], error)
ApproveDevice(context.Context, *connect.Request[v1.ApproveDeviceRequest]) (*connect.Response[v1.ApproveDeviceResponse], error)
Whoami(context.Context, *connect.Request[v1.WhoamiRequest]) (*connect.Response[v1.WhoamiResponse], error)
}
// NewPluginAuthServiceClient constructs a client for the orchestrator.v1.PluginAuthService service.
// By default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped
// responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
// connect.WithGRPC() or connect.WithGRPCWeb() options.
//
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
// http://api.acme.com or https://acme.com/grpc).
func NewPluginAuthServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) PluginAuthServiceClient {
baseURL = strings.TrimRight(baseURL, "/")
pluginAuthServiceMethods := v1.File_orchestrator_v1_plugin_registry_proto.Services().ByName("PluginAuthService").Methods()
return &pluginAuthServiceClient{
startDevice: connect.NewClient[v1.StartDeviceRequest, v1.StartDeviceResponse](
httpClient,
baseURL+PluginAuthServiceStartDeviceProcedure,
connect.WithSchema(pluginAuthServiceMethods.ByName("StartDevice")),
connect.WithClientOptions(opts...),
),
pollDevice: connect.NewClient[v1.PollDeviceRequest, v1.PollDeviceResponse](
httpClient,
baseURL+PluginAuthServicePollDeviceProcedure,
connect.WithSchema(pluginAuthServiceMethods.ByName("PollDevice")),
connect.WithClientOptions(opts...),
),
approveDevice: connect.NewClient[v1.ApproveDeviceRequest, v1.ApproveDeviceResponse](
httpClient,
baseURL+PluginAuthServiceApproveDeviceProcedure,
connect.WithSchema(pluginAuthServiceMethods.ByName("ApproveDevice")),
connect.WithClientOptions(opts...),
),
whoami: connect.NewClient[v1.WhoamiRequest, v1.WhoamiResponse](
httpClient,
baseURL+PluginAuthServiceWhoamiProcedure,
connect.WithSchema(pluginAuthServiceMethods.ByName("Whoami")),
connect.WithClientOptions(opts...),
),
}
}
// pluginAuthServiceClient implements PluginAuthServiceClient.
type pluginAuthServiceClient struct {
startDevice *connect.Client[v1.StartDeviceRequest, v1.StartDeviceResponse]
pollDevice *connect.Client[v1.PollDeviceRequest, v1.PollDeviceResponse]
approveDevice *connect.Client[v1.ApproveDeviceRequest, v1.ApproveDeviceResponse]
whoami *connect.Client[v1.WhoamiRequest, v1.WhoamiResponse]
}
// StartDevice calls orchestrator.v1.PluginAuthService.StartDevice.
func (c *pluginAuthServiceClient) StartDevice(ctx context.Context, req *connect.Request[v1.StartDeviceRequest]) (*connect.Response[v1.StartDeviceResponse], error) {
return c.startDevice.CallUnary(ctx, req)
}
// PollDevice calls orchestrator.v1.PluginAuthService.PollDevice.
func (c *pluginAuthServiceClient) PollDevice(ctx context.Context, req *connect.Request[v1.PollDeviceRequest]) (*connect.Response[v1.PollDeviceResponse], error) {
return c.pollDevice.CallUnary(ctx, req)
}
// ApproveDevice calls orchestrator.v1.PluginAuthService.ApproveDevice.
func (c *pluginAuthServiceClient) ApproveDevice(ctx context.Context, req *connect.Request[v1.ApproveDeviceRequest]) (*connect.Response[v1.ApproveDeviceResponse], error) {
return c.approveDevice.CallUnary(ctx, req)
}
// Whoami calls orchestrator.v1.PluginAuthService.Whoami.
func (c *pluginAuthServiceClient) Whoami(ctx context.Context, req *connect.Request[v1.WhoamiRequest]) (*connect.Response[v1.WhoamiResponse], error) {
return c.whoami.CallUnary(ctx, req)
}
// PluginAuthServiceHandler is an implementation of the orchestrator.v1.PluginAuthService service.
type PluginAuthServiceHandler interface {
StartDevice(context.Context, *connect.Request[v1.StartDeviceRequest]) (*connect.Response[v1.StartDeviceResponse], error)
PollDevice(context.Context, *connect.Request[v1.PollDeviceRequest]) (*connect.Response[v1.PollDeviceResponse], error)
ApproveDevice(context.Context, *connect.Request[v1.ApproveDeviceRequest]) (*connect.Response[v1.ApproveDeviceResponse], error)
Whoami(context.Context, *connect.Request[v1.WhoamiRequest]) (*connect.Response[v1.WhoamiResponse], error)
}
// NewPluginAuthServiceHandler builds an HTTP handler from the service implementation. It returns
// the path on which to mount the handler and the handler itself.
//
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression.
func NewPluginAuthServiceHandler(svc PluginAuthServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
pluginAuthServiceMethods := v1.File_orchestrator_v1_plugin_registry_proto.Services().ByName("PluginAuthService").Methods()
pluginAuthServiceStartDeviceHandler := connect.NewUnaryHandler(
PluginAuthServiceStartDeviceProcedure,
svc.StartDevice,
connect.WithSchema(pluginAuthServiceMethods.ByName("StartDevice")),
connect.WithHandlerOptions(opts...),
)
pluginAuthServicePollDeviceHandler := connect.NewUnaryHandler(
PluginAuthServicePollDeviceProcedure,
svc.PollDevice,
connect.WithSchema(pluginAuthServiceMethods.ByName("PollDevice")),
connect.WithHandlerOptions(opts...),
)
pluginAuthServiceApproveDeviceHandler := connect.NewUnaryHandler(
PluginAuthServiceApproveDeviceProcedure,
svc.ApproveDevice,
connect.WithSchema(pluginAuthServiceMethods.ByName("ApproveDevice")),
connect.WithHandlerOptions(opts...),
)
pluginAuthServiceWhoamiHandler := connect.NewUnaryHandler(
PluginAuthServiceWhoamiProcedure,
svc.Whoami,
connect.WithSchema(pluginAuthServiceMethods.ByName("Whoami")),
connect.WithHandlerOptions(opts...),
)
return "/orchestrator.v1.PluginAuthService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case PluginAuthServiceStartDeviceProcedure:
pluginAuthServiceStartDeviceHandler.ServeHTTP(w, r)
case PluginAuthServicePollDeviceProcedure:
pluginAuthServicePollDeviceHandler.ServeHTTP(w, r)
case PluginAuthServiceApproveDeviceProcedure:
pluginAuthServiceApproveDeviceHandler.ServeHTTP(w, r)
case PluginAuthServiceWhoamiProcedure:
pluginAuthServiceWhoamiHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
})
}
// UnimplementedPluginAuthServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedPluginAuthServiceHandler struct{}
func (UnimplementedPluginAuthServiceHandler) StartDevice(context.Context, *connect.Request[v1.StartDeviceRequest]) (*connect.Response[v1.StartDeviceResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginAuthService.StartDevice is not implemented"))
}
func (UnimplementedPluginAuthServiceHandler) PollDevice(context.Context, *connect.Request[v1.PollDeviceRequest]) (*connect.Response[v1.PollDeviceResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginAuthService.PollDevice is not implemented"))
}
func (UnimplementedPluginAuthServiceHandler) ApproveDevice(context.Context, *connect.Request[v1.ApproveDeviceRequest]) (*connect.Response[v1.ApproveDeviceResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginAuthService.ApproveDevice is not implemented"))
}
func (UnimplementedPluginAuthServiceHandler) Whoami(context.Context, *connect.Request[v1.WhoamiRequest]) (*connect.Response[v1.WhoamiResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginAuthService.Whoami is not implemented"))
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,184 @@
syntax = "proto3";
package orchestrator.v1;
import "google/protobuf/timestamp.proto";
option go_package = "git.dev.alexdunmow.com/block/ninja/orchestrator/internal/api/orchestrator/v1;orchestratorv1";
// PluginScopeService manages @scope namespaces.
service PluginScopeService {
rpc CreateScope(CreateScopeRequest) returns (CreateScopeResponse);
rpc ListMyScopes(ListMyScopesRequest) returns (ListMyScopesResponse);
rpc GetScope(GetScopeRequest) returns (GetScopeResponse);
}
// PluginRegistryService is the read-side public API plus CreatePlugin.
service PluginRegistryService {
rpc CreatePlugin(CreatePluginRequest) returns (CreatePluginResponse);
rpc GetPlugin(GetPluginRequest) returns (GetPluginResponse);
rpc ListPlugins(ListPluginsRequest) returns (ListPluginsResponse);
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse);
rpc ResolveInstall(ResolveInstallRequest) returns (ResolveInstallResponse);
}
// PluginPublishService is called by the ninja CLI to publish a version.
service PluginPublishService {
rpc PublishVersion(PublishVersionRequest) returns (PublishVersionResponse);
}
// PluginAuthService handles OAuth device flow used by ninja CLI.
service PluginAuthService {
rpc StartDevice(StartDeviceRequest) returns (StartDeviceResponse);
rpc PollDevice(PollDeviceRequest) returns (PollDeviceResponse);
rpc ApproveDevice(ApproveDeviceRequest) returns (ApproveDeviceResponse);
rpc Whoami(WhoamiRequest) returns (WhoamiResponse);
}
// --- Shared messages ---
message Scope {
string id = 1;
string slug = 2;
string display_name = 3;
google.protobuf.Timestamp created_at = 4;
}
message Plugin {
string id = 1;
string scope_slug = 2;
string name = 3;
string visibility = 4;
bool premium = 5;
string description = 6;
string homepage_url = 7;
repeated string categories = 8;
google.protobuf.Timestamp updated_at = 9;
}
message Version {
string id = 1;
string plugin_id = 2;
string version = 3;
string git_commit = 4;
string git_tag = 5;
google.protobuf.Timestamp published_at = 6;
bool yanked = 7;
string sdk_constraint = 8;
string source_archive_sha256 = 9;
int64 source_archive_size = 10;
}
message Requirement {
string scope_slug = 1;
string name = 2;
string constraint_expr = 3;
}
// --- Scope service ---
message CreateScopeRequest { string slug = 1; string display_name = 2; }
message CreateScopeResponse { Scope scope = 1; }
message ListMyScopesRequest {}
message ListMyScopesResponse { repeated Scope scopes = 1; }
message GetScopeRequest { string slug = 1; }
message GetScopeResponse { Scope scope = 1; repeated Plugin plugins = 2; }
// --- Registry service ---
message CreatePluginRequest {
string scope_slug = 1;
string name = 2;
string description = 3;
}
message CreatePluginResponse {
Plugin plugin = 1;
string git_remote_url = 2;
}
message GetPluginRequest { string scope_slug = 1; string name = 2; }
message GetPluginResponse {
Plugin plugin = 1;
repeated Version versions = 2;
map<string,string> channels = 3;
}
message ListPluginsRequest {
int32 limit = 1;
int32 offset = 2;
string query = 3;
}
message ListPluginsResponse { repeated Plugin plugins = 1; }
message GetVersionRequest {
string scope_slug = 1;
string plugin_name = 2;
string version = 3;
}
message GetVersionResponse {
Version version = 1;
repeated Requirement requires = 2;
string readme_md = 3;
string changelog_md = 4;
}
message ResolveInstallRequest {
string scope_slug = 1;
string plugin_name = 2;
string version_or_channel = 3;
}
message ResolveInstallResponse {
string version_id = 1;
string scope_slug = 2;
string plugin_name = 3;
string version = 4;
string channel_pinned = 5;
string archive_url = 6;
string archive_sha256 = 7;
string sdk_constraint = 8;
repeated Requirement requires = 9;
bool yanked = 10;
repeated string warnings = 11;
}
// --- Publish service ---
message PublishVersionRequest {
string plugin_id = 1;
string git_ref = 2;
string channel = 3;
string readme_md = 4;
string changelog_md = 5;
}
message PublishVersionResponse {
Version version = 1;
string archive_url = 2;
repeated string warnings = 3;
}
// --- Auth service (device flow) ---
message StartDeviceRequest { repeated string scopes = 1; }
message StartDeviceResponse {
string device_code = 1;
string user_code = 2;
string verification_uri = 3;
int32 interval_seconds = 4;
int32 expires_in_seconds = 5;
}
message PollDeviceRequest { string device_code = 1; }
message PollDeviceResponse {
string access_token = 1;
string status = 2;
}
message ApproveDeviceRequest { string user_code = 1; }
message ApproveDeviceResponse {}
message WhoamiRequest {}
message WhoamiResponse {
string user_id = 1;
string email = 2;
string display_name = 3;
}