sh3-server 0.7.5 → 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.
- package/app/assets/index-Cb-zoqb1.js +17 -0
- package/app/assets/index-Cb-zoqb1.js.map +1 -0
- package/app/assets/index-DPcN5Lor.css +1 -0
- package/app/index.html +2 -2
- package/dist/auth.d.ts +10 -3
- package/dist/auth.js +14 -22
- package/dist/caller.d.ts +16 -0
- package/dist/caller.js +54 -0
- package/dist/cli.js +9 -7
- package/dist/fs-backend.d.ts +10 -0
- package/dist/fs-backend.js +105 -0
- package/dist/index.js +30 -12
- package/dist/keys.d.ts +33 -19
- package/dist/keys.js +172 -49
- package/dist/packages.d.ts +23 -3
- package/dist/packages.js +67 -6
- package/dist/routes/admin.js +7 -3
- package/dist/routes/docs.d.ts +2 -0
- package/dist/routes/docs.js +30 -0
- package/dist/routes/keys.d.ts +21 -0
- package/dist/routes/keys.js +164 -0
- package/dist/scope.d.ts +9 -0
- package/dist/scope.js +25 -0
- package/dist/settings.d.ts +13 -0
- package/dist/settings.js +33 -0
- package/dist/shard-router.d.ts +9 -2
- package/dist/shard-router.js +58 -29
- package/dist/shell-shard/index.d.ts +6 -1
- package/dist/shell-shard/index.js +3 -1
- package/dist/shell-shard/session-manager.d.ts +2 -1
- package/dist/shell-shard/session-manager.js +15 -2
- package/dist/shell-shard/ws.js +14 -14
- package/dist/tenant-fs/http.d.ts +15 -0
- package/dist/tenant-fs/http.js +109 -0
- package/dist/tenant-fs/index.d.ts +4 -0
- package/dist/tenant-fs/index.js +4 -0
- package/dist/tenant-fs/paths.d.ts +23 -0
- package/dist/tenant-fs/paths.js +51 -0
- package/dist/tenant-fs/resolve.d.ts +16 -0
- package/dist/tenant-fs/resolve.js +48 -0
- package/dist/tenant-fs/session-required.d.ts +11 -0
- package/dist/tenant-fs/session-required.js +19 -0
- package/package.json +2 -2
- package/app/assets/index-25fXNyG3.js +0 -12
- package/app/assets/index-25fXNyG3.js.map +0 -1
- package/app/assets/index-BcQ1cruS.css +0 -1
|
@@ -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-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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 —
|
|
24
|
-
*
|
|
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(
|
|
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 —
|
|
65
|
-
*
|
|
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(
|
|
67
|
+
export function adminAuth(_sessions, _keys, settings) {
|
|
68
68
|
return async (c, next) => {
|
|
69
|
-
|
|
70
|
-
if (settings && !settings.get().auth.required) {
|
|
69
|
+
if (settings && !settings.get().auth.required)
|
|
71
70
|
return next();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
}
|
package/dist/caller.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -21,10 +21,12 @@ const { values } = parseArgs({
|
|
|
21
21
|
'no-auth': { type: 'boolean', default: false },
|
|
22
22
|
},
|
|
23
23
|
});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
(async () => {
|
|
25
|
+
const server = await createServer({
|
|
26
|
+
port: parseInt(values.port, 10),
|
|
27
|
+
dataDir: values.data,
|
|
28
|
+
distDir: values.dist,
|
|
29
|
+
noAuth: values['no-auth'],
|
|
30
|
+
});
|
|
31
|
+
await server.start();
|
|
32
|
+
})();
|
|
@@ -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,17 @@ 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 {
|
|
30
|
+
import { createFsDocumentBackend } from './fs-backend.js';
|
|
31
|
+
import { ShardRouter, buildShardCtx } from './shard-router.js';
|
|
32
|
+
import { registerTenantFsRoutes } from './tenant-fs/index.js';
|
|
29
33
|
import shellShardServer from './shell-shard/index.js';
|
|
30
34
|
export async function createServer(options = {}) {
|
|
31
35
|
const port = options.port ?? 3000;
|
|
@@ -44,6 +48,8 @@ export async function createServer(options = {}) {
|
|
|
44
48
|
const keys = new KeyStore(dataDir);
|
|
45
49
|
const users = new UserStore(dataDir);
|
|
46
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);
|
|
47
53
|
// --no-auth: disable auth enforcement (Tauri sidecar / local-owner mode)
|
|
48
54
|
if (options.noAuth) {
|
|
49
55
|
settings.update({ auth: { required: false, guestAllowed: true } });
|
|
@@ -112,8 +118,14 @@ export async function createServer(options = {}) {
|
|
|
112
118
|
app.route('/api/docs', createDocsRouter(dataDir));
|
|
113
119
|
// --- Session-gated routes ---
|
|
114
120
|
app.use('/api/*', sessionAuth(sessions, settings));
|
|
121
|
+
app.use('/api/*', resolveCaller(keys));
|
|
115
122
|
// Environment state API (per-shard server-backed config)
|
|
116
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
|
+
}));
|
|
117
129
|
// Package listing (public read, writes need admin — handled by admin middleware on management routes)
|
|
118
130
|
app.get('/api/packages', (c) => {
|
|
119
131
|
const packages = scanPackages(dataDir);
|
|
@@ -166,9 +178,10 @@ export async function createServer(options = {}) {
|
|
|
166
178
|
// Package management (install/uninstall) — admin-gated
|
|
167
179
|
app.use('/api/packages/install', adminAuth(sessions, keys, settings));
|
|
168
180
|
app.use('/api/packages/uninstall', adminAuth(sessions, keys, settings));
|
|
169
|
-
|
|
181
|
+
const frameworkShardIds = ['__sh3core__', 'shell', 'sh3-store', 'sh3-admin'];
|
|
182
|
+
app.route('/api/packages', createPackageManagementRoutes(shardRouter, dataDir, keys, settings, wsRegister, documentBackend, frameworkShardIds));
|
|
170
183
|
// Serve client bundles from discovered packages
|
|
171
|
-
servePackageBundles(app, dataDir);
|
|
184
|
+
servePackageBundles(app, dataDir, settings);
|
|
172
185
|
// Framework built-in: shell-shard server routes.
|
|
173
186
|
//
|
|
174
187
|
// Mounted directly on the top-level Hono app (not via shardRouter) so
|
|
@@ -185,16 +198,21 @@ export async function createServer(options = {}) {
|
|
|
185
198
|
const shellDataDir = join(shellPkgDir, 'data');
|
|
186
199
|
mkdirSync(shellDataDir, { recursive: true });
|
|
187
200
|
const shellSubApp = new Hono();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
adminOnly: adminOnly(keys, settings),
|
|
192
|
-
wsRegister,
|
|
193
|
-
});
|
|
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));
|
|
194
204
|
app.route('/api/shell', shellSubApp);
|
|
195
205
|
}
|
|
206
|
+
// Framework-level tenant filesystem API — read-only, jailed to each user's documents.
|
|
207
|
+
// Mounted before the dynamic shard router so /api/fs/* is claimed first.
|
|
208
|
+
registerTenantFsRoutes(app, {
|
|
209
|
+
dataDir,
|
|
210
|
+
rootBase: settings.get().tenants?.rootBase ?? '',
|
|
211
|
+
settings,
|
|
212
|
+
maxReadBytes: 10 * 1024 * 1024,
|
|
213
|
+
});
|
|
196
214
|
// Dynamic shard routes (packages). The catch-all comes after static
|
|
197
|
-
// framework mounts above so /api/shell/*
|
|
215
|
+
// framework mounts above so /api/shell/* and /api/fs/* are claimed first.
|
|
198
216
|
app.all('/api/:shardId/*', shardRouter.handler());
|
|
199
217
|
return {
|
|
200
218
|
app,
|
|
@@ -208,7 +226,7 @@ export async function createServer(options = {}) {
|
|
|
208
226
|
async start() {
|
|
209
227
|
// First-boot: generate admin key + admin user
|
|
210
228
|
if (keys.isEmpty()) {
|
|
211
|
-
const initial = keys.generate('Initial admin key');
|
|
229
|
+
const initial = keys.generate({ label: 'Initial admin key', tenantId: null, ownerUserId: null, mintedByShardId: null, scopes: ['admin:*'] });
|
|
212
230
|
const tempPassword = UserStore.generatePassword();
|
|
213
231
|
await users.create({
|
|
214
232
|
username: 'admin',
|
|
@@ -236,7 +254,7 @@ export async function createServer(options = {}) {
|
|
|
236
254
|
console.log(`[sh3] Seeded official registry: ${registryUrl}`);
|
|
237
255
|
}
|
|
238
256
|
}
|
|
239
|
-
await loadPackages(shardRouter, dataDir, keys, settings, wsRegister);
|
|
257
|
+
await loadPackages(shardRouter, dataDir, keys, settings, wsRegister, documentBackend);
|
|
240
258
|
// Periodic session cleanup (every 15 minutes)
|
|
241
259
|
setInterval(() => sessions.cleanup(), 15 * 60 * 1000);
|
|
242
260
|
// API 404
|
package/dist/keys.d.ts
CHANGED
|
@@ -1,33 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* API key
|
|
2
|
+
* Unified API key store — admin, user-tenant, and connector-bound keys.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
}
|