aegis-bridge 2.6.0 → 2.6.2
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/dashboard/dist/assets/index-9Hkkvm_I.css +32 -0
- package/dashboard/dist/assets/index-Bfabq3q-.js +262 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/config.d.ts +2 -0
- package/dist/config.js +3 -0
- package/dist/continuation-pointer.d.ts +11 -0
- package/dist/continuation-pointer.js +64 -0
- package/dist/dashboard/assets/index-9Hkkvm_I.css +32 -0
- package/dist/dashboard/assets/index-Bfabq3q-.js +262 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/diagnostics.d.ts +27 -0
- package/dist/diagnostics.js +95 -0
- package/dist/events.d.ts +14 -0
- package/dist/events.js +43 -14
- package/dist/fault-injection.d.ts +29 -0
- package/dist/fault-injection.js +115 -0
- package/dist/handshake.d.ts +21 -1
- package/dist/handshake.js +37 -3
- package/dist/hook-settings.d.ts +3 -2
- package/dist/hook-settings.js +6 -4
- package/dist/hook.js +10 -1
- package/dist/hooks.js +5 -0
- package/dist/logger.d.ts +35 -0
- package/dist/logger.js +65 -0
- package/dist/monitor.js +72 -11
- package/dist/permission-routes.d.ts +7 -0
- package/dist/permission-routes.js +28 -0
- package/dist/server.js +119 -44
- package/dist/session.d.ts +1 -0
- package/dist/session.js +41 -23
- package/dist/tmux.js +2 -2
- package/dist/utils/circular-buffer.d.ts +11 -0
- package/dist/utils/circular-buffer.js +37 -0
- package/dist/validation.d.ts +34 -0
- package/dist/validation.js +45 -3
- package/package.json +3 -2
- package/dashboard/dist/assets/index-B7DYf7vF.css +0 -32
- package/dashboard/dist/assets/index-DIyuyrlO.js +0 -262
- package/dist/dashboard/assets/index-B7DYf7vF.css +0 -32
- package/dist/dashboard/assets/index-DIyuyrlO.js +0 -262
package/dist/session.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Manages the lifecycle of CC sessions running in tmux windows.
|
|
5
5
|
* Tracks: session ID, window ID, byte offset for JSONL reading, status.
|
|
6
6
|
*/
|
|
7
|
+
import { randomBytes } from 'node:crypto';
|
|
7
8
|
import { readFile, writeFile, rename, mkdir, stat } from 'node:fs/promises';
|
|
8
9
|
import { existsSync, unlinkSync, readdirSync } from 'node:fs';
|
|
9
10
|
import { join, dirname } from 'node:path';
|
|
@@ -13,9 +14,11 @@ import { findSessionFileWithFanout } from './worktree-lookup.js';
|
|
|
13
14
|
import { detectUIState, extractInteractiveContent, parseStatusLine } from './terminal-parser.js';
|
|
14
15
|
import { computeStallThreshold } from './config.js';
|
|
15
16
|
import { neutralizeBypassPermissions, restoreSettings, cleanOrphanedBackup } from './permission-guard.js';
|
|
16
|
-
import { persistedStateSchema
|
|
17
|
+
import { persistedStateSchema } from './validation.js';
|
|
18
|
+
import { loadContinuationPointers } from './continuation-pointer.js';
|
|
17
19
|
import { writeHookSettingsFile, cleanupHookSettingsFile } from './hook-settings.js';
|
|
18
20
|
import { Mutex } from 'async-mutex';
|
|
21
|
+
import { maybeInjectFault } from './fault-injection.js';
|
|
19
22
|
/** Convert parsed JSON arrays to Sets for activeSubagents (#668). */
|
|
20
23
|
function hydrateSessions(raw) {
|
|
21
24
|
const sessions = {};
|
|
@@ -441,12 +444,36 @@ export class SessionManager {
|
|
|
441
444
|
// Merge defaultSessionEnv (from config) with per-session env (per-session wins)
|
|
442
445
|
// Security: validate env var names to prevent injection attacks
|
|
443
446
|
const ENV_NAME_RE = /^[A-Z_][A-Z0-9_]*$/;
|
|
447
|
+
// Issue #630: Expanded blocklist with additional dangerous env vars
|
|
444
448
|
const DANGEROUS_ENV_VARS = new Set([
|
|
449
|
+
// Dynamic loader / library injection
|
|
445
450
|
'PATH', 'LD_PRELOAD', 'LD_LIBRARY_PATH', 'NODE_OPTIONS',
|
|
446
451
|
'DYLD_INSERT_LIBRARIES', 'IFS', 'SHELL', 'ENV', 'BASH_ENV',
|
|
452
|
+
// Language-specific library paths
|
|
447
453
|
'PYTHONPATH', 'PERL5LIB', 'RUBYLIB', 'CLASSPATH',
|
|
448
454
|
'NODE_PATH', 'PYTHONHOME', 'PYTHONSTARTUP',
|
|
455
|
+
// Issue #630: Shell command injection vectors
|
|
456
|
+
'PROMPT_COMMAND', 'GIT_SSH_COMMAND', 'EDITOR', 'VISUAL',
|
|
457
|
+
'SUDO_ASKPASS', 'GIT_EXEC_PATH', 'NODE_ENV',
|
|
458
|
+
// Issue #630: Token/credential leakage
|
|
459
|
+
'GITHUB_TOKEN', 'NPM_TOKEN', 'GITLAB_TOKEN',
|
|
460
|
+
'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN',
|
|
461
|
+
'AZURE_CLIENT_SECRET', 'GOOGLE_APPLICATION_CREDENTIALS',
|
|
462
|
+
'DOCKER_TOKEN', 'HEROKU_API_KEY',
|
|
449
463
|
]);
|
|
464
|
+
// Issue #630: Dangerous env var prefixes (prefix match)
|
|
465
|
+
const DANGEROUS_ENV_PREFIXES = [
|
|
466
|
+
'npm_config_', // npm configuration override
|
|
467
|
+
'BASH_FUNC_', // bash function export
|
|
468
|
+
'SSH_', // SSH keys/agent config (SSH_AUTH_SOCK, SSH_PRIVATE_KEY, etc.)
|
|
469
|
+
'GITHUB_', // GitHub tokens/keys (except GITHUB_PATH which is benign)
|
|
470
|
+
'GITLAB_', // GitLab tokens
|
|
471
|
+
'AWS_', // AWS credentials
|
|
472
|
+
'AZURE_', // Azure credentials
|
|
473
|
+
'TF_', // Terraform tokens
|
|
474
|
+
'CI_', // CI tokens (CI_JOB_TOKEN, CI_BUILD_TOKEN, etc.)
|
|
475
|
+
'DOCKER_', // Docker registry tokens
|
|
476
|
+
];
|
|
450
477
|
const mergedEnv = {};
|
|
451
478
|
const allEnv = { ...this.config.defaultSessionEnv, ...opts.env };
|
|
452
479
|
for (const [key, value] of Object.entries(allEnv)) {
|
|
@@ -456,6 +483,10 @@ export class SessionManager {
|
|
|
456
483
|
if (DANGEROUS_ENV_VARS.has(key)) {
|
|
457
484
|
throw new Error(`Forbidden env var: "${key}" — cannot override dangerous environment variables`);
|
|
458
485
|
}
|
|
486
|
+
// Issue #630: Check dangerous prefixes
|
|
487
|
+
if (DANGEROUS_ENV_PREFIXES.some(prefix => key.startsWith(prefix))) {
|
|
488
|
+
throw new Error(`Forbidden env var: "${key}" — cannot override dangerous environment variable prefix "${DANGEROUS_ENV_PREFIXES.find(p => key.startsWith(p))}"`);
|
|
489
|
+
}
|
|
459
490
|
mergedEnv[key] = value;
|
|
460
491
|
}
|
|
461
492
|
const hasEnv = Object.keys(mergedEnv).length > 0;
|
|
@@ -471,12 +502,14 @@ export class SessionManager {
|
|
|
471
502
|
if (effectivePermissionMode !== 'bypassPermissions') {
|
|
472
503
|
settingsPatched = await neutralizeBypassPermissions(opts.workDir, effectivePermissionMode);
|
|
473
504
|
}
|
|
505
|
+
// Issue #629: Generate per-session HMAC secret for hook URL authentication.
|
|
506
|
+
const hookSecret = randomBytes(32).toString('hex');
|
|
474
507
|
// Issue #169 Phase 2: Generate HTTP hook settings for this session.
|
|
475
508
|
// Writes a temp file with hooks pointing to Aegis's hook receiver.
|
|
476
509
|
let hookSettingsFile;
|
|
477
510
|
try {
|
|
478
511
|
const baseUrl = `http://${this.config.host}:${this.config.port}`;
|
|
479
|
-
hookSettingsFile = await writeHookSettingsFile(baseUrl, id, opts.workDir);
|
|
512
|
+
hookSettingsFile = await writeHookSettingsFile(baseUrl, id, hookSecret, opts.workDir);
|
|
480
513
|
}
|
|
481
514
|
catch (e) {
|
|
482
515
|
console.error(`Hook settings: failed to generate settings file: ${e.message}`);
|
|
@@ -509,6 +542,7 @@ export class SessionManager {
|
|
|
509
542
|
permissionMode: effectivePermissionMode,
|
|
510
543
|
settingsPatched,
|
|
511
544
|
hookSettingsFile,
|
|
545
|
+
hookSecret,
|
|
512
546
|
};
|
|
513
547
|
this.state.sessions[id] = session;
|
|
514
548
|
await this.save();
|
|
@@ -721,6 +755,7 @@ export class SessionManager {
|
|
|
721
755
|
* Issue #840/#880: Atomically acquires the session under a mutex to prevent TOCTOU race. */
|
|
722
756
|
async findIdleSessionByWorkDir(workDir) {
|
|
723
757
|
return this.sessionAcquireMutex.runExclusive(async () => {
|
|
758
|
+
await maybeInjectFault('session.findIdleSessionByWorkDir.start');
|
|
724
759
|
const candidates = Object.values(this.state.sessions).filter((s) => s.workDir === workDir && s.status === 'idle');
|
|
725
760
|
if (candidates.length === 0)
|
|
726
761
|
return null;
|
|
@@ -728,6 +763,7 @@ export class SessionManager {
|
|
|
728
763
|
candidates.sort((a, b) => b.lastActivity - a.lastActivity);
|
|
729
764
|
// Issue #636: verify tmux window exists before returning
|
|
730
765
|
for (const candidate of candidates) {
|
|
766
|
+
await maybeInjectFault('session.findIdleSessionByWorkDir.windowExists');
|
|
731
767
|
if (await this.tmux.windowExists(candidate.windowId)) {
|
|
732
768
|
// Issue #840: Mark session as acquired immediately to prevent
|
|
733
769
|
// concurrent callers from grabbing the same session
|
|
@@ -1303,13 +1339,7 @@ export class SessionManager {
|
|
|
1303
1339
|
if (!existsSync(this.sessionMapFile))
|
|
1304
1340
|
return;
|
|
1305
1341
|
try {
|
|
1306
|
-
const
|
|
1307
|
-
const parsed = sessionMapSchema.safeParse(JSON.parse(raw));
|
|
1308
|
-
if (!parsed.success) {
|
|
1309
|
-
console.warn('session_map.json failed validation in cleanSessionMapForWindow');
|
|
1310
|
-
return;
|
|
1311
|
-
}
|
|
1312
|
-
const mapData = parsed.data;
|
|
1342
|
+
const mapData = await loadContinuationPointers(this.sessionMapFile, this.config.continuationPointerTtlMs);
|
|
1313
1343
|
let changed = false;
|
|
1314
1344
|
for (const [key, info] of Object.entries(mapData)) {
|
|
1315
1345
|
// Clean by window_name (original behavior)
|
|
@@ -1341,13 +1371,7 @@ export class SessionManager {
|
|
|
1341
1371
|
if (!existsSync(this.sessionMapFile))
|
|
1342
1372
|
return;
|
|
1343
1373
|
try {
|
|
1344
|
-
const
|
|
1345
|
-
const parsed = sessionMapSchema.safeParse(JSON.parse(raw));
|
|
1346
|
-
if (!parsed.success) {
|
|
1347
|
-
console.warn('session_map.json failed validation in purgeStaleSessionMapEntries');
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1350
|
-
const mapData = parsed.data;
|
|
1374
|
+
const mapData = await loadContinuationPointers(this.sessionMapFile, this.config.continuationPointerTtlMs);
|
|
1351
1375
|
let changed = false;
|
|
1352
1376
|
const activeNamesLower = new Set([...activeWindowNames].map(n => n.toLowerCase()));
|
|
1353
1377
|
for (const [key, info] of Object.entries(mapData)) {
|
|
@@ -1480,13 +1504,7 @@ export class SessionManager {
|
|
|
1480
1504
|
if (!existsSync(this.sessionMapFile))
|
|
1481
1505
|
return;
|
|
1482
1506
|
try {
|
|
1483
|
-
const
|
|
1484
|
-
const mapParsed = sessionMapSchema.safeParse(JSON.parse(mapRaw));
|
|
1485
|
-
if (!mapParsed.success) {
|
|
1486
|
-
console.warn('session_map.json failed validation in syncSessionMap');
|
|
1487
|
-
return;
|
|
1488
|
-
}
|
|
1489
|
-
const mapData = mapParsed.data;
|
|
1507
|
+
const mapData = await loadContinuationPointers(this.sessionMapFile, this.config.continuationPointerTtlMs);
|
|
1490
1508
|
for (const session of Object.values(this.state.sessions)) {
|
|
1491
1509
|
if (session.claudeSessionId)
|
|
1492
1510
|
continue;
|
package/dist/tmux.js
CHANGED
|
@@ -15,8 +15,8 @@ import { randomBytes } from 'node:crypto';
|
|
|
15
15
|
function shellEscape(s) {
|
|
16
16
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
17
17
|
}
|
|
18
|
-
/** Validate that an env var key contains only safe characters. */
|
|
19
|
-
const ENV_KEY_RE = /^[A-
|
|
18
|
+
/** Validate that an env var key contains only safe characters (Issue #630: uppercase only, aligned with session.ts). */
|
|
19
|
+
const ENV_KEY_RE = /^[A-Z_][A-Z0-9_]*$/;
|
|
20
20
|
const execFileAsync = promisify(execFile);
|
|
21
21
|
/** Default timeout for tmux commands (ms). Prevents hung commands from blocking the server. */
|
|
22
22
|
const TMUX_DEFAULT_TIMEOUT_MS = 10_000;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export class CircularBuffer {
|
|
2
|
+
capacity;
|
|
3
|
+
items;
|
|
4
|
+
head = 0;
|
|
5
|
+
count = 0;
|
|
6
|
+
constructor(capacity) {
|
|
7
|
+
this.capacity = capacity;
|
|
8
|
+
if (!Number.isInteger(capacity) || capacity <= 0) {
|
|
9
|
+
throw new Error(`CircularBuffer capacity must be a positive integer, got: ${capacity}`);
|
|
10
|
+
}
|
|
11
|
+
this.items = new Array(capacity);
|
|
12
|
+
}
|
|
13
|
+
push(item) {
|
|
14
|
+
this.items[this.head] = item;
|
|
15
|
+
this.head = (this.head + 1) % this.capacity;
|
|
16
|
+
if (this.count < this.capacity) {
|
|
17
|
+
this.count++;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
toArray() {
|
|
21
|
+
if (this.count === 0) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
if (this.count < this.capacity) {
|
|
25
|
+
return this.items.slice(0, this.count);
|
|
26
|
+
}
|
|
27
|
+
return [...this.items.slice(this.head), ...this.items.slice(0, this.head)];
|
|
28
|
+
}
|
|
29
|
+
clear() {
|
|
30
|
+
this.items.fill(undefined);
|
|
31
|
+
this.head = 0;
|
|
32
|
+
this.count = 0;
|
|
33
|
+
}
|
|
34
|
+
size() {
|
|
35
|
+
return this.count;
|
|
36
|
+
}
|
|
37
|
+
}
|
package/dist/validation.d.ts
CHANGED
|
@@ -111,6 +111,12 @@ export declare const pipelineSchema: z.ZodObject<{
|
|
|
111
111
|
autoApprove: z.ZodOptional<z.ZodBoolean>;
|
|
112
112
|
}, z.core.$strip>>;
|
|
113
113
|
}, z.core.$strict>;
|
|
114
|
+
/** POST /v1/handshake */
|
|
115
|
+
export declare const handshakeRequestSchema: z.ZodObject<{
|
|
116
|
+
protocolVersion: z.ZodString;
|
|
117
|
+
clientCapabilities: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
118
|
+
clientVersion: z.ZodOptional<z.ZodString>;
|
|
119
|
+
}, z.core.$strict>;
|
|
114
120
|
/** Clamp a numeric value to [min, max]. Returns default if input is NaN. */
|
|
115
121
|
export declare function clamp(value: number, min: number, max: number, fallback: number): number;
|
|
116
122
|
/** Parse an env string to integer with NaN/isFinite guard. Returns fallback on failure. */
|
|
@@ -165,6 +171,21 @@ export declare const persistedStateSchema: z.ZodRecord<z.ZodString, z.ZodObject<
|
|
|
165
171
|
lastDeadAt: z.ZodOptional<z.ZodNumber>;
|
|
166
172
|
ccPid: z.ZodOptional<z.ZodNumber>;
|
|
167
173
|
}, z.core.$strip>>;
|
|
174
|
+
/** Schema for a single continuation pointer entry in session_map.json (Issue #900). */
|
|
175
|
+
export declare const sessionMapEntrySchema: z.ZodObject<{
|
|
176
|
+
session_id: z.ZodString;
|
|
177
|
+
cwd: z.ZodString;
|
|
178
|
+
window_name: z.ZodString;
|
|
179
|
+
transcript_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
180
|
+
permission_mode: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
181
|
+
agent_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
182
|
+
source: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
183
|
+
agent_type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
184
|
+
model: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
185
|
+
written_at: z.ZodNumber;
|
|
186
|
+
schema_version: z.ZodOptional<z.ZodNumber>;
|
|
187
|
+
expires_at: z.ZodOptional<z.ZodNumber>;
|
|
188
|
+
}, z.core.$strip>;
|
|
168
189
|
/** Schema for session_map.json entries. */
|
|
169
190
|
export declare const sessionMapSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
170
191
|
session_id: z.ZodString;
|
|
@@ -177,6 +198,8 @@ export declare const sessionMapSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
|
177
198
|
agent_type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
178
199
|
model: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
179
200
|
written_at: z.ZodNumber;
|
|
201
|
+
schema_version: z.ZodOptional<z.ZodNumber>;
|
|
202
|
+
expires_at: z.ZodOptional<z.ZodNumber>;
|
|
180
203
|
}, z.core.$strip>>;
|
|
181
204
|
/** Schema for stop_signals.json entries. */
|
|
182
205
|
export declare const stopSignalsSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
@@ -247,6 +270,17 @@ export declare const ccSettingsSchema: z.ZodObject<{
|
|
|
247
270
|
}, z.core.$loose>;
|
|
248
271
|
/** Helper: extract error message from unknown catch value. */
|
|
249
272
|
export declare function getErrorMessage(e: unknown): string;
|
|
273
|
+
/** Minimum supported Claude Code version. */
|
|
274
|
+
export declare const MIN_CC_VERSION = "2.1.80";
|
|
275
|
+
/** Parse a semver string into [major, minor, patch], or null if invalid. */
|
|
276
|
+
export declare function parseSemver(v: string): [number, number, number] | null;
|
|
277
|
+
/**
|
|
278
|
+
* Compare two semver strings.
|
|
279
|
+
* Returns -1 if a < b, 0 if equal or either is unparseable (fails open), 1 if a > b.
|
|
280
|
+
*/
|
|
281
|
+
export declare function compareSemver(a: string, b: string): number;
|
|
282
|
+
/** Extract version number from `claude --version` output. */
|
|
283
|
+
export declare function extractCCVersion(output: string): string | null;
|
|
250
284
|
/** Validate workDir to prevent path traversal attacks (Issue #435).
|
|
251
285
|
* 1. Reject raw strings containing ".." before any normalization.
|
|
252
286
|
* 2. Resolve to absolute path and resolve symlinks via fs.realpath().
|
package/dist/validation.js
CHANGED
|
@@ -100,6 +100,12 @@ export const pipelineSchema = z.object({
|
|
|
100
100
|
workDir: z.string().min(1),
|
|
101
101
|
stages: z.array(pipelineStageSchema).min(1),
|
|
102
102
|
}).strict();
|
|
103
|
+
/** POST /v1/handshake */
|
|
104
|
+
export const handshakeRequestSchema = z.object({
|
|
105
|
+
protocolVersion: z.string().min(1),
|
|
106
|
+
clientCapabilities: z.array(z.string().min(1)).optional(),
|
|
107
|
+
clientVersion: z.string().min(1).optional(),
|
|
108
|
+
}).strict();
|
|
103
109
|
/** Clamp a numeric value to [min, max]. Returns default if input is NaN. */
|
|
104
110
|
export function clamp(value, min, max, fallback) {
|
|
105
111
|
if (Number.isNaN(value))
|
|
@@ -153,8 +159,8 @@ export const persistedStateSchema = z.record(z.string(), z.object({
|
|
|
153
159
|
lastDeadAt: z.number().optional(),
|
|
154
160
|
ccPid: z.number().optional(),
|
|
155
161
|
}));
|
|
156
|
-
/** Schema for session_map.json
|
|
157
|
-
export const
|
|
162
|
+
/** Schema for a single continuation pointer entry in session_map.json (Issue #900). */
|
|
163
|
+
export const sessionMapEntrySchema = z.object({
|
|
158
164
|
session_id: z.string(),
|
|
159
165
|
cwd: z.string(),
|
|
160
166
|
window_name: z.string(),
|
|
@@ -165,7 +171,11 @@ export const sessionMapSchema = z.record(z.string(), z.object({
|
|
|
165
171
|
agent_type: z.string().nullable().optional(),
|
|
166
172
|
model: z.string().nullable().optional(),
|
|
167
173
|
written_at: z.number(),
|
|
168
|
-
|
|
174
|
+
schema_version: z.number().int().positive().optional(),
|
|
175
|
+
expires_at: z.number().optional(),
|
|
176
|
+
});
|
|
177
|
+
/** Schema for session_map.json entries. */
|
|
178
|
+
export const sessionMapSchema = z.record(z.string(), sessionMapEntrySchema);
|
|
169
179
|
/** Schema for stop_signals.json entries. */
|
|
170
180
|
export const stopSignalsSchema = z.record(z.string(), z.object({
|
|
171
181
|
event: z.string().optional(),
|
|
@@ -235,6 +245,38 @@ export function getErrorMessage(e) {
|
|
|
235
245
|
return e;
|
|
236
246
|
return String(e);
|
|
237
247
|
}
|
|
248
|
+
// ── CC version validation (Issue #564) ─────────────────────────────────
|
|
249
|
+
/** Minimum supported Claude Code version. */
|
|
250
|
+
export const MIN_CC_VERSION = '2.1.80';
|
|
251
|
+
/** Parse a semver string into [major, minor, patch], or null if invalid. */
|
|
252
|
+
export function parseSemver(v) {
|
|
253
|
+
const match = v.trim().match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
254
|
+
if (!match)
|
|
255
|
+
return null;
|
|
256
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Compare two semver strings.
|
|
260
|
+
* Returns -1 if a < b, 0 if equal or either is unparseable (fails open), 1 if a > b.
|
|
261
|
+
*/
|
|
262
|
+
export function compareSemver(a, b) {
|
|
263
|
+
const pa = parseSemver(a);
|
|
264
|
+
const pb = parseSemver(b);
|
|
265
|
+
if (!pa || !pb)
|
|
266
|
+
return 0;
|
|
267
|
+
for (let i = 0; i < 3; i++) {
|
|
268
|
+
if (pa[i] < pb[i])
|
|
269
|
+
return -1;
|
|
270
|
+
if (pa[i] > pb[i])
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
/** Extract version number from `claude --version` output. */
|
|
276
|
+
export function extractCCVersion(output) {
|
|
277
|
+
const match = output.match(/(\d+\.\d+\.\d+)/);
|
|
278
|
+
return match ? match[1] : null;
|
|
279
|
+
}
|
|
238
280
|
/** Default safe base directories used when allowedWorkDirs is not configured.
|
|
239
281
|
* Prevents sessions from running in system-critical directories. */
|
|
240
282
|
function getDefaultSafeDirs() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aegis-bridge",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Orchestrate Claude Code sessions via API. Create, brief, monitor, refine, ship.",
|
|
6
6
|
"main": "dist/server.js",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"start": "node dist/cli.js",
|
|
31
31
|
"dev": "tsc && node dist/cli.js",
|
|
32
32
|
"prepublishOnly": "npm run build:dashboard && npm run build",
|
|
33
|
-
"test": "vitest run"
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:fault-harness": "vitest run src/__tests__/fault-injection-harness-901.test.ts"
|
|
34
35
|
},
|
|
35
36
|
"keywords": [
|
|
36
37
|
"claude",
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
3
|
-
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
|
4
|
-
* https://github.com/chjj/term.js
|
|
5
|
-
* @license MIT
|
|
6
|
-
*
|
|
7
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
-
* in the Software without restriction, including without limitation the rights
|
|
10
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
-
* furnished to do so, subject to the following conditions:
|
|
13
|
-
*
|
|
14
|
-
* The above copyright notice and this permission notice shall be included in
|
|
15
|
-
* all copies or substantial portions of the Software.
|
|
16
|
-
*
|
|
17
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
-
* THE SOFTWARE.
|
|
24
|
-
*
|
|
25
|
-
* Originally forked from (with the author's permission):
|
|
26
|
-
* Fabrice Bellard's javascript vt100 for jslinux:
|
|
27
|
-
* http://bellard.org/jslinux/
|
|
28
|
-
* Copyright (c) 2011 Fabrice Bellard
|
|
29
|
-
* The original design remains. The terminal itself
|
|
30
|
-
* has been extended to include xterm CSI codes, among
|
|
31
|
-
* other features.
|
|
32
|
-
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */@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-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-divide-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}}}@layer theme{:root,:host{--font-sans:system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-mono:"JetBrains Mono", "Fira Code", "Cascadia Code", "Consolas", monospace;--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-amber-500:oklch(76.9% .188 70.08);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-900:oklch(42.1% .095 57.708);--color-yellow-950:oklch(28.6% .066 53.813);--color-green-200:oklch(92.5% .084 155.995);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-900:oklch(39.3% .095 152.535);--color-green-950:oklch(26.6% .065 152.934);--color-emerald-400:oklch(76.5% .177 163.223);--color-cyan-200:oklch(91.7% .08 205.041);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-950:oklch(30.2% .056 229.695);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-black:#000;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-2xl:42rem;--container-6xl:72rem;--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-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-sm:8px;--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-void:#0a0a0f;--color-void-light:#12121a;--color-void-lighter:#1a1a2e;--color-cyan:#00e5ff;--color-cyan-dim:#00e5ff40;--color-warning:#fa0;--color-error:#f36}}@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%;-moz-tab-size:4;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]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.right-0{right:calc(var(--spacing) * 0)}.right-4{right:calc(var(--spacing) * 4)}.bottom-0{bottom:calc(var(--spacing) * 0)}.bottom-4{bottom:calc(var(--spacing) * 4)}.left-0{left:calc(var(--spacing) * 0)}.z-10{z-index:10}.z-50{z-index:50}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-4{margin-inline:calc(var(--spacing) * 4)}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.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)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-0\.5{height:calc(var(--spacing) * .5)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-10{height:calc(var(--spacing) * 10)}.h-48{height:calc(var(--spacing) * 48)}.h-\[calc\(100vh-380px\)\]{height:calc(100vh - 380px)}.h-\[calc\(100vh-420px\)\]{height:calc(100vh - 420px)}.h-full{height:100%}.h-screen{height:100vh}.max-h-32{max-height:calc(var(--spacing) * 32)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-\[90vh\]{max-height:90vh}.max-h-\[300px\]{max-height:300px}.max-h-\[360px\]{max-height:360px}.min-h-\[44px\]{min-height:44px}.min-h-\[50vh\]{min-height:50vh}.min-h-\[88px\]{min-height:88px}.min-h-\[250px\]{min-height:250px}.min-h-\[300px\]{min-height:300px}.min-h-screen{min-height:100vh}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-10{width:calc(var(--spacing) * 10)}.w-16{width:calc(var(--spacing) * 16)}.w-56{width:calc(var(--spacing) * 56)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-\[80\%\]{max-width:80%}.max-w-\[160px\]{max-width:160px}.max-w-\[200px\]{max-width:200px}.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:calc(var(--spacing) * 0)}.min-w-\[44px\]{min-width:44px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-none{resize:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[1fr_120px_1fr_44px\]{grid-template-columns:1fr 120px 1fr 44px}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-1{gap:calc(var(--spacing) * 1)}.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-6{gap:calc(var(--spacing) * 6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * 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)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-\[\#1a1a2e\]\/50>:not(:last-child)){border-color:#1a1a2e80}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-br-sm{border-bottom-right-radius:var(--radius-sm)}.rounded-bl-sm{border-bottom-left-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-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-\[\#00e5ff\]\/30{border-color:#00e5ff4d}.border-\[\#00e5ff\]\/40{border-color:#00e5ff66}.border-\[\#00ff88\]\/30{border-color:#00ff884d}.border-\[\#1a1a2e\]{border-color:#1a1a2e}.border-\[\#1a1a2e\]\/50{border-color:#1a1a2e80}.border-\[\#ff3366\]\/20{border-color:#f363}.border-\[\#ff3366\]\/30{border-color:#ff33664d}.border-\[\#ff3366\]\/40{border-color:#f366}.border-\[\#ff336620\]{border-color:#ff336620}.border-\[\#ffaa00\]\/40{border-color:#fa06}.border-cyan-500\/50{border-color:#00b7d780}@supports (color:color-mix(in lab,red,red)){.border-cyan-500\/50{border-color:color-mix(in oklab,var(--color-cyan-500) 50%,transparent)}}.border-cyan\/30{border-color:#00e5ff4d}@supports (color:color-mix(in lab,red,red)){.border-cyan\/30{border-color:color-mix(in oklab,var(--color-cyan) 30%,transparent)}}.border-emerald-400\/20{border-color:#00d29433}@supports (color:color-mix(in lab,red,red)){.border-emerald-400\/20{border-color:color-mix(in oklab,var(--color-emerald-400) 20%,transparent)}}.border-emerald-400\/30{border-color:#00d2944d}@supports (color:color-mix(in lab,red,red)){.border-emerald-400\/30{border-color:color-mix(in oklab,var(--color-emerald-400) 30%,transparent)}}.border-gray-500\/30{border-color:#6a72824d}@supports (color:color-mix(in lab,red,red)){.border-gray-500\/30{border-color:color-mix(in oklab,var(--color-gray-500) 30%,transparent)}}.border-green-500\/50{border-color:#00c75880}@supports (color:color-mix(in lab,red,red)){.border-green-500\/50{border-color:color-mix(in oklab,var(--color-green-500) 50%,transparent)}}.border-red-400\/30{border-color:#ff65684d}@supports (color:color-mix(in lab,red,red)){.border-red-400\/30{border-color:color-mix(in oklab,var(--color-red-400) 30%,transparent)}}.border-red-500\/50{border-color:#fb2c3680}@supports (color:color-mix(in lab,red,red)){.border-red-500\/50{border-color:color-mix(in oklab,var(--color-red-500) 50%,transparent)}}.border-void-lighter{border-color:var(--color-void-lighter)}.border-void-lighter\/50{border-color:#1a1a2e80}@supports (color:color-mix(in lab,red,red)){.border-void-lighter\/50{border-color:color-mix(in oklab,var(--color-void-lighter) 50%,transparent)}}.border-yellow-500\/50{border-color:#edb20080}@supports (color:color-mix(in lab,red,red)){.border-yellow-500\/50{border-color:color-mix(in oklab,var(--color-yellow-500) 50%,transparent)}}.bg-\[\#0a0a0f\]{background-color:#0a0a0f}.bg-\[\#0d0d12\]{background-color:#0d0d12}.bg-\[\#00e5ff\]{background-color:#00e5ff}.bg-\[\#00e5ff\]\/10{background-color:#00e5ff1a}.bg-\[\#1a1a00\]\/60{background-color:#1a1a0099}.bg-\[\#1a1a2e\]{background-color:#1a1a2e}.bg-\[\#1a1a3e\]{background-color:#1a1a3e}.bg-\[\#003322\]{background-color:#032}.bg-\[\#003322\]\/50{background-color:#00332280}.bg-\[\#111118\]{background-color:#111118}.bg-\[\#331111\]{background-color:#311}.bg-\[\#ff3366\]\/10{background-color:#ff33661a}.bg-\[\#ff336610\]{background-color:#ff336610}.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-current{background-color:currentColor}.bg-cyan-950\/80{background-color:#053345cc}@supports (color:color-mix(in lab,red,red)){.bg-cyan-950\/80{background-color:color-mix(in oklab,var(--color-cyan-950) 80%,transparent)}}.bg-cyan\/10{background-color:#00e5ff1a}@supports (color:color-mix(in lab,red,red)){.bg-cyan\/10{background-color:color-mix(in oklab,var(--color-cyan) 10%,transparent)}}.bg-emerald-400\/10{background-color:#00d2941a}@supports (color:color-mix(in lab,red,red)){.bg-emerald-400\/10{background-color:color-mix(in oklab,var(--color-emerald-400) 10%,transparent)}}.bg-gray-500\/10{background-color:#6a72821a}@supports (color:color-mix(in lab,red,red)){.bg-gray-500\/10{background-color:color-mix(in oklab,var(--color-gray-500) 10%,transparent)}}.bg-green-900\/30{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.bg-green-900\/30{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.bg-green-950\/80{background-color:#032e15cc}@supports (color:color-mix(in lab,red,red)){.bg-green-950\/80{background-color:color-mix(in oklab,var(--color-green-950) 80%,transparent)}}.bg-red-400\/10{background-color:#ff65681a}@supports (color:color-mix(in lab,red,red)){.bg-red-400\/10{background-color:color-mix(in oklab,var(--color-red-400) 10%,transparent)}}.bg-red-900\/30{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.bg-red-900\/30{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.bg-red-950\/80{background-color:#460809cc}@supports (color:color-mix(in lab,red,red)){.bg-red-950\/80{background-color:color-mix(in oklab,var(--color-red-950) 80%,transparent)}}.bg-void{background-color:var(--color-void)}.bg-void-light{background-color:var(--color-void-light)}.bg-void-lighter{background-color:var(--color-void-lighter)}.bg-yellow-900\/30{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.bg-yellow-900\/30{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.bg-yellow-950\/80{background-color:#432004cc}@supports (color:color-mix(in lab,red,red)){.bg-yellow-950\/80{background-color:color-mix(in oklab,var(--color-yellow-950) 80%,transparent)}}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-2{padding:calc(var(--spacing) * 2)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.p-12{padding:calc(var(--spacing) * 12)}.px-1{padding-inline:calc(var(--spacing) * 1)}.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-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.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-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pb-0{padding-bottom:calc(var(--spacing) * 0)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.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-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.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-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#00e5ff\]{color:#00e5ff}.text-\[\#00ff88\]{color:#0f8}.text-\[\#333\]{color:#333}.text-\[\#444\]{color:#444}.text-\[\#555\]{color:#555}.text-\[\#666\]{color:#666}.text-\[\#888\]{color:#888}.text-\[\#e0e0e0\]{color:#e0e0e0}.text-\[\#ff3366\]{color:#f36}.text-\[\#ffaa00\]{color:#fa0}.text-amber-500{color:var(--color-amber-500)}.text-cyan{color:var(--color-cyan)}.text-cyan-200{color:var(--color-cyan-200)}.text-emerald-400{color:var(--color-emerald-400)}.text-gray-100{color:var(--color-gray-100)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-green-200{color:var(--color-green-200)}.text-green-400{color:var(--color-green-400)}.text-red-200{color:var(--color-red-200)}.text-red-400{color:var(--color-red-400)}.text-yellow-200{color:var(--color-yellow-200)}.text-yellow-400{color:var(--color-yellow-400)}.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,)}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.placeholder-gray-600::placeholder{color:var(--color-gray-600)}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.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)}.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)}.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-sm{--tw-backdrop-blur:blur(var(--blur-sm));-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-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-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}@media(hover:hover){.hover\:border-l-2:hover{border-left-style:var(--tw-border-style);border-left-width:2px}.hover\:border-\[\#00e5ff\]\/30:hover{border-color:#00e5ff4d}.hover\:border-l-cyan:hover{border-left-color:var(--color-cyan)}.hover\:bg-\[\#00e5ff\]\/20:hover{background-color:#00e5ff33}.hover\:bg-\[\#1a1a2e\]\/30:hover{background-color:#1a1a2e4d}.hover\:bg-\[\#2a2a3e\]:hover{background-color:#2a2a3e}.hover\:bg-\[\#004433\]:hover{background-color:#043}.hover\:bg-\[\#442222\]:hover{background-color:#422}.hover\:bg-green-900\/50:hover{background-color:#0d542b80}@supports (color:color-mix(in lab,red,red)){.hover\:bg-green-900\/50:hover{background-color:color-mix(in oklab,var(--color-green-900) 50%,transparent)}}.hover\:bg-red-900\/50:hover{background-color:#82181a80}@supports (color:color-mix(in lab,red,red)){.hover\:bg-red-900\/50:hover{background-color:color-mix(in oklab,var(--color-red-900) 50%,transparent)}}.hover\:bg-void-lighter:hover{background-color:var(--color-void-lighter)}.hover\:bg-yellow-900\/50:hover{background-color:#733e0a80}@supports (color:color-mix(in lab,red,red)){.hover\:bg-yellow-900\/50:hover{background-color:color-mix(in oklab,var(--color-yellow-900) 50%,transparent)}}.hover\:text-\[\#00e5ff\]:hover{color:#00e5ff}.hover\:text-\[\#777\]:hover{color:#777}.hover\:text-\[\#888\]:hover{color:#888}.hover\:text-\[\#e0e0e0\]:hover{color:#e0e0e0}.hover\:text-\[\#ff3366\]:hover{color:#f36}.hover\:text-cyan:hover{color:var(--color-cyan)}.hover\:text-gray-200:hover{color:var(--color-gray-200)}.hover\:text-gray-300:hover{color:var(--color-gray-300)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}}.focus\:border-\[\#00e5ff\]:focus{border-color:#00e5ff}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:bg-\[\#1a1a2e\]\/50:active{background-color:#1a1a2e80}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:40rem){.sm\:flex{display:flex}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:h-\[calc\(100vh-420px\)\]{height:calc(100vh - 420px)}.sm\:h-\[calc\(100vh-460px\)\]{height:calc(100vh - 460px)}.sm\:min-h-\[300px\]{min-height:300px}.sm\:min-h-\[400px\]{min-height:400px}.sm\:max-w-xs{max-width:var(--container-xs)}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:gap-2{gap:calc(var(--spacing) * 2)}.sm\:gap-3{gap:calc(var(--spacing) * 3)}.sm\:gap-4{gap:calc(var(--spacing) * 4)}:where(.sm\:space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.sm\:p-4{padding:calc(var(--spacing) * 4)}.sm\:p-5{padding:calc(var(--spacing) * 5)}.sm\:px-4{padding-inline:calc(var(--spacing) * 4)}.sm\:px-5{padding-inline:calc(var(--spacing) * 5)}.sm\:py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.sm\:py-4{padding-block:calc(var(--spacing) * 4)}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}}@media(min-width:48rem){.md\:block{display:block}.md\:hidden{display:none}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:64rem){.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}}}body{font-family:var(--font-sans);background-color:var(--color-void);color:#e0e0e8}*{scrollbar-width:thin;scrollbar-color:var(--color-void-lighter) transparent}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--color-void-lighter);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--color-cyan-dim)}.status-dot{border-radius:50%;flex-shrink:0;width:8px;height:8px;display:inline-block}.status-dot--idle{background-color:var(--color-cyan);box-shadow:0 0 6px var(--color-cyan-dim)}.status-dot--working{background-color:var(--color-warning);animation:1.5s ease-in-out infinite pulse;box-shadow:0 0 6px #ffaa0040}.status-dot--permission_prompt,.status-dot--bash_approval{background-color:var(--color-error);animation:1s ease-in-out infinite pulse;box-shadow:0 0 6px #ff336640}.status-dot--plan_mode,.status-dot--ask_question{background-color:#a78bfa;box-shadow:0 0 6px #a78bfa40}.status-dot--settings{background-color:var(--color-warning)}.status-dot--unknown{background-color:#666}@keyframes pulse{50%{opacity:.5}}.font-code{font-family:var(--font-mono)}.glow-cyan{box-shadow:0 0 12px var(--color-cyan-dim)}.glow-text-cyan{text-shadow:0 0 8px var(--color-cyan-dim)}@keyframes slide-in{0%{opacity:0;transform:translate(100%)}to{opacity:1;transform:translate(0)}}.animate-slide-in{animation:.2s ease-out slide-in}@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-divide-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}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}
|