@clawnify/connections
v0.3.0
Published
One-declaration, one-accessor credentials SDK for Clawnify apps. connect(service) returns a typed client, secret(name) reads an injected key, and describe(org) tells an agent what's wired — all over the Clawnify credentials binding, with the broker kept i
Readme
@clawnify/connections
One declaration, one accessor for every credential a Clawnify app needs.
import { connect, secret, describe } from "@clawnify/connections";
// OAuth or managed — app code is identical either way:
const accounts = await connect("metaads", env).get("/me/adaccounts", { fields: "name,id" });
const rows = await connect("googleads", env).query("SELECT customer.id FROM customer");
// Provider keys / secrets:
const key = secret("OPENROUTER_API_KEY", env);
// What's wired, for an agent to read before writing code:
const status = await describe(env, env.CLAWNIFY_ORG_ID, REQUIRES);env is your worker's env (it carries the CREDENTIALS binding and any
injected keys). Pass orgId explicitly or let it default to
env.CLAWNIFY_ORG_ID.
Why
Before this, every app hand-wrote its credential plumbing: which integration is
OAuth-direct vs. managed, the exact env var name for each key, the response
shape of each managed call. That knowledge belongs in one place. This SDK is
the accessor; @clawnify/integrations is the descriptor
registry it reads. The broker that actually holds a credential is invisible to
your app — connect() routes to it; describe() never names it.
The registry is an enhancement, not a gate
A descriptor exists only to give an integration a typed, ergonomic client
(connect("googleads").query(gaql)). Functionality never depends on one:
connect("anything", env)with no descriptor returns aGenericClientwithtoken()andrun(action, args)— every integration supports those two operations, so a toolkit just added in the dashboard works immediately.secret(name, env)reads any key by name — names come from the dashboard, not from a release.describe(env, org, requires)reports unregistered integrations too (connectivity + a generic accessor).
So you publish a new version of @clawnify/integrations only when you want to
upgrade a specific integration from generic to first-class — never just to
make a new integration usable. Adding the descriptor is opt-in sugar for the
handful of integrations where a typed client earns its keep.
API
| Call | Returns | Use for |
|------|---------|---------|
| connect(service, env, orgId?) | typed client (ServiceClients[service]) | OAuth / managed integrations |
| secret(name, env) | string \| null | provider keys & user secrets |
| isConnected(service, env, orgId?) | Promise<boolean> | gating a feature on a connection |
| describe(env, orgId?, requires?) | Promise<DescribeEntry[]> | agent-legible readiness snapshot |
Declaring requirements
In clawnify.json:
{
"requires": [
{ "service": "metaads", "as": "integration" },
{ "service": "googleads", "as": "integration" },
{ "name": "OPENROUTER_API_KEY", "as": "key" }
]
}requires only declares — it never provisions. Connections and keys are
added in the Clawnify dashboard; when one is missing, describe() returns the
exact dashboard step in its hint.
Local dev
Two ways to run off-platform:
One org token (recommended). Put your org's Clawnify token in
.dev.vars:CLAWNIFY_TOKEN=clw_…With no in-process
CREDENTIALSbinding, the SDK resolves connections over HTTP via the Clawnify API —connect("googleads"),connect("metaads"),describe()all hit your real org connections, including managed ones. The org is derived from the token server-side, so you don't set an org id and you paste no per-service secrets. Override the API base withCLAWNIFY_API_URL.Bare env tokens. Without
CLAWNIFY_TOKEN, the SDK falls back toMETAADS_BEARER_TOKEN/OPENROUTER_API_KEY/ etc. from.dev.vars. Managed integrations can't resolve this way and report not-ready.
In deployed apps neither is needed — Clawnify injects the in-process binding.
