conductor-remote 1.81.0 → 1.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/assets/index-CJakNa4V.css +1 -0
- package/dist/assets/{index-DBkjooUE.js → index-CZWaZeNN.js} +37 -37
- package/dist/index.html +2 -2
- package/dist/sw.js +1 -1
- package/dist-node/src/mcp-tools.js +21 -1
- package/dist-node/src/plan-usage.js +430 -0
- package/dist-node/src/routes.js +2 -0
- package/dist-node/src/server.js +8 -0
- package/package.json +1 -1
- package/dist/assets/index-r0GKAzkH.css +0 -1
package/dist/index.html
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
<title>Conductor Remote</title>
|
|
14
14
|
<!-- Runs before the module bundle so it can catch a stale shell that fails to boot. -->
|
|
15
15
|
<script src="/self-heal.js"></script>
|
|
16
|
-
<script type="module" crossorigin src="/assets/index-
|
|
17
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
16
|
+
<script type="module" crossorigin src="/assets/index-CZWaZeNN.js"></script>
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CJakNa4V.css">
|
|
18
18
|
<link rel="manifest" href="/manifest.webmanifest"></head>
|
|
19
19
|
<body>
|
|
20
20
|
<div id="root"></div>
|
package/dist/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(s[o])return;let l={};const
|
|
1
|
+
if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(s[o])return;let l={};const t=e=>i(e,o),c={module:{uri:o},exports:l,require:t};s[o]=Promise.all(n.map(e=>c[e]||t(e))).then(e=>(r(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";importScripts("/push-sw.js"),self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"self-heal.js",revision:"49bd63adb25a09341f8d2610e8bd3c76"},{url:"push-sw.js",revision:"e7ef44deca46c0539e6ff7bba5eb815e"},{url:"index.html",revision:"92fe37786069a3859a13db5d5d63f6e3"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-CZWaZeNN.js",revision:null},{url:"assets/index-CJakNa4V.css",revision:null},{url:"apple-touch-icon.png",revision:"1127bb396b4648add53dce3f22c92aee"},{url:"icon-192.png",revision:"c5e01ac58768627e18ee7b8b6a9239ef"},{url:"icon-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon-maskable-512.png",revision:"a9b0d962686287452216492cd2247499"},{url:"icon.svg",revision:"c1aee186821798733dd477e69a0ef243"},{url:"manifest.webmanifest",revision:"cf88fbc5755108a7fe0616fa160a8a15"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]}))});
|
|
@@ -31,7 +31,7 @@ import { HIT_CLOSE, HIT_OPEN, isToolResult, timestampMs, workspaceTitle } from "
|
|
|
31
31
|
/** Versions we know how to speak. The client's choice wins when we know it. */
|
|
32
32
|
export const PROTOCOL_VERSIONS = ['2025-06-18', '2025-03-26', '2024-11-05'];
|
|
33
33
|
export const SERVER_INFO = { name: 'conductor-remote', version: '1' };
|
|
34
|
-
export const INSTRUCTIONS = 'Drives local Conductor agents through the conductor-remote relay. search_chats and read_chat reach archived workspaces, which is where most finished work lives. create_workspace opens its workspace link without direct UI control; requested agent settings and the first prompt are applied later through Conductor’s UI. dismiss_prompt, keep_awake and relay_logs touch no UI. send_prompt, split_chat, stop_turn, set_agent_options, set_default_model, a live list_models call, set_workspace_status and archive_workspace drive the real Mac UI and steal focus for a few seconds — confirm with the user before using them on a chat they did not name. archive_workspace also deletes the worktree and stops whatever is running there.';
|
|
34
|
+
export const INSTRUCTIONS = 'Drives local Conductor agents through the conductor-remote relay. search_chats and read_chat reach archived workspaces, which is where most finished work lives. create_workspace opens its workspace link without direct UI control; requested agent settings and the first prompt are applied later through Conductor’s UI. plan_usage, dismiss_prompt, keep_awake and relay_logs touch no UI. send_prompt, split_chat, stop_turn, set_agent_options, set_default_model, a live list_models call, set_workspace_status and archive_workspace drive the real Mac UI and steal focus for a few seconds — confirm with the user before using them on a chat they did not name. archive_workspace also deletes the worktree and stops whatever is running there.';
|
|
35
35
|
/** Reads are quick; a UI write is measured in tens of seconds (writes.ts ▸ SEND_ATTEMPT_MS). */
|
|
36
36
|
export const READ_TIMEOUT_MS = 10_000;
|
|
37
37
|
export const WRITE_TIMEOUT_MS = 75_000;
|
|
@@ -359,6 +359,26 @@ export function createTools(call) {
|
|
|
359
359
|
return data.repos.map(r => `${r.name} (${r.default_branch ?? '?'}) ${r.root_path ?? ''}`).join('\n');
|
|
360
360
|
}
|
|
361
361
|
},
|
|
362
|
+
{
|
|
363
|
+
name: 'plan_usage',
|
|
364
|
+
description: 'Read rolling Claude Code and Codex subscription allowances from their local CLIs: percentage used and reset time. Sends no model prompt and touches no Conductor UI. Cursor Agent and OpenCode explain why no comparable plan quota is available.',
|
|
365
|
+
inputSchema: { type: 'object', properties: {} },
|
|
366
|
+
run: async () => {
|
|
367
|
+
const data = await call(routes.planUsage.path(), { timeoutMs: 15_000 });
|
|
368
|
+
const sections = data.providers.map(provider => {
|
|
369
|
+
const head = `## ${provider.label}${provider.plan ? ` · ${provider.plan}` : ''}`;
|
|
370
|
+
if (provider.status !== 'available')
|
|
371
|
+
return `${head}\n${provider.message ?? 'unavailable'}`;
|
|
372
|
+
const lines = provider.buckets.flatMap(bucket => bucket.windows.map(window => {
|
|
373
|
+
const bucketName = provider.buckets.length > 1 ? `${bucket.label} — ` : '';
|
|
374
|
+
const reset = window.resetsAt === null ? 'reset unavailable' : `resets ${new Date(window.resetsAt).toISOString()}`;
|
|
375
|
+
return `- ${bucketName}${window.label}: ${Math.round(window.usedPercent)}% used · ${reset}`;
|
|
376
|
+
}));
|
|
377
|
+
return `${head}\n${lines.join('\n')}`;
|
|
378
|
+
});
|
|
379
|
+
return `${sections.join('\n\n')}\n\nupdated ${new Date(data.fetchedAt).toISOString()}`;
|
|
380
|
+
}
|
|
381
|
+
},
|
|
362
382
|
{
|
|
363
383
|
name: 'create_workspace',
|
|
364
384
|
description: 'Start a new Conductor workspace in a repo, optionally with a first prompt and agent settings. The workspace starts from a Conductor deep link, so creation itself needs no Accessibility. The relay applies requested model, effort, plan, and fast settings through Conductor’s UI after it creates the first chat and before it delivers the prompt. Returns as soon as the workspace row exists (~2s), before the worktree is built.',
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
function object(value) {
|
|
6
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
7
|
+
}
|
|
8
|
+
function text(value) {
|
|
9
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
10
|
+
}
|
|
11
|
+
function number(value) {
|
|
12
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
13
|
+
}
|
|
14
|
+
function percent(value) {
|
|
15
|
+
const parsed = number(value);
|
|
16
|
+
return parsed === null ? null : Math.max(0, Math.min(100, parsed));
|
|
17
|
+
}
|
|
18
|
+
function timestamp(value) {
|
|
19
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
20
|
+
// Codex reports seconds; tolerate milliseconds if the protocol changes.
|
|
21
|
+
return value < 10_000_000_000 ? value * 1000 : value;
|
|
22
|
+
}
|
|
23
|
+
if (typeof value !== 'string')
|
|
24
|
+
return null;
|
|
25
|
+
const parsed = Date.parse(value);
|
|
26
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
27
|
+
}
|
|
28
|
+
function codexWindowLabel(duration, slot) {
|
|
29
|
+
if (duration === 300)
|
|
30
|
+
return '5-hour limit';
|
|
31
|
+
if (duration === 1_440)
|
|
32
|
+
return 'Daily limit';
|
|
33
|
+
if (duration === 10_080)
|
|
34
|
+
return 'Weekly limit';
|
|
35
|
+
if (duration && duration % 1_440 === 0)
|
|
36
|
+
return `${duration / 1_440}-day limit`;
|
|
37
|
+
if (duration && duration % 60 === 0)
|
|
38
|
+
return `${duration / 60}-hour limit`;
|
|
39
|
+
return slot === 'primary' ? 'Primary limit' : 'Secondary limit';
|
|
40
|
+
}
|
|
41
|
+
function codexWindow(bucketId, slot, raw) {
|
|
42
|
+
const value = object(raw);
|
|
43
|
+
const usedPercent = percent(value?.usedPercent);
|
|
44
|
+
if (!value || usedPercent === null)
|
|
45
|
+
return null;
|
|
46
|
+
const duration = number(value.windowDurationMins);
|
|
47
|
+
return {
|
|
48
|
+
id: `${bucketId}:${slot}`,
|
|
49
|
+
label: codexWindowLabel(duration, slot),
|
|
50
|
+
usedPercent,
|
|
51
|
+
resetsAt: timestamp(value.resetsAt),
|
|
52
|
+
windowDurationMins: duration
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Reduce Codex's app-server response to the stable, provider-neutral wire shape. */
|
|
56
|
+
export function parseCodexPlanUsage(raw) {
|
|
57
|
+
const envelope = object(raw);
|
|
58
|
+
const payload = object(envelope?.result) ?? envelope;
|
|
59
|
+
const legacy = object(payload?.rateLimits);
|
|
60
|
+
const byLimit = object(payload?.rateLimitsByLimitId);
|
|
61
|
+
const entries = byLimit && Object.keys(byLimit).length ? Object.entries(byLimit) : legacy ? [['codex', legacy]] : [];
|
|
62
|
+
const buckets = [];
|
|
63
|
+
let plan = null;
|
|
64
|
+
for (const [key, candidate] of entries) {
|
|
65
|
+
const snapshot = object(candidate);
|
|
66
|
+
if (!snapshot)
|
|
67
|
+
continue;
|
|
68
|
+
plan ??= text(snapshot.planType);
|
|
69
|
+
const id = text(snapshot.limitId) ?? key;
|
|
70
|
+
const windows = [
|
|
71
|
+
codexWindow(id, 'primary', snapshot.primary),
|
|
72
|
+
codexWindow(id, 'secondary', snapshot.secondary)
|
|
73
|
+
].filter((window) => window !== null);
|
|
74
|
+
if (!windows.length)
|
|
75
|
+
continue;
|
|
76
|
+
buckets.push({ id, label: text(snapshot.limitName) ?? (id === 'codex' ? 'Codex' : id), windows });
|
|
77
|
+
}
|
|
78
|
+
if (!buckets.length) {
|
|
79
|
+
return {
|
|
80
|
+
provider: 'codex',
|
|
81
|
+
label: 'Codex',
|
|
82
|
+
status: 'unavailable',
|
|
83
|
+
plan,
|
|
84
|
+
buckets: [],
|
|
85
|
+
message: 'Codex returned no rolling plan limits for this account.'
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
buckets.sort((a, b) => Number(b.id === 'codex') - Number(a.id === 'codex') || a.label.localeCompare(b.label));
|
|
89
|
+
return { provider: 'codex', label: 'Codex', status: 'available', plan, buckets };
|
|
90
|
+
}
|
|
91
|
+
function claudeWindowLabel(limit) {
|
|
92
|
+
const kind = text(limit.kind);
|
|
93
|
+
const model = text(object(object(limit.scope)?.model)?.display_name);
|
|
94
|
+
if (kind === 'session')
|
|
95
|
+
return 'Current session';
|
|
96
|
+
if (kind === 'weekly_all')
|
|
97
|
+
return 'Current week';
|
|
98
|
+
if (kind === 'weekly_scoped' && model)
|
|
99
|
+
return `Current week (${model})`;
|
|
100
|
+
if (model)
|
|
101
|
+
return model;
|
|
102
|
+
return kind?.replaceAll('_', ' ') ?? 'Plan limit';
|
|
103
|
+
}
|
|
104
|
+
function claudeWindow(id, label, raw, active) {
|
|
105
|
+
const value = object(raw);
|
|
106
|
+
const usedPercent = percent(value?.utilization ?? value?.percent);
|
|
107
|
+
if (!value || usedPercent === null)
|
|
108
|
+
return null;
|
|
109
|
+
return {
|
|
110
|
+
id,
|
|
111
|
+
label,
|
|
112
|
+
usedPercent,
|
|
113
|
+
resetsAt: timestamp(value.resets_at),
|
|
114
|
+
...(active === undefined ? {} : { active })
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Reduce Claude's experimental `get_usage` control response; tolerate its older named-window shape. */
|
|
118
|
+
export function parseClaudePlanUsage(raw) {
|
|
119
|
+
const envelope = object(raw);
|
|
120
|
+
const control = object(envelope?.response);
|
|
121
|
+
const payload = object(control?.response) ?? envelope;
|
|
122
|
+
const plan = text(payload?.subscription_type);
|
|
123
|
+
if (payload?.rate_limits_available === false) {
|
|
124
|
+
return {
|
|
125
|
+
provider: 'claude',
|
|
126
|
+
label: 'Claude Code',
|
|
127
|
+
status: 'unavailable',
|
|
128
|
+
plan,
|
|
129
|
+
buckets: [],
|
|
130
|
+
message: 'Plan limits are not available for API-key or third-party-provider sessions.'
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const rateLimits = object(payload?.rate_limits);
|
|
134
|
+
const windows = [];
|
|
135
|
+
const limits = Array.isArray(rateLimits?.limits) ? rateLimits.limits : [];
|
|
136
|
+
for (const [index, candidate] of limits.entries()) {
|
|
137
|
+
const limit = object(candidate);
|
|
138
|
+
if (!limit)
|
|
139
|
+
continue;
|
|
140
|
+
const kind = text(limit.kind);
|
|
141
|
+
// These are the rolling plan allowances. Spend/credit records have a different
|
|
142
|
+
// unit and belong in a future money-shaped control rather than a percentage bar.
|
|
143
|
+
if (!kind || !['session', 'weekly_all', 'weekly_scoped'].includes(kind))
|
|
144
|
+
continue;
|
|
145
|
+
const model = text(object(object(limit.scope)?.model)?.display_name);
|
|
146
|
+
const parsed = claudeWindow(`claude:${kind}:${model ?? index}`, claudeWindowLabel(limit), limit, limit.is_active === true);
|
|
147
|
+
if (parsed)
|
|
148
|
+
windows.push(parsed);
|
|
149
|
+
}
|
|
150
|
+
// Claude 2.1 first exposed the same data as named fields. Keep this fallback so an
|
|
151
|
+
// app update that removes the additive `limits` array does not blank the whole card.
|
|
152
|
+
if (!windows.length && rateLimits) {
|
|
153
|
+
const named = [
|
|
154
|
+
['five_hour', 'Current session'],
|
|
155
|
+
['seven_day', 'Current week'],
|
|
156
|
+
['seven_day_opus', 'Current week (Opus)'],
|
|
157
|
+
['seven_day_sonnet', 'Current week (Sonnet)']
|
|
158
|
+
];
|
|
159
|
+
for (const [key, label] of named) {
|
|
160
|
+
const parsed = claudeWindow(`claude:${key}`, label, rateLimits[key]);
|
|
161
|
+
if (parsed)
|
|
162
|
+
windows.push(parsed);
|
|
163
|
+
}
|
|
164
|
+
const scoped = Array.isArray(rateLimits.model_scoped) ? rateLimits.model_scoped : [];
|
|
165
|
+
for (const [index, candidate] of scoped.entries()) {
|
|
166
|
+
const value = object(candidate);
|
|
167
|
+
const model = text(value?.display_name);
|
|
168
|
+
const parsed = claudeWindow(`claude:model:${model ?? index}:${index}`, `Current week (${model ?? 'model'})`, value);
|
|
169
|
+
if (parsed)
|
|
170
|
+
windows.push(parsed);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!windows.length) {
|
|
174
|
+
return {
|
|
175
|
+
provider: 'claude',
|
|
176
|
+
label: 'Claude Code',
|
|
177
|
+
status: 'unavailable',
|
|
178
|
+
plan,
|
|
179
|
+
buckets: [],
|
|
180
|
+
message: 'Claude Code returned no rolling plan limits for this account.'
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
provider: 'claude',
|
|
185
|
+
label: 'Claude Code',
|
|
186
|
+
status: 'available',
|
|
187
|
+
plan,
|
|
188
|
+
buckets: [{ id: 'claude', label: 'Claude Code', windows }]
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const BUNDLED_BINARIES = path.join(os.homedir(), 'Library', 'Application Support', 'com.conductor.app', 'agent-binaries');
|
|
192
|
+
/** Prefer the CLI Conductor runs, falling back to the user's PATH for older app installs. */
|
|
193
|
+
function agentBinary(provider) {
|
|
194
|
+
const root = path.join(BUNDLED_BINARIES, provider);
|
|
195
|
+
try {
|
|
196
|
+
const versions = fs
|
|
197
|
+
.readdirSync(root, { withFileTypes: true })
|
|
198
|
+
.filter(entry => entry.isDirectory())
|
|
199
|
+
.map(entry => entry.name)
|
|
200
|
+
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
|
|
201
|
+
for (const version of versions) {
|
|
202
|
+
const candidate = path.join(root, version, provider);
|
|
203
|
+
try {
|
|
204
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
205
|
+
return candidate;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// A half-downloaded version is not a CLI; try the next one.
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Conductor did not bundle this harness yet; spawn can still resolve the user's CLI.
|
|
214
|
+
}
|
|
215
|
+
return provider;
|
|
216
|
+
}
|
|
217
|
+
const MAX_CLI_OUTPUT = 4 * 1024 * 1024;
|
|
218
|
+
function failureMessage(name, stderr, code) {
|
|
219
|
+
const detail = stderr.trim().slice(-2_000);
|
|
220
|
+
return new Error(`${name} exited before returning usage${code === null ? '' : ` (${code})`}${detail ? `: ${detail}` : ''}`);
|
|
221
|
+
}
|
|
222
|
+
function codexResponse(binary = agentBinary('codex')) {
|
|
223
|
+
return new Promise((resolve, reject) => {
|
|
224
|
+
const child = spawn(binary, ['app-server'], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
225
|
+
let stdout = '';
|
|
226
|
+
let stderr = '';
|
|
227
|
+
let requested = false;
|
|
228
|
+
let settled = false;
|
|
229
|
+
const finish = (error, result) => {
|
|
230
|
+
if (settled)
|
|
231
|
+
return;
|
|
232
|
+
settled = true;
|
|
233
|
+
clearTimeout(timer);
|
|
234
|
+
child.kill();
|
|
235
|
+
if (error)
|
|
236
|
+
reject(error);
|
|
237
|
+
else
|
|
238
|
+
resolve(result);
|
|
239
|
+
};
|
|
240
|
+
const timer = setTimeout(() => finish(new Error('Codex plan-usage read timed out')), 8_000);
|
|
241
|
+
child.stdout.setEncoding('utf8');
|
|
242
|
+
child.stderr.setEncoding('utf8');
|
|
243
|
+
child.stderr.on('data', chunk => {
|
|
244
|
+
stderr = (stderr + chunk).slice(-MAX_CLI_OUTPUT);
|
|
245
|
+
});
|
|
246
|
+
child.stdout.on('data', chunk => {
|
|
247
|
+
stdout += chunk;
|
|
248
|
+
if (stdout.length > MAX_CLI_OUTPUT)
|
|
249
|
+
return finish(new Error('Codex plan-usage response was too large'));
|
|
250
|
+
let newline = stdout.indexOf('\n');
|
|
251
|
+
while (newline >= 0) {
|
|
252
|
+
const line = stdout.slice(0, newline).trim();
|
|
253
|
+
stdout = stdout.slice(newline + 1);
|
|
254
|
+
newline = stdout.indexOf('\n');
|
|
255
|
+
if (!line)
|
|
256
|
+
continue;
|
|
257
|
+
let message;
|
|
258
|
+
try {
|
|
259
|
+
message = JSON.parse(line);
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (message.id === 1 && !requested) {
|
|
265
|
+
requested = true;
|
|
266
|
+
child.stdin.write(`${JSON.stringify({ method: 'initialized' })}\n`);
|
|
267
|
+
child.stdin.write(`${JSON.stringify({ id: 2, method: 'account/rateLimits/read', params: null })}\n`);
|
|
268
|
+
}
|
|
269
|
+
else if (message.id === 2) {
|
|
270
|
+
if (message.error)
|
|
271
|
+
return finish(new Error('Codex rejected the plan-usage request'));
|
|
272
|
+
return finish(null, message);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
child.on('error', error => finish(error));
|
|
277
|
+
child.on('close', code => finish(failureMessage('Codex', stderr, code)));
|
|
278
|
+
child.stdin.on('error', error => finish(error));
|
|
279
|
+
child.stdin.write(`${JSON.stringify({
|
|
280
|
+
id: 1,
|
|
281
|
+
method: 'initialize',
|
|
282
|
+
params: { clientInfo: { name: 'conductor-remote', version: '1' } }
|
|
283
|
+
})}\n`);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
function claudeResponse(binary = agentBinary('claude')) {
|
|
287
|
+
return new Promise((resolve, reject) => {
|
|
288
|
+
const child = spawn(binary, [
|
|
289
|
+
'-p',
|
|
290
|
+
'--input-format',
|
|
291
|
+
'stream-json',
|
|
292
|
+
'--output-format',
|
|
293
|
+
'stream-json',
|
|
294
|
+
'--verbose',
|
|
295
|
+
'--no-session-persistence',
|
|
296
|
+
'--safe-mode'
|
|
297
|
+
], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
298
|
+
let stdout = '';
|
|
299
|
+
let stderr = '';
|
|
300
|
+
let settled = false;
|
|
301
|
+
const finish = (error, result) => {
|
|
302
|
+
if (settled)
|
|
303
|
+
return;
|
|
304
|
+
settled = true;
|
|
305
|
+
clearTimeout(timer);
|
|
306
|
+
child.kill();
|
|
307
|
+
if (error)
|
|
308
|
+
reject(error);
|
|
309
|
+
else
|
|
310
|
+
resolve(result);
|
|
311
|
+
};
|
|
312
|
+
const timer = setTimeout(() => finish(new Error('Claude plan-usage read timed out')), 10_000);
|
|
313
|
+
child.stdout.setEncoding('utf8');
|
|
314
|
+
child.stderr.setEncoding('utf8');
|
|
315
|
+
child.stderr.on('data', chunk => {
|
|
316
|
+
stderr = (stderr + chunk).slice(-MAX_CLI_OUTPUT);
|
|
317
|
+
});
|
|
318
|
+
child.stdout.on('data', chunk => {
|
|
319
|
+
stdout += chunk;
|
|
320
|
+
if (stdout.length > MAX_CLI_OUTPUT)
|
|
321
|
+
return finish(new Error('Claude plan-usage response was too large'));
|
|
322
|
+
let newline = stdout.indexOf('\n');
|
|
323
|
+
while (newline >= 0) {
|
|
324
|
+
const line = stdout.slice(0, newline).trim();
|
|
325
|
+
stdout = stdout.slice(newline + 1);
|
|
326
|
+
newline = stdout.indexOf('\n');
|
|
327
|
+
if (!line)
|
|
328
|
+
continue;
|
|
329
|
+
let message;
|
|
330
|
+
try {
|
|
331
|
+
message = JSON.parse(line);
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const response = object(message.response);
|
|
337
|
+
if (message.type !== 'control_response' || response?.request_id !== 'plan-usage')
|
|
338
|
+
continue;
|
|
339
|
+
if (response.subtype !== 'success')
|
|
340
|
+
return finish(new Error('Claude rejected the structured plan-usage request'));
|
|
341
|
+
return finish(null, message);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
child.on('error', error => finish(error));
|
|
345
|
+
child.on('close', code => finish(failureMessage('Claude', stderr, code)));
|
|
346
|
+
child.stdin.on('error', error => finish(error));
|
|
347
|
+
child.stdin.end(`${JSON.stringify({
|
|
348
|
+
type: 'control_request',
|
|
349
|
+
request_id: 'plan-usage',
|
|
350
|
+
request: { subtype: 'get_usage' }
|
|
351
|
+
})}\n`);
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
function unavailable(provider, label, message) {
|
|
355
|
+
return { provider, label, status: 'unavailable', plan: null, buckets: [], message };
|
|
356
|
+
}
|
|
357
|
+
function failed(provider, label, error) {
|
|
358
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
359
|
+
console.warn(`[relay] ${label} plan usage failed: ${detail}`);
|
|
360
|
+
const missing = error?.code === 'ENOENT';
|
|
361
|
+
return {
|
|
362
|
+
provider,
|
|
363
|
+
label,
|
|
364
|
+
status: missing ? 'unavailable' : 'error',
|
|
365
|
+
plan: null,
|
|
366
|
+
buckets: [],
|
|
367
|
+
message: missing ? `${label} is not installed on this Mac.` : `Could not read plan usage from ${label}.`
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
export async function readCodexPlanUsage() {
|
|
371
|
+
try {
|
|
372
|
+
return parseCodexPlanUsage(await codexResponse());
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
return failed('codex', 'Codex', error);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
export async function readClaudePlanUsage() {
|
|
379
|
+
try {
|
|
380
|
+
return parseClaudePlanUsage(await claudeResponse());
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
return failed('claude', 'Claude Code', error);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const DEFAULT_READERS = [
|
|
387
|
+
{ provider: 'claude', label: 'Claude Code', read: readClaudePlanUsage },
|
|
388
|
+
{ provider: 'codex', label: 'Codex', read: readCodexPlanUsage }
|
|
389
|
+
];
|
|
390
|
+
const UNSUPPORTED = [
|
|
391
|
+
unavailable('cursor', 'Cursor Agent', 'Cursor Agent does not expose plan limits through its CLI.'),
|
|
392
|
+
unavailable('opencode', 'OpenCode', 'OpenCode reports local token and cost totals, not provider plan limits.')
|
|
393
|
+
];
|
|
394
|
+
/** Coalesced provider reads. `/api/usage` may be opened from several phones at once. */
|
|
395
|
+
export class PlanUsageService {
|
|
396
|
+
readers;
|
|
397
|
+
cacheMs;
|
|
398
|
+
now;
|
|
399
|
+
cached = null;
|
|
400
|
+
inFlight = null;
|
|
401
|
+
constructor(options = {}) {
|
|
402
|
+
this.readers = options.readers ?? DEFAULT_READERS;
|
|
403
|
+
this.cacheMs = options.cacheMs ?? 60_000;
|
|
404
|
+
this.now = options.now ?? Date.now;
|
|
405
|
+
}
|
|
406
|
+
read(force = false) {
|
|
407
|
+
if (!force && this.cached && this.now() - this.cached.fetchedAt < this.cacheMs)
|
|
408
|
+
return Promise.resolve(this.cached);
|
|
409
|
+
if (this.inFlight)
|
|
410
|
+
return this.inFlight;
|
|
411
|
+
const pending = Promise.all(this.readers.map(async (reader) => {
|
|
412
|
+
try {
|
|
413
|
+
return await reader.read();
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
return failed(reader.provider, reader.label, error);
|
|
417
|
+
}
|
|
418
|
+
})).then(providers => {
|
|
419
|
+
const snapshot = { providers: [...providers, ...UNSUPPORTED], fetchedAt: this.now() };
|
|
420
|
+
this.cached = snapshot;
|
|
421
|
+
return snapshot;
|
|
422
|
+
});
|
|
423
|
+
this.inFlight = pending;
|
|
424
|
+
void pending.finally(() => {
|
|
425
|
+
if (this.inFlight === pending)
|
|
426
|
+
this.inFlight = null;
|
|
427
|
+
});
|
|
428
|
+
return pending;
|
|
429
|
+
}
|
|
430
|
+
}
|
package/dist-node/src/routes.js
CHANGED
|
@@ -46,6 +46,8 @@ export const routes = {
|
|
|
46
46
|
repos: flat('GET', '/api/repos'),
|
|
47
47
|
/** Picker labels the relay has previously read from Conductor, grouped by harness. */
|
|
48
48
|
modelCatalog: flat('GET', '/api/models'),
|
|
49
|
+
/** Rolling subscription limits, read without a model request from each provider CLI. */
|
|
50
|
+
planUsage: flat('GET', '/api/usage'),
|
|
49
51
|
repoIcon: param('GET', '/api/repos/:repo/icon'),
|
|
50
52
|
/** A temporary image emitted in a chat message, fetched with the phone's auth header. */
|
|
51
53
|
localImage: param('GET', '/api/local-images/:path'),
|
package/dist-node/src/server.js
CHANGED
|
@@ -20,6 +20,7 @@ import { ModelCache } from "./model-cache.js";
|
|
|
20
20
|
import { armNoSleep, disarmNoSleep, MAX_SECONDS as NOSLEEP_MAX_SECONDS, nosleepState, watchNoSleepExpiry } from "./nosleep.js";
|
|
21
21
|
import { chatRoute, noteViewing, notifyAll, notifyDevice, pushConfig, startNotifier, subscribeDevice, unsubscribeDevice } from "./notify.js";
|
|
22
22
|
import { ParkedPromptQueue } from "./parked.js";
|
|
23
|
+
import { PlanUsageService } from "./plan-usage.js";
|
|
23
24
|
import { attachPrStatus } from "./pr.js";
|
|
24
25
|
import { readPrefs, writePrefs } from "./prefs.js";
|
|
25
26
|
import { Reads } from "./reads.js";
|
|
@@ -46,6 +47,7 @@ const STAGED_ATTACHMENTS_DIR = path.join(stateDir(), 'attachment-staging');
|
|
|
46
47
|
// relay state alongside the prompt queues. This lets a brand-new workspace choose
|
|
47
48
|
// from a list before Conductor has created its first chat.
|
|
48
49
|
const modelCache = new ModelCache(path.join(stateDir(), 'model-cache.json'));
|
|
50
|
+
const planUsage = new PlanUsageService();
|
|
49
51
|
// Full-text index over the chat prose, in the relay's own sidecar DB — never in
|
|
50
52
|
// Conductor's (see src/search.ts). It backfills in the background and is disposable:
|
|
51
53
|
// deleting the file rebuilds it on the next start.
|
|
@@ -880,6 +882,12 @@ const server = http.createServer(async (req, res) => {
|
|
|
880
882
|
if (isRoute(routes.modelCatalog, req.method, pathname)) {
|
|
881
883
|
return json(req, res, 200, { groups: modelCache.list(), defaultModel: modelCache.defaultModel() });
|
|
882
884
|
}
|
|
885
|
+
// GET /api/usage — structured subscription limits from the CLIs Conductor
|
|
886
|
+
// itself bundles. Both reads are prompt-free and cached; `refresh=1` is the
|
|
887
|
+
// explicit user action in the sheet, never a background poll.
|
|
888
|
+
if (isRoute(routes.planUsage, req.method, pathname)) {
|
|
889
|
+
return json(req, res, 200, await planUsage.read(url.searchParams.get('refresh') === '1'));
|
|
890
|
+
}
|
|
883
891
|
// GET /api/settings — relay preferences plus what the phone needs to edit them:
|
|
884
892
|
// the SSIDs this Mac already holds credentials for, so the picker offers a choice
|
|
885
893
|
// instead of asking someone to type a network name from memory on a phone keyboard.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-remote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.82.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"packageManager": "yarn@4.15.0",
|
|
6
6
|
"description": "Phone control panel for local Conductor agents. Reads ride SQLite + git; prompts ride Conductor's own dispatch path.",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-outline-style:solid;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, -apple-system, "SF Pro Text", "Segoe UI", Roboto, sans-serif;--font-mono:ui-monospace, "SF Mono", "JetBrains Mono", "Fira Code", Menlo, monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--ease-out:cubic-bezier(0, 0, .2, 1);--animate-spin:spin 1s linear infinite;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-bg:#0a0b0e;--color-surface:#14161b;--color-surface-2:#1b1e26;--color-border:#262a33;--color-border-soft:#1e222a;--color-text:#e7e9ee;--color-muted:#8b909d;--color-faint:#5c616d;--color-accent:#8b7dff;--color-accent-soft:#2a2650;--color-working:#f5a623;--color-idle:#3ecf8e;--color-done:#5b9dff;--color-pr-merged:#ac47ff;--color-pr-draft:#a4a3a2;--color-pr-attention:#facc15;--color-pr-mergeable:#49de80;--color-add:#3ecf8e;--color-del:#ff6b6b}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{-webkit-tap-highlight-color:transparent}html,body,#root{height:var(--app-height,100dvh)}html,body{overflow:hidden}@media(display-mode:standalone){html,body,#root{height:var(--app-height,100vh)}}html[data-standalone],html[data-standalone] body,html[data-standalone] #root{height:var(--app-height,100vh)}.app-height{height:var(--app-height,100dvh)}@media(display-mode:standalone){.app-height{height:var(--app-height,100vh)}}html[data-standalone] .app-height{height:var(--app-height,100vh)}@media(min-width:48rem){.app-height,html[data-standalone] .app-height{height:auto}}html{background:var(--color-bg)}body{background:var(--color-bg);color:var(--color-text);font-family:var(--font-sans);-webkit-font-smoothing:antialiased;overscroll-behavior:none;margin:0}button{font:inherit;color:inherit;cursor:pointer}::-webkit-scrollbar{width:0;height:0}}@layer components{.card{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-border);background-color:var(--color-surface);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3.5);text-align:left;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}.card:active{background-color:var(--color-surface-2);scale:.985}.pill{padding-inline:calc(var(--spacing) * 3.5);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-muted);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px}.pill-active{background-color:var(--color-surface-2);color:var(--color-text)}.ctl{padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing);--tw-font-weight:var(--font-weight-medium);font-size:13px;font-weight:var(--font-weight-medium);color:var(--color-muted);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px}.ctl-on{background-color:var(--color-surface-2);color:var(--color-text)}.ctl-staged{background-color:var(--color-accent-soft);color:var(--color-accent)}.dot{width:calc(var(--spacing) * 2.5);height:calc(var(--spacing) * 2.5);background-color:var(--color-faint);border-radius:3.40282e38px;flex-shrink:0}.dot-spinner{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5);animation:var(--animate-spin);border-style:var(--tw-border-style);--spin-color:var(--color-working);border-width:2px;border-color:var(--spin-color);border-radius:3.40282e38px;flex-shrink:0}@supports (color:color-mix(in lab,red,red)){.dot-spinner{border-color:color-mix(in srgb,var(--spin-color) 30%,transparent)}}.dot-spinner{border-top-color:var(--spin-color);border-right-color:var(--spin-color)}.dot-idle{background:var(--color-idle)}.dot-done{background:var(--color-done)}.typing-dot{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5);background-color:var(--color-muted);border-radius:3.40282e38px;animation:1.2s ease-in-out infinite typing}.typing-dot:nth-child(2){animation-delay:.15s}.typing-dot:nth-child(3){animation-delay:.3s}.md>:first-child{margin-top:0}.md>:last-child{margin-bottom:0}.md p{margin:.5em 0}.md h1,.md h2,.md h3,.md h4,.md h5,.md h6{margin:.9em 0 .4em;font-weight:600;line-height:1.3}.md h1{font-size:1.6em}.md h2{font-size:1.4em}.md h3{font-size:1.2em}.md h4{font-size:1.1em}.md ul,.md ol{margin:.5em 0;padding-left:1.4em}.md ul{list-style:outside}.md ol{list-style:decimal}.md li{margin:.3em 0}.md li>ul,.md li>ol{margin:.2em 0}.md code{background:#1b1e26cc;border-radius:.3rem}@supports (color:color-mix(in lab,red,red)){.md code{background:color-mix(in srgb,var(--color-surface-2) 80%,transparent)}}.md code{font-family:var(--font-mono);padding:.1em .35em;font-size:.85em}.md pre{border:1px solid var(--color-border-soft);background:var(--color-bg);border-radius:.6rem;margin:.5em 0;padding:.6em .75em;overflow-x:auto}.md pre code{background:0 0;padding:0;font-size:12px;line-height:1.5}.md a{color:var(--color-accent);text-underline-offset:2px;text-decoration:underline}.md blockquote{border-left:2px solid var(--color-border);color:var(--color-muted);margin:.5em 0;padding-left:.75em}.md hr{border:0;border-top:1px solid var(--color-border-soft);margin:1.25em 0}.md img{border-radius:.6rem;max-width:100%;height:auto;margin:.5em 0;display:block}.md table{border-collapse:collapse;max-width:100%;margin:.5em 0;font-size:.9em;display:block;overflow-x:auto}.md th,.md td{border:1px solid var(--color-border);text-align:left;padding:.3em .6em}.md th{background:var(--color-surface-2);font-weight:600}.md strong{font-weight:600}.md input[type=checkbox]{margin-right:.4em}.hljs-comment,.hljs-quote{color:var(--color-faint);font-style:italic}.hljs-keyword,.hljs-literal,.hljs-type,.hljs-built_in,.hljs-selector-tag,.hljs-doctag,.hljs-meta .hljs-keyword{color:var(--color-accent)}.hljs-string,.hljs-regexp,.hljs-char.escape_,.hljs-addition,.hljs-meta .hljs-string{color:var(--color-add)}.hljs-number,.hljs-symbol,.hljs-bullet,.hljs-link,.hljs-meta,.hljs-template-variable{color:var(--color-working)}.hljs-title,.hljs-title.class_,.hljs-title.function_,.hljs-name,.hljs-section,.hljs-selector-id,.hljs-selector-class{color:var(--color-done)}.hljs-attr,.hljs-attribute,.hljs-property,.hljs-variable,.hljs-params,.hljs-subst,.hljs-template-tag{color:var(--color-text)}.hljs-deletion{color:var(--color-del)}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:600}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.inset-1{inset:var(--spacing)}.inset-x-0{inset-inline:0}.inset-y-0{inset-block:0}.top-0{top:0}.top-0\.5{top:calc(var(--spacing) * .5)}.top-1\.5{top:calc(var(--spacing) * 1.5)}.top-1\/2{top:50%}.top-full{top:100%}.-right-0\.5{right:calc(var(--spacing) * -.5)}.right-0{right:0}.right-1{right:var(--spacing)}.right-1\.5{right:calc(var(--spacing) * 1.5)}.right-2{right:calc(var(--spacing) * 2)}.right-3{right:calc(var(--spacing) * 3)}.-bottom-0\.5{bottom:calc(var(--spacing) * -.5)}.bottom-0{bottom:0}.bottom-full{bottom:100%}.left-0{left:0}.left-0\.5{left:calc(var(--spacing) * .5)}.left-3{left:calc(var(--spacing) * 3)}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[60\]{z-index:60}.col-span-3{grid-column:span 3/span 3}.m-0{margin:0}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mx-auto{margin-inline:auto}.my-1\.5{margin-block:calc(var(--spacing) * 1.5)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-2\.5{margin-top:calc(var(--spacing) * 2.5)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.-mr-1{margin-right:calc(var(--spacing) * -1)}.-mr-2{margin-right:calc(var(--spacing) * -2)}.mr-1{margin-right:var(--spacing)}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.mb-1{margin-bottom:var(--spacing)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.-ml-1{margin-left:calc(var(--spacing) * -1)}.ml-1{margin-left:var(--spacing)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-auto{margin-left:auto}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-4{-webkit-line-clamp:4;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-2\.5{width:calc(var(--spacing) * 2.5);height:calc(var(--spacing) * 2.5)}.size-3{width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.size-11{width:calc(var(--spacing) * 11);height:calc(var(--spacing) * 11)}.size-20{width:calc(var(--spacing) * 20);height:calc(var(--spacing) * 20)}.size-60{width:calc(var(--spacing) * 60);height:calc(var(--spacing) * 60)}.size-\[15px\]{width:15px;height:15px}.size-\[18px\]{width:18px;height:18px}.size-full{width:100%;height:100%}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-12{height:calc(var(--spacing) * 12)}.h-\[15px\]{height:15px}.h-full{height:100%}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-72{max-height:calc(var(--spacing) * 72)}.max-h-\[70\%\]{max-height:70%}.max-h-\[85dvh\]{max-height:85dvh}.min-h-0{min-height:0}.min-h-14{min-height:calc(var(--spacing) * 14)}.w-0\.5{width:calc(var(--spacing) * .5)}.w-1{width:var(--spacing)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-9{width:calc(var(--spacing) * 9)}.w-11{width:calc(var(--spacing) * 11)}.w-12{width:calc(var(--spacing) * 12)}.w-56{width:calc(var(--spacing) * 56)}.w-60{width:calc(var(--spacing) * 60)}.w-64{width:calc(var(--spacing) * 64)}.w-\[85\%\]{width:85%}.w-full{width:100%}.max-w-36{max-width:calc(var(--spacing) * 36)}.max-w-40{max-width:calc(var(--spacing) * 40)}.max-w-64{max-width:calc(var(--spacing) * 64)}.max-w-72{max-width:calc(var(--spacing) * 72)}.max-w-80{max-width:calc(var(--spacing) * 80)}.max-w-\[55vw\]{max-width:55vw}.max-w-\[70vw\]{max-width:70vw}.max-w-\[85\%\]{max-width:85%}.max-w-\[85vw\]{max-width:85vw}.max-w-\[92\%\]{max-width:92%}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:0}.min-w-5{min-width:calc(var(--spacing) * 5)}.min-w-52{min-width:calc(var(--spacing) * 52)}.min-w-max{min-width:max-content}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-0{--tw-translate-x:0;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-0{--tw-translate-y:0;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-3{--tw-translate-y:calc(var(--spacing) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.-rotate-90{rotate:-90deg}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-none{resize:none}.scrollbar-thin{scrollbar-width:thin}.list-none{list-style-type:none}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.grid-cols-\[minmax\(0\,1fr\)_auto_auto\]{grid-template-columns:minmax(0,1fr) auto auto}.grid-cols-subgrid{grid-template-columns:subgrid}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-0{gap:0}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-\[1\.5px\]{gap:1.5px}:where(.space-y-1\.25>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.25) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.25) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-y-1{row-gap:var(--spacing)}.self-center{align-self:center}.self-start{align-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t-2xl{border-top-left-radius:var(--radius-2xl);border-top-right-radius:var(--radius-2xl)}.rounded-t-3xl{border-top-left-radius:var(--radius-3xl);border-top-right-radius:var(--radius-3xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-accent{border-color:var(--color-accent)}.border-accent\/50{border-color:#8b7dff80}@supports (color:color-mix(in lab,red,red)){.border-accent\/50{border-color:color-mix(in oklab,var(--color-accent) 50%,transparent)}}.border-border{border-color:var(--color-border)}.border-border-soft{border-color:var(--color-border-soft)}.border-del\/30{border-color:#ff6b6b4d}@supports (color:color-mix(in lab,red,red)){.border-del\/30{border-color:color-mix(in oklab,var(--color-del) 30%,transparent)}}.border-del\/40{border-color:#ff6b6b66}@supports (color:color-mix(in lab,red,red)){.border-del\/40{border-color:color-mix(in oklab,var(--color-del) 40%,transparent)}}.border-faint{border-color:var(--color-faint)}.border-white\/80{border-color:#fffc}@supports (color:color-mix(in lab,red,red)){.border-white\/80{border-color:color-mix(in oklab,var(--color-white) 80%,transparent)}}.border-t-accent{border-top-color:var(--color-accent)}.border-t-text{border-top-color:var(--color-text)}.border-l-accent{border-left-color:var(--color-accent)}.bg-accent{background-color:var(--color-accent)}.bg-accent-soft{background-color:var(--color-accent-soft)}.bg-accent-soft\/90{background-color:#2a2650e6}@supports (color:color-mix(in lab,red,red)){.bg-accent-soft\/90{background-color:color-mix(in oklab,var(--color-accent-soft) 90%,transparent)}}.bg-accent\/10{background-color:#8b7dff1a}@supports (color:color-mix(in lab,red,red)){.bg-accent\/10{background-color:color-mix(in oklab,var(--color-accent) 10%,transparent)}}.bg-accent\/25{background-color:#8b7dff40}@supports (color:color-mix(in lab,red,red)){.bg-accent\/25{background-color:color-mix(in oklab,var(--color-accent) 25%,transparent)}}.bg-add{background-color:var(--color-add)}.bg-add\/10{background-color:#3ecf8e1a}@supports (color:color-mix(in lab,red,red)){.bg-add\/10{background-color:color-mix(in oklab,var(--color-add) 10%,transparent)}}.bg-bg{background-color:var(--color-bg)}.bg-bg\/80{background-color:#0a0b0ecc}@supports (color:color-mix(in lab,red,red)){.bg-bg\/80{background-color:color-mix(in oklab,var(--color-bg) 80%,transparent)}}.bg-bg\/90{background-color:#0a0b0ee6}@supports (color:color-mix(in lab,red,red)){.bg-bg\/90{background-color:color-mix(in oklab,var(--color-bg) 90%,transparent)}}.bg-black{background-color:var(--color-black)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black) 60%,transparent)}}.bg-black\/75{background-color:#000000bf}@supports (color:color-mix(in lab,red,red)){.bg-black\/75{background-color:color-mix(in oklab,var(--color-black) 75%,transparent)}}.bg-current{background-color:currentColor}.bg-del{background-color:var(--color-del)}.bg-del\/5{background-color:#ff6b6b0d}@supports (color:color-mix(in lab,red,red)){.bg-del\/5{background-color:color-mix(in oklab,var(--color-del) 5%,transparent)}}.bg-del\/10{background-color:#ff6b6b1a}@supports (color:color-mix(in lab,red,red)){.bg-del\/10{background-color:color-mix(in oklab,var(--color-del) 10%,transparent)}}.bg-del\/15{background-color:#ff6b6b26}@supports (color:color-mix(in lab,red,red)){.bg-del\/15{background-color:color-mix(in oklab,var(--color-del) 15%,transparent)}}.bg-pr-merged\/10{background-color:#ac47ff1a}@supports (color:color-mix(in lab,red,red)){.bg-pr-merged\/10{background-color:color-mix(in oklab,var(--color-pr-merged) 10%,transparent)}}.bg-pr-merged\/15{background-color:#ac47ff26}@supports (color:color-mix(in lab,red,red)){.bg-pr-merged\/15{background-color:color-mix(in oklab,var(--color-pr-merged) 15%,transparent)}}.bg-surface{background-color:var(--color-surface)}.bg-surface-2{background-color:var(--color-surface-2)}.bg-surface-2\/60{background-color:#1b1e2699}@supports (color:color-mix(in lab,red,red)){.bg-surface-2\/60{background-color:color-mix(in oklab,var(--color-surface-2) 60%,transparent)}}.bg-surface\/40{background-color:#14161b66}@supports (color:color-mix(in lab,red,red)){.bg-surface\/40{background-color:color-mix(in oklab,var(--color-surface) 40%,transparent)}}.bg-surface\/60{background-color:#14161b99}@supports (color:color-mix(in lab,red,red)){.bg-surface\/60{background-color:color-mix(in oklab,var(--color-surface) 60%,transparent)}}.bg-surface\/70{background-color:#14161bb3}@supports (color:color-mix(in lab,red,red)){.bg-surface\/70{background-color:color-mix(in oklab,var(--color-surface) 70%,transparent)}}.bg-surface\/95{background-color:#14161bf2}@supports (color:color-mix(in lab,red,red)){.bg-surface\/95{background-color:color-mix(in oklab,var(--color-surface) 95%,transparent)}}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-working{background-color:var(--color-working)}.bg-working\/10{background-color:#f5a6231a}@supports (color:color-mix(in lab,red,red)){.bg-working\/10{background-color:color-mix(in oklab,var(--color-working) 10%,transparent)}}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-0{padding:0}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-16{padding-block:calc(var(--spacing) * 16)}.py-px{padding-block:1px}.pt-0\.5{padding-top:calc(var(--spacing) * .5)}.pt-1\.5{padding-top:calc(var(--spacing) * 1.5)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-2\.5{padding-top:calc(var(--spacing) * 2.5)}.pr-1{padding-right:var(--spacing)}.pr-9{padding-right:calc(var(--spacing) * 9)}.pb-0\.5{padding-bottom:calc(var(--spacing) * .5)}.pb-1{padding-bottom:var(--spacing)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-2\.5{padding-bottom:calc(var(--spacing) * 2.5)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-2\.5{padding-left:calc(var(--spacing) * 2.5)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-baseline{vertical-align:baseline}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.9em\]{font-size:.9em}.text-\[10px\]{font-size:10px}.text-\[11\.5px\]{font-size:11.5px}.text-\[11px\]{font-size:11px}.text-\[12\.5px\]{font-size:12.5px}.text-\[12px\]{font-size:12px}.text-\[13\.5px\]{font-size:13.5px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.leading-\[1\.5\]{--tw-leading:1.5;line-height:1.5}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.\[overflow-wrap\:anywhere\]{overflow-wrap:anywhere}.break-words{overflow-wrap:break-word}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent{color:var(--color-accent)}.text-add{color:var(--color-add)}.text-bg{color:var(--color-bg)}.text-black{color:var(--color-black)}.text-del{color:var(--color-del)}.text-del\/80{color:#ff6b6bcc}@supports (color:color-mix(in lab,red,red)){.text-del\/80{color:color-mix(in oklab,var(--color-del) 80%,transparent)}}.text-faint{color:var(--color-faint)}.text-muted{color:var(--color-muted)}.text-pr-merged{color:var(--color-pr-merged)}.text-text{color:var(--color-text)}.text-white{color:var(--color-white)}.text-working{color:var(--color-working)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.decoration-dotted{text-decoration-style:dotted}.underline-offset-2{text-underline-offset:2px}.\[color-scheme\:dark\]{color-scheme:dark}.opacity-0{opacity:0}.opacity-20{opacity:.2}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_0_0_100vmax_rgba\(0\,0\,0\,0\.5\)\]{--tw-shadow:0 0 0 100vmax var(--tw-shadow-color,#00000080);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-black\/40{--tw-shadow-color:#0006}@supports (color:color-mix(in lab,red,red)){.shadow-black\/40{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-black) 40%, transparent) var(--tw-shadow-alpha), transparent)}}.shadow-black\/60{--tw-shadow-color:#0009}@supports (color:color-mix(in lab,red,red)){.shadow-black\/60{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-black) 60%, transparent) var(--tw-shadow-alpha), transparent)}}.ring-surface{--tw-ring-color:var(--color-surface)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-xl{--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.group-open\/steps\:invisible:is(:where(.group\/steps):is([open],:popover-open,:open) *){visibility:hidden}.group-open\/steps\:rotate-90:is(:where(.group\/steps):is([open],:popover-open,:open) *),.group-open\/think\:rotate-90:is(:where(.group\/think):is([open],:popover-open,:open) *){rotate:90deg}.group-open\/tool\:invisible:is(:where(.group\/tool):is([open],:popover-open,:open) *){visibility:hidden}.group-open\/tool\:rotate-90:is(:where(.group\/tool):is([open],:popover-open,:open) *){rotate:90deg}.peer-focus-visible\:outline-2:is(:where(.peer):focus-visible~*){outline-style:var(--tw-outline-style);outline-width:2px}.peer-focus-visible\:outline-offset-2:is(:where(.peer):focus-visible~*){outline-offset:2px}.peer-focus-visible\:outline-accent:is(:where(.peer):focus-visible~*){outline-color:var(--color-accent)}.placeholder\:font-sans::placeholder{font-family:var(--font-sans)}.placeholder\:text-faint::placeholder{color:var(--color-faint)}@media(hover:hover){.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-accent\/50:focus{border-color:#8b7dff80}@supports (color:color-mix(in lab,red,red)){.focus\:border-accent\/50:focus{border-color:color-mix(in oklab,var(--color-accent) 50%,transparent)}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.focus-visible\:-outline-offset-2:focus-visible{outline-offset:-2px}.focus-visible\:outline-accent:focus-visible{outline-color:var(--color-accent)}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.active\:scale-\[0\.985\]:active{scale:.985}.active\:bg-black\/70:active{background-color:#000000b3}@supports (color:color-mix(in lab,red,red)){.active\:bg-black\/70:active{background-color:color-mix(in oklab,var(--color-black) 70%,transparent)}}.active\:bg-surface:active{background-color:var(--color-surface)}.active\:bg-surface-2:active{background-color:var(--color-surface-2)}.active\:text-text:active{color:var(--color-text)}.active\:opacity-80:active{opacity:.8}.disabled\:bg-surface-2:disabled{background-color:var(--color-surface-2)}.disabled\:text-faint:disabled{color:var(--color-faint)}.disabled\:text-faint\/40:disabled{color:#5c616d66}@supports (color:color-mix(in lab,red,red)){.disabled\:text-faint\/40:disabled{color:color-mix(in oklab,var(--color-faint) 40%,transparent)}}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-60:disabled{opacity:.6}.has-\[textarea\:focus\]\:border-accent\/60:has(:is(textarea:focus)){border-color:#8b7dff99}@supports (color:color-mix(in lab,red,red)){.has-\[textarea\:focus\]\:border-accent\/60:has(:is(textarea:focus)){border-color:color-mix(in oklab,var(--color-accent) 60%,transparent)}}@media(prefers-reduced-motion:reduce){.motion-reduce\:transition-none{transition-property:none}}@media not all and (min-width:340px){.max-\[340px\]\:hidden{display:none}}@media(min-width:400px){.min-\[400px\]\:block{display:block}}@media(min-width:48rem){.md\:static{position:static}.md\:inset-0{inset:0}.md\:inset-6{inset:calc(var(--spacing) * 6)}.md\:inset-x-auto{inset-inline:auto}.md\:top-16{top:calc(var(--spacing) * 16)}.md\:left-1\/2{left:50%}.md\:z-auto{z-index:auto}.md\:m-auto{margin:auto}.md\:mb-6{margin-bottom:calc(var(--spacing) * 6)}.md\:block{display:block}.md\:hidden{display:none}.md\:h-fit{height:fit-content}.md\:max-h-\[70vh\]{max-height:70vh}.md\:w-72{width:calc(var(--spacing) * 72)}.md\:w-\[36rem\]{width:36rem}.md\:max-w-\[92vw\]{max-width:92vw}.md\:max-w-none{max-width:none}.md\:shrink-0{flex-shrink:0}.md\:-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.md\:translate-x-0{--tw-translate-x:0;translate:var(--tw-translate-x) var(--tw-translate-y)}.md\:rounded-2xl{border-radius:var(--radius-2xl)}.md\:rounded-3xl{border-radius:var(--radius-3xl)}.md\:border{border-style:var(--tw-border-style);border-width:1px}.md\:border-border{border-color:var(--color-border)}.md\:border-border-soft{border-color:var(--color-border-soft)}.md\:shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.md\:transition-none{transition-property:none}}@media(min-width:64rem){.lg\:static{position:static}.lg\:z-auto{z-index:auto}.lg\:w-80{width:calc(var(--spacing) * 80)}.lg\:w-\[380px\]{width:380px}.lg\:shrink-0{flex-shrink:0}.lg\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.lg\:border-border-soft{border-color:var(--color-border-soft)}}@media(min-width:80rem){.xl\:w-\[460px\]{width:460px}}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}}@keyframes typing{0%,60%,to{opacity:.35;transform:none}30%{opacity:1;transform:translateY(-3px)}}@keyframes fade-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.fade-in{animation:.18s ease-out fade-in}@media(prefers-reduced-motion:reduce){.fade-in{opacity:1;animation:none}.typing-dot{opacity:.6;animation:none}}.pt-safe{padding-top:max(env(safe-area-inset-top),.5rem)}.pb-safe{padding-bottom:max(env(safe-area-inset-bottom),.5rem)}html[data-keyboard] .pb-safe{padding-bottom:.5rem}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}
|