@lelouchhe/webagent 0.1.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.
@@ -0,0 +1,555 @@
1
+ :root {
2
+ --bg: #0d1117;
3
+ --surface: #161b22;
4
+ --border: #30363d;
5
+ --text: #c9d1d9;
6
+ --text-dim: #6e7681;
7
+ --accent: #58a6ff;
8
+ --green: #3fb950;
9
+ --yellow: #d29922;
10
+ --red: #f85149;
11
+ --blue: #58a6ff;
12
+ --radius: 4px;
13
+ --user-bg: #1e3a5f;
14
+ --code-bg: #161b22;
15
+ --permission-bg: #1c1305;
16
+ --permission-title: #d29922;
17
+ }
18
+ [data-theme="light"] {
19
+ --bg: #ffffff;
20
+ --surface: #f6f8fa;
21
+ --border: #d0d7de;
22
+ --text: #1f2328;
23
+ --text-dim: #656d76;
24
+ --accent: #0969da;
25
+ --green: #1a7f37;
26
+ --yellow: #9a6700;
27
+ --red: #cf222e;
28
+ --blue: #0969da;
29
+ --user-bg: #ddf4ff;
30
+ --code-bg: #f6f8fa;
31
+ --permission-bg: #fff4cc;
32
+ --permission-title: #7a4f00;
33
+ }
34
+ @media (prefers-color-scheme: light) {
35
+ [data-theme="auto"] {
36
+ --bg: #ffffff;
37
+ --surface: #f6f8fa;
38
+ --border: #d0d7de;
39
+ --text: #1f2328;
40
+ --text-dim: #656d76;
41
+ --accent: #0969da;
42
+ --green: #1a7f37;
43
+ --yellow: #9a6700;
44
+ --red: #cf222e;
45
+ --blue: #0969da;
46
+ --user-bg: #ddf4ff;
47
+ --code-bg: #f6f8fa;
48
+ --permission-bg: #fff4cc;
49
+ --permission-title: #7a4f00;
50
+ }
51
+ }
52
+ * { box-sizing: border-box; margin: 0; padding: 0; }
53
+ body {
54
+ font-family: "SF Mono", "Fira Code", "Cascadia Code", "Consolas", monospace;
55
+ background: var(--bg);
56
+ color: var(--text);
57
+ height: 100dvh;
58
+ display: flex;
59
+ flex-direction: column;
60
+ font-size: 16px;
61
+ }
62
+
63
+ /* Header */
64
+ #header {
65
+ padding: 6px 16px;
66
+ background: var(--surface);
67
+ border-bottom: 1px solid var(--border);
68
+ display: grid;
69
+ grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) minmax(0, 1fr);
70
+ align-items: center;
71
+ gap: 12px;
72
+ font-size: 15px;
73
+ }
74
+ #header .logo { font-weight: bold; color: var(--green); }
75
+ .header-side {
76
+ min-width: 0;
77
+ display: flex;
78
+ align-items: center;
79
+ }
80
+ .header-left { justify-content: flex-start; }
81
+ .header-right {
82
+ justify-content: flex-end;
83
+ gap: 12px;
84
+ }
85
+ #header .status { color: var(--text-dim); }
86
+ #status {
87
+ width: 12px;
88
+ height: 12px;
89
+ display: inline-block;
90
+ flex: 0 0 auto;
91
+ border-radius: 999px;
92
+ border: 2px solid transparent;
93
+ background: transparent;
94
+ }
95
+ #status.is-disconnected {
96
+ background: var(--red);
97
+ border-color: var(--red);
98
+ }
99
+ #status.is-connected {
100
+ background: var(--green);
101
+ border-color: var(--green);
102
+ }
103
+ #status.is-connecting {
104
+ border-color: var(--yellow);
105
+ animation: status-pulse 1.2s ease-in-out infinite;
106
+ }
107
+ @keyframes status-pulse {
108
+ 0%, 100% {
109
+ background: transparent;
110
+ opacity: 0.9;
111
+ }
112
+ 50% {
113
+ background: var(--yellow);
114
+ opacity: 1;
115
+ }
116
+ }
117
+ #session-info {
118
+ overflow: hidden;
119
+ text-overflow: ellipsis;
120
+ white-space: nowrap;
121
+ min-width: 0;
122
+ width: 100%;
123
+ text-align: center;
124
+ }
125
+ #theme-btn {
126
+ background: transparent;
127
+ border: none;
128
+ color: var(--text-dim);
129
+ cursor: pointer;
130
+ font-size: 15px;
131
+ font-family: inherit;
132
+ padding: 2px 6px;
133
+ }
134
+ #theme-btn:hover { color: var(--text); }
135
+ @media (max-width: 640px) {
136
+ #header {
137
+ display: flex;
138
+ gap: 8px;
139
+ }
140
+ .header-left,
141
+ .header-right {
142
+ flex: 0 0 auto;
143
+ }
144
+ .header-right {
145
+ margin-left: auto;
146
+ }
147
+ #session-info {
148
+ flex: 1 1 auto;
149
+ text-align: left;
150
+ }
151
+ }
152
+
153
+ /* Messages */
154
+ #messages {
155
+ flex: 1;
156
+ overflow-y: auto;
157
+ padding: 16px;
158
+ display: flex;
159
+ flex-direction: column;
160
+ gap: 4px;
161
+ }
162
+ .msg {
163
+ padding: 8px 14px;
164
+ line-height: 1.6;
165
+ font-size: 16px;
166
+ overflow-wrap: break-word;
167
+ }
168
+ .msg.user {
169
+ background: var(--user-bg);
170
+ border-left: 3px solid var(--accent);
171
+ border-radius: 0 var(--radius) var(--radius) 0;
172
+ margin: 8px 0 4px 0;
173
+ font-weight: 500;
174
+ }
175
+ .msg.user::before {
176
+ content: '▶ ';
177
+ color: var(--accent);
178
+ font-weight: bold;
179
+ }
180
+ .msg.assistant {
181
+ padding: 4px 14px;
182
+ }
183
+ .msg.assistant pre {
184
+ background: var(--code-bg);
185
+ padding: 10px;
186
+ border-radius: 4px;
187
+ overflow-x: auto;
188
+ margin: 8px 0;
189
+ font-size: 15px;
190
+ }
191
+ .msg.assistant code {
192
+ font-family: "SF Mono", "Fira Code", monospace;
193
+ font-size: 15px;
194
+ }
195
+ .msg.assistant p { margin: 4px 0; }
196
+ .msg.assistant ul, .msg.assistant ol { padding-left: 20px; margin: 4px 0; }
197
+
198
+ /* Thinking */
199
+ .thinking {
200
+ color: var(--text-dim);
201
+ font-style: italic;
202
+ font-size: 15px;
203
+ padding: 6px 14px;
204
+ border-left: 2px solid var(--border);
205
+ }
206
+ .thinking summary { cursor: pointer; }
207
+ .thinking-content { cursor: default; }
208
+ .thinking summary.active { animation: pulse 1.5s ease-in-out infinite; }
209
+
210
+ /* Waiting indicator (after user sends, before first response) */
211
+ @keyframes blink { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
212
+ @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
213
+ #waiting {
214
+ padding: 6px 14px;
215
+ color: var(--text-dim);
216
+ font-size: 15px;
217
+ }
218
+ #waiting .cursor { animation: blink 1s step-end infinite; }
219
+
220
+ /* System messages */
221
+ .system-msg {
222
+ font-size: 15px;
223
+ padding: 2px 14px;
224
+ color: var(--text-dim);
225
+ }
226
+
227
+ /* Tool calls */
228
+ .tool-call {
229
+ font-size: 15px;
230
+ padding: 2px 14px;
231
+ color: var(--text-dim);
232
+ animation: pulse 1.5s ease-in-out infinite;
233
+ }
234
+ .tool-call .icon { margin-right: 4px; }
235
+ .tool-call.completed { color: var(--green); animation: none; }
236
+ .tool-call.failed { color: var(--red); animation: none; }
237
+ .tool-call { cursor: pointer; }
238
+ .tool-call .tc-detail {
239
+ display: block;
240
+ color: var(--text-dim);
241
+ white-space: nowrap;
242
+ overflow: hidden;
243
+ text-overflow: ellipsis;
244
+ max-width: 100%;
245
+ }
246
+ .tool-call .tc-detail.expanded {
247
+ white-space: pre-wrap;
248
+ overflow: visible;
249
+ text-overflow: unset;
250
+ }
251
+ .tool-call details { margin-top: 2px; }
252
+ .tool-call details summary { cursor: pointer; color: var(--text-dim); }
253
+ .tool-call .tc-content {
254
+ margin: 4px 0 4px 16px;
255
+ padding: 6px 10px;
256
+ background: var(--code-bg);
257
+ border-radius: var(--radius);
258
+ font-size: 14px;
259
+ white-space: pre-wrap;
260
+ max-height: 200px;
261
+ overflow-y: auto;
262
+ color: var(--text);
263
+ cursor: default;
264
+ }
265
+
266
+ /* Diff view for edit tool calls */
267
+ .diff-view {
268
+ margin: 4px 0 4px 16px;
269
+ padding: 6px 10px;
270
+ background: var(--code-bg);
271
+ border-radius: var(--radius);
272
+ font-size: 13px;
273
+ font-family: monospace;
274
+ white-space: pre-wrap;
275
+ word-break: break-all;
276
+ max-height: 300px;
277
+ overflow-y: auto;
278
+ line-height: 1.4;
279
+ cursor: default;
280
+ }
281
+ .diff-view .diff-file { color: var(--blue); font-weight: bold; }
282
+ .diff-view .diff-hunk { color: var(--text-dim); }
283
+ .diff-view .diff-del { color: var(--red); }
284
+ .diff-view .diff-add { color: var(--green); }
285
+
286
+ /* Plan */
287
+ .plan {
288
+ font-size: 15px;
289
+ padding: 6px 14px;
290
+ border-left: 3px solid var(--blue);
291
+ border-radius: 0 var(--radius) var(--radius) 0;
292
+ }
293
+ .plan-title { color: var(--blue); font-weight: bold; margin-bottom: 2px; }
294
+ .plan-entry { padding: 1px 0; }
295
+
296
+ /* Permission dialog */
297
+ .permission {
298
+ background: var(--permission-bg);
299
+ border-left: 3px solid var(--yellow);
300
+ border-radius: 0 var(--radius) var(--radius) 0;
301
+ padding: 8px 14px;
302
+ font-size: 15px;
303
+ }
304
+ .permission .title { color: var(--permission-title); font-weight: bold; margin-bottom: 6px; }
305
+ .permission button {
306
+ margin: 2px 4px 2px 0;
307
+ padding: 4px 10px;
308
+ border: 1px solid var(--border);
309
+ border-radius: var(--radius);
310
+ background: transparent;
311
+ color: var(--text);
312
+ cursor: pointer;
313
+ font-size: 14px;
314
+ font-family: inherit;
315
+ }
316
+ .permission button:hover { background: var(--surface); }
317
+ .permission button.allow { border-color: var(--green); }
318
+ .permission button.deny { border-color: var(--red); }
319
+
320
+ /* Bash command output */
321
+ .bash-block {
322
+ margin: 2px 0;
323
+ padding: 2px 14px;
324
+ font-size: 15px;
325
+ }
326
+ .bash-block .bash-cmd {
327
+ color: var(--green);
328
+ font-family: monospace;
329
+ font-size: 14px;
330
+ cursor: pointer;
331
+ }
332
+ .bash-block .bash-cmd::before { content: '$ '; color: var(--text-dim); }
333
+ .bash-block .bash-cmd.running { animation: pulse 1.5s ease-in-out infinite; }
334
+ .bash-block .bash-output {
335
+ margin: 4px 0 4px 0;
336
+ padding: 6px 10px;
337
+ background: var(--code-bg);
338
+ border-radius: var(--radius);
339
+ font-size: 13px;
340
+ font-family: monospace;
341
+ white-space: pre-wrap;
342
+ word-break: break-all;
343
+ max-height: 400px;
344
+ overflow-y: auto;
345
+ color: var(--text);
346
+ line-height: 1.4;
347
+ display: none;
348
+ }
349
+ .bash-block .bash-output.has-content { display: block; }
350
+ .bash-block .bash-output .stderr { color: var(--red); }
351
+ .bash-block .bash-exit { font-size: 13px; color: var(--text-dim); margin-left: 8px; }
352
+ .bash-block .bash-exit.ok { color: var(--green); }
353
+ .bash-block .bash-exit.fail { color: var(--red); }
354
+
355
+ /* Input */
356
+ #input-area {
357
+ position: relative;
358
+ padding: 8px 16px 20px;
359
+ background: var(--bg);
360
+ border-top: 1px solid var(--border);
361
+ display: flex;
362
+ align-items: center;
363
+ gap: 4px;
364
+ transition: border-color .15s;
365
+ }
366
+ #input-area.bash-mode {
367
+ border-top: 2px solid #e8872b;
368
+ }
369
+ #input-area.bash-mode #input-prompt {
370
+ color: #e8872b;
371
+ }
372
+ #input-area.bash-mode #input {
373
+ color: #e8872b;
374
+ }
375
+ #input-area.plan-mode {
376
+ border-top: 2px solid var(--green);
377
+ }
378
+ #input-area.plan-mode::before {
379
+ content: 'plan';
380
+ position: absolute;
381
+ top: -7px;
382
+ left: 16px;
383
+ font-size: 10px;
384
+ line-height: 1;
385
+ padding: 0 4px;
386
+ background: var(--bg);
387
+ color: var(--green);
388
+ text-transform: uppercase;
389
+ letter-spacing: 0.5px;
390
+ }
391
+ #input-area.plan-mode #input-prompt {
392
+ color: var(--green);
393
+ }
394
+ #input-area.autopilot-mode {
395
+ border-top: 2px solid var(--red);
396
+ }
397
+ #input-area.autopilot-mode::before {
398
+ content: 'autopilot';
399
+ position: absolute;
400
+ top: -7px;
401
+ left: 16px;
402
+ font-size: 10px;
403
+ line-height: 1;
404
+ padding: 0 4px;
405
+ background: var(--bg);
406
+ color: var(--red);
407
+ text-transform: uppercase;
408
+ letter-spacing: 0.5px;
409
+ }
410
+ #input-area.autopilot-mode #input-prompt {
411
+ color: var(--red);
412
+ }
413
+ #input-prompt {
414
+ color: var(--text);
415
+ font-weight: bold;
416
+ user-select: none;
417
+ line-height: 1.5;
418
+ cursor: pointer;
419
+ }
420
+ #input {
421
+ flex: 1;
422
+ background: transparent;
423
+ color: var(--text);
424
+ border: none;
425
+ padding: 4px 8px;
426
+ font-size: 16px;
427
+ font-family: inherit;
428
+ resize: none;
429
+ min-height: 28px;
430
+ max-height: 200px;
431
+ line-height: 1.5;
432
+ }
433
+ #input:focus { outline: none; }
434
+ .input-btn {
435
+ background: transparent;
436
+ color: var(--text-dim);
437
+ border: 1px solid var(--border);
438
+ border-radius: var(--radius);
439
+ width: 34px;
440
+ height: 28px;
441
+ padding: 0 6px;
442
+ font-size: 14px;
443
+ font-family: inherit;
444
+ cursor: pointer;
445
+ display: inline-flex;
446
+ align-items: center;
447
+ justify-content: center;
448
+ line-height: 1;
449
+ flex: 0 0 auto;
450
+ }
451
+ .input-btn:hover {
452
+ color: var(--text);
453
+ border-color: var(--text-dim);
454
+ }
455
+ #new-btn {
456
+ font-size: 11px;
457
+ letter-spacing: 0.2px;
458
+ }
459
+ #send-btn.cancel {
460
+ color: var(--red);
461
+ border-color: var(--red);
462
+ font-weight: bold;
463
+ }
464
+ #send-btn.cancel:hover,
465
+ #send-btn.cancel:focus,
466
+ #send-btn.cancel:active {
467
+ color: var(--red);
468
+ border-color: var(--red);
469
+ }
470
+ #input-prompt.busy { font-size: 0; position: relative; }
471
+ #input-prompt.busy::before {
472
+ content: '⠋';
473
+ font-size: 16px;
474
+ animation: spinner 0.8s steps(1) infinite;
475
+ }
476
+ @keyframes spinner {
477
+ 0% { content: '⠋'; } 10% { content: '⠙'; } 20% { content: '⠹'; }
478
+ 30% { content: '⠸'; } 40% { content: '⠼'; } 50% { content: '⠴'; }
479
+ 60% { content: '⠦'; } 70% { content: '⠧'; } 80% { content: '⠇'; }
480
+ 90% { content: '⠏'; }
481
+ }
482
+
483
+ /* Slash command autocomplete */
484
+ #slash-menu {
485
+ display: none;
486
+ position: absolute;
487
+ bottom: 100%;
488
+ left: 0;
489
+ right: 0;
490
+ background: var(--surface);
491
+ border: 1px solid var(--border);
492
+ border-bottom: none;
493
+ max-height: 220px;
494
+ overflow-y: auto;
495
+ z-index: 10;
496
+ }
497
+ #slash-menu.active { display: block; }
498
+ .slash-item {
499
+ padding: 6px 14px;
500
+ cursor: pointer;
501
+ font-size: 15px;
502
+ display: flex;
503
+ gap: 12px;
504
+ }
505
+ .slash-item:hover, .slash-item.selected {
506
+ background: var(--accent);
507
+ color: #fff;
508
+ }
509
+ .slash-item .slash-cmd { font-weight: bold; min-width: 120px; }
510
+ .slash-item .slash-desc { color: var(--text-dim); }
511
+ .slash-item:hover .slash-desc, .slash-item.selected .slash-desc { color: rgba(255,255,255,0.8); }
512
+
513
+ /* Image attach */
514
+ #new-btn.hidden { display: none; }
515
+ #attach-preview {
516
+ display: none;
517
+ padding: 4px 16px;
518
+ background: var(--bg);
519
+ border-top: 1px solid var(--border);
520
+ }
521
+ #attach-preview.active { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
522
+ .attach-thumb {
523
+ position: relative;
524
+ display: inline-block;
525
+ }
526
+ .attach-thumb img {
527
+ max-height: 60px;
528
+ max-width: 120px;
529
+ border-radius: 4px;
530
+ border: 1px solid var(--border);
531
+ }
532
+ .attach-thumb .remove {
533
+ position: absolute;
534
+ top: -4px;
535
+ right: -4px;
536
+ background: var(--red);
537
+ color: #fff;
538
+ border: none;
539
+ border-radius: 50%;
540
+ width: 16px;
541
+ height: 16px;
542
+ font-size: 10px;
543
+ line-height: 16px;
544
+ text-align: center;
545
+ cursor: pointer;
546
+ padding: 0;
547
+ }
548
+ .msg.user img.user-image {
549
+ max-width: 200px;
550
+ max-height: 150px;
551
+ border-radius: 4px;
552
+ border: 1px solid var(--border);
553
+ display: block;
554
+ margin-top: 4px;
555
+ }
package/dist/sw.js ADDED
@@ -0,0 +1,5 @@
1
+ // Minimal service worker for PWA installability.
2
+ // No offline caching — app requires WebSocket connection.
3
+
4
+ self.addEventListener('install', () => self.skipWaiting());
5
+ self.addEventListener('activate', (e) => e.waitUntil(self.clients.claim()));
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@lelouchhe/webagent",
3
+ "version": "0.1.0",
4
+ "description": "A terminal-style web UI for ACP-compatible agents",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/LelouchHe/webagent.git"
10
+ },
11
+ "homepage": "https://github.com/LelouchHe/webagent#readme",
12
+ "keywords": [
13
+ "acp",
14
+ "agent",
15
+ "web-ui",
16
+ "copilot",
17
+ "chat",
18
+ "websocket"
19
+ ],
20
+ "engines": {
21
+ "node": ">=22.6.0"
22
+ },
23
+ "bin": {
24
+ "webagent": "bin/webagent.mjs"
25
+ },
26
+ "files": [
27
+ "bin/",
28
+ "src/",
29
+ "dist/",
30
+ "config.toml"
31
+ ],
32
+ "scripts": {
33
+ "prepublishOnly": "npm run build",
34
+ "build": "node scripts/build.js",
35
+ "start": "node --experimental-strip-types src/server.ts --config config.toml",
36
+ "test": "node --experimental-strip-types --test test/*.test.ts",
37
+ "test:e2e": "playwright test",
38
+ "dev": "node --experimental-strip-types --watch src/server.ts --config config.dev.toml",
39
+ "dev:start": "node --experimental-strip-types src/server.ts --config config.dev.toml &",
40
+ "dev:stop": "lsof -ti:6801 | xargs kill 2>/dev/null || true"
41
+ },
42
+ "dependencies": {
43
+ "@agentclientprotocol/sdk": "^0.14.1",
44
+ "better-sqlite3": "^12.6.2",
45
+ "smol-toml": "^1.6.0",
46
+ "ws": "^8.19.0",
47
+ "zod": "^4.3.6"
48
+ },
49
+ "devDependencies": {
50
+ "@types/better-sqlite3": "^7.6.13",
51
+ "@types/node": "^25.3.3",
52
+ "@types/ws": "^8.18.1",
53
+ "happy-dom": "^20.8.3",
54
+ "playwright": "^1.58.2"
55
+ }
56
+ }