Data services
Reference for dd.http fetch against host allowlists, the dd.weather helpers, and dd.claudeUsage.
dd.http
Needs the http permission with the exact hostname allowlisted in the
manifest (permissions.http.hosts). All traffic goes through the host's
proxy; widgets have no direct network access at all.
dd.http.fetch()
dd.http.fetch(req: {
url: string;
method?: string; // GET (default), POST, PUT, PATCH, DELETE, HEAD
headers?: Record<string, string | { $secret: string }>;
/** Extra query params merged into the URL host-side after allowlist
* validation. Values may be { $secret: name } like headers. */
query?: Record<string, string | { $secret: string }>;
body?: string;
/** "dataUrl" returns the raw response bytes as a base64 data: URL
* (bodyText/json omitted), for images. Default "text". */
as?: "text" | "dataUrl";
}): Promise<{
status: number;
headers: Record<string, string>;
bodyText?: string;
json?: unknown; // parsed when the response is JSON
dataUrl?: string;
}>The rules, all enforced host-side:
- Hosts. The URL's hostname must exactly match an entry in
permissions.http.hosts.httpsonly (httpallowed for localhost). Redirects are followed up to 4 hops, each hop re-validated against the allowlist. - Methods.
GET,POST,PUT,PATCH,DELETE,HEAD. Headers that would let a widget impersonate the browser session (host,cookie,origin,referer, connection-control headers) are refused; the response exposes a small safe set (content-type,content-length,date,cache-control,etag,last-modified). - Secrets. A header or query value of
{ "$secret": "settingKey" }names one of this widget'ssecretRefsettings; the host injects the user's stored value after validation. The resolved value (and the merged URL) never appears in widget JS, logs, or error messages, and secret query params are never re-appended across redirects. - Sizes and pacing. Request body up to 256 KB; response up to 1 MB;
query up to 32 pairs (keys 128 bytes, values 2048 bytes, final URL 8 KB);
30 second timeout; 2 concurrent requests per instance; 30 requests per
minute by default, raised by the manifest's
http.rateLimitup to 120. - Binary.
as: "dataUrl"returns the bytes as a base64data:URL (same 1 MB cap). The widget CSP allowsimg-src data:, so the result can feed animgordd-icondirectly.
const res = await dd.http.fetch({
url: "https://api.example.com/v1/status",
headers: { authorization: { $secret: "apiKey" } },
});
if (res.status === 200) render(res.json);dd.weather
The shared weather stack, built on Open-Meteo. Convenience code, not a
capability: everything routes through this widget's own grants. geocode
needs geocoding-api.open-meteo.com in http.hosts, current needs
api.open-meteo.com, and the geocode cache uses your storage grant.
dd.weather.describe()
dd.weather.describe(code: number, isDay: number | boolean): {
label: string;
glyph: WeatherGlyph;
}
type WeatherGlyph =
| "clearDay" | "clearNight" | "partlyDay" | "partlyNight" | "cloudy"
| "fog" | "drizzle" | "rain" | "snow" | "thunder";Maps a WMO weather code to a human label plus a glyph name for icon().
dd.weather.geocode()
dd.weather.geocode(query: string, opts?: { cacheKey?: string }): Promise<{
query: string;
lat: number;
lon: number;
label: string;
}>Resolves a place name, cached in the widget's own storage (one entry per
cacheKey; a changed query re-fetches).
dd.weather.current()
dd.weather.current(opts: {
lat: number;
lon: number;
units?: "celsius" | "fahrenheit";
}): Promise<{
temperature: number; // in the requested unit
code: number; // WMO code; feed it to describe()
isDay: boolean;
}>dd.weather.icon()
dd.weather.icon(glyph: WeatherGlyph | string): stringA built-in line-art icon as a data: URI, ready for <dd-icon src=...>
(the widget CSP allows img-src data:). Unknown names return "".
dd.claudeUsage
Needs the claudeUsage permission.
dd.claudeUsage.status()
dd.claudeUsage.status(opts: { dirKey: string }): Promise<ClaudeUsage>Claude Code's own usage as one aggregated snapshot. dirKey names one of
this widget's filePath settings; the host resolves the .claude directory
the user configured there (blank = the default install), reads the limit
bars, today's tokens by model, and recently-active sessions, and refreshes
the bars from Anthropic's usage endpoint in the background. The OAuth token,
transcript contents, and raw paths never reach the widget. Poll it. 20 calls
per rolling 10 seconds.
interface ClaudeUsage {
/** False when the configured directory is not a readable Claude home:
* show a "set Claude home dir" hint. */
dirOk: boolean;
claudeVersion: string | null;
/** Where limits came from: "live" (Anthropic's usage endpoint), "cache"
* (Claude Code's own cache), or "none" (nothing available yet). */
source: "live" | "cache" | "none";
/** When limits was produced (epoch ms), for an "as of" readout. */
fetchedAtMs: number | null;
/** One bar per active limit window; percent is 0..100. */
limits: {
kind: string;
group: string;
label: string;
percent: number;
severity: string;
resetsAtMs: number | null;
isActive: boolean;
}[];
/** Today's usage summed from the transcripts (local day), by model. */
today: {
byModel: {
model: string;
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
messages: number;
}[];
totals: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
messages: number;
};
};
/** Recently-active Claude Code sessions. */
sessions: {
name: string | null;
cwd: string;
status: string;
updatedAtMs: number;
}[];
}