@firstpick/pi-package-webui 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -5
- package/WEBUI_TUI_NATIVE_PARITY.json +2 -2
- package/bin/pi-webui.mjs +336 -16
- package/index.ts +16 -1
- package/lib/trust-boundaries.mjs +1 -0
- package/package.json +5 -3
- package/public/app.js +524 -49
- package/public/index.html +17 -4
- package/public/styles.css +176 -55
- package/tests/http-endpoints-harness.test.mjs +57 -0
- package/tests/mobile-static.test.mjs +61 -10
- package/tests/remote-auth-settings-harness.test.mjs +81 -0
- package/tests/session-auth-harness.test.mjs +4 -0
package/public/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<link rel="manifest" href="/manifest.webmanifest" />
|
|
13
13
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
14
14
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
15
|
-
<link rel="stylesheet" href="/styles.css?v=
|
|
15
|
+
<link rel="stylesheet" href="/styles.css?v=53" />
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<button id="sidePanelExpandButton" class="side-panel-expand-button" type="button" aria-controls="sidePanel" aria-expanded="false" aria-label="Expand side panel" title="Expand side panel">
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
aria-haspopup="menu"
|
|
211
211
|
aria-expanded="false"
|
|
212
212
|
aria-controls="optionsMenu"
|
|
213
|
-
data-tooltip="Options: command palette, resume, reload, name, clone, settings, export, fork, or tree."
|
|
213
|
+
data-tooltip="Options: command palette, resume, reload, remote, name, clone, settings, export, fork, or tree."
|
|
214
214
|
><svg class="composer-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7h16M4 12h16M4 17h16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="8" cy="7" r="1.6" fill="currentColor"/><circle cx="16" cy="12" r="1.6" fill="currentColor"/><circle cx="11" cy="17" r="1.6" fill="currentColor"/></svg></button>
|
|
215
215
|
<div id="optionsMenu" class="composer-publish-menu-panel composer-options-menu-panel" role="menu" aria-label="Common Pi options">
|
|
216
216
|
<button id="optionsCommandPaletteButton" class="composer-publish-menu-item composer-options-menu-item" type="button" role="menuitem">
|
|
@@ -237,6 +237,9 @@
|
|
|
237
237
|
<button id="optionsNameButton" class="composer-publish-menu-item composer-options-menu-item" type="button" role="menuitem" data-command="/name">
|
|
238
238
|
<span>Name Session</span>
|
|
239
239
|
</button>
|
|
240
|
+
<button id="optionsRemoteButton" class="composer-publish-menu-item composer-options-menu-item" type="button" role="menuitem" data-command="/remote" hidden>
|
|
241
|
+
<span>Open Remote</span>
|
|
242
|
+
</button>
|
|
240
243
|
<button id="optionsReloadButton" class="composer-publish-menu-item composer-options-menu-item" type="button" role="menuitem" data-command="/reload">
|
|
241
244
|
<span>Reload Pi</span>
|
|
242
245
|
</button>
|
|
@@ -285,7 +288,7 @@
|
|
|
285
288
|
aria-label="Follow-up: type your message first, then tap this button to send it as a follow-up."
|
|
286
289
|
data-tooltip="Follow-up usage: 1. Type your message in the textarea. 2. Tap Follow-up to send it as a follow-up. Use for extra context or the next request."
|
|
287
290
|
>Follow-up</button>
|
|
288
|
-
<button id="abortButton" class="composer-abort-button danger" type="button" title="Abort the active Pi run
|
|
291
|
+
<button id="abortButton" class="composer-abort-button danger" type="button" title="Hold Esc or the Abort button for 3 seconds to abort the active Pi run" aria-label="Hold Esc or the Abort button for 3 seconds to abort the active Pi run" hidden disabled>Abort</button>
|
|
289
292
|
<button id="sendButton" type="submit" class="primary" title="Send prompt">Send</button>
|
|
290
293
|
</div>
|
|
291
294
|
</form>
|
|
@@ -370,6 +373,13 @@
|
|
|
370
373
|
<div class="control-field network-control-field">
|
|
371
374
|
<label>Network</label>
|
|
372
375
|
<div id="networkStatus" class="network-status closed">Local only</div>
|
|
376
|
+
<label class="toggle-control remote-auth-toggle" for="remoteAuthToggle">
|
|
377
|
+
<input id="remoteAuthToggle" type="checkbox" />
|
|
378
|
+
<span>
|
|
379
|
+
<span class="toggle-control-label">Remote PIN auth</span>
|
|
380
|
+
<span id="remoteAuthStatus" class="toggle-control-hint">Off</span>
|
|
381
|
+
</span>
|
|
382
|
+
</label>
|
|
373
383
|
<button id="openNetworkButton" type="button">Open to network</button>
|
|
374
384
|
</div>
|
|
375
385
|
<div class="control-field server-control-field">
|
|
@@ -592,7 +602,10 @@
|
|
|
592
602
|
<span class="command-palette-kicker">Quick launcher</span>
|
|
593
603
|
<h2>Command palette</h2>
|
|
594
604
|
</div>
|
|
595
|
-
<
|
|
605
|
+
<div class="command-palette-header-actions">
|
|
606
|
+
<span class="command-palette-shortcut">Ctrl/Cmd+K</span>
|
|
607
|
+
<button id="commandPaletteCloseButton" class="command-palette-close-button" type="button" aria-label="Close command palette" title="Close command palette">Close</button>
|
|
608
|
+
</div>
|
|
596
609
|
</div>
|
|
597
610
|
<input id="commandPaletteInput" class="dialog-input command-palette-input" type="search" autocomplete="off" spellcheck="false" placeholder="Search commands, tabs, models, sessions…" aria-label="Search command palette" />
|
|
598
611
|
<div id="commandPaletteList" class="command-palette-list" role="listbox" aria-label="Command palette results"></div>
|
package/public/styles.css
CHANGED
|
@@ -4228,8 +4228,10 @@ button.composer-skill-tag:focus-visible {
|
|
|
4228
4228
|
min-width: 5.8rem;
|
|
4229
4229
|
}
|
|
4230
4230
|
.composer-abort-button {
|
|
4231
|
+
min-width: 8.2rem;
|
|
4231
4232
|
position: relative;
|
|
4232
4233
|
overflow: hidden;
|
|
4234
|
+
white-space: nowrap;
|
|
4233
4235
|
box-shadow: 0 0 1rem rgba(243, 139, 168, 0.30), inset 0 1px 0 rgba(255,255,255,0.05);
|
|
4234
4236
|
}
|
|
4235
4237
|
.composer-abort-button.long-pressing {
|
|
@@ -4244,7 +4246,7 @@ button.composer-skill-tag:focus-visible {
|
|
|
4244
4246
|
background: linear-gradient(90deg, rgba(255,255,255,0.28), rgba(255,255,255,0.08));
|
|
4245
4247
|
transform: scaleX(0);
|
|
4246
4248
|
transform-origin: left center;
|
|
4247
|
-
animation: abort-long-press-fill
|
|
4249
|
+
animation: abort-long-press-fill var(--abort-long-press-duration, 3000ms) linear forwards;
|
|
4248
4250
|
}
|
|
4249
4251
|
@keyframes abort-long-press-fill {
|
|
4250
4252
|
to { transform: scaleX(1); }
|
|
@@ -4432,6 +4434,9 @@ button.composer-skill-tag:focus-visible {
|
|
|
4432
4434
|
max-height: min(60vh, 24rem);
|
|
4433
4435
|
padding-bottom: 0.38rem;
|
|
4434
4436
|
overflow: auto;
|
|
4437
|
+
overflow-y: auto;
|
|
4438
|
+
overscroll-behavior: contain;
|
|
4439
|
+
-webkit-overflow-scrolling: touch;
|
|
4435
4440
|
scrollbar-width: thin;
|
|
4436
4441
|
}
|
|
4437
4442
|
.composer-publish-menu:hover .composer-publish-menu-panel,
|
|
@@ -6264,7 +6269,10 @@ button.composer-skill-tag:focus-visible {
|
|
|
6264
6269
|
body.side-panel-collapsed .terminal-tabs-shell { padding-right: calc(44px + 0.8rem); }
|
|
6265
6270
|
.terminal-tabs-toggle-button {
|
|
6266
6271
|
display: block;
|
|
6267
|
-
|
|
6272
|
+
flex: 1 1 auto;
|
|
6273
|
+
width: auto;
|
|
6274
|
+
min-width: 0;
|
|
6275
|
+
max-width: min(14rem, calc(100vw - 11.4rem));
|
|
6268
6276
|
min-height: 28px;
|
|
6269
6277
|
padding: 0.16rem 0.58rem;
|
|
6270
6278
|
overflow: hidden;
|
|
@@ -6301,6 +6309,7 @@ button.composer-skill-tag:focus-visible {
|
|
|
6301
6309
|
line-height: 1.1;
|
|
6302
6310
|
}
|
|
6303
6311
|
.terminal-tabs {
|
|
6312
|
+
position: absolute;
|
|
6304
6313
|
left: 0.55rem;
|
|
6305
6314
|
right: 0.55rem;
|
|
6306
6315
|
top: calc(100% + 0.35rem);
|
|
@@ -6310,6 +6319,8 @@ button.composer-skill-tag:focus-visible {
|
|
|
6310
6319
|
gap: 0.34rem;
|
|
6311
6320
|
max-height: min(42dvh, 20rem);
|
|
6312
6321
|
overflow: auto;
|
|
6322
|
+
overscroll-behavior: contain;
|
|
6323
|
+
-webkit-overflow-scrolling: touch;
|
|
6313
6324
|
padding: 0.55rem;
|
|
6314
6325
|
border: 1px solid rgba(148, 226, 213, 0.26);
|
|
6315
6326
|
border-radius: 0.95rem;
|
|
@@ -6486,19 +6497,34 @@ button.composer-skill-tag:focus-visible {
|
|
|
6486
6497
|
}
|
|
6487
6498
|
.sticky-user-prompt-button {
|
|
6488
6499
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6500
|
+
min-height: 36px;
|
|
6501
|
+
margin: 0 0 0.32rem;
|
|
6502
|
+
padding: 0.32rem 0.46rem;
|
|
6503
|
+
gap: 0.32rem;
|
|
6504
|
+
border-radius: 0.72rem;
|
|
6492
6505
|
}
|
|
6493
6506
|
.sticky-user-prompt-label { display: none; }
|
|
6494
|
-
.sticky-user-prompt-text {
|
|
6507
|
+
.sticky-user-prompt-text {
|
|
6508
|
+
font-size: 0.72rem;
|
|
6509
|
+
line-height: 1.2;
|
|
6510
|
+
}
|
|
6511
|
+
.sticky-user-prompt-meta {
|
|
6512
|
+
font-size: 0.64rem;
|
|
6513
|
+
line-height: 1.1;
|
|
6514
|
+
}
|
|
6495
6515
|
.sticky-user-follow-up-prompt {
|
|
6496
6516
|
grid-template-columns: minmax(0, 1fr);
|
|
6497
|
-
gap: 0.
|
|
6498
|
-
padding-top: 0.
|
|
6517
|
+
gap: 0.14rem;
|
|
6518
|
+
padding-top: 0.22rem;
|
|
6519
|
+
}
|
|
6520
|
+
.sticky-user-follow-up-label {
|
|
6521
|
+
font-size: 0.56rem;
|
|
6522
|
+
line-height: 1.1;
|
|
6523
|
+
}
|
|
6524
|
+
.sticky-user-follow-up-text {
|
|
6525
|
+
font-size: 0.66rem;
|
|
6526
|
+
line-height: 1.15;
|
|
6499
6527
|
}
|
|
6500
|
-
.sticky-user-follow-up-label { font-size: 0.62rem; }
|
|
6501
|
-
.sticky-user-follow-up-text { font-size: 0.74rem; }
|
|
6502
6528
|
.jump-to-latest-button {
|
|
6503
6529
|
min-height: 44px;
|
|
6504
6530
|
margin: 0.35rem auto;
|
|
@@ -6548,9 +6574,10 @@ button.composer-skill-tag:focus-visible {
|
|
|
6548
6574
|
.statusbar {
|
|
6549
6575
|
max-height: none;
|
|
6550
6576
|
overflow: visible;
|
|
6551
|
-
padding: 0.
|
|
6552
|
-
font-size: 0.
|
|
6577
|
+
padding: 0.28rem 0.42rem;
|
|
6578
|
+
font-size: 0.64rem;
|
|
6553
6579
|
}
|
|
6580
|
+
.context-meter-bar { display: none !important; }
|
|
6554
6581
|
body.footer-details-expanded .statusbar {
|
|
6555
6582
|
max-height: min(42dvh, 20rem);
|
|
6556
6583
|
overflow: auto;
|
|
@@ -6578,7 +6605,7 @@ button.composer-skill-tag:focus-visible {
|
|
|
6578
6605
|
position: fixed;
|
|
6579
6606
|
left: max(0.55rem, env(safe-area-inset-left));
|
|
6580
6607
|
right: max(0.55rem, env(safe-area-inset-right));
|
|
6581
|
-
bottom: var(--footer-model-picker-bottom, calc(
|
|
6608
|
+
bottom: var(--footer-model-picker-bottom, calc(7.2rem + env(safe-area-inset-bottom)));
|
|
6582
6609
|
width: auto;
|
|
6583
6610
|
max-height: min(52dvh, 24rem);
|
|
6584
6611
|
z-index: 60;
|
|
@@ -6586,15 +6613,15 @@ button.composer-skill-tag:focus-visible {
|
|
|
6586
6613
|
.footer-line-main {
|
|
6587
6614
|
display: none;
|
|
6588
6615
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
6589
|
-
gap: 0.
|
|
6590
|
-
margin-bottom: 0.
|
|
6616
|
+
gap: 0.26rem;
|
|
6617
|
+
margin-bottom: 0.26rem;
|
|
6591
6618
|
}
|
|
6592
6619
|
body.footer-details-expanded .footer-line-main { display: grid; }
|
|
6593
6620
|
body.footer-details-expanded .footer-line-main .footer-metric { display: inline-grid; }
|
|
6594
6621
|
.footer-line-meta {
|
|
6595
6622
|
display: grid;
|
|
6596
6623
|
grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr) auto;
|
|
6597
|
-
gap: 0.
|
|
6624
|
+
gap: 0.26rem;
|
|
6598
6625
|
align-items: stretch;
|
|
6599
6626
|
}
|
|
6600
6627
|
.footer-line-meta .footer-meta { display: none; }
|
|
@@ -6616,38 +6643,51 @@ button.composer-skill-tag:focus-visible {
|
|
|
6616
6643
|
width: 100%;
|
|
6617
6644
|
min-width: 0;
|
|
6618
6645
|
}
|
|
6619
|
-
.footer-metric
|
|
6646
|
+
.footer-metric,
|
|
6647
|
+
.footer-meta {
|
|
6648
|
+
min-height: 0;
|
|
6649
|
+
padding: 0.24rem 0.38rem;
|
|
6650
|
+
}
|
|
6620
6651
|
.footer-details-toggle {
|
|
6621
6652
|
display: block;
|
|
6622
|
-
min-height:
|
|
6623
|
-
padding: 0.
|
|
6653
|
+
min-height: 36px;
|
|
6654
|
+
padding: 0.24rem 0.5rem;
|
|
6624
6655
|
color: var(--ctp-yellow);
|
|
6625
6656
|
border-color: rgba(249, 226, 175, 0.34);
|
|
6626
|
-
font-size: 0.
|
|
6657
|
+
font-size: 0.68rem;
|
|
6627
6658
|
font-weight: 900;
|
|
6659
|
+
line-height: 1.1;
|
|
6628
6660
|
}
|
|
6629
6661
|
.composer {
|
|
6630
6662
|
position: sticky;
|
|
6631
6663
|
bottom: 0;
|
|
6632
6664
|
z-index: 50;
|
|
6633
|
-
padding: 0.
|
|
6665
|
+
padding: 0.38rem 0.42rem calc(0.38rem + env(safe-area-inset-bottom));
|
|
6634
6666
|
background: linear-gradient(180deg, rgba(var(--ctp-crust-rgb), 0.92), rgba(var(--ctp-crust-rgb), 0.98));
|
|
6635
6667
|
box-shadow: 0 -0.85rem 1.8rem rgba(var(--ctp-crust-rgb), 0.56), inset 0 1px 0 rgba(255,255,255,0.04);
|
|
6636
6668
|
}
|
|
6669
|
+
.composer-input-row { gap: 0.36rem; }
|
|
6670
|
+
.composer-attach-button {
|
|
6671
|
+
width: 2.55rem;
|
|
6672
|
+
min-width: 2.55rem;
|
|
6673
|
+
min-height: 38px;
|
|
6674
|
+
padding: 0.44rem;
|
|
6675
|
+
border-radius: 0.72rem;
|
|
6676
|
+
}
|
|
6637
6677
|
#promptInput {
|
|
6638
|
-
min-height: calc(1.
|
|
6639
|
-
max-height: min(
|
|
6640
|
-
padding: 0.
|
|
6678
|
+
min-height: calc(1.35em + 1.05rem);
|
|
6679
|
+
max-height: min(24dvh, 10rem);
|
|
6680
|
+
padding: 0.52rem 0.62rem;
|
|
6641
6681
|
resize: none;
|
|
6642
|
-
font-size:
|
|
6643
|
-
line-height: 1.
|
|
6682
|
+
font-size: 0.95rem;
|
|
6683
|
+
line-height: 1.35;
|
|
6644
6684
|
}
|
|
6645
6685
|
.composer-row {
|
|
6646
6686
|
display: grid;
|
|
6647
6687
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
6648
|
-
gap: 0.
|
|
6688
|
+
gap: 0.3rem;
|
|
6649
6689
|
align-items: stretch;
|
|
6650
|
-
margin-top: 0.
|
|
6690
|
+
margin-top: 0.38rem;
|
|
6651
6691
|
}
|
|
6652
6692
|
.composer-actions-button {
|
|
6653
6693
|
display: block;
|
|
@@ -6669,15 +6709,15 @@ button.composer-skill-tag:focus-visible {
|
|
|
6669
6709
|
scroll-padding-bottom: calc(7.5rem + var(--keyboard-inset-bottom, 0px));
|
|
6670
6710
|
}
|
|
6671
6711
|
body.mobile-keyboard-open .composer {
|
|
6672
|
-
padding: 0.
|
|
6712
|
+
padding: 0.32rem 0.4rem calc(0.32rem + env(safe-area-inset-bottom));
|
|
6673
6713
|
}
|
|
6674
6714
|
body.mobile-keyboard-open #promptInput {
|
|
6675
|
-
min-height: calc(1.
|
|
6676
|
-
max-height: min(
|
|
6715
|
+
min-height: calc(1.35em + 1.05rem);
|
|
6716
|
+
max-height: min(22dvh, 8rem);
|
|
6677
6717
|
}
|
|
6678
6718
|
body.mobile-keyboard-open .composer-row {
|
|
6679
6719
|
grid-template-columns: 1fr;
|
|
6680
|
-
margin-top: 0.
|
|
6720
|
+
margin-top: 0.28rem;
|
|
6681
6721
|
}
|
|
6682
6722
|
body.mobile-keyboard-open .composer-actions-button,
|
|
6683
6723
|
body.mobile-keyboard-open .composer-actions-panel {
|
|
@@ -6686,7 +6726,7 @@ button.composer-skill-tag:focus-visible {
|
|
|
6686
6726
|
body.mobile-keyboard-open .composer-row button.primary,
|
|
6687
6727
|
body.mobile-keyboard-open .composer-abort-button:not([hidden]) {
|
|
6688
6728
|
grid-column: 1 / -1;
|
|
6689
|
-
min-height:
|
|
6729
|
+
min-height: 40px;
|
|
6690
6730
|
}
|
|
6691
6731
|
.composer-actions-panel {
|
|
6692
6732
|
position: absolute;
|
|
@@ -6720,15 +6760,15 @@ button.composer-skill-tag:focus-visible {
|
|
|
6720
6760
|
.spacer { display: none; }
|
|
6721
6761
|
.composer-row button {
|
|
6722
6762
|
width: 100%;
|
|
6723
|
-
min-height:
|
|
6763
|
+
min-height: 40px;
|
|
6724
6764
|
margin-right: 0;
|
|
6725
|
-
padding: 0.
|
|
6726
|
-
font-size: 0.
|
|
6765
|
+
padding: 0.34rem 0.44rem;
|
|
6766
|
+
font-size: 0.78rem;
|
|
6727
6767
|
}
|
|
6728
6768
|
.composer-row button.primary {
|
|
6729
6769
|
grid-column: 1 / -1;
|
|
6730
|
-
min-height:
|
|
6731
|
-
font-size: 0.
|
|
6770
|
+
min-height: 40px;
|
|
6771
|
+
font-size: 0.86rem;
|
|
6732
6772
|
}
|
|
6733
6773
|
body:not(.pi-run-active):not(.mobile-keyboard-open) .composer-row button.primary { grid-column: span 4; }
|
|
6734
6774
|
body.pi-run-active:not(.mobile-keyboard-open) .composer-abort-button:not([hidden]) {
|
|
@@ -6783,18 +6823,17 @@ button.composer-skill-tag:focus-visible {
|
|
|
6783
6823
|
width: 100%;
|
|
6784
6824
|
min-width: 0;
|
|
6785
6825
|
max-width: 100%;
|
|
6786
|
-
max-height: min(34dvh,
|
|
6826
|
+
max-height: min(var(--mobile-dropdown-max-height, 34dvh), calc(var(--visual-viewport-height, 100dvh) - 2rem));
|
|
6787
6827
|
margin: 0;
|
|
6788
6828
|
padding-bottom: 0;
|
|
6789
6829
|
overflow: auto;
|
|
6830
|
+
overflow-y: auto;
|
|
6831
|
+
overscroll-behavior: contain;
|
|
6832
|
+
-webkit-overflow-scrolling: touch;
|
|
6790
6833
|
}
|
|
6791
|
-
.composer-actions-panel > .composer-options-menu .composer-publish-menu-panel
|
|
6792
|
-
inset-inline: auto 0;
|
|
6793
|
-
max-height: min(calc(var(--visual-viewport-height, 100dvh) - 2rem), 44rem);
|
|
6794
|
-
}
|
|
6834
|
+
.composer-actions-panel > .composer-options-menu .composer-publish-menu-panel,
|
|
6795
6835
|
.composer-actions-panel > .composer-app-runner-menu .composer-publish-menu-panel {
|
|
6796
6836
|
inset-inline: auto 0;
|
|
6797
|
-
max-height: min(calc(var(--visual-viewport-height, 100dvh) - 2rem), 44rem);
|
|
6798
6837
|
}
|
|
6799
6838
|
.composer-actions-panel > .composer-publish-menu .composer-publish-menu-item {
|
|
6800
6839
|
width: 100%;
|
|
@@ -7329,6 +7368,39 @@ button.composer-skill-tag:focus-visible {
|
|
|
7329
7368
|
.command-palette-header > div {
|
|
7330
7369
|
min-width: 0;
|
|
7331
7370
|
}
|
|
7371
|
+
.command-palette-header-actions {
|
|
7372
|
+
display: inline-flex;
|
|
7373
|
+
flex: 0 0 auto;
|
|
7374
|
+
align-items: center;
|
|
7375
|
+
justify-content: flex-end;
|
|
7376
|
+
gap: 0.5rem;
|
|
7377
|
+
min-width: 0;
|
|
7378
|
+
}
|
|
7379
|
+
.command-palette-close-button {
|
|
7380
|
+
display: inline-flex;
|
|
7381
|
+
align-items: center;
|
|
7382
|
+
justify-content: center;
|
|
7383
|
+
min-width: 44px;
|
|
7384
|
+
min-height: 44px;
|
|
7385
|
+
padding: 0.42rem 0.74rem;
|
|
7386
|
+
border: 1px solid rgba(180, 190, 254, 0.22);
|
|
7387
|
+
border-radius: 999px;
|
|
7388
|
+
color: rgba(var(--ctp-subtext-rgb), 0.88);
|
|
7389
|
+
font-weight: 850;
|
|
7390
|
+
background:
|
|
7391
|
+
linear-gradient(180deg, rgba(var(--ctp-surface-rgb), 0.42), rgba(var(--ctp-crust-rgb), 0.58)),
|
|
7392
|
+
rgba(var(--ctp-crust-rgb), 0.58);
|
|
7393
|
+
box-shadow: 0 0 0 1px rgba(203, 166, 247, 0.04), 0 0.45rem 1rem rgba(var(--ctp-crust-rgb), 0.18);
|
|
7394
|
+
}
|
|
7395
|
+
.command-palette-close-button:hover,
|
|
7396
|
+
.command-palette-close-button:focus-visible {
|
|
7397
|
+
color: var(--ctp-text);
|
|
7398
|
+
border-color: rgba(148, 226, 213, 0.58);
|
|
7399
|
+
background:
|
|
7400
|
+
linear-gradient(120deg, rgba(148, 226, 213, 0.16), rgba(137, 180, 250, 0.12)),
|
|
7401
|
+
rgba(var(--ctp-surface-rgb), 0.62);
|
|
7402
|
+
box-shadow: 0 0 0 1px rgba(148, 226, 213, 0.16), 0 0 1rem rgba(148, 226, 213, 0.14);
|
|
7403
|
+
}
|
|
7332
7404
|
.command-palette-shortcut {
|
|
7333
7405
|
flex: 0 0 auto;
|
|
7334
7406
|
max-width: 9rem;
|
|
@@ -7481,24 +7553,73 @@ button.composer-skill-tag:focus-visible {
|
|
|
7481
7553
|
.context-meter-actions button { flex: 1 1 auto; }
|
|
7482
7554
|
}
|
|
7483
7555
|
@media (max-width: 720px) {
|
|
7484
|
-
.terminal-command-palette-button
|
|
7556
|
+
.terminal-command-palette-button,
|
|
7557
|
+
.terminal-dashboard-button {
|
|
7558
|
+
display: inline-grid;
|
|
7559
|
+
width: 32px;
|
|
7560
|
+
min-width: 32px;
|
|
7561
|
+
min-height: 28px;
|
|
7562
|
+
border-radius: 0.58rem;
|
|
7563
|
+
}
|
|
7564
|
+
.terminal-command-palette-button .command-palette-launcher-icon,
|
|
7565
|
+
.terminal-dashboard-button .workspace-overview-icon {
|
|
7566
|
+
width: 1rem;
|
|
7567
|
+
height: 1rem;
|
|
7568
|
+
}
|
|
7485
7569
|
.workspace-dashboard { padding: 0.62rem 0.7rem; }
|
|
7486
7570
|
.workspace-dashboard-metrics { grid-template-columns: 1fr; }
|
|
7487
7571
|
.command-palette-dialog {
|
|
7488
|
-
inset: calc(0.
|
|
7489
|
-
width: min(100vw -
|
|
7490
|
-
max-height: calc(var(--visual-viewport-height, 100dvh) -
|
|
7572
|
+
inset: calc(0.38rem + env(safe-area-inset-top)) 0 auto 0;
|
|
7573
|
+
width: min(100vw - 0.5rem, 42rem);
|
|
7574
|
+
max-height: calc(var(--visual-viewport-height, 100dvh) - 0.76rem - env(safe-area-inset-top));
|
|
7491
7575
|
margin-inline: auto;
|
|
7492
|
-
border-radius:
|
|
7576
|
+
border-radius: 0.9rem;
|
|
7577
|
+
}
|
|
7578
|
+
.command-palette-header {
|
|
7579
|
+
gap: 0.52rem;
|
|
7580
|
+
padding: 0.62rem 0.58rem 0.42rem;
|
|
7581
|
+
}
|
|
7582
|
+
.command-palette-header h2 { font-size: 0.92rem; }
|
|
7583
|
+
.command-palette-header-actions { gap: 0.38rem; }
|
|
7584
|
+
.command-palette-kicker,
|
|
7585
|
+
.command-palette-shortcut { font-size: 0.58rem; }
|
|
7586
|
+
.command-palette-shortcut {
|
|
7587
|
+
max-width: 5.8rem;
|
|
7588
|
+
padding: 0.12rem 0.38rem;
|
|
7589
|
+
}
|
|
7590
|
+
.command-palette-close-button {
|
|
7591
|
+
min-width: 54px;
|
|
7592
|
+
min-height: 44px;
|
|
7593
|
+
padding: 0.42rem 0.58rem;
|
|
7594
|
+
font-size: 0.72rem;
|
|
7493
7595
|
}
|
|
7494
|
-
.command-palette-header { padding: 0.85rem 0.85rem 0.62rem; }
|
|
7495
7596
|
.command-palette-input {
|
|
7496
|
-
width: calc(100% - 1.
|
|
7497
|
-
|
|
7597
|
+
width: calc(100% - 1.16rem);
|
|
7598
|
+
min-height: 38px;
|
|
7599
|
+
margin: 0 0.58rem 0.46rem;
|
|
7600
|
+
padding: 0.62rem 0.68rem;
|
|
7601
|
+
font-size: 0.92rem;
|
|
7498
7602
|
}
|
|
7499
|
-
.command-palette-list {
|
|
7500
|
-
|
|
7501
|
-
|
|
7603
|
+
.command-palette-list {
|
|
7604
|
+
gap: 0.3rem;
|
|
7605
|
+
padding: 0 0.46rem 0.52rem;
|
|
7606
|
+
scrollbar-gutter: auto;
|
|
7607
|
+
}
|
|
7608
|
+
.command-palette-item {
|
|
7609
|
+
grid-template-columns: minmax(3.4rem, 0.26fr) minmax(0, 1fr);
|
|
7610
|
+
gap: 0.04rem 0.46rem;
|
|
7611
|
+
min-height: 2.72rem;
|
|
7612
|
+
padding: 0.42rem 0.5rem;
|
|
7613
|
+
border-radius: 0.68rem;
|
|
7614
|
+
line-height: 1.18;
|
|
7615
|
+
}
|
|
7616
|
+
.command-palette-item-kind {
|
|
7617
|
+
grid-row: 1 / span 2;
|
|
7618
|
+
font-size: 0.56rem;
|
|
7619
|
+
letter-spacing: 0.055em;
|
|
7620
|
+
}
|
|
7621
|
+
.command-palette-item-label { font-size: 0.82rem; }
|
|
7622
|
+
.command-palette-item-description { font-size: 0.6rem; }
|
|
7502
7623
|
.message-edit-retry-button {
|
|
7503
7624
|
top: 0.38rem;
|
|
7504
7625
|
right: 2.8rem;
|
|
@@ -38,6 +38,7 @@ async function request(host, pathname, { method = "GET", body, timeoutMs = 5_000
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
const cwd = await mkdtemp(path.join(tmpdir(), "pi-webui-http-harness-"));
|
|
41
|
+
const settingsFile = path.join(cwd, "webui-settings.json");
|
|
41
42
|
await chmod(fakePi, 0o755);
|
|
42
43
|
|
|
43
44
|
const child = spawn(process.execPath, [serverScript, "--cwd", cwd, "--host", "0.0.0.0", "--port", String(port), "--pi", fakePi], {
|
|
@@ -48,6 +49,7 @@ const child = spawn(process.execPath, [serverScript, "--cwd", cwd, "--host", "0.
|
|
|
48
49
|
GIT_AUTHOR_EMAIL: "pi-webui-test@example.invalid",
|
|
49
50
|
GIT_COMMITTER_NAME: "Pi WebUI Test",
|
|
50
51
|
GIT_COMMITTER_EMAIL: "pi-webui-test@example.invalid",
|
|
52
|
+
PI_WEBUI_SETTINGS_FILE: settingsFile,
|
|
51
53
|
},
|
|
52
54
|
});
|
|
53
55
|
let serverOutput = "";
|
|
@@ -245,8 +247,15 @@ try {
|
|
|
245
247
|
assert.equal(traversalDelete.status, 403, "session delete outside the session dir must return 403");
|
|
246
248
|
assert.match(String(traversalDelete.body?.error || ""), /session directory/i);
|
|
247
249
|
|
|
250
|
+
const initialAuth = await request("127.0.0.1", "/api/remote-auth");
|
|
251
|
+
assert.equal(initialAuth.status, 200);
|
|
252
|
+
assert.equal(initialAuth.body?.data?.auth?.enabled, false, "remote PIN auth should be off by default");
|
|
253
|
+
|
|
248
254
|
const lan = lanAddress();
|
|
249
255
|
if (lan) {
|
|
256
|
+
const remoteHealthBeforeAuth = await request(lan, "/api/health");
|
|
257
|
+
assert.equal(remoteHealthBeforeAuth.status, 200, "LAN clients should connect without a PIN while auth is off");
|
|
258
|
+
|
|
250
259
|
const remoteDelete = await request(lan, "/api/session-delete", {
|
|
251
260
|
method: "POST",
|
|
252
261
|
body: { sessionPath: path.join(cwd, "outside.jsonl"), confirmed: true, tab: tabId },
|
|
@@ -262,7 +271,55 @@ try {
|
|
|
262
271
|
|
|
263
272
|
const remoteClose = await request(lan, "/api/network/close", { method: "POST" });
|
|
264
273
|
assert.equal(remoteClose.status, 403, "network close must be localhost-only");
|
|
274
|
+
|
|
275
|
+
const enableAuth = await request("127.0.0.1", "/api/remote-auth/settings", { method: "POST", body: { enabled: true } });
|
|
276
|
+
assert.equal(enableAuth.status, 200, "localhost can enable remote PIN auth");
|
|
277
|
+
const pin = enableAuth.body?.data?.auth?.pin;
|
|
278
|
+
assert.match(pin, /^\d{4}$/, "enabling remote auth should generate a 4-digit PIN");
|
|
279
|
+
|
|
280
|
+
const remoteHealthWithAuth = await request(lan, "/api/health");
|
|
281
|
+
assert.equal(remoteHealthWithAuth.status, 401, "unauthenticated LAN clients should be challenged while remote auth is on");
|
|
282
|
+
|
|
283
|
+
const wrongPin = pin === "0000" ? "0001" : "0000";
|
|
284
|
+
const badLogin = await request(lan, "/api/remote-auth", { method: "POST", body: { pin: wrongPin } });
|
|
285
|
+
assert.equal(badLogin.status, 403, "wrong remote PIN should be rejected");
|
|
286
|
+
|
|
287
|
+
const loginResponse = await fetch(`http://${lan}:${port}/api/remote-auth`, {
|
|
288
|
+
method: "POST",
|
|
289
|
+
headers: { "content-type": "application/json" },
|
|
290
|
+
body: JSON.stringify({ pin }),
|
|
291
|
+
signal: AbortSignal.timeout(5_000),
|
|
292
|
+
});
|
|
293
|
+
assert.equal(loginResponse.status, 200, "correct remote PIN should be accepted");
|
|
294
|
+
const authCookie = loginResponse.headers.get("set-cookie")?.split(";", 1)[0];
|
|
295
|
+
assert.ok(authCookie, "remote auth login should set an auth cookie");
|
|
296
|
+
|
|
297
|
+
const authedHealth = await fetch(`http://${lan}:${port}/api/health`, {
|
|
298
|
+
headers: { cookie: authCookie },
|
|
299
|
+
signal: AbortSignal.timeout(5_000),
|
|
300
|
+
});
|
|
301
|
+
assert.equal(authedHealth.status, 200, "authenticated LAN client should reach guarded APIs");
|
|
302
|
+
await authedHealth.json();
|
|
303
|
+
|
|
304
|
+
const remoteSettings = await fetch(`http://${lan}:${port}/api/remote-auth/settings`, {
|
|
305
|
+
method: "POST",
|
|
306
|
+
headers: { "content-type": "application/json", cookie: authCookie },
|
|
307
|
+
body: JSON.stringify({ enabled: false }),
|
|
308
|
+
signal: AbortSignal.timeout(5_000),
|
|
309
|
+
});
|
|
310
|
+
assert.equal(remoteSettings.status, 403, "remote clients must not toggle remote PIN auth settings");
|
|
311
|
+
await remoteSettings.json().catch(() => undefined);
|
|
312
|
+
|
|
313
|
+
const disableAuth = await request("127.0.0.1", "/api/remote-auth/settings", { method: "POST", body: { enabled: false } });
|
|
314
|
+
assert.equal(disableAuth.status, 200, "localhost can disable remote PIN auth");
|
|
315
|
+
const remoteHealthAfterDisable = await request(lan, "/api/health");
|
|
316
|
+
assert.equal(remoteHealthAfterDisable.status, 200, "LAN clients should reconnect without a PIN after auth is disabled");
|
|
265
317
|
} else {
|
|
318
|
+
const enableAuth = await request("127.0.0.1", "/api/remote-auth/settings", { method: "POST", body: { enabled: true } });
|
|
319
|
+
assert.equal(enableAuth.status, 200, "localhost can enable remote PIN auth");
|
|
320
|
+
assert.match(enableAuth.body?.data?.auth?.pin, /^\d{4}$/);
|
|
321
|
+
const disableAuth = await request("127.0.0.1", "/api/remote-auth/settings", { method: "POST", body: { enabled: false } });
|
|
322
|
+
assert.equal(disableAuth.status, 200, "localhost can disable remote PIN auth");
|
|
266
323
|
console.log("http-endpoints-harness: no LAN address detected; skipping remote-client checks");
|
|
267
324
|
}
|
|
268
325
|
|