🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@openthomas/thomas

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@openthomas/thomas - npm Package Compare versions

Comparing version
0.4.1
to
0.6.0
+418
dist/backends-CNlW4KGQ.js
import { atomicWrite, fileExists, paths } from "./secrets-C3_a-_Fn.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 Thomas — 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": "thomas"
},
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 };
#!/usr/bin/env node
import { getSecret, readSecrets } from "../secrets-C3_a-_Fn.js";
import { activeProviderFor, readToolProviders, searchBaidu, searchBrave, searchDuckDuckGo } from "../backends-CNlW4KGQ.js";
import process from "node:process";
//#region src/core/web-search/fetch.ts
const FETCH_USER_AGENT = "thomas-tools/0.1 (+https://openthomas.com)";
const DEFAULT_MAX_BYTES = 1024 * 1024;
const DEFAULT_TIMEOUT_MS = 15e3;
async function fetchUrl(opts) {
const policy = checkUrlPolicy(opts.url);
if (!policy.ok) return policy;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
let res;
try {
res = await fetch(opts.url, {
headers: {
"user-agent": FETCH_USER_AGENT,
accept: "text/html, text/plain, */*"
},
redirect: "follow",
signal: controller.signal
});
} catch (err$1) {
clearTimeout(timer);
return {
ok: false,
error: `fetch failed: ${err$1.message}`
};
}
clearTimeout(timer);
const finalPolicy = checkUrlPolicy(res.url);
if (!finalPolicy.ok) return finalPolicy;
const contentType = res.headers.get("content-type") ?? "";
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
const { text, truncated } = await readBounded(res, maxBytes);
const isHtml = /\bhtml\b/i.test(contentType) || /^\s*<!doctype html/i.test(text);
const extracted = isHtml ? htmlToText(text) : text.trim();
const title = isHtml ? extractTitle(text) : void 0;
return {
ok: true,
url: opts.url,
finalUrl: res.url,
status: res.status,
contentType,
...title ? { title } : {},
text: extracted,
truncated
};
}
/** Reject URLs that point at the local machine, private networks,
* link-local space, or cloud metadata endpoints. Operates on the URL
* string only (no DNS resolution) — for ip-literal hosts this is
* exact; for DNS names we still let the request go and re-check after
* redirect. Bypassing this via DNS rebinding would need a layered
* network policy (out of scope for v0). */
function checkUrlPolicy(rawUrl) {
let url;
try {
url = new URL(rawUrl);
} catch {
return {
ok: false,
error: "invalid url"
};
}
if (url.protocol !== "http:" && url.protocol !== "https:") return {
ok: false,
error: `unsupported scheme: ${url.protocol}`
};
const host = url.hostname.toLowerCase();
if (BLOCKED_HOSTS.has(host)) return {
ok: false,
error: `host "${host}" is on the local-network deny list`
};
if (isPrivateIpLiteral(host)) return {
ok: false,
error: `host "${host}" resolves to a private/local network`
};
return { ok: true };
}
const BLOCKED_HOSTS = new Set([
"localhost",
"localhost.localdomain",
"0.0.0.0",
"127.0.0.1",
"::1",
"169.254.169.254",
"metadata.google.internal"
]);
/** True if the host is an IPv4 or IPv6 literal inside a private /
* loopback / link-local range. Pure parse, no DNS. */
function isPrivateIpLiteral(host) {
if (host.includes(":")) {
if (host === "::1" || host.startsWith("fe80:") || host.startsWith("fc") || host.startsWith("fd")) return true;
return false;
}
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
if (!m) return false;
const [a, b] = [Number(m[1]), Number(m[2])];
if (a === 10) return true;
if (a === 127) return true;
if (a === 169 && b === 254) return true;
if (a === 172 && b >= 16 && b <= 31) return true;
if (a === 192 && b === 168) return true;
if (a === 100 && b >= 64 && b <= 127) return true;
if (a === 0) return true;
return false;
}
async function readBounded(res, maxBytes) {
if (!res.body) return {
text: "",
truncated: false
};
const reader = res.body.getReader();
const chunks = [];
let total = 0;
let truncated = false;
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (!value) continue;
if (total + value.byteLength > maxBytes) {
const remaining = Math.max(0, maxBytes - total);
if (remaining > 0) chunks.push(value.subarray(0, remaining));
total += remaining;
truncated = true;
try {
await reader.cancel();
} catch {}
break;
}
chunks.push(value);
total += value.byteLength;
}
const merged = new Uint8Array(total);
let off = 0;
for (const c of chunks) {
merged.set(c, off);
off += c.byteLength;
}
return {
text: new TextDecoder("utf-8", { fatal: false }).decode(merged),
truncated
};
}
function extractTitle(html) {
const m = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
if (!m?.[1]) return void 0;
const stripped = m[1].replace(/\s+/g, " ").trim();
return stripped || void 0;
}
/** Pretty-bad HTML→text extractor. Drops <script>/<style> contents
* entirely, strips remaining tags, decodes a handful of entities,
* collapses runs of whitespace. Good enough for "let the model read
* this page" — not for archival fidelity. Kept dependency-free so
* the binary stays small. */
function htmlToText(html) {
return decodeEntities(html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<(?:br|\/p|\/div|\/li|\/h[1-6])\s*[^>]*>/gi, "\n").replace(/<[^>]+>/g, " ").replace(/[ \t]+/g, " ").replace(/\n[ \t]+/g, "\n").replace(/\n{3,}/g, "\n\n").trim());
}
function decodeEntities(s) {
return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&#x27;/g, "'").replace(/&nbsp;/g, " ").replace(/&#(\d+);/g, (_, n) => {
const code = Number(n);
return Number.isFinite(code) && code >= 32 && code < 65536 ? String.fromCodePoint(code) : "";
});
}
//#endregion
//#region src/bin/tools.ts
const SERVER_NAME = "thomas-tools";
const SERVER_VERSION = "0.1.0";
const PROTOCOL_VERSION = "2024-11-05";
const WEB_SEARCH_TOOL = {
name: "web_search",
description: "Search the web via the user-configured backend (DuckDuckGo, Brave, SearXNG, or Tavily). Returns a list of {title, url, snippet} results. Use when you need current information beyond your training cutoff or to locate a specific page.",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "The search query."
},
count: {
type: "number",
description: "Maximum number of results to return (default 10).",
minimum: 1,
maximum: 25
}
},
required: ["query"]
}
};
const WEB_FETCH_TOOL = {
name: "web_fetch",
description: "Fetch a public web URL and return its title plus extracted text content. Blocks requests to localhost, private networks, and cloud metadata endpoints — the model cannot probe the user's intranet. Follows redirects; responses are truncated at ~1 MiB.",
inputSchema: {
type: "object",
properties: {
url: {
type: "string",
description: "The absolute http(s) URL to fetch."
},
maxBytes: {
type: "number",
description: "Cap on response bytes read (default 1048576).",
minimum: 1024,
maximum: 4 * 1024 * 1024
}
},
required: ["url"]
}
};
function send(msg) {
process.stdout.write(`${JSON.stringify(msg)}\n`);
}
function err(id, code, message) {
send({
jsonrpc: "2.0",
id,
error: {
code,
message
}
});
}
async function dispatch(req) {
const id = req.id ?? null;
switch (req.method) {
case "initialize":
send({
jsonrpc: "2.0",
id,
result: {
protocolVersion: PROTOCOL_VERSION,
serverInfo: {
name: SERVER_NAME,
version: SERVER_VERSION
},
capabilities: { tools: {} }
}
});
return;
case "notifications/initialized": return;
case "tools/list":
send({
jsonrpc: "2.0",
id,
result: { tools: [WEB_SEARCH_TOOL, WEB_FETCH_TOOL] }
});
return;
case "tools/call": {
const params = req.params ?? {};
if (params.name === "web_search") {
const args = params.arguments ?? {};
const query = typeof args.query === "string" ? args.query.trim() : "";
if (!query) {
err(id, -32602, "web_search: `query` is required");
return;
}
const count = typeof args.count === "number" && args.count > 0 ? Math.min(25, args.count) : 10;
const outcome = await runWebSearch({
query,
count
});
send({
jsonrpc: "2.0",
id,
result: toSearchToolResult(outcome)
});
return;
}
if (params.name === "web_fetch") {
const args = params.arguments ?? {};
const url = typeof args.url === "string" ? args.url.trim() : "";
if (!url) {
err(id, -32602, "web_fetch: `url` is required");
return;
}
const maxBytes = typeof args.maxBytes === "number" && args.maxBytes > 0 ? args.maxBytes : void 0;
const outcome = await fetchUrl({
url,
...maxBytes ? { maxBytes } : {}
});
send({
jsonrpc: "2.0",
id,
result: toFetchToolResult(outcome)
});
return;
}
err(id, -32601, `unknown tool: ${String(params.name)}`);
return;
}
case "ping":
send({
jsonrpc: "2.0",
id,
result: {}
});
return;
default: err(id, -32601, `method not implemented: ${req.method}`);
}
}
async function runWebSearch(opts) {
let provider;
try {
const store = await readToolProviders();
provider = activeProviderFor(store, "web_search");
} catch (e) {
return {
ok: false,
error: `tool-providers.json unreadable: ${e.message}`
};
}
if (!provider) return {
ok: false,
error: "no web_search backend configured — add one in Control · Tool Providers"
};
switch (provider.backend) {
case "duckduckgo": return searchDuckDuckGo({
query: opts.query,
count: opts.count
});
case "brave": {
const apiKey = await resolveSecret(provider);
if (!apiKey) return {
ok: false,
error: `provider "${provider.id}" is missing its API key`
};
return searchBrave({
query: opts.query,
count: opts.count,
apiKey
});
}
case "baidu": {
const apiKey = await resolveSecret(provider);
if (!apiKey) return {
ok: false,
error: `provider "${provider.id}" is missing its API key`
};
return searchBaidu({
query: opts.query,
count: opts.count,
apiKey
});
}
case "searxng":
case "tavily": return {
ok: false,
error: `backend "${provider.backend}" is not implemented yet`
};
}
}
async function resolveSecret(p) {
if (!p.authRef) return void 0;
try {
const secrets = await readSecrets();
return getSecret(secrets, p.authRef) ?? void 0;
} catch {
return void 0;
}
}
function toSearchToolResult(outcome) {
if (!outcome.ok) return {
isError: true,
content: [{
type: "text",
text: `web_search failed: ${outcome.error}`
}]
};
return { content: [{
type: "text",
text: formatResults(outcome.results)
}] };
}
function toFetchToolResult(outcome) {
if (!outcome.ok) return {
isError: true,
content: [{
type: "text",
text: `web_fetch failed: ${outcome.error}`
}]
};
const header = [
`URL: ${outcome.finalUrl}${outcome.finalUrl !== outcome.url ? ` (from ${outcome.url})` : ""}`,
`Status: ${outcome.status}`,
outcome.contentType ? `Content-Type: ${outcome.contentType}` : "",
outcome.title ? `Title: ${outcome.title}` : "",
outcome.truncated ? "(response truncated at maxBytes)" : ""
].filter(Boolean).join("\n");
return { content: [{
type: "text",
text: `${header}\n\n${outcome.text}`
}] };
}
function formatResults(results) {
if (results.length === 0) return "No results.";
const lines = [];
results.forEach((r, i) => {
lines.push(`${i + 1}. ${r.title}`);
lines.push(` ${r.url}`);
if (r.snippet) lines.push(` ${r.snippet}`);
lines.push("");
});
return lines.join("\n").trimEnd();
}
let buf = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
buf += chunk;
let nl;
while ((nl = buf.indexOf("\n")) >= 0) {
const line = buf.slice(0, nl).trim();
buf = buf.slice(nl + 1);
if (!line) continue;
let req;
try {
req = JSON.parse(line);
} catch (e) {
err(null, -32700, `parse error: ${e.message}`);
continue;
}
dispatch(req).catch((e) => err(req.id ?? null, -32e3, e.message));
}
});
process.stdin.on("end", () => {
process.exit(0);
});
//#endregion
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();
const THOMAS_HOME = process.env.THOMAS_HOME ?? join(HOME, ".thomas");
const paths = {
home: THOMAS_HOME,
wire: {
dir: join(THOMAS_HOME, "wire"),
routes: join(THOMAS_HOME, "wire", "routes.json"),
traces: join(THOMAS_HOME, "wire", "traces"),
tracesArchive: join(THOMAS_HOME, "wire", "traces", "archive"),
daemonSock: join(THOMAS_HOME, "wire", "daemon.sock")
},
backups: {
dir: join(THOMAS_HOME, "backups"),
manifest: join(THOMAS_HOME, "backups", "manifest.json")
},
logs: {
dir: join(THOMAS_HOME, "logs"),
daemon: join(THOMAS_HOME, "logs", "daemon.log"),
daemonErr: join(THOMAS_HOME, "logs", "daemon.err")
},
config: join(THOMAS_HOME, "config.json"),
update: join(THOMAS_HOME, "update.json"),
models: join(THOMAS_HOME, "models.json"),
routing: join(THOMAS_HOME, "routing.json"),
secrets: join(THOMAS_HOME, "secrets.json"),
toolProviders: join(THOMAS_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(THOMAS_HOME, "pricing.json");
const PRICING_CATALOG_CACHE = join(THOMAS_HOME, "pricing-catalog.json");
const DAEMON_PORT = process.env.THOMAS_PORT ? Number.parseInt(process.env.THOMAS_PORT, 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 { 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-C3_a-_Fn.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

+4
-3
{
"name": "@openthomas/thomas",
"version": "0.4.1",
"description": "The dynamic harness for your agent fleet — make every agent reliable. See your fleet free; Control + Govern with paid editions.",
"version": "0.6.0",
"description": "Give vision-blind coding models eyes (DeepSeek + Qwen3.6, any agent). See cost per task, not per model. The dynamic harness for your agent fleet — Free for See; Control + Govern paid.",
"license": "MIT",

@@ -15,3 +15,4 @@ "type": "module",

"thomas": "dist/bin/thomas.js",
"thomas-tap": "dist/bin/tap.js"
"thomas-tap": "dist/bin/tap.js",
"thomas-tools": "dist/bin/tools.js"
},

@@ -18,0 +19,0 @@ "publishConfig": {

@@ -12,4 +12,12 @@ # Privacy & data handling

section. It carries no user data, contacts only the registry npm
itself uses, and can be switched off.
itself uses, and can be switched off. From v0.5 an **opt-in** daily
**pricing-catalog refresh** (off by default) can fetch a public price
list from models.dev — see the v0.5 section.
From v1, when you route an agent onto a managed **subscription**
provider, Thomas may refresh that subscription's OAuth token against
the agent's own identity provider — see the v1 section. It is
request-driven, stays within the agent's existing upstream, sends only
the token the agent already holds, and never contacts us.
This file is updated **every release** with the exact behaviour of

@@ -32,2 +40,176 @@ the version it ships with. If a future release sends any data **about

## v1
v1 adds **model routing & combos** (`thomas route` and the Control ·
Routing pages) — you can reconfigure which model(s) a wired agent's
traffic reaches. This changes where your agent traffic goes, so here is
exactly what it does.
### Where your traffic goes
By default every route is plain **passthrough**: traffic reaches the
same upstream your agent already chose, unchanged. Nothing about routing
takes effect until you explicitly opt a route in.
When you do route an agent to a different model or a combo, its requests
go to the upstream **you configured for that model's provider** — a URL
you typed in yourself, via the UI or `thomas route provider add`. Thomas
still opens no outbound surface of its own: it only ever talks to
upstreams you named. There is no Thomas/Anthropic host in this path.
### `secrets.json` — your API keys
Cross-agent routing and the vision companion may need API keys for
providers your agent never authenticates to itself. Those keys are
stored **locally** in `~/.thomas/secrets.json`, written with file mode
`0600` (readable only by your user account).
* The keys are sent **only** to the upstream URLs you configured for
their providers — nowhere else, never to us.
* The secret store is **write-only** across the CLI and the daemon API:
you can set and remove keys, but a value can never be read back out.
The UI shows only which key *refs* exist, never their contents.
* Removing a provider removes its stored key.
### Captured credentials & subscription token refresh
So one agent can use another agent's model, Thomas manages every
agent's credentials itself. `thomas wire` reads each agent's existing
credential from its **own config** and handles it one of two ways.
**Static API keys** — Claude Code's `ANTHROPIC_API_KEY`, Codex's
`OPENAI_API_KEY`, Hermes / OpenClaw provider keys — are copied into
`~/.thomas/secrets.json` (mode `0600`, see above) under a
`provider:<agent>/<provider>` ref. They are read from files already on
your machine and only ever sent to the same upstream that agent
already calls.
**Subscription logins** — Claude Code's Claude.ai login, Codex's
ChatGPT login — use OAuth tokens that expire. Thomas does **not** copy
these into `secrets.json`. It reads them, only when a managed route
needs them, from the agent's own credential store: the macOS Keychain
item `Claude Code-credentials` (fallback `~/.claude/.credentials.json`)
and `~/.codex/auth.json`.
#### The refresh call
A subscription access token expires. When a managed subscription route
is used and the token is within five minutes of expiry, Thomas
refreshes it:
* **Request:** an HTTPS `POST` to the agent's **own** OAuth provider —
`https://platform.claude.com/v1/oauth/token` for Claude Code,
`https://auth.openai.com/oauth/token` for Codex. These are the exact
endpoints the agent itself calls to refresh the exact same token.
* **What it sends:** your `refresh_token` and the agent's public
`client_id`. Nothing else — no machine ID, no usage data, nothing
identifying you beyond the token the agent already holds.
* **Where it does NOT go:** not `openthomas.com`, not any Thomas server.
We never see this request.
* **When:** only when you have opted a route onto a managed
subscription provider *and* its token is near expiry. A passthrough
route never triggers it.
#### Rotated tokens are written back
OAuth refresh tokens are single-use — a refresh returns a new one.
Thomas writes the rotated token back to the agent's own store
(Keychain / `auth.json`, atomically) so the agent picks it up on its
next read instead of failing. Thomas is a **cooperative co-refresher**
of the same credential, not a competing one.
This stays within Thomas's contract — data about you goes only to the
upstream your agent already talks to, and an agent's OAuth provider is
part of that upstream — but it is called out here, and in the release
notes, all the same.
#### Request shape on managed subscription routes
Anthropic gates a Claude.ai (subscription) OAuth token behind specific
request-shape requirements — the token is accepted only when the
request claims **Claude Code shape**. When Thomas routes a call onto a
managed Claude.ai subscription provider, it adjusts the outgoing
request to satisfy those gates:
* Two `anthropic-beta` flags on the request:
`claude-code-20250219` and `oauth-2025-04-20`.
* A fixed Claude-Code identity prepended to the request's `system`
block: *"You are Claude Code, Anthropic's official CLI for Claude."*
Your agent's own system prompt is preserved after it.
This is a **protocol-level concession** to the upstream — the same
shape Claude Code itself sends on every call. No new data about you
leaves the machine, and no new outbound surface is opened. Without
these adjustments, Anthropic rejects subscription tokens used outside
Claude Code with `4xx` errors or aggressive throttling. Disclosed here
for the same reason the refresh call is: Thomas is shaping a request
on your behalf, and you should know what shape it takes.
### New files on disk
| Path | What |
|---|---|
| `~/.thomas/models.json` | The catalog of providers and models Thomas can route to. Provider base URLs, wire formats, model metadata. No secrets — keys live in `secrets.json`. Seeded from your wire and extended by you. |
| `~/.thomas/routing.json` | Per-agent routing decisions and combo definitions. No secrets. |
| `~/.thomas/secrets.json` | Provider API keys — typed in by you or captured from an agent's own config by `thomas wire`. File mode `0600`. Local-only; see above. |
| `~/.thomas/oauth-<agent>.lock` | Transient lock held only while Thomas refreshes a subscription OAuth token, deleted immediately after. No contents. |
These are local files. Routing uploads nothing; the only outbound call
this version adds is the subscription token refresh described above.
---
## v0.5
v0.5 adds an **opt-in pricing-catalog refresh**. By default Thomas prices
calls from the catalog bundled inside the package (and your own
`~/.thomas/pricing.json` overrides) — entirely offline. If you want prices
to stay fresh without upgrading Thomas, you can turn on a daily refresh.
### The pricing refresh
* **Off by default.** Enable with `autoRefreshPricing: true` in
`~/.thomas/config.json`. With it off (the default) the daemon makes no
pricing requests at all.
* **Request:** when on, one plain HTTPS `GET` per day to
`https://models.dev/api.json` — a public, community-maintained price
list. The same source the bundled catalog is built from.
* **What it sends:** nothing about you. No user data, no machine ID, no
model ids you've used, no usage data — just an anonymous GET for the
public catalog.
* **Where it does NOT go:** not `openthomas.com`, not any Thomas or
Anthropic server, never your captured traffic. We never see this request.
* The result is cached at `~/.thomas/pricing-catalog.json` and read locally;
nothing leaves your machine.
This is the second sanctioned outbound call (after the v0.4 version check),
and like that one it is opt-in here, hits only a public third-party
endpoint, and is fully disableable.
### New files on disk
| Path | What |
|---|---|
| `~/.thomas/pricing-catalog.json` | Cached price catalog from the last refresh (only when `autoRefreshPricing` is on). |
### Session correlation (opt-in, local-only)
To attribute captured Claude Code calls to the specific window (instance) and
parallel sub-agent that made them — fleet management — Thomas can correlate
captures with Claude Code's own local transcripts.
* **Off by default.** Enable with `correlateSessions: true` in
`~/.thomas/config.json`.
* **What it reads:** `~/.claude/projects/**/*.jsonl` — Claude Code's session
transcripts, already on your disk. It joins each captured call to its session
on the upstream `message.id` (which Thomas already has in the stored
response) and records the session id + sub-agent id locally.
* **What it sends:** nothing. No network calls at all. This is a local file
read; results are written only to your local trace DB. It never contacts
`openthomas.com`, Anthropic, or any host.
* Reads stop working gracefully when the files are absent (e.g. the daemon and
agent run on different machines) — calls simply stay unattributed.
---
## v0.4

@@ -34,0 +216,0 @@

@@ -7,2 +7,38 @@ # Thomas

v0.5 is the first agent harness to:
### 1. Give vision-blind coding models eyes
DeepSeek codes well but can't see. Qwen3.6 sees images and video but
codes poorly. Thomas combines them as one virtual model: any agent
that hands it an image — Claude Code, Claude Desktop, OpenClaw, Codex,
Hermes — gets a routed pipeline where the vision model describes the
image and the coding model acts on the description. Two strategies
ship in v0.5:
- **Pre-describe** — vision model describes every image once; the
coding model receives the descriptions in place of the images. Cheap,
robust, no model-specific tool-call format required.
- **On-demand `view_image`** — the coding model is given a tool it can
call to ask focused questions about a referenced image; a bounded
agentic loop services each call against the vision model. More
interactive — the coder can zoom in on a region or re-ask.
Configured once per route in `~/.thomas/routes.json`. Works across
every agent on your machine because Thomas wraps the wire, not the
agent.
### 2. Show cost per task — not per model, not per agent
Every other tool rolls cost up by model or by agent. Useful for
finance; useless for figuring out what last night's autonomous run
actually bought you. Thomas tracks every model call back to the
**task** that triggered it. The Tasks view shows you, run by run, the
three pricing buckets — cached-in / fresh-in / out tokens — live
$-cost, and the full action graph that produced them. Filter by
agent, sort by spend, find the run that cost too much, replay it
to see what it was doing.
---
Your agent fleet is only as reliable as its harness.

@@ -127,3 +163,3 @@

Today (v0.4): free only — the See edition. **Personal** and the other
Today (v0.5): free only — the See edition. **Personal** and the other
paid editions land in later releases.

@@ -142,2 +178,3 @@

1,093 actions · 129 verified outcomes · 322.9M tokens
tokens by pricing bucket: 287.4M cached-in (cache hit) · 31.2M fresh-in (non-cache-hit) · 4.3M out
action_rate: 3.39 a/Mtok outcome_rate: 11.8%

@@ -149,6 +186,6 @@ Outcomes: 47 commits · 44 files edited · 27 files created · 9 CI passed

$ thomas list
ID STARTED AGENT STATUS DUR ACT COST IN/OUT CACHE R/W
──────────── ─────────────────── ─────────── ────── ────── ─── ─────── ────── ─────────
ru_aBc1xYz9 2026-05-16 14:23:11 claude-code done 1.4s 1 $0.0024 120/45 0/0
ru_fOj6Ce1H 2026-05-16 14:22:18 hermes done 7.8s 1 $0.97 1/396 565K/5K
ID STARTED AGENT STATUS DUR ACT COST CACHED/FRESH/OUT
──────────── ─────────────────── ─────────── ────── ────── ─── ─────── ────────────────
ru_aBc1xYz9 2026-05-16 14:23:11 claude-code done 1.4s 1 $0.0024 0/120/45
ru_fOj6Ce1H 2026-05-16 14:22:18 hermes done 7.8s 1 $0.97 565K/6K/396
```

@@ -215,5 +252,10 @@

v0.4 — first public release. Solo-built and used daily by the author.
Tested against real Claude Code / OpenClaw / Codex / Hermes traffic.
Bug reports and PRs welcome.
v0.5 — the fleet-harness cut. v0.4 was the first public NPM release
(legible-harness pipeline: capture · decode · score). v0.5 adds the
control surface: per-route failover chains with error/budget/token
switch rules, per-route capabilities (vision pre-describe, web_search
emulation), and a `thomas-tools` MCP that gives the routed model
search/fetch when its upstream can't. Solo-built and used daily by the
author; tested against real Claude Code / Claude Desktop / OpenClaw /
Codex / Hermes traffic. Bug reports and PRs welcome.

@@ -220,0 +262,0 @@ ## License & trust

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

<title>Thomas</title>
<script type="module" crossorigin src="/assets/index-Bl5WmR7P.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-ZknWGt8H.css">
<script type="module" crossorigin src="/assets/index-CgulcI5s.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BnZ7GXsV.css">
</head>

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

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

: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)}.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}.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}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}

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