SDK reference

Every backing the platform exposes is a member of EggletContext, the object passed into the Egglet’s activate() hook. This page is the field-by-field reference. Each section names the surface, the manifest declaration it requires, the signature, and a short example.

Egglet code imports type definitions from @egg/sdk. Concrete implementations live in the host package and are not imported by Egglets directly. By convention authors bind the context to a parameter named ctx; the examples below follow that convention.

import { defineEgglet, type EggletContext } from "@egg/sdk";

export default defineEgglet({
  activate(ctx: EggletContext) {
    // ctx.log, ctx.ai, ctx.storage, ...
  },
});

EggletContext.log

Tagged logger scoped to the Egglet’s id. Calls forward to the host console with a [egglet:<id>] prefix. No manifest declaration required. Use this instead of bare console.log so output is attributable.

log.debug(msg, ...args) / log.info(...) / log.warn(...) / log.error(...)

ParameterTypeRequiredDefaultDescription
msgunknownyesPrimary message. Stringified for the console.
...argsunknown[]no[]Additional values appended to the log line.

Returns: void.

Errors: none.

EggletContext.routes

Mount the Egglet’s top-level view at the path declared in manifest.route. The Egglet does not pick the path; the manifest does. The host calls the loader on first activation of the route and keeps the component mounted thereafter (the view participates in the keep-alive panel system; switching tabs does not unmount it). Two Egglets cannot mount the same route.

ctx.routes.mount(() => import("./MyEggletPage"));

routes.mount(loader)

ParameterTypeRequiredDefaultDescription
loader() => Promise<{ default?: ComponentType; Component?: ComponentType }>yesDynamic-import callback returning a module that exports default or Component. At least one must be present.

Returns: void.

Errors: throws if another Egglet has already mounted this route, or if the loader resolves to a module with neither a default nor a Component export.

EggletContext.tools

Register named tools the agent and other Egglets can call. Tool names are namespaced by convention: <egglet_id>.<verb>. Every name passed to register must appear in manifest.tools. Cross-Egglet calls require both external: true on the owning manifest and a matching entry in the caller’s permissions array.

ctx.tools.register<{ id: string }, { count: number }>(
  "myEgglet.countItems",
  async ({ id }) => {
    const row = await ctx.storage.one<{ n: number }>(
      "SELECT COUNT(*) AS n FROM {{items}} WHERE owner = ?",
      [id],
    );
    return { count: row?.n ?? 0 };
  },
);

tools.register<P, R>(name, handler)

ParameterTypeRequiredDefaultDescription
namestringyesFully-qualified tool name (e.g. "feed.summarize"). Must appear in manifest.tools.
handler(params: P) => Promise<R> \| RyesSync or async function that produces the tool result.

Returns: void.

Errors: throws if name is not declared in the manifest, or if another Egglet has already registered the same name.

tools.call<P, R>(name, params?)

ParameterTypeRequiredDefaultDescription
namestringyesFully-qualified tool name to invoke.
paramsPnoundefinedForwarded as the handler’s first argument.

Returns: Promise<R> — whatever the handler returns.

Errors: throws if the tool is unknown. Cross-Egglet calls throw if the target tool is not external: true or the caller’s manifest does not list the tool in permissions.

tools.has(name)

ParameterTypeRequiredDefaultDescription
namestringyesFully-qualified tool name to test.

Returns: booleantrue if a handler is registered. Permission checks are not applied here.

Errors: none.

EggletContext.storage

Read and write the Egglet’s tables. SQL strings reference tables by their logical name wrapped in double curly braces: {{items}}. The host substitutes the placeholder with the physical name from the manifest before sending the query. SQL referencing tables not declared by the manifest is rejected. Reads run against the local connection directly; writes route through the Gateway daemon so its update hooks fire and any sync push runs. From the Egglet’s perspective the calls look identical; the platform handles routing.

const rows = await ctx.storage.all<{ id: string; title: string }>(
  "SELECT id, title FROM {{items}} WHERE is_read = 0 ORDER BY ts DESC LIMIT ?",
  [50],
);

await ctx.storage.exec(
  "UPDATE {{items}} SET is_read = 1 WHERE id IN (?, ?, ?)",
  ["a", "b", "c"],
);

storage.all<T>(sql, params?)

ParameterTypeRequiredDefaultDescription
sqlstringyesSELECT statement. Table names wrapped in {{ }}.
paramsunknown[]no[]Positional bind values for ? placeholders.

Returns: Promise<T[]> — all rows.

Errors: throws if the SQL references a table not in the manifest, or if SQLite reports an error.

storage.one<T>(sql, params?)

ParameterTypeRequiredDefaultDescription
sqlstringyesSELECT expected to return zero or one row.
paramsunknown[]no[]Positional bind values.

Returns: Promise<T | null> — the first row, or null when the result set is empty. If multiple rows match, only the first is returned.

Errors: same as all.

storage.exec(sql, params?)

ParameterTypeRequiredDefaultDescription
sqlstringyesINSERT / UPDATE / DELETE / CREATE statement.
paramsunknown[]no[]Positional bind values.

Returns: Promise<{ changes: number; lastInsertRowid: number }>.

Errors: throws if uses.gateway.writes is not declared in the manifest, if the SQL references undeclared tables, or if SQLite reports an error.

EggletContext.alwaysOn

Recurring work the platform drives. The Egglet supplies the handler; the platform supplies the cadence and survives a closed window when the work is daemon-driven. Requires uses.gateway.schedules: true.

const handle = ctx.alwaysOn.poller({
  name: "refresh",
  interval: 300,
  handler: async () => { await refreshFeeds(); },
});
// later, to stop it manually:
handle.cancel();

alwaysOn.poller(config)

config: AlwaysOnPollerConfig

FieldTypeRequiredDefaultDescription
namestringyesStable name scoped to the Egglet. Used as the schedule key for catch-up after a window close.
intervalnumber (seconds)yesTime between handler calls. Minimum 1.
handler() => Promise<void> \| voidyesInvoked every interval. Errors are logged and do not stop the poller.
fireOnActivate"always" \| "if-due" \| "never"no"if-due"Whether the handler fires immediately on register. "if-due" fires only if the recorded last-fire is older than the interval.

Returns: AlwaysOnHandle{ cancel(): void }.

Errors: throws if uses.gateway.schedules is not declared, or if a poller with the same name is already registered for this Egglet.

EggletContext.fetch

In-process outbound HTTP, distinct from the Gateway-driven headless path. Routes through the platform’s shared HTTP client. Requires uses.network.fetch: true.

const r = await ctx.fetch("https://api.example.com/v1/items", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ q: "today" }),
  timeoutMs: 10_000,
});
if (r.ok) { const items = JSON.parse(r.body); }

fetch(url, init?)

ParameterTypeRequiredDefaultDescription
urlstringyesAbsolute URL. Loopback / file URLs rejected.
init.methodstringno"GET"HTTP method.
init.headersRecord<string, string>no{}Request headers.
init.bodystringnoRequest body. Stringify JSON yourself.
init.timeoutMsnumbernoplatform defaultPer-request timeout.

Returns: Promise<SafeFetchResponse>{ ok: boolean; status: number; statusText: string; headers: Record<string, string>; body: string }. Body is always a string; parse JSON yourself.

Errors: throws if uses.network.fetch is not declared, on network errors, or on timeout. Non-2xx responses are NOT thrown — check ok.

EggletContext.headless

Hidden-browser page fetch. Useful for SPAs, login-walled feeds, anything where ctx.fetch does not produce JS-rendered content. The Gateway spawns a fresh hidden browser per call. Requires uses.gateway.headless: true.

const result = await ctx.headless.fetch("https://example.com/feed", {
  waitMs: 3_000,
  evalJs: "document.title",
  credentialSource: "example",
});
console.log(result.title, result.evalResult, result.html.length);

headless.fetch(url, opts?)

ParameterTypeRequiredDefaultDescription
urlstringyesAbsolute URL.
opts.waitMsnumberno0Time to wait after the page’s load event before reading state. Use for SPA hydration.
opts.evalJsstringnoJavaScript expression evaluated in the page context after waitMs elapses. The result lands in evalResult.
opts.credentialSourcestringnoCredential name from manifest.credentials; cookies / auth headers are injected before navigation.

Returns: Promise<HeadlessResult>{ title: string; html: string; evalResult?: unknown; url: string; status: number }.

Errors: throws if uses.gateway.headless is not declared, if credentialSource is unknown, or if the hidden browser cannot reach the URL within the timeout.

EggletContext.ai

All model-backed operations live here, grouped by modality: text (chat, embeddings, reranking), image, audio, and video. Every method routes through the Gateway daemon end-to-end: capability resolution, tier gating (per-day quotas based on the user’s plan), credential lookup, and the outbound HTTP call to whichever provider serves the slot all happen there. The browser shell never sees an API key.

A blocked tier returns a structured error the Egglet can surface to the user (e.g., “daily limit reached” or “not available on your plan”). Use EggletContext.tier to check availability before calling.

EggletContext.ai.text

Operations whose input or output is plain text against the user’s configured text-model slot. chat.complete and chat.stream require uses.llm in the manifest; the slot is picked by uses.llm.role (default agent-secondary) and uses.llm.capability (default text). chat.stream emits text deltas, tool-call deltas, usage events, and a final done event; the returned promise resolves with the same shape as complete once the stream closes.

const r = await ctx.ai.text.chat.complete({
  system: "You are a concise summarizer.",
  messages: [{ role: "user", content: "Summarize this in one line: ..." }],
  maxTokens: 64,
});

const { embedding, dims, provider } = await ctx.ai.text.embed({ text: "hello world" });

const { scored } = await ctx.ai.text.rerank({
  query: "cats",
  docs: ["a cute kitten", "the weather is sunny", "a feline pet"],
  topN: 2,
});

ai.text.chat.complete(req)

ParameterTypeRequiredDefaultDescription
req.messages{ role: "user" \| "assistant"; content: string }[]yesConversation, in order. Platform supplies provider-specific framing.
req.systemstringnoSystem prompt.
req.maxTokensnumbernoprovider defaultCap on output tokens.

Returns: Promise<ChatResponse>{ text, inputTokens, outputTokens, provider, model }.

Errors: throws if uses.llm is not declared, on tier block (402), tier limit (429), missing provider key (412), or upstream provider error (502).

ai.text.chat.stream(req, onEvent)

ParameterTypeRequiredDefaultDescription
reqChatRequestyesSame shape as complete.
onEvent(e: ChatStreamEvent) => voidyesCalled for every chunk. Event variants: text, toolCallStart, toolCallDelta, usage, done, error.

Returns: Promise<ChatResponse> — resolves once the stream closes, with accumulated text and totals.

Errors: same gates as complete; an error event during streaming both fires through onEvent and rejects the promise.

ai.text.embed(req)

ParameterTypeRequiredDefaultDescription
req.textstringyesText to embed.
req.provider"google" \| "openai"nopriority orderProvider hint. Default tries Google first, then OpenAI, based on which key is configured.

Returns: Promise<{ embedding: number[]; provider: string; dims: number }>. Google returns 768d, OpenAI returns 1536d — always inspect dims, do not hard-code.

Errors: 412 PRECONDITION_FAILED when no embeddings key is configured; 502 on provider error.

ai.text.rerank(req)

ParameterTypeRequiredDefaultDescription
req.querystringyesQuery string.
req.docsstring[]yesCandidate documents. Empty array is allowed and returns empty scored.
req.topNnumbernoallReturn only the top N entries.
req.variantstringnoplatform defaultReranker model variant id.

Returns: Promise<{ scored: { index: number; score: number }[] }>. index refers to the original docs array. Higher score = more relevant.

Errors: 424 FAILED_DEPENDENCY when the reranker model is not installed (install via POST /x/ai/assets/rerank).

EggletContext.ai.image

generate calls the configured image model (Imagen by default) and writes the PNG to a daemon-known on-disk path. Pair with EggletContext.files to render or serve it. analyze sends the image to a vision-capable text model (Gemini) with a prompt; pass imagePath for files on disk or imageBase64 for in-memory bytes.

const img = await ctx.ai.image.generate({
  prompt: "a green leaf on a white background, studio lighting",
  aspectRatio: "1:1",
});
const url = ctx.files.urlFor(img.path);

const analysis = await ctx.ai.image.analyze({
  imagePath: img.path,
  prompt: "Describe the lighting and composition.",
});

ai.image.generate(req)

ParameterTypeRequiredDefaultDescription
req.promptstringyesText prompt. Provider safety filters may reject; surfaces as a 502.
req.aspectRatiostringno"1:1""16:9", "9:16", "1:1", "4:3", etc. Provider-dependent.

Returns: Promise<{ path: string; sizeBytes: number }> — PNG saved into the daemon’s generated-images store. Use ctx.files.urlFor(path) to render.

Errors: 402 PAYMENT_REQUIRED tier block; 429 tier limit; 502 provider error (including safety-filter rejection); 500 on disk write failure.

ai.image.analyze(req)

ParameterTypeRequiredDefaultDescription
req.imagePathstringone of imagePath / imageBase64Daemon-readable on-disk path. Preferred when the image is on disk.
req.imageBase64stringone of imagePath / imageBase64Base64-encoded bytes, no data: prefix.
req.mimeTypestringno"image/png"Only consulted when imageBase64 is set.
req.promptstringyesQuestion or instruction.
req.systemstringnoSystem prompt.

Returns: Promise<{ text: string; model: string; provider: string }>.

Errors: 400 if neither imagePath nor imageBase64 is provided, or the path cannot be read; 402 / 429 tier gates; 502 provider error.

EggletContext.ai.audio

tts synthesizes speech. backend defaults to auto: prefer local Piper when installed, otherwise cloud. listVoices returns the catalog so you can render a picker. Two paths exist for speech-to-text: transcribe takes a file path or HTTPS URL and runs local Whisper (best for long-form on-disk audio); recognize takes base64-encoded inline bytes and routes to Google Speech-to-Text (best for short-form audio captured in the renderer). vad runs the local Silero voice-activity detector and returns speech intervals — useful for chunking long recordings before transcription.

Caveat: transcribeStatus currently reports best-effort placeholders; a real /api/ai/transcribe-ready endpoint will replace the stub.

const voices = await ctx.ai.audio.listVoices();
const audio = await ctx.ai.audio.tts({ text: "Hello there.", voice: voices[0].key });
const { transcript } = await ctx.ai.audio.transcribe("/path/to/recording.m4a");
const { speech } = await ctx.ai.audio.vad({ inputPath: "/path/to/recording.m4a" });

ai.audio.tts(req)

ParameterTypeRequiredDefaultDescription
req.textstringyesText to synthesize. Long inputs chunked internally.
req.voicestringnoplatform defaultVoice key from listVoices().
req.backend"auto" \| "local" \| "cloud"no"auto"Force local Piper or cloud. auto prefers local when installed.

Returns: Promise<{ path: string; sizeBytes: number }> — on-disk audio file (typically MP3 or WAV).

Errors: 424 FAILED_DEPENDENCY if backend: "local" and Piper has no installed voice; 402/429 tier gates on cloud; 502 provider error.

ai.audio.transcribe(urlOrPath, modelPref?)

ParameterTypeRequiredDefaultDescription
urlOrPathstringyesLocal file path or http(s):// URL. URLs are downloaded into a temp file before transcription.
modelPrefstringnolargest installedWhisper model name to prefer (e.g. "medium.en").

Returns: Promise<{ transcript: string }>.

Errors: 412 PRECONDITION_FAILED if FFmpeg, Whisper, or any Whisper model is not installed; 502 BAD_GATEWAY on URL download failure; 500 on subprocess failure.

ai.audio.transcribeStatus()

No parameters.

Returns: Promise<{ ready: boolean; ffmpeg: boolean; whisper: boolean; model: boolean }>. See caveat above; values are placeholders until /api/ai/transcribe-ready ships.

Errors: none.

ai.audio.recognize(req)

ParameterTypeRequiredDefaultDescription
req.audioBase64stringyesAudio bytes, base64-encoded.
req.encodingstringnoprovider default"LINEAR16", "FLAC", "OGG_OPUS", etc.
req.sampleRateHertznumbernoprovider defaulte.g. 16000.
req.languageCodestringno"en-US"BCP-47 code.
req.modelstringnoprovider defaultProvider-specific model id (e.g. Google "latest_short").

Returns: Promise<{ transcript: string }>.

Errors: 412 PRECONDITION_FAILED when no Speech-to-Text key is configured; 402/429 tier gates; 502 provider error.

ai.audio.vad(req)

ParameterTypeRequiredDefaultDescription
req.inputPathstringyesDaemon-readable audio file path.
req.thresholdnumberno0.5Voice-probability threshold (0.0–1.0).
req.minSpeechMsnumberno250Drop speech spans shorter than this.
req.minSilenceMsnumberno100Treat silence shorter than this as part of the surrounding speech span.

Returns: Promise<{ speech: { startMs: number; endMs: number }[]; durationMs: number }>.

Errors: 400 on invalid path; 424 FAILED_DEPENDENCY if the Silero VAD model is not installed (install via POST /x/ai/assets/vad).

ai.audio.listVoices()

No parameters.

Returns: Promise<Voice[]>{ key, displayName, backend: "local" | "cloud", installed }, local voices first.

Errors: none under normal operation.

EggletContext.ai.video

generate calls the configured video model (Grok-Imagine by default). It needs a public HTTPS seed image URL and accepts 5–15 second durations. The daemon polls the provider until completion (up to ~3 minutes) and returns the provider-hosted video URL. analyze sends the video to a multimodal text model with a prompt; pass videoPath for files on disk or videoBase64 for in-memory bytes. The model must support video input (Gemini today).

const video = await ctx.ai.video.generate({
  prompt: "the leaf gently rustles in the breeze",
  imageUrl: "https://...leaf.png",
  durationSeconds: 5,
});
const description = await ctx.ai.video.analyze({
  videoPath: "/path/to/clip.mp4",
  prompt: "Summarize the action in one sentence.",
});

ai.video.generate(req)

ParameterTypeRequiredDefaultDescription
req.promptstringyesAction / scene description.
req.imageUrlstringyesPublic HTTPS seed image URL the provider can fetch.
req.durationSecondsnumberno55–15 seconds (xAI limit).
req.aspectRatiostringnoprovider defaulte.g. "16:9".

Returns: Promise<{ videoUrl: string; requestId: string }> — provider-hosted URL. Daemon polls up to ~3 minutes.

Errors: 412 PRECONDITION_FAILED when no xAI key is configured; 402/429 tier gates; 504 if generation does not finish within the timeout; 502 provider error.

ai.video.analyze(req)

ParameterTypeRequiredDefaultDescription
req.videoPathstringone of videoPath / videoBase64Daemon-readable on-disk path. Preferred when the video is on disk.
req.videoBase64stringone of videoPath / videoBase64Base64-encoded bytes, no data: prefix.
req.mimeTypestringno"video/mp4"Only consulted when videoBase64 is set.
req.promptstringyesQuestion or instruction.
req.systemstringnoSystem prompt.

Returns: Promise<{ text: string; model: string; provider: string }>.

Errors: 400 if neither videoPath nor videoBase64 is provided, or the path cannot be read; 402/429 tier gates; 502 provider error (the routed model must support video).

EggletContext.files

List, delete, and resolve URLs for files the daemon has written into its generated-media store (TTS output, generated images, captured video, etc.). The Egglet receives a path back from ctx.ai.*; pass that path to urlFor to get a URL it can embed. No manifest declaration required.

const images = await ctx.files.listGenerated("image");
for (const f of images) {
  console.log(f.name, f.sizeBytes, ctx.files.urlFor(f.path));
}

files.listGenerated(kind)

ParameterTypeRequiredDefaultDescription
kind"audio" \| "image" \| "video" \| "document"yesFilter by media kind.

Returns: Promise<GeneratedFile[]>{ id, path, name, fileType, mimeType, sizeBytes, createdAt }, newest first.

Errors: none under normal operation.

files.deleteGenerated(path)

ParameterTypeRequiredDefaultDescription
pathstringyesPath returned by an earlier listGenerated or ctx.ai.* call.

Returns: Promise<void>.

Errors: 404 if the file is unknown to the daemon (no error if it’s already gone from disk).

files.urlFor(path)

ParameterTypeRequiredDefaultDescription
pathstringyesDaemon-known on-disk path.

Returns: string — daemon HTTP URL that streams the file. Safe to drop into <img>, <audio>, <video>, or to pass to a download flow. Synchronous.

Errors: none.

EggletContext.artifacts

Long-lived domain artifacts: code, documents, diagrams. Distinct from files, which surfaces ephemeral generated media. Artifacts are durable, addressable by id, and round-trip through sync.

artifacts.list()

No parameters.

Returns: Promise<Artifact[]> — all artifacts owned by the user, newest first.

Errors: none under normal operation.

artifacts.get(id)

ParameterTypeRequiredDefaultDescription
idstringyesArtifact id.

Returns: Promise<Artifact | null>null when not found.

Errors: none.

artifacts.create(req)

ParameterTypeRequiredDefaultDescription
req.kindstringyesArtifact kind (e.g. "code", "doc", "diagram").
req.titlestringyesDisplay title.
req.contentstringyesArtifact body.
req.metadataRecord<string, unknown>no{}Free-form domain metadata.

Returns: Promise<Artifact> — full record with assigned id and timestamps.

Errors: 400 on missing required fields.

artifacts.update(id, patch)

ParameterTypeRequiredDefaultDescription
idstringyesArtifact id.
patchPartial<Artifact>yesFields to overwrite. Omitted fields are preserved.

Returns: Promise<Artifact> — updated record.

Errors: 404 if the id is unknown.

artifacts.remove(id)

ParameterTypeRequiredDefaultDescription
idstringyesArtifact id.

Returns: Promise<void>.

Errors: 404 if the id is unknown.

EggletContext.tier

The user’s plan tier and per-capability availability/quota. Use this to gate AI calls in the UI and avoid showing buttons the user’s plan can’t honor. Capability strings are the same daemon-side gate names used by ctx.ai.* (text_fast, vision, imagen, veo, tts, stt, etc.).

if (await ctx.tier.isAvailable("imagen")) {
  // safe to call ctx.ai.image.generate(...)
}

tier.getInfo()

No parameters.

Returns: Promise<TierInfo>{ tier: "free" | "plus" | "pro"; byokAllowed: boolean; capabilities: { capability: string; available: boolean; limit: number; used: number }[] }. limit: -1 means unlimited.

Errors: none under normal operation. Network failure surfaces as a rejected promise.

tier.isAvailable(capability)

ParameterTypeRequiredDefaultDescription
capabilitystringyesCapability slug.

Returns: Promise<boolean>true if the user’s tier exposes this capability. Falls back to true on transport failure (fail-open).

Errors: none.

tier.isLimitReached(capability)

ParameterTypeRequiredDefaultDescription
capabilitystringyesCapability slug.

Returns: Promise<boolean>true when the capability is unavailable OR the daily limit has been hit. Falls back to false on transport failure (fail-open).

Errors: none.

EggletContext.eggletIpc

Fire-and-forget messaging between Egglets. Use this for one-way signals (“new item arrived”, “follow-up needed”); use ctx.tools.call when you need a response. Both methods require uses.eggletIpc: true. Messages are dropped silently if the target has no subscribers; senders cannot tell.

// Sender
ctx.eggletIpc.send("people", { kind: "profileVisited", id: "alice-123" });

// Receiver
const handle = ctx.eggletIpc.subscribe<{ kind: string; id: string }>((msg) => {
  console.log(`from ${msg.from}: ${msg.payload.kind}`);
});

eggletIpc.send<P>(targetEgglet, payload)

ParameterTypeRequiredDefaultDescription
targetEggletstringyesRecipient’s Egglet id.
payloadPyesJSON-serializable payload.

Returns: void.

Errors: throws if uses.eggletIpc is not declared. Does NOT throw when the target has no subscribers.

eggletIpc.subscribe<P>(handler)

ParameterTypeRequiredDefaultDescription
handler(msg: EggletIpcMessage<P>) => void \| Promise<void>yesCalled for every message addressed to this Egglet. Receives { from, payload }.

Returns: EggletIpcHandle{ cancel(): void } to stop receiving messages.

Errors: throws if uses.eggletIpc is not declared.

EggletContext.refs

Ref kinds are the cross-Egglet handle system: stable, serializable values like @e:alice-123 that can travel through tables, agent prompts, sync payloads, and other contexts. Each kind has exactly one owning Egglet, declared in manifest.refs. The owning Egglet calls register in activate() with a function that resolves an id to a RefView. Other Egglets call resolve to render or parse to extract refs from text.

ctx.refs.register("e", async (id) => {
  const row = await ctx.storage.one<{ id: string; name: string }>(
    "SELECT id, name FROM {{entities}} WHERE id = ?",
    [id],
  );
  return row ? { kind: "e", id: row.id, label: row.name, route: `/people/${row.id}` } : null;
});

refs.register(kind, resolver)

ParameterTypeRequiredDefaultDescription
kindstringyesKind prefix (e.g. "e"). Must appear in manifest.refs.
resolver(id: string) => Promise<RefView | null> \| RefView \| nullyesReturns { kind, id, label, icon?, route?, description? } or null when unresolved.

Returns: void.

Errors: throws if kind is not declared in the manifest or another Egglet owns it.

refs.encode(kind, id)

ParameterTypeRequiredDefaultDescription
kindstringyesKind prefix.
idstringyesDomain id.

Returns: string — the serialized ref (e.g. "@e:alice-123"). Synchronous.

Errors: none.

refs.resolve(ref)

ParameterTypeRequiredDefaultDescription
refstringyesSerialized ref (e.g. "@e:alice-123").

Returns: Promise<RefView | null>null when the kind has no registered resolver or the resolver reports unresolved.

Errors: none for malformed input (returns null).

refs.parse(text)

ParameterTypeRequiredDefaultDescription
textstringyesArbitrary text to scan for embedded refs.

Returns: ParsedRef[] — each entry is { kind, id, start, end, raw }. Synchronous; does not call resolvers.

Errors: none.

EggletContext.credentials

Read access to user-authorized credentials. Egglets cannot create or revoke credentials; the user manages them in Settings. The source must appear in manifest.credentials.

const entry = await ctx.credentials.get("github");
if (!entry) { ctx.log.warn("no GitHub credential authorized"); return; }
const { token } = JSON.parse(entry.dataJson);

credentials.get(source)

ParameterTypeRequiredDefaultDescription
sourcestringyesCredential source name (e.g. "github"). Must appear in manifest.credentials.

Returns: Promise<CredentialEntry | null>{ credentialType: string; dataJson: string; expiresAt?: string }, or null when no credential is authorized. The Egglet parses dataJson.

Errors: throws if source is not declared in manifest.credentials.