awel 0.1.0 → 0.1.1
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 +9 -0
- package/README.zh-CN.md +107 -0
- package/dist/cli/agent.js +11 -0
- package/dist/cli/comment-popup.js +50 -26
- package/dist/cli/providers/vercel.js +18 -4
- package/dist/cli/subprocess.js +76 -14
- package/dist/dashboard/assets/index-B374_cjZ.css +1 -0
- package/dist/dashboard/assets/{index-Bk--q3wu.js → index-BJ6wUfxa.js} +89 -84
- package/dist/dashboard/index.html +2 -2
- package/dist/host/host.js +62 -47
- package/package.json +1 -2
- package/dist/dashboard/assets/index-DkWV03So.css +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Awel
|
|
2
2
|
|
|
3
|
+
English | [中文](./README.zh-CN.md)
|
|
4
|
+
|
|
3
5
|
AI-powered development overlay for Next.js. Awel runs a proxy in front of your dev server, injects a floating chat button into your app, and lets you talk to an AI agent that can read, write, and edit files in your project — all from an embedded dashboard.
|
|
4
6
|
|
|
5
7
|

|
|
@@ -71,6 +73,13 @@ The AI agent has access to:
|
|
|
71
73
|
## Features
|
|
72
74
|
|
|
73
75
|
- **Element inspector** — click the crosshair icon to select an element in your app and attach it as context to your prompt
|
|
76
|
+
|
|
77
|
+

|
|
78
|
+
|
|
79
|
+
- **Screenshot annotator** — annotate screenshots with shapes, arrows, and text before sending to the agent
|
|
80
|
+
|
|
81
|
+

|
|
82
|
+
|
|
74
83
|
- **Image attachments** — attach screenshots or reference images
|
|
75
84
|
- **Plan approval** — the agent can propose plans for you to review before making changes
|
|
76
85
|
- **Undo** — roll back all file changes from an agent session in one click
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Awel
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | 中文
|
|
4
|
+
|
|
5
|
+
为 Next.js 打造的 AI 开发助手。Awel 在你的开发服务器前运行一个代理,向页面注入一个悬浮聊天按钮,让你通过内嵌的面板与 AI 智能体对话——它可以读取、编写和编辑项目中的文件。
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## 快速开始
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 如果你已经在一个 Next.js 项目中,可以跳过这一步
|
|
13
|
+
npx create-next-app@latest my-app && cd my-app
|
|
14
|
+
|
|
15
|
+
npx awel dev
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Awel 会在端口 3001 启动,并代理运行在端口 3000 的 Next.js 开发服务器。打开 `http://localhost:3001` 即可看到带有 Awel 浮层的应用。
|
|
19
|
+
|
|
20
|
+
### 选项
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
awel dev [options]
|
|
24
|
+
|
|
25
|
+
-p, --port <port> 目标应用端口(默认:3000)
|
|
26
|
+
-v, --verbose 将 LLM 流式事件输出到 stderr
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 工作原理
|
|
30
|
+
|
|
31
|
+
Awel 位于浏览器和开发服务器之间:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
浏览器 → Awel(代理 :3001)→ 你的应用(开发服务器 :3000)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
1. Awel 拦截 HTML 响应,注入一段脚本
|
|
38
|
+
2. 脚本在页面右下角渲染一个悬浮按钮(通过 Shadow DOM 隔离)
|
|
39
|
+
3. 点击按钮会打开一个全屏聊天面板(iframe)
|
|
40
|
+
4. 描述你的需求——AI 智能体会读取代码、进行编辑、执行命令,并实时流式返回结果
|
|
41
|
+
|
|
42
|
+
HMR / WebSocket 流量会透明代理,在智能体编辑文件期间暂停,以防止热重载干扰。
|
|
43
|
+
|
|
44
|
+
## 支持的模型
|
|
45
|
+
|
|
46
|
+
Awel 使用 [Vercel AI SDK](https://sdk.vercel.ai),支持多个服务商。设置对应的环境变量即可启用:
|
|
47
|
+
|
|
48
|
+
| 服务商 | 环境变量 | 示例模型 |
|
|
49
|
+
|--------|----------|----------|
|
|
50
|
+
| Claude Code | PATH 中有 Claude CLI | sonnet, opus, haiku |
|
|
51
|
+
| Anthropic API | `ANTHROPIC_API_KEY` | claude-sonnet-4-5, claude-opus-4-5 |
|
|
52
|
+
| OpenAI | `OPENAI_API_KEY` | gpt-5.2-codex, gpt-5.1-codex |
|
|
53
|
+
| Google AI | `GOOGLE_GENERATIVE_AI_API_KEY` | gemini-3-pro-preview, gemini-2.5-pro |
|
|
54
|
+
| 通义千问 | `DASHSCOPE_API_KEY` | qwen-max, qwen-plus-latest |
|
|
55
|
+
| MiniMax | `MINIMAX_API_KEY` | MiniMax-M2 |
|
|
56
|
+
| Vercel Gateway | `AI_GATEWAY_API_KEY` | 通过网关访问多种模型 |
|
|
57
|
+
|
|
58
|
+
可随时在面板顶部的下拉菜单中切换模型。
|
|
59
|
+
|
|
60
|
+
## 智能体工具
|
|
61
|
+
|
|
62
|
+
AI 智能体可使用以下工具:
|
|
63
|
+
|
|
64
|
+
- **Read** / **Write** / **Edit** / **MultiEdit** — 文件操作
|
|
65
|
+
- **Bash** — 执行 Shell 命令
|
|
66
|
+
- **Glob** / **Grep** / **CodeSearch** — 查找文件和搜索代码
|
|
67
|
+
- **WebSearch** / **WebFetch** — 网络搜索
|
|
68
|
+
- **ProposePlan** — 提出多步骤实施计划,等待你审批后再执行
|
|
69
|
+
- **AskUser** — 在执行过程中向你提问
|
|
70
|
+
- **RestartDevServer** — 配置变更后重启开发服务器
|
|
71
|
+
- **TodoRead** / **TodoWrite** — 跨对话的任务管理
|
|
72
|
+
|
|
73
|
+
## 功能特性
|
|
74
|
+
|
|
75
|
+
- **元素检查器** — 点击十字准星图标,在应用中选择一个元素,自动作为上下文附加到提示中
|
|
76
|
+
|
|
77
|
+

|
|
78
|
+
|
|
79
|
+
- **截图标注** — 用图形、箭头和文字标注截图后发送给智能体
|
|
80
|
+
|
|
81
|
+

|
|
82
|
+
|
|
83
|
+
- **图片附件** — 附加截图或参考图片
|
|
84
|
+
- **计划审批** — 智能体可以提出计划,由你审核后再执行变更
|
|
85
|
+
- **撤销** — 一键回滚整个智能体会话的所有文件变更
|
|
86
|
+
- **Diff 审查** — 在接受变更前查看所有文件修改的摘要
|
|
87
|
+
- **深色模式** — 跟随系统偏好
|
|
88
|
+
- **国际化** — 支持英文和中文
|
|
89
|
+
|
|
90
|
+
## 开发
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm run build # 构建所有模块
|
|
94
|
+
npm run dev # 监听模式(仅 CLI)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
单独构建:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm run build:cli # TypeScript → dist/cli/
|
|
101
|
+
npm run build:dashboard # Vite → dist/dashboard/
|
|
102
|
+
npm run build:host # esbuild → dist/host/host.js
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 许可证
|
|
106
|
+
|
|
107
|
+
MIT
|
package/dist/cli/agent.js
CHANGED
|
@@ -164,6 +164,13 @@ export function createAgentRoute(projectCwd, targetPort) {
|
|
|
164
164
|
appendUserMessage(userContent);
|
|
165
165
|
appendResponseMessages(responseMessages);
|
|
166
166
|
}
|
|
167
|
+
})
|
|
168
|
+
.catch((err) => {
|
|
169
|
+
// Swallow provider-level rejections (e.g. API 400 from tool-use
|
|
170
|
+
// concurrency in Claude Code). The error has already been surfaced
|
|
171
|
+
// as an SSE error event inside streamResponse.
|
|
172
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
173
|
+
console.error(`[awel] streamResponse rejected: ${msg}`);
|
|
167
174
|
})
|
|
168
175
|
.finally(() => {
|
|
169
176
|
if (!signal.aborted) {
|
|
@@ -262,5 +269,9 @@ export function createAgentRoute(projectCwd, targetPort) {
|
|
|
262
269
|
agent.get('/api/dev-server/status', (c) => {
|
|
263
270
|
return c.json(getDevServerStatus());
|
|
264
271
|
});
|
|
272
|
+
// ─── Project Info ───────────────────────────────────────
|
|
273
|
+
agent.get('/api/project-info', (c) => {
|
|
274
|
+
return c.json({ projectCwd });
|
|
275
|
+
});
|
|
265
276
|
return agent;
|
|
266
277
|
}
|
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
const
|
|
3
|
-
|
|
2
|
+
const i18n = {
|
|
3
|
+
en: { placeholder: 'Describe what you want to change...', cancel: 'Cancel', send: 'Send' },
|
|
4
|
+
zh: { placeholder: '描述您想进行的更改...', cancel: '取消', send: '发送' },
|
|
5
|
+
};
|
|
6
|
+
function getCommentPopupHtml(lang, theme) {
|
|
7
|
+
const t = i18n[lang] ?? i18n.en;
|
|
8
|
+
const isDark = theme !== 'light';
|
|
9
|
+
return `<!DOCTYPE html>
|
|
10
|
+
<html lang="en" data-theme="${isDark ? 'dark' : 'light'}">
|
|
4
11
|
<head>
|
|
5
12
|
<meta charset="UTF-8">
|
|
6
13
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
14
|
<style>
|
|
15
|
+
:root[data-theme="dark"] {
|
|
16
|
+
--bg: #18181b; --fg: #fafafa;
|
|
17
|
+
--title: #e4e4e7; --muted: #a1a1aa; --dim: #71717a; --sep: #52525b;
|
|
18
|
+
--input-bg: #09090b; --input-border: #27272a; --input-focus: #a1a1aa;
|
|
19
|
+
--btn-bg: #27272a; --btn-fg: #a1a1aa; --btn-hover-bg: #3f3f46; --btn-hover-fg: #fafafa;
|
|
20
|
+
--primary-bg: #fafafa; --primary-border: #fafafa; --primary-fg: #18181b; --primary-hover: #e4e4e7;
|
|
21
|
+
}
|
|
22
|
+
:root[data-theme="light"] {
|
|
23
|
+
--bg: #ffffff; --fg: #18181b;
|
|
24
|
+
--title: #27272a; --muted: #71717a; --dim: #a1a1aa; --sep: #d4d4d8;
|
|
25
|
+
--input-bg: #f4f4f5; --input-border: #d4d4d8; --input-focus: #71717a;
|
|
26
|
+
--btn-bg: #e4e4e7; --btn-fg: #71717a; --btn-hover-bg: #d4d4d8; --btn-hover-fg: #18181b;
|
|
27
|
+
--primary-bg: #18181b; --primary-border: #18181b; --primary-fg: #fafafa; --primary-hover: #27272a;
|
|
28
|
+
}
|
|
8
29
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
30
|
body {
|
|
10
31
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
11
|
-
background:
|
|
12
|
-
color:
|
|
32
|
+
background: var(--bg);
|
|
33
|
+
color: var(--fg);
|
|
13
34
|
padding: 0 12px 12px;
|
|
14
35
|
height: 100vh;
|
|
15
36
|
display: flex;
|
|
@@ -30,7 +51,7 @@ const commentPopupHtml = `<!DOCTYPE html>
|
|
|
30
51
|
.header-title {
|
|
31
52
|
font-size: 12px;
|
|
32
53
|
font-weight: 600;
|
|
33
|
-
color:
|
|
54
|
+
color: var(--title);
|
|
34
55
|
}
|
|
35
56
|
.element-info {
|
|
36
57
|
margin-left: auto;
|
|
@@ -38,7 +59,7 @@ const commentPopupHtml = `<!DOCTYPE html>
|
|
|
38
59
|
align-items: center;
|
|
39
60
|
gap: 4px;
|
|
40
61
|
font-size: 11px;
|
|
41
|
-
color:
|
|
62
|
+
color: var(--muted);
|
|
42
63
|
overflow: hidden;
|
|
43
64
|
}
|
|
44
65
|
.element-name {
|
|
@@ -49,10 +70,10 @@ const commentPopupHtml = `<!DOCTYPE html>
|
|
|
49
70
|
max-width: 100px;
|
|
50
71
|
}
|
|
51
72
|
.element-sep {
|
|
52
|
-
color:
|
|
73
|
+
color: var(--sep);
|
|
53
74
|
}
|
|
54
75
|
.element-file {
|
|
55
|
-
color:
|
|
76
|
+
color: var(--dim);
|
|
56
77
|
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
57
78
|
font-size: 10px;
|
|
58
79
|
white-space: nowrap;
|
|
@@ -63,10 +84,10 @@ const commentPopupHtml = `<!DOCTYPE html>
|
|
|
63
84
|
textarea {
|
|
64
85
|
flex: 1;
|
|
65
86
|
width: 100%;
|
|
66
|
-
background:
|
|
67
|
-
border: 1px solid
|
|
87
|
+
background: var(--input-bg);
|
|
88
|
+
border: 1px solid var(--input-border);
|
|
68
89
|
border-radius: 6px;
|
|
69
|
-
color:
|
|
90
|
+
color: var(--fg);
|
|
70
91
|
font-family: inherit;
|
|
71
92
|
font-size: 13px;
|
|
72
93
|
padding: 8px 10px;
|
|
@@ -75,10 +96,10 @@ const commentPopupHtml = `<!DOCTYPE html>
|
|
|
75
96
|
transition: border-color 0.15s ease;
|
|
76
97
|
}
|
|
77
98
|
textarea:focus {
|
|
78
|
-
border-color:
|
|
99
|
+
border-color: var(--input-focus);
|
|
79
100
|
}
|
|
80
101
|
textarea::placeholder {
|
|
81
|
-
color:
|
|
102
|
+
color: var(--dim);
|
|
82
103
|
}
|
|
83
104
|
.buttons {
|
|
84
105
|
display: flex;
|
|
@@ -92,26 +113,26 @@ const commentPopupHtml = `<!DOCTYPE html>
|
|
|
92
113
|
font-weight: 500;
|
|
93
114
|
padding: 6px 14px;
|
|
94
115
|
border-radius: 6px;
|
|
95
|
-
border: 1px solid
|
|
116
|
+
border: 1px solid var(--input-border);
|
|
96
117
|
cursor: pointer;
|
|
97
118
|
transition: all 0.15s ease;
|
|
98
119
|
}
|
|
99
120
|
button:active { transform: scale(0.97); }
|
|
100
121
|
.btn-close {
|
|
101
|
-
background:
|
|
102
|
-
color:
|
|
122
|
+
background: var(--btn-bg);
|
|
123
|
+
color: var(--btn-fg);
|
|
103
124
|
}
|
|
104
125
|
.btn-close:hover {
|
|
105
|
-
background:
|
|
106
|
-
color:
|
|
126
|
+
background: var(--btn-hover-bg);
|
|
127
|
+
color: var(--btn-hover-fg);
|
|
107
128
|
}
|
|
108
129
|
.btn-submit {
|
|
109
|
-
background:
|
|
110
|
-
border-color:
|
|
111
|
-
color:
|
|
130
|
+
background: var(--primary-bg);
|
|
131
|
+
border-color: var(--primary-border);
|
|
132
|
+
color: var(--primary-fg);
|
|
112
133
|
}
|
|
113
134
|
.btn-submit:hover {
|
|
114
|
-
background:
|
|
135
|
+
background: var(--primary-hover);
|
|
115
136
|
}
|
|
116
137
|
.btn-submit:disabled {
|
|
117
138
|
opacity: 0.4;
|
|
@@ -135,10 +156,10 @@ const commentPopupHtml = `<!DOCTYPE html>
|
|
|
135
156
|
</div>
|
|
136
157
|
<div class="element-info" id="elementInfo"></div>
|
|
137
158
|
</div>
|
|
138
|
-
<textarea id="comment" placeholder="
|
|
159
|
+
<textarea id="comment" placeholder="${t.placeholder}" autofocus></textarea>
|
|
139
160
|
<div class="buttons">
|
|
140
|
-
<button class="btn-close" id="closeBtn"
|
|
141
|
-
<button class="btn-submit" id="submitBtn" disabled
|
|
161
|
+
<button class="btn-close" id="closeBtn">${t.cancel}</button>
|
|
162
|
+
<button class="btn-submit" id="submitBtn" disabled>${t.send}<kbd id="shortcutHint"></kbd></button>
|
|
142
163
|
</div>
|
|
143
164
|
<script>
|
|
144
165
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -197,10 +218,13 @@ const commentPopupHtml = `<!DOCTYPE html>
|
|
|
197
218
|
</script>
|
|
198
219
|
</body>
|
|
199
220
|
</html>`;
|
|
221
|
+
}
|
|
200
222
|
export function createCommentPopupRoute() {
|
|
201
223
|
const app = new Hono();
|
|
202
224
|
app.get('/_awel/comment-popup', (c) => {
|
|
203
|
-
|
|
225
|
+
const lang = c.req.query('lang') ?? 'en';
|
|
226
|
+
const theme = c.req.query('theme') ?? 'dark';
|
|
227
|
+
return c.html(getCommentPopupHtml(lang, theme));
|
|
204
228
|
});
|
|
205
229
|
return app;
|
|
206
230
|
}
|
|
@@ -63,7 +63,10 @@ Inspector Context:
|
|
|
63
63
|
- CRITICAL: Focus your changes on the specific selected tag, NOT the entire parent component. The rendered HTML attributes help you locate the exact JSX element in the source code.
|
|
64
64
|
- Use the parent component source code only as context to find and modify the specific tag.
|
|
65
65
|
- Prioritize addressing what the user sees: if props are undefined/null, investigate why.
|
|
66
|
-
- Reference the specific line numbers from the context when making edits
|
|
66
|
+
- Reference the specific line numbers from the context when making edits.
|
|
67
|
+
|
|
68
|
+
Language:
|
|
69
|
+
- IMPORTANT: Always respond in the same language the user writes in. If the user writes in Chinese, respond in Chinese. If the user writes in English, respond in English. Match the user's language throughout the conversation.`;
|
|
67
70
|
/** Detects files Claude Code uses for plan output (.claude/plans/*.md, plan.md) */
|
|
68
71
|
function isPlanFile(filePath) {
|
|
69
72
|
const normalized = filePath.replace(/\\/g, '/');
|
|
@@ -96,6 +99,7 @@ function createModel(modelId, providerType, cwd) {
|
|
|
96
99
|
permissionMode: 'acceptEdits',
|
|
97
100
|
streamingInput: 'always',
|
|
98
101
|
maxTurns: 25,
|
|
102
|
+
appendSystemPrompt: 'IMPORTANT: Always respond in the same language the user writes in. If the user writes in Chinese, respond in Chinese. If the user writes in English, respond in English. Match the user\'s language throughout the conversation.',
|
|
99
103
|
});
|
|
100
104
|
}
|
|
101
105
|
else if (providerType === 'anthropic') {
|
|
@@ -427,10 +431,20 @@ export function createVercelProvider(modelId, providerType) {
|
|
|
427
431
|
}
|
|
428
432
|
}
|
|
429
433
|
catch (err) {
|
|
430
|
-
// Ignore abort errors from user-input pauses or external cancellation
|
|
434
|
+
// Ignore abort errors from user-input pauses or external cancellation.
|
|
435
|
+
// For other errors (e.g. transient API 400s from tool-use concurrency),
|
|
436
|
+
// log and surface them as SSE error events instead of killing the stream.
|
|
431
437
|
const externallyAborted = config.signal?.aborted;
|
|
432
|
-
if (!waitingForUserInput && !externallyAborted)
|
|
433
|
-
|
|
438
|
+
if (!waitingForUserInput && !externallyAborted) {
|
|
439
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
440
|
+
logEvent('error', `stream error (non-fatal): ${errorMsg}`);
|
|
441
|
+
const errorData = JSON.stringify({
|
|
442
|
+
type: 'error',
|
|
443
|
+
message: errorMsg
|
|
444
|
+
});
|
|
445
|
+
addToHistory('error', errorData);
|
|
446
|
+
await stream.writeSSE({ event: 'error', data: errorData });
|
|
447
|
+
}
|
|
434
448
|
}
|
|
435
449
|
// Capture response messages for multi-turn accumulation.
|
|
436
450
|
// Skip only when externally aborted (new request cancelled this one).
|
package/dist/cli/subprocess.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Handles spawning, restarting, health checks, and auto-restart on crash.
|
|
4
4
|
*/
|
|
5
5
|
import { execa } from 'execa';
|
|
6
|
+
import { watch } from 'node:fs';
|
|
6
7
|
import { awel, pipeChildOutput } from './logger.js';
|
|
7
8
|
const state = {
|
|
8
9
|
process: null,
|
|
@@ -14,6 +15,59 @@ const state = {
|
|
|
14
15
|
lastError: null,
|
|
15
16
|
};
|
|
16
17
|
let autoRestartEnabled = true;
|
|
18
|
+
let crashWatcher = null;
|
|
19
|
+
let crashDebounceTimer = null;
|
|
20
|
+
const SOURCE_EXT_RE = /\.(ts|tsx|js|jsx|json|css|scss|html|mdx?)$/;
|
|
21
|
+
const IGNORE_RE = /(?:^|[/\\])(?:node_modules|\.next|\.git|dist|build)[/\\]/;
|
|
22
|
+
/**
|
|
23
|
+
* Start watching the project directory for file changes while the server is
|
|
24
|
+
* crashed. When a source file is modified (e.g. the agent fixed the bug),
|
|
25
|
+
* restart immediately instead of waiting for the next backoff tick.
|
|
26
|
+
*/
|
|
27
|
+
function startCrashWatcher() {
|
|
28
|
+
if (crashWatcher)
|
|
29
|
+
return;
|
|
30
|
+
crashWatcher = watch(state.cwd, { recursive: true }, (_event, filename) => {
|
|
31
|
+
if (!filename)
|
|
32
|
+
return;
|
|
33
|
+
if (IGNORE_RE.test(filename))
|
|
34
|
+
return;
|
|
35
|
+
if (!SOURCE_EXT_RE.test(filename))
|
|
36
|
+
return;
|
|
37
|
+
if (state.status !== 'crashed')
|
|
38
|
+
return;
|
|
39
|
+
// Debounce — agents often write multiple files in quick succession
|
|
40
|
+
if (crashDebounceTimer)
|
|
41
|
+
clearTimeout(crashDebounceTimer);
|
|
42
|
+
crashDebounceTimer = setTimeout(async () => {
|
|
43
|
+
if (state.status !== 'crashed')
|
|
44
|
+
return;
|
|
45
|
+
awel.log(`File changed (${filename}), restarting dev server...`);
|
|
46
|
+
stopCrashWatcher();
|
|
47
|
+
try {
|
|
48
|
+
state.restartCount = 0;
|
|
49
|
+
await doSpawn();
|
|
50
|
+
awel.log('Dev server restarted after file change.');
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
54
|
+
awel.error(`Restart after file change failed: ${msg}`);
|
|
55
|
+
// Re-enable watcher for the next change
|
|
56
|
+
startCrashWatcher();
|
|
57
|
+
}
|
|
58
|
+
}, 500);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function stopCrashWatcher() {
|
|
62
|
+
if (crashDebounceTimer) {
|
|
63
|
+
clearTimeout(crashDebounceTimer);
|
|
64
|
+
crashDebounceTimer = null;
|
|
65
|
+
}
|
|
66
|
+
if (crashWatcher) {
|
|
67
|
+
crashWatcher.close();
|
|
68
|
+
crashWatcher = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
17
71
|
/**
|
|
18
72
|
* Wait for the dev server to respond on its port.
|
|
19
73
|
* Polls with 500ms intervals up to the timeout.
|
|
@@ -59,7 +113,8 @@ function getBackoffDelay(restartCount) {
|
|
|
59
113
|
return delay;
|
|
60
114
|
}
|
|
61
115
|
/**
|
|
62
|
-
* Handle process exit — auto-restart
|
|
116
|
+
* Handle process exit — attempt one auto-restart, then switch to file-watch
|
|
117
|
+
* mode so the server restarts as soon as the agent (or user) saves a fix.
|
|
63
118
|
*/
|
|
64
119
|
function attachExitHandler(child) {
|
|
65
120
|
child.catch(async (error) => {
|
|
@@ -72,26 +127,33 @@ function attachExitHandler(child) {
|
|
|
72
127
|
if (!autoRestartEnabled)
|
|
73
128
|
return;
|
|
74
129
|
state.restartCount++;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
130
|
+
// First crash: try one immediate auto-restart (could be a transient issue)
|
|
131
|
+
if (state.restartCount <= 1) {
|
|
132
|
+
const delay = getBackoffDelay(state.restartCount);
|
|
133
|
+
awel.log(`Auto-restarting dev server in ${delay}ms (attempt ${state.restartCount})...`);
|
|
134
|
+
await new Promise(r => setTimeout(r, delay));
|
|
135
|
+
if (state.status !== 'crashed')
|
|
136
|
+
return;
|
|
137
|
+
try {
|
|
138
|
+
await doSpawn();
|
|
139
|
+
awel.log('Dev server auto-restarted successfully.');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
144
|
+
awel.error(`Auto-restart failed: ${msg}`);
|
|
145
|
+
}
|
|
88
146
|
}
|
|
147
|
+
// Repeated crashes: stop retrying blindly, watch for file changes instead
|
|
148
|
+
awel.log('Watching for file changes to restart dev server...');
|
|
149
|
+
startCrashWatcher();
|
|
89
150
|
});
|
|
90
151
|
}
|
|
91
152
|
/**
|
|
92
153
|
* Internal spawn + health-check routine.
|
|
93
154
|
*/
|
|
94
155
|
async function doSpawn() {
|
|
156
|
+
stopCrashWatcher();
|
|
95
157
|
state.status = 'starting';
|
|
96
158
|
const child = spawn(state.port, state.cwd);
|
|
97
159
|
state.process = child;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}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;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--background: 0 0% 100%;--foreground: 0 0% 4%;--card: 0 0% 100%;--card-foreground: 0 0% 4%;--primary: 0 0% 9%;--primary-foreground: 0 0% 98%;--secondary: 0 0% 96%;--secondary-foreground: 0 0% 9%;--muted: 0 0% 96%;--muted-foreground: 0 0% 45%;--accent: 0 0% 96%;--accent-foreground: 0 0% 9%;--border: 0 0% 90%;--input: 0 0% 90%;--ring: 0 0% 64%;--radius: .5rem;--scrollbar-thumb: rgba(0, 0, 0, .15);--scrollbar-thumb-hover: rgba(0, 0, 0, .25)}.dark{--background: 0 0% 4%;--foreground: 0 0% 98%;--card: 0 0% 7%;--card-foreground: 0 0% 98%;--primary: 0 0% 98%;--primary-foreground: 0 0% 9%;--secondary: 0 0% 15%;--secondary-foreground: 0 0% 98%;--muted: 0 0% 15%;--muted-foreground: 0 0% 64%;--accent: 0 0% 15%;--accent-foreground: 0 0% 98%;--border: 0 0% 15%;--input: 0 0% 15%;--ring: 0 0% 80%;--scrollbar-thumb: rgba(255, 255, 255, .15);--scrollbar-thumb-hover: rgba(255, 255, 255, .25)}*{--tw-border-opacity: 1;border-color:hsl(var(--border) / var(--tw-border-opacity, 1))}html,body,#root{width:100%;height:100%;margin:0;padding:0;overflow:hidden}body{background-color:transparent;--tw-text-opacity: 1;color:hsl(var(--foreground) / var(--tw-text-opacity, 1));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.-right-1\.5{right:-.375rem}.-top-1\.5{top:-.375rem}.bottom-20{bottom:5rem}.bottom-4{bottom:1rem}.bottom-full{bottom:100%}.left-0{left:0}.left-1\/2{left:50%}.left-4{left:1rem}.left-6{left:1.5rem}.right-0{right:0}.right-2{right:.5rem}.right-4{right:1rem}.right-6{right:1.5rem}.top-0{top:0}.top-1\/2{top:50%}.top-2{top:.5rem}.top-4{top:1rem}.top-6{top:1.5rem}.top-full{top:100%}.z-50{z-index:50}.z-\[60\]{z-index:60}.z-\[9999\]{z-index:9999}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-4{margin-left:1rem;margin-right:1rem}.my-0\.5{margin-top:.125rem;margin-bottom:.125rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-1\.5{margin-top:.375rem;margin-bottom:.375rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.mb-0\.5{margin-bottom:.125rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.ml-0\.5{margin-left:.125rem}.ml-1\.5{margin-left:.375rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mr-4{margin-right:1rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.hidden{display:none}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-48{max-height:12rem}.max-h-64{max-height:16rem}.max-h-\[140px\]{max-height:140px}.max-h-\[200px\]{max-height:200px}.max-h-\[70vh\]{max-height:70vh}.max-h-\[80vh\]{max-height:80vh}.max-h-\[90vh\]{max-height:90vh}.min-h-0{min-height:0px}.min-h-\[36px\]{min-height:36px}.min-h-full{min-height:100%}.w-1\.5{width:.375rem}.w-10{width:2.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-\[380px\]{width:380px}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.min-w-0{min-width:0px}.max-w-2xl{max-width:42rem}.max-w-\[10rem\]{max-width:10rem}.max-w-\[160px\]{max-width:160px}.max-w-\[180px\]{max-width:180px}.max-w-\[200px\]{max-width:200px}.max-w-\[220px\]{max-width:220px}.max-w-\[250px\]{max-width:250px}.max-w-\[85\%\]{max-width:85%}.max-w-\[90vw\]{max-width:90vw}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.grow{flex-grow:1}.-translate-x-1\/2{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.self-end{align-self:flex-end}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-y{border-top-width:1px;border-bottom-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-amber-500\/50{border-color:#f59e0b80}.border-blue-500\/50{border-color:#3b82f680}.border-border{--tw-border-opacity: 1;border-color:hsl(var(--border) / var(--tw-border-opacity, 1))}.border-border\/50{border-color:hsl(var(--border) / .5)}.border-border\/60{border-color:hsl(var(--border) / .6)}.border-cyan-500\/50{border-color:#06b6d480}.border-input{--tw-border-opacity: 1;border-color:hsl(var(--input) / var(--tw-border-opacity, 1))}.border-muted\/50{border-color:hsl(var(--muted) / .5)}.border-orange-500\/50{border-color:#f9731680}.border-purple-500\/50{border-color:#a855f780}.border-red-300\/60{border-color:#fca5a599}.border-red-500\/50{border-color:#ef444480}.border-violet-400{--tw-border-opacity: 1;border-color:rgb(167 139 250 / var(--tw-border-opacity, 1))}.border-violet-500{--tw-border-opacity: 1;border-color:rgb(139 92 246 / var(--tw-border-opacity, 1))}.border-violet-500\/70{border-color:#8b5cf6b3}.border-violet-600\/60{border-color:#7c3aed99}.border-violet-700\/50{border-color:#6d28d980}.border-yellow-300\/60{border-color:#fde04799}.bg-amber-100\/60{background-color:#fef3c799}.bg-amber-600{--tw-bg-opacity: 1;background-color:rgb(217 119 6 / var(--tw-bg-opacity, 1))}.bg-background{--tw-bg-opacity: 1;background-color:hsl(var(--background) / var(--tw-bg-opacity, 1))}.bg-black\/60{background-color:#0009}.bg-black\/80{background-color:#000c}.bg-blue-100\/60{background-color:#dbeafe99}.bg-card{--tw-bg-opacity: 1;background-color:hsl(var(--card) / var(--tw-bg-opacity, 1))}.bg-card\/50{background-color:hsl(var(--card) / .5)}.bg-card\/60{background-color:hsl(var(--card) / .6)}.bg-card\/80{background-color:hsl(var(--card) / .8)}.bg-emerald-400{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity, 1))}.bg-green-100\/50{background-color:#dcfce780}.bg-green-100\/60{background-color:#dcfce799}.bg-muted{--tw-bg-opacity: 1;background-color:hsl(var(--muted) / var(--tw-bg-opacity, 1))}.bg-muted\/30{background-color:hsl(var(--muted) / .3)}.bg-muted\/40{background-color:hsl(var(--muted) / .4)}.bg-muted\/50{background-color:hsl(var(--muted) / .5)}.bg-muted\/60{background-color:hsl(var(--muted) / .6)}.bg-muted\/80{background-color:hsl(var(--muted) / .8)}.bg-primary{--tw-bg-opacity: 1;background-color:hsl(var(--primary) / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-100\/50{background-color:#fee2e280}.bg-red-100\/60{background-color:#fee2e299}.bg-red-200\/60{background-color:#fecaca99}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-rose-100{--tw-bg-opacity: 1;background-color:rgb(255 228 230 / var(--tw-bg-opacity, 1))}.bg-secondary{--tw-bg-opacity: 1;background-color:hsl(var(--secondary) / var(--tw-bg-opacity, 1))}.bg-violet-500{--tw-bg-opacity: 1;background-color:rgb(139 92 246 / var(--tw-bg-opacity, 1))}.bg-violet-600{--tw-bg-opacity: 1;background-color:rgb(124 58 237 / var(--tw-bg-opacity, 1))}.bg-violet-900\/50{background-color:#4c1d9580}.bg-violet-950\/40{background-color:#2e106566}.bg-violet-950\/50{background-color:#2e106580}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-100{--tw-bg-opacity: 1;background-color:rgb(254 249 195 / var(--tw-bg-opacity, 1))}.bg-yellow-100\/60{background-color:#fef9c399}.bg-yellow-200\/60{background-color:#fef08a99}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pb-0{padding-bottom:0}.pb-1{padding-bottom:.25rem}.pl-2{padding-left:.5rem}.pl-3{padding-left:.75rem}.pr-1{padding-right:.25rem}.pr-7{padding-right:1.75rem}.pt-0{padding-top:0}.pt-1{padding-top:.25rem}.pt-1\.5{padding-top:.375rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-baseline{vertical-align:baseline}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-base{font-size:1rem;line-height:1.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-5{line-height:1.25rem}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-amber-700{--tw-text-opacity: 1;color:rgb(180 83 9 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-blue-700{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.text-card-foreground{--tw-text-opacity: 1;color:hsl(var(--card-foreground) / var(--tw-text-opacity, 1))}.text-current{color:currentColor}.text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity, 1))}.text-cyan-600{--tw-text-opacity: 1;color:rgb(8 145 178 / var(--tw-text-opacity, 1))}.text-emerald-600{--tw-text-opacity: 1;color:rgb(5 150 105 / var(--tw-text-opacity, 1))}.text-foreground{--tw-text-opacity: 1;color:hsl(var(--foreground) / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-green-700{--tw-text-opacity: 1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.text-muted-foreground{--tw-text-opacity: 1;color:hsl(var(--muted-foreground) / var(--tw-text-opacity, 1))}.text-orange-400{--tw-text-opacity: 1;color:rgb(251 146 60 / var(--tw-text-opacity, 1))}.text-orange-600{--tw-text-opacity: 1;color:rgb(234 88 12 / var(--tw-text-opacity, 1))}.text-pink-600{--tw-text-opacity: 1;color:rgb(219 39 119 / var(--tw-text-opacity, 1))}.text-primary{--tw-text-opacity: 1;color:hsl(var(--primary) / var(--tw-text-opacity, 1))}.text-primary-foreground{--tw-text-opacity: 1;color:hsl(var(--primary-foreground) / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-purple-600{--tw-text-opacity: 1;color:rgb(147 51 234 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-red-600\/60{color:#dc262699}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-rose-600{--tw-text-opacity: 1;color:rgb(225 29 72 / var(--tw-text-opacity, 1))}.text-rose-800{--tw-text-opacity: 1;color:rgb(159 18 57 / var(--tw-text-opacity, 1))}.text-secondary-foreground{--tw-text-opacity: 1;color:hsl(var(--secondary-foreground) / var(--tw-text-opacity, 1))}.text-violet-200{--tw-text-opacity: 1;color:rgb(221 214 254 / var(--tw-text-opacity, 1))}.text-violet-300{--tw-text-opacity: 1;color:rgb(196 181 253 / var(--tw-text-opacity, 1))}.text-violet-400{--tw-text-opacity: 1;color:rgb(167 139 250 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-white\/70{color:#ffffffb3}.text-yellow-600{--tw-text-opacity: 1;color:rgb(202 138 4 / var(--tw-text-opacity, 1))}.text-yellow-800{--tw-text-opacity: 1;color:rgb(133 77 14 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.placeholder-muted-foreground::-moz-placeholder{--tw-placeholder-opacity: 1;color:hsl(var(--muted-foreground) / var(--tw-placeholder-opacity, 1))}.placeholder-muted-foreground::placeholder{--tw-placeholder-opacity: 1;color:hsl(var(--muted-foreground) / var(--tw-placeholder-opacity, 1))}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-80{opacity:.8}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-offset-background{--tw-ring-offset-color: hsl(var(--background) / 1)}.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)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.placeholder\:text-muted-foreground::-moz-placeholder{--tw-text-opacity: 1;color:hsl(var(--muted-foreground) / var(--tw-text-opacity, 1))}.placeholder\:text-muted-foreground::placeholder{--tw-text-opacity: 1;color:hsl(var(--muted-foreground) / var(--tw-text-opacity, 1))}.focus-within\:border-ring:focus-within{--tw-border-opacity: 1;border-color:hsl(var(--ring) / var(--tw-border-opacity, 1))}.focus-within\:ring-2:focus-within{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-within\:ring-ring\/50:focus-within{--tw-ring-color: hsl(var(--ring) / .5)}.hover\:border-ring:hover{--tw-border-opacity: 1;border-color:hsl(var(--ring) / var(--tw-border-opacity, 1))}.hover\:bg-accent:hover{--tw-bg-opacity: 1;background-color:hsl(var(--accent) / var(--tw-bg-opacity, 1))}.hover\:bg-accent\/80:hover{background-color:hsl(var(--accent) / .8)}.hover\:bg-amber-200\/60:hover{background-color:#fde68a99}.hover\:bg-amber-500:hover{--tw-bg-opacity: 1;background-color:rgb(245 158 11 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-200\/60:hover{background-color:#bfdbfe99}.hover\:bg-card:hover{--tw-bg-opacity: 1;background-color:hsl(var(--card) / var(--tw-bg-opacity, 1))}.hover\:bg-muted:hover{--tw-bg-opacity: 1;background-color:hsl(var(--muted) / var(--tw-bg-opacity, 1))}.hover\:bg-muted\/50:hover{background-color:hsl(var(--muted) / .5)}.hover\:bg-muted\/60:hover{background-color:hsl(var(--muted) / .6)}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary) / .9)}.hover\:bg-red-200:hover{--tw-bg-opacity: 1;background-color:rgb(254 202 202 / var(--tw-bg-opacity, 1))}.hover\:bg-red-200\/60:hover{background-color:#fecaca99}.hover\:bg-red-500:hover{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.hover\:bg-red-500\/90:hover{background-color:#ef4444e6}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary) / .8)}.hover\:bg-violet-500:hover{--tw-bg-opacity: 1;background-color:rgb(139 92 246 / var(--tw-bg-opacity, 1))}.hover\:bg-yellow-200:hover{--tw-bg-opacity: 1;background-color:rgb(254 240 138 / var(--tw-bg-opacity, 1))}.hover\:bg-yellow-200\/60:hover{background-color:#fef08a99}.hover\:text-accent-foreground:hover{--tw-text-opacity: 1;color:hsl(var(--accent-foreground) / var(--tw-text-opacity, 1))}.hover\:text-amber-800:hover{--tw-text-opacity: 1;color:rgb(146 64 14 / var(--tw-text-opacity, 1))}.hover\:text-blue-800:hover{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.hover\:text-foreground:hover{--tw-text-opacity: 1;color:hsl(var(--foreground) / var(--tw-text-opacity, 1))}.hover\:text-red-700:hover{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.hover\:opacity-80:hover{opacity:.8}.hover\:brightness-125:hover{--tw-brightness: brightness(1.25);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)}.focus\:border-ring:focus{--tw-border-opacity: 1;border-color:hsl(var(--ring) / var(--tw-border-opacity, 1))}.focus\:border-violet-500\/70:focus{border-color:#8b5cf6b3}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: hsl(var(--ring) / var(--tw-ring-opacity, 1))}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width: 2px}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.group\/tip:hover .group-hover\/tip\:visible{visibility:visible}.group:hover .group-hover\:flex{display:flex}.group\/tip:hover .group-hover\/tip\:opacity-100,.group:hover .group-hover\:opacity-100{opacity:1}.dark\:border-red-800\/60:is(.dark *){border-color:#991b1b99}.dark\:border-yellow-800\/60:is(.dark *){border-color:#854d0e99}.dark\:bg-amber-900\/40:is(.dark *){background-color:#78350f66}.dark\:bg-blue-900\/40:is(.dark *){background-color:#1e3a8a66}.dark\:bg-green-900\/20:is(.dark *){background-color:#14532d33}.dark\:bg-green-900\/40:is(.dark *){background-color:#14532d66}.dark\:bg-red-800:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(153 27 27 / var(--tw-bg-opacity, 1))}.dark\:bg-red-800\/60:is(.dark *){background-color:#991b1b99}.dark\:bg-red-800\/80:is(.dark *){background-color:#991b1bcc}.dark\:bg-red-900\/20:is(.dark *){background-color:#7f1d1d33}.dark\:bg-red-950\/30:is(.dark *){background-color:#450a0a4d}.dark\:bg-red-950\/40:is(.dark *){background-color:#450a0a66}.dark\:bg-rose-800\/80:is(.dark *){background-color:#9f1239cc}.dark\:bg-yellow-800:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(133 77 14 / var(--tw-bg-opacity, 1))}.dark\:bg-yellow-800\/60:is(.dark *){background-color:#854d0e99}.dark\:bg-yellow-800\/80:is(.dark *){background-color:#854d0ecc}.dark\:bg-yellow-950\/40:is(.dark *){background-color:#42200666}.dark\:text-amber-300:is(.dark *){--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.dark\:text-blue-300:is(.dark *){--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.dark\:text-blue-400:is(.dark *){--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.dark\:text-cyan-400:is(.dark *){--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity, 1))}.dark\:text-emerald-400:is(.dark *){--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.dark\:text-green-300:is(.dark *){--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity, 1))}.dark\:text-green-400:is(.dark *){--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.dark\:text-orange-400:is(.dark *){--tw-text-opacity: 1;color:rgb(251 146 60 / var(--tw-text-opacity, 1))}.dark\:text-pink-400:is(.dark *){--tw-text-opacity: 1;color:rgb(244 114 182 / var(--tw-text-opacity, 1))}.dark\:text-purple-400:is(.dark *){--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.dark\:text-red-100:is(.dark *){--tw-text-opacity: 1;color:rgb(254 226 226 / var(--tw-text-opacity, 1))}.dark\:text-red-200:is(.dark *){--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.dark\:text-red-300:is(.dark *){--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.dark\:text-red-400:is(.dark *){--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.dark\:text-red-400\/60:is(.dark *){color:#f8717199}.dark\:text-rose-100:is(.dark *){--tw-text-opacity: 1;color:rgb(255 228 230 / var(--tw-text-opacity, 1))}.dark\:text-rose-200:is(.dark *){--tw-text-opacity: 1;color:rgb(254 205 211 / var(--tw-text-opacity, 1))}.dark\:text-rose-400:is(.dark *){--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.dark\:text-yellow-100:is(.dark *){--tw-text-opacity: 1;color:rgb(254 249 195 / var(--tw-text-opacity, 1))}.dark\:text-yellow-200:is(.dark *){--tw-text-opacity: 1;color:rgb(254 240 138 / var(--tw-text-opacity, 1))}.dark\:text-yellow-400:is(.dark *){--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.dark\:text-yellow-400\/60:is(.dark *){color:#facc1599}.dark\:hover\:bg-amber-800\/50:hover:is(.dark *){background-color:#92400e80}.dark\:hover\:bg-blue-800\/50:hover:is(.dark *){background-color:#1e40af80}.dark\:hover\:bg-red-700\/80:hover:is(.dark *){background-color:#b91c1ccc}.dark\:hover\:bg-red-900\/40:hover:is(.dark *){background-color:#7f1d1d66}.dark\:hover\:bg-yellow-700\/80:hover:is(.dark *){background-color:#a16207cc}.dark\:hover\:bg-yellow-900\/40:hover:is(.dark *){background-color:#713f1266}.dark\:hover\:text-amber-200:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity, 1))}.dark\:hover\:text-blue-200:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:hover\:text-red-300:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}
|