tailorclient

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 15, 2026 License: MIT Imports: 16 Imported by: 0

README

[!IMPORTANT] This is an unofficial library.

tailor-client-go

tailor-client-go is an unofficial Go client library for the Tailor Platform.

It does not implement its own login flow. Authentication piggybacks on the official Tailor SDK: you log in once with npx tailor-sdk login, and tailor-client-go reuses the access / refresh tokens stored by the SDK to authenticate RPCs. Token refresh and (optionally) writeback are kept compatible with the SDK config so other Tailor tools stay in sync.

In short, tailorclient.New(ctx) returns the buf.build generated connect-go OperatorServiceClient already wired with bearer-token auth and auto-refresh.

Features

  • Piggybacks on the Tailor SDK authentication: tokens are sourced from ~/.config/tailor-platform/config.yaml (file and keyring storage both supported)
  • One-call constructor: tailorclient.New(ctx) returns a ready-to-use authenticated client
  • Automatic token refresh on Unauthenticated RPC errors
  • Optional SDK config writeback on token refresh (off by default) so the SDK and other tools see the new tokens
  • Embeds tailorv1connect.OperatorServiceClient, so RPC methods are callable directly on the client

Install

$ go get github.com/k1LoW/tailor-client-go

Authentication model

tailor-client-go does not handle the OAuth2 login flow itself. The expected workflow is:

  1. The user (or operator) logs in once via the Tailor SDK:

    $ npx tailor-sdk login
    

    This writes access_token, refresh_token, and token_expires_at for the current user to ~/.config/tailor-platform/config.yaml (or, when the user is configured for storage: keyring, into the OS keyring).

  2. Any Go program using tailor-client-go calls tailorclient.New(ctx) and the library transparently:

    • reads the current user's tokens from the SDK config (or keyring),
    • refreshes the access token if token_expires_at is in the past,
    • retries once with a refreshed token if an RPC returns Unauthenticated.

If you prefer to manage tokens yourself (e.g. in CI, or against a service account), pass them in explicitly with WithTokens and the SDK config is not touched.

Usage

package main

import (
	"context"
	"fmt"
	"log"

	tailorv1 "buf.build/gen/go/tailor-inc/tailor/protocolbuffers/go/tailor/v1"
	"connectrpc.com/connect"

	tailorclient "github.com/k1LoW/tailor-client-go"
)

func main() {
	ctx := context.Background()

	c, err := tailorclient.New(ctx)
	if err != nil {
		log.Fatal(err)
	}

	res, err := c.GetApplication(ctx, connect.NewRequest(&tailorv1.GetApplicationRequest{
		WorkspaceId:     "ws_xxx",
		ApplicationName: "my-app",
	}))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Msg.GetApplication().GetName())
}

Without any options, New reads the current user's tokens from the Tailor SDK config. When token_expires_at indicates the access token is stale, it is refreshed proactively before the client is returned.

Explicit tokens

To bypass the SDK config and supply tokens directly:

c, err := tailorclient.New(ctx,
	tailorclient.WithTokens(accessToken, refreshToken),
	tailorclient.WithPlatformURL(tailorclient.DefaultPlatformURL),
)
Persisting refreshed tokens

By default, tokens refreshed during a session are kept in-memory only. Pass WithTokenPersist() to write them back to the SDK config (or keyring, if the user is configured for keyring storage) so other tools stay in sync:

c, err := tailorclient.New(ctx, tailorclient.WithTokenPersist())
Available options
Option Description
WithPlatformURL(url string) Override the Tailor Platform endpoint (default: https://api.tailor.tech)
WithTokens(access, refresh string) Use supplied tokens instead of reading the SDK config
WithTokenPersist() Write refreshed tokens back to the SDK config (default: off)
WithHTTPClient(h connect.HTTPClient) Override the underlying HTTP client
WithInterceptors(ics ...connect.Interceptor) Append additional connect interceptors

How it works

  1. Resolves access / refresh tokens (explicit values via WithTokens, or the Tailor SDK config)
  2. If the SDK config token is expired, refreshes it proactively against the OAuth2 token endpoint
  3. Builds a connect-go OperatorServiceClient wrapped with an interceptor that:
    • Attaches the Authorization: Bearer <token> header to every request
    • On an Unauthenticated error, exchanges the refresh token for a new access token and retries once
  4. When WithTokenPersist() is set, refreshed tokens are written back to the SDK config (or keyring)

License

MIT License

Documentation

Overview

Package tailorclient is an UNOFFICIAL Go client library for the Tailor Platform.

It does not implement its own login flow. Authentication piggybacks on the official Tailor SDK (https://github.com/tailor-platform/sdk): the user logs in once with `npx tailor-sdk login`, and this package reuses the access / refresh tokens that the SDK stores in ~/.config/tailor-platform/config.yaml (file or keyring).

New returns a connect-go OperatorServiceClient already wired with bearer-token authentication and automatic refresh on Unauthenticated errors. Tokens can also be supplied explicitly via WithTokens.

Example

Example builds an authenticated client from the SDK config and calls an OperatorService RPC. The RPC method is promoted from the embedded tailorv1connect.OperatorServiceClient, so it is callable directly on *Client.

package main

import (
	"context"
	"fmt"
	"log"

	tailorv1 "buf.build/gen/go/tailor-inc/tailor/protocolbuffers/go/tailor/v1"
	"connectrpc.com/connect"

	tailorclient "github.com/k1LoW/tailor-client-go"
)

func main() {
	ctx := context.Background()
	c, err := tailorclient.New(ctx)
	if err != nil {
		log.Fatal(err)
	}

	res, err := c.GetApplication(ctx, connect.NewRequest(&tailorv1.GetApplicationRequest{
		WorkspaceId:     "ws_xxx",
		ApplicationName: "my-app",
	}))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Msg.GetApplication().GetName())
}

Index

Examples

Constants

View Source
const DefaultPlatformURL = "https://api.tailor.tech"

DefaultPlatformURL is the production Tailor Platform endpoint.

Variables

This section is empty.

Functions

func IsTokenExpired

func IsTokenExpired(expiresAt string) bool

IsTokenExpired checks if a token_expires_at string indicates an expired token.

func ReadSDKTokens

func ReadSDKTokens() (accessToken, refreshToken, tokenExpiresAt string, err error)

ReadSDKTokens reads access_token, refresh_token, and token_expires_at from the SDK config for the current_user. Supports both file-based (v1) and keyring-based (v2) storage.

func WriteSDKTokens

func WriteSDKTokens(accessToken, refreshToken, tokenExpiresAt string) error

WriteSDKTokens updates the tokens for the current_user. Writes to keyring or config file depending on the user's storage mode.

Types

type Client

type Client struct {
	tailorv1connect.OperatorServiceClient
	// contains filtered or unexported fields
}

Client is an authenticated Tailor Platform client.

It embeds OperatorServiceClient, so RPC methods can be called directly (e.g. c.GetApplication). The auto-refresh interceptor is wired into the embedded client at construction time.

func New

func New(ctx context.Context, opts ...Option) (*Client, error)

New builds an authenticated client.

Without WithTokens, New reads the current user's tokens from the Tailor SDK config and proactively refreshes them if expired. Token refresh on unauthenticated RPC errors is always enabled; SDK config writeback is opt-in via WithTokenPersist.

Example (ExplicitTokens)

ExampleNew_explicitTokens passes tokens explicitly instead of reading the SDK config.

package main

import (
	"context"
	"log"

	tailorclient "github.com/k1LoW/tailor-client-go"
)

func main() {
	ctx := context.Background()
	_, err := tailorclient.New(ctx,
		tailorclient.WithTokens("access-token", "refresh-token"),
		tailorclient.WithPlatformURL(tailorclient.DefaultPlatformURL),
	)
	if err != nil {
		log.Fatal(err)
	}
}
Example (PersistTokens)

ExampleNew_persistTokens enables SDK config writeback on token refresh. Disabled by default.

package main

import (
	"context"
	"log"

	tailorclient "github.com/k1LoW/tailor-client-go"
)

func main() {
	ctx := context.Background()
	_, err := tailorclient.New(ctx, tailorclient.WithTokenPersist())
	if err != nil {
		log.Fatal(err)
	}
}

func (*Client) HTTPClient

func (c *Client) HTTPClient() connect.HTTPClient

HTTPClient returns the underlying HTTP client.

func (*Client) PlatformURL

func (c *Client) PlatformURL() string

PlatformURL returns the configured Tailor Platform endpoint.

type Option

type Option func(*options)

Option configures New.

func WithHTTPClient

func WithHTTPClient(h connect.HTTPClient) Option

WithHTTPClient overrides the underlying HTTP client.

func WithInterceptors

func WithInterceptors(ics ...connect.Interceptor) Option

WithInterceptors appends additional connect interceptors. The auto-refresh interceptor is always installed first.

func WithPlatformURL

func WithPlatformURL(u string) Option

WithPlatformURL overrides the Tailor Platform endpoint.

func WithTokenPersist

func WithTokenPersist() Option

WithTokenPersist enables writing refreshed tokens back to the SDK config. Disabled by default.

func WithTokens

func WithTokens(accessToken, refreshToken string) Option

WithTokens uses the supplied tokens instead of reading the SDK config.

type SDKConfig

type SDKConfig struct {
	Version             int                       `yaml:"version"`
	MinSDKVersion       string                    `yaml:"min_sdk_version,omitempty"`
	LatestVersion       *int                      `yaml:"latest_version,omitempty"`
	LatestMinSDKVersion string                    `yaml:"latest_min_sdk_version,omitempty"`
	Users               map[string]*SDKUserTokens `yaml:"users"`
	Profiles            yaml.MapSlice             `yaml:"profiles,omitempty"`
	CurrentUser         *string                   `yaml:"current_user"`
}

SDKConfig represents the Tailor SDK config.yaml (v1/v2 format).

type SDKUserTokens

type SDKUserTokens struct {
	AccessToken    string  `yaml:"access_token,omitempty"`
	RefreshToken   string  `yaml:"refresh_token,omitempty"`
	TokenExpiresAt string  `yaml:"token_expires_at"`
	Storage        *string `yaml:"storage,omitempty"`
}

type TokenResponse

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	ExpiresIn    int    `json:"expires_in"`
	Error        string `json:"error,omitempty"`
}

TokenResponse is the response from the OAuth2 token endpoint.

func RefreshAccessToken

func RefreshAccessToken(platformURL, refreshToken string) (*TokenResponse, error)

RefreshAccessToken exchanges a refresh_token for a new access_token.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL