claudeck 1.2.0 → 1.3.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 +64 -5
- package/cli.js +53 -4
- package/package.json +3 -2
- package/public/css/core/responsive.css +2 -2
- package/public/css/ui/file-picker.css +243 -17
- package/public/css/ui/messages.css +72 -9
- package/public/css/ui/toolbox.css +43 -0
- package/public/index.html +80 -745
- package/public/js/components/add-project-modal.js +27 -0
- package/public/js/components/agent-modal.js +73 -0
- package/public/js/components/agent-monitor-modal.js +19 -0
- package/public/js/components/bg-confirm-modal.js +22 -0
- package/public/js/components/chain-modal.js +52 -0
- package/public/js/components/cost-dashboard-modal.js +39 -0
- package/public/js/components/dag-editor-modal.js +55 -0
- package/public/js/components/file-picker-modal.js +45 -0
- package/public/js/components/linear-create-modal.js +43 -0
- package/public/js/components/mcp-modal.js +58 -0
- package/public/js/components/orchestrate-modal.js +40 -0
- package/public/js/components/permission-modal.js +30 -0
- package/public/js/components/prompt-modal.js +31 -0
- package/public/js/components/shortcuts-modal.js +45 -0
- package/public/js/components/status-bar.js +97 -0
- package/public/js/components/system-prompt-modal.js +29 -0
- package/public/js/components/telegram-modal.js +84 -0
- package/public/js/components/welcome-overlay.js +60 -0
- package/public/js/components/workflow-modal.js +41 -0
- package/public/js/core/api.js +10 -0
- package/public/js/core/dom.js +3 -2
- package/public/js/core/ws.js +7 -1
- package/public/js/features/attachments.js +226 -23
- package/public/js/features/projects.js +7 -0
- package/public/js/main.js +22 -0
- package/public/js/ui/shortcuts.js +4 -8
- package/public/login.html +470 -0
- package/public/offline.html +300 -168
- package/public/sw.js +10 -2
- package/server/agent-loop.js +1 -0
- package/server/auth.js +141 -0
- package/server/orchestrator.js +1 -0
- package/server/ws-handler.js +2 -0
- package/server.js +14 -3
package/README.md
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
A browser-based UI for <a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a> — chat, workflows, agents, cost tracking, and more.
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.producthunt.com/products/claudeck?embed=true&utm_source=badge-featured&utm_medium=badge&utm_campaign=badge-claudeck" target="_blank" rel="noopener noreferrer"><img alt="Claudeck on Product Hunt" width="250" height="54" src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1105387&theme=light&t=1774340505179" /></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
11
15
|
<p align="center">
|
|
12
16
|
<a href="https://www.npmjs.com/package/claudeck"><img src="https://img.shields.io/npm/v/claudeck?color=cb3837&label=npm" alt="npm version" /></a>
|
|
13
17
|
<a href="https://github.com/hamedafarag/claudeck/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/claudeck?color=blue" alt="license" /></a>
|
|
@@ -27,6 +31,9 @@ npx claudeck
|
|
|
27
31
|
# Custom port
|
|
28
32
|
npx claudeck --port 3000
|
|
29
33
|
|
|
34
|
+
# Enable authentication (for remote access via Cloudflare Tunnel, etc.)
|
|
35
|
+
npx claudeck --auth
|
|
36
|
+
|
|
30
37
|
# Or install globally
|
|
31
38
|
npm install -g claudeck
|
|
32
39
|
claudeck
|
|
@@ -42,10 +49,11 @@ User data lives in `~/.claudeck/` (config, database, plugins) — safe for NPX u
|
|
|
42
49
|
|
|
43
50
|
## Why Claudeck?
|
|
44
51
|
|
|
45
|
-
- **Zero-framework** — Vanilla JS, 6 npm dependencies, no build step
|
|
52
|
+
- **Zero-framework** — Vanilla JS + Web Components, 6 npm dependencies, no build step
|
|
46
53
|
- **Full agent orchestration** — Chains, DAGs, orchestrator, and monitoring dashboard
|
|
47
54
|
- **Persistent memory** — Cross-session project knowledge with FTS5 search and AI optimization
|
|
48
55
|
- **Cost visibility** — Per-session tracking, daily charts, token breakdowns
|
|
56
|
+
- **Secure remote access** — Token-based auth for Cloudflare Tunnel or reverse proxy setups
|
|
49
57
|
- **Works everywhere** — PWA, mobile responsive, Telegram AFK approval
|
|
50
58
|
- **Extensible** — Full-stack plugin system with auto-discovery
|
|
51
59
|
|
|
@@ -80,6 +88,7 @@ User data lives in `~/.claudeck/` (config, database, plugins) — safe for NPX u
|
|
|
80
88
|
### Code & Files
|
|
81
89
|
|
|
82
90
|
- **File Explorer** — Lazy tree, syntax-highlighted preview, drag-to-chat
|
|
91
|
+
- **File Picker** — Attach files with type dots, binary detection, search, selected chips
|
|
83
92
|
- **Git Panel** — Branch switching, staging, commit, log, inline diff viewer
|
|
84
93
|
- **Git Worktrees** — Run any chat/agent task in an isolated worktree; merge, diff, or discard results
|
|
85
94
|
- **Repos Manager** — Organize repos in nested groups with GitHub links
|
|
@@ -141,8 +150,9 @@ User data lives in `~/.claudeck/` (config, database, plugins) — safe for NPX u
|
|
|
141
150
|
| **Confirm All** | Prompt for every tool call |
|
|
142
151
|
| **Plan Mode** | No execution, planning only |
|
|
143
152
|
|
|
144
|
-
###
|
|
153
|
+
### Security & UI
|
|
145
154
|
|
|
155
|
+
- **Authentication** — `--auth` flag enables token-based auth with login page, HttpOnly cookies, and WebSocket verification. Localhost bypasses auth by default (auto-detected proxy headers like `X-Forwarded-For` disable the bypass for tunneled requests).
|
|
146
156
|
- Dark theme (terminal CRT aesthetic) and light theme
|
|
147
157
|
- Installable as a PWA with offline fallback
|
|
148
158
|
- Mobile responsive with tablet/mobile breakpoints
|
|
@@ -162,7 +172,8 @@ browser ──── WebSocket ──── server.js ──── Claude Code S
|
|
|
162
172
|
server/dag-executor.js ├── data.db (SQLite + memories)
|
|
163
173
|
server/notification-logger.js
|
|
164
174
|
server/utils/git-worktree.js
|
|
165
|
-
server/
|
|
175
|
+
server/auth.js
|
|
176
|
+
server/memory-optimizer.js └── .env (VAPID keys, auth token)
|
|
166
177
|
plugins/
|
|
167
178
|
```
|
|
168
179
|
|
|
@@ -172,7 +183,8 @@ browser ──── WebSocket ──── server.js ──── Claude Code S
|
|
|
172
183
|
| Backend | Express 4, WebSocket (ws 8), web-push 3 |
|
|
173
184
|
| AI SDK | @anthropic-ai/claude-code |
|
|
174
185
|
| Database | SQLite via better-sqlite3 (WAL mode) |
|
|
175
|
-
| Frontend | Vanilla JS ES modules, CSS custom properties |
|
|
186
|
+
| Frontend | Vanilla JS ES modules + Web Components (Light DOM), CSS custom properties |
|
|
187
|
+
| Testing | Vitest + happy-dom (2,400+ unit tests, 55% coverage) + WS perf benchmarks |
|
|
176
188
|
| Rendering | highlight.js, Mermaid (diagrams) — CDN |
|
|
177
189
|
|
|
178
190
|
---
|
|
@@ -226,7 +238,7 @@ All user data lives in `~/.claudeck/` (override with `CLAUDECK_HOME`):
|
|
|
226
238
|
│ └── skillsmp-config.json Skills Marketplace config
|
|
227
239
|
├── plugins/ User-installed plugins
|
|
228
240
|
├── data.db SQLite database
|
|
229
|
-
└── .env VAPID keys, port
|
|
241
|
+
└── .env VAPID keys, port, auth token
|
|
230
242
|
```
|
|
231
243
|
|
|
232
244
|
Defaults are copied on first run. User edits are never overwritten on upgrade.
|
|
@@ -272,6 +284,52 @@ npx skills add https://github.com/hamedafarag/claudeck-skills
|
|
|
272
284
|
|
|
273
285
|
---
|
|
274
286
|
|
|
287
|
+
## Testing
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
npm test # Run all 2,400+ tests
|
|
291
|
+
npm test -- --coverage # With coverage report
|
|
292
|
+
npm run test:perf # WebSocket performance benchmarks
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
| Layer | Tests | Coverage |
|
|
296
|
+
|-------|-------|----------|
|
|
297
|
+
| **components/** (Web Components) | 170+ | 100% |
|
|
298
|
+
| **core/** | 110+ | 90% |
|
|
299
|
+
| **ui/** | 280+ | 65% |
|
|
300
|
+
| **features/** | 210+ | 22% |
|
|
301
|
+
| **panels/** | 150+ | 35% |
|
|
302
|
+
| **server/** | 1,350+ | 95% |
|
|
303
|
+
|
|
304
|
+
19 Web Components in `public/js/components/` — each is a self-contained Custom Element (Light DOM) that owns its HTML, testable with zero mocks.
|
|
305
|
+
|
|
306
|
+
### Performance Benchmarks
|
|
307
|
+
|
|
308
|
+
The `test:perf` suite measures WebSocket relay performance with real TCP connections over localhost (no mocked sockets). Results from 4 scenarios:
|
|
309
|
+
|
|
310
|
+
**Approval Round-Trip Latency** — server sends `permission_request` → client responds → server receives:
|
|
311
|
+
|
|
312
|
+
| Concurrent Sessions | p50 | p95 | p99 |
|
|
313
|
+
|---|---|---|---|
|
|
314
|
+
| 1 | 70 µs | 132 µs | 196 µs |
|
|
315
|
+
| 5 | 187 µs | 222 µs | 244 µs |
|
|
316
|
+
| 10 | 300 µs | 466 µs | 721 µs |
|
|
317
|
+
| 25 | 382 µs | 570 µs | 764 µs |
|
|
318
|
+
|
|
319
|
+
**Message Throughput** — streaming text chunks to connected clients:
|
|
320
|
+
|
|
321
|
+
| Clients | Total msg/s |
|
|
322
|
+
|---|---|
|
|
323
|
+
| 1 | ~295k |
|
|
324
|
+
| 10 | ~393k |
|
|
325
|
+
| 50 | ~435k |
|
|
326
|
+
|
|
327
|
+
**Connection Scaling** — 100 simultaneous connections: p50 establish time 156 µs, ~35 KB memory per connection.
|
|
328
|
+
|
|
329
|
+
**Broadcast Fan-Out** — notification delivery to all connected clients: p50 under 1 ms even at 100 clients.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
275
333
|
## Contributing
|
|
276
334
|
|
|
277
335
|
Contributions are welcome! Fork the repo, make your changes, and open a PR.
|
|
@@ -281,6 +339,7 @@ git clone https://github.com/hamedafarag/claudeck.git
|
|
|
281
339
|
cd claudeck
|
|
282
340
|
npm install
|
|
283
341
|
npm start
|
|
342
|
+
npm test # Run tests before submitting
|
|
284
343
|
```
|
|
285
344
|
|
|
286
345
|
See [DOCUMENTATION.md](docs/DOCUMENTATION.md) for architecture details and [CONFIGURATION.md](docs/CONFIGURATION.md) for the config system.
|
package/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { homedir } from "os";
|
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
5
5
|
import { createInterface } from "readline";
|
|
6
|
+
import crypto from "crypto";
|
|
6
7
|
|
|
7
8
|
const DEFAULT_PORT = 9009;
|
|
8
9
|
const envDir = process.env.CLAUDECK_HOME || join(homedir(), ".claudeck");
|
|
@@ -13,16 +14,21 @@ function readEnv() {
|
|
|
13
14
|
try { return readFileSync(envPath, "utf-8"); } catch { return ""; }
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
function
|
|
17
|
+
function saveEnvVar(key, value) {
|
|
17
18
|
let content = readEnv();
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
const re = new RegExp(`^${key}=.*`, "m");
|
|
20
|
+
if (re.test(content)) {
|
|
21
|
+
content = content.replace(re, `${key}=${value}`);
|
|
20
22
|
} else {
|
|
21
|
-
content = content.trimEnd() + `\
|
|
23
|
+
content = content.trimEnd() + `\n${key}=${value}\n`;
|
|
22
24
|
}
|
|
23
25
|
writeFileSync(envPath, content);
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
function savePort(port) {
|
|
29
|
+
saveEnvVar("PORT", port);
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
function getSavedPort() {
|
|
27
33
|
const match = readEnv().match(/^PORT=(\d+)/m);
|
|
28
34
|
return match ? match[1] : null;
|
|
@@ -35,7 +41,50 @@ function ask(question) {
|
|
|
35
41
|
});
|
|
36
42
|
}
|
|
37
43
|
|
|
44
|
+
function handleAuthFlags() {
|
|
45
|
+
// --no-auth: explicitly disable for this run
|
|
46
|
+
if (process.argv.includes("--no-auth")) {
|
|
47
|
+
process.env.CLAUDECK_AUTH = "false";
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// --token <value> or --token=<value>: set custom token + enable auth
|
|
52
|
+
const tokenArg = process.argv.find(a => a.startsWith("--token"));
|
|
53
|
+
if (tokenArg) {
|
|
54
|
+
const token = tokenArg.includes("=")
|
|
55
|
+
? tokenArg.split("=")[1]
|
|
56
|
+
: process.argv[process.argv.indexOf(tokenArg) + 1];
|
|
57
|
+
if (token) {
|
|
58
|
+
process.env.CLAUDECK_TOKEN = token;
|
|
59
|
+
process.env.CLAUDECK_AUTH = "true";
|
|
60
|
+
saveEnvVar("CLAUDECK_TOKEN", token);
|
|
61
|
+
saveEnvVar("CLAUDECK_AUTH", "true");
|
|
62
|
+
console.log(`\x1b[2m Auth token set and saved to ~/.claudeck/.env\x1b[0m`);
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// --auth: enable auth, auto-generate token if missing
|
|
68
|
+
if (process.argv.includes("--auth")) {
|
|
69
|
+
process.env.CLAUDECK_AUTH = "true";
|
|
70
|
+
const envContent = readEnv();
|
|
71
|
+
const existingToken = envContent.match(/^CLAUDECK_TOKEN=(.+)/m);
|
|
72
|
+
if (existingToken) {
|
|
73
|
+
process.env.CLAUDECK_TOKEN = existingToken[1];
|
|
74
|
+
} else {
|
|
75
|
+
const token = crypto.randomBytes(32).toString("hex");
|
|
76
|
+
process.env.CLAUDECK_TOKEN = token;
|
|
77
|
+
saveEnvVar("CLAUDECK_TOKEN", token);
|
|
78
|
+
saveEnvVar("CLAUDECK_AUTH", "true");
|
|
79
|
+
console.log(`\x1b[2m Generated auth token and saved to ~/.claudeck/.env\x1b[0m`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
38
84
|
async function main() {
|
|
85
|
+
// Handle auth flags before anything else
|
|
86
|
+
handleAuthFlags();
|
|
87
|
+
|
|
39
88
|
// --port flag takes priority
|
|
40
89
|
const portArg = process.argv.find(a => a.startsWith('--port'));
|
|
41
90
|
if (portArg) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudeck",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A browser-based UI for Claude Code — chat, run workflows, manage MCP servers, track costs, and orchestrate autonomous agents from a local web interface. Installable as a PWA.",
|
|
6
6
|
"main": "server.js",
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"start": "node server.js",
|
|
46
46
|
"test": "vitest run",
|
|
47
47
|
"test:watch": "vitest",
|
|
48
|
-
"test:coverage": "vitest run --coverage"
|
|
48
|
+
"test:coverage": "vitest run --coverage",
|
|
49
|
+
"test:perf": "vitest run --config vitest.config.perf.js"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
52
|
"@anthropic-ai/claude-code": "^1.0.128",
|
|
@@ -150,8 +150,8 @@ body.sidebar-open #sidebar-backdrop {
|
|
|
150
150
|
padding: 6px 8px;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
/* Hide toolbox
|
|
154
|
-
.
|
|
153
|
+
/* Hide toolbox strip on mobile */
|
|
154
|
+
.toolbox-strip {
|
|
155
155
|
display: none;
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -1,46 +1,266 @@
|
|
|
1
1
|
/* ── File Picker ──────────────────────────────────────── */
|
|
2
2
|
.file-picker-modal {
|
|
3
|
-
width:
|
|
4
|
-
max-height:
|
|
3
|
+
width: 580px;
|
|
4
|
+
max-height: 75vh;
|
|
5
5
|
display: flex;
|
|
6
6
|
flex-direction: column;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/* Constraint info banner */
|
|
10
|
+
.fp-info-banner {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
gap: 8px;
|
|
14
|
+
padding: 8px 12px;
|
|
15
|
+
margin-bottom: 12px;
|
|
16
|
+
background: var(--accent-dim);
|
|
17
|
+
border: 1px solid var(--accent-mid);
|
|
18
|
+
border-radius: var(--radius-md);
|
|
19
|
+
font-size: 11px;
|
|
20
|
+
color: var(--text-secondary);
|
|
21
|
+
font-family: var(--font-sans);
|
|
22
|
+
line-height: 1.4;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.fp-info-banner svg {
|
|
26
|
+
flex-shrink: 0;
|
|
27
|
+
color: var(--accent);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.fp-info-banner strong {
|
|
31
|
+
color: var(--text);
|
|
32
|
+
font-weight: 600;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Search wrapper */
|
|
36
|
+
.fp-search-wrap {
|
|
37
|
+
position: relative;
|
|
11
38
|
margin-bottom: 10px;
|
|
12
39
|
}
|
|
13
40
|
|
|
41
|
+
.fp-search-icon {
|
|
42
|
+
position: absolute;
|
|
43
|
+
left: 10px;
|
|
44
|
+
top: 50%;
|
|
45
|
+
transform: translateY(-50%);
|
|
46
|
+
color: var(--text-dim);
|
|
47
|
+
pointer-events: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.fp-search-wrap .file-picker-search {
|
|
51
|
+
width: 100%;
|
|
52
|
+
padding-left: 32px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Selected files chip strip */
|
|
56
|
+
.fp-selected-strip {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-wrap: wrap;
|
|
59
|
+
gap: 6px;
|
|
60
|
+
padding-bottom: 10px;
|
|
61
|
+
margin-bottom: 8px;
|
|
62
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.fp-selected-strip.hidden { display: none; }
|
|
66
|
+
|
|
67
|
+
.fp-chip {
|
|
68
|
+
display: inline-flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: 4px;
|
|
71
|
+
padding: 3px 4px 3px 8px;
|
|
72
|
+
background: var(--accent-dim);
|
|
73
|
+
border: 1px solid var(--accent-mid);
|
|
74
|
+
border-radius: 4px;
|
|
75
|
+
font-size: 11px;
|
|
76
|
+
font-family: var(--font-mono);
|
|
77
|
+
color: var(--accent);
|
|
78
|
+
max-width: 220px;
|
|
79
|
+
animation: fpChipIn 0.15s var(--ease-out-expo);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@keyframes fpChipIn {
|
|
83
|
+
from { opacity: 0; transform: scale(0.9); }
|
|
84
|
+
to { opacity: 1; transform: scale(1); }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.fp-chip-name {
|
|
88
|
+
overflow: hidden;
|
|
89
|
+
text-overflow: ellipsis;
|
|
90
|
+
white-space: nowrap;
|
|
91
|
+
direction: rtl;
|
|
92
|
+
text-align: left;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.fp-chip-remove {
|
|
96
|
+
background: none;
|
|
97
|
+
border: none;
|
|
98
|
+
color: var(--accent);
|
|
99
|
+
font-size: 15px;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
padding: 0 2px;
|
|
102
|
+
line-height: 1;
|
|
103
|
+
opacity: 0.5;
|
|
104
|
+
transition: opacity 0.15s;
|
|
105
|
+
flex-shrink: 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.fp-chip-remove:hover { opacity: 1; }
|
|
109
|
+
|
|
110
|
+
/* File list */
|
|
14
111
|
.file-picker-list {
|
|
15
112
|
flex: 1;
|
|
16
113
|
overflow-y: auto;
|
|
17
|
-
max-height:
|
|
114
|
+
max-height: 320px;
|
|
18
115
|
border: 1px solid var(--border-subtle);
|
|
19
116
|
border-radius: var(--radius-md);
|
|
20
117
|
background: var(--bg);
|
|
21
118
|
}
|
|
22
119
|
|
|
120
|
+
/* File items */
|
|
23
121
|
.file-picker-item {
|
|
122
|
+
display: flex;
|
|
123
|
+
align-items: center;
|
|
124
|
+
gap: 8px;
|
|
24
125
|
padding: 7px 12px;
|
|
25
126
|
font-size: 12px;
|
|
26
127
|
font-family: var(--font-mono);
|
|
27
128
|
color: var(--text-secondary);
|
|
28
129
|
cursor: pointer;
|
|
29
130
|
border-bottom: 1px solid var(--border-subtle);
|
|
30
|
-
transition: background 0.
|
|
31
|
-
|
|
32
|
-
overflow: hidden;
|
|
33
|
-
text-overflow: ellipsis;
|
|
131
|
+
transition: background 0.1s, color 0.1s;
|
|
132
|
+
position: relative;
|
|
34
133
|
}
|
|
35
134
|
|
|
36
135
|
.file-picker-item:last-child { border-bottom: none; }
|
|
37
|
-
|
|
136
|
+
|
|
137
|
+
.file-picker-item:hover {
|
|
138
|
+
background: var(--bg-tertiary);
|
|
139
|
+
color: var(--text);
|
|
140
|
+
}
|
|
38
141
|
|
|
39
142
|
.file-picker-item.selected {
|
|
40
143
|
background: var(--accent-dim);
|
|
41
144
|
color: var(--accent);
|
|
42
145
|
}
|
|
43
146
|
|
|
147
|
+
/* File type color dot */
|
|
148
|
+
.fp-type-dot {
|
|
149
|
+
width: 6px;
|
|
150
|
+
height: 6px;
|
|
151
|
+
border-radius: 50%;
|
|
152
|
+
flex-shrink: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.fp-type-dot.type-code { background: var(--accent); }
|
|
156
|
+
.fp-type-dot.type-config { background: var(--amber); }
|
|
157
|
+
.fp-type-dot.type-markup { background: var(--cyan); }
|
|
158
|
+
.fp-type-dot.type-docs { background: var(--purple); }
|
|
159
|
+
.fp-type-dot.type-data { background: var(--user); }
|
|
160
|
+
.fp-type-dot.type-binary { background: var(--error); }
|
|
161
|
+
.fp-type-dot.type-default { background: var(--text-dim); }
|
|
162
|
+
|
|
163
|
+
/* File path text */
|
|
164
|
+
.fp-path {
|
|
165
|
+
flex: 1;
|
|
166
|
+
overflow: hidden;
|
|
167
|
+
text-overflow: ellipsis;
|
|
168
|
+
white-space: nowrap;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Checkmark for selected items */
|
|
172
|
+
.fp-check {
|
|
173
|
+
opacity: 0;
|
|
174
|
+
color: var(--accent);
|
|
175
|
+
flex-shrink: 0;
|
|
176
|
+
transition: opacity 0.1s;
|
|
177
|
+
margin-left: auto;
|
|
178
|
+
font-size: 13px;
|
|
179
|
+
font-weight: 700;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.file-picker-item.selected .fp-check {
|
|
183
|
+
opacity: 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Loading state */
|
|
187
|
+
.file-picker-item.loading {
|
|
188
|
+
pointer-events: none;
|
|
189
|
+
opacity: 0.6;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.fp-spinner {
|
|
193
|
+
width: 14px;
|
|
194
|
+
height: 14px;
|
|
195
|
+
border: 2px solid var(--border);
|
|
196
|
+
border-top-color: var(--accent);
|
|
197
|
+
border-radius: 50%;
|
|
198
|
+
animation: fpSpin 0.6s linear infinite;
|
|
199
|
+
flex-shrink: 0;
|
|
200
|
+
margin-left: auto;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@keyframes fpSpin {
|
|
204
|
+
to { transform: rotate(360deg); }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* Error state */
|
|
208
|
+
.file-picker-item.error {
|
|
209
|
+
background: rgba(237, 51, 59, 0.05);
|
|
210
|
+
cursor: not-allowed;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.file-picker-item.error .fp-path {
|
|
214
|
+
text-decoration: line-through;
|
|
215
|
+
opacity: 0.5;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.fp-error-msg {
|
|
219
|
+
font-size: 10px;
|
|
220
|
+
color: var(--error);
|
|
221
|
+
margin-left: auto;
|
|
222
|
+
flex-shrink: 0;
|
|
223
|
+
font-family: var(--font-sans);
|
|
224
|
+
white-space: nowrap;
|
|
225
|
+
font-weight: 500;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Binary file warning */
|
|
229
|
+
.file-picker-item.binary-warn {
|
|
230
|
+
opacity: 0.45;
|
|
231
|
+
cursor: not-allowed;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.fp-binary-label {
|
|
235
|
+
font-size: 10px;
|
|
236
|
+
color: var(--text-dim);
|
|
237
|
+
margin-left: auto;
|
|
238
|
+
flex-shrink: 0;
|
|
239
|
+
font-family: var(--font-sans);
|
|
240
|
+
font-style: italic;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Empty state */
|
|
244
|
+
.fp-empty-state {
|
|
245
|
+
display: flex;
|
|
246
|
+
flex-direction: column;
|
|
247
|
+
align-items: center;
|
|
248
|
+
justify-content: center;
|
|
249
|
+
padding: 40px 16px;
|
|
250
|
+
color: var(--text-dim);
|
|
251
|
+
font-size: 13px;
|
|
252
|
+
font-family: var(--font-sans);
|
|
253
|
+
gap: 10px;
|
|
254
|
+
min-height: 120px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.fp-empty-state.hidden { display: none; }
|
|
258
|
+
|
|
259
|
+
.fp-empty-state svg {
|
|
260
|
+
opacity: 0.3;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* Footer */
|
|
44
264
|
.file-picker-footer {
|
|
45
265
|
display: flex;
|
|
46
266
|
align-items: center;
|
|
@@ -50,20 +270,26 @@
|
|
|
50
270
|
color: var(--text-dim);
|
|
51
271
|
}
|
|
52
272
|
|
|
273
|
+
/* Badge */
|
|
274
|
+
#attach-btn {
|
|
275
|
+
position: relative;
|
|
276
|
+
}
|
|
277
|
+
|
|
53
278
|
.attach-badge {
|
|
54
279
|
position: absolute;
|
|
55
|
-
top: -
|
|
56
|
-
right: -
|
|
57
|
-
min-width:
|
|
58
|
-
height:
|
|
280
|
+
top: -3px;
|
|
281
|
+
right: -3px;
|
|
282
|
+
min-width: 14px;
|
|
283
|
+
height: 14px;
|
|
59
284
|
background: var(--accent-solid);
|
|
60
285
|
color: #fff;
|
|
61
|
-
font-size:
|
|
286
|
+
font-size: 9px;
|
|
62
287
|
font-weight: 700;
|
|
63
|
-
border-radius:
|
|
288
|
+
border-radius: 7px;
|
|
64
289
|
display: flex;
|
|
65
290
|
align-items: center;
|
|
66
291
|
justify-content: center;
|
|
67
|
-
padding: 0
|
|
292
|
+
padding: 0 3px;
|
|
68
293
|
line-height: 1;
|
|
294
|
+
pointer-events: none;
|
|
69
295
|
}
|
|
@@ -770,12 +770,17 @@ html[data-theme="light"] .msg-user {
|
|
|
770
770
|
border-top: 1px solid var(--border);
|
|
771
771
|
display: flex;
|
|
772
772
|
gap: 8px;
|
|
773
|
-
align-items: flex-
|
|
773
|
+
align-items: flex-end;
|
|
774
774
|
max-width: calc(var(--chat-max-w) + 48px);
|
|
775
775
|
width: 100%;
|
|
776
776
|
margin: 0 auto;
|
|
777
777
|
}
|
|
778
778
|
|
|
779
|
+
.input-bar > .send-history-group {
|
|
780
|
+
align-self: flex-end;
|
|
781
|
+
margin-bottom: 24px;
|
|
782
|
+
}
|
|
783
|
+
|
|
779
784
|
/* Gradient fade above input */
|
|
780
785
|
.input-bar::before {
|
|
781
786
|
content: "";
|
|
@@ -837,21 +842,79 @@ html[data-theme="light"] .msg-user {
|
|
|
837
842
|
transition: all 0.2s var(--ease-smooth);
|
|
838
843
|
}
|
|
839
844
|
|
|
840
|
-
|
|
841
|
-
#send-btn
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
845
|
+
/* Send button */
|
|
846
|
+
#send-btn {
|
|
847
|
+
width: 42px;
|
|
848
|
+
height: 42px;
|
|
849
|
+
border-radius: 50%;
|
|
850
|
+
background: var(--accent-solid);
|
|
851
|
+
color: #000;
|
|
852
|
+
position: relative;
|
|
853
|
+
box-shadow: 0 0 12px rgba(46, 194, 126, 0.2);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
#send-btn::after {
|
|
857
|
+
content: "";
|
|
858
|
+
position: absolute;
|
|
859
|
+
inset: -2px;
|
|
860
|
+
border-radius: 50%;
|
|
861
|
+
border: 1.5px solid var(--accent);
|
|
862
|
+
opacity: 0;
|
|
863
|
+
transition: opacity 0.2s, transform 0.2s;
|
|
864
|
+
transform: scale(0.95);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
#send-btn:hover {
|
|
868
|
+
background: var(--accent);
|
|
869
|
+
box-shadow: 0 0 20px rgba(46, 194, 126, 0.35), var(--glow-strong);
|
|
870
|
+
transform: scale(1.05);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
#send-btn:hover::after {
|
|
874
|
+
opacity: 0.4;
|
|
875
|
+
transform: scale(1);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
#send-btn:active {
|
|
879
|
+
transform: scale(0.93);
|
|
880
|
+
box-shadow: 0 0 8px rgba(46, 194, 126, 0.15);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
#send-btn:disabled {
|
|
884
|
+
opacity: 0.2;
|
|
885
|
+
cursor: not-allowed;
|
|
886
|
+
transform: none;
|
|
887
|
+
box-shadow: none;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
#send-btn:disabled::after { display: none; }
|
|
891
|
+
|
|
892
|
+
/* Stop button */
|
|
893
|
+
#stop-btn {
|
|
894
|
+
width: 42px;
|
|
895
|
+
height: 42px;
|
|
896
|
+
border-radius: 50%;
|
|
897
|
+
background: var(--error);
|
|
898
|
+
color: #fff;
|
|
899
|
+
box-shadow: 0 0 12px rgba(237, 51, 59, 0.2);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
#stop-btn:hover {
|
|
903
|
+
box-shadow: 0 0 20px rgba(237, 51, 59, 0.35);
|
|
904
|
+
transform: scale(1.05);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
#stop-btn:active {
|
|
908
|
+
transform: scale(0.93);
|
|
909
|
+
}
|
|
847
910
|
|
|
848
911
|
/* ── Input Meta Labels ────────────────────────────────── */
|
|
849
912
|
.input-meta {
|
|
850
913
|
display: flex;
|
|
851
914
|
align-items: center;
|
|
852
915
|
gap: 6px;
|
|
853
|
-
padding: 4px 2px 0;
|
|
854
916
|
user-select: none;
|
|
917
|
+
margin-left: auto;
|
|
855
918
|
}
|
|
856
919
|
|
|
857
920
|
.input-meta-item {
|