integrations

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNotConnected = errors.New("not connected — authorize this integration first")

ErrNotConnected means no token is stored for this integration.

Distinct from a refresh failure on purpose: this one is fixed by a human pressing Authorize, and a collector that says "not connected" sends them there instead of into logs.

Functions

func ByCategory

func ByCategory() map[Category][]Integration

ByCategory groups the catalogue for the gallery.

func Categories

func Categories() []struct {
	Key   Category `json:"key"`
	Title Text     `json:"title"`
	Help  Text     `json:"help"`
}

Categories in display order, with their labels.

func Register

func Register(i Integration)

Register adds an integration. Called from init() in the per-category files.

func SetCreator

func SetCreator(c Creator)

SetCreator installs the connection creator. Called once during boot.

func SetCredentialStore

func SetCredentialStore(v interface {
	RevealSystem(ctx context.Context, name, reason string) (string, error)
	HasSecret(ctx context.Context, name string) bool
})

SetCredentialStore installs the vault reader. Called once during boot.

func SetCredentialWriter

func SetCredentialWriter(w CredentialWriter)

SetCredentialWriter installs the vault writer. Called once during boot.

func SetPlanner

func SetPlanner(p Planner)

SetPlanner installs the model client. Called once during boot.

Types

type Auth

type Auth string

Auth is HOW an operator connects, and it decides what the Connect button does.

const (
	// AuthTerminal opens an interactive session and runs the vendor's login.
	//
	// The important property: we never see or store the credential. `gh auth
	// login` writes gh's own config file; `claude login` writes Claude Code's.
	// That is why these integrations are not blocked by SF-001 — there is no
	// secret of ours to grant.
	AuthTerminal Auth = "terminal"
	// AuthToken stores a named secret in the vault (SF-001 applies).
	AuthToken Auth = "token"
	// AuthDSN stores a connection string in the vault (SF-001 applies).
	AuthDSN Auth = "dsn"
	// AuthOAuth is a redirect dance. Not implemented yet; declared so the
	// gallery can show "coming soon" honestly rather than offering a button
	// that fails.
	AuthOAuth Auth = "oauth"
	// AuthNone needs no credential at all — a public RSS feed, a self-hosted
	// SearXNG with anonymous access.
	AuthNone Auth = "none"
)

type AuthState

type AuthState struct {
	State       string
	Verifier    string
	Challenge   string
	RedirectURI string
}

AuthState is one in-flight authorization.

func NewAuthState

func NewAuthState(redirectURI string) (AuthState, error)

NewAuthState mints the CSRF state and the PKCE pair.

type Category

type Category string

Category groups integrations in the gallery.

const (
	// CatTerminal — connect by running the vendor's own login in a terminal.
	CatTerminal Category = "terminal"
	// CatAPI — token or OAuth; can usually both receive and send.
	CatAPI Category = "api"
	// CatDatabase — a DSN we connect with directly.
	CatDatabase Category = "database"
	// CatReader — read-only collectors. No actions, by nature not by omission.
	CatReader Category = "reader"
	// CatAnalytics — measurement properties and submission endpoints.
	CatAnalytics Category = "analytics"
)

type Creator

type Creator interface {
	CreateFromPlan(ctx context.Context, kind, name string, config json.RawMessage) (string, error)
	TestConnection(ctx context.Context, id string) error
}

Creator makes a connection once the plan is validated.

Satisfied by *sources.Store. Narrow on purpose: this package may create a connection and test it, and nothing else.

type CredentialWriter

type CredentialWriter interface {
	PutSystem(ctx context.Context, name, value, reason string) error
}

CredentialWriter stores installation-owned credentials.

type Integration

type Integration struct {
	Slug     string   `json:"slug"`
	Title    Text     `json:"title"`
	Summary  Text     `json:"summary"`
	Category Category `json:"category"`
	Auth     Auth     `json:"auth"`

	// Icon is a glyph name from the shell's inlined set.
	Icon  string `json:"icon"`
	Color string `json:"color"`

	// Collects: can pull content into the brain.
	Collects bool `json:"collects"`
	// Acts: can send outward. False here is a STATEMENT — see the package doc.
	Acts bool `json:"acts"`

	// ActorKind links to internal/actors when Acts is true. Empty otherwise.
	ActorKind string `json:"actorKind,omitempty"`
	// SourceKind links to internal/sources when Collects is true.
	SourceKind string `json:"sourceKind,omitempty"`

	// Inputs is a JSON Schema for this integration's own configuration. One
	// schema, two consumers: the form the operator fills, and server-side
	// validation. Two copies is how a field ends up optional in one and
	// required in the other.
	Inputs json.RawMessage `json:"inputs"`

	// Terminal is set when Auth is AuthTerminal.
	Terminal *Terminal `json:"terminal,omitempty"`

	// DocsURL is the vendor's own setup page.
	DocsURL string `json:"docsUrl,omitempty"`

	// Beta marks an integration that is declared but not finished. The gallery
	// shows it as unavailable rather than hiding it, so "is X supported?" has a
	// visible answer.
	Beta bool `json:"beta,omitempty"`
}

Integration is one entry in the catalogue.

func All

func All() []Integration

All returns the catalogue, ordered for display: by category in the order the gallery shows them, then alphabetically within a category.

func Get

func Get(slug string) (Integration, bool)

Get returns one integration.

type OAuthStore

type OAuthStore interface {
	SaveState(ctx context.Context, st AuthState, integration, createdBy string) error
	// TakeState consumes a state, returning it exactly once. A replayed
	// callback must not succeed twice.
	TakeState(ctx context.Context, state string) (AuthState, string, error)
	SaveToken(ctx context.Context, integration string, tok Token) error
}

OAuthStore persists in-flight states and issued tokens.

type Planner

type Planner interface {
	Plan(ctx context.Context, prompt string) (string, error)
}

Planner runs one bounded model call and returns its text.

An interface rather than a concrete session so this package does not depend on the runner, and so a test can plan without spending money.

type Provider

type Provider struct {
	// AuthURL and TokenURL are the vendor's endpoints.
	AuthURL  string
	TokenURL string
	// Scopes are what we ask for. Least privilege: read-only wherever the
	// vendor offers a read-only scope, because an integration that collects
	// analytics has no business being able to change them.
	Scopes []string
	// ClientIDEnv / ClientSecretEnv name the env vars holding the app
	// credentials. The NAMES are declared; the values are never in code.
	ClientIDEnv     string
	ClientSecretEnv string
	// Extra query parameters on the authorize leg. Google needs
	// access_type=offline to issue a refresh token at all, and prompt=consent
	// to reissue one when the user has authorised before — without both, a
	// second authorization silently returns no refresh token and the
	// integration dies an hour later.
	AuthParams map[string]string
	// UsesPKCE — send a code challenge. Harmless where supported.
	UsesPKCE bool
	// AccountFrom names the field in the token or userinfo response that
	// identifies the account, so the UI can say whose it is.
	AccountFrom string
}

Provider is the per-vendor half of an OAuth flow — all data.

func ProviderFor

func ProviderFor(slug string) (string, Provider, bool)

ProviderFor returns the OAuth provider an integration authorises against.

func (Provider) AuthorizeURL

func (p Provider) AuthorizeURL(ctx context.Context, st AuthState) (string, error)

AuthorizeURL builds the redirect the operator's browser follows.

func (Provider) Configured

func (p Provider) Configured(ctx context.Context) (bool, string)

Configured reports whether the operator has supplied this provider's app credentials.

Checked BEFORE showing a Connect button: an OAuth button that redirects to a vendor error page because no client id is set is worse than a card that says what is missing.

type SQLOAuthStore

type SQLOAuthStore struct{ DB *sql.DB }

func (SQLOAuthStore) SaveState

func (s SQLOAuthStore) SaveState(ctx context.Context, st AuthState, integration, by string) error

func (SQLOAuthStore) SaveToken

func (s SQLOAuthStore) SaveToken(ctx context.Context, integration string, tok Token) error

func (SQLOAuthStore) TakeState

func (s SQLOAuthStore) TakeState(ctx context.Context, state string) (AuthState, string, error)

type Service

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

func New

func New(log *slog.Logger, t Tmux, o OAuthStore) *Service

func (*Service) OAuthAppRoutes

func (s *Service) OAuthAppRoutes(r chi.Router)

OAuthAppRoutes mounts the provider-configuration surface.

func (*Service) OAuthRoutes

func (s *Service) OAuthRoutes(r chi.Router)

OAuthRoutes registers the flow. Mounted alongside the rest of the service.

func (*Service) Routes

func (s *Service) Routes(r chi.Router)

func (*Service) SmartRoutes

func (s *Service) SmartRoutes(r chi.Router)

func (*Service) WithTokens

func (s *Service) WithTokens(t interface {
	Connected(ctx context.Context, integration string) (bool, string)
}) *Service

WithTokens supplies the OAuth token reader used for status badges.

type SessionPlanner

type SessionPlanner struct {
	Log *slog.Logger
	// Model is optional; empty uses whatever the CLI is configured with.
	Model string
	// Dir is where the session runs. It reads nothing, but the CLI wants a
	// working directory that exists.
	Dir string
}

SessionPlanner runs the plan prompt through the Claude Code CLI.

func (SessionPlanner) Plan

func (p SessionPlanner) Plan(ctx context.Context, prompt string) (string, error)

Plan asks the model for a connection plan and returns its raw reply.

type State

type State string

State is what the badge shows.

const (
	// StateConnected — the tool answered yes.
	StateConnected State = "connected"
	// StateDisconnected — the tool is installed and says it is not signed in.
	StateDisconnected State = "disconnected"
	// StateMissing — the binary is not on PATH. Distinct from disconnected on
	// purpose: one is fixed by signing in, the other by installing, and telling
	// an operator to sign in to something they have not installed is the kind
	// of advice that wastes an afternoon.
	StateMissing State = "missing"
	// StateUnknown — the check itself failed to run or timed out.
	StateUnknown State = "unknown"
)

type Status

type Status struct {
	Slug    string `json:"slug"`
	State   State  `json:"state"`
	Version string `json:"version,omitempty"`
	// Detail is a short, already-scrubbed line for the UI.
	Detail string `json:"detail,omitempty"`
	// Install is populated when State is StateMissing.
	//
	// omitzero, not omitempty: omitempty has no effect on a struct, so this
	// would have serialised {"en":"","ar":""} on every status the UI renders —
	// and a truthy-looking empty object is exactly what makes a client show an
	// install hint to someone who has the tool installed.
	Install Text `json:"install,omitzero"`
}

func Probe

func Probe(ctx context.Context, i Integration) Status

Probe asks one integration whether it is connected.

Never returns an error: every failure mode is a State the UI can render, and an error here would just be turned back into one by the caller.

func ProbeAll

func ProbeAll(ctx context.Context) []Status

ProbeAll checks every terminal integration concurrently.

Serially this is three subprocess round trips on a screen open; the slowest one sets the wait either way, so they run together.

type Terminal

type Terminal struct {
	// Bin is the executable, checked for presence before anything else. A
	// missing binary is the most common reason a connect does nothing, and it
	// deserves "gh is not installed" rather than a blank terminal.
	Bin string `json:"bin"`
	// Install is what to tell the operator when Bin is absent.
	Install Text `json:"install"`
	// Login is the interactive command. It runs in a tmux session the operator
	// attaches to, because these prompts ask questions — a device code to
	// paste, a browser to approve, a project to pick — and answering them is
	// the whole point.
	Login []string `json:"login"`
	// Status is a NON-interactive command whose exit code answers "is this
	// connected?". It must never block waiting for input; a status check that
	// hangs looks identical to one that failed.
	Status []string `json:"status"`
	// Version reports the installed version for display.
	Version []string `json:"version"`
	// Logout undoes it, so an operator can disconnect from the same screen.
	Logout []string `json:"logout"`
}

Terminal describes how a CatTerminal integration logs in and reports status.

type Text

type Text struct {
	EN string `json:"en"`
	AR string `json:"ar"`
}

Text is a localized label.

type Tmux

type Tmux interface {
	// Ensure creates the session if absent, and reports the name.
	Ensure(ctx context.Context, name string) error
	// SendKeys types a command into it, as if the operator had.
	SendKeys(ctx context.Context, name string, args ...string) error
}

Tmux is the slice of terminal control this package needs.

func NewTmux

func NewTmux(workdir string) Tmux

NewTmux returns a Tmux backed by the real binary.

type Token

type Token struct {
	Account      string
	AccessToken  string
	RefreshToken string
	Scopes       string
	TokenType    string
	ExpiresAt    *time.Time
}

type Tokens

type Tokens struct {
	DB *sql.DB
	// contains filtered or unexported fields
}

Tokens hands out valid access tokens, refreshing as needed.

func NewTokens

func NewTokens(db *sql.DB) *Tokens

func (*Tokens) AccessToken

func (t *Tokens) AccessToken(ctx context.Context, integration string) (string, error)

AccessToken returns a token good for at least refreshMargin.

Takes an INTEGRATION slug and resolves it to the provider that issued the token, because one Google grant serves five Google integrations — see the note on SaveToken in the callback.

func (*Tokens) Connected

func (t *Tokens) Connected(ctx context.Context, integration string) (bool, string)

Connected reports whether an integration has a usable token, for the gallery.

Deliberately does NOT refresh: this runs for every OAuth card on every page load, and refreshing there would spend a rotation just to draw a badge.

Jump to

Keyboard shortcuts

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