🚀 Socket Launch Week Day 4:Socket MCP Adds Org Alerts, Threat Feed Review, and Package Inspection.Learn more
Sign In

@openguardrails/agentfw

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@openguardrails/agentfw - npm Package Compare versions

Comparing version
0.2.0
to
0.3.0
+418
dist/backends-DYCvzl-L.js
import { atomicWrite, fileExists, paths } from "./secrets-BF5E7_9T.js";
import { readFile } from "node:fs/promises";
//#region src/core/tool-providers.ts
const TOOL_PROVIDERS_VERSION = 1;
/** Seeded default — a keyless DuckDuckGo backend so first-run users
* have a working `web_search` without any setup. Quality is mediocre
* vs Brave/Tavily but it costs nothing and never asks for an account. */
const SEEDED_DDG = {
id: "ddg",
label: "DuckDuckGo (built-in)",
kind: "web_search",
backend: "duckduckgo",
origin: "seeded"
};
const EMPTY_STORE = {
version: TOOL_PROVIDERS_VERSION,
providers: [SEEDED_DDG],
active: {}
};
function isObj(v) {
return typeof v === "object" && v !== null && !Array.isArray(v);
}
const KINDS = ["web_search"];
const BACKENDS = [
"duckduckgo",
"brave",
"searxng",
"tavily",
"baidu"
];
function normalizeProvider(raw) {
if (!isObj(raw)) return void 0;
if (typeof raw.id !== "string" || raw.id === "") return void 0;
if (!KINDS.includes(raw.kind)) return void 0;
if (!BACKENDS.includes(raw.backend)) return void 0;
return {
id: raw.id,
label: typeof raw.label === "string" && raw.label !== "" ? raw.label : raw.id,
kind: raw.kind,
backend: raw.backend,
...typeof raw.baseUrl === "string" && raw.baseUrl !== "" ? { baseUrl: raw.baseUrl } : {},
...typeof raw.authRef === "string" && raw.authRef !== "" ? { authRef: raw.authRef } : {},
...typeof raw.costPerCall === "number" && raw.costPerCall >= 0 ? { costPerCall: raw.costPerCall } : {},
origin: raw.origin === "manual" ? "manual" : "seeded"
};
}
function normalizeToolProviders(raw) {
if (!isObj(raw)) return {
...EMPTY_STORE,
providers: [SEEDED_DDG]
};
const providers = Array.isArray(raw.providers) ? raw.providers.map(normalizeProvider).filter((p) => p != null) : [];
if (!providers.some((p) => p.kind === "web_search")) providers.push(SEEDED_DDG);
const active = {};
if (isObj(raw.active)) for (const k of KINDS) {
const v = raw.active[k];
if (typeof v === "string" && v !== "") active[k] = v;
}
return {
version: TOOL_PROVIDERS_VERSION,
providers,
active
};
}
async function readToolProviders() {
if (!await fileExists(paths.toolProviders)) return {
...EMPTY_STORE,
providers: [SEEDED_DDG]
};
return normalizeToolProviders(JSON.parse(await readFile(paths.toolProviders, "utf8")));
}
async function writeToolProviders(store) {
await atomicWrite(paths.toolProviders, `${JSON.stringify(store, null, 2)}\n`);
}
let writeChain = Promise.resolve();
/** Serialized read-modify-write — see model-registry.ts mutateModelRegistry. */
function mutateToolProviders(fn) {
const next = writeChain.then(async () => {
const store = await readToolProviders();
const updated = fn(store);
if (updated) await writeToolProviders(updated);
return updated ?? store;
});
writeChain = next.catch(() => {});
return next;
}
/** Active provider for a kind — explicit `active[kind]` wins, then
* the first provider of that kind, then undefined (caller falls back
* to "no backend, return an error to the tool caller"). */
function activeProviderFor(store, kind) {
const explicit = store.active[kind];
if (explicit) {
const hit = store.providers.find((p) => p.id === explicit && p.kind === kind);
if (hit) return hit;
}
return store.providers.find((p) => p.kind === kind);
}
//#endregion
//#region src/core/web-search/backends.ts
const DDG_URL = "https://html.duckduckgo.com/html/";
const DDG_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36";
async function searchDuckDuckGo(opts) {
const form = new URLSearchParams({ q: opts.query });
let res;
try {
res = await fetch(DDG_URL, {
method: "POST",
headers: {
"content-type": "application/x-www-form-urlencoded",
"user-agent": DDG_USER_AGENT,
accept: "text/html"
},
body: form.toString()
});
} catch (err) {
return {
ok: false,
error: `duckduckgo unreachable: ${err.message}`
};
}
if (!res.ok) return {
ok: false,
error: `duckduckgo HTTP ${res.status}`
};
const html = await res.text();
if (isDuckDuckGoAnomalyPage(html)) return {
ok: false,
error: "DuckDuckGo flagged this request as bot traffic and served a challenge page. This often resolves after a few minutes; for sustained use, switch to a key-based backend (Brave Search API) in Control · Tool Providers."
};
const results = parseDuckDuckGoHtml(html).slice(0, opts.count ?? 10);
return {
ok: true,
results
};
}
/** True when DDG returned its bot-challenge page instead of search
* results. The anomaly form's action URL is the strongest signal. */
function isDuckDuckGoAnomalyPage(html) {
return /anomaly\.js/.test(html) || /id="challenge-form"/.test(html);
}
/** Parse DDG's HTML results page. Their non-JS endpoint renders each
* result as a `result__a` anchor + `result__snippet` span. URLs come
* through a `//duckduckgo.com/l/?uddg=<encoded>` redirector — we
* decode the inner URL so callers get the real destination. The
* parser is intentionally a regex scan (no DOM lib) so it works in
* the daemon's plain node runtime; markup churn risk is the tradeoff. */
function parseDuckDuckGoHtml(html) {
const results = [];
const anchorRe = /<a[^>]+class="[^"]*\bresult__a\b[^"]*"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/g;
const snippetRe = /<(?:a|span)[^>]+class="[^"]*\bresult__snippet\b[^"]*"[^>]*>([\s\S]*?)<\/(?:a|span)>/g;
const snippets = [];
let s;
while ((s = snippetRe.exec(html)) !== null) snippets.push(stripHtml(s[1] ?? ""));
let i = 0;
let m;
while ((m = anchorRe.exec(html)) !== null) {
const rawHref = decodeHtmlEntities(m[1] ?? "");
const url = unwrapDdgRedirect(rawHref);
const title = stripHtml(m[2] ?? "");
if (!url || !title) {
i++;
continue;
}
const snippet = snippets[i];
results.push(snippet ? {
title,
url,
snippet
} : {
title,
url
});
i++;
}
return results;
}
function unwrapDdgRedirect(href) {
const m = /[?&]uddg=([^&]+)/.exec(href);
if (m?.[1]) try {
return decodeURIComponent(m[1]);
} catch {
return "";
}
if (href.startsWith("//")) return `https:${href}`;
if (href.startsWith("http")) return href;
return "";
}
function stripHtml(s) {
return decodeHtmlEntities(s.replace(/<[^>]+>/g, "")).replace(/\s+/g, " ").trim();
}
function decodeHtmlEntities(s) {
return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&#x27;/g, "'").replace(/&nbsp;/g, " ");
}
const BRAVE_URL = "https://api.search.brave.com/res/v1/web/search";
async function searchBrave(opts) {
const url = new URL(BRAVE_URL);
url.searchParams.set("q", opts.query);
url.searchParams.set("count", String(opts.count ?? 10));
if (opts.locale) url.searchParams.set("country", opts.locale);
let res;
try {
res = await fetch(url, { headers: {
accept: "application/json",
"x-subscription-token": opts.apiKey
} });
} catch (err) {
return {
ok: false,
error: `brave unreachable: ${err.message}`
};
}
const text = await res.text();
if (!res.ok) return {
ok: false,
error: `brave HTTP ${res.status}: ${text.slice(0, 200)}`
};
let json;
try {
json = JSON.parse(text);
} catch {
return {
ok: false,
error: `brave returned non-JSON: ${text.slice(0, 200)}`
};
}
return {
ok: true,
results: parseBraveJson(json).slice(0, opts.count ?? 10)
};
}
function parseBraveJson(json) {
if (typeof json !== "object" || json === null) return [];
const web = json.web;
if (!web || !Array.isArray(web.results)) return [];
const out = [];
for (const r of web.results) {
if (typeof r !== "object" || r === null) continue;
const row = r;
const title = typeof row.title === "string" ? row.title : "";
const url = typeof row.url === "string" ? row.url : "";
const description = typeof row.description === "string" ? row.description : "";
if (!title || !url) continue;
out.push(description ? {
title,
url,
snippet: description
} : {
title,
url
});
}
return out;
}
const BAIDU_SMART_URL = "https://qianfan.baidubce.com/v2/ai_search/chat/completions";
const BAIDU_WEB_URL = "https://qianfan.baidubce.com/v2/ai_search/web_search";
const BAIDU_SMART_MODEL = "ernie-4.5-turbo-32k";
async function searchBaidu(opts) {
const smart = await searchBaiduSmart(opts);
if (smart.ok) return smart;
const plain = await searchBaiduWeb(opts);
if (plain.ok) return plain;
return {
ok: false,
error: `baidu smart-search failed (${smart.error}); plain web_search also failed (${plain.error})`
};
}
/** Smart search via /v2/ai_search/chat/completions. Carries a model
* parameter (the LLM summary is discarded by agentfw — the routed
* model synthesises its own answer — but the references[] field is
* the same shape we consume from plain web_search). */
async function searchBaiduSmart(opts) {
const count = Math.max(1, Math.min(20, opts.count ?? 10));
const body = {
messages: [{
role: "user",
content: opts.query
}],
model: BAIDU_SMART_MODEL,
search_source: "baidu_search_v2",
stream: false,
resource_type_filter: [{
type: "web",
top_k: count
}],
enable_reasoning: false,
enable_deep_search: false,
enable_followup_queries: false,
search_mode: "required"
};
const filter = freshnessToFilter(opts.freshness);
if (filter) body.search_filter = filter;
return callBaidu(BAIDU_SMART_URL, opts.apiKey, body, count);
}
/** Plain web search via /v2/ai_search/web_search. No model invocation,
* larger monthly quota — the fallback when smart search refuses. */
async function searchBaiduWeb(opts) {
const count = Math.max(1, Math.min(50, opts.count ?? 10));
const body = {
messages: [{
role: "user",
content: opts.query
}],
search_source: "baidu_search_v2",
resource_type_filter: [{
type: "web",
top_k: count
}]
};
const filter = freshnessToFilter(opts.freshness);
if (filter) body.search_filter = filter;
return callBaidu(BAIDU_WEB_URL, opts.apiKey, body, count);
}
async function callBaidu(url, apiKey, body, count) {
let res;
try {
res = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/json",
authorization: `Bearer ${apiKey}`,
"x-appbuilder-from": "agentfw"
},
body: JSON.stringify(body)
});
} catch (err) {
return {
ok: false,
error: `baidu unreachable: ${err.message}`
};
}
const text = await res.text();
if (!res.ok) return {
ok: false,
error: `baidu HTTP ${res.status}: ${text.slice(0, 200)}`
};
let json;
try {
json = JSON.parse(text);
} catch {
return {
ok: false,
error: `baidu returned non-JSON: ${text.slice(0, 200)}`
};
}
if (json && typeof json === "object" && "code" in json && json.code) {
const msg = json.message;
return {
ok: false,
error: `baidu error: ${typeof msg === "string" ? msg : JSON.stringify(msg)}`
};
}
return {
ok: true,
results: parseBaiduJson(json).slice(0, count)
};
}
function parseBaiduJson(json) {
if (typeof json !== "object" || json === null) return [];
const refs = json.references;
if (!Array.isArray(refs)) return [];
const out = [];
for (const r of refs) {
if (typeof r !== "object" || r === null) continue;
const row = r;
const title = typeof row.title === "string" ? row.title : "";
const url = typeof row.url === "string" ? row.url : "";
const snippetField = typeof row.snippet === "string" ? row.snippet : typeof row.content === "string" ? row.content : "";
if (!title || !url) continue;
out.push(snippetField ? {
title,
url,
snippet: snippetField
} : {
title,
url
});
}
return out;
}
/** Map the Baidu `freshness` shorthand to its `search_filter` JSON.
* Returns undefined when freshness is absent or malformed (caller
* omits the filter so Baidu's default time range applies). */
function freshnessToFilter(freshness) {
if (!freshness) return void 0;
const now = new Date();
const dayShift = (n) => {
const d = new Date(now);
d.setUTCDate(d.getUTCDate() - n);
return d.toISOString().slice(0, 10);
};
const tomorrow = (() => {
const d = new Date(now);
d.setUTCDate(d.getUTCDate() + 1);
return d.toISOString().slice(0, 10);
})();
let start;
let end = tomorrow;
if (freshness === "pd") start = dayShift(1);
else if (freshness === "pw") start = dayShift(6);
else if (freshness === "pm") start = dayShift(30);
else if (freshness === "py") start = dayShift(364);
else {
const m = /^(\d{4}-\d{2}-\d{2})to(\d{4}-\d{2}-\d{2})$/.exec(freshness);
if (!m) return void 0;
start = m[1];
end = m[2] ?? end;
}
return { range: { page_time: {
gte: start,
lt: end
} } };
}
//#endregion
export { activeProviderFor, mutateToolProviders, readToolProviders, searchBaidu, searchBrave, searchDuckDuckGo };
import process from "node:process";
import { dirname, join } from "node:path";
import { homedir } from "node:os";
import { access, chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
//#region src/core/paths.ts
const HOME = homedir();
/** Resolve the agentfw home directory. Honors an explicit `AGENTFW_HOME`
* override; otherwise `~/.agentfw`. */
function resolveHome() {
return process.env.AGENTFW_HOME ?? join(HOME, ".agentfw");
}
const AGENTFW_HOME = resolveHome();
const paths = {
home: AGENTFW_HOME,
wire: {
dir: join(AGENTFW_HOME, "wire"),
routes: join(AGENTFW_HOME, "wire", "routes.json"),
traces: join(AGENTFW_HOME, "wire", "traces"),
tracesArchive: join(AGENTFW_HOME, "wire", "traces", "archive"),
daemonSock: join(AGENTFW_HOME, "wire", "daemon.sock"),
daemonPid: join(AGENTFW_HOME, "wire", "daemon.pid")
},
backups: {
dir: join(AGENTFW_HOME, "backups"),
manifest: join(AGENTFW_HOME, "backups", "manifest.json")
},
logs: {
dir: join(AGENTFW_HOME, "logs"),
daemon: join(AGENTFW_HOME, "logs", "daemon.log"),
daemonErr: join(AGENTFW_HOME, "logs", "daemon.err")
},
config: join(AGENTFW_HOME, "config.json"),
update: join(AGENTFW_HOME, "update.json"),
models: join(AGENTFW_HOME, "models.json"),
routing: join(AGENTFW_HOME, "routing.json"),
secrets: join(AGENTFW_HOME, "secrets.json"),
masking: join(AGENTFW_HOME, "masking.json"),
toolProviders: join(AGENTFW_HOME, "tool-providers.json"),
agent: {
claudeCode: {
settings: join(HOME, ".claude", "settings.json"),
legacy: join(HOME, ".claude.json")
},
claudeDesktop: {
root: join(HOME, "Library", "Application Support", "Claude"),
mcpConfig: join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json")
},
openclaw: join(HOME, ".openclaw", "openclaw.json"),
opencode: join(HOME, ".config", "opencode", "opencode.json"),
hermes: {
config: join(HOME, ".hermes", "config.yaml"),
env: join(HOME, ".hermes", ".env")
},
codex: {
config: join(HOME, ".codex", "config.toml"),
auth: join(HOME, ".codex", "auth.json")
},
cursor: {
darwin: join(HOME, "Library", "Application Support", "Cursor", "User", "settings.json"),
linux: join(HOME, ".config", "Cursor", "User", "settings.json")
},
gemini: join(HOME, ".gemini", ".env")
}
};
const PRICING_OVERRIDE = join(AGENTFW_HOME, "pricing.json");
const PRICING_CATALOG_CACHE = join(AGENTFW_HOME, "pricing-catalog.json");
const DAEMON_PORT = (() => {
const p = process.env.AGENTFW_PORT;
return p ? Number.parseInt(p, 10) : 9877;
})();
const DAEMON_HOST = "localhost";
const DAEMON_BASE_URL = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
//#endregion
//#region src/core/atomic-file.ts
async function atomicWrite(path, content, opts) {
await mkdir(dirname(path), { recursive: true });
const tmp = `${path}.tmp.${process.pid}`;
await writeFile(tmp, content);
if (opts?.mode != null) await chmod(tmp, opts.mode);
await rename(tmp, path);
}
async function fileExists(path) {
try {
await access(path);
return true;
} catch {
return false;
}
}
//#endregion
//#region src/core/secrets.ts
const SECRETS_VERSION = 1;
const SECRETS_MODE = 384;
const EMPTY_SECRETS = {
version: SECRETS_VERSION,
secrets: {}
};
function getSecret(store, ref) {
return store.secrets[ref];
}
/** Refs present in the store, for the UI to show which keys are configured
* (it never receives the values themselves). */
function secretRefs(store) {
return Object.keys(store.secrets);
}
function isObj(v) {
return typeof v === "object" && v !== null && !Array.isArray(v);
}
function normalizeSecretStore(raw) {
if (!isObj(raw)) return { ...EMPTY_SECRETS };
if (raw.version !== SECRETS_VERSION) throw new Error(`secrets.json version ${String(raw.version)} not supported (expected ${SECRETS_VERSION})`);
const secrets = {};
if (isObj(raw.secrets)) {
for (const [ref, value] of Object.entries(raw.secrets)) if (typeof value === "string") secrets[ref] = value;
}
return {
version: SECRETS_VERSION,
secrets
};
}
async function readSecrets() {
if (!await fileExists(paths.secrets)) return { ...EMPTY_SECRETS };
return normalizeSecretStore(JSON.parse(await readFile(paths.secrets, "utf8")));
}
async function writeSecrets(store) {
await atomicWrite(paths.secrets, `${JSON.stringify(store, null, 2)}\n`, { mode: SECRETS_MODE });
}
let writeChain = Promise.resolve();
/** Serialized read-modify-write — see model-registry.ts mutateModelRegistry. */
function mutateSecrets(fn) {
const next = writeChain.then(async () => {
const store = await readSecrets();
const updated = fn(store);
if (updated) await writeSecrets(updated);
return updated ?? store;
});
writeChain = next.catch(() => {});
return next;
}
/** Store a secret value under a ref. */
function setSecret(ref, value) {
return mutateSecrets((store) => ({
...store,
secrets: {
...store.secrets,
[ref]: value
}
}));
}
/** Remove a secret. No-op if the ref is absent. */
function removeSecret(ref) {
return mutateSecrets((store) => {
if (!(ref in store.secrets)) return void 0;
const secrets = { ...store.secrets };
delete secrets[ref];
return {
...store,
secrets
};
});
}
//#endregion
export { AGENTFW_HOME, DAEMON_BASE_URL, DAEMON_PORT, EMPTY_SECRETS, PRICING_CATALOG_CACHE, PRICING_OVERRIDE, SECRETS_VERSION, atomicWrite, fileExists, getSecret, mutateSecrets, normalizeSecretStore, paths, readSecrets, removeSecret, secretRefs, setSecret, writeSecrets };
import { EMPTY_SECRETS, SECRETS_VERSION, getSecret, mutateSecrets, normalizeSecretStore, readSecrets, removeSecret, secretRefs, setSecret, writeSecrets } from "./secrets-BF5E7_9T.js";
export { readSecrets };

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

+2
-2
#!/usr/bin/env node
import { getSecret, readSecrets } from "../secrets-DH1as49c.js";
import { activeProviderFor, readToolProviders, searchBaidu, searchBrave, searchDuckDuckGo } from "../backends-BPwWHt7D.js";
import { getSecret, readSecrets } from "../secrets-BF5E7_9T.js";
import { activeProviderFor, readToolProviders, searchBaidu, searchBrave, searchDuckDuckGo } from "../backends-DYCvzl-L.js";
import process from "node:process";

@@ -5,0 +5,0 @@

{
"name": "@openguardrails/agentfw",
"version": "0.2.0",
"description": "An AI agent firewall on the wire. Taps the traffic between your agents and their model providers — see every request, swap the model per route, and run security detectors over untrusted tool-call content. Free and fully open source.",
"version": "0.3.0",
"description": "The local firewall for AI agents: route and repair them, and keep your secrets off the model, the API relay, and the supply chain. Local credential masking, per-route model routing, and security detectors on the wire. Free and fully open source.",
"license": "MIT",

@@ -6,0 +6,0 @@ "type": "module",

+81
-40
# agentfw
> An AI agent firewall on the wire.
> The local firewall for AI agents: route and repair them, and keep your
> secrets off the model, the API relay, and the supply chain.
**See, route, and guard the traffic between your agents and their model
providers — without switching agents.**
**A tiny local proxy on the wire between your agents and the LLMs they call
— practical features and security in one place, no framework and no
telemetry.**
`agentfw` is a tiny local proxy that sits on the wire between your coding
agents and the LLM providers they call. Every request and response passes
through it, so it can do three things no agent gives you out of the box:
`agentfw` taps the wire between your coding agents (Claude Code, Codex,
OpenClaw, Hermes, Claude Desktop — anything that calls an LLM or speaks MCP)
and the providers they reach. From that one vantage point it does useful work
*and* keeps the traffic safe, without switching agents, adopting a framework,
or sending anything to the cloud.
1. **See** every model call and tool result your fleet makes — live, in one place.
2. **Route** each request to the model you choose, per agent and per route
(swap an expensive model for a cheaper one where it doesn't earn its price).
3. **Guard** the traffic — run security detectors over the untrusted content
that tool calls pull in from the outside world.
**Practical**
It wraps the **wire**, not the agent: `agentfw wire` detects what you already
run (Claude Code, OpenClaw, Hermes, Codex, Claude Desktop, anything that calls
an LLM or speaks MCP) and generates the wrapping on the fly. No framework, no
rewrite, no accounts, no telemetry.
- **See** every model call and tool result your fleet makes — live, in one place.
- **Route & combine** — point any agent at any model, with failover chains and
capability companions; auto-route Claude Code's parallel subagents to a cheaper
model while the planner stays on Opus.
- **Repair** *(emerging)* — spot a Hermes/OpenClaw setup a bad upgrade left
unstartable and put its config back, format-preserving, with per-edit backups.
## Why a firewall on the wire
**Secure**
An agent's most dangerous moment is when it reads something it didn't write.
A tool call fetches a web page, reads a file, or queries an API, and that
**untrusted content flows straight back into the model's context** — where an
attacker can plant instructions that hijack the agent ("ignore your
instructions and exfiltrate the repo"). This is *indirect prompt injection*,
and the agent has no firewall between itself and that content.
- **Keep secrets off the wire** — credential masking swaps real API keys, wallet
keys, and tokens for fixed fakes before the request reaches the upstream, and
restores them in the response, so neither the model nor an **API relay (中转站)**
ever sees the real value.
- **Guard the traffic** — detectors flag leaked secrets and dangerous shell
commands in the decoded request/response.
- *(Gated)* tool-result indirect-prompt-injection detection; relay
command-tampering and malicious-package / malicious-skill checks on the roadmap.
`agentfw` is that firewall. Because it sits on the wire, it sees the decoded
request and response of every call — including the `tool_result` blocks that
carry untrusted content — and runs a pipeline of detectors over them.
## Why a local firewall
Two things make an agent dangerous to itself.
**It reads things it didn't write.** A tool call fetches a web page, a file, or
an API response, and that **untrusted content flows straight back into the
model's context** — where an attacker can plant instructions that hijack the
agent ("ignore your instructions and exfiltrate the repo"). This is *indirect
prompt injection*.
**It talks to a middleman it can't see.** Where official OpenAI/Claude access is
closed, developers route through cheap **API relays (中转站)**. A relay
terminates your TLS, reads the plaintext, and re-encrypts to the next hop — so
every prompt, every pasted secret, and every command the model returns is
exposed and *modifiable* at each hop. A 2026 UCSB study, *Your Agent Is Mine:
Measuring Malicious Intermediary Attacks on the LLM Supply Chain*
([arXiv:2604.08407]), tested 428 relays: 17 exfiltrated injected AWS keys, 1
drained a real Ethereum private key, and 9 tampered with returned commands —
e.g. swapping a download link for a trojan, or rewriting `pip install requests`
into the typosquatted `pip install reqeusts` (an attacker-owned package). Over
6% misbehaved — and several triggered only after ~50 requests or only under an
agent's auto-execute (YOLO) mode, so a sandbox spot-check can't clear them.
`agentfw` sits between your agent and both. It's local — no account, no cloud —
and it sees the decoded request and response of every call, so it can strip your
secrets out before they reach the upstream (masking keeps the real values on
your machine) and run detectors over what comes back.
## What it does today

@@ -41,22 +69,33 @@

call (Anthropic, OpenAI chat & responses, Codex) and MCP frame, normalizes
them into a common shape, and stores a local trace.
- **Per-route model routing.** Point any agent's traffic at any model, with
failover chains and capability rules. The flagship case: Claude Code
them into a common shape, and stores a local trace — so you can see exactly
which upstream (provider or relay) each agent is actually talking to.
- **Credential masking.** Opt-in, per upstream. Real secrets — OpenAI /
Anthropic / Stripe / GitHub / AWS keys, Ethereum & Bitcoin wallet keys,
bearer & Slack tokens — are swapped for fixed fakes before the request leaves
your machine and restored in the response, so the provider and any relay see
only fakes while the agent keeps working with the real values. Configure it on
the dashboard's **Guard** page.
- **Model routing & combination.** Point any agent's traffic at any model, with
failover chains and capability companions. The flagship case: Claude Code
[Dynamic Workflows][dw] spawn *tens to hundreds of parallel subagents* that
all inherit the session model (Opus 4.8). `agentfw` tells the planner from
the workers **on the wire, exactly** — the planner always carries the
all inherit the session model (Opus 4.8). `agentfw` tells the planner from the
workers **on the wire, exactly** — the planner always carries the
orchestrator-only `Agent` tool; subagents never do — and routes only the
workers to a cheaper model. Verified 100% on 672 real calls; the planner is
never touched.
- **Security detectors.** A detector pipeline runs over every decoded packet:
secret-leak and dangerous-shell detection today, plus a **tool-result
indirect-prompt-injection** detector that inspects the untrusted content
returned by tool calls and flags instruction-override, role-injection,
exfiltration, and hidden-character payloads.
- **Security detectors.** A pipeline runs over every decoded packet: secret-leak
and dangerous-shell detection today. (The tool-result
indirect-prompt-injection detector is kept but gated.)
- **Agent-aware config handling.** `agentfw` understands Hermes, OpenClaw, and
Codex config formats and edits them format-preservingly (YAML / JSONC / TOML
AST, comments intact) with per-edit backups — the foundation for spotting and
repairing a setup a bad upgrade left unstartable.
## On the roadmap
The detector framework is the foundation. Next: richer indirect-prompt-injection
classification, blocking (not just flagging) high-severity hits inline on the
wire, data-exfiltration and tool-allowlist policies, and per-agent guard rules.
One-command repair of a broken agent setup; **blocking** (not just flagging)
high-severity hits inline on the wire; detection of relay command/download
tampering and typosquatted supply-chain packages; malicious-skill scanning;
richer indirect-prompt-injection classification; data-exfiltration and
tool-allowlist policies.

@@ -109,5 +148,7 @@ ## Quick start

Free and open source (MIT), entirely. Built on a capture → decode → route →
detect pipeline, tested against real Claude Code, Claude Desktop, OpenClaw,
Codex, and Hermes traffic. Bug reports and PRs welcome.
detect pipeline with per-upstream credential masking on top, tested against real
Claude Code, Claude Desktop, OpenClaw, Codex, and Hermes traffic. Bug reports and
PRs welcome.
[dw]: https://claude.com/blog/introducing-dynamic-workflows-in-claude-code
[arXiv:2604.08407]: https://arxiv.org/abs/2604.08407

@@ -7,4 +7,4 @@ <!doctype html>

<title>agentfw</title>
<script type="module" crossorigin src="/assets/index-CQlq25_u.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Bkn16sjG.css">
<script type="module" crossorigin src="/assets/index-DWRYuc7J.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C-PFIK_E.css">
</head>

@@ -11,0 +11,0 @@ <body>

import { atomicWrite, fileExists, paths } from "./secrets-DH1as49c.js";
import { readFile } from "node:fs/promises";
//#region src/core/tool-providers.ts
const TOOL_PROVIDERS_VERSION = 1;
/** Seeded default — a keyless DuckDuckGo backend so first-run users
* have a working `web_search` without any setup. Quality is mediocre
* vs Brave/Tavily but it costs nothing and never asks for an account. */
const SEEDED_DDG = {
id: "ddg",
label: "DuckDuckGo (built-in)",
kind: "web_search",
backend: "duckduckgo",
origin: "seeded"
};
const EMPTY_STORE = {
version: TOOL_PROVIDERS_VERSION,
providers: [SEEDED_DDG],
active: {}
};
function isObj(v) {
return typeof v === "object" && v !== null && !Array.isArray(v);
}
const KINDS = ["web_search"];
const BACKENDS = [
"duckduckgo",
"brave",
"searxng",
"tavily",
"baidu"
];
function normalizeProvider(raw) {
if (!isObj(raw)) return void 0;
if (typeof raw.id !== "string" || raw.id === "") return void 0;
if (!KINDS.includes(raw.kind)) return void 0;
if (!BACKENDS.includes(raw.backend)) return void 0;
return {
id: raw.id,
label: typeof raw.label === "string" && raw.label !== "" ? raw.label : raw.id,
kind: raw.kind,
backend: raw.backend,
...typeof raw.baseUrl === "string" && raw.baseUrl !== "" ? { baseUrl: raw.baseUrl } : {},
...typeof raw.authRef === "string" && raw.authRef !== "" ? { authRef: raw.authRef } : {},
...typeof raw.costPerCall === "number" && raw.costPerCall >= 0 ? { costPerCall: raw.costPerCall } : {},
origin: raw.origin === "manual" ? "manual" : "seeded"
};
}
function normalizeToolProviders(raw) {
if (!isObj(raw)) return {
...EMPTY_STORE,
providers: [SEEDED_DDG]
};
const providers = Array.isArray(raw.providers) ? raw.providers.map(normalizeProvider).filter((p) => p != null) : [];
if (!providers.some((p) => p.kind === "web_search")) providers.push(SEEDED_DDG);
const active = {};
if (isObj(raw.active)) for (const k of KINDS) {
const v = raw.active[k];
if (typeof v === "string" && v !== "") active[k] = v;
}
return {
version: TOOL_PROVIDERS_VERSION,
providers,
active
};
}
async function readToolProviders() {
if (!await fileExists(paths.toolProviders)) return {
...EMPTY_STORE,
providers: [SEEDED_DDG]
};
return normalizeToolProviders(JSON.parse(await readFile(paths.toolProviders, "utf8")));
}
async function writeToolProviders(store) {
await atomicWrite(paths.toolProviders, `${JSON.stringify(store, null, 2)}\n`);
}
let writeChain = Promise.resolve();
/** Serialized read-modify-write — see model-registry.ts mutateModelRegistry. */
function mutateToolProviders(fn) {
const next = writeChain.then(async () => {
const store = await readToolProviders();
const updated = fn(store);
if (updated) await writeToolProviders(updated);
return updated ?? store;
});
writeChain = next.catch(() => {});
return next;
}
/** Active provider for a kind — explicit `active[kind]` wins, then
* the first provider of that kind, then undefined (caller falls back
* to "no backend, return an error to the tool caller"). */
function activeProviderFor(store, kind) {
const explicit = store.active[kind];
if (explicit) {
const hit = store.providers.find((p) => p.id === explicit && p.kind === kind);
if (hit) return hit;
}
return store.providers.find((p) => p.kind === kind);
}
//#endregion
//#region src/core/web-search/backends.ts
const DDG_URL = "https://html.duckduckgo.com/html/";
const DDG_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36";
async function searchDuckDuckGo(opts) {
const form = new URLSearchParams({ q: opts.query });
let res;
try {
res = await fetch(DDG_URL, {
method: "POST",
headers: {
"content-type": "application/x-www-form-urlencoded",
"user-agent": DDG_USER_AGENT,
accept: "text/html"
},
body: form.toString()
});
} catch (err) {
return {
ok: false,
error: `duckduckgo unreachable: ${err.message}`
};
}
if (!res.ok) return {
ok: false,
error: `duckduckgo HTTP ${res.status}`
};
const html = await res.text();
if (isDuckDuckGoAnomalyPage(html)) return {
ok: false,
error: "DuckDuckGo flagged this request as bot traffic and served a challenge page. This often resolves after a few minutes; for sustained use, switch to a key-based backend (Brave Search API) in Control · Tool Providers."
};
const results = parseDuckDuckGoHtml(html).slice(0, opts.count ?? 10);
return {
ok: true,
results
};
}
/** True when DDG returned its bot-challenge page instead of search
* results. The anomaly form's action URL is the strongest signal. */
function isDuckDuckGoAnomalyPage(html) {
return /anomaly\.js/.test(html) || /id="challenge-form"/.test(html);
}
/** Parse DDG's HTML results page. Their non-JS endpoint renders each
* result as a `result__a` anchor + `result__snippet` span. URLs come
* through a `//duckduckgo.com/l/?uddg=<encoded>` redirector — we
* decode the inner URL so callers get the real destination. The
* parser is intentionally a regex scan (no DOM lib) so it works in
* the daemon's plain node runtime; markup churn risk is the tradeoff. */
function parseDuckDuckGoHtml(html) {
const results = [];
const anchorRe = /<a[^>]+class="[^"]*\bresult__a\b[^"]*"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/g;
const snippetRe = /<(?:a|span)[^>]+class="[^"]*\bresult__snippet\b[^"]*"[^>]*>([\s\S]*?)<\/(?:a|span)>/g;
const snippets = [];
let s;
while ((s = snippetRe.exec(html)) !== null) snippets.push(stripHtml(s[1] ?? ""));
let i = 0;
let m;
while ((m = anchorRe.exec(html)) !== null) {
const rawHref = decodeHtmlEntities(m[1] ?? "");
const url = unwrapDdgRedirect(rawHref);
const title = stripHtml(m[2] ?? "");
if (!url || !title) {
i++;
continue;
}
const snippet = snippets[i];
results.push(snippet ? {
title,
url,
snippet
} : {
title,
url
});
i++;
}
return results;
}
function unwrapDdgRedirect(href) {
const m = /[?&]uddg=([^&]+)/.exec(href);
if (m?.[1]) try {
return decodeURIComponent(m[1]);
} catch {
return "";
}
if (href.startsWith("//")) return `https:${href}`;
if (href.startsWith("http")) return href;
return "";
}
function stripHtml(s) {
return decodeHtmlEntities(s.replace(/<[^>]+>/g, "")).replace(/\s+/g, " ").trim();
}
function decodeHtmlEntities(s) {
return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&#x27;/g, "'").replace(/&nbsp;/g, " ");
}
const BRAVE_URL = "https://api.search.brave.com/res/v1/web/search";
async function searchBrave(opts) {
const url = new URL(BRAVE_URL);
url.searchParams.set("q", opts.query);
url.searchParams.set("count", String(opts.count ?? 10));
if (opts.locale) url.searchParams.set("country", opts.locale);
let res;
try {
res = await fetch(url, { headers: {
accept: "application/json",
"x-subscription-token": opts.apiKey
} });
} catch (err) {
return {
ok: false,
error: `brave unreachable: ${err.message}`
};
}
const text = await res.text();
if (!res.ok) return {
ok: false,
error: `brave HTTP ${res.status}: ${text.slice(0, 200)}`
};
let json;
try {
json = JSON.parse(text);
} catch {
return {
ok: false,
error: `brave returned non-JSON: ${text.slice(0, 200)}`
};
}
return {
ok: true,
results: parseBraveJson(json).slice(0, opts.count ?? 10)
};
}
function parseBraveJson(json) {
if (typeof json !== "object" || json === null) return [];
const web = json.web;
if (!web || !Array.isArray(web.results)) return [];
const out = [];
for (const r of web.results) {
if (typeof r !== "object" || r === null) continue;
const row = r;
const title = typeof row.title === "string" ? row.title : "";
const url = typeof row.url === "string" ? row.url : "";
const description = typeof row.description === "string" ? row.description : "";
if (!title || !url) continue;
out.push(description ? {
title,
url,
snippet: description
} : {
title,
url
});
}
return out;
}
const BAIDU_SMART_URL = "https://qianfan.baidubce.com/v2/ai_search/chat/completions";
const BAIDU_WEB_URL = "https://qianfan.baidubce.com/v2/ai_search/web_search";
const BAIDU_SMART_MODEL = "ernie-4.5-turbo-32k";
async function searchBaidu(opts) {
const smart = await searchBaiduSmart(opts);
if (smart.ok) return smart;
const plain = await searchBaiduWeb(opts);
if (plain.ok) return plain;
return {
ok: false,
error: `baidu smart-search failed (${smart.error}); plain web_search also failed (${plain.error})`
};
}
/** Smart search via /v2/ai_search/chat/completions. Carries a model
* parameter (the LLM summary is discarded by agentfw — the routed
* model synthesises its own answer — but the references[] field is
* the same shape we consume from plain web_search). */
async function searchBaiduSmart(opts) {
const count = Math.max(1, Math.min(20, opts.count ?? 10));
const body = {
messages: [{
role: "user",
content: opts.query
}],
model: BAIDU_SMART_MODEL,
search_source: "baidu_search_v2",
stream: false,
resource_type_filter: [{
type: "web",
top_k: count
}],
enable_reasoning: false,
enable_deep_search: false,
enable_followup_queries: false,
search_mode: "required"
};
const filter = freshnessToFilter(opts.freshness);
if (filter) body.search_filter = filter;
return callBaidu(BAIDU_SMART_URL, opts.apiKey, body, count);
}
/** Plain web search via /v2/ai_search/web_search. No model invocation,
* larger monthly quota — the fallback when smart search refuses. */
async function searchBaiduWeb(opts) {
const count = Math.max(1, Math.min(50, opts.count ?? 10));
const body = {
messages: [{
role: "user",
content: opts.query
}],
search_source: "baidu_search_v2",
resource_type_filter: [{
type: "web",
top_k: count
}]
};
const filter = freshnessToFilter(opts.freshness);
if (filter) body.search_filter = filter;
return callBaidu(BAIDU_WEB_URL, opts.apiKey, body, count);
}
async function callBaidu(url, apiKey, body, count) {
let res;
try {
res = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/json",
authorization: `Bearer ${apiKey}`,
"x-appbuilder-from": "agentfw"
},
body: JSON.stringify(body)
});
} catch (err) {
return {
ok: false,
error: `baidu unreachable: ${err.message}`
};
}
const text = await res.text();
if (!res.ok) return {
ok: false,
error: `baidu HTTP ${res.status}: ${text.slice(0, 200)}`
};
let json;
try {
json = JSON.parse(text);
} catch {
return {
ok: false,
error: `baidu returned non-JSON: ${text.slice(0, 200)}`
};
}
if (json && typeof json === "object" && "code" in json && json.code) {
const msg = json.message;
return {
ok: false,
error: `baidu error: ${typeof msg === "string" ? msg : JSON.stringify(msg)}`
};
}
return {
ok: true,
results: parseBaiduJson(json).slice(0, count)
};
}
function parseBaiduJson(json) {
if (typeof json !== "object" || json === null) return [];
const refs = json.references;
if (!Array.isArray(refs)) return [];
const out = [];
for (const r of refs) {
if (typeof r !== "object" || r === null) continue;
const row = r;
const title = typeof row.title === "string" ? row.title : "";
const url = typeof row.url === "string" ? row.url : "";
const snippetField = typeof row.snippet === "string" ? row.snippet : typeof row.content === "string" ? row.content : "";
if (!title || !url) continue;
out.push(snippetField ? {
title,
url,
snippet: snippetField
} : {
title,
url
});
}
return out;
}
/** Map the Baidu `freshness` shorthand to its `search_filter` JSON.
* Returns undefined when freshness is absent or malformed (caller
* omits the filter so Baidu's default time range applies). */
function freshnessToFilter(freshness) {
if (!freshness) return void 0;
const now = new Date();
const dayShift = (n) => {
const d = new Date(now);
d.setUTCDate(d.getUTCDate() - n);
return d.toISOString().slice(0, 10);
};
const tomorrow = (() => {
const d = new Date(now);
d.setUTCDate(d.getUTCDate() + 1);
return d.toISOString().slice(0, 10);
})();
let start;
let end = tomorrow;
if (freshness === "pd") start = dayShift(1);
else if (freshness === "pw") start = dayShift(6);
else if (freshness === "pm") start = dayShift(30);
else if (freshness === "py") start = dayShift(364);
else {
const m = /^(\d{4}-\d{2}-\d{2})to(\d{4}-\d{2}-\d{2})$/.exec(freshness);
if (!m) return void 0;
start = m[1];
end = m[2] ?? end;
}
return { range: { page_time: {
gte: start,
lt: end
} } };
}
//#endregion
export { activeProviderFor, mutateToolProviders, readToolProviders, searchBaidu, searchBrave, searchDuckDuckGo };
import { EMPTY_SECRETS, SECRETS_VERSION, getSecret, mutateSecrets, normalizeSecretStore, readSecrets, removeSecret, secretRefs, setSecret, writeSecrets } from "./secrets-DH1as49c.js";
export { readSecrets };
import process from "node:process";
import { dirname, join } from "node:path";
import { homedir } from "node:os";
import { access, chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
//#region src/core/paths.ts
const HOME = homedir();
/** Resolve the agentfw home directory. Honors an explicit `AGENTFW_HOME`
* override; otherwise `~/.agentfw`. */
function resolveHome() {
return process.env.AGENTFW_HOME ?? join(HOME, ".agentfw");
}
const AGENTFW_HOME = resolveHome();
const paths = {
home: AGENTFW_HOME,
wire: {
dir: join(AGENTFW_HOME, "wire"),
routes: join(AGENTFW_HOME, "wire", "routes.json"),
traces: join(AGENTFW_HOME, "wire", "traces"),
tracesArchive: join(AGENTFW_HOME, "wire", "traces", "archive"),
daemonSock: join(AGENTFW_HOME, "wire", "daemon.sock"),
daemonPid: join(AGENTFW_HOME, "wire", "daemon.pid")
},
backups: {
dir: join(AGENTFW_HOME, "backups"),
manifest: join(AGENTFW_HOME, "backups", "manifest.json")
},
logs: {
dir: join(AGENTFW_HOME, "logs"),
daemon: join(AGENTFW_HOME, "logs", "daemon.log"),
daemonErr: join(AGENTFW_HOME, "logs", "daemon.err")
},
config: join(AGENTFW_HOME, "config.json"),
update: join(AGENTFW_HOME, "update.json"),
models: join(AGENTFW_HOME, "models.json"),
routing: join(AGENTFW_HOME, "routing.json"),
secrets: join(AGENTFW_HOME, "secrets.json"),
toolProviders: join(AGENTFW_HOME, "tool-providers.json"),
agent: {
claudeCode: {
settings: join(HOME, ".claude", "settings.json"),
legacy: join(HOME, ".claude.json")
},
claudeDesktop: {
root: join(HOME, "Library", "Application Support", "Claude"),
mcpConfig: join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json")
},
openclaw: join(HOME, ".openclaw", "openclaw.json"),
opencode: join(HOME, ".config", "opencode", "opencode.json"),
hermes: {
config: join(HOME, ".hermes", "config.yaml"),
env: join(HOME, ".hermes", ".env")
},
codex: {
config: join(HOME, ".codex", "config.toml"),
auth: join(HOME, ".codex", "auth.json")
},
cursor: {
darwin: join(HOME, "Library", "Application Support", "Cursor", "User", "settings.json"),
linux: join(HOME, ".config", "Cursor", "User", "settings.json")
},
gemini: join(HOME, ".gemini", ".env")
}
};
const PRICING_OVERRIDE = join(AGENTFW_HOME, "pricing.json");
const PRICING_CATALOG_CACHE = join(AGENTFW_HOME, "pricing-catalog.json");
const DAEMON_PORT = (() => {
const p = process.env.AGENTFW_PORT;
return p ? Number.parseInt(p, 10) : 9877;
})();
const DAEMON_HOST = "localhost";
const DAEMON_BASE_URL = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
//#endregion
//#region src/core/atomic-file.ts
async function atomicWrite(path, content, opts) {
await mkdir(dirname(path), { recursive: true });
const tmp = `${path}.tmp.${process.pid}`;
await writeFile(tmp, content);
if (opts?.mode != null) await chmod(tmp, opts.mode);
await rename(tmp, path);
}
async function fileExists(path) {
try {
await access(path);
return true;
} catch {
return false;
}
}
//#endregion
//#region src/core/secrets.ts
const SECRETS_VERSION = 1;
const SECRETS_MODE = 384;
const EMPTY_SECRETS = {
version: SECRETS_VERSION,
secrets: {}
};
function getSecret(store, ref) {
return store.secrets[ref];
}
/** Refs present in the store, for the UI to show which keys are configured
* (it never receives the values themselves). */
function secretRefs(store) {
return Object.keys(store.secrets);
}
function isObj(v) {
return typeof v === "object" && v !== null && !Array.isArray(v);
}
function normalizeSecretStore(raw) {
if (!isObj(raw)) return { ...EMPTY_SECRETS };
if (raw.version !== SECRETS_VERSION) throw new Error(`secrets.json version ${String(raw.version)} not supported (expected ${SECRETS_VERSION})`);
const secrets = {};
if (isObj(raw.secrets)) {
for (const [ref, value] of Object.entries(raw.secrets)) if (typeof value === "string") secrets[ref] = value;
}
return {
version: SECRETS_VERSION,
secrets
};
}
async function readSecrets() {
if (!await fileExists(paths.secrets)) return { ...EMPTY_SECRETS };
return normalizeSecretStore(JSON.parse(await readFile(paths.secrets, "utf8")));
}
async function writeSecrets(store) {
await atomicWrite(paths.secrets, `${JSON.stringify(store, null, 2)}\n`, { mode: SECRETS_MODE });
}
let writeChain = Promise.resolve();
/** Serialized read-modify-write — see model-registry.ts mutateModelRegistry. */
function mutateSecrets(fn) {
const next = writeChain.then(async () => {
const store = await readSecrets();
const updated = fn(store);
if (updated) await writeSecrets(updated);
return updated ?? store;
});
writeChain = next.catch(() => {});
return next;
}
/** Store a secret value under a ref. */
function setSecret(ref, value) {
return mutateSecrets((store) => ({
...store,
secrets: {
...store.secrets,
[ref]: value
}
}));
}
/** Remove a secret. No-op if the ref is absent. */
function removeSecret(ref) {
return mutateSecrets((store) => {
if (!(ref in store.secrets)) return void 0;
const secrets = { ...store.secrets };
delete secrets[ref];
return {
...store,
secrets
};
});
}
//#endregion
export { AGENTFW_HOME, DAEMON_BASE_URL, DAEMON_PORT, EMPTY_SECRETS, PRICING_CATALOG_CACHE, PRICING_OVERRIDE, SECRETS_VERSION, atomicWrite, fileExists, getSecret, mutateSecrets, normalizeSecretStore, paths, readSecrets, removeSecret, secretRefs, setSecret, writeSecrets };
:root{--bg: #0d1117;--fg: #c9d1d9;--fg-muted: #8b949e;--border: #30363d;--accent: #58a6ff;--tool: #d29922;--thinking: #a371f7;--mcp: #3fb950;--err: #f85149;--risk-info: #58a6ff;--risk-warn: #d29922;--risk-high: #f85149}*{box-sizing:border-box}html,body{margin:0;background:var(--bg);color:var(--fg);font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,Monaco,monospace}.app{max-width:1200px;margin:0 auto;padding:1.5rem}header{display:flex;align-items:baseline;gap:1rem;border-bottom:1px solid var(--border);padding-bottom:.75rem;margin-bottom:1.5rem}header h1{margin:0;font-size:1.4rem;font-weight:600}header .slug{color:var(--fg-muted);font-size:.85rem}table.runs{width:100%;border-collapse:collapse}table.runs th,table.runs td{text-align:left;padding:.4rem .75rem;border-bottom:1px solid var(--border)}table.runs th{font-weight:500;color:var(--fg-muted);text-transform:uppercase;font-size:.7rem;letter-spacing:.05em}table.runs tbody tr{cursor:pointer}table.runs tbody tr:hover{background:#388bfd1a}code{font-family:inherit;color:var(--accent)}.run-detail .back{background:none;border:1px solid var(--border);color:var(--fg);padding:.3rem .6rem;cursor:pointer;font:inherit;border-radius:4px}.run-detail .back:hover{background:#ffffff0d}.run-detail .meta{margin:1rem 0 2rem}.run-detail .meta h2{margin:0 0 .75rem;font-size:1rem;font-weight:500}.run-detail .meta dl{display:grid;grid-template-columns:100px 1fr;gap:.25rem 1rem;font-size:.85rem;margin:0}.run-detail .meta dt{color:var(--fg-muted)}.run-detail .meta dd{margin:0}.action{border:1px solid var(--border);border-radius:6px;padding:.75rem 1rem;margin-bottom:1rem;background:#ffffff05}.action-head{display:flex;gap:1rem;align-items:center;margin-bottom:.5rem;font-size:.85rem}.action-head .kind{background:var(--border);padding:.1rem .5rem;border-radius:3px;color:var(--accent)}.action-head .kind.kind-mcp_call{color:var(--mcp)}.action-head .kind.kind-tool_call{color:var(--tool)}.mcp-dir{font-weight:700;display:inline-block;min-width:1ch}.mcp-dir-request{color:var(--accent)}.mcp-dir-response{color:var(--mcp)}.mcp-dir-notification{color:var(--fg-muted)}.risks{display:flex;flex-wrap:wrap;gap:.35rem;margin:.35rem 0 .5rem}.risk{font-size:.7rem;padding:.1rem .45rem;border-radius:3px;border:1px solid var(--border);white-space:nowrap}.risk-info{border-color:var(--risk-info);color:var(--risk-info)}.risk-warn{border-color:var(--risk-warn);color:var(--risk-warn)}.risk-high{border-color:var(--risk-high);color:var(--risk-high);background:#f851491f}.action-head .id{color:var(--fg-muted);font-size:.75rem}.action-head .time{margin-left:auto;color:var(--fg-muted)}.action .row{margin:.25rem 0}.action details{margin:.5rem 0}.action details summary{cursor:pointer;color:var(--fg-muted)}.action pre{background:#0000004d;padding:.75rem;border-radius:4px;overflow-x:auto;white-space:pre-wrap;word-break:break-word;font-size:.8rem;margin:.5rem 0}.action .blocks{margin-top:.5rem}.block{margin:.5rem 0;padding:.5rem .75rem;border-left:2px solid var(--border)}.block.text{white-space:pre-wrap}.block.tool_use{border-left-color:var(--tool)}.block.tool_use .tool-name{color:var(--tool);font-weight:600;display:block;margin-bottom:.25rem}.block.thinking{color:var(--thinking);font-style:italic;white-space:pre-wrap;border-left-color:var(--thinking)}.drift-banner{margin:0 0 1.25rem;padding:.75rem 1rem;border:1px solid var(--risk-warn);border-radius:6px;background:#d2992214;color:var(--fg);font-size:.85rem}.drift-banner strong{color:var(--risk-warn)}.drift-banner ul{margin:.4rem 0;padding-left:1.2rem}.drift-banner li{margin:.15rem 0}.drift-banner .path{color:var(--fg-muted);font-size:.8rem}.empty,.loading,.error{padding:2rem;text-align:center;color:var(--fg-muted)}.error{color:var(--err)}.empty .hint{font-size:.85rem;margin-top:1rem}.err{color:var(--err)}.share-app{max-width:none;padding:1.25rem 2rem}.share-toolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:1.25rem;font:.85rem/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.share-back{color:var(--fg-muted);text-decoration:none}.share-back:hover{color:var(--fg)}.share-actions{display:flex;gap:.6rem}.share-btn{background:transparent;border:1px solid var(--border);color:var(--fg);padding:.5rem 1rem;border-radius:6px;font:inherit;cursor:pointer;transition:border-color .15s,color .15s}.share-btn:hover{border-color:var(--fg)}.share-btn.primary{background:#d97706;border-color:#d97706;color:#fff}.share-btn.primary:hover{background:#b45309;border-color:#b45309}.share-stage{position:relative;overflow:hidden}.share-card-wrap{display:inline-block;box-shadow:0 8px 40px #00000080}.share-hint{margin-top:1.25rem;font:.8rem/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;color:var(--fg-muted)}.share-hint code{background:#ffffff0d;padding:.1rem .4rem;border-radius:3px;font-size:.85rem}.share-toast{position:fixed;bottom:2rem;left:50%;transform:translate(-50%);background:var(--fg);color:var(--bg);padding:.65rem 1.25rem;border-radius:6px;font:.85rem/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;box-shadow:0 4px 20px #0006;z-index:100}:root{--accent-amber: #d97706}.nav{display:flex;align-items:center;gap:2rem;padding-bottom:.75rem;margin-bottom:1.5rem;border-bottom:1px solid var(--border);font:.85rem/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.nav-brand{display:flex;align-items:center;gap:.5rem;font-weight:600;font-size:1rem;color:var(--fg);text-decoration:none}a.nav-brand:hover{color:var(--accent)}.nav-dot{width:10px;height:10px;border-radius:50%;background:var(--accent-amber);display:inline-block}.nav-links{display:flex;gap:1.25rem}.nav-links a{color:var(--fg-muted);text-decoration:none;padding:.25rem 0;border-bottom:2px solid transparent;transition:color .15s,border-color .15s}.nav-links a:hover{color:var(--fg)}.nav-links a.active{color:var(--fg);border-bottom-color:var(--accent-amber)}.nav-meta{margin-left:auto;display:flex;align-items:center;gap:1rem;color:var(--fg-muted)}.nav-drift{color:var(--risk-warn)}.nav-health.ok:before{content:"●";color:var(--mcp);margin-right:.4em}.nav-health.fail{color:var(--err)}.nav-health.fail:before{content:"●";margin-right:.4em}.home{display:flex;flex-direction:column;gap:1.25rem}.module{border:1px solid var(--border);border-radius:8px;padding:1.25rem 1.5rem;background:#ffffff04}.module-row{display:flex;align-items:center;justify-content:space-between;gap:1.5rem}.module-title{font-size:.75rem;letter-spacing:.1em;text-transform:uppercase;color:var(--fg-muted);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.module-summary{color:var(--fg-muted);font-size:.9rem}.module-link{color:var(--fg-muted);text-decoration:none;font-size:.85rem}.module-link:hover{color:var(--accent-amber)}.spend-module .hero-stat{display:flex;flex-direction:column;gap:.25rem}.spend-module .hero-stat-label{font-size:.75rem;letter-spacing:.1em;text-transform:uppercase;color:var(--fg-muted);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.spend-module .hero-stat-value{font-size:2.6rem;font-weight:700;letter-spacing:-.02em;color:var(--fg);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.sparkline{display:flex;align-items:flex-end;gap:2px;height:64px;flex:1;max-width:480px}.sparkline-bar{flex:1;background:var(--border);border-radius:1px;min-height:2px;transition:background .15s}.sparkline-bar:hover,.sparkline-bar.current{background:var(--accent-amber)}.sub-stats{margin-top:1.25rem;padding-top:1rem;border-top:1px solid var(--border);display:flex;gap:3rem;justify-content:flex-start}.sub-stat-label{font-size:.75rem;color:var(--fg-muted);letter-spacing:.05em;text-transform:uppercase;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;margin-bottom:.25rem}.sub-stat-value{font-size:1.1rem;color:var(--fg);font-weight:500;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.card-row{display:grid;grid-template-columns:1fr 1fr;gap:1.25rem}@media(max-width:720px){.card-row{grid-template-columns:1fr}}.card-module{padding:1rem 1.25rem .75rem}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:.75rem}.card-title{font-size:.75rem;letter-spacing:.1em;text-transform:uppercase;color:var(--fg-muted);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.card-meta{color:var(--accent-amber);font-weight:600;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.card-empty{color:var(--fg-muted);font-size:.85rem;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.card-body{display:flex;flex-direction:column;gap:.3rem}.card-body-empty{padding:1rem 0;color:var(--fg-muted);font-size:.85rem;text-align:left;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.card-row-link{display:grid;grid-template-columns:80px 1fr auto;align-items:baseline;gap:.75rem;padding:.4rem 0;color:var(--fg);text-decoration:none;border-bottom:1px dashed transparent}.card-row-link:hover{border-bottom-color:var(--border)}.cost{font-weight:600;text-align:right}.cost-accent{color:var(--accent-amber)}.cost-warn{color:#f97316}.card-row-desc{color:var(--fg);font-size:.85rem}.card-row-meta{color:var(--fg-muted);font-size:.75rem}.card-footer{padding-top:.6rem;margin-top:.4rem;border-top:1px solid var(--border)}.card-footer a{color:var(--fg-muted);text-decoration:none;font-size:.8rem;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.card-footer a:hover{color:var(--accent-amber)}.agent-bars{margin-top:1rem;display:flex;flex-direction:column;gap:.5rem}.agent-bar{display:grid;grid-template-columns:130px 1fr 160px;align-items:center;gap:1rem}.agent-bar-label{font-weight:500}.agent-bar-track{height:8px;background:var(--border);border-radius:4px;overflow:hidden}.agent-bar-fill{height:100%;background:var(--accent-amber);border-radius:4px;transition:width .4s}.agent-bar-stat{color:var(--fg-muted);font-size:.85rem;text-align:right;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.activity{margin-top:.75rem;display:flex;flex-direction:column}.activity-row{display:grid;grid-template-columns:50px 130px 1fr 80px 90px;align-items:baseline;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--border);color:var(--fg);text-decoration:none;font-size:.9rem}.activity-row:hover{background:#ffffff05}.activity-time{color:var(--fg-muted);font-size:.85rem}.activity-agent{color:var(--fg)}.activity-goal{color:var(--fg-muted);font-size:.85rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.activity-dur{color:var(--fg-muted);font-size:.8rem;text-align:right}.activity-cost{color:var(--accent-amber);font-weight:500;text-align:right}.run-header{display:flex;align-items:baseline;gap:1rem;flex-wrap:wrap;margin-bottom:1rem}.run-header h2{margin:0}.run-header-meta{color:var(--fg-muted);font-size:.85rem;display:flex;gap:.5rem;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.cost-breakdown{border:1px solid var(--border);border-radius:8px;padding:1rem 1.25rem;margin:1rem 0;background:#ffffff04}.cost-breakdown-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:.6rem}.cost-breakdown-title{font-size:.75rem;letter-spacing:.1em;text-transform:uppercase;color:var(--fg-muted);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.cost-breakdown-total{font-size:1.4rem;font-weight:700;color:var(--accent-amber);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.cost-breakdown-rows{display:flex;flex-direction:column;gap:.35rem}.cost-breakdown-row{display:grid;grid-template-columns:100px 40px 1fr 70px 40px;align-items:center;gap:.6rem;font-size:.85rem}.cost-breakdown-count{color:var(--fg-muted);font-size:.8rem}.cost-breakdown-bar-track{height:6px;background:var(--border);border-radius:3px;overflow:hidden}.cost-breakdown-bar-fill{height:100%;background:var(--accent-amber);border-radius:3px}.cost-breakdown-cost{text-align:right;font-weight:500;color:var(--fg)}.cost-breakdown-pct{text-align:right;color:var(--fg-muted);font-size:.8rem}.action-cost{margin-left:auto;color:var(--fg-muted);font-weight:500}.action-cost-top{color:var(--accent-amber)}.action-cost-tag{margin-left:.5rem;font-size:.7rem;color:var(--accent-amber);letter-spacing:.04em}.action.action-top-cost{border-left:2px solid var(--accent-amber);padding-left:.75rem}.helptip{display:inline-flex;align-items:center;position:relative;cursor:help;margin-left:.4em;vertical-align:middle}.helptip-icon{width:14px;height:14px;border:1px solid var(--fg-muted);border-radius:50%;color:var(--fg-muted);display:inline-flex;align-items:center;justify-content:center;font:600 9px/1 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;text-transform:none;letter-spacing:0;transition:color .15s,border-color .15s}.helptip:hover .helptip-icon{color:var(--accent-amber);border-color:var(--accent-amber)}.helptip-content{position:absolute;top:calc(100% + 8px);left:50%;transform:translate(-50%);width:280px;background:#1a1f26;border:1px solid var(--border);padding:.7rem .85rem;border-radius:6px;font:.78rem/1.55 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;color:var(--fg);text-transform:none;letter-spacing:0;opacity:0;visibility:hidden;transition:opacity .15s,visibility .15s;z-index:20;box-shadow:0 6px 24px #00000080;pointer-events:none}.helptip-content code{background:#ffffff0f;padding:.05rem .3rem;border-radius:3px;font-size:.9em}.helptip:hover .helptip-content{opacity:1;visibility:visible}.sparkline-with-label{display:flex;flex-direction:column;align-items:stretch;gap:.4rem;flex:1;max-width:480px}.sparkline-label{font:.7rem/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;color:var(--fg-muted);letter-spacing:.04em;display:flex;align-items:center;justify-content:flex-end}:root{--thomas-gold: #f5b500;--thomas-gold-dim: rgba(245, 181, 0, .18);--thomas-blue-1: #0d1530;--thomas-blue-2: #14204a;--thomas-blue-3: #1c2b65;--thomas-text: #e9eaf5;--thomas-text-dim: rgba(233, 234, 245, .6)}.thomas-hero{background:linear-gradient(135deg,var(--thomas-blue-1) 0%,var(--thomas-blue-2) 60%,var(--thomas-blue-3) 100%);color:var(--thomas-text);border-radius:12px;padding:1.75rem 2rem 1.5rem;position:relative;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.thomas-hero:after{content:"";position:absolute;width:380px;height:380px;right:-120px;top:-120px;border-radius:50%;background:radial-gradient(circle,#f5b5001f,#f5b50000 70%);pointer-events:none}.thomas-hero-eyebrow{display:flex;align-items:center;gap:.6rem;font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;color:var(--thomas-text-dim);margin-bottom:.6rem}.thomas-hero-eyebrow .helptip-icon{border-color:var(--thomas-text-dim);color:var(--thomas-text-dim)}.thomas-hero-eyebrow .helptip:hover .helptip-icon{color:var(--thomas-gold);border-color:var(--thomas-gold)}.period-tabs{margin-left:auto;display:flex;gap:.3rem}.period-tab{background:transparent;border:1px solid rgba(255,255,255,.1);color:var(--thomas-text-dim);padding:.25rem .7rem;font:inherit;font-size:.75rem;letter-spacing:.04em;cursor:pointer;border-radius:4px;transition:all .15s}.period-tab:hover{color:var(--thomas-text);border-color:#ffffff4d}.period-tab.active{color:var(--thomas-gold);border-color:var(--thomas-gold-dim);background:#f5b50014}.thomas-hero-main{display:flex;flex-direction:column;gap:.5rem;margin-bottom:.6rem}.thomas-numbers{display:flex;align-items:baseline;gap:1.5rem}.thomas-number-block{display:flex;flex-direction:column;gap:.1rem}.thomas-number{font-size:5.5rem;font-weight:700;line-height:1;letter-spacing:-.04em;color:var(--thomas-gold)}.thomas-number-verified{font-size:3rem;font-weight:600;line-height:1;letter-spacing:-.03em;color:#e9eaf5d9}.thomas-number-label{font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;color:var(--thomas-text-dim);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;margin-top:.2rem}.thomas-number-sep{font-size:3rem;font-weight:300;color:#e9eaf54d;align-self:center;margin-bottom:1.2rem}.thomas-band{color:var(--thomas-text-dim);font-size:.95rem;font-style:italic}.thomas-gap{font-style:normal}.thomas-gap-pct{font-style:normal;font-weight:600}.thomas-gap-pct.gap-good{color:#34d399}.thomas-gap-pct.gap-warn{color:var(--thomas-gold)}.thomas-gap-pct.gap-bad{color:#f97316}.thomas-summary{display:flex;flex-direction:column;gap:.25rem;margin-bottom:1rem}.thomas-summary strong{color:var(--thomas-text);font-weight:600}.thomas-summary .dim{color:var(--thomas-text-dim);font-size:.85rem}.thomas-submetrics{display:flex;gap:2.5rem;margin-bottom:1.25rem}.submetric-label{font-size:.7rem;letter-spacing:.1em;text-transform:uppercase;color:var(--thomas-text-dim);margin-bottom:.25rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;display:flex;align-items:center}.submetric-value{font-size:1.15rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--thomas-text)}.thomas-actions{display:flex;gap:1.25rem;align-items:center}.thomas-share-btn{background:var(--thomas-gold);color:var(--thomas-blue-1);font-weight:600;padding:.55rem 1.1rem;border-radius:6px;text-decoration:none;font-size:.9rem;transition:background .15s,transform .15s}.thomas-share-btn:hover{background:#ffc91a;transform:translateY(-1px)}.thomas-spec-link{color:var(--thomas-text-dim);text-decoration:none;font-size:.85rem}.thomas-spec-link:hover{color:var(--thomas-gold)}.outcomes-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(135px,1fr));gap:.7rem;margin-top:.6rem}.outcome-tile{border:1px solid var(--border);border-radius:6px;padding:.7rem .9rem;background:#ffffff05}.outcome-tile-icon{font-size:1rem;opacity:.7;margin-bottom:.25rem}.outcome-tile-count{font-size:1.6rem;font-weight:700;color:var(--thomas-gold);line-height:1;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;letter-spacing:-.02em}.outcome-tile-label{font-size:.75rem;color:var(--fg-muted);margin-top:.3rem;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.agent-thomas-rows{display:flex;flex-direction:column;gap:.65rem;margin-top:.6rem}.agent-thomas-row{display:grid;grid-template-columns:140px 1fr 230px;align-items:center;gap:1rem}.agent-thomas-name{font-weight:500;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;display:flex;flex-direction:column;gap:.15rem}.agent-thomas-type{font-size:.65rem;letter-spacing:.08em;text-transform:uppercase;color:var(--fg-muted);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:400}.agent-thomas-bar-track{height:10px;background:#ffffff0d;border-radius:5px;overflow:hidden;position:relative}.agent-thomas-bar-fill{height:100%;background:#f5b50059;border-radius:5px;transition:width .4s;position:absolute;left:0;top:0}.agent-thomas-bar-verified{height:100%;background:var(--thomas-gold);border-radius:5px;transition:width .4s;position:absolute;left:0;top:0}.agent-thomas-stat{display:flex;flex-direction:column;align-items:flex-end;text-align:right;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.agent-thomas-t{color:var(--thomas-gold);font-weight:600}.agent-thomas-t-verified{color:#e9eaf5a6;font-weight:400;font-size:.85em}.agent-thomas-sub{font-size:.78rem;color:var(--fg-muted);margin-top:.15rem}.tools-ranking .module-title{display:flex;align-items:center;gap:.5rem}.tools-rows{display:flex;flex-direction:column;gap:.4rem;margin-top:.5rem}.tools-row{display:grid;grid-template-columns:1fr 2fr 3rem 4rem 1.6fr;align-items:center;gap:.75rem;font-size:13px}.tools-name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tools-bar-track{height:8px;background:#ffffff0d;border-radius:4px;overflow:hidden;position:relative}.tools-bar-fill{height:100%;background:#f5b50080;border-radius:4px;position:absolute;left:0;top:0;transition:width .4s}.tools-bar-error{height:100%;background:#dc4646b3;border-radius:4px;position:absolute;left:0;top:0;transition:width .4s}.tools-count{text-align:right;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--fg-muted)}.tools-cost{text-align:right;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--thomas-gold);font-size:12px}.tools-agents{color:var(--fg-muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.agent-thomas-row{display:grid;grid-template-columns:1.6fr 3fr 1.5fr;grid-template-rows:auto auto;align-items:center;gap:.4rem .8rem}.agent-subagents{grid-column:1 / -1;display:flex;flex-wrap:wrap;gap:.35rem;padding-left:.2rem;padding-top:.15rem}.agent-subagent-chip{display:inline-flex;align-items:baseline;gap:.35rem;padding:.15rem .5rem;border-radius:9999px;background:#f5b50014;border:1px solid rgba(245,181,0,.18);font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.agent-subagent-name{color:var(--fg)}.agent-subagent-count{color:var(--fg-muted)}.full-messages{margin-top:1rem;display:flex;flex-direction:column;gap:.5rem}.full-messages-head{display:flex;align-items:baseline;gap:.5rem;margin-bottom:.3rem}.full-messages-title{font-size:13px;font-weight:600;color:var(--fg)}.full-messages-count{background:#ffffff14;color:var(--fg-muted);font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;padding:.1rem .5rem;border-radius:9999px}.full-messages-list{display:flex;flex-direction:column;gap:.75rem}.msg-card{border:1px solid rgba(255,255,255,.08);border-radius:6px;background:#ffffff06;overflow:hidden}.msg-card-head{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.5rem .75rem;background:#ffffff08;border-bottom:1px solid rgba(255,255,255,.06);font-size:12px;flex-wrap:wrap}.msg-card-head-left{display:flex;align-items:center;flex-wrap:wrap;gap:.5rem;min-width:0}.msg-card-num{color:var(--fg-muted);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.msg-role{font-size:11px;font-weight:600;padding:.1rem .55rem;border-radius:9999px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;text-transform:lowercase;letter-spacing:.02em;border:1px solid}.msg-role-system{background:#a855f71f;color:#d8b4fe;border-color:#d8b4fe66}.msg-role-user{background:#3b82f61f;color:#93c5fd;border-color:#93c5fd66}.msg-role-assistant{background:#10b9811f;color:#6ee7b7;border-color:#6ee7b766}.msg-role-tool{background:#f59e0b1f;color:#fcd34d;border-color:#fcd34d66}.msg-role-developer,.msg-role-function{background:#6366f11f;color:#a5b4fc;border-color:#a5b4fc66}.msg-role-unknown{background:#a0a0a01f;color:#d2d2d2d9;border-color:#a0a0a059}.msg-meta{font-size:11px;color:var(--fg-muted);font-family:-apple-system,BlinkMacSystemFont,sans-serif;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.msg-meta code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--fg);background:transparent;padding:0}.msg-expand-btn{background:#ffffff0d;border:1px solid rgba(255,255,255,.1);color:var(--fg);font-size:11px;padding:.15rem .55rem;border-radius:3px;cursor:pointer;font-family:-apple-system,BlinkMacSystemFont,sans-serif;white-space:nowrap;transition:background .12s,border-color .12s}.msg-expand-btn:hover{background:#f5b5001f;border-color:#f5b50059}.msg-card-body{margin:0;padding:.65rem .85rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;line-height:1.55;color:#e6edf3;white-space:pre-wrap;word-break:break-word;max-height:28rem;overflow:auto;background:transparent}.msg-card-toolcalls{display:flex;flex-direction:column;gap:.4rem;padding:.5rem .75rem .65rem;border-top:1px solid rgba(255,255,255,.06);background:#0000001f}.toolcall-card{border:1px solid rgba(255,255,255,.08);border-radius:5px;background:#ffffff06;overflow:hidden}.toolcall-card-head{display:flex;align-items:center;gap:.5rem;padding:.35rem .6rem;border-bottom:1px solid rgba(255,255,255,.06);font-size:11.5px;flex-wrap:wrap}.toolcall-badge{font-size:10px;font-weight:600;padding:.08rem .5rem;border-radius:9999px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;text-transform:lowercase;letter-spacing:.04em;background:#f59e0b1f;color:#fcd34d;border:1px solid rgba(252,211,77,.4)}.toolcall-name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:600;color:var(--fg);background:transparent;padding:0}.toolcall-id{font-size:11px;color:var(--fg-muted)}.toolcall-id code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--fg);background:transparent;padding:0}.toolcall-card-body{margin:0;padding:.5rem .75rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;line-height:1.55;color:#e6edf3;white-space:pre-wrap;word-break:break-word;max-height:16rem;overflow:auto;background:#0000002e}.args-list{margin:0;padding:.45rem .75rem .5rem;display:grid;grid-template-columns:max-content 1fr;gap:.15rem .85rem;background:#00000029;max-height:18rem;overflow:auto;align-content:start}.args-list-result{max-height:32rem;background:transparent;padding:.65rem .85rem}.msg-card-result{background:#0000001f}.args-row{display:contents}.args-key{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;color:#fcd34d;font-weight:500;padding-top:.05rem;white-space:nowrap}.args-key-nested{color:#fcd34db3}.args-val{margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.55;color:#e6edf3;word-break:break-word;white-space:pre-wrap;min-width:0}.args-string{color:#a5d6ff}.args-num{color:#79c0ff}.args-bool{color:#ffa657;font-weight:500}.args-dim{color:var(--fg-muted)}.args-array{color:#e6edf3}.args-array-item{color:#a5d6ff}.args-array-item:not(:last-child):after{content:", ";color:var(--fg-muted)}.args-nested{display:grid;grid-template-columns:max-content 1fr;gap:.1rem .65rem;padding:.25rem 0 .1rem .6rem;border-left:1px dashed rgba(255,255,255,.1);margin:.15rem 0}.conversation{display:flex;flex-direction:column;gap:.6rem;margin-top:.6rem}.turn{border-left:2px solid rgba(255,255,255,.08);padding:.5rem .85rem .6rem;margin-left:.25rem}.turn-user{border-left-color:#508cdc99;background:#508cdc06;border-radius:0 4px 4px 0}.turn-assistant{border-left-color:#f5b500b3;background:#f5b50005;border-radius:0 4px 4px 0}.turn-mcp{border-left-color:#b464dc8c;background:#b464dc06;border-radius:0 4px 4px 0;padding:.35rem .85rem .4rem}.turn-role{display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--fg-muted);font-weight:600;margin-bottom:.35rem}.turn-assistant .turn-role>span:first-child{color:#f5b500f2}.turn-user .turn-role>span:first-child{color:#78aaf0f2}.turn-mcp .turn-role{color:#c88cebf2}.turn-stamp{font-size:10px;letter-spacing:normal;text-transform:none;font-weight:400;color:var(--fg-muted);display:inline-flex;align-items:baseline;gap:.25rem}.turn-stamp code{font-size:10px;background:transparent;padding:0;color:var(--fg)}.turn-stamp .dim{color:#b4b4b480}.turn-stamp .err{color:#ef4444}.turn-risks{display:inline-flex;gap:.35rem;flex-wrap:wrap;margin-left:auto}.turn-blocks{display:flex;flex-direction:column;gap:.5rem}.block-text{font-size:14px;line-height:1.6;white-space:pre-wrap;word-break:break-word;color:var(--fg);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.block-text+.block-text{margin-top:.25rem}.block-thinking{font-size:12px;color:var(--fg-muted)}.block-thinking summary{cursor:pointer;font-style:italic;color:var(--fg-muted)}.block-thinking-text{margin-top:.35rem;padding:.5rem .7rem;border-left:2px solid rgba(160,160,160,.3);white-space:pre-wrap;background:#ffffff05;font-style:italic;font-size:12.5px}.tool-card{background:#f5b5000d;border:1px solid rgba(245,181,0,.22);border-radius:5px;overflow:hidden}.tool-card-err{border-color:#ef444466}.tool-card-head{display:flex;align-items:baseline;flex-wrap:wrap;gap:.45rem;font-size:13.5px;padding:.45rem .65rem;border-bottom:1px dashed rgba(245,181,0,.18)}.tool-card-icon{color:#f5b500f2;font-weight:700;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.tool-card-name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:600;color:var(--fg);background:#f5b5001f;padding:0 .4rem;border-radius:3px;font-size:12px}.tool-card-headline{color:var(--fg-muted);font-size:13px;overflow:hidden;text-overflow:ellipsis;word-break:break-word;flex:1}.tool-card-headline code{font-size:12px;background:transparent;padding:0;color:var(--fg)}.tool-card-headline .dim{color:#b4b4b48c}.tool-card-headline .cmd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--fg);font-size:12px}.tool-card-body{font-size:12px;padding:.45rem .65rem .5rem;border-bottom:1px dashed rgba(245,181,0,.12)}.tool-card-body:last-child{border-bottom:none}.tool-diff{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;border-radius:3px;overflow:hidden}.tool-diff .diff-removed{background:#ef444414;border-left:2px solid rgba(239,68,68,.4);padding:.3rem .55rem;white-space:pre-wrap;color:#ef6464f2}.tool-diff .diff-added{background:#22c55e14;border-left:2px solid rgba(34,197,94,.4);padding:.3rem .55rem;white-space:pre-wrap;color:#50c878f2;margin-top:2px}.tool-note{font-size:12px;color:var(--fg-muted);white-space:pre-wrap;padding-left:.6rem;border-left:2px solid rgba(160,160,160,.2)}.todo-list{list-style:none;padding:0;margin:0;font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.todo-list li{padding:1px 0}.todo-list .todo-completed{color:#b4b4b4b3;text-decoration:line-through}.todo-list .todo-in_progress{color:#f5b500f2}.tool-args{display:flex;flex-wrap:wrap;gap:.35rem .85rem;margin:0;padding:0;font-size:11.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.tool-arg-key{color:var(--fg-muted)}.tool-arg-val{color:var(--fg)}.tool-arg.dim{color:#b4b4b48c}.tool-result{padding:.45rem .65rem .5rem;background:#ffffff05;font-size:12.5px}.tool-result-ok{border-left:2px solid rgba(34,197,94,.5)}.tool-result-err{border-left:2px solid rgba(239,68,68,.6);background:#ef44440a}.tool-result-head{display:flex;align-items:baseline;gap:.45rem;margin-bottom:.2rem}.tool-result-icon{font-weight:700;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.tool-result-ok .tool-result-icon{color:#50c878f2}.tool-result-err .tool-result-icon{color:#ef6464f2}.tool-result-summary{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:var(--fg);word-break:break-word}.tool-result-summary.dim{color:var(--fg-muted)}.tool-result-output{margin:.25rem 0 0;padding:.35rem .5rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;white-space:pre-wrap;word-break:break-word;max-height:16rem;overflow:auto;background:#0000002e;border-radius:3px;color:var(--fg)}.tool-result-error{margin-top:.3rem;font-size:11px;color:#ef6464f2;white-space:pre-wrap}.tool-result-url{margin:.2rem 0 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:#78aaf0d9;word-break:break-all}.tool-result-kv{display:flex;flex-wrap:wrap;gap:.35rem .85rem;margin:.25rem 0 0;padding:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px}.mcp-compact .mcp-compact-line{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:var(--fg)}.mcp-compact .mcp-method{color:var(--fg);background:#b464dc1f;padding:0 .35rem;border-radius:3px}.mcp-compact .dim{color:#b4b4b48c}.run-list-wrap{display:flex;flex-direction:column;gap:.6rem}.pager{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-top:.5rem;font-size:12px;color:var(--fg-muted);padding:.5rem .25rem;border-top:1px solid rgba(255,255,255,.06)}.pager-count{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.pager-controls{display:flex;align-items:center;gap:.35rem}.pager-btn{background:#ffffff0a;border:1px solid rgba(255,255,255,.08);color:var(--fg);padding:.25rem .6rem;border-radius:3px;font-size:12px;cursor:pointer;font-family:-apple-system,BlinkMacSystemFont,sans-serif;transition:background .12s}.pager-btn:hover:not(:disabled){background:#f5b5001f;border-color:#f5b50059}.pager-btn:disabled{opacity:.35;cursor:not-allowed}.pager-page{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;padding:0 .4rem;color:var(--fg)}.msg-card-prompt{padding:.55rem .85rem .7rem}.prompt-structured{display:flex;flex-direction:column;gap:.5rem}.prompt-lede .prompt-section-body{background:#a855f70f;border-left:2px solid rgba(216,180,254,.45);padding:.45rem .7rem;border-radius:0 3px 3px 0;font-style:normal}.prompt-section{border:1px solid rgba(255,255,255,.06);background:#ffffff05;border-radius:5px;overflow:hidden}.prompt-section-title{display:flex;align-items:center;flex-wrap:wrap;gap:.5rem;padding:.35rem .7rem;background:#a855f70f;border-bottom:1px solid rgba(216,180,254,.18);font-size:12.5px;font-weight:600;color:#e6d6fa}.prompt-section-marker{color:#d8b4feb3;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:700;font-size:12px}.prompt-section-count{background:#d8b4fe24;border:1px solid rgba(216,180,254,.3);color:#d8b4fe;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10.5px;padding:.02rem .45rem;border-radius:9999px;font-weight:600}.prompt-section-expand{margin-left:auto}.prompt-section-body{padding:.55rem .85rem .65rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.55;white-space:pre-wrap;word-break:break-word;color:#d4dae3;max-height:22rem;overflow:auto}.prompt-skills .prompt-section-title{background:#f59e0b0f;border-bottom-color:#fcd34d38;color:#fde68a}.prompt-skills .prompt-section-marker{color:#fcd34d}.prompt-skills .prompt-section-count{background:#f59e0b1f;border-color:#fcd34d66;color:#fcd34d}.prompt-skills-groups{display:flex;flex-direction:column;gap:.5rem;padding:.55rem .7rem .7rem}.prompt-skills-group-name{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:#fcd34db3;margin-bottom:.25rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.prompt-skills-list{display:flex;flex-wrap:wrap;gap:.35rem;padding:.55rem .7rem .7rem}.prompt-skills-group .prompt-skills-list{padding:0}.skill-chip{display:inline-flex;align-items:baseline;gap:.4rem;padding:.15rem .55rem;border-radius:9999px;background:#f59e0b14;border:1px solid rgba(252,211,77,.28);font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;max-width:32rem}.skill-chip-name{color:#fcd34d;font-weight:600}.skill-chip-desc{color:var(--fg-muted);font-family:-apple-system,BlinkMacSystemFont,sans-serif;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:26rem}.prompt-skills-more{align-self:center;font-size:11px;color:var(--fg-muted);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.prompt-reminder .prompt-section-title{background:#fca5a50f;border-bottom-color:#fca5a538;color:#fecaca}.prompt-reminder .prompt-section-marker{color:#fca5a5}.prompt-reminder-body{background:#fca5a505}.models-page{display:flex;flex-direction:column;gap:1.25rem}.models-head{display:flex;flex-direction:column;gap:.35rem}.models-head h2{margin:0;font-size:1.2rem;font-weight:600;letter-spacing:-.005em}.models-head .dim{font-size:.85rem;color:var(--fg-muted)}.models-head a{color:var(--accent);text-decoration:none}.models-head a:hover{text-decoration:underline}.models-table{width:100%;border-collapse:collapse;font-size:13px}.models-table th,.models-table td{text-align:left;padding:.4rem .55rem;border-bottom:1px solid var(--border);vertical-align:middle}.models-table th{font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--fg-muted);font-weight:500}.models-table th.num,.models-table td.num{text-align:right;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-variant-numeric:tabular-nums;white-space:nowrap}.models-table .dim{color:var(--fg-muted)}.models-table td code{background:transparent;color:var(--fg);font-size:12px;padding:0}.models-table tr.is-editing{background:#f5b5000a}.models-table tr.edit-row td{border-bottom:1px solid var(--border);background:#ffffff05;padding:.65rem .85rem}.models-table .row-actions{white-space:nowrap;text-align:right}.models-table .row-actions>*+*{margin-left:.35rem}.btn-small{background:#ffffff0a;border:1px solid rgba(255,255,255,.1);color:var(--fg);padding:.2rem .6rem;border-radius:3px;font-size:11px;cursor:pointer;font-family:-apple-system,BlinkMacSystemFont,sans-serif}.btn-small:hover:not(:disabled){background:#f5b5001f;border-color:#f5b50059}.btn-small:disabled{opacity:.4;cursor:not-allowed}.btn-small.btn-primary{background:#f5b50029;border-color:#f5b50080;color:#fcd34d}.btn-small.btn-primary:hover:not(:disabled){background:#f5b5003d}.btn-small.btn-danger{border-color:#ef444466;color:#ef6464f2}.btn-small.btn-danger:hover:not(:disabled){background:#ef44441f}.badge{font-size:10px;font-weight:600;padding:.1rem .5rem;border-radius:9999px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;text-transform:lowercase;letter-spacing:.02em;border:1px solid}.badge-custom{background:#f5b5001f;color:#fcd34d;border-color:#fcd34d66}.badge-resolved{background:#10b9811a;color:#6ee7b7;border-color:#6ee7b759}.badge-missing{background:#ef444414;color:#ef6464f2;border-color:#ef444459}.catalog-filters{display:flex;align-items:center;gap:.6rem;padding:.35rem 0 .5rem;font-size:12px}.catalog-search{flex:1;max-width:22rem;background:#ffffff0d;border:1px solid var(--border);color:var(--fg);padding:.3rem .55rem;border-radius:3px;font:inherit}.catalog-filters select{background:#ffffff0d;border:1px solid var(--border);color:var(--fg);padding:.3rem .55rem;border-radius:3px;font:inherit}.catalog-filters .dim{color:var(--fg-muted);margin-left:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.add-card{background:#ffffff05;border:1px solid var(--border);border-radius:5px;padding:.65rem .85rem;margin-bottom:.6rem}.pricing-editor{display:flex;flex-direction:column;gap:.55rem}.pricing-editor-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:.5rem .85rem}.pricing-editor label{display:flex;flex-direction:column;gap:.2rem;font-size:11px;color:var(--fg-muted)}.pricing-editor label>span{font-size:10px;letter-spacing:.08em;text-transform:uppercase}.pricing-editor input,.pricing-editor select{background:#ffffff0d;border:1px solid var(--border);color:var(--fg);padding:.3rem .5rem;border-radius:3px;font:inherit;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.pricing-editor input:disabled,.pricing-editor select:disabled{opacity:.55}.pricing-editor-err{color:#ef6464f2;font-size:12px}.pricing-editor-actions{display:flex;justify-content:flex-end;gap:.4rem}.model-cell{max-width:22rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.prompt-tagged .prompt-section-title{background:#6366f114;border-bottom-color:#a5b4fc38;color:#c7d2fe}.prompt-tagged .prompt-section-marker{color:#a5b4fc;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.prompt-tagged-body{background:#6366f105}.thomas-magnitude-block{display:flex;flex-direction:column;align-items:flex-start;gap:.15rem}.thomas-magnitude{font-size:2.6rem;font-weight:700;line-height:1;color:#d4dae3;letter-spacing:-.02em;font-variant-numeric:tabular-nums}:root{--tier-personal: #58a6ff;--tier-solo: #d97706;--tier-team: #a371f7;--pillar-locked-fg: #6b7280;--pillar-locked-bg: rgba(255, 255, 255, .015)}.nav{display:flex;flex-direction:column;gap:0;align-items:stretch;padding-bottom:0;margin-bottom:1.5rem;border-bottom:1px solid var(--border)}.nav-row{display:flex;align-items:center;gap:1.5rem}.nav-row-main{padding:.5rem 0 .7rem}.nav-row-sub{padding:.45rem 0 .6rem;border-top:1px solid rgba(255,255,255,.04);font-size:.78rem}.nav-pillars{display:flex;align-items:stretch;gap:.25rem}.nav-pillar{display:flex;flex-direction:column;gap:.1rem;padding:.4rem .85rem;border-radius:6px;text-decoration:none;color:var(--fg);border:1px solid transparent;transition:background .15s,border-color .15s,color .15s;position:relative}.nav-pillar-label{font-size:.92rem;font-weight:600;letter-spacing:-.005em;display:inline-flex;align-items:center;gap:.35em}.nav-pillar-sub{font-size:.66rem;text-transform:uppercase;letter-spacing:.07em;color:var(--fg-muted)}.nav-pillar:hover{background:#ffffff08;border-color:var(--border)}.nav-pillar.active{background:#d9770612;border-color:#d9770652}.nav-pillar.active .nav-pillar-sub{color:var(--accent-amber)}.nav-pillar-locked,.nav-pillar-locked .nav-pillar-sub{color:var(--pillar-locked-fg)}.nav-pillar-locked.active{background:#6366f10f;border-color:#6366f14d;color:var(--fg)}.nav-pillar-locked.active .nav-pillar-sub{color:#818cf8}.nav-pillar-lock{font-size:.7em;opacity:.7}.nav-sub{display:flex;gap:1.25rem;padding-left:.25rem}.nav-sub a{color:var(--fg-muted);text-decoration:none;padding:.2rem 0;border-bottom:2px solid transparent;transition:color .15s,border-color .15s}.nav-sub a:hover{color:var(--fg)}.nav-sub a.active{color:var(--fg);border-bottom-color:var(--accent-amber)}.nav-tier-badge{display:inline-flex;align-items:center;gap:.4rem;padding:.3rem .7rem;border-radius:999px;border:1px solid rgba(217,119,6,.35);background:#d9770614;color:var(--fg);text-decoration:none;font-size:.78rem;font-weight:500;transition:background .15s,border-color .15s}.nav-tier-badge:hover,.nav-tier-badge.active{background:#d9770629;border-color:var(--accent-amber)}.nav-tier-dot{width:7px;height:7px;border-radius:50%;background:var(--accent-amber);display:inline-block}.nav-tier-text{font-weight:600}.nav-tier-sub{color:var(--fg-muted);font-size:.7rem;letter-spacing:.04em;text-transform:uppercase;border-left:1px solid rgba(217,119,6,.3);padding-left:.4rem}.pillar-locked{display:flex;flex-direction:column;gap:2rem;font:.9rem/1.55 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.pillar-hero{display:grid;grid-template-columns:1fr 320px;gap:2rem;align-items:stretch;padding:1.75rem 2rem;border-radius:12px;background:linear-gradient(135deg,#6366f10f,#d977060a);border:1px solid rgba(99,102,241,.18)}.pillar-hero-left h1{margin:.2rem 0 .6rem;font-size:2.2rem;font-weight:700;letter-spacing:-.025em;color:var(--fg)}.pillar-kicker{font-size:.72rem;text-transform:uppercase;letter-spacing:.12em;color:#818cf8}.pillar-lock{font-size:.85em;margin-right:.25em}.pillar-tagline{margin:0;font-size:1.05rem;line-height:1.5;color:#c9d1d9;max-width:56ch}.pillar-unlock-card{display:flex;flex-direction:column;gap:.4rem;padding:1.1rem 1.25rem;border-radius:10px;background:#00000040;border:1px solid rgba(217,119,6,.3)}.pillar-unlock-eyebrow{font-size:.7rem;text-transform:uppercase;letter-spacing:.1em;color:var(--fg-muted)}.pillar-unlock-tier{font-size:1.2rem;font-weight:700;color:var(--fg);letter-spacing:-.01em}.pillar-unlock-price{font-size:.92rem;color:var(--accent-amber);margin-bottom:.5rem}.pillar-cta-primary{display:block;padding:.55rem .9rem;background:var(--accent-amber);color:#fff;border-radius:6px;text-align:center;text-decoration:none;font-weight:600;transition:background .15s}.pillar-cta-primary:hover{background:#b45309}.pillar-cta-secondary{display:block;padding:.45rem .9rem;border:1px solid var(--border);border-radius:6px;text-align:center;text-decoration:none;color:var(--fg);font-size:.85rem;transition:border-color .15s}.pillar-cta-secondary:hover{border-color:var(--fg)}.pillar-section{display:flex;flex-direction:column;gap:.9rem}.pillar-section-head{display:flex;align-items:baseline;gap:1rem;flex-wrap:wrap}.pillar-section-head h3{margin:0;font-size:1.05rem;font-weight:600;color:var(--fg)}.pillar-section-sub{font-size:.82rem;color:var(--fg-muted)}.pillar-free-list{list-style:none;padding:0;margin:0;display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:.5rem 1.5rem}.pillar-free-list li{display:flex;align-items:flex-start;gap:.45rem;color:var(--fg);font-size:.88rem}.pillar-check{color:var(--mcp);font-weight:700;flex-shrink:0}.pillar-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:.85rem}.pillar-card{display:flex;flex-direction:column;gap:.55rem;padding:1rem 1.1rem;border-radius:8px;border:1px solid var(--border);background:#ffffff05}.pillar-card-locked{background:var(--pillar-locked-bg);border-style:dashed;border-color:#ffffff1f;position:relative}.pillar-card-locked:before{content:"🔒";position:absolute;top:.75rem;right:.85rem;font-size:.85em;opacity:.4}.pillar-card-head{display:flex;flex-direction:column;gap:.3rem}.pillar-card-head h4{margin:0;font-size:.95rem;font-weight:600;color:var(--fg);padding-right:1.5em}.pillar-card p{margin:0;font-size:.84rem;line-height:1.5;color:#b1bac4}.pillar-card-foot{margin-top:auto;padding-top:.5rem;font-size:.72rem;color:var(--fg-muted)}.pillar-status{display:inline-block;padding:.1rem .5rem;border-radius:999px;border:1px solid var(--border);background:#ffffff05;color:var(--fg-muted);letter-spacing:.02em}.pillar-tier-chip{display:inline-flex;align-items:center;padding:.12rem .55rem;border-radius:999px;font-size:.7rem;font-weight:600;letter-spacing:.02em;width:fit-content}.tier-personal{background:#58a6ff1f;color:var(--tier-personal);border:1px solid rgba(88,166,255,.35)}.tier-solo{background:#d977061f;color:var(--accent-amber);border:1px solid rgba(217,119,6,.35)}.tier-team{background:#a371f71f;color:var(--tier-team);border:1px solid rgba(163,113,247,.35)}.pillar-prose{margin:0;max-width:70ch;color:#b1bac4}.pillar-foot{border-top:1px solid var(--border);padding-top:1rem;font-size:.85rem;color:var(--fg-muted)}.pillar-foot a{color:var(--accent-amber);text-decoration:none}.pillar-foot a:hover{text-decoration:underline}.pricing-page{font:.9rem/1.55 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;display:flex;flex-direction:column;gap:2.5rem}.pricing-hero{display:flex;flex-direction:column;gap:.7rem;padding:1.5rem 0 1.8rem;border-bottom:1px solid var(--border)}.pricing-kicker{font-size:.72rem;text-transform:uppercase;letter-spacing:.12em;color:var(--accent-amber)}.pricing-hero h1{margin:0;font-size:2.3rem;font-weight:700;letter-spacing:-.025em;color:var(--fg)}.pricing-hero-accent{color:var(--accent-amber)}.pricing-sub{margin:0;max-width:72ch;font-size:1rem;line-height:1.55;color:#b1bac4}.pricing-pillars-row{display:flex;gap:.6rem;flex-wrap:wrap;margin-top:.4rem}.pillar-chip{display:inline-flex;align-items:center;padding:.3rem .75rem;border-radius:999px;font-size:.78rem;font-weight:600;letter-spacing:.01em}.pillar-chip.verb-see{background:#d977061f;color:var(--accent-amber);border:1px solid rgba(217,119,6,.4)}.pillar-chip.verb-control{background:#58a6ff1a;color:var(--tier-personal);border:1px solid rgba(88,166,255,.35)}.pillar-chip.verb-govern{background:#a371f71a;color:var(--tier-team);border:1px solid rgba(163,113,247,.35)}.tier-enterprise{background:#a371f71f;color:var(--tier-team);border:1px solid rgba(163,113,247,.35)}.pricing-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(205px,1fr));gap:.85rem}.pricing-card{display:flex;flex-direction:column;gap:1.25rem;padding:1.5rem 1.4rem;border:1px solid var(--border);border-radius:12px;background:#ffffff05;transition:border-color .15s,transform .15s}.pricing-card-featured{border-color:#d9770680;background:linear-gradient(180deg,#d977060f,#ffffff05 60%);box-shadow:0 10px 30px #d9770614}.pricing-card-enterprise{border-color:#a371f752;background:linear-gradient(180deg,#a371f70d,#ffffff05 60%)}.pricing-card-enterprise .pricing-card-cta{background:var(--tier-team);border-color:var(--tier-team);color:#fff}.pricing-card-enterprise .pricing-card-cta:hover{background:#8a52f5;border-color:#8a52f5}.pricing-card-head{display:flex;flex-direction:column;gap:.5rem}.pricing-card-name{font-size:1.1rem;font-weight:700;color:var(--fg);letter-spacing:-.01em}.pricing-card-pillar{font-size:.72rem;text-transform:uppercase;letter-spacing:.08em;color:var(--fg-muted)}.pricing-card-price-row{display:flex;align-items:baseline;gap:.35rem;margin-top:.5rem}.pricing-card-price{font-size:1.7rem;font-weight:700;letter-spacing:-.02em;color:var(--fg)}.pricing-card-unit{font-size:.78rem;color:var(--fg-muted)}.pricing-card-tagline{font-size:.86rem;color:#b1bac4;line-height:1.45}.pricing-features{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:.45rem}.pricing-features li{display:flex;align-items:flex-start;gap:.5rem;font-size:.85rem;line-height:1.45}.pricing-feature-on{color:var(--fg)}.pricing-feature-off{color:var(--fg-muted)}.pricing-feature-mark{flex-shrink:0;width:1.1em;text-align:center;font-weight:700}.pricing-feature-on .pricing-feature-mark{color:var(--mcp)}.pricing-feature-off .pricing-feature-mark{color:var(--border)}.pricing-card-cta{margin-top:auto;padding:.6rem 1rem;text-align:center;text-decoration:none;border-radius:6px;font-weight:600;font-size:.88rem;border:1px solid var(--border);color:var(--fg);transition:background .15s,border-color .15s}.pricing-card-cta:hover{border-color:var(--fg)}.pricing-card-featured .pricing-card-cta{background:var(--accent-amber);border-color:var(--accent-amber);color:#fff}.pricing-card-featured .pricing-card-cta:hover{background:#b45309;border-color:#b45309}.pricing-note{padding:1.5rem 1.75rem;border-radius:10px;background:#ffffff05;border:1px solid var(--border)}.pricing-note h3{margin:0 0 .75rem;font-size:1rem;font-weight:600;color:var(--fg)}.pricing-note p{margin:0 0 .85rem;max-width:78ch;color:#b1bac4}.pricing-fineprint{font-size:.82rem;color:var(--fg-muted)!important}.pricing-faq h3{margin:0 0 1rem;font-size:1.1rem;font-weight:600}.pricing-faq-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.5rem}.pricing-faq-grid h4{margin:0 0 .4rem;font-size:.95rem;font-weight:600;color:var(--fg)}.pricing-faq-grid p{margin:0;font-size:.86rem;line-height:1.5;color:#b1bac4}.pricing-faq-grid a,.pricing-note a{color:var(--accent-amber);text-decoration:none}.pricing-faq-grid a:hover,.pricing-note a:hover{text-decoration:underline}.pricing-faq-grid code,.pricing-note code{font:.85em ui-monospace,SFMono-Regular,Menlo,monospace;background:#ffffff0f;padding:.05em .4em;border-radius:3px;color:var(--fg)}.harness-diagram{font:.85rem/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.hd-frame{border:2px solid rgba(217,119,6,.4);border-radius:14px;padding:1.1rem 1.25rem 1.25rem;background:linear-gradient(180deg,#d977060d,#ffffff04 40%);position:relative}.hd-head{display:flex;align-items:baseline;gap:.75rem;flex-wrap:wrap;margin-bottom:.9rem}.hd-label{font-size:.95rem;font-weight:700;color:var(--fg);letter-spacing:-.01em}.hd-tagline{font-size:.78rem;color:var(--fg-muted)}.hd-components{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:.6rem;margin-bottom:.9rem}.hd-tile{border:1px solid var(--border);border-radius:8px;padding:.6rem .7rem;background:#0d11178c;display:flex;flex-direction:column;gap:.3rem}.hd-tile-name{font-size:.84rem;font-weight:700;color:var(--fg)}.hd-tile-free{font-size:.76rem;color:#b1bac4;line-height:1.4}.hd-tile-paid{font-size:.76rem;color:#8b97f5;line-height:1.4}.hd-track{display:flex;gap:.25rem;margin-top:.15rem}.hd-verb{flex:1;text-align:center;font-size:.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;padding:.15rem 0;border-radius:4px;border:1px solid var(--border)}.hd-verb.off{color:#3a414d;border-color:#ffffff0d}.hd-verb.on.verb-see{color:var(--accent-amber);background:#d9770624;border-color:#d9770666}.hd-verb.on.verb-control{color:var(--tier-personal);background:#58a6ff1f;border-color:#58a6ff66}.hd-verb.on.verb-govern{color:var(--tier-team);background:#a371f71f;border-color:#a371f766}.hd-fleet{border:1px dashed rgba(255,255,255,.18);border-radius:9px;padding:.7rem .85rem;background:#00000040}.hd-fleet-label{font-size:.68rem;text-transform:uppercase;letter-spacing:.12em;color:var(--fg-muted)}.hd-fleet-chips{display:flex;flex-wrap:wrap;gap:.45rem;margin-top:.5rem}.hd-agent{display:inline-flex;align-items:center;gap:.4rem;padding:.3rem .65rem;border-radius:6px;border:1px solid var(--border);background:#ffffff08;font-size:.8rem;color:var(--fg)}.hd-agent-byoa{border-style:dashed;border-color:#a371f780;color:#c9b6f5}.hd-byoa-tag{font-size:.6rem;font-weight:700;letter-spacing:.06em;padding:.05rem .35rem;border-radius:999px;background:#a371f733;color:var(--tier-team)}.pricing-card-toprow{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.pricing-verb-badge{font-size:.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;padding:.15rem .5rem;border-radius:999px}.pricing-verb-badge.verb-see{color:var(--accent-amber);background:#d9770624;border:1px solid rgba(217,119,6,.4)}.pricing-verb-badge.verb-control{color:var(--tier-personal);background:#58a6ff1f;border:1px solid rgba(88,166,255,.4)}.pricing-verb-badge.verb-govern{color:var(--tier-team);background:#a371f71f;border:1px solid rgba(163,113,247,.4)}.pricing-family{display:flex;align-items:center;justify-content:space-between;gap:1.5rem;padding:1.25rem 1.5rem;border:1px dashed rgba(163,113,247,.4);border-radius:10px;background:#a371f70a;font:.9rem/1.55 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.pricing-family-name{font-size:1.05rem;font-weight:700;color:var(--fg);margin-bottom:.35rem}.pricing-family-tag{font-size:.66rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--tier-team);border:1px solid rgba(163,113,247,.4);border-radius:999px;padding:.1rem .5rem;margin-left:.5rem;vertical-align:middle}.pricing-family-left p{margin:0;max-width:70ch;color:#b1bac4;font-size:.86rem}.pricing-family-cta{flex-shrink:0;padding:.55rem 1rem;border:1px solid rgba(163,113,247,.5);border-radius:6px;color:#c9b6f5;text-decoration:none;font-weight:600;font-size:.85rem;white-space:nowrap}.pricing-family-cta:hover{background:#a371f71f}.pillar-unlock-note{font-size:.74rem;color:var(--fg-muted);line-height:1.4;margin-top:.5rem;padding-top:.5rem;border-top:1px solid rgba(255,255,255,.06)}.pillar-byoa{padding:1.1rem 1.25rem;border-radius:10px;border:1px solid rgba(163,113,247,.22);background:#a371f70d}.pillar-family{padding:1.1rem 1.25rem;border-radius:10px;border:1px dashed rgba(163,113,247,.3);background:#a371f708}.update-banner{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin:0 0 1.25rem;padding:.7rem 1rem;border-radius:6px;border:1px solid var(--border);font:.85rem/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.update-banner-available{border-color:#d9770680;background:#d9770614}.update-banner-progress{border-color:#58a6ff73;background:#58a6ff14}.update-banner-ok{border-color:#3fb95080;background:#3fb95017}.update-banner-fail{border-color:var(--risk-high);background:#f851491a}.update-actions{display:flex;gap:.5rem;flex-shrink:0}.update-btn{font:inherit;font-size:.8rem;padding:.3rem .75rem;border-radius:5px;border:1px solid var(--border);background:transparent;color:var(--fg);cursor:pointer;transition:background .15s,border-color .15s}.update-btn:hover{border-color:var(--fg)}.update-btn.primary{background:var(--accent-amber);border-color:var(--accent-amber);color:#fff;font-weight:600}.update-btn.primary:hover{background:#b45309;border-color:#b45309}.update-btn:disabled{opacity:.6;cursor:default}.update-spinner{width:12px;height:12px;border:2px solid rgba(88,166,255,.3);border-top-color:var(--accent);border-radius:50%;display:inline-block;animation:update-spin .7s linear infinite;flex-shrink:0}@keyframes update-spin{to{transform:rotate(360deg)}}.view-head{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin-bottom:.25rem}.view-head h2{margin:0;font-size:1.05rem;font-weight:600;color:var(--fg)}.agent-detail-stats{display:flex;flex-wrap:wrap;gap:1.5rem;margin:.5rem 0 1rem}.agent-detail-stat-value{font-size:1.1rem;font-weight:600;color:var(--fg)}.agent-detail-stat-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.06em;color:var(--fg-muted);margin-top:.1rem}table.runs tbody tr.run-errored{background:#f851490f}table.runs tbody tr.run-errored:hover{background:#f851491f}table.runs tbody tr.run-recovered{background:#d299220d}table.runs tbody tr.run-recovered:hover{background:#d299221a}.run-error-badge{font-size:.7rem;font-weight:600;padding:.1rem .45rem;border-radius:3px;border:1px solid var(--risk-high);color:var(--risk-high);background:#f851491f}.run-errors{display:flex;flex-direction:column;gap:.6rem;margin:.75rem 0 1rem}.run-error-card{border:1px solid var(--risk-high);border-left-width:3px;border-radius:5px;background:#f851490f;padding:.6rem .85rem}.run-error-head{display:flex;align-items:center;gap:.55rem;flex-wrap:wrap}.run-error-status{font-weight:600;color:var(--risk-high)}.run-error-model{font-size:.85rem;color:var(--fg-muted)}.run-error-body{margin:.55rem 0 0;padding:.6rem .75rem;border-radius:4px;background:#00000040;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.5;color:#e6edf3;white-space:pre-wrap;word-break:break-word;max-height:22rem;overflow:auto}.run-recovered-note{font-size:.85rem;color:var(--fg-muted);display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.run-recovered-badge,.run-errors-recovered .run-error-badge{font-size:.7rem;font-weight:600;padding:.1rem .45rem;border-radius:3px;border:1px solid var(--risk-warn);color:var(--risk-warn);background:#d299221f}.run-error-card-recovered{border-color:var(--risk-warn);background:#d299220d}a.spend-row{text-decoration:none;color:inherit;cursor:pointer}a.spend-row:hover{background:#ffffff08;border-radius:6px}.spend-retry{color:var(--risk-high);font-weight:600}.home-hero{margin-bottom:1.75rem}.home-hero h1{font-size:1.6rem;margin:0 0 .4rem;letter-spacing:-.01em}.home-sub{color:var(--fg-muted);max-width:62ch;margin:0;line-height:1.6}.home-sub strong{color:var(--fg);font-weight:600}.home-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.85rem;margin-bottom:2rem}.home-card{display:block;text-decoration:none;color:inherit;border:1px solid var(--border);border-radius:8px;padding:1.1rem 1.25rem;background:#ffffff04;transition:border-color .12s ease,background .12s ease}.home-card:hover{border-color:var(--accent);background:#388bfd0f}.home-card-num{font-size:2rem;font-weight:600;line-height:1;color:var(--accent)}.home-card-label{color:var(--fg-muted);font-size:.8rem;margin-top:.4rem}.home-card-cta{margin-top:.85rem;font-size:.8rem;color:var(--accent)}.home-section,.routing section{margin-bottom:2rem}.home-section h2,.routing h2{font-size:.8rem;text-transform:uppercase;letter-spacing:.05em;color:var(--fg-muted);font-weight:600;margin:0 0 .75rem;border-bottom:1px solid var(--border);padding-bottom:.4rem}table.kv-table{width:100%;border-collapse:collapse;font-size:.85rem}table.kv-table th,table.kv-table td{text-align:left;padding:.45rem .75rem;border-bottom:1px solid var(--border);vertical-align:top}table.kv-table th{font-weight:500;color:var(--fg-muted);text-transform:uppercase;font-size:.7rem;letter-spacing:.05em}table.kv-table code.path{word-break:break-all}.routing select{background:var(--bg);color:var(--fg);border:1px solid var(--border);border-radius:4px;padding:.25rem .4rem;font:inherit;max-width:22rem}.routing select:hover{border-color:var(--accent)}.ok-badge{color:var(--mcp);font-size:.8rem}.muted{color:var(--fg-muted)}.guard-summary{display:flex;gap:.5rem;margin-bottom:1rem}.sev-pill{display:inline-block;padding:.05rem .5rem;border-radius:999px;font-size:.72rem;font-weight:600;text-transform:uppercase;letter-spacing:.03em;border:1px solid}.sev-pill.sev-high{color:var(--risk-high);border-color:var(--risk-high);background:#f851491a}.sev-pill.sev-warn{color:var(--risk-warn);border-color:var(--risk-warn);background:#d299221a}.sev-pill.sev-info{color:var(--risk-info);border-color:var(--risk-info);background:#388bfd1a}.guard-detail{color:var(--fg-muted);max-width:42ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.error.inline{padding:.6rem .9rem;text-align:left;color:var(--risk-high);border:1px solid var(--risk-high);border-radius:6px;margin-bottom:1rem;font-size:.85rem}.subtabs{display:flex;gap:.25rem;border-bottom:1px solid var(--border);margin-bottom:1.5rem}.subtab{background:none;border:none;border-bottom:2px solid transparent;color:var(--fg-muted);font:inherit;padding:.5rem .9rem;cursor:pointer;margin-bottom:-1px}.subtab:hover{color:var(--fg)}.subtab.active{color:var(--fg);border-bottom-color:var(--accent)}.add-form{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;margin-top:1rem;padding:.85rem;border:1px dashed var(--border);border-radius:8px}.add-form input,.add-form select{background:var(--bg);color:var(--fg);border:1px solid var(--border);border-radius:4px;padding:.3rem .45rem;font:inherit}.add-form input{min-width:9rem}.add-form input:focus,.add-form select:focus,.routing select:focus{outline:none;border-color:var(--accent)}.kv-table .row-actions{white-space:nowrap;text-align:right}.kv-table .row-actions>*+*{margin-left:.35rem}.reg-name{font-weight:500}.reg-id{display:block;margin-top:.1rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.72rem;color:var(--fg-muted)}.modality-select{display:inline-flex;flex-wrap:wrap;align-items:center;gap:.35rem .8rem;padding:.1rem .2rem}.modality-select .toggle{font-size:.82rem}.modality-select input[type=checkbox]{min-width:0;padding:0}button.btn-plain{background:none;border:1px solid var(--border);color:var(--fg-muted);border-radius:4px;padding:.15rem .5rem;cursor:pointer;font:inherit;font-size:.78rem}button.btn-plain:hover:not(:disabled){border-color:var(--accent);color:var(--accent)}button.btn-plain:disabled{opacity:.4;cursor:not-allowed}.add-form button,.subtab+.subtab,button.btn-danger{font:inherit}.add-form button[type=submit]{background:#388bfd1f;color:var(--accent);border:1px solid var(--accent);border-radius:4px;padding:.3rem .7rem;cursor:pointer}.add-form button[type=submit]:hover{background:#388bfd38}button.btn-danger{background:none;border:1px solid var(--border);color:var(--fg-muted);border-radius:4px;padding:.15rem .5rem;cursor:pointer;font-size:.78rem}button.btn-danger:hover{color:var(--risk-high);border-color:var(--risk-high)}button:disabled{opacity:.5;cursor:default}.toggle{display:inline-flex;align-items:center;gap:.3rem;cursor:pointer}.fallback-edit{display:flex;flex-direction:column;gap:.25rem;align-items:flex-start}.fallback-row{display:inline-flex;align-items:center;gap:.35rem}.task-title{color:var(--fg);display:inline-block;max-width:52ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.detail-back a{color:var(--fg-muted);text-decoration:none;font-size:.85rem}.detail-back a:hover{color:var(--accent)}.task-detail-title{font-size:1.3rem;margin:.6rem 0 .4rem}.task-meta{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;color:var(--fg-muted);font-size:.85rem;margin-bottom:1.5rem}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display