@greatlhd/ailo-desktop 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/copy-static.mjs +11 -0
  2. package/dist/browser_control.js +767 -0
  3. package/dist/browser_snapshot.js +174 -0
  4. package/dist/cli.js +36 -0
  5. package/dist/code_executor.js +95 -0
  6. package/dist/config_server.js +658 -0
  7. package/dist/connection_util.js +14 -0
  8. package/dist/constants.js +2 -0
  9. package/dist/desktop_state_store.js +57 -0
  10. package/dist/desktop_types.js +1 -0
  11. package/dist/desktop_verifier.js +40 -0
  12. package/dist/dingtalk-handler.js +173 -0
  13. package/dist/dingtalk-types.js +1 -0
  14. package/dist/email_handler.js +501 -0
  15. package/dist/exec_tool.js +90 -0
  16. package/dist/feishu-handler.js +620 -0
  17. package/dist/feishu-types.js +8 -0
  18. package/dist/feishu-utils.js +162 -0
  19. package/dist/fs_tools.js +398 -0
  20. package/dist/index.js +433 -0
  21. package/dist/mcp/config-manager.js +64 -0
  22. package/dist/mcp/index.js +3 -0
  23. package/dist/mcp/rpc.js +109 -0
  24. package/dist/mcp/session.js +140 -0
  25. package/dist/mcp_manager.js +253 -0
  26. package/dist/mouse_keyboard.js +516 -0
  27. package/dist/qq-handler.js +153 -0
  28. package/dist/qq-types.js +15 -0
  29. package/dist/qq-ws.js +178 -0
  30. package/dist/screenshot.js +271 -0
  31. package/dist/skills_hub.js +212 -0
  32. package/dist/skills_manager.js +103 -0
  33. package/dist/static/AGENTS.md +25 -0
  34. package/dist/static/app.css +539 -0
  35. package/dist/static/app.html +292 -0
  36. package/dist/static/app.js +380 -0
  37. package/dist/static/chat.html +994 -0
  38. package/dist/time_tool.js +22 -0
  39. package/dist/utils.js +15 -0
  40. package/package.json +38 -0
  41. package/src/browser_control.ts +739 -0
  42. package/src/browser_snapshot.ts +196 -0
  43. package/src/cli.ts +44 -0
  44. package/src/code_executor.ts +101 -0
  45. package/src/config_server.ts +723 -0
  46. package/src/connection_util.ts +23 -0
  47. package/src/constants.ts +2 -0
  48. package/src/desktop_state_store.ts +64 -0
  49. package/src/desktop_types.ts +44 -0
  50. package/src/desktop_verifier.ts +45 -0
  51. package/src/dingtalk-types.ts +26 -0
  52. package/src/exec_tool.ts +93 -0
  53. package/src/feishu-handler.ts +722 -0
  54. package/src/feishu-types.ts +66 -0
  55. package/src/feishu-utils.ts +174 -0
  56. package/src/fs_tools.ts +411 -0
  57. package/src/index.ts +474 -0
  58. package/src/mcp/config-manager.ts +85 -0
  59. package/src/mcp/index.ts +7 -0
  60. package/src/mcp/rpc.ts +131 -0
  61. package/src/mcp/session.ts +182 -0
  62. package/src/mcp_manager.ts +273 -0
  63. package/src/mouse_keyboard.ts +526 -0
  64. package/src/qq-types.ts +49 -0
  65. package/src/qq-ws.ts +223 -0
  66. package/src/screenshot.ts +297 -0
  67. package/src/static/app.css +539 -0
  68. package/src/static/app.html +292 -0
  69. package/src/static/app.js +380 -0
  70. package/src/static/chat.html +994 -0
  71. package/src/time_tool.ts +24 -0
  72. package/src/utils.ts +22 -0
  73. package/tsconfig.json +13 -0
@@ -0,0 +1,103 @@
1
+ import { readdir, readFile } from "fs/promises";
2
+ import { join, dirname, resolve } from "path";
3
+ import { fileURLToPath } from "url";
4
+ export class SkillsManager {
5
+ builtinDir;
6
+ constructor(builtinDir) {
7
+ this.builtinDir = builtinDir ?? this.resolveBuiltinDir();
8
+ }
9
+ resolveBuiltinDir() {
10
+ try {
11
+ const thisFile = fileURLToPath(import.meta.url);
12
+ return resolve(dirname(thisFile), "../../..", "skills");
13
+ }
14
+ catch {
15
+ return join(process.cwd(), "skills");
16
+ }
17
+ }
18
+ async listAll() {
19
+ const result = [];
20
+ const entries = await this.scanDir(this.builtinDir);
21
+ for (const [, dir] of entries) {
22
+ const meta = await this.parseSkillMd(dir);
23
+ if (meta)
24
+ result.push(meta);
25
+ }
26
+ return result;
27
+ }
28
+ async getSkill(name) {
29
+ const skillPath = join(this.builtinDir, name, "SKILL.md");
30
+ try {
31
+ const content = await readFile(skillPath, "utf-8");
32
+ return this.parseSkillMdContent(content, name);
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ async scanDir(dir) {
39
+ const result = new Map();
40
+ try {
41
+ const entries = await readdir(dir, { withFileTypes: true });
42
+ for (const entry of entries) {
43
+ if (!entry.isDirectory())
44
+ continue;
45
+ const skillPath = join(dir, entry.name, "SKILL.md");
46
+ try {
47
+ await readFile(skillPath, "utf-8");
48
+ result.set(entry.name, join(dir, entry.name));
49
+ }
50
+ catch { }
51
+ }
52
+ }
53
+ catch { }
54
+ return result;
55
+ }
56
+ async parseSkillMd(dir) {
57
+ try {
58
+ const raw = await readFile(join(dir, "SKILL.md"), "utf-8");
59
+ const dirName = dir.split(/[/\\]/).pop() ?? "skill";
60
+ return this.parseSkillMdContent(raw, dirName);
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ parseSkillMdContent(raw, fallbackName) {
67
+ const trimmed = raw.trim();
68
+ if (!trimmed)
69
+ return { name: fallbackName, description: "", content: "" };
70
+ let name = fallbackName;
71
+ let description = "";
72
+ let content;
73
+ if (trimmed.startsWith("---")) {
74
+ const endIdx = trimmed.indexOf("---", 3);
75
+ if (endIdx > 0) {
76
+ const frontmatter = trimmed.slice(3, endIdx);
77
+ content = trimmed.slice(endIdx + 3).trim();
78
+ for (const line of frontmatter.split("\n")) {
79
+ const colonIdx = line.indexOf(":");
80
+ if (colonIdx < 0)
81
+ continue;
82
+ const key = line.slice(0, colonIdx).trim().toLowerCase();
83
+ const val = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, "");
84
+ if (key === "name" && val)
85
+ name = val;
86
+ if (key === "description" && val)
87
+ description = val;
88
+ }
89
+ }
90
+ else {
91
+ content = trimmed;
92
+ }
93
+ }
94
+ else {
95
+ content = trimmed;
96
+ }
97
+ if (!description) {
98
+ const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"));
99
+ description = firstLine?.trim().slice(0, 200) ?? name;
100
+ }
101
+ return { name, description, content };
102
+ }
103
+ }
@@ -0,0 +1,25 @@
1
+ <!-- Parent: ../AGENTS.md -->
2
+ <!-- Generated: 2026-04-10 | Updated: 2026-04-10 -->
3
+
4
+ # ailo-desktop/src/static
5
+
6
+ ## Purpose
7
+ Web UI static assets for the desktop endpoint config interface.
8
+
9
+ ## Files
10
+
11
+ | File | Description |
12
+ |------|-------------|
13
+ | `app.html` | Main config UI HTML |
14
+ | `app.css` | Config UI styles |
15
+ | `app.js` | Config UI JavaScript |
16
+ | `chat.html` | Chat UI HTML (if separate) |
17
+
18
+ ## For AI Agents
19
+
20
+ ### Working In This Directory
21
+ - These files are copied to `dist/` during build
22
+ - Serve as the web-based configuration UI for desktop endpoint
23
+ - User accesses via browser to configure credentials
24
+
25
+ <!-- MANUAL: -->
@@ -0,0 +1,539 @@
1
+ /* ─── Design Tokens ──────────────────────────────────────────── */
2
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,400&display=swap');
3
+
4
+ :root {
5
+ --bg-base: #0a0a0f;
6
+ --bg-elevated: #12121a;
7
+ --bg-surface: #18181f;
8
+ --bg-surface-2: #1e1e28;
9
+ --bg-muted: #262630;
10
+ --accent: #7c6ff7;
11
+ --accent-light: #9d8fff;
12
+ --accent-soft: rgba(124,111,247,.15);
13
+ --accent-user: #5b9eef;
14
+ --text-primary: #f0f0f5;
15
+ --text-secondary:#9898a8;
16
+ --text-muted: #606070;
17
+ --border: rgba(255,255,255,.08);
18
+ --border-focus: rgba(124,111,247,.5);
19
+ --success: #50d68d;
20
+ --success-soft: rgba(80,214,141,.15);
21
+ --warning: #f5c842;
22
+ --warning-soft: rgba(245,200,66,.15);
23
+ --error: #f57070;
24
+ --error-soft: rgba(245,112,112,.15);
25
+ --radius-sm: 6px;
26
+ --radius-md: 10px;
27
+ --radius-lg: 14px;
28
+ --radius-xl: 20px;
29
+ --sidebar-w: 220px;
30
+ --shadow-sm: 0 2px 8px rgba(0,0,0,.3);
31
+ --shadow-md: 0 8px 32px rgba(0,0,0,.45);
32
+ --transition: .18s ease;
33
+ }
34
+
35
+ /* ─── Reset ──────────────────────────────────────────────────── */
36
+ *,*::before,*::after { box-sizing:border-box; margin:0; padding:0; }
37
+
38
+ body {
39
+ font-family: 'DM Sans','Noto Sans SC',-apple-system,BlinkMacSystemFont,sans-serif;
40
+ background: var(--bg-base);
41
+ color: var(--text-primary);
42
+ height: 100vh;
43
+ overflow: hidden;
44
+ display: flex;
45
+ -webkit-font-smoothing: antialiased;
46
+ font-size: 15px;
47
+ line-height: 1.55;
48
+ background-image:
49
+ radial-gradient(ellipse at 10% 0%, rgba(124,111,247,.06) 0%, transparent 55%),
50
+ radial-gradient(ellipse at 90% 100%, rgba(91,158,239,.04) 0%, transparent 55%);
51
+ }
52
+
53
+ /* ─── Scrollbar ──────────────────────────────────────────────── */
54
+ ::-webkit-scrollbar { width:5px; height:5px; }
55
+ ::-webkit-scrollbar-track { background:transparent; }
56
+ ::-webkit-scrollbar-thumb { background:var(--bg-muted); border-radius:3px; }
57
+ ::-webkit-scrollbar-thumb:hover { background:var(--text-muted); }
58
+
59
+ /* ─── Sidebar ────────────────────────────────────────────────── */
60
+ .sidebar {
61
+ width: var(--sidebar-w);
62
+ height: 100vh;
63
+ background: var(--bg-elevated);
64
+ border-right: 1px solid var(--border);
65
+ display: flex;
66
+ flex-direction: column;
67
+ flex-shrink: 0;
68
+ overflow-y: auto;
69
+ }
70
+
71
+ .sidebar-brand {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 11px;
75
+ padding: 22px 18px 18px;
76
+ border-bottom: 1px solid var(--border);
77
+ }
78
+
79
+ .brand-icon {
80
+ width: 34px; height: 34px;
81
+ background: linear-gradient(135deg,var(--accent),var(--accent-light));
82
+ border-radius: 9px;
83
+ display: flex; align-items: center; justify-content: center;
84
+ flex-shrink: 0;
85
+ box-shadow: 0 4px 12px rgba(124,111,247,.3);
86
+ }
87
+ .brand-icon svg { width:18px; height:18px; color:#fff; }
88
+
89
+ .brand-info { min-width:0; }
90
+ .brand-name {
91
+ font-size: 15px; font-weight: 700; letter-spacing:-.02em;
92
+ background: linear-gradient(135deg,var(--text-primary),var(--accent-light));
93
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
94
+ background-clip: text;
95
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
96
+ }
97
+ .brand-status {
98
+ display: flex; align-items: center; gap: 6px;
99
+ margin-top: 3px; font-size: 12px; color: var(--text-muted);
100
+ }
101
+ .status-dot {
102
+ width: 7px; height: 7px; border-radius: 50%;
103
+ background: var(--text-muted); flex-shrink: 0;
104
+ transition: background var(--transition), box-shadow var(--transition);
105
+ }
106
+ .status-dot.on { background:var(--success); box-shadow:0 0 8px rgba(80,214,141,.5); }
107
+ .status-dot.off { background:var(--error); box-shadow:0 0 8px rgba(245,112,112,.4); }
108
+
109
+ .sidebar-nav {
110
+ flex: 1;
111
+ padding: 10px 10px;
112
+ display: flex;
113
+ flex-direction: column;
114
+ gap: 2px;
115
+ }
116
+
117
+ .nav-section-label {
118
+ font-size: 11px; font-weight: 600; letter-spacing: .08em;
119
+ color: var(--text-muted); text-transform: uppercase;
120
+ padding: 10px 8px 4px;
121
+ }
122
+
123
+ .nav-item {
124
+ display: flex; align-items: center; gap: 10px;
125
+ padding: 9px 10px;
126
+ border-radius: var(--radius-md);
127
+ cursor: pointer;
128
+ font-size: 14px; font-weight: 500; color: var(--text-secondary);
129
+ transition: background var(--transition), color var(--transition);
130
+ user-select: none;
131
+ position: relative;
132
+ border: 1px solid transparent;
133
+ }
134
+ .nav-item svg { width:17px; height:17px; flex-shrink:0; opacity:.75; transition:opacity var(--transition); }
135
+ .nav-item:hover { background:var(--bg-surface); color:var(--text-primary); }
136
+ .nav-item:hover svg { opacity:1; }
137
+ .nav-item.active {
138
+ background: var(--accent-soft);
139
+ color: var(--accent-light);
140
+ border-color: rgba(124,111,247,.2);
141
+ }
142
+ .nav-item.active svg { opacity:1; color:var(--accent-light); }
143
+ .nav-item.active::before {
144
+ content:''; position:absolute; left:-1px; top:25%; bottom:25%;
145
+ width:3px; background:var(--accent); border-radius:0 2px 2px 0;
146
+ }
147
+
148
+ .nav-badge {
149
+ margin-left: auto;
150
+ font-size: 11px; font-weight: 600;
151
+ background: var(--accent-soft); color: var(--accent-light);
152
+ padding: 1px 7px; border-radius: 100px;
153
+ }
154
+ .nav-badge.on { background:var(--success-soft); color:var(--success); }
155
+ .nav-badge.off { background:var(--error-soft); color:var(--error); }
156
+
157
+ /* ─── Main ───────────────────────────────────────────────────── */
158
+ .main {
159
+ flex: 1;
160
+ height: 100vh;
161
+ overflow-y: auto;
162
+ display: flex;
163
+ flex-direction: column;
164
+ }
165
+
166
+ .main-header {
167
+ padding: 24px 32px 0;
168
+ flex-shrink: 0;
169
+ }
170
+ .main-header h1 {
171
+ font-size: 1.35rem; font-weight: 700; letter-spacing:-.02em;
172
+ color: var(--text-primary);
173
+ }
174
+ .main-header p { font-size:14px; color:var(--text-muted); margin-top:4px; }
175
+
176
+ .main-content {
177
+ padding: 20px 32px 32px;
178
+ width: 100%;
179
+ }
180
+
181
+ /* panels */
182
+ .panel { display:none; }
183
+ .panel.active { display:block; }
184
+
185
+ /* ─── Card ───────────────────────────────────────────────────── */
186
+ .card {
187
+ background: var(--bg-elevated);
188
+ border: 1px solid var(--border);
189
+ border-radius: var(--radius-lg);
190
+ padding: 20px 24px;
191
+ margin-bottom: 16px;
192
+ }
193
+ .card-header {
194
+ display: flex; justify-content: space-between; align-items: center;
195
+ margin-bottom: 14px;
196
+ }
197
+ .card-title {
198
+ font-size: 15px; font-weight: 600; color: var(--text-primary);
199
+ display: flex; align-items: center; gap: 9px;
200
+ }
201
+ .card-title svg { width:17px; height:17px; color:var(--accent); opacity:.8; }
202
+ .card-desc { font-size:13px; color:var(--text-muted); margin-top:4px; line-height:1.5; }
203
+
204
+ /* ─── Badge ──────────────────────────────────────────────────── */
205
+ .badge {
206
+ display: inline-flex; align-items:center; gap:5px;
207
+ padding: 3px 10px; border-radius: 100px;
208
+ font-size: 12px; font-weight: 600;
209
+ }
210
+ .badge svg { width:10px; height:10px; }
211
+ .badge.on { background:var(--success-soft); color:var(--success); }
212
+ .badge.off { background:var(--error-soft); color:var(--error); }
213
+ .badge.warning { background:var(--warning-soft); color:var(--warning); }
214
+ .badge.info { background:var(--accent-soft); color:var(--accent-light); }
215
+ .badge.muted { background:rgba(255,255,255,.06); color:var(--text-muted); }
216
+
217
+ /* ─── Status Connection ──────────────────────────────────────── */
218
+ .connection-hero {
219
+ display: flex; align-items: center; gap: 18px;
220
+ padding: 18px 0 14px;
221
+ }
222
+ .connection-icon {
223
+ width: 52px; height: 52px; flex-shrink:0;
224
+ background: linear-gradient(135deg,var(--accent),var(--accent-light));
225
+ border-radius: var(--radius-lg);
226
+ display: flex; align-items:center; justify-content:center;
227
+ box-shadow: 0 6px 20px rgba(124,111,247,.3);
228
+ }
229
+ .connection-icon svg { width:26px; height:26px; color:#fff; }
230
+ .connection-label { font-size:20px; font-weight:700; letter-spacing:-.02em; }
231
+ .connection-sublabel { font-size:13px; color:var(--text-muted); margin-top:3px; }
232
+
233
+ .info-grid {
234
+ display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
235
+ margin-top: 14px;
236
+ }
237
+ .info-item { background:var(--bg-surface); border:1px solid var(--border); border-radius:var(--radius-md); padding:12px 16px; }
238
+ .info-item-label { font-size:12px; color:var(--text-muted); margin-bottom:4px; }
239
+ .info-item-value { font-size:14px; color:var(--text-primary); font-weight:500; font-family:monospace; word-break:break-all; }
240
+
241
+ /* ─── Form ───────────────────────────────────────────────────── */
242
+ .form-group { margin-bottom:14px; }
243
+ .form-group label {
244
+ display: block; font-size:13px; font-weight:500;
245
+ color: var(--text-secondary); margin-bottom: 7px;
246
+ }
247
+ .form-group label span.optional { font-weight:400; color:var(--text-muted); font-size:12px; margin-left:4px; }
248
+
249
+ input, textarea, select {
250
+ width: 100%;
251
+ background: var(--bg-surface);
252
+ border: 1px solid var(--border);
253
+ color: var(--text-primary);
254
+ padding: 10px 14px;
255
+ border-radius: var(--radius-md);
256
+ font-size: 14px;
257
+ font-family: inherit;
258
+ transition: border-color var(--transition), box-shadow var(--transition);
259
+ outline: none;
260
+ }
261
+ input:focus, textarea:focus, select:focus {
262
+ border-color: var(--border-focus);
263
+ box-shadow: 0 0 0 3px rgba(124,111,247,.12);
264
+ }
265
+ textarea { min-height: 88px; resize: vertical; line-height:1.5; }
266
+ select { cursor:pointer; }
267
+ input::placeholder, textarea::placeholder { color:var(--text-muted); }
268
+
269
+ .form-row { display:flex; gap:12px; }
270
+ .form-row .form-group { flex:1; }
271
+
272
+ /* ─── Buttons ────────────────────────────────────────────────── */
273
+ .btn {
274
+ display: inline-flex; align-items: center; gap: 7px;
275
+ padding: 9px 16px;
276
+ border-radius: var(--radius-md);
277
+ border: 1px solid transparent;
278
+ cursor: pointer;
279
+ font-size: 14px; font-weight: 500;
280
+ font-family: inherit;
281
+ transition: all var(--transition);
282
+ white-space: nowrap;
283
+ }
284
+ .btn svg { width:15px; height:15px; flex-shrink:0; }
285
+ .btn:disabled { opacity:.5; cursor:not-allowed; }
286
+ .btn-sm { padding:6px 12px; font-size:13px; }
287
+ .btn-sm svg { width:13px; height:13px; }
288
+
289
+ .btn-primary { background:linear-gradient(135deg,var(--accent),#6a5ff7); color:#fff; box-shadow:0 3px 12px rgba(124,111,247,.35); }
290
+ .btn-primary:hover:not(:disabled) { filter:brightness(1.1); box-shadow:0 5px 18px rgba(124,111,247,.45); transform:translateY(-1px); }
291
+ .btn-primary:active { transform:translateY(0); }
292
+
293
+ .btn-secondary { background:var(--bg-surface-2); color:var(--text-secondary); border-color:var(--border); }
294
+ .btn-secondary:hover:not(:disabled) { background:var(--bg-muted); color:var(--text-primary); border-color:rgba(255,255,255,.12); }
295
+
296
+ .btn-danger { background:var(--error-soft); color:var(--error); border-color:rgba(245,112,112,.25); }
297
+ .btn-danger:hover:not(:disabled) { background:rgba(245,112,112,.25); }
298
+
299
+ .btn-success { background:var(--success-soft); color:var(--success); border-color:rgba(80,214,141,.25); }
300
+ .btn-success:hover:not(:disabled) { background:rgba(80,214,141,.25); }
301
+
302
+ .btn-group { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-top:4px; }
303
+
304
+ /* Loading spinner inside button */
305
+ @keyframes spin { to { transform:rotate(360deg); } }
306
+ .spinner {
307
+ width:14px; height:14px; border:2px solid rgba(255,255,255,.25);
308
+ border-top-color:#fff; border-radius:50%;
309
+ animation: spin .6s linear infinite;
310
+ flex-shrink:0;
311
+ }
312
+
313
+ /* ─── Table ──────────────────────────────────────────────────── */
314
+ .table-wrap { overflow-x: auto; }
315
+ table { width:100%; border-collapse:collapse; font-size:14px; }
316
+ th {
317
+ text-align:left; padding:10px 14px;
318
+ border-bottom:1px solid var(--border);
319
+ color:var(--text-muted); font-weight:500; font-size:13px;
320
+ white-space:nowrap;
321
+ }
322
+ td {
323
+ padding:11px 14px;
324
+ border-bottom:1px solid rgba(255,255,255,.04);
325
+ color:var(--text-primary); vertical-align:middle;
326
+ }
327
+ tr:last-child td { border-bottom:none; }
328
+ tr:hover td { background:rgba(255,255,255,.02); }
329
+
330
+ code {
331
+ font-size:13px; font-family:'SF Mono','Fira Code',monospace;
332
+ background:rgba(0,0,0,.3); padding:2px 7px; border-radius:5px;
333
+ color:var(--accent-light); word-break:break-all;
334
+ }
335
+
336
+ /* ─── Sub-tabs ───────────────────────────────────────────────── */
337
+ .sub-tabs {
338
+ display: flex; gap:4px;
339
+ border-bottom: 1px solid var(--border);
340
+ margin-bottom: 16px; padding-bottom: 0;
341
+ }
342
+ .sub-tab {
343
+ padding: 8px 14px;
344
+ border-radius: var(--radius-md) var(--radius-md) 0 0;
345
+ cursor: pointer; font-size: 13px; font-weight: 500;
346
+ color: var(--text-muted); border: 1px solid transparent;
347
+ border-bottom: none;
348
+ transition: color var(--transition), background var(--transition);
349
+ background: transparent;
350
+ font-family: inherit;
351
+ }
352
+ .sub-tab:hover { color:var(--text-primary); }
353
+ .sub-tab.active {
354
+ color: var(--accent-light);
355
+ background: var(--bg-elevated);
356
+ border-color: var(--border);
357
+ border-bottom-color: var(--bg-elevated);
358
+ margin-bottom: -1px;
359
+ }
360
+ .sub-panel { display:none; }
361
+ .sub-panel.active { display:block; }
362
+
363
+ /* ─── Blueprint viewer ───────────────────────────────────────── */
364
+ .blueprint-tabs { flex-wrap:wrap; }
365
+ .blueprint-content {
366
+ background: var(--bg-base);
367
+ border: 1px solid var(--border);
368
+ border-radius: var(--radius-md);
369
+ padding: 16px 18px;
370
+ font-size: 13px; line-height:1.65;
371
+ white-space: pre-wrap; overflow: auto;
372
+ max-height: 60vh; color: var(--text-secondary);
373
+ font-family:'SF Mono','Fira Code',monospace;
374
+ }
375
+
376
+ /* ─── Skills ─────────────────────────────────────────────────── */
377
+ .skill-item {
378
+ display: flex; justify-content:space-between; align-items:center;
379
+ padding: 13px 0;
380
+ border-bottom: 1px solid rgba(255,255,255,.04);
381
+ }
382
+ .skill-item:last-child { border-bottom:none; }
383
+ .skill-info { min-width:0; flex:1; margin-right:16px; }
384
+ .skill-name { font-size:14px; font-weight:600; display:flex; align-items:center; gap:8px; }
385
+ .skill-desc { font-size:13px; color:var(--text-muted); margin-top:3px; }
386
+ .skill-actions { display:flex; align-items:center; gap:10px; flex-shrink:0; }
387
+
388
+ /* Toggle */
389
+ .toggle { position:relative; width:40px; height:22px; display:inline-block; flex-shrink:0; }
390
+ .toggle input { opacity:0; width:0; height:0; position:absolute; }
391
+ .toggle .slider {
392
+ position:absolute; inset:0; cursor:pointer;
393
+ background:var(--bg-muted); border-radius:11px;
394
+ transition: background var(--transition);
395
+ border: 1px solid var(--border);
396
+ }
397
+ .toggle input:checked + .slider { background:var(--accent); border-color:transparent; }
398
+ .toggle .slider::before {
399
+ content:''; position:absolute;
400
+ height:16px; width:16px; left:2px; bottom:2px;
401
+ background:#fff; border-radius:50%;
402
+ transition: transform var(--transition);
403
+ box-shadow: 0 1px 4px rgba(0,0,0,.4);
404
+ }
405
+ .toggle input:checked + .slider::before { transform:translateX(18px); }
406
+
407
+ /* ─── MCP ────────────────────────────────────────────────────── */
408
+ .input-row { display:flex; gap:8px; align-items:center; }
409
+ .input-row input { flex:1; min-width:0; }
410
+ .remove-btn {
411
+ flex-shrink:0; width:28px; height:28px;
412
+ background:none; border:1px solid var(--border); border-radius:var(--radius-sm);
413
+ color:var(--text-muted); cursor:pointer; font-size:16px; line-height:1;
414
+ display:flex; align-items:center; justify-content:center;
415
+ transition: all var(--transition);
416
+ }
417
+ .remove-btn:hover { border-color:var(--error); color:var(--error); background:var(--error-soft); }
418
+
419
+ /* ─── Env Check ──────────────────────────────────────────────── */
420
+ .env-item {
421
+ display: flex; align-items: center; gap: 14px;
422
+ padding: 14px 0;
423
+ border-bottom: 1px solid rgba(255,255,255,.04);
424
+ }
425
+ .env-item:last-child { border-bottom:none; }
426
+ .env-icon {
427
+ width:40px; height:40px; border-radius:var(--radius-md); flex-shrink:0;
428
+ display:flex; align-items:center; justify-content:center;
429
+ font-size:20px;
430
+ }
431
+ .env-icon.ok { background:var(--success-soft); }
432
+ .env-icon.fail{ background:var(--error-soft); }
433
+ .env-body { flex:1; min-width:0; }
434
+ .env-name { font-size:14px; font-weight:600; display:flex; align-items:center; gap:8px; }
435
+ .env-desc { font-size:13px; color:var(--text-muted); margin-top:2px; }
436
+ .env-detail { font-size:12px; color:var(--success); margin-top:2px; font-family:monospace; }
437
+
438
+ /* ─── Modal ──────────────────────────────────────────────────── */
439
+ .modal-overlay {
440
+ position:fixed; inset:0;
441
+ background:rgba(5,5,10,.8);
442
+ backdrop-filter:blur(16px);
443
+ display:none; align-items:center; justify-content:center;
444
+ z-index:500;
445
+ animation:fadeIn .2s ease;
446
+ }
447
+ .modal-overlay.open { display:flex; }
448
+ @keyframes fadeIn { from{opacity:0} to{opacity:1} }
449
+
450
+ .modal {
451
+ background:var(--bg-elevated);
452
+ border:1px solid var(--border);
453
+ border-radius:var(--radius-xl);
454
+ padding:28px;
455
+ max-width:480px; width:92%;
456
+ box-shadow:var(--shadow-md);
457
+ animation:modalIn .25s ease;
458
+ max-height:92vh; overflow-y:auto;
459
+ }
460
+ .modal.modal-lg { max-width:560px; }
461
+ @keyframes modalIn { from{opacity:0;transform:scale(.96) translateY(10px)} to{opacity:1;transform:scale(1) translateY(0)} }
462
+ .modal-title { font-size:17px; font-weight:700; margin-bottom:6px; color:var(--text-primary); }
463
+ .modal-sub { font-size:13px; color:var(--text-muted); margin-bottom:10px; line-height:1.5; }
464
+ .modal-footer{ display:flex; justify-content:flex-end; gap:8px; margin-top:18px; }
465
+ .modal-market-list {
466
+ list-style:none; padding:0; margin:0 0 16px;
467
+ display:flex; flex-direction:column; gap:7px;
468
+ }
469
+ .modal-market-list li {
470
+ font-size:13px; color:var(--text-secondary); line-height:1.5;
471
+ padding:7px 10px; background:var(--bg-tertiary,rgba(255,255,255,0.04));
472
+ border-radius:6px; border:1px solid var(--border-subtle,rgba(255,255,255,0.07));
473
+ }
474
+ .modal-market-list a {
475
+ font-weight:600; color:var(--accent,#7c6fe0); text-decoration:none;
476
+ }
477
+ .modal-market-list a:hover { text-decoration:underline; }
478
+ .modal-market-fmt {
479
+ font-family:monospace; font-size:11.5px; color:var(--text-muted);
480
+ word-break:break-all;
481
+ }
482
+ .modal-market-name { font-weight:600; color:var(--text-secondary); }
483
+
484
+ /* ─── Toast ──────────────────────────────────────────────────── */
485
+ #toast-container {
486
+ position: fixed; bottom:24px; right:24px;
487
+ display:flex; flex-direction:column; gap:8px;
488
+ z-index:1000; pointer-events:none;
489
+ }
490
+ .toast {
491
+ display:flex; align-items:center; gap:10px;
492
+ background:var(--bg-surface-2); border:1px solid var(--border);
493
+ border-radius:var(--radius-lg);
494
+ padding:12px 18px;
495
+ font-size:14px; font-weight:500; color:var(--text-primary);
496
+ box-shadow:var(--shadow-md);
497
+ animation:toastIn .25s ease;
498
+ pointer-events:auto; max-width:340px;
499
+ }
500
+ .toast svg { width:17px; height:17px; flex-shrink:0; }
501
+ .toast.success { border-color:rgba(80,214,141,.3); }
502
+ .toast.success svg { color:var(--success); }
503
+ .toast.error { border-color:rgba(245,112,112,.3); }
504
+ .toast.error svg { color:var(--error); }
505
+ .toast.info { border-color:rgba(124,111,247,.3); }
506
+ .toast.info svg { color:var(--accent-light); }
507
+ @keyframes toastIn { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
508
+ @keyframes toastOut { from{opacity:1;transform:translateY(0)} to{opacity:0;transform:translateY(8px)} }
509
+ .toast.hiding { animation:toastOut .2s ease forwards; }
510
+
511
+ /* ─── Platform channel config (feishu/dingtalk/qq/email) ─────── */
512
+ .channel-header {
513
+ display:flex; align-items:center; gap:14px; margin-bottom:18px;
514
+ }
515
+ .channel-icon {
516
+ width:44px; height:44px; border-radius:var(--radius-md); flex-shrink:0;
517
+ display:flex; align-items:center; justify-content:center; font-size:22px;
518
+ }
519
+ .channel-title { font-size:16px; font-weight:700; }
520
+ .channel-subtitle { font-size:13px; color:var(--text-muted); margin-top:2px; }
521
+
522
+ /* ─── Webchat embed ──────────────────────────────────────────── */
523
+ .chat-embed {
524
+ width:100%; height:calc(100vh - 40px); min-height:400px;
525
+ border:none; border-radius:0;
526
+ background:var(--bg-base);
527
+ display:block;
528
+ }
529
+
530
+ /* ─── Responsive ─────────────────────────────────────────────── */
531
+ @media (max-width: 700px) {
532
+ :root { --sidebar-w:60px; }
533
+ .nav-item span { display:none; }
534
+ .nav-section-label { display:none; }
535
+ .brand-info { display:none; }
536
+ .sidebar-brand { justify-content:center; padding:16px 0; }
537
+ .main-content { padding:16px; }
538
+ .info-grid { grid-template-columns:1fr; }
539
+ }