pinokiod 7.3.9 → 7.3.11

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 (52) hide show
  1. package/kernel/bin/brew.js +12 -2
  2. package/kernel/bin/caddy.js +24 -20
  3. package/kernel/bin/huggingface.js +2 -2
  4. package/kernel/bin/setup.js +2 -3
  5. package/kernel/bin/uv.js +13 -6
  6. package/kernel/connect/index.js +5 -1
  7. package/kernel/connect/providers/huggingface/index.js +213 -75
  8. package/kernel/environment.js +16 -1
  9. package/kernel/router/localhost_home_router.js +7 -0
  10. package/kernel/shell.js +1 -5
  11. package/kernel/util.js +1 -0
  12. package/package.json +1 -1
  13. package/server/index.js +75 -33
  14. package/server/public/common.js +52 -88
  15. package/server/public/install.js +20 -2
  16. package/server/public/layout.js +1 -1
  17. package/server/public/nav.js +3 -1
  18. package/server/public/style.css +1455 -521
  19. package/server/public/tab-link-popover.css +162 -18
  20. package/server/public/tab-link-popover.js +230 -21
  21. package/server/public/task-launcher.css +182 -91
  22. package/server/public/terminal-settings.js +227 -50
  23. package/server/public/universal-launcher.css +42 -33
  24. package/server/public/urldropdown.css +284 -0
  25. package/server/views/app.ejs +1718 -352
  26. package/server/views/autolaunch.ejs +4 -5
  27. package/server/views/checkpoints.ejs +223 -50
  28. package/server/views/connect/huggingface.ejs +406 -325
  29. package/server/views/connect.ejs +0 -1
  30. package/server/views/github.ejs +277 -324
  31. package/server/views/index.ejs +65 -8
  32. package/server/views/install.ejs +134 -65
  33. package/server/views/logs.ejs +9 -8
  34. package/server/views/net.ejs +341 -64
  35. package/server/views/network.ejs +85 -63
  36. package/server/views/partials/main_sidebar.ejs +249 -24
  37. package/server/views/plugins.ejs +141 -3
  38. package/server/views/settings.ejs +103 -7
  39. package/server/views/setup.ejs +0 -5
  40. package/server/views/skills.ejs +0 -1
  41. package/server/views/task_list.ejs +0 -1
  42. package/server/views/terminal.ejs +285 -60
  43. package/server/views/terminals.ejs +346 -6
  44. package/server/views/tools.ejs +828 -1691
  45. package/test/caddy-install.test.js +53 -0
  46. package/test/connect-setup.test.js +16 -0
  47. package/test/github-connection.test.js +1 -1
  48. package/test/huggingface-bin.test.js +4 -4
  49. package/test/huggingface-connect.test.js +73 -0
  50. package/test/main-sidebar.test.js +31 -0
  51. package/test/shell-run-template.test.js +5 -1
  52. package/test/uv-bin.test.js +29 -0
@@ -1,1733 +1,870 @@
1
- <html>
1
+ <!doctype html>
2
+ <html lang="en">
2
3
  <head>
3
- <meta charset="UTF-8">
4
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
5
- <link href="/xterm.min.css" rel="stylesheet" />
6
- <link href="/css/fontawesome.min.css" rel="stylesheet">
7
- <link href="/css/solid.min.css" rel="stylesheet">
8
- <link href="/css/regular.min.css" rel="stylesheet">
9
- <link href="/css/brands.min.css" rel="stylesheet">
10
- <link href="/markdown.css" rel="stylesheet"/>
11
- <link href="/noty.css" rel="stylesheet"/>
12
- <link href="/style.css" rel="stylesheet"/>
13
- <link href="/task-launcher.css" rel="stylesheet"/>
14
- <link href="/urldropdown.css" rel="stylesheet" />
15
- <% if (agent === "electron") { %>
16
- <link href="/electron.css" rel="stylesheet"/>
17
- <% } %>
18
- <style>
19
- .line2 {
20
- display: flex;
21
- align-items: center;
22
- cursor: pointer;
23
- background: rgba(0,0,100,0.04);
24
- }
25
- .line2 a {
26
- text-decoration: none;
27
- color: black;
28
- }
29
- .status {
30
- padding: 10px;
31
- margin: 10px;
32
- border-radius: 10px;
33
- }
34
- .status.offline {
35
- background: silver;
36
- }
37
- .status.online {
38
- background: yellowgreen;
39
- }
40
- .switch {
41
- padding: 10px;
42
- margin: 10px 0;
43
- }
44
- .switch[data-online=true] {
45
- color: yellowgreen;
46
- }
47
- .button {
48
- padding: 10px;
49
- }
50
- .on, .off {
51
- display: flex;
52
- align-items: center;
53
- }
54
- /*
55
- .btn {
56
- margin-right: 5px;
57
- font-weight: normal;
58
- padding: 5px 15px;
59
- min-width: 100px;
60
- text-align: center;
61
- }
62
- */
63
- .items {
64
- max-width: 600px;
65
- margin: 50px auto;
66
- }
67
- body.dark .item {
68
- border-left: 5px solid rgba(255,255,255,0.06);
69
- }
70
- .item {
71
- margin: 0;
72
- /*
73
- border-top: 1px solid rgba(255,255,255,0.1);
74
- border-bottom: 1px solid rgba(255,255,255,0.1);
75
- */
76
- padding: 10px;
77
- border-left: 5px solid rgba(0,0,0,0.06);
78
- }
79
- .titleview {
80
- margin: 0;
81
- padding: 25px;
82
- max-width: 600px;
83
- }
84
- .titleview h1 {
85
- word-break: break-word;
86
- font-size: 40px;
87
- margin-bottom: 10px;
88
- }
89
- .item > .d {
90
- padding-bottom: 10px;
91
- }
92
- .item label {
93
- display: block;
94
- text-transform: capitalize;
95
- font-size: 20px;
96
- font-weight: bold;
97
- padding-bottom: 5px;
98
- }
99
- .item .explanation {
100
- padding-top: 5px;
101
- color: #bf411d;
102
- font-size: 12px;
103
- }
104
- .item input[type=text] {
105
- background: whitesmoke;
106
- }
107
- .item select {
108
- background: whitesmoke;
109
- }
110
- /*
111
- body.dark .item input[type=text] {
112
- background: rgba(255,255,255,0.1);
113
- color: white;
114
- }
115
- body.dark .item select {
116
- color: white;
117
- background: rgba(255,255,255,0.1);
118
- }
119
- */
120
- .item input[type=text] {
121
- padding: 10px;
122
- flex-grow: 1;
123
- border: none;
124
- /*
125
- background: rgba(0,0,0,0.05);
126
- */
127
- width: 100%;
128
- }
129
- .item select {
130
- /*
131
- -webkit-appearance: none;
132
- -moz-appearance: none;
133
- appearance: none;
134
- */
135
-
136
- /*
137
- background: rgba(0,0,0,0.05);
138
- */
139
- padding: 10px;
140
- box-sizing: border-box;
141
- width: 100%;
142
- border: none;
143
- }
144
- .item img {
145
- width: 100px;
146
- }
147
- .item .title {
148
- text-decoration: none;
149
- color: royalblue;
150
- }
151
- .item .col {
152
- padding: 10px;
153
- }
154
- .item .col > * {
155
- margin: 5px 0;
156
- }
157
- .item .stat {
158
- color: rgba(0,0,0,0.8);
159
- display: flex;
160
- }
161
- .item .stat > * {
162
- margin-right: 15px;
163
- }
164
- .timestamp {
165
- color: rgba(0,0,0,0.5);
166
- }
167
- body.dark .loading {
168
- background: rgba(255,255,255,0.06);
169
- }
170
- .loading {
171
- padding: 20px;
172
- text-align: center;
173
- background: rgba(0,0,0,0.06);
174
- }
175
- body.dark .btn {
176
- color: white;
177
- background: rgba(255,255,255,0.1);
178
- }
179
- body.light {
180
- /*
181
- background: var(--light-nav-bg);
182
- */
183
- }
184
- /*
185
- form {
186
- text-align: center;
187
- }
188
- body.dark header .btn2 {
189
- color: var(--light-link-color);
190
- }
191
- header .btn2 {
192
- color: var(--light-color);
193
- }
194
- */
195
- /*
196
- body.dark header .home {
197
- color: var(--light-link-color);
198
- }
199
- */
200
- header .home {
201
- color: var(--light-color);
202
- }
203
- body.dark hr {
204
- background: white;
205
- }
206
- hr {
207
- opacity: 0.03;
208
- background: black;
209
- margin: 30px 0;
210
- height: 1px;
211
- border: none;
212
- }
213
- body.dark .config {
214
- color: white;
215
- }
216
- .config {
217
- color: black;
218
- padding: 30px;
219
- margin: 10px 0;
220
- }
221
- .config-header {
222
- }
223
- .config-body {
224
- padding-top: 20px;
225
- margin-top: 20px;
226
- }
227
- .config .btn {
228
- font-size: 16px;
229
- font-weight: bold;
230
- width: 100px;
231
- background: royalblue;
232
- flex-shrink: 0;
233
- }
234
- .config-row {
235
- padding: 10px 20px 0 0;
236
- }
237
- .label {
238
- padding-bottom: 5px;
239
- font-weight: bold;
240
- }
241
- .config-row input[type=text] {
242
- padding: 7px;
243
- }
244
- .header-label {
245
- padding: 0;
246
- font-size: 30px;
247
- text-transform: capitalize;
248
- }
249
- body.dark .container {
250
- color: white;
251
- }
252
- .container {
253
- }
254
- .dark .container-row {
255
- background: rgba(255, 255, 255, 0.04);
256
- }
257
- .container-row {
258
- background: rgba(0, 0, 100, 0.04);
259
- box-sizing: border-box;
260
- }
261
- .header-label-sub h1 {
262
- font-size: 25px;
263
- letter-spacing: -1px;
264
- font-weight: lighter;
265
- }
266
- .header-label-sub {
267
- font-size: 16px;
268
- padding: 10px 0;
269
- border-bottom: 1px solid rgba(0,0,0,0.1);
270
- }
271
- .header-label-sub .s {
272
- padding: 10px 0;
273
- opacity: 0.7;
274
- }
275
- body.dark .section-header {
276
- }
277
- .section-header {
278
- font-weight: normal;
279
- font-size: 14px;
280
- padding: 15px 5px;
281
- }
282
- .section-header h2 {
283
- font-size: 20px;
284
- font-weight: lighter;
285
- margin: 0;
286
- }
287
- .section-header .section {
288
- padding: 0;
289
- }
290
- .section-header .section div {
291
- font-weight: normal;
292
- opacity: 0.6;
293
- font-size: 14px;
294
- }
295
- .section-header h3 {
296
- display: flex;
297
- margin: 0;
298
- }
299
- .section-header .col {
300
- padding-left: 10px;
301
- flex-grow: 1;
302
- min-width: 0;
303
- word-wrap: break-word;
304
- }
305
- .tab-content {
306
- width: 100%;
307
- display: flex;
308
- flex-direction: column;
309
- flex-wrap: wrap;
310
- gap: 5px;
311
- }
312
- .tab:hover {
313
- color: royalblue;
314
- }
315
- .tab-content .tab {
316
- text-decoration: none;
317
- display: flex;
318
- font-size: 14px;
319
- font-weight: bold;
320
- color: black;
321
- overflow: hidden;
322
- box-sizing: border-box;
323
- align-items: center;
324
- gap: 2px;
325
- background: rgba(0,0,0,0.04);
326
- border-radius: 5px;
327
- padding: 5px;
328
- }
329
- body.dark .tab {
330
- color: white;
331
- background: rgba(255,255,255,0.03);
332
- }
333
- .tab h2 {
334
- margin: 0;
335
- font-size: 14px;
336
- }
337
- .tab .col {
338
- flex-grow: 1;
339
- margin-left: 10px;
340
- }
341
- .tab .emoji {
342
- width: 30px;
343
- height: 30px;
344
- font-size: 20px;
345
- box-sizing: border-box;
346
- text-align:center;
347
- display: flex;
348
- justify-content: center;
349
- align-items: center;
350
- }
351
- .tab i.img {
352
- height: 30px;
353
- width: 30px;
354
- display: flex;
355
- justify-content: center;
356
- font-size: 20px;
357
- align-items: center;
358
- border-radius: 3px;
359
- }
360
- .menu-container {
361
- /*
362
- max-width: 800px;
363
- */
364
- padding: 20px;
365
- align-items: stretch;
366
- }
367
- .menu-container {
368
- display: flex;
369
- box-sizing: border-box;
370
- flex-direction: column;
371
- }
372
- .package-tabs {
373
- display: flex;
374
- border-bottom: 2px solid rgba(0,0,0,0.1);
375
- margin-bottom: 20px;
376
- }
377
- body.dark .package-tabs {
378
- border-bottom: 2px solid rgba(255,255,255,0.1);
379
- }
380
- .package-tab {
381
- padding: 10px 20px;
382
- background: rgba(0,0,0,0.04);
383
- border: none;
384
- cursor: pointer;
385
- font-size: 14px;
386
- font-weight: bold;
387
- color: rgba(0,0,0,0.7);
388
- margin-right: 2px;
389
- border-radius: 5px 5px 0 0;
390
- transition: all 0.2s ease;
391
- }
392
- body.dark .package-tab {
393
- background: rgba(255,255,255,0.04);
394
- color: rgba(255,255,255,0.7);
395
- }
396
- .package-tab:hover {
397
- color: royalblue;
398
- background: rgba(0,0,100,0.08);
399
- }
400
- body.dark .package-tab:hover {
401
- background: rgba(255,255,255,0.08);
402
- }
403
- .package-tab.active {
404
- background: royalblue;
405
- color: white;
406
- }
407
- .package-tab.active:hover {
408
- background: royalblue;
409
- color: white;
410
- }
411
- .tab-panel {
412
- display: none;
413
- }
414
- .tab-panel.active {
415
- display: block;
416
- }
417
- .bundles-section {
418
- margin-bottom: 30px;
419
- }
420
- .bundles-header {
421
- padding: 15px 0;
422
- border-bottom: 1px solid rgba(0,0,0,0.1);
423
- margin-bottom: 20px;
424
- }
425
- body.dark .bundles-header {
426
- border-bottom: 1px solid rgba(255,255,255,0.1);
427
- }
428
- .bundles-header h3 {
429
- margin: 0;
430
- font-size: 18px;
431
- font-weight: lighter;
432
- color: rgba(0,0,0,0.8);
433
- }
434
- body.dark .bundles-header h3 {
435
- color: rgba(255,255,255,0.8);
436
- }
437
- .bundles-grid {
438
- display: grid;
439
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
440
- gap: 15px;
441
- }
442
- .bundle-card {
443
- background: rgba(0,0,0,0.04);
444
- padding: 15px;
445
- border-left: 4px solid royalblue;
446
- }
447
- body.dark .bundle-card {
448
- background: rgba(255,255,255,0.04);
449
- }
450
- .bundle-card.installed {
451
- border-left-color: #28a745;
452
- }
453
- .bundle-card.pending {
454
- border-left-color: #ffc107;
455
- }
456
- .bundle-header {
457
- display: flex;
458
- justify-content: space-between;
459
- align-items: flex-start;
460
- margin-bottom: 15px;
461
- }
462
- .bundle-title-section {
463
- display: flex;
464
- align-items: center;
465
- gap: 10px;
466
- flex-grow: 1;
467
- }
468
- .bundle-icon {
469
- width: 40px;
470
- height: 40px;
471
- display: flex;
472
- align-items: center;
473
- justify-content: center;
474
- background: royalblue;
475
- color: white;
476
- border-radius: 8px;
477
- font-size: 18px;
478
- flex-shrink: 0;
479
- }
480
- body.dark .bundle-icon {
481
- background: lightblue;
482
- color: black;
483
- }
484
- .bundle-title-text {
485
- flex-grow: 1;
486
- }
487
- .bundle-name {
488
- font-size: 16px;
489
- font-weight: bold;
490
- color: royalblue;
491
- margin-bottom: 2px;
492
- }
493
- body.dark .bundle-name {
494
- color: lightblue;
495
- }
496
- .bundle-code {
497
- font-size: 12px;
498
- color: rgba(0,0,0,0.6);
499
- font-weight: normal;
500
- }
501
- body.dark .bundle-code {
502
- color: rgba(255,255,255,0.6);
503
- }
504
- .bundle-description {
505
- font-size: 13px;
506
- color: rgba(0,0,0,0.7);
507
- margin-bottom: 15px;
508
- line-height: 1.4;
509
- }
510
- body.dark .bundle-description {
511
- color: rgba(255,255,255,0.7);
512
- }
513
- .bundle-status {
514
- font-size: 12px;
515
- padding: 3px 8px;
516
- border-radius: 12px;
517
- text-transform: uppercase;
518
- font-weight: bold;
519
- }
520
- .bundle-status.installed {
521
- background: #28a745;
522
- color: white;
523
- }
524
- .bundle-status.pending {
525
- background: #ffc107;
526
- color: black;
527
- }
528
- .bundle-packages {
529
- margin-bottom: 15px;
530
- }
531
- .bundle-packages-title {
532
- font-size: 12px;
533
- font-weight: bold;
534
- color: rgba(0,0,0,0.6);
535
- margin-bottom: 5px;
536
- }
537
- body.dark .bundle-packages-title {
538
- color: rgba(255,255,255,0.6);
539
- }
540
- .bundle-packages-list {
541
- display: flex;
542
- flex-wrap: wrap;
543
- gap: 5px;
544
- }
545
- .package-badge {
546
- background: rgba(0,0,100,0.1);
547
- color: rgba(0,0,0,0.8);
548
- padding: 2px 6px;
549
- border-radius: 10px;
550
- font-size: 11px;
551
- position: relative;
552
- display: inline-flex;
553
- align-items: center;
554
- gap: 3px;
555
- }
556
- body.dark .package-badge {
557
- background: rgba(255,255,255,0.1);
558
- color: rgba(255,255,255,0.8);
559
- }
560
- .package-badge.installed {
561
- background: rgba(40, 167, 69, 0.2);
562
- color: #28a745;
563
- border: 1px solid rgba(40, 167, 69, 0.3);
564
- }
565
- body.dark .package-badge.installed {
566
- background: rgba(40, 167, 69, 0.3);
567
- color: #4caf50;
568
- border: 1px solid rgba(40, 167, 69, 0.4);
569
- }
570
- .package-badge.not-installed {
571
- background: rgba(220, 53, 69, 0.2);
572
- color: #dc3545;
573
- border: 1px solid rgba(220, 53, 69, 0.3);
574
- }
575
- body.dark .package-badge.not-installed {
576
- background: rgba(220, 53, 69, 0.3);
577
- color: #f56565;
578
- border: 1px solid rgba(220, 53, 69, 0.4);
579
- }
580
- .package-badge-icon {
581
- font-size: 8px;
582
- font-weight: bold;
583
- }
584
- .bundle-install-btn {
585
- background: royalblue;
586
- color: white;
587
- border: none;
588
- padding: 8px 15px;
589
- border-radius: 5px;
590
- text-decoration: none;
591
- display: inline-block;
592
- font-size: 12px;
593
- font-weight: bold;
594
- text-align: center;
595
- transition: background 0.2s ease;
596
- width: 100%;
597
- box-sizing: border-box;
598
- }
599
- .bundle-install-btn:hover {
600
- background: #4169e1;
601
- color: white;
602
- }
603
- .bundle-install-btn.installed {
604
- background: #28a745;
605
- cursor: default;
606
- }
607
- .bundle-install-btn.installed:hover {
608
- background: #28a745;
609
- }
610
- .loading-container {
611
- display: flex;
612
- flex-direction: column;
613
- align-items: center;
614
- justify-content: center;
615
- min-height: 400px;
616
- text-align: center;
617
- }
618
- .loading-spinner {
619
- width: 50px;
620
- height: 50px;
621
- border: 4px solid rgba(0,0,0,0.1);
622
- border-left: 4px solid royalblue;
623
- border-radius: 50%;
624
- animation: spin 1s linear infinite;
625
- margin-bottom: 20px;
626
- }
627
- body.dark .loading-spinner {
628
- border: 4px solid rgba(255,255,255,0.1);
629
- border-left: 4px solid lightblue;
630
- }
631
- @keyframes spin {
632
- 0% { transform: rotate(0deg); }
633
- 100% { transform: rotate(360deg); }
634
- }
635
- .loading-text {
636
- font-size: 16px;
637
- color: rgba(0,0,0,0.7);
638
- margin-bottom: 10px;
639
- }
640
- body.dark .loading-text {
641
- color: rgba(255,255,255,0.7);
642
- }
643
- .loading-subtext {
644
- font-size: 14px;
645
- color: rgba(0,0,0,0.5);
646
- }
647
- body.dark .loading-subtext {
648
- color: rgba(255,255,255,0.5);
649
- }
650
- .reset-cache-loading {
651
- padding: 10px;
652
- background: rgba(0,0,0,0.1);
653
- margin-bottom: 10px;
654
- text-align: center;
655
- }
656
- .reset-bin-loading {
657
- padding: 10px;
658
- background: rgba(0,0,0,0.1);
659
- margin-bottom: 10px;
660
- text-align: center;
661
- }
662
- #proxy {
663
- text-decoration: underline;
664
- margin-left: 10px;
665
- display: inline-block;
666
- cursor: pointer;
667
- }
668
- body.dark .keys pre {
669
- background: rgba(255,255,255,0.02) !important;
670
- }
671
- .keys pre {
672
- padding: 20px;
673
- margin: 20px 0;
674
- background: rgba(0,0,100,0.04) !important;
675
- }
676
- .swal2-actions {
677
- justify-content: center !important;
678
- }
679
- .swal2-title {
680
- text-align: center !important;
681
- }
682
-
683
- .package-install-card {
684
- background: rgba(0, 0, 0, 0.035);
685
- border: 1px solid rgba(0, 0, 0, 0.06);
686
- border-radius: 10px;
687
- padding: 15px;
688
- margin-bottom: 20px;
689
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
690
- }
691
- body.dark .package-install-card {
692
- background: rgba(255, 255, 255, 0.05);
693
- border-color: rgba(255, 255, 255, 0.08);
694
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
695
- }
696
- .package-install-form {
697
- display: flex;
698
- flex-wrap: wrap;
699
- gap: 15px;
700
- align-items: flex-end;
701
- }
702
- .package-install-field {
703
- flex: 1 1 220px;
704
- display: flex;
705
- flex-direction: column;
706
- gap: 6px;
707
- }
708
- .package-install-label {
709
- font-size: 12px;
710
- font-weight: 600;
711
- text-transform: uppercase;
712
- letter-spacing: 0.06em;
713
- color: rgba(0, 0, 0, 0.55);
714
- }
715
- body.dark .package-install-label {
716
- color: rgba(255, 255, 255, 0.65);
717
- }
718
- .package-install-input {
719
- border-radius: 8px;
720
- border: 1px solid rgba(0, 0, 0, 0.14);
721
- padding: 10px 12px;
722
- font-size: 14px;
723
- background: rgba(255, 255, 255, 0.95);
724
- color: rgba(15, 23, 42, 0.95);
725
- transition: border-color 0.2s ease, box-shadow 0.2s ease;
726
- }
727
- body.dark .package-install-input {
728
- background: rgba(15, 23, 42, 0.85);
729
- border-color: rgba(148, 163, 184, 0.3);
730
- color: rgba(226, 232, 240, 0.92);
731
- }
732
- .package-install-input:focus {
733
- outline: none;
734
- border-color: rgba(59, 130, 246, 0.6);
735
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
736
- }
737
- body.dark .package-install-input:focus {
738
- border-color: rgba(96, 165, 250, 0.7);
739
- box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.25);
740
- }
741
- .package-install-submit {
742
- flex: 0 0 auto;
743
- display: inline-flex;
744
- align-items: center;
745
- gap: 8px;
746
- padding: 11px 18px;
747
- font-size: 14px;
748
- font-weight: 600;
749
- border-radius: 8px;
750
- border: none;
751
- cursor: pointer;
752
- background: rgba(59, 130, 246, 0.9);
753
- color: #ffffff;
754
- transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease;
755
- }
756
- .package-install-submit:hover {
757
- background: rgba(37, 99, 235, 0.95);
758
- box-shadow: 0 8px 18px rgba(37, 99, 235, 0.25);
759
- transform: translateY(-1px);
760
- }
761
- .package-install-submit:active {
762
- transform: translateY(0);
763
- box-shadow: none;
764
- }
765
- .package-install-submit i {
766
- font-size: 16px;
767
- }
768
- .package-install-description {
769
- margin-top: 12px;
770
- font-size: 12px;
771
- color: rgba(0, 0, 0, 0.55);
772
- }
773
- body.dark .package-install-description {
774
- color: rgba(255, 255, 255, 0.55);
775
- }
776
- .package-install-description code {
777
- font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
778
- background: rgba(15, 23, 42, 0.06);
779
- padding: 2px 6px;
780
- border-radius: 6px;
781
- color: inherit;
782
- }
783
- body.dark .package-install-description code {
784
- background: rgba(255, 255, 255, 0.08);
785
- }
786
-
787
- @media only screen and (max-width: 720px) {
788
- .package-install-form {
789
- flex-direction: column;
790
- align-items: stretch;
791
- }
792
- .package-install-field {
793
- width: 100%;
794
- }
795
- .package-install-submit {
796
- width: 100%;
797
- justify-content: center;
798
- }
799
- }
800
-
801
- :root {
802
- --pinokio-modal-bg: #ffffff;
803
- --pinokio-modal-text: #0f172a;
804
- --pinokio-modal-title-color: #0f172a;
805
- --pinokio-modal-subtitle-color: rgba(71, 85, 105, 0.75);
806
- --pinokio-modal-body-bg: rgba(241, 245, 249, 0.9);
807
- --pinokio-modal-body-border: rgba(203, 213, 225, 0.7);
808
- --pinokio-modal-body-iframe-bg: #ffffff;
809
- --pinokio-modal-label-color: rgba(71, 85, 105, 0.95);
810
- --pinokio-modal-input-bg: #ffffff;
811
- --pinokio-modal-input-border: rgba(148, 163, 184, 0.5);
812
- --pinokio-modal-input-text: #0f172a;
813
- --pinokio-modal-input-focus-border: rgba(59, 130, 246, 0.6);
814
- --pinokio-modal-input-focus-shadow: rgba(59, 130, 246, 0.2);
815
- --pinokio-modern-shadow: 0 32px 80px rgba(15, 23, 42, 0.25);
816
- --pinokio-modern-close-color: rgba(71, 85, 105, 0.65);
817
- --pinokio-modern-close-hover-bg: rgba(148, 163, 184, 0.18);
818
- --pinokio-modern-close-hover-color: #0f172a;
819
- }
820
- body.dark {
821
- --pinokio-modal-bg: #0f172a;
822
- --pinokio-modal-text: #e2e8f0;
823
- --pinokio-modal-title-color: #f8fafc;
824
- --pinokio-modal-subtitle-color: rgba(148, 163, 184, 0.85);
825
- --pinokio-modal-body-bg: rgba(15, 23, 42, 0.6);
826
- --pinokio-modal-body-border: rgba(59, 130, 246, 0.18);
827
- --pinokio-modal-body-iframe-bg: #0b1220;
828
- --pinokio-modal-label-color: rgba(203, 213, 225, 0.9);
829
- --pinokio-modal-input-bg: rgba(15, 23, 42, 0.85);
830
- --pinokio-modal-input-border: rgba(148, 163, 184, 0.25);
831
- --pinokio-modal-input-text: #f8fafc;
832
- --pinokio-modal-input-focus-border: rgba(96, 165, 250, 0.7);
833
- --pinokio-modal-input-focus-shadow: rgba(37, 99, 235, 0.25);
834
- --pinokio-modern-shadow: 0 40px 120px rgba(2, 8, 23, 0.7);
835
- --pinokio-modern-close-color: rgba(226, 232, 240, 0.6);
836
- --pinokio-modern-close-hover-bg: rgba(148, 163, 184, 0.12);
837
- --pinokio-modern-close-hover-color: #f8fafc;
838
- }
839
-
840
- .pinokio-modern-modal.swal2-popup {
841
- background: var(--pinokio-modal-bg) !important;
842
- color: var(--pinokio-modal-text) !important;
843
- border-radius: 18px;
844
- padding: 0 !important;
845
- box-shadow: var(--pinokio-modern-shadow) !important;
846
- overflow: hidden;
847
- }
848
- .pinokio-modern-html {
849
- padding: 0 !important;
850
- }
851
- .pinokio-modern-close {
852
- color: var(--pinokio-modern-close-color) !important;
853
- border-radius: 50%;
854
- width: 34px;
855
- height: 34px;
856
- display: flex !important;
857
- align-items: center;
858
- justify-content: center;
859
- transition: background 0.2s ease, color 0.2s ease;
860
- }
861
- .pinokio-modern-close:hover {
862
- background: var(--pinokio-modern-close-hover-bg) !important;
863
- color: var(--pinokio-modern-close-hover-color) !important;
864
- }
865
-
866
- .pinokio-modal-surface {
867
- display: flex;
868
- flex-direction: column;
869
- gap: 0;
870
- color: var(--pinokio-modal-text);
871
- }
872
- .pinokio-modal-header {
873
- display: flex;
874
- align-items: center;
875
- gap: 16px;
876
- padding: 22px;
877
- }
878
- .pinokio-modal-icon {
879
- width: 48px;
880
- height: 48px;
881
- border-radius: 16px;
882
- display: flex;
883
- align-items: center;
884
- justify-content: center;
885
- font-size: 22px;
886
- background: rgba(59, 130, 246, 0.12);
887
- color: rgba(37, 99, 235, 0.95);
888
- }
889
- body.dark .pinokio-modal-icon {
890
- background: rgba(59, 130, 246, 0.18);
891
- color: rgba(147, 197, 253, 0.95);
892
- }
893
- .pinokio-modal-heading {
894
- text-align: left;
895
- flex: 1;
896
- display: flex;
897
- flex-direction: column;
898
- gap: 6px;
899
- }
900
- .pinokio-modal-title {
901
- font-size: 20px;
902
- font-weight: 700;
903
- color: var(--pinokio-modal-title-color);
904
- }
905
- .pinokio-modal-subtitle {
906
- font-size: 13px;
907
- color: var(--pinokio-modal-subtitle-color);
908
- line-height: 1.4;
909
- }
910
- .pinokio-modal-subtitle code {
911
- font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
912
- font-size: 12px;
913
- padding: 2px 6px;
914
- border-radius: 6px;
915
- background: rgba(15, 23, 42, 0.08);
916
- color: inherit;
917
- }
918
- body.dark .pinokio-modal-subtitle code {
919
- background: rgba(15, 23, 42, 0.4);
920
- }
921
- .pinokio-modal-body {
922
- background: var(--pinokio-modal-body-bg);
923
- border-top: 1px solid var(--pinokio-modal-body-border);
924
- padding: 10px;
925
- }
926
- .pinokio-modal-body--iframe {
927
- height: 520px;
928
- background: var(--pinokio-modal-body-iframe-bg);
929
- }
930
- .pinokio-modal-body--iframe iframe {
931
- width: 100%;
932
- height: 100%;
933
- border: none;
934
- background: transparent;
935
- }
936
-
937
- main {
938
- display: flex;
939
- }
940
- aside {
941
- width: 200px;
942
- display: block;
943
- flex-shrink: 0;
944
- }
945
- aside .tab i {
946
- width: 20px;
947
- text-align: center;
948
- }
949
- body.dark aside .tab {
950
- color: white;
951
- }
952
- body.dark aside .tab:hover, aside .tab:hover {
953
- color: royalblue !important;
954
- opacity: 1;
955
- }
956
- aside .tab {
957
- display: flex;
958
- align-items: center;
959
- gap: 5px;
960
- color: black;
961
- text-decoration: none;
962
- padding: 10px;
963
- font-size: 12px;
964
- opacity: 0.5;
965
- border-left: 10px solid transparent;
966
- }
967
- body.dark aside .tab.selected {
968
- color: white;
969
- }
970
- aside .selected {
971
- font-weight: bold;
972
- opacity: 1;
973
- }
974
- @media only screen and (max-width: 600px) {
975
- .bundle-header {
976
- display: block;
977
- }
978
- .bundle-status {
979
- margin-top: 10px;
980
- }
981
- .bundles-grid {
982
- grid-template-columns: minmax(0, 1fr);
983
- }
984
- aside {
985
- width: unset;
986
- flex-shrink: unset;
987
- }
988
- aside {
989
- padding: 0 10px;
990
- }
991
- aside .tab i {
992
- width: 100%;
993
- }
994
- aside .tab .caption {
995
- display: none;
996
- }
997
- aside .tab {
998
- margin: 0;
999
- padding: 10px;
1000
- border-left: none;
1001
- }
1002
- aside .btn-tab {
1003
- flex-direction: column;
1004
- padding: 10px 0;
1005
- }
1006
- aside .btn-tab .btn {
1007
- display: flex;
1008
- justify-content: center;
1009
- }
1010
- aside .btn-tab .btn .caption {
1011
- display: none;
1012
- }
1013
- header .flexible {
1014
- min-width: unset;
1015
- }
1016
- }
1017
- @media only screen and (max-width: 480px) {
1018
- .btn2 {
1019
- padding: 5px;
1020
- font-size: 11px;
1021
- }
1022
- .nav-btns {
1023
- flex-grow: 1;
1024
- justify-content: center;
1025
- padding: 0;
1026
- }
1027
- }
1028
-
1029
- /*
1030
- @media only screen and (max-width: 800px) {
1031
- body {
1032
- display: flex !important;
1033
- flex-direction: row !important;
1034
- }
1035
- }
1036
- */
1037
- @media only screen and (max-width: 600px) {
1038
- aside {
1039
- width: unset;
1040
- flex-shrink: unset;
1041
- }
1042
- aside {
1043
- padding: 0 10px;
1044
- }
1045
- aside .tab.submenu {
1046
- padding: 10px;
1047
- }
1048
- aside .tab i {
1049
- width: 100%;
1050
- }
1051
- aside .tab .caption {
1052
- display: none;
1053
- }
1054
- aside .tab {
1055
- margin: 0;
1056
- padding: 10px;
1057
- border-left: none;
1058
- }
1059
- aside .btn-tab {
1060
- flex-direction: column;
1061
- padding: 10px 0;
1062
- }
1063
- aside .btn-tab .btn {
1064
- display: flex;
1065
- justify-content: center;
1066
- }
1067
- aside .btn-tab .btn .caption {
1068
- display: none;
1069
- }
1070
- header .flexible {
1071
- min-width: unset;
1072
- }
1073
- }
1074
- body.tools-page .task-container {
1075
- padding: 20px 28px 32px;
1076
- }
1077
- body.tools-page .task-shell {
1078
- width: min(1140px, 100%);
1079
- margin: 0;
1080
- background: transparent;
1081
- border: 0;
1082
- border-radius: 0;
1083
- box-shadow: none;
1084
- overflow: visible;
1085
- backdrop-filter: none;
1086
- }
1087
- body.tools-page .task-shell-header {
1088
- padding-bottom: 18px;
1089
- }
1090
- body.tools-page .task-shell-body {
1091
- padding: 0 28px 28px;
1092
- }
1093
- body.tools-page .menu-container {
1094
- display: grid;
1095
- gap: 28px;
1096
- }
1097
- body.tools-page .loading-container {
1098
- min-height: 320px;
1099
- border: 1px solid var(--task-border);
1100
- border-radius: 18px;
1101
- background: color-mix(in srgb, var(--task-panel) 94%, var(--task-soft));
1102
- }
1103
- body.tools-page .loading-spinner {
1104
- border-color: color-mix(in srgb, var(--task-text) 10%, transparent);
1105
- border-left-color: var(--task-accent);
1106
- }
1107
- body.dark.tools-page .loading-spinner {
1108
- border-color: color-mix(in srgb, white 10%, transparent);
1109
- border-left-color: var(--task-accent);
1110
- }
1111
- body.tools-page .loading-text,
1112
- body.tools-page .loading-subtext,
1113
- body.dark.tools-page .loading-text,
1114
- body.dark.tools-page .loading-subtext {
1115
- color: var(--task-muted);
1116
- }
1117
- body.tools-page .bundles-section {
1118
- margin: 0;
1119
- }
1120
- body.tools-page .bundles-header {
1121
- margin: 0 0 14px;
1122
- padding: 0;
1123
- border: 0;
1124
- }
1125
- body.tools-page .bundles-header h3 {
1126
- margin: 0;
1127
- font-size: 18px;
1128
- font-weight: 700;
1129
- color: var(--task-text);
1130
- }
1131
- body.dark.tools-page .bundles-header h3 {
1132
- color: var(--task-text);
1133
- }
1134
- body.tools-page .bundles-grid {
1135
- gap: 14px;
1136
- grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
1137
- }
1138
- body.tools-page .bundle-card {
1139
- border: 1px solid var(--task-border);
1140
- border-left: 0;
1141
- border-radius: 16px;
1142
- padding: 18px;
1143
- background: color-mix(in srgb, var(--task-panel) 94%, var(--task-soft));
1144
- }
1145
- body.tools-page .bundle-card.installed {
1146
- box-shadow: inset 0 0 0 1px color-mix(in srgb, #22c55e 22%, transparent);
1147
- }
1148
- body.tools-page .bundle-card.pending {
1149
- box-shadow: inset 0 0 0 1px color-mix(in srgb, #f59e0b 24%, transparent);
1150
- }
1151
- body.tools-page .bundle-icon,
1152
- body.dark.tools-page .bundle-icon {
1153
- background: var(--task-accent-surface);
1154
- color: var(--task-accent-contrast);
1155
- border-radius: 12px;
1156
- }
1157
- body.tools-page .bundle-name,
1158
- body.dark.tools-page .bundle-name {
1159
- color: var(--task-text);
1160
- }
1161
- body.tools-page .bundle-code,
1162
- body.tools-page .bundle-description,
1163
- body.tools-page .bundle-packages-title,
1164
- body.dark.tools-page .bundle-code,
1165
- body.dark.tools-page .bundle-description,
1166
- body.dark.tools-page .bundle-packages-title {
1167
- color: var(--task-muted);
1168
- }
1169
- body.tools-page .bundle-status {
1170
- padding: 4px 10px;
1171
- border-radius: 999px;
1172
- font-size: 11px;
1173
- letter-spacing: 0.02em;
1174
- }
1175
- body.tools-page .bundle-status.installed {
1176
- background: color-mix(in srgb, #22c55e 14%, var(--task-panel));
1177
- color: #15803d;
1178
- }
1179
- body.dark.tools-page .bundle-status.installed {
1180
- color: #86efac;
1181
- }
1182
- body.tools-page .bundle-status.pending {
1183
- background: color-mix(in srgb, #f59e0b 14%, var(--task-panel));
1184
- color: #b45309;
1185
- }
1186
- body.dark.tools-page .bundle-status.pending {
1187
- color: #fcd34d;
1188
- }
1189
- body.tools-page .package-badge,
1190
- body.dark.tools-page .package-badge {
1191
- background: color-mix(in srgb, var(--task-panel) 94%, var(--task-soft));
1192
- color: var(--task-text);
1193
- border: 1px solid var(--task-border);
1194
- border-radius: 999px;
1195
- padding: 3px 8px;
1196
- }
1197
- body.tools-page .package-badge.installed {
1198
- background: color-mix(in srgb, #22c55e 12%, var(--task-panel));
1199
- color: #15803d;
1200
- border-color: color-mix(in srgb, #22c55e 28%, transparent);
1201
- }
1202
- body.dark.tools-page .package-badge.installed {
1203
- color: #86efac;
1204
- }
1205
- body.tools-page .package-badge.not-installed {
1206
- background: color-mix(in srgb, #ef4444 10%, var(--task-panel));
1207
- color: #b91c1c;
1208
- border-color: color-mix(in srgb, #ef4444 24%, transparent);
1209
- }
1210
- body.dark.tools-page .package-badge.not-installed {
1211
- color: #fca5a5;
1212
- }
1213
- body.tools-page .bundle-install-btn,
1214
- body.tools-page .package-install-submit {
1215
- border-radius: 12px;
1216
- background: var(--task-accent-surface);
1217
- color: var(--task-accent-contrast);
1218
- }
1219
- body.tools-page .bundle-install-btn:hover,
1220
- body.tools-page .package-install-submit:hover {
1221
- background: var(--task-accent-hover);
1222
- }
1223
- body.tools-page .bundle-install-btn.installed {
1224
- background: color-mix(in srgb, var(--task-panel) 94%, var(--task-soft));
1225
- border: 1px solid var(--task-border);
1226
- color: var(--task-muted);
1227
- }
1228
- body.tools-page .package-tabs {
1229
- margin: 0;
1230
- border: 0;
1231
- display: flex;
1232
- flex-wrap: wrap;
1233
- gap: 8px;
1234
- }
1235
- body.tools-page .package-tab,
1236
- body.dark.tools-page .package-tab {
1237
- margin: 0;
1238
- padding: 8px 12px;
1239
- border-radius: 999px;
1240
- border: 1px solid var(--task-border);
1241
- background: transparent;
1242
- color: var(--task-muted);
1243
- }
1244
- body.tools-page .package-tab:hover {
1245
- background: color-mix(in srgb, var(--task-panel) 92%, var(--task-soft));
1246
- color: var(--task-text);
1247
- }
1248
- body.tools-page .package-tab.active {
1249
- background: var(--task-accent-surface);
1250
- color: var(--task-accent-contrast);
1251
- border-color: transparent;
1252
- }
1253
- body.tools-page .section-header {
1254
- padding: 0 0 10px;
1255
- }
1256
- body.tools-page .section-header h2 {
1257
- font-size: 18px;
1258
- font-weight: 700;
1259
- color: var(--task-text);
1260
- }
1261
- body.tools-page .section-header .section div {
1262
- color: var(--task-muted);
1263
- }
1264
- body.tools-page .package-install-card {
1265
- margin-bottom: 16px;
1266
- border: 1px solid var(--task-border);
1267
- border-radius: 16px;
1268
- background: color-mix(in srgb, var(--task-panel) 94%, var(--task-soft));
1269
- box-shadow: none;
1270
- }
1271
- body.tools-page .package-install-label,
1272
- body.dark.tools-page .package-install-label {
1273
- color: var(--task-muted);
1274
- }
1275
- body.tools-page .package-install-input {
1276
- border-radius: 12px;
1277
- border-color: var(--task-border-strong);
1278
- background: color-mix(in srgb, var(--task-panel) 98%, white);
1279
- color: var(--task-text);
1280
- }
1281
- body.dark.tools-page .package-install-input {
1282
- background: color-mix(in srgb, var(--task-panel) 96%, black);
1283
- color: var(--task-text);
1284
- }
1285
- body.tools-page .package-install-input:focus,
1286
- body.dark.tools-page .package-install-input:focus {
1287
- border-color: var(--task-accent);
1288
- }
1289
- body.tools-page .package-install-description,
1290
- body.dark.tools-page .package-install-description {
1291
- color: var(--task-muted);
1292
- }
1293
- body.tools-page .package-install-description code {
1294
- background: color-mix(in srgb, var(--task-panel) 94%, var(--task-soft));
1295
- }
1296
- body.tools-page .tab-content {
1297
- gap: 8px;
1298
- }
1299
- body.tools-page .tab-content .tab,
1300
- body.dark.tools-page .tab-content .tab {
1301
- padding: 12px 14px;
1302
- border: 1px solid var(--task-border);
1303
- border-radius: 14px;
1304
- background: color-mix(in srgb, var(--task-panel) 96%, var(--task-soft));
1305
- color: var(--task-text);
1306
- }
1307
- body.tools-page .tab .col {
1308
- margin-left: 8px;
1309
- }
1310
- body.tools-page .tab h2 {
1311
- font-size: 14px;
1312
- color: inherit;
1313
- }
1314
- body.tools-page .tab .emoji {
1315
- width: 34px;
1316
- height: 34px;
1317
- border-radius: 10px;
1318
- background: color-mix(in srgb, var(--task-accent) 12%, transparent);
1319
- color: var(--task-accent);
1320
- }
1321
- @media only screen and (max-width: 820px) {
1322
- body.tools-page .task-shell-header,
1323
- body.tools-page .task-shell-body {
1324
- padding-left: 18px;
1325
- padding-right: 18px;
1326
- }
1327
- }
1328
- @media only screen and (max-width: 560px) {
1329
- body.tools-page .task-container {
1330
- padding: 16px 14px 24px;
1331
- }
1332
- body.tools-page .task-shell-body {
1333
- padding-bottom: 22px;
1334
- }
1335
- }
1336
- </style>
1337
- <script src="/window_storage.js"></script>
1338
- <script src="/popper.min.js"></script>
1339
- <script src="/tippy-bundle.umd.min.js"></script>
1340
- <script src="/hotkeys.min.js"></script>
1341
- <script src="/sweetalert2.js"></script>
1342
- <script src="/noty.js"></script>
1343
- <script src="/notyq.js"></script>
1344
- <script src="/xterm.js"></script>
1345
- <script src="/xterm-addon-fit.js"></script>
1346
- <script src="/xterm-addon-web-links.js"></script>
1347
- <script src="/xterm-theme.js"></script>
1348
- <script src="/install.js"></script>
1349
- <script src="/timeago.min.js"></script>
1350
- <script src="/common.js"></script>
1351
- <script src="/opener.js"></script>
1352
- <script src="/nav.js"></script>
1353
- <script src="/urldropdown.js"></script>
1354
- <script>
1355
- // Initialize URL Dropdown with empty behavior for tools page
1356
- document.addEventListener('DOMContentLoaded', function() {
1357
- initUrlDropdown({
1358
- clearBehavior: 'empty'
1359
- });
1360
- });
1361
- </script>
1362
- <script src="/report.js"></script>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Tools</title>
7
+ <link href="/css/fontawesome.min.css" rel="stylesheet">
8
+ <link href="/css/solid.min.css" rel="stylesheet">
9
+ <link href="/css/brands.min.css" rel="stylesheet">
10
+ <link href="/noty.css" rel="stylesheet">
11
+ <link href="/style.css" rel="stylesheet">
12
+ <link href="/task-launcher.css" rel="stylesheet">
13
+ <% if (agent === "electron") { %>
14
+ <link href="/electron.css" rel="stylesheet"/>
15
+ <% } %>
16
+ <style>
17
+ body.tools-page .task-shell-header-main {
18
+ max-width: 720px;
19
+ }
20
+ body.tools-page .task-description {
21
+ max-width: 70ch;
22
+ }
23
+ body.tools-page .tools-summary {
24
+ align-items: flex-start;
25
+ }
26
+ body.tools-page .tools-summary-item {
27
+ display: inline-flex;
28
+ align-items: center;
29
+ gap: 4px;
30
+ min-height: 26px;
31
+ box-sizing: border-box;
32
+ padding: 0 9px;
33
+ border: 1px solid var(--task-border);
34
+ border-radius: 999px;
35
+ color: var(--task-muted);
36
+ font-size: 11px;
37
+ line-height: 1;
38
+ white-space: nowrap;
39
+ }
40
+ body.tools-page .tools-summary-item strong {
41
+ color: var(--task-text);
42
+ font-weight: 700;
43
+ }
44
+ body.tools-page .tools-loading-state {
45
+ display: grid;
46
+ grid-template-columns: auto minmax(0, 1fr);
47
+ gap: 12px;
48
+ align-items: center;
49
+ min-height: 150px;
50
+ box-sizing: border-box;
51
+ padding: 18px;
52
+ border: 1px solid var(--task-border);
53
+ border-radius: 8px;
54
+ background: color-mix(in srgb, var(--task-panel) 96%, var(--task-soft));
55
+ }
56
+ body.tools-page .tools-loading-icon {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ width: 34px;
61
+ height: 34px;
62
+ border-radius: 8px;
63
+ background: color-mix(in srgb, var(--task-accent) 10%, transparent);
64
+ color: var(--task-accent);
65
+ font-size: 15px;
66
+ }
67
+ body.tools-page .tools-loading-title,
68
+ body.tools-page .tools-bundle-title,
69
+ body.tools-page .tools-module-title {
70
+ color: var(--task-text);
71
+ font-size: 14px;
72
+ font-weight: 700;
73
+ line-height: 1.25;
74
+ letter-spacing: -0.02em;
75
+ }
76
+ body.tools-page .tools-loading-copy {
77
+ display: grid;
78
+ gap: 4px;
79
+ min-width: 0;
80
+ }
81
+ body.tools-page .tools-loading-text,
82
+ body.tools-page .tools-bundle-code,
83
+ body.tools-page .tools-bundle-description {
84
+ margin: 0;
85
+ color: var(--task-muted);
86
+ font-size: 12px;
87
+ line-height: 1.45;
88
+ }
89
+ body.tools-page .tools-bundle-list {
90
+ display: grid;
91
+ gap: 10px;
92
+ }
93
+ body.tools-page .tools-bundle-row {
94
+ display: grid;
95
+ grid-template-columns: minmax(0, 1fr) auto;
96
+ gap: 14px;
97
+ align-items: start;
98
+ border: 1px solid var(--task-border);
99
+ border-radius: 8px;
100
+ padding: 14px;
101
+ background: transparent;
102
+ }
103
+ body.tools-page .tools-bundle-main {
104
+ display: grid;
105
+ gap: 10px;
106
+ min-width: 0;
107
+ }
108
+ body.tools-page .tools-bundle-title-line {
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 8px;
112
+ flex-wrap: wrap;
113
+ min-width: 0;
114
+ }
115
+ body.tools-page .tools-bundle-title {
116
+ font-size: 16px;
117
+ line-height: 1.2;
118
+ font-weight: 650;
119
+ }
120
+ body.tools-page .tools-bundle-title,
121
+ body.tools-page .tools-module-title {
122
+ overflow-wrap: anywhere;
123
+ }
124
+ body.tools-page .tools-bundle-code {
125
+ display: inline-flex;
126
+ align-items: center;
127
+ margin: 0;
128
+ color: var(--task-muted);
129
+ font-size: 12px;
130
+ line-height: 1.2;
131
+ }
132
+ body.tools-page .tools-bundle-description {
133
+ max-width: 72ch;
134
+ font-size: 13px;
135
+ line-height: 1.45;
136
+ opacity: 0.72;
137
+ }
138
+ body.tools-page .tools-bundle-detail {
139
+ display: flex;
140
+ gap: 8px;
141
+ align-items: baseline;
142
+ min-width: 0;
143
+ font-size: 12px;
144
+ }
145
+ body.tools-page .tools-bundle-detail-label {
146
+ flex: 0 0 auto;
147
+ width: 108px;
148
+ color: var(--task-muted);
149
+ opacity: 0.72;
150
+ }
151
+ body.tools-page .tools-bundle-actions {
152
+ display: grid;
153
+ gap: 8px;
154
+ justify-items: end;
155
+ min-width: 118px;
156
+ }
157
+ body.tools-page .tools-bundle-status {
158
+ display: inline-flex;
159
+ align-items: center;
160
+ min-height: 26px;
161
+ color: var(--task-muted);
162
+ font-size: 12px;
163
+ line-height: 1.2;
164
+ font-weight: 500;
165
+ white-space: nowrap;
166
+ }
167
+ body.tools-page .tools-bundle-status.good {
168
+ color: #166534;
169
+ }
170
+ body.tools-page .tools-bundle-status.warn {
171
+ color: #9a3412;
172
+ }
173
+ body.dark.tools-page .tools-bundle-status.good {
174
+ color: #86efac;
175
+ }
176
+ body.dark.tools-page .tools-bundle-status.warn {
177
+ color: #fdba74;
178
+ }
179
+ body.tools-page .tools-package-list {
180
+ display: flex;
181
+ flex-wrap: wrap;
182
+ gap: 5px 13px;
183
+ min-width: 0;
184
+ }
185
+ body.tools-page .tools-package-pill {
186
+ display: inline-flex;
187
+ align-items: center;
188
+ gap: 5px;
189
+ max-width: 100%;
190
+ min-height: 18px;
191
+ color: var(--task-muted);
192
+ font-size: 12px;
193
+ line-height: 1.25;
194
+ }
195
+ body.tools-page .tools-package-pill span:last-child {
196
+ min-width: 0;
197
+ overflow: hidden;
198
+ text-overflow: ellipsis;
199
+ white-space: nowrap;
200
+ }
201
+ body.tools-page .tools-package-pill.is-installed {
202
+ color: var(--task-muted);
203
+ }
204
+ body.tools-page .tools-package-pill.needs-install {
205
+ color: #b91c1c;
206
+ }
207
+ body.dark.tools-page .tools-package-pill.needs-install {
208
+ color: #fca5a5;
209
+ }
210
+ body.tools-page .tools-package-pill i {
211
+ width: 12px;
212
+ font-size: 11px;
213
+ text-align: center;
214
+ flex: 0 0 auto;
215
+ }
216
+ body.tools-page .tools-package-pill.is-installed i {
217
+ color: #15803d;
218
+ }
219
+ body.dark.tools-page .tools-package-pill.is-installed i {
220
+ color: #86efac;
221
+ }
222
+ body.tools-page .tools-tabs {
223
+ margin-bottom: 12px;
224
+ }
225
+ body.tools-page .tools-module-section-head {
226
+ align-items: center;
227
+ }
228
+ body.tools-page .tools-tab-button {
229
+ appearance: none;
230
+ cursor: pointer;
231
+ gap: 6px;
232
+ text-transform: none;
233
+ }
234
+ body.tools-page .tools-tab-count {
235
+ color: var(--task-muted);
236
+ font-weight: 600;
237
+ }
238
+ body.tools-page .tools-tab-button.active .tools-tab-count {
239
+ color: inherit;
240
+ }
241
+ body.tools-page .tools-install-open {
242
+ min-height: 30px;
243
+ padding: 0 10px;
244
+ font-size: 12px;
245
+ white-space: nowrap;
246
+ }
247
+ body.tools-page .tools-package-panel {
248
+ display: none;
249
+ }
250
+ body.tools-page .tools-package-panel.active {
251
+ display: grid;
252
+ gap: 10px;
253
+ }
254
+ body.tools-page .tools-install-form {
255
+ display: grid;
256
+ gap: 12px;
257
+ }
258
+ body.tools-page .tools-install-submit {
259
+ min-width: 96px;
260
+ min-height: 38px;
261
+ font-size: 12px;
262
+ font-weight: 650;
263
+ }
264
+ body.tools-page .tools-install-actions {
265
+ display: flex;
266
+ justify-content: flex-end;
267
+ gap: 8px;
268
+ }
269
+ body.tools-page .tools-module-list {
270
+ display: grid;
271
+ gap: 6px;
272
+ }
273
+ body.tools-page .tools-module-row {
274
+ display: grid;
275
+ grid-template-columns: minmax(0, 1fr);
276
+ padding: 10px 0;
277
+ border-top: 1px solid var(--task-border);
278
+ }
279
+ body.tools-page .tools-module-row:first-child {
280
+ padding-top: 0;
281
+ border-top: 0;
282
+ }
283
+ body.tools-page .tools-module-row:last-child {
284
+ padding-bottom: 0;
285
+ }
286
+ body.tools-page .pinokio-modern-modal.swal2-popup {
287
+ background: var(--task-panel) !important;
288
+ color: var(--task-text) !important;
289
+ border: 1px solid var(--task-border) !important;
290
+ border-radius: 8px !important;
291
+ box-shadow: var(--task-shadow) !important;
292
+ padding: 0 !important;
293
+ overflow: hidden !important;
294
+ }
295
+ body.tools-page .pinokio-modern-html {
296
+ padding: 0 !important;
297
+ margin: 0 !important;
298
+ }
299
+ body.tools-page .pinokio-modern-close {
300
+ top: 10px !important;
301
+ right: 10px !important;
302
+ width: 28px !important;
303
+ height: 28px !important;
304
+ border-radius: 6px !important;
305
+ color: var(--task-muted) !important;
306
+ }
307
+ body.tools-page .pinokio-modern-close:hover,
308
+ body.tools-page .pinokio-modern-close:focus-visible {
309
+ background: var(--task-soft) !important;
310
+ color: var(--task-text) !important;
311
+ outline: none !important;
312
+ }
313
+ body.tools-page .pinokio-modal-surface {
314
+ display: grid;
315
+ background: var(--task-panel);
316
+ color: var(--task-text);
317
+ }
318
+ body.tools-page .pinokio-modal-header {
319
+ display: grid;
320
+ grid-template-columns: auto minmax(0, 1fr);
321
+ gap: 10px;
322
+ align-items: start;
323
+ padding: 14px;
324
+ }
325
+ body.tools-page .pinokio-modal-icon {
326
+ display: inline-flex;
327
+ align-items: center;
328
+ justify-content: center;
329
+ width: 32px;
330
+ height: 32px;
331
+ border-radius: 7px;
332
+ background: color-mix(in srgb, var(--task-accent) 10%, transparent);
333
+ color: var(--task-accent);
334
+ font-size: 14px;
335
+ }
336
+ body.tools-page .pinokio-modal-heading {
337
+ display: grid;
338
+ gap: 4px;
339
+ min-width: 0;
340
+ text-align: left;
341
+ }
342
+ body.tools-page .pinokio-modal-title {
343
+ color: var(--task-text);
344
+ font-size: 16px;
345
+ font-weight: 700;
346
+ line-height: 1.25;
347
+ }
348
+ body.tools-page .pinokio-modal-subtitle {
349
+ color: var(--task-muted);
350
+ font-size: 12px;
351
+ line-height: 1.45;
352
+ overflow-wrap: anywhere;
353
+ }
354
+ body.tools-page .pinokio-modal-subtitle code {
355
+ padding: 2px 6px;
356
+ border-radius: 6px;
357
+ background: color-mix(in srgb, var(--task-panel) 88%, var(--task-soft));
358
+ color: inherit;
359
+ }
360
+ body.tools-page .pinokio-modal-body {
361
+ border-top: 1px solid var(--task-border);
362
+ background: color-mix(in srgb, var(--task-panel) 96%, var(--task-soft));
363
+ }
364
+ body.tools-page .pinokio-modal-body--iframe {
365
+ height: 520px;
366
+ }
367
+ body.tools-page .pinokio-modal-body--iframe iframe {
368
+ width: 100%;
369
+ height: 100%;
370
+ border: 0;
371
+ background: var(--task-panel);
372
+ }
373
+ @media (max-width: 760px) {
374
+ body.tools-page .tools-bundle-row {
375
+ grid-template-columns: 1fr;
376
+ }
377
+ body.tools-page .tools-bundle-actions {
378
+ justify-items: start;
379
+ min-width: 0;
380
+ }
381
+ body.tools-page .tools-bundle-detail {
382
+ display: grid;
383
+ gap: 6px;
384
+ }
385
+ body.tools-page .tools-bundle-detail-label {
386
+ width: auto;
387
+ }
388
+ body.tools-page .tools-module-section-head {
389
+ display: grid;
390
+ gap: 10px;
391
+ }
392
+ body.tools-page .tools-install-open,
393
+ body.tools-page .tools-install-submit {
394
+ width: 100%;
395
+ }
396
+ body.tools-page .tools-install-actions {
397
+ display: grid;
398
+ grid-template-columns: 1fr;
399
+ }
400
+ }
401
+ </style>
1363
402
  </head>
1364
- <body class='<%=theme%> main-sidebar-page task-launcher-page task-page tools-page' data-agent="<%=agent%>">
1365
- <!--
1366
- <nav>
1367
- <a class='logo' href="/home">dal</a>
1368
- </nav>
1369
- -->
1370
- <header class='navheader grabbable'>
1371
- <h1>
1372
- <a class='home' href="/home"><img class='icon' src="/pinokio-black.png"></a>
1373
- <button class='btn2' id='minimize-header' data-tippy-content="fullscreen" title='fullscreen'>
1374
- <div><i class="fa-solid fa-expand"></i></div>
1375
- </button>
1376
- <button class='btn2' id='back' data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
1377
- <button class='btn2' id='forward' data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
1378
- <button class='btn2' id='refresh-page' data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
1379
- <button class='btn2' id='screenshot' data-tippy-content="screen capture"><i class="fa-solid fa-camera"></i></button>
1380
- <button class='btn2' id='inspector' data-tippy-content="X-ray mode"><i class="fa-solid fa-eye"></i></button>
1381
- <button class='btn2 mobile-link-button' id='mobile-link-button' data-tippy-content="enter url"><i class="fa-solid fa-link"></i></button>
1382
- <div class='mobile-flexible'></div>
1383
- <form class='urlbar'>
1384
- <div class='url-input-container'>
1385
- <input type='url' placeholder='enter any local url to open in pinokio'>
1386
- <div class='url-dropdown' id='url-dropdown'></div>
1387
- </div>
1388
- </form>
1389
- <a class='btn2' href="/columns" data-tippy-content="split into 2 columns">
1390
- <div><i class="fa-solid fa-table-columns"></i></div>
1391
- </a>
1392
- <a class='btn2' href="/rows" data-tippy-content="split into 2 rows">
1393
- <div><i class="fa-solid fa-table-columns fa-rotate-270"></i></div>
1394
- </a>
1395
- <button class='btn2' id='new-window' data-tippy-content="open a new window" title='open a new window' data-agent="<%=agent%>">
1396
- <div><i class="fa-solid fa-plus"></i></div>
1397
- </button>
1398
- <button class='btn2 hidden' id='close-window' data-tippy-content='close this section'>
1399
- <div><i class="fa-solid fa-xmark"></i></div>
1400
- </button>
1401
- </h1>
1402
- </header>
1403
- <main>
1404
- <div class='task-container'>
1405
- <section class='task-shell tools-shell'>
1406
- <header class='task-shell-header'>
1407
- <div class='task-shell-header-main'>
1408
- <p class='task-eyebrow'>Tools</p>
1409
- <h1 class='task-title'>Installed tools</h1>
1410
- <p class='task-description'>Package bundles and package-manager modules available on this machine.</p>
1411
- </div>
1412
- </header>
1413
- <div class='task-shell-body tools-shell-body'>
1414
- <div class='menu-container'>
1415
- <% if (pending) { %>
1416
- <div class="loading-container">
1417
- <div class="loading-spinner"></div>
1418
- <div class="loading-text">Loading installed tools...</div>
1419
- <div class="loading-subtext">Scanning package managers and modules</div>
1420
- </div>
1421
- <% } else { %>
1422
- <div class="bundles-section">
1423
- <div class="bundles-header">
1424
- <h3>Package Bundles</h3>
1425
- </div>
1426
- <div class="bundles-grid">
1427
- <% bundles.forEach((bundle) => { %>
1428
- <div class="bundle-card <%= !bundle.install_required ? 'installed' : 'pending' %>">
1429
- <div class="bundle-header">
1430
- <div class="bundle-title-section">
1431
- <div class="bundle-icon">
1432
- <i class="<%= bundle.icon %>"></i>
1433
- </div>
1434
- <div class="bundle-title-text">
1435
- <div class="bundle-name"><%= bundle.title %></div>
1436
- <div class="bundle-code">(<%= bundle.name %>)</div>
1437
- </div>
403
+ <%
404
+ const bundleList = Array.isArray(bundles) ? bundles : [];
405
+ const installList = Array.isArray(installs) ? installs : [];
406
+ const installedBundleCount = bundleList.filter((bundle) => !bundle.install_required).length;
407
+ const moduleCount = installList.reduce((count, install) => {
408
+ return count + (Array.isArray(install.modules) ? install.modules.length : 0);
409
+ }, 0);
410
+ const managerName = (manager) => {
411
+ const value = String(manager || "package").trim();
412
+ if (!value) return "Package";
413
+ return value
414
+ .split(/[\s_-]+/)
415
+ .map((word) => word ? word.charAt(0).toUpperCase() + word.slice(1) : "")
416
+ .join(" ");
417
+ };
418
+ const managerPanelId = (manager) => `tools-${String(manager || "package").replace(/[^a-z0-9_-]/gi, "-").toLowerCase()}-panel`;
419
+ %>
420
+ <body class="<%= theme %> main-sidebar-page task-launcher-page task-page task-library-page tools-page" data-agent="<%= agent %>">
421
+ <%- include('partials/app_navheader', { agent }) %>
422
+ <main>
423
+ <div class="task-container">
424
+ <section class="task-shell tools-shell">
425
+ <header class="task-shell-header">
426
+ <div class="task-shell-header-main">
427
+ <h1 class="task-title">Tools</h1>
428
+ <p class="task-description">Package bundles and package-manager modules available on this machine.</p>
429
+ </div>
430
+ <div class="task-header-actions tools-summary">
431
+ <span class="tools-summary-item"><strong><%= installedBundleCount %></strong> of <%= bundleList.length %> bundles ready</span>
432
+ <span class="tools-summary-item"><strong><%= moduleCount %></strong> modules</span>
433
+ </div>
434
+ </header>
435
+
436
+ <div class="task-shell-body tools-shell-body">
437
+ <% if (pending) { %>
438
+ <section class="task-section task-section-subtle" aria-live="polite">
439
+ <div class="tools-loading-state">
440
+ <div class="tools-loading-icon" aria-hidden="true">
441
+ <i class="fa-solid fa-circle-notch fa-spin"></i>
1438
442
  </div>
1439
- <div class="bundle-status <%= !bundle.install_required ? 'installed' : 'pending' %>">
1440
- <%= !bundle.install_required ? 'Installed' : 'Not installed' %>
443
+ <div class="tools-loading-copy">
444
+ <div class="tools-loading-title">Loading installed tools</div>
445
+ <p class="tools-loading-text">Scanning package managers and modules. This page will refresh when the scan finishes.</p>
1441
446
  </div>
1442
447
  </div>
1443
-
1444
- <div class="bundle-description">
1445
- <%= bundle.description %>
448
+ </section>
449
+ <% } else { %>
450
+ <section class="task-section" aria-label="Package bundles">
451
+ <div class="task-section-head">
452
+ <div>
453
+ <h2 class="task-section-title">Package Bundles</h2>
454
+ <p class="task-section-subcopy"><%= installedBundleCount %> ready, <%= bundleList.length - installedBundleCount %> need install.</p>
455
+ </div>
1446
456
  </div>
1447
-
1448
- <div class="bundle-packages">
1449
- <div class="bundle-packages-title">Included packages:</div>
1450
- <div class="bundle-packages-list">
1451
- <% bundle.requirements.forEach((req) => { %>
1452
- <span class="package-badge <%= req.installed ? 'installed' : 'not-installed' %>">
1453
- <span class="package-badge-icon">
1454
- <%= req.installed ? '✓' : '✗' %>
1455
- </span>
1456
- <%= req.name %>
1457
- </span>
457
+
458
+ <% if (!bundleList.length) { %>
459
+ <div class="task-empty-state">
460
+ <p class="task-empty-copy">No package bundles were found.</p>
461
+ </div>
462
+ <% } else { %>
463
+ <div class="tools-bundle-list">
464
+ <% bundleList.forEach((bundle) => { %>
465
+ <article class="tools-bundle-row <%= !bundle.install_required ? 'is-installed' : 'needs-install' %>">
466
+ <div class="tools-bundle-main">
467
+ <div>
468
+ <div class="tools-bundle-title-line">
469
+ <span class="tools-bundle-title"><%= bundle.title %></span>
470
+ <span class="tools-bundle-code"><%= bundle.name %></span>
471
+ </div>
472
+ <p class="tools-bundle-description"><%= bundle.description %></p>
473
+ </div>
474
+
475
+ <div class="tools-bundle-detail">
476
+ <span class="tools-bundle-detail-label">Packages</span>
477
+ <div class="tools-package-list">
478
+ <% bundle.requirements.forEach((req) => { %>
479
+ <span class="tools-package-pill <%= req.installed ? 'is-installed' : 'needs-install' %>">
480
+ <i class="fa-solid <%= req.installed ? 'fa-check' : 'fa-xmark' %>" aria-hidden="true"></i>
481
+ <span><%= req.name %></span>
482
+ </span>
483
+ <% }) %>
484
+ </div>
485
+ </div>
486
+ </div>
487
+
488
+ <div class="tools-bundle-actions">
489
+ <span class="tools-bundle-status <%= !bundle.install_required ? 'good' : 'warn' %>">
490
+ <%= !bundle.install_required ? 'Installed' : 'Not installed' %>
491
+ </span>
492
+ <% if (bundle.install_required) { %>
493
+ <a href="<%= bundle.setup %>" class="task-link-button">
494
+ <i class="fa-solid fa-download" aria-hidden="true"></i>
495
+ <span>Install</span>
496
+ </a>
497
+ <% } %>
498
+ </div>
499
+ </article>
1458
500
  <% }) %>
1459
501
  </div>
1460
- </div>
1461
-
1462
- <% if (bundle.install_required) { %>
1463
- <a href="<%= bundle.setup %>" class="bundle-install-btn">Install Bundle</a>
1464
502
  <% } %>
1465
- </div>
1466
- <% }) %>
1467
- </div>
1468
- </div>
1469
-
1470
- <div class="package-tabs">
1471
- <% installs.forEach((install, index) => { %>
1472
- <button class="package-tab <%= index === 0 ? 'active' : '' %>" data-tab="<%= install.package_manager %>">
1473
- <%= install.package_manager.toUpperCase() %> (<%= install.modules.length %>)
1474
- </button>
1475
- <% }) %>
1476
- </div>
1477
-
1478
- <% installs.forEach((install, index) => { %>
1479
- <div class="tab-panel <%= index === 0 ? 'active' : '' %>" id="<%= install.package_manager %>-panel">
1480
- <div class="section-header">
1481
- <h2><%= install.package_manager.toUpperCase() %></h2>
1482
- <div class="section">
1483
- <div><%= install.modules.length %> modules installed</div>
1484
- </div>
1485
- </div>
1486
- <div class="package-install-card">
1487
- <form class="package-install-form" data-manager="<%= install.package_manager %>">
1488
- <% if (install.package_manager === 'conda') { %>
1489
- <div class="package-install-field">
1490
- <label class="package-install-label" for="install-channel-<%= install.package_manager %>">Channel</label>
1491
- <input id="install-channel-<%= install.package_manager %>" class="package-install-input" name="channel" type="text" value="conda-forge" placeholder="Enter channel" autocomplete="off">
503
+ </section>
504
+
505
+ <section class="task-section" aria-label="Installed modules">
506
+ <div class="task-section-head tools-module-section-head">
507
+ <div>
508
+ <h2 class="task-section-title">Modules</h2>
1492
509
  </div>
1493
- <div class="package-install-field">
1494
- <label class="package-install-label" for="install-name-<%= install.package_manager %>">Package name</label>
1495
- <input id="install-name-<%= install.package_manager %>" class="package-install-input" name="name" type="text" placeholder="Enter package name" autocomplete="off" required>
510
+ <% if (installList.length) { %>
511
+ <button class="task-link-button tools-install-open" type="button" data-package-install-open-active>
512
+ <i class="fa-solid fa-plus" aria-hidden="true"></i>
513
+ <span>Install package</span>
514
+ </button>
515
+ <% } %>
516
+ </div>
517
+
518
+ <% if (!installList.length) { %>
519
+ <div class="task-empty-state">
520
+ <p class="task-empty-copy">No package managers were found.</p>
1496
521
  </div>
1497
522
  <% } else { %>
1498
- <div class="package-install-field">
1499
- <label class="package-install-label" for="install-name-<%= install.package_manager %>">Package name</label>
1500
- <input id="install-name-<%= install.package_manager %>" class="package-install-input" name="name" type="text" placeholder="Enter package name" autocomplete="off" required>
523
+ <div class="task-filter-row tools-tabs" role="tablist" aria-label="Package managers">
524
+ <% installList.forEach((install, index) => { %>
525
+ <% const panelId = managerPanelId(install.package_manager); %>
526
+ <button
527
+ class="task-filter-chip tools-tab-button <%= index === 0 ? 'active' : '' %>"
528
+ type="button"
529
+ role="tab"
530
+ aria-selected="<%= index === 0 ? 'true' : 'false' %>"
531
+ aria-controls="<%= panelId %>"
532
+ data-tab="<%= install.package_manager %>">
533
+ <span><%= managerName(install.package_manager) %></span>
534
+ <span class="tools-tab-count"><%= install.modules.length %></span>
535
+ </button>
536
+ <% }) %>
1501
537
  </div>
538
+
539
+ <% installList.forEach((install, index) => { %>
540
+ <% const panelId = managerPanelId(install.package_manager); %>
541
+ <div
542
+ class="tools-package-panel <%= index === 0 ? 'active' : '' %>"
543
+ id="<%= panelId %>"
544
+ role="tabpanel"
545
+ aria-label="<%= managerName(install.package_manager) %> modules"
546
+ data-panel="<%= install.package_manager %>">
547
+ <% if (!install.modules.length) { %>
548
+ <div class="task-empty-state">
549
+ <p class="task-empty-copy">No modules installed.</p>
550
+ </div>
551
+ <% } else { %>
552
+ <div class="tools-module-list">
553
+ <% install.modules.forEach((module) => { %>
554
+ <div class="tools-module-row">
555
+ <div class="tools-module-title"><%= module %></div>
556
+ </div>
557
+ <% }) %>
558
+ </div>
559
+ <% } %>
560
+ </div>
561
+ <% }) %>
562
+ <% installList.forEach((install) => { %>
563
+ <% const sheetTitleId = `${managerPanelId(install.package_manager)}-install-title`; %>
564
+ <div class="task-tool-sheet-layer tools-package-install-layer" data-package-install-layer="<%= install.package_manager %>" hidden>
565
+ <button type="button" class="task-tool-sheet-backdrop" aria-label="Close install package dialog" data-package-install-close></button>
566
+ <section class="task-tool-sheet tools-package-install-sheet" role="dialog" aria-modal="true" aria-labelledby="<%= sheetTitleId %>">
567
+ <div class="task-tool-sheet-header">
568
+ <div class="task-tool-sheet-heading">
569
+ <div class="task-tool-sheet-title" id="<%= sheetTitleId %>">Install package</div>
570
+ <div class="task-tool-sheet-description">Add a package through <%= managerName(install.package_manager) %>.</div>
571
+ </div>
572
+ <button type="button" class="task-tool-sheet-close" aria-label="Close install package dialog" data-package-install-close>
573
+ <i class="fa-solid fa-xmark" aria-hidden="true"></i>
574
+ </button>
575
+ </div>
576
+ <form class="task-tool-sheet-body tools-install-form package-install-form" data-manager="<%= install.package_manager %>">
577
+ <% if (install.package_manager === 'conda') { %>
578
+ <label class="task-field">
579
+ <span class="task-label">Channel</span>
580
+ <input class="task-input" name="channel" type="text" value="conda-forge" placeholder="Enter channel" autocomplete="off">
581
+ </label>
582
+ <label class="task-field">
583
+ <span class="task-label">Package name</span>
584
+ <input class="task-input" name="name" type="text" placeholder="Enter package name" autocomplete="off" required>
585
+ </label>
586
+ <% } else { %>
587
+ <label class="task-field">
588
+ <span class="task-label">Package name</span>
589
+ <input class="task-input" name="name" type="text" placeholder="Enter package name" autocomplete="off" required>
590
+ </label>
591
+ <% } %>
592
+ <div class="task-actions tools-install-actions">
593
+ <button class="task-link-button" type="button" data-package-install-close>Cancel</button>
594
+ <button type="submit" class="task-button primary tools-install-submit">
595
+ <i class="fa-solid fa-plus" aria-hidden="true"></i>
596
+ <span>Install</span>
597
+ </button>
598
+ </div>
599
+ </form>
600
+ </section>
601
+ </div>
602
+ <% }) %>
1502
603
  <% } %>
1503
- <button type="submit" class="package-install-submit">
1504
- <i class="fa-solid fa-terminal"></i>
1505
- <span>Install</span>
1506
- </button>
1507
- </form>
1508
- <div class="package-install-description">
1509
- <% if (install.package_manager === 'conda') { %>
1510
- Runs <code>conda install -y --channel &lt;channel&gt; &lt;name&gt;</code>
1511
- <% } else if (install.package_manager === 'brew') { %>
1512
- Runs <code>brew install &lt;name&gt;</code>
1513
- <% } else { %>
1514
- Runs <code><%= install.package_manager %> install &lt;name&gt;</code>
1515
- <% } %>
1516
- </div>
1517
- </div>
1518
- <div class='tab-content'>
1519
- <% install.modules.forEach((module) => { %>
1520
- <div class="tab">
1521
- <div class='emoji'><i class="fa-solid fa-cube"></i></div>
1522
- <div class='col'>
1523
- <h2><%= module %></h2>
1524
- </div>
1525
- </div>
1526
- <% }) %>
1527
- </div>
604
+ </section>
605
+ <% } %>
1528
606
  </div>
1529
- <% }) %>
1530
- <% } %>
1531
- </div>
607
+ </section>
1532
608
  </div>
1533
- </section>
1534
- </div>
1535
- <%- include('partials/main_sidebar', { selected: 'tools' }) %>
1536
- </main>
1537
- <script>
1538
- const PACKAGE_MANAGER_ICONS = {
1539
- conda: 'fa-solid fa-flask',
1540
- brew: 'fa-solid fa-beer-mug-empty',
1541
- pip: 'fa-brands fa-python',
1542
- npm: 'fa-brands fa-npm',
1543
- yarn: 'fa-brands fa-yarn',
1544
- cargo: 'fa-solid fa-box-open'
1545
- };
1546
-
1547
- const escapeHtml = (value) => {
1548
- if (!value) return '';
1549
- return value
1550
- .replace(/&/g, '&amp;')
1551
- .replace(/</g, '&lt;')
1552
- .replace(/>/g, '&gt;')
1553
- .replace(/"/g, '&quot;')
1554
- .replace(/'/g, '&#39;');
1555
- };
609
+ <%- include('partials/main_sidebar', { selected: 'tools' }) %>
610
+ </main>
1556
611
 
1557
- const toDisplayName = (manager) => {
1558
- if (!manager) return 'Package Manager';
1559
- return manager
1560
- .split(/[\s_-]+/)
1561
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
1562
- .join(' ');
1563
- };
1564
-
1565
- const buildInstallPayload = (manager, formData) => {
1566
- const trimmedManager = (manager || '').trim();
1567
- const normalizedManager = trimmedManager.toLowerCase();
1568
- const name = (formData.get('name') || '').trim();
1569
- if (!normalizedManager || !name) {
1570
- return null;
1571
- }
612
+ <%- include('partials/app_common_scripts') %>
613
+ <script>
614
+ const PACKAGE_MANAGER_ICONS = {
615
+ conda: "fa-solid fa-flask",
616
+ brew: "fa-solid fa-beer-mug-empty",
617
+ pip: "fa-brands fa-python",
618
+ npm: "fa-brands fa-npm",
619
+ yarn: "fa-brands fa-yarn",
620
+ cargo: "fa-solid fa-box-open"
621
+ };
1572
622
 
1573
- if (normalizedManager === 'conda') {
1574
- let channel = (formData.get('channel') || '').trim();
1575
- if (!channel) {
1576
- channel = 'conda-forge';
1577
- }
1578
- return {
1579
- manager: normalizedManager,
1580
- command: `conda install -y --channel ${channel} ${name}`,
1581
- name,
1582
- channel
623
+ const escapeHtml = (value) => {
624
+ if (!value) return "";
625
+ return String(value)
626
+ .replace(/&/g, "&amp;")
627
+ .replace(/</g, "&lt;")
628
+ .replace(/>/g, "&gt;")
629
+ .replace(/"/g, "&quot;")
630
+ .replace(/'/g, "&#39;");
1583
631
  };
1584
- }
1585
632
 
1586
- if (normalizedManager === 'brew') {
1587
- return {
1588
- manager: normalizedManager,
1589
- command: `brew install ${name}`,
1590
- name
633
+ const toDisplayName = (manager) => {
634
+ if (!manager) return "Package Manager";
635
+ return manager
636
+ .split(/[\s_-]+/)
637
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
638
+ .join(" ");
1591
639
  };
1592
- }
1593
640
 
1594
- return {
1595
- manager: normalizedManager,
1596
- command: `${normalizedManager} install ${name}`,
1597
- name
1598
- };
1599
- };
641
+ const buildInstallPayload = (manager, formData) => {
642
+ const trimmedManager = (manager || "").trim();
643
+ const normalizedManager = trimmedManager.toLowerCase();
644
+ const name = (formData.get("name") || "").trim();
645
+ if (!normalizedManager || !name) {
646
+ return null;
647
+ }
648
+
649
+ if (normalizedManager === "conda") {
650
+ let channel = (formData.get("channel") || "").trim();
651
+ if (!channel) {
652
+ channel = "conda-forge";
653
+ }
654
+ return {
655
+ manager: normalizedManager,
656
+ command: `conda install -y --channel ${channel} ${name}`,
657
+ name,
658
+ channel
659
+ };
660
+ }
661
+
662
+ if (normalizedManager === "brew") {
663
+ return {
664
+ manager: normalizedManager,
665
+ command: `brew install ${name}`,
666
+ name
667
+ };
668
+ }
669
+
670
+ return {
671
+ manager: normalizedManager,
672
+ command: `${normalizedManager} install ${name}`,
673
+ name
674
+ };
675
+ };
1600
676
 
1601
- const openPackageInstallModal = ({ manager, command, name, channel }) => {
1602
- if (typeof Swal === 'undefined') {
1603
- console.error('SweetAlert2 is not available.');
1604
- return;
1605
- }
677
+ const openPackageInstallModal = ({ manager, command, name, channel }) => {
678
+ if (typeof Swal === "undefined") {
679
+ console.error("SweetAlert2 is not available.");
680
+ return;
681
+ }
1606
682
 
1607
- const icon = PACKAGE_MANAGER_ICONS[manager] || 'fa-solid fa-box-open';
1608
- const displayName = toDisplayName(manager);
1609
- const safeName = escapeHtml(name);
1610
- const safeChannel = channel ? escapeHtml(channel) : null;
1611
- const commandHtml = escapeHtml(command);
1612
- const detailParts = [];
1613
- if (safeChannel) {
1614
- detailParts.push(`Channel: ${safeChannel}`);
1615
- }
1616
- if (safeName) {
1617
- detailParts.push(`Package: ${safeName}`);
1618
- }
1619
- const detailsHtml = detailParts.join(' · ');
683
+ const icon = PACKAGE_MANAGER_ICONS[manager] || "fa-solid fa-box-open";
684
+ const displayName = toDisplayName(manager);
685
+ const safeName = escapeHtml(name);
686
+ const safeChannel = channel ? escapeHtml(channel) : null;
687
+ const detailParts = [];
688
+ if (safeChannel) {
689
+ detailParts.push(`Channel: ${safeChannel}`);
690
+ }
691
+ if (safeName) {
692
+ detailParts.push(`Package: ${safeName}`);
693
+ }
694
+ const detailsHtml = detailParts.join(" - ");
1620
695
 
1621
- const sanitizedManager = (manager || 'pkg').replace(/[^a-z0-9_-]/gi, '-').toLowerCase();
1622
- const shellId = encodeURIComponent(`pkg-install-${sanitizedManager}-${Date.now()}`);
1623
- const params = new URLSearchParams();
1624
- params.set('path', '~/');
1625
- params.set('message', command);
1626
- params.set('target', '_top');
1627
- const shellUrl = `/shell/${shellId}?${params.toString()}`;
696
+ const sanitizedManager = (manager || "pkg").replace(/[^a-z0-9_-]/gi, "-").toLowerCase();
697
+ const shellId = encodeURIComponent(`pkg-install-${sanitizedManager}-${Date.now()}`);
698
+ const params = new URLSearchParams();
699
+ params.set("path", "~/");
700
+ params.set("message", command);
701
+ params.set("target", "_top");
702
+ const shellUrl = `/shell/${shellId}?${params.toString()}`;
1628
703
 
1629
- const modalHtml = `
1630
- <div class="pinokio-modal-surface">
1631
- <div class="pinokio-modal-header">
1632
- <div class="pinokio-modal-icon"><i class="${icon}"></i></div>
1633
- <div class="pinokio-modal-heading">
1634
- <div class="pinokio-modal-title">${escapeHtml(displayName)} install</div>
1635
- <div class="pinokio-modal-subtitle">
1636
- ${detailsHtml ? `${detailsHtml} · ` : ''}Running <code>${commandHtml}</code>
704
+ const modalHtml = `
705
+ <div class="pinokio-modal-surface">
706
+ <div class="pinokio-modal-header">
707
+ <div class="pinokio-modal-icon"><i class="${icon}"></i></div>
708
+ <div class="pinokio-modal-heading">
709
+ <div class="pinokio-modal-title">Install with ${escapeHtml(displayName)}</div>
710
+ <div class="pinokio-modal-subtitle">
711
+ ${detailsHtml || "Pinokio opens an install terminal for this package manager."}
712
+ </div>
713
+ </div>
714
+ </div>
715
+ <div class="pinokio-modal-body pinokio-modal-body--iframe">
716
+ <iframe src="${shellUrl}" allow="fullscreen *;" allowfullscreen></iframe>
1637
717
  </div>
1638
718
  </div>
1639
- </div>
1640
- <div class="pinokio-modal-body pinokio-modal-body--iframe">
1641
- <iframe src="${shellUrl}" allow="fullscreen *;" allowfullscreen></iframe>
1642
- </div>
1643
- </div>
1644
- `;
719
+ `;
1645
720
 
1646
- Swal.fire({
1647
- html: modalHtml,
1648
- customClass: {
1649
- popup: 'pinokio-modern-modal',
1650
- htmlContainer: 'pinokio-modern-html',
1651
- closeButton: 'pinokio-modern-close'
1652
- },
1653
- backdrop: 'rgba(9,11,15,0.65)',
1654
- width: 'min(760px, 90vw)',
1655
- showConfirmButton: false,
1656
- showCloseButton: true,
1657
- buttonsStyling: false,
1658
- focusConfirm: false
1659
- });
1660
- };
721
+ Swal.fire({
722
+ html: modalHtml,
723
+ customClass: {
724
+ popup: "pinokio-modern-modal",
725
+ htmlContainer: "pinokio-modern-html",
726
+ closeButton: "pinokio-modern-close"
727
+ },
728
+ backdrop: "rgba(9, 11, 15, 0.65)",
729
+ width: "min(760px, 90vw)",
730
+ showConfirmButton: false,
731
+ showCloseButton: true,
732
+ buttonsStyling: false,
733
+ focusConfirm: false
734
+ });
735
+ };
1661
736
 
1662
- const registerPackageInstallForms = () => {
1663
- const forms = document.querySelectorAll('.package-install-form');
1664
- forms.forEach((form) => {
1665
- form.addEventListener('submit', (event) => {
1666
- event.preventDefault();
1667
- if (typeof form.reportValidity === 'function' && !form.reportValidity()) {
1668
- return;
737
+ const packageInstallLayers = () => Array.from(document.querySelectorAll("[data-package-install-layer]"));
738
+
739
+ const closePackageInstallLayer = (layer) => {
740
+ if (!layer) return;
741
+ layer.hidden = true;
742
+ layer.setAttribute("aria-hidden", "true");
743
+
744
+ const hasOpenLayer = packageInstallLayers().some((item) => !item.hidden);
745
+ if (!hasOpenLayer) {
746
+ document.body.classList.remove("task-modal-open");
1669
747
  }
748
+ };
749
+
750
+ const openPackageInstallLayer = (manager) => {
751
+ const layer = packageInstallLayers().find((item) => item.dataset.packageInstallLayer === manager);
752
+ if (!layer) return;
1670
753
 
1671
- const manager = (form.dataset.manager || '').toLowerCase();
1672
- const formData = new FormData(form);
1673
- const payload = buildInstallPayload(manager, formData);
1674
- if (!payload) {
1675
- const nameInput = form.querySelector('input[name="name"]');
1676
- if (nameInput) {
1677
- nameInput.focus();
754
+ packageInstallLayers().forEach((item) => {
755
+ if (item !== layer) {
756
+ closePackageInstallLayer(item);
1678
757
  }
1679
- return;
758
+ });
759
+
760
+ layer.hidden = false;
761
+ layer.removeAttribute("aria-hidden");
762
+ document.body.classList.add("task-modal-open");
763
+
764
+ const nameInput = layer.querySelector("input[name=\"name\"]");
765
+ const firstInput = nameInput || layer.querySelector("input");
766
+ if (firstInput) {
767
+ setTimeout(() => firstInput.focus(), 0);
1680
768
  }
769
+ };
1681
770
 
1682
- openPackageInstallModal(payload);
1683
- form.reset();
1684
- if (manager === 'conda') {
1685
- const channelInput = form.querySelector('input[name="channel"]');
1686
- if (channelInput && !channelInput.value) {
1687
- channelInput.value = 'conda-forge';
1688
- }
771
+ const registerPackageInstallSheets = () => {
772
+ const openButton = document.querySelector("[data-package-install-open-active]");
773
+ if (openButton) {
774
+ openButton.addEventListener("click", () => {
775
+ const activeTab = document.querySelector(".tools-tab-button.active") || document.querySelector(".tools-tab-button");
776
+ if (activeTab) {
777
+ openPackageInstallLayer(activeTab.dataset.tab);
778
+ }
779
+ });
1689
780
  }
1690
- });
1691
- });
1692
- };
1693
781
 
1694
- document.addEventListener('DOMContentLoaded', function() {
1695
- const isPending = <%= pending ? 'true' : 'false' %>;
1696
-
1697
- // Auto-refresh when pending
1698
- if (isPending) {
1699
- setTimeout(function() {
1700
- window.location.reload();
1701
- }, 3000);
1702
- }
1703
-
1704
- // Tab functionality (only when not pending)
1705
- if (!isPending) {
1706
- const tabs = document.querySelectorAll('.package-tab');
1707
- const panels = document.querySelectorAll('.tab-panel');
1708
-
1709
- registerPackageInstallForms();
782
+ document.querySelectorAll("[data-package-install-close]").forEach((button) => {
783
+ button.addEventListener("click", () => {
784
+ closePackageInstallLayer(button.closest("[data-package-install-layer]"));
785
+ });
786
+ });
1710
787
 
1711
- tabs.forEach(tab => {
1712
- tab.addEventListener('click', function() {
1713
- const targetTab = this.getAttribute('data-tab');
1714
-
1715
- // Remove active class from all tabs and panels
1716
- tabs.forEach(t => t.classList.remove('active'));
1717
- panels.forEach(p => p.classList.remove('active'));
1718
-
1719
- // Add active class to clicked tab
1720
- this.classList.add('active');
1721
-
1722
- // Show corresponding panel
1723
- const targetPanel = document.getElementById(targetTab + '-panel');
1724
- if (targetPanel) {
1725
- targetPanel.classList.add('active');
1726
- }
788
+ document.addEventListener("keydown", (event) => {
789
+ if (event.key !== "Escape") return;
790
+ packageInstallLayers().forEach((layer) => {
791
+ if (!layer.hidden) {
792
+ closePackageInstallLayer(layer);
793
+ }
794
+ });
795
+ });
796
+ };
797
+
798
+ const registerPackageInstallForms = () => {
799
+ const forms = document.querySelectorAll(".package-install-form");
800
+ forms.forEach((form) => {
801
+ form.addEventListener("submit", (event) => {
802
+ event.preventDefault();
803
+ if (typeof form.reportValidity === "function" && !form.reportValidity()) {
804
+ return;
805
+ }
806
+
807
+ const manager = (form.dataset.manager || "").toLowerCase();
808
+ const formData = new FormData(form);
809
+ const payload = buildInstallPayload(manager, formData);
810
+ if (!payload) {
811
+ const nameInput = form.querySelector("input[name=\"name\"]");
812
+ if (nameInput) {
813
+ nameInput.focus();
814
+ }
815
+ return;
816
+ }
817
+
818
+ closePackageInstallLayer(form.closest("[data-package-install-layer]"));
819
+ openPackageInstallModal(payload);
820
+ form.reset();
821
+ if (manager === "conda") {
822
+ const channelInput = form.querySelector("input[name=\"channel\"]");
823
+ if (channelInput && !channelInput.value) {
824
+ channelInput.value = "conda-forge";
825
+ }
826
+ }
827
+ });
828
+ });
829
+ };
830
+
831
+ document.addEventListener("DOMContentLoaded", function() {
832
+ const isPending = <%= pending ? 'true' : 'false' %>;
833
+
834
+ if (isPending) {
835
+ setTimeout(function() {
836
+ window.location.reload();
837
+ }, 3000);
838
+ return;
839
+ }
840
+
841
+ registerPackageInstallSheets();
842
+ registerPackageInstallForms();
843
+
844
+ const tabs = document.querySelectorAll(".tools-tab-button");
845
+ const panels = document.querySelectorAll(".tools-package-panel");
846
+ tabs.forEach((tab) => {
847
+ tab.addEventListener("click", function() {
848
+ const targetTab = this.getAttribute("data-tab");
849
+
850
+ tabs.forEach((item) => {
851
+ item.classList.remove("active");
852
+ item.setAttribute("aria-selected", "false");
853
+ });
854
+ panels.forEach((panel) => {
855
+ panel.classList.remove("active");
856
+ });
857
+
858
+ this.classList.add("active");
859
+ this.setAttribute("aria-selected", "true");
860
+
861
+ const targetPanel = Array.from(panels).find((panel) => panel.getAttribute("data-panel") === targetTab);
862
+ if (targetPanel) {
863
+ targetPanel.classList.add("active");
864
+ }
865
+ });
1727
866
  });
1728
867
  });
1729
- }
1730
- });
1731
- </script>
868
+ </script>
1732
869
  </body>
1733
870
  </html>