api

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 11, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package api is the typed HTTP client for the positronick.com read API. It owns the wire types, the error envelope, auth-header injection, and the retry policy; callers receive decoded structs or a typed *APIError.

Index

Constants

This section is empty.

Variables

View Source
var ListingTypes = []string{"harness", "cli", "mcp", "agent", "skill", "plugin", "loop"}

ListingTypes are the kinds of official tooling the registry catalogs. Mirrors LISTING_TYPES in src/lib/types.ts.

Functions

func IsAuthError

func IsAuthError(err error) bool

IsAuthError reports whether err is an APIError for missing or insufficient authentication (401 or 403).

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is an APIError for a missing resource (404).

Types

type APIError

type APIError struct {
	// Status is the HTTP status code.
	Status int
	// Code is the machine-readable code from the envelope ("" when absent).
	Code string
	// Message is human-readable; never empty.
	Message string
}

APIError is a non-2xx response from the API, decoded from the server's error envelope {"error":{"code":"...","message":"..."}} when present, or synthesized from the HTTP status text when the body is not the envelope (e.g. a proxy answering with HTML).

func (*APIError) Error

func (e *APIError) Error() string

Error implements the error interface.

type APIKey

type APIKey struct {
	ID        string  `json:"id"`
	Name      string  `json:"name"`
	Start     *string `json:"start"`
	Prefix    *string `json:"prefix"`
	Key       string  `json:"key"`
	ExpiresAt *string `json:"expiresAt"`
}

APIKey is the POST /api/auth/api-key/create response. Key is the raw secret — the server stores only a hash, so this is the one and only time it is visible. ExpiresAt stays an ISO-8601 string (or null for no expiry).

type AdminListing

type AdminListing struct {
	Listing
	Source string `json:"source"`
}

AdminListing is a listing as the admin API returns it, plus Source (same semantics as AdminSoul.Source).

type AdminSoul

type AdminSoul struct {
	Soul
	Source string `json:"source"`
}

AdminSoul is a soul as the admin API returns it: the public shape plus Source, the ownership marker ("seed" = owned by the git content pipeline, "api" = owned by the write API; the seed skips api-owned rows).

type Anonymous

type Anonymous struct{}

Anonymous is a CredentialsProvider that never authenticates.

func (Anonymous) Get

func (Anonymous) Get() (Credentials, error)

Get implements CredentialsProvider.

type Client

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

Client is a positronick.com API client. Construct it with New; the zero value is not usable.

func New

func New(baseURL string, creds CredentialsProvider, opts ...Option) (*Client, error)

New returns a Client for the API at baseURL. The scheme must be http or https; a trailing slash is stripped. A nil creds means anonymous. Redirects are followed (the server 301s historical soul slugs to current ones).

func (*Client) AdminListing

func (c *Client) AdminListing(ctx context.Context, id string) (*AdminListing, error)

AdminListing fetches one listing by id, any status: GET /api/admin/listings/{id}.

func (*Client) AdminSoul

func (c *Client) AdminSoul(ctx context.Context, id string) (*AdminSoul, error)

AdminSoul fetches one soul by id, any status: GET /api/admin/souls/{id}.

func (*Client) CreateAPIKey

func (c *Client) CreateAPIKey(ctx context.Context, name string, expiresIn int) (*APIKey, error)

CreateAPIKey mints a new API key: POST /api/auth/api-key/create. The server only allows session (bearer) credentials to mint keys — an API key cannot create more API keys. expiresIn is in seconds; 0 means the server default.

func (*Client) CreateListing

func (c *Client) CreateListing(ctx context.Context, fields map[string]any) (*AdminListing, error)

CreateListing creates a registry listing: POST /api/admin/listings. The authoring profile is referenced by handle (profileHandle) and must already exist — profiles stay git-curated; the server answers 422 unknown_profile otherwise.

func (*Client) CreateSoul

func (c *Client) CreateSoul(ctx context.Context, fields map[string]any) (*AdminSoul, error)

CreateSoul creates a soul: POST /api/admin/souls. fields is sent verbatim as the JSON body — the server validates with the seed's own validator and answers 422 with the validator message, 409 on a slug conflict. The id is server-assigned; the server rejects a client-supplied one.

func (*Client) Listing

func (c *Client) Listing(ctx context.Context, slug string) (*Listing, error)

Listing fetches one registry listing: GET /api/listings/{slug}.

func (*Client) Listings

func (c *Client) Listings(ctx context.Context, listingType string) ([]Listing, error)

Listings fetches registry listings: GET /api/listings. An empty listingType returns all listings; otherwise ?type= is sent and the server rejects unknown types with an invalid_type error.

func (*Client) Me

func (c *Client) Me(ctx context.Context) (*Me, error)

Me fetches the authenticated identity: GET /api/me. Without valid credentials the server answers 401, surfaced as an *APIError for which IsAuthError is true.

func (*Client) Soul

func (c *Client) Soul(ctx context.Context, slug string) (*Soul, error)

Soul fetches one soul with its markdown body: GET /api/souls/{slug}. A renamed slug is followed via the server's 301; a missing one surfaces as an *APIError for which IsNotFound is true.

func (*Client) SoulMarkdown

func (c *Client) SoulMarkdown(ctx context.Context, slug string) (string, error)

SoulMarkdown fetches the raw SOUL.md body verbatim: GET /api/souls/{slug}.md. This is the install contract — the returned string is byte-identical to what `curl .../api/souls/{slug}.md` writes, so installs are lossless.

func (*Client) Souls

func (c *Client) Souls(ctx context.Context) ([]SoulCard, error)

Souls fetches the full soul gallery: GET /api/souls.

func (*Client) UpdateListing

func (c *Client) UpdateListing(ctx context.Context, id string, patch map[string]any) (*AdminListing, bool, error)

UpdateListing patches a listing: PATCH /api/admin/listings/{id}. A profileHandle in the patch is re-resolved server-side. tookOwnership has the same semantics as UpdateSoul.

func (*Client) UpdateSoul

func (c *Client) UpdateSoul(ctx context.Context, id string, patch map[string]any) (*AdminSoul, bool, error)

UpdateSoul patches a soul: PATCH /api/admin/souls/{id}. patch carries only the fields to change. tookOwnership is true when the row was seed-owned and this update flipped it to api-owned — the caller must warn that the git copy is now inert and will be skipped by deploys.

type Credentials

type Credentials struct {
	// APIKey is sent as the x-api-key header.
	APIKey string
	// Bearer is sent as "Authorization: Bearer ..." when APIKey is empty.
	Bearer string
}

Credentials carries the secrets a request can authenticate with. APIKey always wins: when set, the bearer token is not sent at all.

type CredentialsProvider

type CredentialsProvider interface {
	Get() (Credentials, error)
}

CredentialsProvider supplies credentials per request, so tokens refreshed or exported mid-process are picked up. The future auth package implements this; the client only depends on the interface.

type EnvCredentials

type EnvCredentials struct{}

EnvCredentials reads POSITRONICK_API_KEY from the environment at call time (not at construction), so a key exported after client setup still applies.

func (EnvCredentials) Get

func (EnvCredentials) Get() (Credentials, error)

Get implements CredentialsProvider.

type Listing

type Listing struct {
	// ID is the stable, immutable id (ULID).
	ID string `json:"id"`
	// Slug is the human-facing url segment: /listings/[slug].
	Slug string `json:"slug"`
	// ProfileHandle/ProfileName denormalize the authoring profile for cards.
	ProfileHandle string `json:"profileHandle"`
	ProfileName   string `json:"profileName"`
	Name          string `json:"name"`
	// Type is one of ListingTypes.
	Type string `json:"type"`
	// Tagline is the short one-liner shown on cards.
	Tagline     string   `json:"tagline"`
	Description *string  `json:"description"`
	Category    string   `json:"category"`
	Tags        []string `json:"tags"`
	// Official is true when published by the owner's verified official account.
	Official bool `json:"official"`
	// SourceURL is the official source that was verified: repo, docs, or registry.
	SourceURL string  `json:"sourceUrl"`
	RepoURL   *string `json:"repoUrl"`
	// InstallCmd is the canonical official install/run command, if any.
	InstallCmd *string `json:"installCmd"`
	// Data holds type-specific extras (e.g. LoopData for loops); {} when none.
	Data          map[string]any `json:"data"`
	Confidence    string         `json:"confidence"`
	Status        string         `json:"status"`
	DownloadCount int            `json:"downloadCount"`
	CreatedAt     string         `json:"createdAt"`
	UpdatedAt     string         `json:"updatedAt"`
}

Listing is a catalog entry for one official tool. Mirrors Listing in src/lib/types.ts.

func (*Listing) LoopData

func (l *Listing) LoopData() (LoopData, error)

LoopData decodes the untyped Data payload into LoopData. Empty or nil Data yields the zero value; a wrong-typed field is an error, never a silent zero.

type LoopData

type LoopData struct {
	// Goal is what "done" looks like for the loop.
	Goal string `json:"goal,omitempty"`
	// CheckCommand is the command run between iterations to gauge progress.
	CheckCommand string `json:"checkCommand,omitempty"`
	// ExitCondition is the condition that ends the loop.
	ExitCondition string `json:"exitCondition,omitempty"`
	// MaxIterations is the safety cap on iterations.
	MaxIterations int `json:"maxIterations,omitempty"`
	// CompatibleTools lists agents/harnesses the loop is known to work with.
	CompatibleTools []string `json:"compatibleTools,omitempty"`
	// Kickoff is the prompt a user copies to start the loop.
	Kickoff string `json:"kickoff,omitempty"`
}

LoopData is the type-specific extras for a `loop` listing, stored in Listing.Data. Mirrors LoopData in src/lib/types.ts.

type Me

type Me struct {
	User    User `json:"user"`
	IsAdmin bool `json:"isAdmin"`
}

Me is the GET /api/me response. IsAdmin is computed server-side; the CLI caches it but never decides it.

type Option

type Option func(*Client)

Option customizes a Client.

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient replaces the underlying *http.Client (e.g. for a custom transport). The caller owns the timeout configuration.

func WithSleep

func WithSleep(fn func(time.Duration)) Option

WithSleep replaces the backoff sleep function — a test seam so retry tests assert waits instead of serving them.

type Soul

type Soul struct {
	SoulCard
	Content string `json:"content"`
}

Soul is a full soul, including the raw SOUL.md markdown body. Mirrors Soul in src/lib/types.ts (SoulMeta + content).

type SoulCard

type SoulCard struct {
	// ID is the stable, immutable id (ULID). Never changes, even if the slug does.
	ID string `json:"id"`
	// Slug is the human-facing url segment: /souls/[slug].
	Slug string `json:"slug"`
	// SlugHistory holds previous slugs; the server 301s them to the current slug.
	SlugHistory []string `json:"slugHistory"`
	Name        string   `json:"name"`
	// AuthorHandle is the author handle — the join key for user accounts.
	AuthorHandle string  `json:"authorHandle"`
	AuthorName   *string `json:"authorName"`
	AuthorURL    *string `json:"authorUrl"`
	// Tagline is the short one-liner shown on cards.
	Tagline     string   `json:"tagline"`
	Description *string  `json:"description"`
	Category    string   `json:"category"`
	Tags        []string `json:"tags"`
	Frameworks  []string `json:"frameworks"`
	Models      []string `json:"models"`
	// Version is semver.
	Version string `json:"version"`
	// License is an SPDX license id, e.g. "MIT".
	License string  `json:"license"`
	RepoURL *string `json:"repoUrl"`
	// ContentHash is the sha256 of the normalized SOUL.md body.
	ContentHash string `json:"contentHash"`
	// Status is draft | pending | published.
	Status        string   `json:"status"`
	DownloadCount int      `json:"downloadCount"`
	RatingAvg     *float64 `json:"ratingAvg"`
	RatingCount   int      `json:"ratingCount"`
	ArenaRank     *int     `json:"arenaRank"`
	CreatedAt     string   `json:"createdAt"`
	UpdatedAt     string   `json:"updatedAt"`
}

SoulCard is the lightweight list/gallery view of a soul — everything needed to render a card or detail header, deliberately excluding the heavy markdown body. Mirrors SoulCard (= SoulMeta) in src/lib/types.ts.

Nullable fields in the TS contract (string | null, number | null) are pointers so that null round-trips as null in --json output.

type User

type User struct {
	ID          string  `json:"id"`
	Name        string  `json:"name"`
	Email       string  `json:"email"`
	Image       *string `json:"image"`
	GithubLogin *string `json:"githubLogin"`
}

User is the authenticated identity returned by GET /api/me. Image and GithubLogin are nullable (e.g. a Google-only account has no GitHub login), so they are pointers and null round-trips as null in --json output.

Jump to

Keyboard shortcuts

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