sh3-server 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ .modal-frame.svelte-2tcvcm{position:absolute;inset:0;display:grid;place-items:center;pointer-events:auto}.modal-box.svelte-2tcvcm{background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));color:var(--shell-fg);border:1px solid var(--shell-border-strong);border-radius:var(--shell-radius);min-width:320px;max-width:min(640px,90vw);max-height:90vh;overflow:auto;box-shadow:0 20px 48px #00000080;outline:none}.popup-frame.svelte-mp81cl{position:absolute;background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));color:var(--shell-fg);border:1px solid var(--shell-border-strong);border-radius:var(--shell-radius-sm);box-shadow:0 8px 24px #0006;min-width:120px;outline:none;pointer-events:auto}.toast.svelte-12gwnj0{pointer-events:auto;display:flex;align-items:center;gap:var(--shell-pad-md);padding:var(--shell-pad-sm) var(--shell-pad-md);background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));color:var(--shell-fg);border:1px solid var(--shell-border-strong);border-left-width:3px;border-radius:var(--shell-radius-sm);box-shadow:0 8px 20px #0006;font-size:12px;min-width:220px;max-width:360px;cursor:pointer;animation:svelte-12gwnj0-toast-in .16s ease-out both}.toast-level.svelte-12gwnj0{text-transform:uppercase;font-family:var(--shell-font-mono);font-size:10px;letter-spacing:.5px;color:var(--shell-fg-muted)}.toast-message.svelte-12gwnj0{flex:1}.toast-info.svelte-12gwnj0{border-left-color:var(--shell-accent)}.toast-success.svelte-12gwnj0{border-left-color:#5cb176}.toast-warn.svelte-12gwnj0{border-left-color:#d6a84a}.toast-error.svelte-12gwnj0{border-left-color:#d06060}@keyframes svelte-12gwnj0-toast-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.sync-grant-picker.svelte-106syj7{display:grid;gap:.75rem;padding:1rem;border:1px solid var(--sh3-border, #444);border-radius:6px}.error.svelte-106syj7{color:var(--sh3-error, #c00)}footer.svelte-106syj7{display:flex;gap:.5rem;justify-content:flex-end}.document-sync-explorer.svelte-wc9uuh{display:grid;gap:.75rem}ul.svelte-wc9uuh{list-style:none;padding:0;margin:0}li.svelte-wc9uuh{display:flex;gap:.5rem;align-items:center}.shell-title.svelte-1ifjvh5{display:inline-block;line-height:0;overflow:hidden}.shell-title.svelte-1ifjvh5 canvas{transform:scale(var(--shell-title-scale));transform-origin:top left;display:block}.shell-title-fallback.svelte-1ifjvh5{margin:0;font-size:42px;color:var(--shell-accent);letter-spacing:2px}.shell-home.svelte-cpn2x2{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;padding:48px 24px;overflow:auto;background:var(--shell-grad-bg, var(--shell-bg));color:var(--shell-fg);font-family:system-ui,sans-serif}.shell-home-header.svelte-cpn2x2{text-align:center;margin-bottom:24px;display:flex;flex-direction:column;align-items:center;gap:12px}.shell-home-title-row.svelte-cpn2x2{display:flex;align-items:baseline;gap:6px}.shell-home-credit.svelte-cpn2x2{font-size:11px;color:var(--shell-fg-muted);letter-spacing:.04em;margin-top:-4px}.shell-home-credit.svelte-cpn2x2 a:where(.svelte-cpn2x2){color:var(--shell-fg-subtle);text-decoration:none;border-bottom:1px dotted var(--shell-fg-muted)}.shell-home-credit.svelte-cpn2x2 a:where(.svelte-cpn2x2):hover{color:var(--shell-accent);border-bottom-color:var(--shell-accent)}.shell-home-version.svelte-cpn2x2{font-size:14px;color:var(--shell-fg-subtle);letter-spacing:.04em}.shell-home-alpha.svelte-cpn2x2{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#fff;background:var(--shell-accent);padding:2px 8px;border-radius:8px;position:relative;top:-1px}.shell-home-filter.svelte-cpn2x2{width:100%;max-width:720px;margin-bottom:24px}.shell-home-filter-input.svelte-cpn2x2{width:100%;padding:10px 14px;font:inherit;font-size:14px;color:var(--shell-fg);background:var(--shell-bg-elevated);border:1px solid var(--shell-border);border-radius:var(--shell-radius-md);outline:none;transition:border-color .12s ease,box-shadow .12s ease}.shell-home-filter-input.svelte-cpn2x2::placeholder{color:var(--shell-fg-muted)}.shell-home-filter-input.svelte-cpn2x2:focus{border-color:var(--shell-accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--shell-accent) 25%,transparent)}.shell-home-empty.svelte-cpn2x2{color:var(--shell-fg-muted);font-style:italic}.shell-home-section.svelte-cpn2x2{width:100%;max-width:720px;margin-bottom:28px}.shell-home-section-title.svelte-cpn2x2{font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--shell-fg-subtle);margin:0 0 12px}.shell-home-grid.svelte-cpn2x2{display:grid;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:10px}.shell-home-card.svelte-cpn2x2{aspect-ratio:1 / 1;display:flex;flex-direction:column;justify-content:space-between;text-align:left;padding:10px;background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));border:1px solid var(--shell-border);border-radius:var(--shell-radius-md);color:inherit;font:inherit;cursor:pointer;box-shadow:0 2px 6px #00000040,0 1px 2px #00000026;transition:transform .12s ease,border-color .12s ease,box-shadow .12s ease,background .12s ease}.shell-home-card.svelte-cpn2x2:hover{border-color:var(--shell-accent);transform:translateY(-1px);box-shadow:0 6px 14px #0000004d,0 0 0 1px color-mix(in srgb,var(--shell-accent) 35%,transparent),0 4px 12px color-mix(in srgb,var(--shell-accent) 18%,transparent)}.shell-home-card.svelte-cpn2x2:focus-visible{outline:none;border-color:var(--shell-accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--shell-accent) 40%,transparent)}.shell-home-card.svelte-cpn2x2:active{transform:translateY(0)}.shell-home-card-label.svelte-cpn2x2{font-weight:600;font-size:12px;line-height:1.2;overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2}.shell-home-card-meta.svelte-cpn2x2{display:flex;flex-direction:column;gap:1px;font-size:9px;color:var(--shell-fg-subtle);min-width:0}.shell-home-card-id.svelte-cpn2x2{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.shell-home-card-version.svelte-cpn2x2{color:var(--shell-fg-muted)}.keys-peers.svelte-19l5m7c{padding:24px;font-family:system-ui,sans-serif;color:var(--shell-fg)}.keys-peers-header.svelte-19l5m7c{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.keys-peers-header.svelte-19l5m7c h2:where(.svelte-19l5m7c){margin:0;font-size:18px}.keys-peers-list.svelte-19l5m7c{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.keys-peers-item.svelte-19l5m7c{display:flex;justify-content:space-between;align-items:flex-start;padding:12px 16px;background:var(--shell-bg-elevated, #252540);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px);gap:12px}.keys-peers-info.svelte-19l5m7c{display:flex;flex-direction:column;gap:3px;min-width:0}.keys-peers-label.svelte-19l5m7c{font-weight:600}.keys-peers-meta.svelte-19l5m7c{font-size:11px;color:var(--shell-fg-subtle)}.keys-peers-actions.svelte-19l5m7c{display:flex;gap:6px;flex-shrink:0;align-items:flex-start;padding-top:2px}.keys-peers-btn-danger.svelte-19l5m7c{background:transparent;color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);font-size:12px;padding:4px 10px;border-radius:var(--shell-radius, 6px);cursor:pointer}.keys-peers-btn-secondary.svelte-19l5m7c{background:transparent;color:var(--shell-fg-subtle);border:1px solid var(--shell-border, #3a3a5c);font-size:12px;padding:4px 10px;border-radius:var(--shell-radius, 6px);cursor:pointer}.keys-peers-muted.svelte-19l5m7c{color:var(--shell-fg-muted);font-style:italic}.keys-peers-error.svelte-19l5m7c{color:var(--shell-error, #d32f2f);font-size:13px}.shell-text.svelte-ypr8s{margin:0;padding:0 8px;font-family:var(--shell-font-mono, monospace);white-space:pre-wrap;word-break:break-word;color:var(--shell-fg, #ddd)}.shell-text.stderr.svelte-ypr8s{color:var(--shell-fg-error, #f88)}.shell-prompt.svelte-u0gb59{padding:4px 8px 0;font-family:var(--shell-font-mono, monospace);display:flex;gap:8px}.shell-prompt-cwd.svelte-u0gb59{color:var(--shell-fg-muted, #888)}.shell-prompt-arrow.svelte-u0gb59{color:var(--shell-accent, #6cf)}.shell-prompt-line.svelte-u0gb59{color:var(--shell-fg, #ddd)}.shell-status.svelte-nfxdpt{padding:2px 8px;font-family:var(--shell-font-mono, monospace);font-style:italic}.shell-status.info.svelte-nfxdpt{color:var(--shell-fg-muted, #888)}.shell-status.warn.svelte-nfxdpt{color:var(--shell-fg-warn, #fc6)}.shell-status.error.svelte-nfxdpt{color:var(--shell-fg-error, #f88)}.shell-rich.svelte-1rnhl05{padding:4px 8px}.shell-scrollback.svelte-isy3jt{flex:1 1 auto;overflow-y:auto;background:var(--shell-bg, #111);color:var(--shell-fg, #ddd)}.shell-input.svelte-1dfv2gk{display:flex;gap:8px;padding:4px 8px;border-top:1px solid var(--shell-border, #333);font-family:var(--shell-font-mono, monospace)}.shell-input-cwd.svelte-1dfv2gk{color:var(--shell-fg-muted, #888)}.shell-input-arrow.svelte-1dfv2gk{color:var(--shell-accent, #6cf)}.shell-input-field.svelte-1dfv2gk{flex:1 1 auto;background:transparent;border:0;outline:0;color:var(--shell-fg, #ddd);font:inherit}.shell-input.locked.svelte-1dfv2gk .shell-input-field:where(.svelte-1dfv2gk){opacity:.5;cursor:default}.toolbar.svelte-gnowwz{display:flex;align-items:center;gap:6px;padding:4px 6px;background:var(--shell-bg-elevated, var(--shell-bg, #1a1a1a));border-bottom:1px solid var(--shell-border, #333);flex-shrink:0}.toolbar-slots.svelte-gnowwz{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.mode-bar.svelte-1i4cqlj{display:inline-flex;gap:2px;padding:1px;border:1px solid var(--shell-border, #444);border-radius:3px}.mode-btn.svelte-1i4cqlj{background:none;border:none;color:var(--shell-fg-dim, var(--shell-fg-muted, #888));padding:2px 8px;border-radius:2px;cursor:pointer;font-size:.85em;line-height:1.4}.mode-btn.svelte-1i4cqlj:hover{background:var(--shell-hover, color-mix(in srgb, var(--shell-fg, #ddd) 10%, transparent));color:var(--shell-fg, #ddd)}.mode-btn.active.svelte-1i4cqlj{background:var(--shell-accent, #7c7cf0);color:var(--shell-bg, #1a1a2e)}.mode-label.svelte-1i4cqlj{font-size:.85em;color:var(--shell-fg-dim, var(--shell-fg-muted, #888))}.focus-lock-btn.svelte-1q6gbap{background:none;border:1px solid var(--shell-border, #444);border-radius:3px;cursor:pointer;padding:2px 5px;font-size:.9em;line-height:1}.focus-lock-btn.svelte-1q6gbap:hover{background:var(--shell-hover, #222)}.target-shard.svelte-1nn2m6x{font-size:.85em;color:var(--shell-fg-dim, #888);font-family:monospace}.shell-terminal.svelte-13komre{display:flex;flex-direction:column;height:100%;background:var(--shell-bg, #111);color:var(--shell-fg, #ddd)}.shell-rich-help.svelte-m248jy table:where(.svelte-m248jy){border-collapse:collapse;width:100%}.shell-rich-help.svelte-m248jy th:where(.svelte-m248jy),.shell-rich-help.svelte-m248jy td:where(.svelte-m248jy){padding:2px 8px;text-align:left}.shell-rich-help.svelte-m248jy button:where(.svelte-m248jy){background:none;border:0;color:var(--shell-link, #6cf);cursor:pointer;padding:0;font:inherit}.shell-rich-help.svelte-m248jy button:where(.svelte-m248jy):hover{text-decoration:underline}.shell-rich-history.svelte-o8m437 ol:where(.svelte-o8m437){list-style-position:inside;margin:0;padding:0}.shell-rich-history.svelte-o8m437 li:where(.svelte-o8m437){padding:2px 0}.shell-rich-history.svelte-o8m437 button:where(.svelte-o8m437){background:none;border:0;color:var(--shell-link, #6cf);cursor:pointer;padding:0;font:inherit;text-align:left}.shell-rich-history.svelte-o8m437 button:where(.svelte-o8m437):hover{text-decoration:underline}.shell-rich-apps.svelte-1u5krjz table:where(.svelte-1u5krjz){border-collapse:collapse;width:100%}.shell-rich-apps.svelte-1u5krjz th:where(.svelte-1u5krjz),.shell-rich-apps.svelte-1u5krjz td:where(.svelte-1u5krjz){padding:2px 8px;text-align:left}.shell-rich-apps.svelte-1u5krjz button:where(.svelte-1u5krjz){background:none;border:0;color:var(--shell-link, #6cf);cursor:pointer;padding:0;font:inherit}.shell-rich-apps.svelte-1u5krjz button:where(.svelte-1u5krjz):hover{text-decoration:underline}.shell-rich-appcard.svelte-npcp8i{padding:8px;border:1px solid var(--shell-border, #444)}.shell-rich-appcard.svelte-npcp8i h3:where(.svelte-npcp8i){margin:0 0 8px}.shell-rich-appcard.svelte-npcp8i p:where(.svelte-npcp8i){margin:4px 0}.shell-rich-shards.svelte-sc3ux4 table:where(.svelte-sc3ux4){border-collapse:collapse;width:100%}.shell-rich-shards.svelte-sc3ux4 th:where(.svelte-sc3ux4),.shell-rich-shards.svelte-sc3ux4 td:where(.svelte-sc3ux4){padding:2px 8px;text-align:left}.shell-rich-views.svelte-15elbr1 table:where(.svelte-15elbr1){border-collapse:collapse;width:100%}.shell-rich-views.svelte-15elbr1 th:where(.svelte-15elbr1),.shell-rich-views.svelte-15elbr1 td:where(.svelte-15elbr1){padding:2px 8px;text-align:left}.shell-rich-views.svelte-15elbr1 button:where(.svelte-15elbr1){background:none;border:0;color:var(--shell-link, #6cf);cursor:pointer;padding:0;font:inherit}.shell-rich-views.svelte-15elbr1 button:where(.svelte-15elbr1):hover{text-decoration:underline}.shell-rich-zones.svelte-1w7onag table:where(.svelte-1w7onag){border-collapse:collapse;width:100%}.shell-rich-zones.svelte-1w7onag th:where(.svelte-1w7onag),.shell-rich-zones.svelte-1w7onag td:where(.svelte-1w7onag){padding:2px 8px;text-align:left}.shell-rich-zonetree.svelte-13iiyr1 pre:where(.svelte-13iiyr1){overflow:auto;padding:8px;background:var(--shell-bg, #000);color:var(--shell-fg, #fff)}.shell-rich-env.svelte-1ox8vog table:where(.svelte-1ox8vog){border-collapse:collapse;width:100%}.shell-rich-env.svelte-1ox8vog th:where(.svelte-1ox8vog),.shell-rich-env.svelte-1ox8vog td:where(.svelte-1ox8vog){padding:2px 8px;text-align:left}.store-view.svelte-1aafzt0{font-family:var(--shell-font-ui);color:var(--shell-fg, #e0e0e0);background:var(--shell-bg, #1e1e1e);padding:16px;height:100%;overflow-y:auto;box-sizing:border-box}.store-header.svelte-1aafzt0{margin-bottom:16px}.store-header.svelte-1aafzt0 h2:where(.svelte-1aafzt0){margin:0 0 8px;font-size:1.25rem;font-weight:600}.store-controls.svelte-1aafzt0{display:flex;gap:8px;flex-wrap:wrap}.store-search.svelte-1aafzt0{flex:1;min-width:160px;padding:6px 10px;background:var(--shell-input-bg, #2a2a2a);color:var(--shell-fg, #e0e0e0);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius);font-family:inherit;font-size:.875rem}.store-search.svelte-1aafzt0::placeholder{color:var(--shell-fg-muted, #888)}.store-filter.svelte-1aafzt0{padding:6px 10px;background:var(--shell-input-bg, #2a2a2a);color:var(--shell-fg, #e0e0e0);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius);font-family:inherit;font-size:.875rem}.store-refresh.svelte-1aafzt0:disabled{opacity:.6;cursor:not-allowed}.store-error.svelte-1aafzt0{padding:8px 12px;margin-bottom:12px;background:color-mix(in srgb,var(--shell-error, #d32f2f) 15%,transparent);color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);border-radius:var(--shell-radius);font-size:.8125rem}.store-grid.svelte-1aafzt0{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}.store-card.svelte-1aafzt0{background:var(--shell-input-bg, #2a2a2a);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius-md);padding:14px;display:flex;flex-direction:column;gap:8px}.store-card.svelte-1aafzt0:hover{border-color:var(--shell-accent, #007acc)}.store-card-header.svelte-1aafzt0{display:flex;align-items:center;gap:10px}.store-card-icon.svelte-1aafzt0{width:36px;height:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center}.store-icon-img.svelte-1aafzt0{width:36px;height:36px;border-radius:var(--shell-radius);object-fit:cover}.store-icon-placeholder.svelte-1aafzt0{width:36px;height:36px;display:flex;align-items:center;justify-content:center;background:var(--shell-accent, #007acc);color:#fff;border-radius:var(--shell-radius);font-weight:700;font-size:1rem}.store-card-title.svelte-1aafzt0{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.store-card-label.svelte-1aafzt0{font-weight:600;font-size:.9375rem}.store-card-badge.svelte-1aafzt0{font-size:.6875rem;padding:1px 6px;border-radius:var(--shell-radius-sm);text-transform:uppercase;font-weight:600;letter-spacing:.04em}.badge-shard.svelte-1aafzt0{background:color-mix(in srgb,var(--shell-accent, #007acc) 25%,transparent);color:var(--shell-accent, #007acc)}.badge-app.svelte-1aafzt0{background:color-mix(in srgb,var(--shell-success, #4caf50) 25%,transparent);color:var(--shell-success, #4caf50)}.store-card-version.svelte-1aafzt0{font-size:.75rem;color:var(--shell-fg-muted, #888)}.store-card-desc.svelte-1aafzt0{margin:0;font-size:.8125rem;color:var(--shell-fg-muted, #888);line-height:1.4}.store-card-author.svelte-1aafzt0{font-size:.75rem;color:var(--shell-fg-muted, #888)}.store-card-warning.svelte-1aafzt0{font-size:.75rem;color:var(--shell-warning, #ff9800);padding:4px 8px;background:color-mix(in srgb,var(--shell-warning, #ff9800) 10%,transparent);border-radius:var(--shell-radius-sm)}.store-card-actions.svelte-1aafzt0{margin-top:auto;display:flex;justify-content:flex-end}.store-install-btn.svelte-1aafzt0{padding:5px 14px;font-size:.8125rem}.store-install-btn.svelte-1aafzt0:disabled{opacity:.6;cursor:not-allowed}.store-installed-label.svelte-1aafzt0{font-size:.8125rem;color:var(--shell-success, #4caf50);font-weight:600}.store-install-wrap.svelte-1aafzt0{display:flex;flex-direction:column;align-items:flex-end;gap:4px}.store-card-missing.svelte-1aafzt0{font-size:.75rem;color:var(--shell-warning, #ff9800)}.store-update-btn.svelte-1aafzt0{padding:5px 14px;background:var(--shell-warning, #ff9800);font-size:.8125rem}.store-update-btn.svelte-1aafzt0:hover:not(:disabled){filter:brightness(1.1)}.store-update-btn.svelte-1aafzt0:disabled{opacity:.6;cursor:not-allowed}.store-empty.svelte-1aafzt0{text-align:center;padding:32px 16px;color:var(--shell-fg-muted, #888);font-size:.875rem}.store-registries.svelte-1aafzt0{display:flex;flex-direction:column;gap:4px;margin-bottom:8px}.store-registry-entry.svelte-1aafzt0{display:flex;align-items:center;justify-content:space-between;padding:4px 8px;background:var(--shell-input-bg, #2a2a2a);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius);font-size:.8125rem}.store-registry-url.svelte-1aafzt0{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--shell-fg-muted, #888)}.store-registry-remove.svelte-1aafzt0{padding:2px 8px;background:transparent;color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);border-radius:var(--shell-radius-sm);font-size:.75rem;flex-shrink:0;margin-left:8px}.store-add-registry.svelte-1aafzt0{display:flex;gap:8px;margin-bottom:12px}.store-registry-input.svelte-1aafzt0{flex:1;padding:6px 10px;background:var(--shell-input-bg, #2a2a2a);color:var(--shell-fg, #e0e0e0);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius);font-family:inherit;font-size:.8125rem}.store-registry-input.svelte-1aafzt0::placeholder{color:var(--shell-fg-muted, #888)}.store-add-btn.svelte-1aafzt0{font-size:.8125rem;white-space:nowrap}.store-add-btn.svelte-1aafzt0:disabled{opacity:.6;cursor:not-allowed}.installed-view.svelte-1hqclkp{font-family:var(--shell-font-ui);color:var(--shell-fg, #e0e0e0);background:var(--shell-bg, #1e1e1e);padding:16px;height:100%;overflow-y:auto;box-sizing:border-box}.installed-header.svelte-1hqclkp{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.installed-header.svelte-1hqclkp h2:where(.svelte-1hqclkp){margin:0;font-size:1.25rem;font-weight:600}.installed-empty.svelte-1hqclkp{text-align:center;padding:32px 16px;color:var(--shell-fg-muted, #888);font-size:.875rem}.installed-list.svelte-1hqclkp{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.installed-item.svelte-1hqclkp{background:var(--shell-input-bg, #2a2a2a);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius-md);padding:12px 14px;display:flex;flex-direction:column;gap:6px}.installed-item-main.svelte-1hqclkp{display:flex;align-items:center;gap:8px}.installed-item-id.svelte-1hqclkp{font-weight:600;font-size:.9375rem}.installed-item-badge.svelte-1hqclkp{font-size:.6875rem;padding:1px 6px;border-radius:var(--shell-radius-sm);text-transform:uppercase;font-weight:600;letter-spacing:.04em}.badge-shard.svelte-1hqclkp{background:color-mix(in srgb,var(--shell-accent, #007acc) 25%,transparent);color:var(--shell-accent, #007acc)}.badge-app.svelte-1hqclkp{background:color-mix(in srgb,var(--shell-success, #4caf50) 25%,transparent);color:var(--shell-success, #4caf50)}.installed-item-version.svelte-1hqclkp{font-size:.75rem;color:var(--shell-fg-muted, #888)}.installed-item-meta.svelte-1hqclkp{display:flex;gap:16px;flex-wrap:wrap;font-size:.75rem;color:var(--shell-fg-muted, #888)}.installed-item-actions.svelte-1hqclkp{display:flex;justify-content:flex-end;gap:8px}.installed-uninstall-btn.svelte-1hqclkp{padding:4px 12px;background:transparent;color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);font-size:.8125rem}.installed-uninstall-btn.svelte-1hqclkp:hover:not(:disabled){background:color-mix(in srgb,var(--shell-error, #d32f2f) 15%,transparent)}.installed-uninstall-btn.svelte-1hqclkp:disabled{opacity:.6;cursor:not-allowed}.installed-update-btn.svelte-1hqclkp{padding:4px 12px;background:var(--shell-warning, #ff9800);font-size:.8125rem}.installed-update-btn.svelte-1hqclkp:hover:not(:disabled){filter:brightness(1.1)}.installed-update-btn.svelte-1hqclkp:disabled{opacity:.6;cursor:not-allowed}.installed-error.svelte-1hqclkp{padding:8px 12px;margin-bottom:12px;background:color-mix(in srgb,var(--shell-error, #d32f2f) 15%,transparent);color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);border-radius:var(--shell-radius);font-size:.8125rem}.admin-users.svelte-4q792t{padding:24px;font-family:system-ui,sans-serif;color:var(--shell-fg)}.admin-users-header.svelte-4q792t{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.admin-users-header.svelte-4q792t h2:where(.svelte-4q792t){margin:0;font-size:18px}.admin-create-form.svelte-4q792t,.admin-edit-form.svelte-4q792t{display:flex;flex-direction:column;gap:8px;margin-bottom:16px;max-width:400px}.admin-input.svelte-4q792t{padding:8px 12px;background:var(--shell-bg, #1a1a2e);color:var(--shell-fg);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px);font-size:13px}.admin-btn.svelte-4q792t{font-weight:600;font-size:13px}.admin-btn.svelte-4q792t:disabled{opacity:.6;cursor:not-allowed}.admin-btn-secondary.svelte-4q792t{background:transparent;color:var(--shell-fg-subtle);border:1px solid var(--shell-border);font-size:12px}.admin-btn-danger.svelte-4q792t{background:transparent;color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);font-size:12px}.admin-user-list.svelte-4q792t{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.admin-user-item.svelte-4q792t{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:var(--shell-bg-elevated, #252540);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px)}.admin-user-info.svelte-4q792t{display:flex;flex-direction:column;gap:2px}.admin-user-name.svelte-4q792t{font-weight:600}.admin-user-meta.svelte-4q792t{font-size:11px;color:var(--shell-fg-subtle)}.admin-user-actions.svelte-4q792t,.admin-edit-actions.svelte-4q792t{display:flex;gap:6px}.admin-error.svelte-4q792t{color:var(--shell-error, #d32f2f);font-size:13px}.admin-muted.svelte-4q792t{color:var(--shell-fg-muted);font-style:italic}.admin-auth.svelte-1hsgdb4{padding:24px;font-family:system-ui,sans-serif;color:var(--shell-fg)}.admin-auth.svelte-1hsgdb4 h2:where(.svelte-1hsgdb4){margin:0 0 16px;font-size:18px}.admin-auth-fields.svelte-1hsgdb4{display:flex;flex-direction:column;gap:16px;max-width:480px}.admin-toggle.svelte-1hsgdb4{display:flex;flex-wrap:wrap;align-items:center;gap:8px;cursor:pointer}.admin-toggle.svelte-1hsgdb4 input:where(.svelte-1hsgdb4){accent-color:var(--shell-accent, #7c7cf0)}.admin-hint.svelte-1hsgdb4{flex-basis:100%;font-size:11px;color:var(--shell-fg-muted);margin-left:24px}.admin-field.svelte-1hsgdb4{display:flex;flex-direction:column;gap:4px}.admin-field.svelte-1hsgdb4 span:where(.svelte-1hsgdb4){font-size:13px}.admin-input.svelte-1hsgdb4{padding:8px 12px;background:var(--shell-bg);color:var(--shell-fg);border:1px solid var(--shell-border);border-radius:var(--shell-radius, 6px);font-size:13px}.admin-input-sm.svelte-1hsgdb4{max-width:120px}.admin-btn.svelte-1hsgdb4{padding:8px 16px;font-weight:600;align-self:flex-start}.admin-btn.svelte-1hsgdb4:disabled{opacity:.6;cursor:not-allowed}.admin-error.svelte-1hsgdb4{margin-top:8px;color:var(--shell-error, #d32f2f);font-size:13px}.admin-muted.svelte-1hsgdb4{color:var(--shell-fg-muted);font-style:italic}.admin-system.svelte-1gkiloq{padding:24px;font-family:system-ui,sans-serif;color:var(--shell-fg)}.admin-system.svelte-1gkiloq h2:where(.svelte-1gkiloq){margin:0 0 16px;font-size:18px}.admin-system.svelte-1gkiloq h3:where(.svelte-1gkiloq){margin:0 0 8px;font-size:14px;text-transform:uppercase;letter-spacing:.05em;color:var(--shell-fg-subtle)}.admin-system-info.svelte-1gkiloq{margin-bottom:24px}.admin-system-row.svelte-1gkiloq{display:flex;gap:12px;padding:8px 0;border-bottom:1px solid var(--shell-border, #3a3a5c);font-size:13px}.admin-system-label.svelte-1gkiloq{color:var(--shell-fg-subtle);min-width:140px}.admin-system-section.svelte-1gkiloq{margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--shell-border, #3a3a5c)}.admin-system-section.svelte-1gkiloq:last-child{border-bottom:none}.admin-system-hint.svelte-1gkiloq{font-size:13px;color:var(--shell-fg-subtle);margin:0 0 12px}.admin-system-readout.svelte-1gkiloq{font-size:13px;margin:8px 0 12px}.admin-system-snaps.svelte-1gkiloq{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px}.admin-snap.svelte-1gkiloq{padding:4px 10px;font-size:12px;background:transparent;border:1px solid var(--shell-border, #3a3a5c);color:var(--shell-fg);cursor:pointer;border-radius:var(--shell-radius-sm, 3px)}.admin-snap.active.svelte-1gkiloq{background:var(--shell-accent, #4a7bd4);color:var(--shell-bg);border-color:var(--shell-accent, #4a7bd4)}.admin-snap.svelte-1gkiloq:disabled{opacity:.5;cursor:not-allowed}.admin-system-actions.svelte-1gkiloq{display:flex;flex-direction:row;gap:8px;align-items:center;flex-wrap:wrap}.admin-btn.svelte-1gkiloq{padding:8px 16px;background:var(--shell-accent, #4a7bd4);color:var(--shell-bg);border:1px solid var(--shell-accent, #4a7bd4);font-weight:600;cursor:pointer;border-radius:var(--shell-radius-sm, 3px)}.admin-btn.svelte-1gkiloq:disabled{opacity:.5;cursor:not-allowed}.admin-btn-ghost.svelte-1gkiloq{padding:8px 16px;background:transparent;color:var(--shell-fg);border:1px solid var(--shell-border, #3a3a5c);cursor:pointer;border-radius:var(--shell-radius-sm, 3px)}.admin-btn-ghost.svelte-1gkiloq:disabled{opacity:.5;cursor:not-allowed}.admin-btn-danger.svelte-1gkiloq{padding:8px 16px;background:transparent;color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);font-weight:600;cursor:pointer;border-radius:var(--shell-radius-sm, 3px)}.admin-btn-danger.svelte-1gkiloq:disabled{opacity:.6;cursor:not-allowed}.admin-error.svelte-1gkiloq{color:var(--shell-error, #d32f2f);font-size:13px}input[type=range].svelte-1gkiloq{width:100%;max-width:480px}.admin-keys.svelte-1afg5z5{padding:24px;font-family:system-ui,sans-serif;color:var(--shell-fg)}.admin-keys-header.svelte-1afg5z5{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.admin-keys-header.svelte-1afg5z5 h2:where(.svelte-1afg5z5){margin:0;font-size:18px}.admin-create-form.svelte-1afg5z5{display:flex;flex-direction:column;gap:8px;margin-bottom:16px;max-width:400px}.admin-input.svelte-1afg5z5{padding:8px 12px;background:var(--shell-bg, #1a1a2e);color:var(--shell-fg);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px);font-size:13px}.admin-btn.svelte-1afg5z5{font-weight:600;font-size:13px}.admin-btn.svelte-1afg5z5:disabled{opacity:.6;cursor:not-allowed}.admin-btn-secondary.svelte-1afg5z5{background:transparent;color:var(--shell-fg-subtle);border:1px solid var(--shell-border);font-size:12px}.admin-btn-danger.svelte-1afg5z5{background:transparent;color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);font-size:12px}.admin-key-list.svelte-1afg5z5{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.admin-key-item.svelte-1afg5z5{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:var(--shell-bg-elevated, #252540);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px)}.admin-key-info.svelte-1afg5z5{display:flex;flex-direction:column;gap:2px}.admin-key-label.svelte-1afg5z5{font-weight:600}.admin-key-meta.svelte-1afg5z5{font-size:11px;color:var(--shell-fg-subtle)}.admin-key-value.svelte-1afg5z5{font-size:12px;color:var(--shell-fg-muted);background:var(--shell-bg, #1a1a2e);padding:2px 6px;border-radius:3px;margin-top:2px;word-break:break-all}.admin-key-actions.svelte-1afg5z5{display:flex;gap:6px;flex-shrink:0}.admin-muted.svelte-1afg5z5{color:var(--shell-fg-muted);font-style:italic}.admin-error.svelte-1afg5z5{color:var(--shell-error, #d32f2f);font-size:13px}button,input[type=button],input[type=submit],input[type=reset],.shell-base-button{padding:6px 14px;background:var(--shell-accent, #6ea8fe);color:#fff;border:none;border-radius:var(--shell-radius);cursor:pointer;font-family:inherit;font-size:.875rem;line-height:var(--shell-line)}button:hover,input[type=button]:hover,input[type=submit]:hover,input[type=reset]:hover,.shell-base-button:hover{filter:brightness(1.12)}button:active,input[type=button]:active,input[type=submit]:active,input[type=reset]:active,.shell-base-button:active{filter:brightness(.92)}.splitter.svelte-1adl4o1{display:flex;width:100%;height:100%;min-width:0;min-height:0}.splitter.horizontal.svelte-1adl4o1{flex-direction:row}.splitter.vertical.svelte-1adl4o1{flex-direction:column}.splitter-pane.svelte-1adl4o1{position:relative;min-width:0;min-height:0;overflow:hidden;display:flex}.horizontal.svelte-1adl4o1>.splitter-pane:where(.svelte-1adl4o1){flex-direction:row}.vertical.svelte-1adl4o1>.splitter-pane:where(.svelte-1adl4o1){flex-direction:column}.splitter-pane.collapsed.svelte-1adl4o1{overflow:visible}.pane-content.svelte-1adl4o1{flex:1 1 0;position:relative;min-width:0;min-height:0;overflow:hidden}.collapse-header.svelte-1adl4o1{appearance:none;flex:0 0 auto;display:flex;align-items:center;justify-content:center;background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));border:none;color:var(--shell-fg-muted);cursor:pointer;padding:0;font-size:10px}.collapse-header.svelte-1adl4o1:hover{color:var(--shell-fg);background:var(--shell-accent-muted)}body[data-dragging] .collapse-header.svelte-1adl4o1{pointer-events:none}.collapse-header.horizontal.svelte-1adl4o1{width:100%;height:100%;writing-mode:vertical-rl}.collapse-header.vertical.svelte-1adl4o1{width:100%;height:100%}.collapse-header.expanded.horizontal.svelte-1adl4o1{width:16px;height:100%;border-right:1px solid var(--shell-border)}.collapse-header.expanded.vertical.svelte-1adl4o1{width:100%;height:16px;border-bottom:1px solid var(--shell-border)}.splitter-handle.svelte-1adl4o1{flex:0 0 auto;background:var(--shell-border);transition:background-color .12s ease;touch-action:none}.splitter-handle.svelte-1adl4o1:hover,.splitter-handle.dragging.svelte-1adl4o1{background:var(--shell-accent)}body[data-dragging] .splitter-handle.svelte-1adl4o1{pointer-events:none}.splitter-handle.disabled.svelte-1adl4o1{cursor:default;pointer-events:none}.horizontal.svelte-1adl4o1>.splitter-handle:where(.svelte-1adl4o1){width:4px;cursor:col-resize}.vertical.svelte-1adl4o1>.splitter-handle:where(.svelte-1adl4o1){height:4px;cursor:row-resize}.tabbed-panel.svelte-1kxcyhf{display:flex;flex-direction:column;width:100%;height:100%;min-width:0;min-height:0;background:var(--shell-grad-bg, var(--shell-bg))}.tab-strip.svelte-1kxcyhf{position:relative;flex:0 0 auto;display:flex;gap:1px;background:var(--shell-grad-bg-sunken, var(--shell-bg-sunken));border-bottom:1px solid var(--shell-border);padding:0 var(--shell-pad-sm);user-select:none}.tab.svelte-1kxcyhf{appearance:none;background:transparent;border:none;color:var(--shell-fg-muted);font:inherit;font-size:12px;padding:var(--shell-pad-sm) var(--shell-pad-md);margin-top:2px;display:inline-flex;align-items:center;gap:var(--shell-pad-sm);cursor:pointer;border-top:2px solid transparent;border-radius:2px 2px 0 0;touch-action:none}.tab.svelte-1kxcyhf:hover{color:var(--shell-fg);background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated))}.tab.active.svelte-1kxcyhf{color:var(--shell-fg);background:var(--shell-grad-bg, var(--shell-bg));border-top-color:var(--shell-accent)}.tab-icon.svelte-1kxcyhf{font-size:11px}.tab-label.svelte-1kxcyhf{white-space:nowrap}.tab-dirty.svelte-1kxcyhf{width:8px;height:8px;border-radius:50%;background:var(--shell-accent);flex-shrink:0}.tab-close.svelte-1kxcyhf{display:inline-flex;font-size:10px;line-height:1;padding:2px;border-radius:var(--shell-radius-sm);color:var(--shell-fg-muted);cursor:pointer;flex-shrink:0;margin-left:auto}.tab-close.svelte-1kxcyhf:hover{color:var(--shell-fg);background:var(--shell-bg-sunken)}.drop-indicator.svelte-1kxcyhf{position:absolute;width:2px;background:var(--shell-accent);box-shadow:0 0 6px var(--shell-accent);pointer-events:none;border-radius:1px}.tab-body.svelte-1kxcyhf{flex:1 1 auto;position:relative;min-width:0;min-height:0;overflow:hidden}.tab-body-pane.svelte-1kxcyhf{position:absolute;inset:0;min-width:0;min-height:0;display:none}.tab-body-pane.active.svelte-1kxcyhf{display:block}.slot.svelte-1czj0s8{position:relative;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden}.slot-placeholder.svelte-1czj0s8{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--shell-pad-sm);color:var(--shell-fg-muted);font-size:12px;text-align:center;padding:var(--shell-pad-md);background:repeating-linear-gradient(45deg,var(--shell-bg) 0 10px,var(--shell-bg-elevated) 10px 20px);border:1px dashed var(--shell-border-strong);pointer-events:none}.slot-id.svelte-1czj0s8{color:var(--shell-fg);font-family:var(--shell-font-mono);font-size:13px}.slot-meta.svelte-1czj0s8 code:where(.svelte-1czj0s8){font-family:var(--shell-font-mono);color:var(--shell-accent)}.slot-dims.svelte-1czj0s8{font-family:var(--shell-font-mono);color:var(--shell-fg-subtle);font-size:11px}.slot-drop-zone.svelte-re71zu{position:absolute;inset:0;pointer-events:none}.slot-drop-zone.active.svelte-re71zu{pointer-events:auto}.quad-highlight.svelte-re71zu{position:absolute;background:var(--shell-accent);opacity:.18;border:1px dashed var(--shell-accent);pointer-events:none;transition:inset 80ms ease}.quad-highlight.quad-left.svelte-re71zu{inset:0 50% 0 0}.quad-highlight.quad-right.svelte-re71zu{inset:0 0 0 50%}.quad-highlight.quad-top.svelte-re71zu{inset:0 0 50%}.quad-highlight.quad-bottom.svelte-re71zu{inset:50% 0 0}.quad-target.svelte-re71zu{position:absolute;pointer-events:none}.quad-target.quad-left.svelte-re71zu{inset:0 50% 0 0}.quad-target.quad-right.svelte-re71zu{inset:0 0 0 50%}.quad-target.quad-top.svelte-re71zu{inset:0 0 50%}.quad-target.quad-bottom.svelte-re71zu{inset:50% 0 0}.tab-slot-wrapper.svelte-1gw9g38,.leaf-slot-wrapper.svelte-1gw9g38{position:absolute;inset:0;min-width:0;min-height:0}.empty-tabs-placeholder.svelte-1gw9g38{width:100%;height:100%;min-width:0;min-height:0;position:relative}.empty-tabs-default.svelte-1gw9g38{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--shell-fg-muted);font-size:12px;background:repeating-linear-gradient(45deg,var(--shell-bg) 0 10px,var(--shell-bg-elevated) 10px 20px);border:1px dashed var(--shell-border-strong)}.empty-tabs-custom.svelte-1gw9g38{position:absolute;inset:0}.empty-tabs-drop.svelte-1gw9g38{position:absolute;inset:0;pointer-events:auto}.drag-preview.svelte-133fiip{position:absolute;display:inline-flex;align-items:center;gap:var(--shell-pad-sm);padding:var(--shell-pad-sm) var(--shell-pad-md);background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));color:var(--shell-fg);border:1px solid var(--shell-accent);border-radius:var(--shell-radius-sm);box-shadow:0 8px 24px #00000080;font-size:12px;font-family:var(--shell-font-ui);pointer-events:none;opacity:.9}.drag-preview-icon.svelte-133fiip{font-size:11px}.drag-preview-label.svelte-133fiip{white-space:nowrap}.sh3-float-frame.svelte-1p734al{position:absolute;display:flex;flex-direction:column;background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated, #1e1e1e));color:var(--shell-fg);border:1px solid var(--shell-border-strong);border-radius:var(--shell-radius);box-shadow:0 8px 24px #0006;pointer-events:auto}.sh3-float-header.svelte-1p734al{display:flex;align-items:center;justify-content:space-between;padding:4px 8px;background:var(--shell-bg, #111);cursor:move;user-select:none;border-bottom:1px solid var(--shell-border-strong);border-top-left-radius:var(--shell-radius);border-top-right-radius:var(--shell-radius);flex-shrink:0}.sh3-float-title.svelte-1p734al{font-size:12px;color:var(--shell-fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sh3-float-close.svelte-1p734al{background:transparent;border:none;color:var(--shell-fg);font-size:16px;line-height:1;cursor:pointer;padding:0 4px;flex-shrink:0}.sh3-float-body.svelte-1p734al{flex:1;position:relative;overflow:hidden;min-height:0}.sh3-float-layer.svelte-1rzcset{position:absolute;inset:0;pointer-events:none}.guest-banner.svelte-pymkh{display:flex;align-items:center;justify-content:center;gap:12px;padding:6px var(--shell-pad-md, 12px);background:color-mix(in srgb,var(--shell-accent, #7c7cf0) 15%,transparent);border-bottom:1px solid var(--shell-border, #3a3a5c);font-size:12px;color:var(--shell-fg, #e0e0e0)}.guest-banner-action.svelte-pymkh{padding:3px 10px;color:var(--shell-bg, #1a1a2e);font-size:11px;font-weight:600}.guest-signin-overlay.svelte-pymkh{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#00000080;z-index:9999}.guest-signin-card.svelte-pymkh{display:flex;flex-direction:column;gap:12px;padding:32px;background:var(--shell-bg-elevated, #252540);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius-lg, 12px);min-width:300px}.guest-signin-form.svelte-pymkh{display:flex;flex-direction:column;gap:10px}.guest-signin-input.svelte-pymkh{padding:8px 12px;background:var(--shell-bg, #1a1a2e);color:var(--shell-fg, #e0e0e0);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px);font-size:13px}.guest-signin-input.svelte-pymkh::placeholder{color:var(--shell-fg-muted, #888)}.guest-signin-actions.svelte-pymkh{display:flex;gap:8px}.guest-signin-btn.svelte-pymkh{flex:1;padding:8px;color:var(--shell-bg, #1a1a2e);font-weight:600}.guest-signin-btn.svelte-pymkh:disabled{opacity:.6;cursor:not-allowed}.guest-signin-cancel.svelte-pymkh{padding:8px 12px;background:transparent;color:var(--shell-fg-subtle, #aaa);border:1px solid var(--shell-border, #3a3a5c)}.guest-signin-error.svelte-pymkh{padding:6px 10px;font-size:12px;color:var(--shell-error, #d32f2f);background:color-mix(in srgb,var(--shell-error, #d32f2f) 10%,transparent);border-radius:var(--shell-radius, 6px)}.sh3-consent-backdrop.svelte-14me5sj{position:fixed;inset:0;background:#0000008c;display:flex;align-items:center;justify-content:center;z-index:9999}.sh3-consent-card.svelte-14me5sj{background:var(--shell-bg-elevated, #222);color:var(--shell-fg, #eee);border:1px solid var(--shell-border, #444);padding:1.5rem;border-radius:8px;min-width:360px;max-width:520px;box-shadow:0 8px 32px #0006}.sh3-consent-title.svelte-14me5sj{margin:0 0 1rem;font-size:1rem;font-weight:600;color:var(--shell-fg, #eee)}.sh3-consent-title.svelte-14me5sj span:where(.svelte-14me5sj){color:var(--shell-accent, #7eb8f7)}.sh3-consent-fields.svelte-14me5sj{margin:0 0 1rem;display:grid;grid-template-columns:auto 1fr;column-gap:.75rem;row-gap:.25rem;align-items:baseline}.sh3-consent-fields.svelte-14me5sj dt:where(.svelte-14me5sj){font-weight:600;color:var(--shell-fg-muted, #aaa);font-size:.8rem;text-transform:uppercase;letter-spacing:.05em;padding-top:.35rem}.sh3-consent-fields.svelte-14me5sj dd:where(.svelte-14me5sj){margin:0;padding-top:.35rem;word-break:break-word}.sh3-consent-scopes.svelte-14me5sj{display:flex;flex-wrap:wrap;gap:.25rem}.sh3-consent-scope.svelte-14me5sj{display:inline-block;padding:.1rem .35rem;background:#ffffff14;border:1px solid rgba(255,255,255,.12);border-radius:3px;font-size:.82em;font-family:var(--shell-font-mono, monospace)}.sh3-consent-actions.svelte-14me5sj{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1.25rem}.sh3-consent-deny.svelte-14me5sj,.sh3-consent-approve.svelte-14me5sj{padding:.4rem 1rem;border-radius:4px;font-size:.875rem;cursor:pointer;border:1px solid transparent;transition:opacity .1s}.sh3-consent-deny.svelte-14me5sj{background:transparent;color:var(--shell-fg-muted, #aaa);border-color:var(--shell-border, #444)}.sh3-consent-deny.svelte-14me5sj:hover{color:var(--shell-fg, #eee);border-color:var(--shell-fg-muted, #aaa)}.sh3-consent-approve.svelte-14me5sj{background:var(--shell-accent, #7eb8f7);color:#000;border-color:var(--shell-accent, #7eb8f7)}.sh3-consent-approve.svelte-14me5sj:hover{opacity:.85}.shell.svelte-187fra4{display:grid;grid-template-rows:var(--shell-tabbar-height) auto 1fr var(--shell-statusbar-height);height:100%;width:100%;position:relative;background:var(--shell-grad-bg, var(--shell-bg));color:var(--shell-fg)}.shell-tabbar.svelte-187fra4{display:flex;align-items:center;gap:var(--shell-pad-md);padding:0 var(--shell-pad-md);background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));border-bottom:1px solid var(--shell-border);user-select:none}.shell-tabbar-brand.svelte-187fra4{font-weight:600;color:var(--shell-accent);letter-spacing:.5px}.shell-content.svelte-187fra4{position:relative;overflow:hidden;background:var(--shell-grad-bg, var(--shell-bg));min-width:0;min-height:0}.shell-statusbar.svelte-187fra4{display:flex;align-items:center;justify-content:space-between;padding:0 var(--shell-pad-md);background:var(--shell-grad-bg-sunken, var(--shell-bg-sunken));border-top:1px solid var(--shell-border);color:var(--shell-fg-muted);font-size:11px;user-select:none}.shell-overlays.svelte-187fra4,.shell-overlay-root.svelte-187fra4{position:absolute;inset:0;pointer-events:none}.shell-tabbar-home-button.svelte-187fra4{display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;background:transparent;color:var(--shell-fg-muted);border:1px solid var(--shell-border)}.shell-tabbar-home-button.svelte-187fra4:hover:not(:disabled){color:var(--shell-fg);border-color:var(--shell-fg-muted)}.shell-tabbar-home-button.svelte-187fra4:disabled{color:var(--shell-fg-subtle);border-color:var(--shell-border);cursor:default}.shell-tabbar-home-icon.svelte-187fra4{width:14px;height:14px}.shell-tabbar-user.svelte-187fra4{display:flex;align-items:center;gap:6px;margin-left:auto}.shell-tabbar-user-name.svelte-187fra4{font-size:12px;color:var(--shell-fg-subtle)}.shell-tabbar-tag.svelte-187fra4{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#fff;background:var(--shell-accent);padding:1px 6px;border-radius:6px}.shell-tabbar-signout.svelte-187fra4{display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;background:transparent;color:var(--shell-fg-muted);border:1px solid var(--shell-border)}.shell-tabbar-signout.svelte-187fra4:hover{color:var(--shell-fg);border-color:var(--shell-fg-muted)}.shell-tabbar-signout-icon.svelte-187fra4{width:14px;height:14px}.signin-wall.svelte-lh4k15{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:var(--shell-grad-bg, var(--shell-bg, #1a1a2e));color:var(--shell-fg, #e0e0e0);font-family:system-ui,sans-serif}.signin-card.svelte-lh4k15{display:flex;flex-direction:column;align-items:center;gap:16px;padding:48px 40px;background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated, #252540));border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius-lg, 12px);min-width:320px}.signin-brand.svelte-lh4k15{margin:0 0 8px;font-size:42px;color:var(--shell-accent, #7c7cf0);letter-spacing:2px}.signin-form.svelte-lh4k15{display:flex;flex-direction:column;gap:12px;width:100%}.signin-input.svelte-lh4k15{padding:10px 14px;background:var(--shell-bg, #1a1a2e);color:var(--shell-fg, #e0e0e0);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px);font-size:14px}.signin-input.svelte-lh4k15::placeholder{color:var(--shell-fg-muted, #888)}.signin-btn.svelte-lh4k15{padding:10px 16px;color:var(--shell-bg, #1a1a2e);font-weight:600;font-size:14px}.signin-btn.svelte-lh4k15:disabled{opacity:.6;cursor:not-allowed}.signin-link.svelte-lh4k15{background:none;color:var(--shell-accent, #7c7cf0);font-size:13px;padding:0}.signin-link.svelte-lh4k15:hover{text-decoration:underline}.signin-error.svelte-lh4k15{padding:8px 12px;font-size:13px;color:var(--shell-error, #d32f2f);background:color-mix(in srgb,var(--shell-error, #d32f2f) 10%,transparent);border-radius:var(--shell-radius, 6px);width:100%;text-align:center}:root{--shell-bg: #1a1b1e;--shell-bg-elevated: #22232a;--shell-bg-sunken: #141518;--shell-border: #2e3038;--shell-border-strong: #3c3f4a;--shell-fg: #e4e6eb;--shell-fg-muted: #9aa0aa;--shell-fg-subtle: #6b7280;--shell-accent: #6ea8fe;--shell-accent-muted: #3a5580;--shell-input-bg: #2a2a2a;--shell-error: #f87171;--shell-warning: #fbbf24;--shell-success: #34d399;--shell-font-ui: system-ui, -apple-system, "Segoe UI", sans-serif;--shell-font-mono: ui-monospace, "Cascadia Code", "Consolas", monospace;--shell-font-size: 13px;--shell-line: 1.45;--shell-radius-sm: 3px;--shell-radius: 4px;--shell-radius-md: 6px;--shell-radius-lg: 8px;--shell-pad-xs: 2px;--shell-pad-sm: 4px;--shell-pad-md: 8px;--shell-pad-lg: 12px;--shell-tabbar-height: 32px;--shell-statusbar-height: 22px;--shell-z-layer-0: 0;--shell-z-layer-1: 100;--shell-z-layer-2: 200;--shell-z-layer-3: 300;--shell-z-layer-4: 400;--shell-z-layer-5: 500;--shell-z-layer-6: 600}*,*:before,*:after{box-sizing:border-box}html,body{margin:0;padding:0;height:100%;overflow:hidden;background:var(--shell-grad-bg, var(--shell-bg));color:var(--shell-fg);font-family:var(--shell-font-ui);font-size:var(--shell-font-size);line-height:var(--shell-line);-webkit-font-smoothing:antialiased}#app{height:100%}
package/app/index.html CHANGED
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>SH3</title>
7
- <script type="module" crossorigin src="/assets/index-C3rCTpjL.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-GfhVhkjD.css">
7
+ <script type="module" crossorigin src="/assets/index-Cb-zoqb1.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-DPcN5Lor.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="app"></div>
package/dist/auth.d.ts CHANGED
@@ -20,10 +20,17 @@ import type { SettingsStore } from './settings.js';
20
20
  */
21
21
  export declare function sessionAuth(sessions: SessionStore, settings: SettingsStore): MiddlewareHandler;
22
22
  /**
23
- * Admin middleware — requires admin session OR valid API key.
24
- * Must be mounted after sessionAuth so c.get('session') is available.
23
+ * Admin middleware — backwards-compatible alias for `scopeRequired('admin:*')`.
24
+ * New code should use `scopeRequired('admin:*')` directly.
25
+ *
26
+ * NOTE: `resolveCaller` must be mounted before any route that uses this
27
+ * middleware. The server bootstrap in index.ts wires the chain:
28
+ * sessionAuth → resolveCaller → scope guards.
29
+ *
30
+ * The legacy signature (sessions, keys, settings) is preserved so call
31
+ * sites in index.ts keep compiling; arguments are ignored.
25
32
  */
26
- export declare function adminAuth(sessions: SessionStore, keys: KeyStore, settings?: SettingsStore): MiddlewareHandler;
33
+ export declare function adminAuth(_sessions: SessionStore, _keys: KeyStore, settings?: SettingsStore): MiddlewareHandler;
27
34
  /**
28
35
  * Helper to set the session cookie on login.
29
36
  */
package/dist/auth.js CHANGED
@@ -25,13 +25,6 @@ function extractSessionToken(c) {
25
25
  return auth.slice(7);
26
26
  return null;
27
27
  }
28
- /** Extract API key from Authorization header. */
29
- function extractApiKey(c) {
30
- const auth = c.req.header('Authorization');
31
- if (auth?.startsWith('Bearer sh3_'))
32
- return auth.slice(7);
33
- return null;
34
- }
35
28
  /**
36
29
  * Session middleware — requires a valid session on non-GET requests
37
30
  * when auth is required. Attaches session to context variable.
@@ -61,25 +54,24 @@ export function sessionAuth(sessions, settings) {
61
54
  };
62
55
  }
63
56
  /**
64
- * Admin middleware — requires admin session OR valid API key.
65
- * Must be mounted after sessionAuth so c.get('session') is available.
57
+ * Admin middleware — backwards-compatible alias for `scopeRequired('admin:*')`.
58
+ * New code should use `scopeRequired('admin:*')` directly.
59
+ *
60
+ * NOTE: `resolveCaller` must be mounted before any route that uses this
61
+ * middleware. The server bootstrap in index.ts wires the chain:
62
+ * sessionAuth → resolveCaller → scope guards.
63
+ *
64
+ * The legacy signature (sessions, keys, settings) is preserved so call
65
+ * sites in index.ts keep compiling; arguments are ignored.
66
66
  */
67
- export function adminAuth(sessions, keys, settings) {
67
+ export function adminAuth(_sessions, _keys, settings) {
68
68
  return async (c, next) => {
69
- // When auth is disabled (--no-auth), skip admin checks entirely
70
- if (settings && !settings.get().auth.required) {
69
+ if (settings && !settings.get().auth.required)
71
70
  return next();
72
- }
73
- // Check session first (set by sessionAuth)
74
- const session = c.get('session');
75
- if (session?.role === 'admin') {
71
+ // Delegate to scopeRequired indirectly by checking the caller.
72
+ const caller = c.get('caller');
73
+ if (caller?.scopes.includes('admin:*'))
76
74
  return next();
77
- }
78
- // Fallback: API key (for external tools / CLI)
79
- const apiKey = extractApiKey(c);
80
- if (apiKey && keys.validate(apiKey)) {
81
- return next();
82
- }
83
75
  return c.json({ error: 'Admin privileges required' }, 403);
84
76
  };
85
77
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Caller resolution — produces a CallerIdentity from session or bearer token.
3
+ *
4
+ * Must be mounted after sessionAuth. Session wins over bearer when both
5
+ * are present; bearer tokens are ignored on session-authenticated calls.
6
+ */
7
+ import type { MiddlewareHandler } from 'hono';
8
+ import type { KeyStore } from './keys.js';
9
+ export interface CallerIdentity {
10
+ tenantId: string | null;
11
+ userId: string | null;
12
+ scopes: string[];
13
+ connectorId?: string;
14
+ source: 'session' | 'key' | 'none';
15
+ }
16
+ export declare function resolveCaller(keys: KeyStore): MiddlewareHandler;
package/dist/caller.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Caller resolution — produces a CallerIdentity from session or bearer token.
3
+ *
4
+ * Must be mounted after sessionAuth. Session wins over bearer when both
5
+ * are present; bearer tokens are ignored on session-authenticated calls.
6
+ */
7
+ function identityFromSession(session) {
8
+ const scopes = ['session:user'];
9
+ if (session.role === 'admin')
10
+ scopes.unshift('admin:*');
11
+ return {
12
+ tenantId: session.userId,
13
+ userId: session.userId,
14
+ scopes,
15
+ source: 'session',
16
+ };
17
+ }
18
+ function extractBearerKey(authorization) {
19
+ if (!authorization)
20
+ return null;
21
+ if (!authorization.startsWith('Bearer sh3_'))
22
+ return null;
23
+ return authorization.slice('Bearer '.length);
24
+ }
25
+ export function resolveCaller(keys) {
26
+ return async (c, next) => {
27
+ const session = c.get('session');
28
+ if (session) {
29
+ c.set('caller', identityFromSession(session));
30
+ return next();
31
+ }
32
+ const token = extractBearerKey(c.req.header('Authorization'));
33
+ if (token) {
34
+ const row = keys.resolve(token);
35
+ if (row) {
36
+ c.set('caller', {
37
+ tenantId: row.tenantId,
38
+ userId: row.ownerUserId,
39
+ scopes: [...row.scopes],
40
+ connectorId: row.connectorId,
41
+ source: 'key',
42
+ });
43
+ return next();
44
+ }
45
+ }
46
+ c.set('caller', {
47
+ tenantId: null,
48
+ userId: null,
49
+ scopes: [],
50
+ source: 'none',
51
+ });
52
+ return next();
53
+ };
54
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Filesystem-backed DocumentBackend for sh3-server.
3
+ *
4
+ * Storage layout mirrors the docs HTTP API:
5
+ * <docsDir>/<tenantId>/<shardId>/<path>
6
+ *
7
+ * where docsDir = <dataDir>/docs
8
+ */
9
+ import type { DocumentBackend } from 'sh3-core/server-sync';
10
+ export declare function createFsDocumentBackend(dataDir: string): DocumentBackend;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Filesystem-backed DocumentBackend for sh3-server.
3
+ *
4
+ * Storage layout mirrors the docs HTTP API:
5
+ * <docsDir>/<tenantId>/<shardId>/<path>
6
+ *
7
+ * where docsDir = <dataDir>/docs
8
+ */
9
+ import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync, statSync, readdirSync, } from 'node:fs';
10
+ import { join, dirname, relative, resolve, sep } from 'node:path';
11
+ function collectFiles(dir, base) {
12
+ const results = [];
13
+ if (!existsSync(dir))
14
+ return results;
15
+ const entries = readdirSync(dir, { withFileTypes: true });
16
+ for (const entry of entries) {
17
+ const full = join(dir, entry.name);
18
+ if (entry.isDirectory()) {
19
+ results.push(...collectFiles(full, base));
20
+ }
21
+ else {
22
+ const stat = statSync(full);
23
+ results.push({
24
+ path: relative(base, full).replace(/\\/g, '/'),
25
+ size: stat.size,
26
+ lastModified: stat.mtimeMs,
27
+ });
28
+ }
29
+ }
30
+ return results;
31
+ }
32
+ class FsDocumentBackend {
33
+ #docsDir;
34
+ constructor(dataDir) {
35
+ this.#docsDir = join(dataDir, 'docs');
36
+ }
37
+ #resolve(tenantId, shardId, path) {
38
+ const base = resolve(this.#docsDir, tenantId, shardId);
39
+ const resolved = resolve(base, path);
40
+ if (resolved !== base && !resolved.startsWith(base + sep)) {
41
+ throw new Error(`Path escapes tenant/shard boundary: ${path}`);
42
+ }
43
+ return resolved;
44
+ }
45
+ async read(tenantId, shardId, path) {
46
+ const resolved = this.#resolve(tenantId, shardId, path);
47
+ if (!existsSync(resolved))
48
+ return null;
49
+ const buf = readFileSync(resolved);
50
+ try {
51
+ return new TextDecoder('utf-8', { fatal: true }).decode(buf);
52
+ }
53
+ catch {
54
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
55
+ }
56
+ }
57
+ async write(tenantId, shardId, path, content) {
58
+ const resolved = this.#resolve(tenantId, shardId, path);
59
+ mkdirSync(dirname(resolved), { recursive: true });
60
+ if (typeof content === 'string') {
61
+ writeFileSync(resolved, content, 'utf-8');
62
+ }
63
+ else {
64
+ writeFileSync(resolved, Buffer.from(content));
65
+ }
66
+ }
67
+ async delete(tenantId, shardId, path) {
68
+ const resolved = this.#resolve(tenantId, shardId, path);
69
+ if (existsSync(resolved))
70
+ unlinkSync(resolved);
71
+ }
72
+ async list(tenantId, shardId) {
73
+ const dir = join(this.#docsDir, tenantId, shardId);
74
+ return collectFiles(dir, dir);
75
+ }
76
+ async exists(tenantId, shardId, path) {
77
+ return existsSync(this.#resolve(tenantId, shardId, path));
78
+ }
79
+ async listAllShards(tenantId) {
80
+ const tenantDir = join(this.#docsDir, tenantId);
81
+ if (!existsSync(tenantDir))
82
+ return [];
83
+ return readdirSync(tenantDir, { withFileTypes: true })
84
+ .filter((e) => e.isDirectory())
85
+ .map((e) => e.name);
86
+ }
87
+ async listAllDocuments(tenantId) {
88
+ const tenantDir = join(this.#docsDir, tenantId);
89
+ if (!existsSync(tenantDir))
90
+ return [];
91
+ const out = [];
92
+ for (const entry of readdirSync(tenantDir, { withFileTypes: true })) {
93
+ if (!entry.isDirectory())
94
+ continue;
95
+ const shardDir = join(tenantDir, entry.name);
96
+ for (const f of collectFiles(shardDir, shardDir)) {
97
+ out.push({ ...f, shardId: entry.name });
98
+ }
99
+ }
100
+ return out;
101
+ }
102
+ }
103
+ export function createFsDocumentBackend(dataDir) {
104
+ return new FsDocumentBackend(dataDir);
105
+ }
package/dist/index.js CHANGED
@@ -19,13 +19,16 @@ import { UserStore } from './users.js';
19
19
  import { SessionStore } from './sessions.js';
20
20
  import { SettingsStore } from './settings.js';
21
21
  import { sessionAuth, adminAuth } from './auth.js';
22
+ import { resolveCaller } from './caller.js';
22
23
  import { createAuthRouter } from './routes/auth.js';
23
24
  import { createBootRouter } from './routes/boot.js';
24
25
  import { createAdminRouter } from './routes/admin.js';
25
26
  import { createDocsRouter } from './routes/docs.js';
26
27
  import { createEnvStateRouter } from './routes/env-state.js';
28
+ import { createKeysRouter } from './routes/keys.js';
27
29
  import { loadPackages, scanPackages, servePackageBundles, createPackageManagementRoutes } from './packages.js';
28
- import { ShardRouter, adminOnly } from './shard-router.js';
30
+ import { createFsDocumentBackend } from './fs-backend.js';
31
+ import { ShardRouter, buildShardCtx } from './shard-router.js';
29
32
  import { registerTenantFsRoutes } from './tenant-fs/index.js';
30
33
  import shellShardServer from './shell-shard/index.js';
31
34
  export async function createServer(options = {}) {
@@ -45,6 +48,8 @@ export async function createServer(options = {}) {
45
48
  const keys = new KeyStore(dataDir);
46
49
  const users = new UserStore(dataDir);
47
50
  const settings = new SettingsStore(dataDir);
51
+ // Server-side document backend (used by ctx.sync / ctx.syncRegistry on server shards)
52
+ const documentBackend = createFsDocumentBackend(dataDir);
48
53
  // --no-auth: disable auth enforcement (Tauri sidecar / local-owner mode)
49
54
  if (options.noAuth) {
50
55
  settings.update({ auth: { required: false, guestAllowed: true } });
@@ -113,8 +118,14 @@ export async function createServer(options = {}) {
113
118
  app.route('/api/docs', createDocsRouter(dataDir));
114
119
  // --- Session-gated routes ---
115
120
  app.use('/api/*', sessionAuth(sessions, settings));
121
+ app.use('/api/*', resolveCaller(keys));
116
122
  // Environment state API (per-shard server-backed config)
117
123
  app.route('/api/env-state', createEnvStateRouter(dataDir));
124
+ // User-tenant key management (list/revoke). Mint lives at /api/shards-keys.
125
+ app.route('/api/keys', createKeysRouter(keys, async (event) => {
126
+ // Broadcast will be wired in Task 10 once the shell has a consumer.
127
+ console.log(`[sh3] key revoked: tenant=${event.tenantId} id=${event.id}`);
128
+ }));
118
129
  // Package listing (public read, writes need admin — handled by admin middleware on management routes)
119
130
  app.get('/api/packages', (c) => {
120
131
  const packages = scanPackages(dataDir);
@@ -168,7 +179,7 @@ export async function createServer(options = {}) {
168
179
  app.use('/api/packages/install', adminAuth(sessions, keys, settings));
169
180
  app.use('/api/packages/uninstall', adminAuth(sessions, keys, settings));
170
181
  const frameworkShardIds = ['__sh3core__', 'shell', 'sh3-store', 'sh3-admin'];
171
- app.route('/api/packages', createPackageManagementRoutes(shardRouter, dataDir, keys, settings, wsRegister, frameworkShardIds));
182
+ app.route('/api/packages', createPackageManagementRoutes(shardRouter, dataDir, keys, settings, wsRegister, documentBackend, frameworkShardIds));
172
183
  // Serve client bundles from discovered packages
173
184
  servePackageBundles(app, dataDir, settings);
174
185
  // Framework built-in: shell-shard server routes.
@@ -187,12 +198,9 @@ export async function createServer(options = {}) {
187
198
  const shellDataDir = join(shellPkgDir, 'data');
188
199
  mkdirSync(shellDataDir, { recursive: true });
189
200
  const shellSubApp = new Hono();
190
- await shellShardServer.routes(shellSubApp, {
191
- shardId: 'shell',
192
- dataDir: shellDataDir,
193
- adminOnly: adminOnly(keys, settings),
194
- wsRegister,
195
- });
201
+ const shellPermissions = [];
202
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
203
+ await shellShardServer.routes(shellSubApp, buildShardCtx('shell', shellDataDir, shellPermissions, keys, settings, wsRegister, documentBackend));
196
204
  app.route('/api/shell', shellSubApp);
197
205
  }
198
206
  // Framework-level tenant filesystem API — read-only, jailed to each user's documents.
@@ -218,7 +226,7 @@ export async function createServer(options = {}) {
218
226
  async start() {
219
227
  // First-boot: generate admin key + admin user
220
228
  if (keys.isEmpty()) {
221
- const initial = keys.generate('Initial admin key');
229
+ const initial = keys.generate({ label: 'Initial admin key', tenantId: null, ownerUserId: null, mintedByShardId: null, scopes: ['admin:*'] });
222
230
  const tempPassword = UserStore.generatePassword();
223
231
  await users.create({
224
232
  username: 'admin',
@@ -246,7 +254,7 @@ export async function createServer(options = {}) {
246
254
  console.log(`[sh3] Seeded official registry: ${registryUrl}`);
247
255
  }
248
256
  }
249
- await loadPackages(shardRouter, dataDir, keys, settings, wsRegister);
257
+ await loadPackages(shardRouter, dataDir, keys, settings, wsRegister, documentBackend);
250
258
  // Periodic session cleanup (every 15 minutes)
251
259
  setInterval(() => sessions.cleanup(), 15 * 60 * 1000);
252
260
  // API 404
package/dist/keys.d.ts CHANGED
@@ -1,33 +1,47 @@
1
1
  /**
2
- * API key managementgenerate, validate, list, revoke.
2
+ * Unified API key storeadmin, user-tenant, and connector-bound keys.
3
3
  *
4
- * Keys are stored in {dataDir}/keys.json. Each key has an id
5
- * (short identifier for display/revocation), the full key value (used
6
- * for auth), a label, and creation timestamp.
4
+ * Layout on disk:
5
+ * <dataDir>/admin-keys.json — tenantId: null rows only
6
+ * <dataDir>/users/<tenantId>/__system__/keys.json user-tenant rows
7
+ *
8
+ * Legacy migration: a pre-existing <dataDir>/keys.json is treated as admin
9
+ * keys and moved to admin-keys.json on first load, with the original renamed
10
+ * to keys.json.legacy.
7
11
  */
8
12
  export interface ApiKey {
9
- /** Short id for display and revocation. */
10
13
  id: string;
11
- /** The full bearer token value. */
12
14
  key: string;
13
- /** Human-readable label. */
14
15
  label: string;
15
- /** ISO timestamp of creation. */
16
+ tenantId: string | null;
17
+ ownerUserId: string | null;
18
+ mintedByShardId: string | null;
19
+ scopes: string[];
20
+ connectorId?: string;
16
21
  createdAt: string;
22
+ expiresAt?: string;
23
+ }
24
+ export type ApiKeyPublic = Omit<ApiKey, 'key'>;
25
+ export interface GenerateInput {
26
+ label: string;
27
+ tenantId: string | null;
28
+ ownerUserId: string | null;
29
+ mintedByShardId: string | null;
30
+ scopes: string[];
31
+ connectorId?: string;
32
+ expiresAt?: string;
17
33
  }
18
34
  export declare class KeyStore {
19
35
  #private;
20
36
  constructor(dataDir: string);
21
- /** Validate a bearer token. Returns true if the key exists. */
22
- validate(token: string): boolean;
23
- /** List all keys (returns id, label, createdAt — never the full key). */
24
- list(): Omit<ApiKey, 'key'>[];
25
- /** List all keys including full key values (admin only). */
26
- listFull(): ApiKey[];
27
- /** Generate a new API key. Returns the full key (only shown once). */
28
- generate(label: string): ApiKey;
29
- /** Revoke a key by id. Returns true if found and removed. */
30
- revoke(id: string): boolean;
31
- /** True if no keys exist (first boot). */
37
+ generate(input: GenerateInput): ApiKey;
38
+ resolve(token: string): ApiKey | null;
39
+ listForTenant(tenantId: string): ApiKeyPublic[];
40
+ listForShard(tenantId: string, shardId: string): ApiKeyPublic[];
41
+ listAdmin(): ApiKeyPublic[];
42
+ listAll(): ApiKeyPublic[];
43
+ revoke(tenantId: string | null, id: string): ApiKeyPublic | null;
32
44
  isEmpty(): boolean;
45
+ /** @deprecated use resolve() — kept until all callers migrate. */
46
+ validate(token: string): boolean;
33
47
  }