sinatra

package module
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2026 License: MIT Imports: 17 Imported by: 0

README

Sinatra Go SDK

sinatra-go is the official Go client for sinatra.dev, the hosted AI coding agent that turns Linear and GitHub issues into pull requests. Assign Sinatra an issue, mention it in Slack, or start a task from code, and it writes the change in its own sandbox and opens a draft pull request for your team to review.

With this client you can start, follow and stop Sinatra tasks and delegate Linear and GitHub issues from your own programs, CI jobs and tools, through the Sinatra API.

It needs Go 1.24 or later. The types are generated from the API's OpenAPI spec, and a new version is released automatically when the spec changes. There are also official SDKs for Node.js / TypeScript and Python; see SDKs.

Install

go get github.com/sinatra-dev/sinatra-go
import "github.com/sinatra-dev/sinatra-go"

The package name is sinatra.

Quick start

This is the body of main in examples/quickstart/main.go, which has the imports and a check that calls log.Fatal on an error. It starts a task in your first workspace and prints the conversation until the turn ends:

ctx := context.Background()
client, err := sinatra.NewClient() // reads SINATRA_TOKEN
check(err)
me, err := client.Me.Get(ctx)
check(err)
if len(me.Workspaces) == 0 {
	log.Fatal("the token's user isn't in any workspace yet")
}
ws := me.Workspaces[0].Id
created, err := client.Tasks.Create(ctx, ws, sinatra.CreateTaskRequest{Prompt: "Fix the flaky login test"},
	sinatra.WithIdempotencyKey("fix-flaky-login-1"))
check(err)
working := map[string]bool{"pending": true, "investigating": true, "implementing": true, "rework": true}
var after *string
for ; ; time.Sleep(3 * time.Second) {
	task, err := client.Tasks.Get(ctx, ws, created.Task.Id)
	check(err)
	for m, err := range client.Messages.ListAll(ctx, ws, task.Id, &sinatra.MessagesListParams{After: after}) {
		check(err)
		fmt.Printf("%s: %s\n", m.Role, m.Body)
		after = &m.Id
	}
	if !working[task.Status] {
		fmt.Println("status:", task.Status)
		return
	}
}

The API has no streaming. To wait for a reply, read the conversation after the last entry you've seen, and check the task's Status: the agent posts one reply per turn, and the turn is done when the task leaves the working statuses (for example, it reaches replied or pr_open). The loop polls every 3 seconds. The loop reads the status before the conversation, so it never stops before printing the reply that ended the turn. An unknown status counts as done, so the loop can't wait forever on a status added after this was written. See How replies work.

Examples

examples/ has small programs you can run as they are:

  • quickstart: the quick start above.
  • delegate-issue: hand Sinatra a Linear issue (ENG-123) or a GitHub issue (acme/web#42), for example from a CI job.
  • search-issues: list the open issues you could delegate, yours first.
  • open-prs: print the pull requests Sinatra has open in each workspace as a Markdown list, for a standup or a Slack post.

From a checkout:

SINATRA_TOKEN=sntr_... go run ./examples/open-prs

Authentication

Create a personal access token on the Access tokens page (CLI & API in the dashboard). It starts with sntr_ and acts as you in every workspace you belong to. NewClient reads it from the SINATRA_TOKEN environment variable, or you can pass it:

client, err := sinatra.NewClient(sinatra.WithToken(os.Getenv("MY_TOKEN")))

NewClient returns an error if it finds no token. Other options:

  • WithBaseURL(url): defaults to https://api.sinatra.dev.
  • WithHTTPClient(*http.Client): defaults to http.DefaultClient.
  • WithTimeout(d): how long each attempt of a request may take, from sending it to reading the whole response. Defaults to 60 seconds; 0 turns it off. A timed-out attempt isn't retried, and its error wraps context.DeadlineExceeded.

Every method takes a context.Context first. Cancelling it stops the call, including a retry it's waiting to make, and a deadline on it limits the whole call, retries included:

ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
task, err := client.Tasks.Get(ctx, workspaceID, taskID)

A Client is safe for concurrent use.

Operations

The services mirror the API's operation ids:

Method Request
Me.Get(ctx) GET /v1/me
Me.RevokeToken(ctx) DELETE /v1/me/token
Tasks.List(ctx, workspaceID, *TasksListParams) GET /v1/workspaces/{workspaceId}/tasks
Tasks.Get(ctx, workspaceID, taskID) GET /v1/workspaces/{workspaceId}/tasks/{taskId}
Tasks.Create(ctx, workspaceID, CreateTaskRequest, ...CallOption) POST /v1/workspaces/{workspaceId}/tasks
Tasks.Stop(ctx, workspaceID, taskID) POST /v1/workspaces/{workspaceId}/tasks/{taskId}/stop
Messages.Create(ctx, workspaceID, taskID, CreateMessageRequest, ...CallOption) POST /v1/workspaces/{workspaceId}/tasks/{taskId}/messages
Messages.List(ctx, workspaceID, taskID, *MessagesListParams) GET /v1/workspaces/{workspaceId}/tasks/{taskId}/messages
Tickets.Search(ctx, workspaceID, *TicketsSearchParams) GET /v1/workspaces/{workspaceId}/tickets
Tickets.Delegate(ctx, workspaceID, DelegateTarget) POST /v1/workspaces/{workspaceId}/tickets/delegate

Params arguments may be nil. Optional fields are pointers; sinatra.Ptr makes one: &sinatra.TasksListParams{Status: sinatra.Ptr("pr_open")}.

To delegate an issue, pass a DelegateLinearTicket or a DelegateGithubTicket. Source is filled in for you:

client.Tickets.Delegate(ctx, workspaceID, sinatra.DelegateLinearTicket{Id: "ENG-123"})
client.Tickets.Delegate(ctx, workspaceID, sinatra.DelegateGithubTicket{Repo: "acme/web", Number: 42})

Tasks.ListAll and Messages.ListAll page through everything for you, as an iter.Seq2. They stop after the first error, which they yield:

for task, err := range client.Tasks.ListAll(ctx, workspaceID, &sinatra.TasksListParams{Status: sinatra.Ptr("pr_open")}) {
	if err != nil {
		return err
	}
	fmt.Println(task.Id, task.Status)
}

Fields such as Task.Status, Task.Source, Message.Role, Message.Kind and the error Code are plain strings, and the API adds new values over time. Handle values you don't recognise.

Retries and idempotency

Tasks.Create and Messages.Create always send an Idempotency-Key. If you don't pass one with sinatra.WithIdempotencyKey, the client makes a random UUID. Every retry of a call sends the same key, so a retried start never starts a second task. Pass your own key to make a retry safe across process restarts too.

The client retries a request up to 3 times when it's safe to repeat:

  • 429: after the Retry-After the server sends, at most 60 seconds.
  • 503: after 0.5s, then 1s, then 2s.

GETs, Me.RevokeToken, Tasks.Create and Messages.Create retry on both. Tasks.Stop retries on 503 only. Tickets.Delegate takes no idempotency key and is never retried. Other errors, timeouts and network failures are never retried.

Errors

An error response is returned as a *sinatra.Error:

_, err := client.Tasks.Stop(ctx, workspaceID, taskID)
var apiErr *sinatra.Error
if errors.As(err, &apiErr) && apiErr.Code == "not_running" {
	// the task had already stopped
} else if err != nil {
	return err
}
Field
Status The HTTP status.
Code The API's error code, such as capacity_full. http_error when the response carried none, such as an HTML error page from a proxy.
Reason A sentence for a person. Don't branch on it.
RequestID The request's id. Include it when you contact support.
Body The raw response body.
Header The response headers.
Details The decoded body when it was JSON, with extra fields some refusals carry, such as BillingUrl.

Network failures, timeouts and cancellations are not a *sinatra.Error: they are returned as net/http and context report them, so errors.Is(err, context.DeadlineExceeded) works.

The API reference lists every operation's error codes.

Versions

Releases follow semver. While the version is 0.x, a breaking API change bumps the minor version and anything else bumps the patch. From 1.0, additions bump the minor version, and a breaking change bumps the major version, which in Go also means a new module path ending in /vN (github.com/sinatra-dev/sinatra-go/v2) that you import instead. See Major versions. Pin a version in your go.mod as usual.

Development

go generate ./...                  # regenerates api.gen.go from openapi.json
go vet ./... && go test -race ./...
bash scripts/contract.sh           # every operation against a Prism mock of openapi.json (needs Node.js 24)
bash scripts/install-oasdiff.sh    # the pinned oasdiff, into .tools/bin
PATH="$PWD/.tools/bin:$PATH" bash scripts/next-version.test.sh
bash scripts/check-readme.sh       # the README's quick start is examples/quickstart's, and builds against this module

api.gen.go is written by oapi-codegen, pinned in generate.go, with the settings in codegen/, and is never edited by hand. CI fails if it differs from what openapi.json generates. openapi.json is updated by the release workflow. See RELEASING.md.

License

MIT

Documentation

Overview

Package sinatra is the official Go client for sinatra.dev, the AI coding agent that turns Linear and GitHub issues into pull requests: start, follow and stop Sinatra tasks, and delegate Linear and GitHub issues.

The types (Task, Message, Me, ...) are generated from the API's OpenAPI spec into api.gen.go. The client in this file adds authentication, retries, idempotency keys, timeouts and pagination on top.

client, err := sinatra.NewClient() // reads SINATRA_TOKEN
me, err := client.Me.Get(ctx)

Fields such as Task.Status, Task.Source, Message.Role, Message.Kind and Error.Code are plain strings, and the API adds values over time: handle values you don't recognise.

Reference: https://docs.sinatra.dev/docs/api-reference

Index

Constants

View Source
const (
	// DefaultBaseURL is the API the client talks to unless WithBaseURL says otherwise.
	DefaultBaseURL = "https://api.sinatra.dev"
	// DefaultTimeout is how long each attempt of a request may take, unless
	// WithTimeout says otherwise.
	DefaultTimeout = 60 * time.Second
)

Variables

This section is empty.

Functions

func Ptr

func Ptr[T any](v T) *T

Ptr returns a pointer to v, for the optional fields of requests and parameters: sinatra.CreateTaskRequest{Prompt: "...", Repo: sinatra.Ptr("acme/web")}.

Types

type CallOption

type CallOption func(*callOptions)

CallOption configures one call of Tasks.Create or Messages.Create.

func WithIdempotencyKey

func WithIdempotencyKey(key string) CallOption

WithIdempotencyKey sets the call's Idempotency-Key: 1 to 128 printable ASCII characters, with no spaces. Without it, the client makes a random UUID. Either way, every retry of the call sends the same key, so a retried start never starts a second task. Pass your own key to make a retry safe across process restarts too.

type Client

type Client struct {
	Me       *MeService
	Tasks    *TasksService
	Messages *MessagesService
	Tickets  *TicketsService
	// contains filtered or unexported fields
}

Client is a Sinatra API client. Its services mirror the API's operations: Me.Get is GET /v1/me, Tasks.Create is POST /v1/workspaces/{workspaceId}/tasks. A Client is safe for concurrent use.

Requests that are safe to repeat (GETs, the token DELETE, and starts and messages, which always carry an Idempotency-Key) are retried up to 3 times on 429 and 503. Tasks.Stop is retried on 503 only, Tickets.Delegate never. Error responses are returned as *Error.

func NewClient

func NewClient(opts ...Option) (*Client, error)

NewClient returns a client. It fails if there is no token, from WithToken or SINATRA_TOKEN, or if the base URL isn't an absolute http(s) URL.

type CreateMessageRequest

type CreateMessageRequest struct {
	// Text The message, up to 100,000 characters after trimming.
	Text string `json:"text"`
}

CreateMessageRequest A message to send on a task.

type CreateMessageResponse

type CreateMessageResponse struct {
	// MessageId The message's id: the `Idempotency-Key` you sent, or a generated id.
	MessageId string `json:"messageId"`
}

CreateMessageResponse The message that was sent.

type CreateTaskRequest

type CreateTaskRequest struct {
	// Prompt What to do, as you'd ask a teammate. Up to 100,000 characters, after trimming.
	Prompt string `json:"prompt"`

	// Repo `owner/name` of a repository connected to the workspace. Omit it to let the agent choose.
	Repo *string `json:"repo,omitempty"`

	// Title The task's title, up to 200 characters. Defaults to the prompt's first line, cut to 80 characters.
	Title *string `json:"title,omitempty"`
}

CreateTaskRequest A task to start.

type CreateTaskResponse

type CreateTaskResponse struct {
	// MessageId The id of your first message: the `Idempotency-Key` you sent, or a generated id.
	MessageId string `json:"messageId"`

	// Task A task: one piece of work Sinatra does, from any source.
	Task Task `json:"task"`
}

CreateTaskResponse The task that started, and the id of your first message.

type DelegateGithubTicket

type DelegateGithubTicket struct {
	// Number The issue's number in that repository.
	Number int `json:"number"`

	// Repo `owner/name` of a repository enabled in the workspace.
	Repo string `json:"repo"`

	// Source `github` to delegate a GitHub issue.
	Source DelegateGithubTicketSource `json:"source"`
}

DelegateGithubTicket A GitHub issue to delegate, by its repository and number.

type DelegateGithubTicketSource

type DelegateGithubTicketSource string

DelegateGithubTicketSource `github` to delegate a GitHub issue.

const (
	DelegateGithubTicketSourceGithub DelegateGithubTicketSource = "github"
)

Defines values for DelegateGithubTicketSource.

func (DelegateGithubTicketSource) Valid

func (e DelegateGithubTicketSource) Valid() bool

Valid indicates whether the value is a known member of the DelegateGithubTicketSource enum.

type DelegateLinearTicket

type DelegateLinearTicket struct {
	// Id The issue's identifier, such as `ENG-123`, or its id.
	Id string `json:"id"`

	// Source `linear` to delegate a Linear issue.
	Source DelegateLinearTicketSource `json:"source"`
}

DelegateLinearTicket A Linear issue to delegate, by its identifier or id.

type DelegateLinearTicketSource

type DelegateLinearTicketSource string

DelegateLinearTicketSource `linear` to delegate a Linear issue.

const (
	DelegateLinearTicketSourceLinear DelegateLinearTicketSource = "linear"
)

Defines values for DelegateLinearTicketSource.

func (DelegateLinearTicketSource) Valid

func (e DelegateLinearTicketSource) Valid() bool

Valid indicates whether the value is a known member of the DelegateLinearTicketSource enum.

type DelegateTarget

type DelegateTarget interface {
	// contains filtered or unexported methods
}

DelegateTarget is an issue to delegate: a DelegateLinearTicket, a DelegateGithubTicket, or a DelegateTicketRequest. Source is filled in for you.

type DelegateTicketRequest

type DelegateTicketRequest struct {
	// contains filtered or unexported fields
}

DelegateTicketRequest An issue to delegate: a Linear issue by `id`, or a GitHub issue by `repo` and `number`.

func (DelegateTicketRequest) AsDelegateGithubTicket

func (t DelegateTicketRequest) AsDelegateGithubTicket() (DelegateGithubTicket, error)

AsDelegateGithubTicket returns the union data inside the DelegateTicketRequest as a DelegateGithubTicket

func (DelegateTicketRequest) AsDelegateLinearTicket

func (t DelegateTicketRequest) AsDelegateLinearTicket() (DelegateLinearTicket, error)

AsDelegateLinearTicket returns the union data inside the DelegateTicketRequest as a DelegateLinearTicket

func (DelegateTicketRequest) Discriminator

func (t DelegateTicketRequest) Discriminator() (string, error)

func (*DelegateTicketRequest) FromDelegateGithubTicket

func (t *DelegateTicketRequest) FromDelegateGithubTicket(v DelegateGithubTicket) error

FromDelegateGithubTicket overwrites any union data inside the DelegateTicketRequest as the provided DelegateGithubTicket

func (*DelegateTicketRequest) FromDelegateLinearTicket

func (t *DelegateTicketRequest) FromDelegateLinearTicket(v DelegateLinearTicket) error

FromDelegateLinearTicket overwrites any union data inside the DelegateTicketRequest as the provided DelegateLinearTicket

func (DelegateTicketRequest) MarshalJSON

func (t DelegateTicketRequest) MarshalJSON() ([]byte, error)

func (*DelegateTicketRequest) MergeDelegateGithubTicket

func (t *DelegateTicketRequest) MergeDelegateGithubTicket(v DelegateGithubTicket) error

MergeDelegateGithubTicket performs a merge with any union data inside the DelegateTicketRequest, using the provided DelegateGithubTicket

func (*DelegateTicketRequest) MergeDelegateLinearTicket

func (t *DelegateTicketRequest) MergeDelegateLinearTicket(v DelegateLinearTicket) error

MergeDelegateLinearTicket performs a merge with any union data inside the DelegateTicketRequest, using the provided DelegateLinearTicket

func (*DelegateTicketRequest) UnmarshalJSON

func (t *DelegateTicketRequest) UnmarshalJSON(b []byte) error

func (DelegateTicketRequest) ValueByDiscriminator

func (t DelegateTicketRequest) ValueByDiscriminator() (interface{}, error)

type DelegateTicketResponse

type DelegateTicketResponse struct {
	// Ok Always `true`: the issue was handed to Sinatra.
	Ok DelegateTicketResponseOk `json:"ok"`
}

DelegateTicketResponse The issue was delegated.

type DelegateTicketResponseOk

type DelegateTicketResponseOk bool

DelegateTicketResponseOk Always `true`: the issue was handed to Sinatra.

const (
	DelegateTicketResponseOkTrue DelegateTicketResponseOk = true
)

Defines values for DelegateTicketResponseOk.

func (DelegateTicketResponseOk) Valid

func (e DelegateTicketResponseOk) Valid() bool

Valid indicates whether the value is a known member of the DelegateTicketResponseOk enum.

type Error

type Error struct {
	// Status is the HTTP status, such as 409.
	Status int
	// Code is the API's machine-readable error code, such as "capacity_full".
	// New codes can appear at any time, so treat it as an open string. It is
	// "http_error" when the response carried no code, such as an HTML error
	// page from a proxy. Branch on Code, not on Reason.
	Code string
	// Reason is a sentence saying what went wrong, written for a person.
	Reason string
	// RequestID is the request's id, from the body or the x-request-id
	// header. Include it when you contact support.
	RequestID string
	// Body is the raw response body.
	Body []byte
	// Header holds the response headers.
	Header http.Header
	// Details is the decoded body when it was a JSON object, with fields such
	// as BillingUrl on some refusals. Nil otherwise.
	Details *ErrorBody
}

Error is the error every method returns for an HTTP error response. Get it with errors.As:

var apiErr *sinatra.Error
if errors.As(err, &apiErr) && apiErr.Code == "not_running" { ... }

Network failures, timeouts and cancellations are not an *Error: they are returned as net/http and context report them.

func (*Error) Error

func (e *Error) Error() string

type ErrorBody

type ErrorBody struct {
	// BillingUrl The settings section that fixes the refusal: your model credentials or billing, on `payment_required`; billing, when starting a task, on `capacity_full`.
	BillingUrl *string `json:"billingUrl,omitempty"`

	// Error A machine-readable code, such as `invalid_prompt`. Branch on this, not on `reason`. New codes can appear. Known codes: `assigned_to_other`, `bad_request`, `capacity_full`, `github_identity_required`, `github_not_connected`, `github_rate_limited`, `idempotency_conflict`, `internal_error`, `invalid_body`, `invalid_cursor`, `invalid_idempotency_key`, `invalid_limit`, `invalid_prompt`, `invalid_repo`, `invalid_status`, `invalid_title`, `invalid_token`, `issue_closed`, `issue_not_found`, `linear_app_user_missing`, `linear_identity_conflict`, `linear_identity_unlinked`, `linear_not_connected`, `linear_reconnect_required`, `not_an_api_task`, `not_an_issue`, `not_found`, `not_running`, `payment_required`, `repo_disabled`, `unavailable`.
	Error string `json:"error"`

	// MessageId On `unavailable`: the message that was saved but not sent yet. Retry with it as the `Idempotency-Key`.
	MessageId *string `json:"messageId,omitempty"`

	// Plan The workspace's plan, on `capacity_full` when starting a task.
	Plan *string `json:"plan,omitempty"`

	// Queued How many tasks the plan queues, on `capacity_full` when starting a task.
	Queued *int `json:"queued,omitempty"`

	// Reason A sentence saying what went wrong and, where it helps, what to do. Written to show to a person.
	Reason *string `json:"reason,omitempty"`

	// Refusal Why a start was refused, on `payment_required` and `github_not_connected`. For `payment_required`: `own_credentials_required` or `subscription_inactive`. For `github_not_connected`: `missing` or `suspended`.
	Refusal *string `json:"refusal,omitempty"`

	// RequestId The request's id. Present on some errors, not all; the `x-request-id` response header always carries it. Share it with support.
	RequestId *string `json:"requestId,omitempty"`

	// Running How many tasks the plan runs at a time, on `capacity_full` when starting a task.
	Running *int `json:"running,omitempty"`

	// SettingsUrl The settings page that fixes the refusal, on `github_not_connected`.
	SettingsUrl *string `json:"settingsUrl,omitempty"`

	// TaskId On `unavailable` after a start: the task that was made. Retry with the same key, or send a message on it.
	TaskId *string `json:"taskId,omitempty"`
}

ErrorBody The body of every error response.

type IdempotencyKey

type IdempotencyKey = string

IdempotencyKey defines model for IdempotencyKey.

type Me

type Me struct {
	// User The user the token acts as.
	User struct {
		// Email The user's email address: the one they sign in with by email, or their primary GitHub email.
		Email string `json:"email"`

		// GithubLogin The user's GitHub login. `null` for a user without GitHub.
		GithubLogin *string `json:"githubLogin"`

		// Id The user's id.
		Id string `json:"id"`

		// Name The user's name, if they have one.
		Name *string `json:"name"`
	} `json:"user"`

	// Workspaces The workspaces the user belongs to, ordered by name.
	Workspaces []Workspace `json:"workspaces"`
}

Me The token's user and workspaces.

type MeService

type MeService struct {
	// contains filtered or unexported fields
}

MeService is the token's user: Client.Me.

func (*MeService) Get

func (s *MeService) Get(ctx context.Context) (*Me, error)

Get returns the user the token acts as, and the workspaces they belong to (me.get).

func (*MeService) RevokeToken

func (s *MeService) RevokeToken(ctx context.Context) error

RevokeToken revokes the token the client sends (me.revokeToken). Later requests with it get 401 invalid_token.

type Message

type Message struct {
	// Body The text.
	Body string `json:"body"`

	// CreatedAt When it was written.
	CreatedAt time.Time `json:"createdAt"`

	// Id The entry's id. Pass the last one you read as `after` to get only later entries.
	Id string `json:"id"`

	// Kind What it is: `message` (a person's message), `response` (the agent's answer for a turn, or a notice from Sinatra such as a stop or files the agent produced), `clarify` (a question the agent needs answered to continue) or `error` (a refusal or failure from Sinatra). New kinds can appear.
	Kind string `json:"kind"`

	// MessageId For a message a person sent: the id it was sent with, which is its idempotency key or a generated id. `null` on every other entry.
	MessageId *string `json:"messageId"`

	// Role Who wrote it: `user` for a person, `agent` for the agent, `platform` for Sinatra itself. New roles can appear.
	Role string `json:"role"`
}

Message An entry in a task's conversation.

type MessageList

type MessageList struct {
	// Messages The page's entries, oldest first.
	Messages []Message `json:"messages"`

	// Next When it isn't `null`, more entries follow: pass it as `after` to get them. `null` on the last page.
	Next *string `json:"next"`
}

MessageList A page of a task's conversation.

type MessagesCreateJSONRequestBody

type MessagesCreateJSONRequestBody = CreateMessageRequest

MessagesCreateJSONRequestBody defines body for MessagesCreate for application/json ContentType.

type MessagesCreateParams

type MessagesCreateParams struct {
	// IdempotencyKey Makes a retry safe. 1 to 128 printable ASCII characters, with no spaces. A request repeated with the same key answers with the first one's result and does the work once; the same key with different content is `409 idempotency_conflict`. Keys belong to the person who sent them: two people using the same key start two tasks or send two messages. The key is also the `messageId` of the message the request sends, which is a start's first message.
	IdempotencyKey *IdempotencyKey `json:"Idempotency-Key,omitempty"`
}

MessagesCreateParams defines parameters for MessagesCreate.

type MessagesListParams

type MessagesListParams struct {
	// After The `id` of the last entry you've read. Returns only later entries. An id that isn't an entry of this task is `invalid_cursor`.
	After *string `form:"after,omitempty" json:"after,omitempty"`

	// Limit How many entries to return, 1 to 200.
	Limit *int `form:"limit,omitempty" json:"limit,omitempty"`
}

MessagesListParams defines parameters for MessagesList.

type MessagesService

type MessagesService struct {
	// contains filtered or unexported fields
}

MessagesService is the conversation of a task: Client.Messages.

func (*MessagesService) Create

func (s *MessagesService) Create(ctx context.Context, workspaceID, taskID string, body CreateMessageRequest, opts ...CallOption) (*CreateMessageResponse, error)

Create sends a follow-up on a task started from the API, CLI, MCP or dashboard (messages.create). It always sends an Idempotency-Key; see WithIdempotencyKey.

func (*MessagesService) List

func (s *MessagesService) List(ctx context.Context, workspaceID, taskID string, params *MessagesListParams) (*MessageList, error)

List returns one page of the task's conversation, oldest first (messages.list). params may be nil. To wait for a reply, poll with After set to the Id of the last entry you read.

func (*MessagesService) ListAll

func (s *MessagesService) ListAll(ctx context.Context, workspaceID, taskID string, params *MessagesListParams) iter.Seq2[Message, error]

ListAll returns every entry after params.After (or from the start), following next page by page. It stops after the first error, which it yields.

type Option

type Option func(*Client)

Option configures NewClient.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL sets the API's address. It defaults to DefaultBaseURL.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sets the *http.Client requests go through. It defaults to http.DefaultClient.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets how long each attempt of a request may take, from sending it to reading the whole response, before it fails with an error that wraps context.DeadlineExceeded. Timed-out attempts aren't retried. It defaults to DefaultTimeout; 0 means no limit. To limit a whole call, retries included, give it a context with a deadline.

func WithToken

func WithToken(token string) Option

WithToken sets the API token (sntr_...). Without it, NewClient reads the SINATRA_TOKEN environment variable.

type PullRequest

type PullRequest struct {
	// Merged `true` once the pull request is merged.
	Merged bool `json:"merged"`

	// Url The pull request's address on GitHub.
	Url string `json:"url"`
}

PullRequest The pull request a task opened.

type StopTaskResponse

type StopTaskResponse struct {
	// Accepted Always `true`: the task was told to stop.
	Accepted StopTaskResponseAccepted `json:"accepted"`
}

StopTaskResponse The stop was accepted.

type StopTaskResponseAccepted

type StopTaskResponseAccepted bool

StopTaskResponseAccepted Always `true`: the task was told to stop.

const (
	StopTaskResponseAcceptedTrue StopTaskResponseAccepted = true
)

Defines values for StopTaskResponseAccepted.

func (StopTaskResponseAccepted) Valid

func (e StopTaskResponseAccepted) Valid() bool

Valid indicates whether the value is a known member of the StopTaskResponseAccepted enum.

type Task

type Task struct {
	// CreatedAt When the task was created.
	CreatedAt time.Time `json:"createdAt"`

	// Id The task's id. Use it as `taskId` in the task endpoints.
	Id string `json:"id"`

	// LastActivityAt When the task last did something. `null` if it hasn't yet.
	LastActivityAt *time.Time `json:"lastActivityAt"`

	// PullRequest The task's pull request. `null` until one opens.
	PullRequest *PullRequest `json:"pullRequest"`

	// Queued `true` while a turn waits because the workspace is already running as many tasks at a time as its plan allows. The dashboard shows this as Queued.
	Queued bool `json:"queued"`

	// Repo `owner/name` of the task's repository. `null` until a repository is chosen.
	Repo *string `json:"repo"`

	// Source Where the task started: `api` for the CLI, MCP, this API or the dashboard, `linear` or `github_issue` for an issue, `slack` or `telegram` for a chat. Other sources can appear, so treat an unknown value as a task started elsewhere.
	Source string `json:"source"`

	// Status Where the task is in its life, such as `pending`, `implementing`, `replied`, `pr_open` or `completed`. New statuses can appear, so don't treat the list as closed. See [Raw statuses](https://docs.sinatra.dev/docs/getting-started/how-it-works#raw-statuses) for each one.
	Status string `json:"status"`

	// Title The task's title: its issue's title, or a chat task's name. `null` for a task whose issue has no title, or a chat task not yet named.
	Title *string `json:"title"`

	// Url The task's page in the Sinatra dashboard, where anyone in the workspace can follow it. On a task started from the CLI, MCP, API or dashboard they can reply there too.
	Url *string `json:"url"`
}

Task A task: one piece of work Sinatra does, from any source.

type TaskList

type TaskList struct {
	// NextCursor Pass it back as `cursor` to get the next page. Treat it as opaque. `null` on the last page.
	NextCursor *string `json:"nextCursor"`

	// Tasks The page's tasks, most recently active first.
	Tasks []Task `json:"tasks"`
}

TaskList A page of tasks.

type TasksCreateJSONRequestBody

type TasksCreateJSONRequestBody = CreateTaskRequest

TasksCreateJSONRequestBody defines body for TasksCreate for application/json ContentType.

type TasksCreateParams

type TasksCreateParams struct {
	// IdempotencyKey Makes a retry safe. 1 to 128 printable ASCII characters, with no spaces. A request repeated with the same key answers with the first one's result and does the work once; the same key with different content is `409 idempotency_conflict`. Keys belong to the person who sent them: two people using the same key start two tasks or send two messages. The key is also the `messageId` of the message the request sends, which is a start's first message.
	IdempotencyKey *IdempotencyKey `json:"Idempotency-Key,omitempty"`
}

TasksCreateParams defines parameters for TasksCreate.

type TasksListParams

type TasksListParams struct {
	// Status Only tasks in this status, such as `implementing` or `pr_open`. A name that isn't a task status is `invalid_status`.
	Status *string `form:"status,omitempty" json:"status,omitempty"`

	// Limit How many tasks to return, 1 to 100.
	Limit *int `form:"limit,omitempty" json:"limit,omitempty"`

	// Cursor `nextCursor` from the previous page. Treat it as opaque.
	Cursor *string `form:"cursor,omitempty" json:"cursor,omitempty"`
}

TasksListParams defines parameters for TasksList.

type TasksService

type TasksService struct {
	// contains filtered or unexported fields
}

TasksService is a workspace's tasks: Client.Tasks.

func (*TasksService) Create

func (s *TasksService) Create(ctx context.Context, workspaceID string, body CreateTaskRequest, opts ...CallOption) (*CreateTaskResponse, error)

Create starts a task (tasks.create). It returns once the task is made, not when it's done: follow it with Get and Messages.ListAll. It always sends an Idempotency-Key; see WithIdempotencyKey.

func (*TasksService) Get

func (s *TasksService) Get(ctx context.Context, workspaceID, taskID string) (*Task, error)

Get returns one task, wherever it started (tasks.get).

func (*TasksService) List

func (s *TasksService) List(ctx context.Context, workspaceID string, params *TasksListParams) (*TaskList, error)

List returns one page of the workspace's tasks, most recently active first (tasks.list). params may be nil. ListAll follows the pages for you.

func (*TasksService) ListAll

func (s *TasksService) ListAll(ctx context.Context, workspaceID string, params *TasksListParams) iter.Seq2[Task, error]

ListAll returns every task from params.Cursor on (or from the start), following nextCursor page by page. It stops after the first error, which it yields.

func (*TasksService) Stop

func (s *TasksService) Stop(ctx context.Context, workspaceID, taskID string) (*StopTaskResponse, error)

Stop stops the task's running turn (tasks.stop). A task that isn't running is 409 not_running. Retried on 503 only.

type Ticket

type Ticket struct {
	// Context The Linear team's name, or the GitHub repository's name.
	Context *string `json:"context"`

	// Id The issue's id on Sinatra's side. For GitHub it looks like `github:owner/name#42`.
	Id string `json:"id"`

	// Identifier What to delegate it by. A Linear identifier such as `ENG-123` (send it as `id`), or a GitHub `owner/name#42` (send `owner/name` as `repo` and `42` as `number`).
	Identifier string `json:"identifier"`

	// Source Where the issue lives: `linear` or `github`. New sources can appear.
	Source string `json:"source"`

	// State A GitHub issue's state, such as `open`. `null` for a Linear issue.
	State *string `json:"state"`

	// Title The issue's title.
	Title string `json:"title"`

	// Url The issue's address on its tracker.
	Url string `json:"url"`

	// WorkspaceId The id of the workspace that was searched.
	WorkspaceId string `json:"workspaceId"`

	// WorkspaceLabel The name the dashboard's ticket picker gives that workspace.
	WorkspaceLabel string `json:"workspaceLabel"`

	// Yours `true` if the issue is assigned to you or you wrote it.
	Yours bool `json:"yours"`
}

Ticket An open issue you could delegate to Sinatra.

type TicketSearchResult

type TicketSearchResult struct {
	// Github The workspace's GitHub connection.
	Github struct {
		// Connected Whether the workspace is connected to GitHub.
		Connected bool `json:"connected"`

		// Repo The repository the GitHub search covered, when the `repo` query matched an enabled one and had open issues. Otherwise `null`.
		Repo *string `json:"repo"`
	} `json:"github"`

	// Linear The workspace's Linear connection, as it applies to you.
	Linear struct {
		// Connected Whether the workspace is connected to Linear.
		Connected bool `json:"connected"`

		// IdentityBound Whether your Linear identity is tied to your Sinatra user.
		IdentityBound bool `json:"identityBound"`

		// IdentityLinked Whether your Linear account is linked in this workspace. Linear issues are listed only when it is.
		IdentityLinked bool `json:"identityLinked"`
	} `json:"linear"`

	// Tickets The issues you could delegate. Linear issues come first, then GitHub's, each with yours before your team's unassigned ones.
	Tickets []Ticket `json:"tickets"`
}

TicketSearchResult The issues you could delegate, and how the workspace is connected to each tracker.

type TicketsDelegateJSONRequestBody

type TicketsDelegateJSONRequestBody = DelegateTicketRequest

TicketsDelegateJSONRequestBody defines body for TicketsDelegate for application/json ContentType.

type TicketsSearchParams

type TicketsSearchParams struct {
	// Q Text to match: GitHub search terms, and a substring of a Linear issue's title. Up to 200 characters.
	Q *string `form:"q,omitempty" json:"q,omitempty"`

	// Repo `owner/name` of an enabled repository to search first on GitHub. A repository that isn't enabled is ignored.
	Repo *string `form:"repo,omitempty" json:"repo,omitempty"`
}

TicketsSearchParams defines parameters for TicketsSearch.

type TicketsService

type TicketsService struct {
	// contains filtered or unexported fields
}

TicketsService is the issues you could delegate: Client.Tickets.

func (*TicketsService) Delegate

func (s *TicketsService) Delegate(ctx context.Context, workspaceID string, ticket DelegateTarget) (*DelegateTicketResponse, error)

Delegate hands Sinatra an existing issue, as you (tickets.delegate):

client.Tickets.Delegate(ctx, workspaceID, sinatra.DelegateLinearTicket{Id: "ENG-123"})
client.Tickets.Delegate(ctx, workspaceID, sinatra.DelegateGithubTicket{Repo: "acme/web", Number: 42})

Never retried: the operation takes no Idempotency-Key.

func (*TicketsService) Search

func (s *TicketsService) Search(ctx context.Context, workspaceID string, params *TicketsSearchParams) (*TicketSearchResult, error)

Search returns open Linear and GitHub issues you could delegate (tickets.search). params may be nil.

type Workspace

type Workspace struct {
	// Id The workspace's id. Use it as `workspaceId` in the workspace endpoints.
	Id string `json:"id"`

	// Name The workspace's name.
	Name string `json:"name"`

	// Role Your role in it: `owner`, `admin` or `member`. New roles can appear.
	Role string `json:"role"`
}

Workspace A workspace you belong to.

Directories

Path Synopsis
examples
delegate-issue command
Command delegate-issue hands Sinatra an issue you already have, the way assigning it in Linear or labelling it on GitHub would.
Command delegate-issue hands Sinatra an issue you already have, the way assigning it in Linear or labelling it on GitHub would.
open-prs command
Command open-prs prints every pull request Sinatra has open and waiting for review, in each of your workspaces, as a Markdown list you can paste into a standup or a Slack channel.
Command open-prs prints every pull request Sinatra has open and waiting for review, in each of your workspaces, as a Markdown list you can paste into a standup or a Slack channel.
quickstart command
Command quickstart is README.md's quick start as a whole program: it starts a task in your first workspace and prints the conversation until the turn ends.
Command quickstart is README.md's quick start as a whole program: it starts a task in your first workspace and prints the conversation until the turn ends.
search-issues command
Command search-issues lists open Linear and GitHub issues you could hand to Sinatra, yours first.
Command search-issues lists open Linear and GitHub issues you could hand to Sinatra, yours first.

Jump to

Keyboard shortcuts

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