3. Publish a library
A library is reusable TypeScript that an advanced agent imports inside the code
it writes. This is how the agent reaches your API without inventing HTTP calls:
you publish getInvoice once, and every agent that has the library attached
calls it instead of guessing at your endpoints.
Libraries run in the uraiJS sandbox, which is V8 rather than Node. Deployment is a git push.
Install the CLI
Section titled “Install the CLI”npm install -g @uraiai/uraijs-cliuraijs --versionBinaries for macOS and Linux are also published on the releases page if you prefer not to install through npm.
Scaffold the project
Section titled “Scaffold the project”uraijs init acme-api --type librarycd acme-apiYou get an ES module and the files the editor needs:
| File | What it is |
|---|---|
index.ts |
The library entry point. Edit this one. |
urai.toml |
Project manifest: name, type, entry point, local library overrides. |
urai.d.ts, tsconfig.json |
Editor types for the runtime globals and urai:* imports. |
AGENTS.md |
Runtime rules, written for coding agents working in this repo. |
uraijs init also runs git init, which matters because deployment is a push.
Write the exports
Section titled “Write the exports”A library is a normal ES module. There are no decorators and no generated schema; you export functions and the agent calls them.
import { secrets } from "urai:secrets";
const BASE = "https://api.acme.com";
async function authed(path: string, init: RequestInit = {}) { const key = await secrets.get("ACME_API_KEY"); const res = await fetch(`${BASE}${path}`, { ...init, headers: { ...init.headers, Authorization: `Bearer ${key}` }, }); if (!res.ok) { throw new Error(`Acme API ${res.status} on ${path}`); } return res.json();}
/** Fetch one invoice by number for an organization. Returns null when absent. */export async function getInvoice(orgId: string, invoiceNumber: string) { const data = await authed( `/orgs/${orgId}/invoices/${encodeURIComponent(invoiceNumber)}`, ); return data ?? null;}
/** List the most recent invoices for an organization, newest first. */export async function listInvoices(orgId: string, limit = 10) { return authed(`/orgs/${orgId}/invoices?limit=${limit}`);}Design the exports for a model, not for a developer. Small functions with
obvious names, arguments in an order that is hard to get wrong, and errors that
say what failed. The agent gets one observation per run, so a function that
throws Acme API 403 on /orgs/x/invoices is worth far more than one that
returns null.
Secrets resolve by name at execution time. Read them with urai:secrets rather
than meta.secrets in a library, because that form also handles OAuth and
expiring credentials. Never hardcode a key.
What the sandbox gives you
Section titled “What the sandbox gives you”fetch, WebSocket, console, URL, crypto, TextEncoder, timers, and
modern JavaScript. No require, no fs, no process, no Buffer. Imports
work three ways: npm packages by bare specifier or npm: prefix, https://
URLs, and other urai:lib/<org>/<name> libraries. Pick npm packages that stay
on web APIs, because anything reaching for Node built-ins fails at runtime.
Document it for the agent
Section titled “Document it for the agent”The agent never sees your source. It sees whatever documentation the library publishes, so two files decide how well it uses your code:
AGENT.md is the reference injected into the agent’s prompt under
# Available libraries. Write the signatures and the rules, tightly. Leading
YAML frontmatter is stripped, and the real import specifier is rendered for you.
Client for the Acme billing API. Auth is handled inside; do not pass keys.
## Exports
- `getInvoice(orgId, invoiceNumber)`: one invoice, or `null` if not found.- `listInvoices(orgId, limit = 10)`: recent invoices, newest first.
## Rules
- Always pass `meta.vars.organization_id` as `orgId`. Never a value the user typed.- Amounts come back in minor units. Divide by 100 before showing them.- Errors throw with the status code. Report the failure; do not retry blindly.docs/llms.txt is optional and holds the longer version: worked examples,
edge cases, field meanings. When present it is injected after the reference.
Without either file, the agent is told only that the library exists and to call
its exported functions, which is rarely enough. A library with a sharp
AGENT.md gets used correctly on the first attempt.
Test locally
Section titled “Test locally”Write a scratch script that imports the library and run it in one shot:
import { getInvoice } from "urai:lib/acme/acme-api";const invoice = await getInvoice("org_123", "INV-2041");await urai.complete(invoice);uraijs lib link acme/acme-api .uraijs eval scratch.ts -s '{"ACME_API_KEY":"test-key"}'uraijs lib link writes the override into urai.toml so the urai:lib import
resolves to your checkout instead of the published copy. This is the same
one-shot runner the agent uses for every execute, so a script that works here
works in a conversation.
Deploy it
Section titled “Deploy it”-
In the developer studio for your organization, open Libraries and create one with the name you used in
urai.toml. This creates a git repository at/git/<your-org-specifier>/libs/<name>. -
Copy the remote URL from the library’s page and push to it:
Terminal window git remote add urai https://<studio-host>/git/<your-org-specifier>/libs/acme-apigit add .git commit -m "Acme billing client"git push urai mainWhen git asks for credentials, use any username and a git access token as the password. Generate tokens under Settings > Git in the studio; they are scoped to your organization.
Pushing again publishes a new version. The next conversation picks it up, so
treat main as production and keep the exported signatures stable once agents
depend on them.
Attach it to the agent
Section titled “Attach it to the agent”In the console, open the agent, go to Knowledge & Tools, and select the library under Libraries. The change saves immediately.
From then on the agent’s prompt carries a # Available libraries section with
your import specifier and your AGENT.md, and the agent writes code like this
on its own:
import { getInvoice } from "urai:lib/acme/acme-api";const invoice = await getInvoice(meta.vars.organization_id, "INV-2041");await urai.complete(invoice);Add the matching secret names to Allowed secrets on the Security tab, and your API host to the Network allowlist. A library that is attached but whose secret is not allowed fails at the first call.
Then name it in the instructions:
Use the Acme API library for anything about invoices or charges. Do not writeraw fetch calls against api.acme.com.Check before moving on
Section titled “Check before moving on”Ask the agent something that needs the library. In the thread you should see it
import urai:lib/..., call your function, and answer from the observation. If it
wrote a raw fetch instead, the AGENT.md is too thin or the instructions do
not point at the library clearly enough.
Next: write a skill.