connect

package
v0.2.1-rc.1 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: Apache-2.0 Imports: 35 Imported by: 0

Documentation

Overview

Package connect is the accounts layer: it holds the keys that let codeaf act on a person's own SaaS accounts — their mail, their calendar — and hands the rest of the program one ready, self-refreshing http.Client per account.

The registry is the design. Google is one plug and not the point — it is the plug that happens to exist today. A service that arrives later declares itself from its own file's init, and no caller, no settings screen and no line of this file changes. A closed switch over service names would put every future vendor's vocabulary in this source and make the package a merge point for work that has nothing to do with connecting.

The package imports nothing of the surface — no session, no TUI, no config. It is given a profile directory to keep its file in and a map of client credentials to sign requests with, both as plain values, so every law here can be tested without a real account or a real browser.

THE PACKAGE NEVER OPENS A BROWSER. Manager.BeginAuth starts the loopback listener and returns the address to visit; whoever owns the screen decides how that address reaches the person.

THE PACKAGE NEVER LOGS A KEY. No access key, refresh key or client secret is printed, returned in an error, or written anywhere but the store file.

Index

Constants

View Source
const (
	CapabilityRead = "read"
	CapabilityAct  = "act"
)

The generic pair's two ids, exported because the judging seam has to NAME them: a key service's one raw-call tool is a read or an act depending on the verb it was given, and the wiring that picks the half must say which half in this package's vocabulary rather than in a string of its own. See RegisterGenericCapabilities and catalog.go's init.

View Source
const (
	AuthBrowser = "browser"
	AuthKey     = "key"
)

The two ways an account is connected, and there are only two.

AuthBrowser is a trip through the person's own browser and the service's own sign-in page: they are asked there, by the service, in the service's words, and what comes back is a set of keys codeaf renews for itself. Google is one.

AuthKey is a key the person already holds and pastes once. Nothing opens, nothing renews, and the key is exactly as good as the day it was made. Every service the catalog (catalog.go) brings is one of these.

View Source
const CapabilityFileName = "connections.json"

CapabilityFileName is the file a person's answers live in, named here so that a doctor screen and a "where are my things" answer can say the same word the code reads.

IT IS NOT THE CREDENTIALS FILE, and the split is deliberate. What is in credentials.json is a secret: keys that open somebody's mailbox, kept at 0600 and never printed. What is in here is a POLICY: four words about what the person agreed to, which they should be able to open, read, copy between machines and hand to anybody helping them without handing over their account as well. Two different things with two different lifetimes do not belong in one file merely because one package writes both.

View Source
const RegistrationFileName = "toolservers.json"

RegistrationFileName is the file codeaf's own identity with each tool server lives in, named here so that a doctor screen and a "where are my things" answer can say the same word the code reads.

── WHY IT IS NOT credentials.json ──

What is in credentials.json is one person's keys. What is in here is the APPLICATION's: the identity a tool server issued to this copy of codeaf when it introduced itself, plus the two addresses that sign-in used. For every other browser service that identity comes from configuration and is the same for everybody running the same build; for these it is minted per machine, so it has to be written down somewhere or the next start would ask the vendor for a second one and leave the first lying in their console forever.

It is kept at 0600 like the keys and unlike the settings, because a tool server may hand back a secret with it.

── AND WHY IT SURVIVES Disconnect ──

Manager.Disconnect is documented as forgetting the person's keys and keeping the client credential, so that a service can be reconnected without any further setup. This file IS the client credential for these services, so it obeys that law by staying: disconnecting Notion and connecting it again is one browser trip, not a second registration.

View Source
const StoreFileName = "credentials.json"

StoreFileName is the file every connection lives in, named here so that a doctor screen and a "where are my things" answer can say the same word the code reads.

Variables

This section is empty.

Functions

func CalendarCreate

func CalendarCreate(ctx context.Context, client *http.Client, title, start, end, attendees, location, description string) (string, error)

CalendarCreate puts one event on the person's main calendar and answers with what is now on it: when, what it is called, who was invited, and the address of the entry itself.

start and end may each be a full timestamp or a bare YYYY-MM-DD date, and THE START DECIDES WHICH KIND OF EVENT THIS IS: a bare date makes an event that takes whole days, a timestamp makes one with hours. A caller who names no end gets an hour for a timed event and the single named day for an all-day one — and THE DAY YOU NAME IS INCLUDED, which is the same law CalendarList reads a window by, even though the service itself counts the closing date as the morning after.

attendees is a comma-separated list of addresses and may be empty. NAMING SOMEBODY INVITES THEM: the service sends them the invitation, because a meeting that appears on one person's calendar and nobody else's is not the thing anybody meant by inviting them.

func CalendarList

func CalendarList(ctx context.Context, client *http.Client, from, to string) (string, error)

CalendarList answers with the events on the person's main calendar between two moments, one line each: when, what, how many people, and where.

from and to may each be a full timestamp or a bare YYYY-MM-DD date. A BARE DATE MEANS THE WHOLE OF THAT DAY, local time — from starts at its first moment and to ends at its last, so asking for the same date twice returns that day rather than nothing at all. An empty from means now; an empty to means a week after from.

func GmailRead

func GmailRead(ctx context.Context, client *http.Client, id string) (string, error)

GmailRead answers with one whole message: the headers worth reading and the body as plain sentences.

func GmailSearch

func GmailSearch(ctx context.Context, client *http.Client, query string, max int) (string, error)

GmailSearch answers a mailbox query with a list of matching messages, one block each: the identifier to read it with, when it arrived, who sent it, its subject and its opening line.

query is the service's own search language, passed through untouched — "from: alice is:unread", "has:attachment newer_than:7d" — because a model that knows that language should not have it taken away, and one that does not can still pass plain words.

func GmailSend

func GmailSend(ctx context.Context, client *http.Client, to, cc, subject, body string) (string, error)

GmailSend writes one message and sends it, and answers with what left: the subject, who it went to, and the identifier it now has in the mailbox — the same "id …" a search prints, so the sent message can be opened straight back.

to and cc are comma-separated addresses; cc may be empty. A message needs somebody to go to and something to say, and nothing else here is required.

THE MESSAGE IS BUILT ONE HEADER PER LINE AND EVERY VALUE IS FOLDED FLAT. A subject or an address carrying a line break would end that header and start one of the caller's own — a blind copy nobody asked for, a reply-to somewhere else — so every value that goes onto a header line is collapsed to a single line before it does, and the body starts only after the one blank line that separates it.

func MCPToolCapability

func MCPToolCapability(tool MCPTool) string

MCPToolCapability answers which of the two generic capabilities owns one tool.

── WHY THIS IS A FUNCTION AND NOT A MAP ──

RegisterCapabilities takes a map from tool name to capability, written at init from a list somebody typed. A tool server's tools are not knowable at init: they are fetched per account, they differ between two people's Notion accounts, and the service may add one tomorrow. So the map for these services is empty and this is the door instead — the same question, asked of a tool rather than of a name.

It is deliberately NOT a Manager method: the answer depends on the tool and nothing else, so there is nothing for a manager to contribute.

func Register

func Register(p Plug)

Register adds a plug. Meant to be called from a package file's init, which is why it panics rather than returning an error: a plug that failed to register would not fail at registration but silently later, by being absent from a menu nobody thought to check.

func RegisterCapabilities

func RegisterCapabilities(service string, capabilities []Capability, tools map[string]string)

RegisterCapabilities declares what one service can be used for. Like Register it is meant to be called from a package file's init, and like Register it panics rather than returning an error: a declaration that failed would not fail here but silently later, as a settings panel missing a row nobody thought to check.

THE DECLARATION LIVES WITH THE PLUG. A switch over service names in this file would put every future vendor's vocabulary in one source and make this package a merge point for work that has nothing to do with capabilities — the same reason Register exists at all.

tools maps a tool name to the ID of the capability that owns it. Several tools may share one capability; a tool may belong to at most one.

func RegisterGenericCapabilities

func RegisterGenericCapabilities(service string, tools map[string]string)

RegisterGenericCapabilities declares the read/act pair for a service that has no sentences of its own to say.

It is the door for the services that arrive as DATA rather than as code — a catalog of key-based connections, where nobody has written a file per vendor and nobody is going to. Those services still divide the same way every service divides: what only looks, and what leaves the machine in the person's name. So they get the same two rows and the same three states, and the panel cannot tell them apart from a plug that declared four.

func ServiceRequest

func ServiceRequest(ctx context.Context, client *http.Client, service Service, method, path, query, body string) (string, error)

ServiceRequest makes one authenticated call against a service's own address and hands back what it said, bounded.

── WHY THIS IS ONE TOOL AND NOT A HUNDRED ──

There are hundreds of services behind a key and no two of them agree on anything: what a contact is, how a page is asked for, what a date looks like. A hand-written pair of helpers per service is hundreds of files nobody can keep true, and a generic "list the objects" would be a promise the catalog cannot keep — it knows where a service lives and how a key rides on a request, and nothing whatever about what the service holds. So this is the honest shape: the address is ours, the key is ours, the path is the model's, and the service's own documentation is the schema.

THE PATH IS RELATIVE, ALWAYS. An absolute address here would send the person's key to a host nobody vouched for, which is the one thing a signed client must never be talked into.

func SignInFailureReason

func SignInFailureReason(err error) string

SignInFailureReason is owned here because both the session tool and the live surface cross this package, so there is exactly one place that decides which browser failure words may leave it. A SENTENCE NOBODY HERE WROTE IS NOT SHOWN.

func SlackListChannels

func SlackListChannels(ctx context.Context, client *http.Client, filter string, max int) (string, error)

SlackListChannels lists the public and private channels the person can see. Filtering happens locally because Slack's listing method has no name filter.

func SlackReadThread

func SlackReadThread(ctx context.Context, client *http.Client, channel, ts string) (string, error)

SlackReadThread reads one thread in one bounded call. Slack limits an outside-Marketplace application to one of these calls a minute and fifteen messages, so THERE IS NO PAGING LOOP HERE.

func SlackSearch

func SlackSearch(ctx context.Context, client *http.Client, query string, max int) (string, error)

SlackSearch answers a workspace query with the newest matching messages. Each block ends with the channel id and timestamp the thread reader takes.

func SlackSend

func SlackSend(ctx context.Context, client *http.Client, channel, text, threadTS string) (string, error)

SlackSend posts one message, optionally as a reply in a thread.

Types

type Capability

type Capability struct {
	// ID is the stable slug: what the panel keys a row on, what the tool map
	// points at, and what is written to disk. It outlives phrasing changes,
	// so a reworded sentence never loses somebody's answer.
	ID string
	// Phrase is the person's sentence — "read your mail", "send mail as you"
	// — written to be read on a line beside a control, in their vocabulary
	// and never in the machinery's.
	Phrase string
	// Acts reports that this capability REACHES OUTSIDE THIS MACHINE IN THE
	// PERSON'S NAME. It is the same distinction internal/approval draws with
	// its table of tools a blanket allow cannot vouch for, and it is drawn
	// once here per capability rather than per tool: a search that was not
	// wanted costs a moment, a message that was not wanted has been read by
	// somebody else by the time anyone notices.
	//
	// It decides the default state and nothing else. See [defaultState].
	Acts bool
}

Capability is one sentence a person can say yes, ask or off to.

type CapabilityState

type CapabilityState string

CapabilityState is one of the three answers a person can give about a capability. The words are the whole vocabulary of the feature: the panel writes them, the disk keeps them, the gate reads them, and the mid-chat "always" answer writes StateYes exactly as the panel does.

They are a string rather than a number because they are written to a file a person can open, and "yes" in that file says what 0 does not.

const (
	// StateYes is the person's named allow: this may run without asking.
	StateYes CapabilityState = "yes"
	// StateAsk puts the question before every call.
	StateAsk CapabilityState = "ask"
	// StateOff takes the capability away entirely — no tool, no listing, no
	// question. See the package comment on capability.go.
	StateOff CapabilityState = "off"
)

type ClientCredential

type ClientCredential struct {
	ID     string
	Secret string
	// Public says the application proves itself with a proof key made for
	// each connection instead of a secret. For one of these, the id alone is
	// the whole credential.
	Public bool
}

ClientCredential is the application's own identity with one service: the pair a person registers once in that service's developer console and hands to codeaf. It is not the person's account — it only lets codeaf ask for one.

type Flow

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

Flow is one connection in progress: a listener waiting on the loopback address, and an address for the person to visit.

THE ADDRESS IS READY BEFORE THE WAIT BEGINS. Manager.BeginAuth returns only once the listener is up and Flow.URL can be answered, so a caller can put the address on screen, open it, and only then block on Flow.Wait. Any other arrangement makes the screen wait on the network before it can show anything.

func (*Flow) Cancel

func (f *Flow) Cancel()

Cancel abandons a connection nobody is going to finish — the person pressed escape, or closed the tab and came back. It releases the loopback address so the next attempt can have it.

func (*Flow) URL

func (f *Flow) URL() string

URL is the address to open in a browser. It is a loopback address on this machine that immediately sends the browser on to the service's own sign-in page — one hop, so that the long address with all its parameters never has to be printed in a terminal or retyped by hand.

func (*Flow) Wait

func (f *Flow) Wait(ctx context.Context) (Status, error)

Wait blocks until the person finishes in their browser, ctx ends, or the round-trip fails, then saves the connection and answers with its status.

Wait is meant to be called once. Calling it again returns the same answer it gave the first time rather than starting anything new.

type MCPTool

type MCPTool struct {
	// Name is what the tool is called at the far end, and what
	// [Manager.MCPCall] must be given back to run it.
	Name string
	// Description is the service's own sentence about what the tool does,
	// written for a model to read.
	Description string
	// Schema is the arguments the tool takes, as a JSON Schema object exactly
	// as the service published it. It is passed through rather than parsed:
	// this package has no opinion about anybody else's arguments.
	Schema json.RawMessage
	// ReadOnly reports that the service marked this tool as one that only
	// looks. See [MCPToolCapability] for what is done with it.
	//
	// ABSENT MEANS FALSE, AND FALSE IS THE STRICTER READING. A service that
	// says nothing about a tool has not promised that it only looks, and the
	// cost of the two mistakes is not symmetric: treating a read as an act
	// costs one confirmation, treating an act as a read costs whatever the
	// act did.
	ReadOnly bool
}

MCPTool is one tool a connected service offers, in the shape a belt needs: flat, already decided, nothing left to interpret.

type Manager

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

Manager is the one handle the rest of the program holds: it owns the store file, the client credentials, and every connection made with them.

func NewManager

func NewManager(profileDir string, creds map[string]ClientCredential) (*Manager, error)

NewManager opens the store under profileDir and binds it to the credentials the caller was configured with. An empty profileDir means codeaf's own state root, matching every other file the program keeps.

Opening reads the store once so that a damaged file is an error here, at startup, rather than a surprise in the middle of a person's first connection.

func (*Manager) BeginAuth

func (m *Manager) BeginAuth(ctx context.Context, id, answer string) (*Flow, error)

BeginAuth starts connecting one service and returns as soon as there is an address to send the person to.

THE FLOW OUTLIVES THIS CALL. The listener is deliberately not tied to ctx: ctx bounds only the wait for the listener to come up, and the round-trip itself lives until Flow.Wait returns or Flow.Cancel is called. A caller that passed a short-lived context to start a connection should not find the connection dead a moment later for a reason that has nothing to do with the person's browser.

The browser is NOT opened here. Whoever owns the screen decides how the address reaches the person.

answer is the one thing a tool server's address is missing. It is empty for every ordinary browser service and ignored by a tool server with no blank.

func (*Manager) Capabilities

func (m *Manager) Capabilities(service string) []Capability

Capabilities lists what one service can be used for, in a stable order.

THE ORDER IS THE DECLARATION'S ORDER, not the alphabet's. It is the opposite choice from [sortPlugs] and for the same underlying reason: plugs arrive from separate files in init order, which reshuffles when somebody renames a source file, while a service's capabilities are one list written in one place by somebody who put reading before sending on purpose.

A service nobody declared capabilities for answers nothing — THE EMPTINESS LAW: a panel that renders no rows is honest, one that invents a row is not.

func (*Manager) CapabilityState

func (m *Manager) CapabilityState(service, capability string) CapabilityState

CapabilityState answers what a person has said about one capability, falling back to what this build did before anybody said anything.

A CAPABILITY THIS BUILD DOES NOT HAVE IS OFF. An id nobody declared cannot be listed, cannot be set, and the only safe reading of it is that nothing runs under it.

func (*Manager) Client

func (m *Manager) Client(ctx context.Context, id string) (*http.Client, error)

Client hands back an HTTP client that speaks for the person's account on the named service. Every request it makes carries the connection, and a connection that has aged out is renewed underneath the caller without them asking.

THE RENEWAL IS WRITTEN DOWN. A service is free to hand back a new long-lived key when it renews the short-lived one, and a program that used the new key but kept the old one on disk works beautifully until it is restarted and then asks the person to connect again for no reason they can see. The stock renewal machinery offers no way to be told, so the source is wrapped here and the keys are compared after every renewal.

func (*Manager) ConnectKey

func (m *Manager) ConnectKey(ctx context.Context, id string, key string) (Status, error)

ConnectKey connects one service with a key the person already holds.

It is Manager.BeginAuth and Flow.Wait in a single call, because there is no browser to wait for and nothing in the middle to report on: the person has already done the only step there is by the time this is called.

NOTHING IS WRITTEN DOWN UNTIL THE KEY IS BELIEVED. Where the catalog names a cheap read, the key makes it first and a refusal fails here, with the service's own sentence, and stores nothing — the alternative is a screen that says "connected" over a key that was mistyped, and a failure an hour later in the middle of somebody's work.

func (*Manager) Connected

func (m *Manager) Connected(id string) bool

Connected reports whether id can be used right now.

Connected means ALL THREE things are true: the client credential this build was configured with, stored keys for a person's account, and a grant that covers what the service asks for today. Any one of them missing is a service that cannot do the work the caller is about to ask for.

func (*Manager) Disconnect

func (m *Manager) Disconnect(id string) error

Disconnect forgets a service: the stored keys go, the client credential and the menu entry stay, so the service can be connected again without any further setup.

Disconnect on something that was never connected succeeds. The caller asked for a state, the state holds, and an error there would only be an error about bookkeeping.

func (*Manager) MCPCall

func (m *Manager) MCPCall(ctx context.Context, service, tool string, args json.RawMessage) (string, error)

MCPCall runs one of a service's own tools and answers with what it said, as text.

THE ANSWER IS BOUNDED AT [maxToolText] AND THE CUT IS ANNOUNCED, exactly as every other tool answer in this package is (api.go): the reader is a model with a finite context, and a silently truncated answer is one it will reason confidently about the missing half of.

A TOOL THAT REFUSES IS AN ERROR, carrying the service's own sentence. The distinction the protocol draws — a failure of the call against a failure of the tool — is not one a caller here can act on differently, and collapsing it keeps one shape for "this did not happen".

func (*Manager) MCPService

func (m *Manager) MCPService(service string) bool

MCPService reports whether one service brings tools of its own.

It is the question a caller arming a belt has to ask, and it cannot be answered from Status alone: a tool server and Google are both browser connections, and what differs is where their tools come from. A service this answers yes for is armed from Manager.MCPTools and called with Manager.MCPCall; one it answers no for is armed exactly as it is today.

func (*Manager) MCPTools

func (m *Manager) MCPTools(ctx context.Context, service string) ([]MCPTool, error)

MCPTools lists what one connected service can do, as its own tools.

THE LIST IS FETCHED ONCE PER RUN. It costs a connection and a round-trip, the answer is the same for every call in a sitting, and a belt is rebuilt far more often than a service adds a tool. A person who connects a service, uses it, and finds a new tool missing has only to start codeaf again — which is a much smaller surprise than every turn of a conversation paying for a list that has not changed since the one before.

A service that is not connected is an error and not an empty list: a caller that got no tools would arm nothing and say nothing, and the person would be left wondering why the thing they connected does nothing.

func (*Manager) Request

func (m *Manager) Request(ctx context.Context, id, method, path, query, body string) (string, error)

Request is ServiceRequest with the account looked up: the one door the rest of the program uses, so that nothing outside this package has to know where a service lives or how its key rides.

func (*Manager) Services

func (m *Manager) Services() []Status

Services lists what this build can connect, in a stable order.

A service with no client credential is not listed at all. It is not shown greyed out, and it does not appear with an explanation of what to register where: a menu entry that cannot be chosen is a menu entry that wastes the reader's attention. A service connected with a key needs no such credential and is always listed — the person's own key is the whole of what it takes.

THE FILE IS READ ONCE HERE. There are hundreds of services on this list and one shared file behind them; asking the file about each service in turn would read it hundreds of times to answer one question.

func (*Manager) SetCapabilityState

func (m *Manager) SetCapabilityState(service, capability string, state CapabilityState) error

SetCapabilityState remembers what a person said.

Setting a capability back to its default FORGETS it rather than writing the default down, which is the emptiness law applied to disk: see [capabilityStore].

func (*Manager) ToolCapability

func (m *Manager) ToolCapability(service, tool string) string

ToolCapability answers which capability owns one tool.

NO CAPABILITY IS NOT THE SAME AS AN OFF ONE. An empty answer means no sentence in this build covers that tool, so the tool is judged exactly as it is today — by internal/approval and nothing else. A wiring that read the empty answer as a capability and asked for its state would be told off, and would quietly strip the belt of every tool no plug had declared.

type Plug

type Plug interface {
	// Service describes the plug for a menu. It must be cheap and constant.
	Service() Service
	// Endpoint is where the browser trip goes and where the exchange lands.
	Endpoint() oauth2.Endpoint
	// AuthCodeOptions are the extra request parameters this vendor needs on
	// the way out, beyond the ones every plug gets (a proof key, and the
	// permissions from Service). Google needs offline access and a forced
	// consent screen; another vendor may need nothing.
	AuthCodeOptions() []oauth2.AuthCodeOption
	// Account answers whose account the given client is acting for, as an
	// address a person recognises. Returning an error is ordinary and
	// tolerated: the caller stores an empty account and moves on.
	Account(ctx context.Context, client *http.Client) (string, error)
}

Plug is one connectable service. A plug is a value with no state of its own: it describes a service and knows the two things that differ between vendors, which are where the browser trip goes and how to ask the service whose account this is.

func Registered

func Registered() []Plug

Registered lists every plug built into this binary, connected or not, in the same order Manager.Services uses.

TWO PLUGS CANNOT SHARE AN ID, AND THE HAND-WRITTEN ONE WINS. The catalog (catalog.go) brings hundreds of services from somebody else's list, and a service written by hand in this package may one day turn up on that list too. The hand-written plug is the one with a browser trip, named permissions and real tools behind it; the catalog's row for the same service would offer strictly less under the same name, and two rows with one id would make every lookup a coin flip.

type Service

type Service struct {
	// ID is the stable identifier: the key in the credentials map, the key
	// in the store file, and the argument every method here takes.
	ID string
	// Name is what a person reads, for example "Google".
	Name string
	// Blurb is one short line saying what connecting it buys.
	Blurb string
	// Auth is how this one is connected: [AuthBrowser] or [AuthKey].
	Auth string
	// Address is the root every call to this service is made against, for
	// the services connected with a key. A service whose address is not
	// fully known until it is connected carries the part that is known and
	// the missing piece as a plain word in angle brackets, which is a thing
	// a person can read rather than a thing that looks like an address.
	//
	// A service connected through the browser leaves it empty: it has no one
	// address, and THE EMPTINESS LAW says an unknown is empty.
	Address string
	// Blank is the plain label for the one fact a browser-connected service
	// needs before its address is known — "Site", for example. EMPTY IS THE
	// ORDINARY CASE and means the browser can open without asking anything.
	Blank string
	// Answers is the closed list of values Blank may take. EMPTY IS THE
	// ORDINARY CASE and means there is no address question to answer.
	Answers []string
	// KeyAsk is the one instruction a person needs before they can answer the
	// box, either for the handful of key services whose answer is not just a
	// key, or for a browser service asking for the one thing its address is
	// missing. The keyed ones want the workspace, a space, and then the key
	// (key.go's [keyPlug.read]).
	//
	// EMPTY IS THE ORDINARY CASE and it means "a key, and nothing else" — the
	// box's own placeholder says that much already, and a second line
	// repeating it would be a sentence spent teaching somebody what they were
	// already doing.
	KeyAsk string
	// KeyHint is where a person goes to FIND their key: the vendor's own page
	// with the key on it, or the page of their documentation that says where
	// it is kept. One short address, and nothing else — no instructions, no
	// second link, no explanation of what a key is.
	//
	// It exists because "paste your Stripe key" is a perfectly clear
	// instruction that a person cannot follow: they know what is being asked
	// for and not where it is, and the screen asking them holds the answer.
	// A surface shows it while the box is OPEN and never before or after
	// (internal/tui3), which is the emptiness law applied to a link nobody
	// needs until they are looking for one.
	//
	// EMPTY IS ORDINARY. It is empty for every service connected in a browser
	// — there is no key to go and find — and for any catalog service whose
	// vendor names no such page. A screen renders nothing for it, never a
	// search, never a guess: a link to a page that may not exist is worse than
	// no link at all, because a person follows it.
	KeyHint string
	// Category is the one word a catalog of two hundred services is browsed
	// by — "billing", "crm", "calls & meetings" — filled from catalog
	// metadata by a later wiring wave. EMPTY IS THE HONEST DEFAULT and every
	// reader treats it as "other", so a build whose catalog says nothing
	// about categories draws the flat list it drew before this field existed.
	Category string
	// Scopes are the permissions asked for. They are listed here so a
	// screen can say plainly what it is about to request.
	Scopes []string
}

Service is what one connectable account looks like on a menu, flat strings because that is what gets rendered — not a model of anybody's API.

type Status

type Status struct {
	Service
	// Connected reports that codeaf holds usable keys for this service.
	Connected bool
	// Account is the address the keys belong to. THE EMPTINESS LAW: an
	// account we do not know is empty, never a placeholder — a screen that
	// renders nothing is honest, one that renders "unknown" is not.
	Account string
	// KeyEnv is the NAME of the environment variable this connection reads
	// its key from, empty for a key that was pasted whole (keyref.go).
	//
	// It is the one thing a key connection has to say about itself, and it is
	// safe to say: a variable's name is a fact about the person's own machine,
	// where the key is a fact about their account. A screen shows "from
	// $STRIPE_KEY" where a pasted key shows nothing at all — because there IS
	// nothing to show for a pasted key, and a masked row of bullets standing
	// in for one would be this surface pretending to hold something up.
	KeyEnv string
}

Status is a Service plus where it stands right now.

Directories

Path Synopsis
Package ampcatalog is codeaf's own copy of the parts of the amp-labs provider catalog that this program reads.
Package ampcatalog is codeaf's own copy of the parts of the amp-labs provider catalog that this program reads.
gen command
Command gen writes internal/connect/ampcatalog/providers.json from the amp-labs catalog.
Command gen writes internal/connect/ampcatalog/providers.json from the amp-labs catalog.

Jump to

Keyboard shortcuts

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