Cuts 0.54.14. Repairs the [Unreleased] changelog state left by three PRs that merged since v0.54.13 without proper changelog hygiene, sweeps dead code orphaned by #305 and earlier PRs, and bumps the version. - CHANGELOG: #307 bullets moved out of the released [0.54.10] section into [Unreleased]; backfilled missing entries for #305 (Removed) and #308 (Changed); [Unreleased] -> [0.54.14] release cut. - pyproject.toml: 0.54.13 -> 0.54.14. - app/web/router.py: removed orphaned gws_oauth / instance_admin_email / connector_prompts keys from the shared _build_context ctx dict. - app/web/templates/home_not_onboarded.html: swept dead connector-tile, automode, and setup-collapsible/minimize CSS + the orphaned JS click-wiring.
1546 lines
59 KiB
HTML
1546 lines
59 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Setup — {{ instance_name or "AI Data Analyst" }}{% endblock %}
|
||
|
||
{% block content %}
|
||
{% include "_page_chrome.html" %}
|
||
<style>
|
||
.home-mock {
|
||
--hp-primary: #0073D1;
|
||
--hp-primary-dark: #0056A3;
|
||
--hp-primary-light: #E6F3FC;
|
||
--hp-border: #E5E7EB;
|
||
--hp-border-light: #F3F4F6;
|
||
--hp-text-primary: #111827;
|
||
--hp-text-secondary: #6B7280;
|
||
--hp-text-muted: #9CA3AF;
|
||
--hp-orange: #C2410C;
|
||
--hp-orange-light: #FED7AA;
|
||
--hp-font-mono: ui-monospace, "SF Mono", Consolas, monospace;
|
||
color: var(--hp-text-primary);
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
.home-mock * { box-sizing: border-box; }
|
||
|
||
.home-mock .install-hero {
|
||
position: relative;
|
||
background: linear-gradient(135deg, #0073D1 0%, #0056A3 100%);
|
||
color: white;
|
||
border-radius: 16px;
|
||
padding: 38px 40px;
|
||
margin-bottom: 22px;
|
||
box-shadow: 0 8px 24px rgba(0, 86, 163, 0.18);
|
||
}
|
||
.home-mock .install-hero-close {
|
||
position: absolute;
|
||
top: 14px;
|
||
right: 14px;
|
||
width: 32px;
|
||
height: 32px;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.14);
|
||
color: white;
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: background 0.15s;
|
||
}
|
||
.home-mock .install-hero-close:hover,
|
||
.home-mock .install-hero-close:focus-visible {
|
||
background: rgba(255, 255, 255, 0.28);
|
||
outline: none;
|
||
}
|
||
.home-mock .offboard-strip {
|
||
margin: 0 0 22px;
|
||
padding: 10px 14px;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 10px;
|
||
background: var(--hp-border-light);
|
||
color: var(--hp-text-secondary);
|
||
font-size: 13px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
.home-mock .offboard-strip button {
|
||
border: 1px solid var(--hp-border);
|
||
background: white;
|
||
border-radius: 6px;
|
||
padding: 4px 10px;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
}
|
||
.home-mock .offboard-strip button:hover { background: var(--hp-border-light); }
|
||
.home-mock .install-hero .eyebrow {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.8px;
|
||
opacity: 0.85;
|
||
margin-bottom: 10px;
|
||
}
|
||
.home-mock .install-hero h1 {
|
||
font-size: 30px;
|
||
font-weight: 600;
|
||
letter-spacing: -0.4px;
|
||
margin-bottom: 12px;
|
||
line-height: 1.2;
|
||
color: white;
|
||
}
|
||
.home-mock .install-hero .lead {
|
||
font-size: 15px;
|
||
opacity: 0.94;
|
||
line-height: 1.6;
|
||
margin-bottom: 22px;
|
||
}
|
||
.home-mock .install-hero .lead strong { font-weight: 600; }
|
||
|
||
/* ── Getting Started + Overview + Usage modes (added in PR #289) ───
|
||
Three new content cards rendered between the install-hero and the
|
||
connector tiles. Getting Started + Usage modes ship in OSS;
|
||
Overview body comes from instance.overview yaml (operator-owned),
|
||
hidden when unset. Generic dismiss pattern: any element with
|
||
`<button class="home-card-close" data-dismiss-key="...">` gets
|
||
per-device localStorage dismissibility for free. */
|
||
|
||
.home-mock .home-getting-started,
|
||
.home-mock .home-overview,
|
||
.home-mock .home-usage {
|
||
position: relative;
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 12px;
|
||
padding: 22px 24px;
|
||
margin-bottom: 18px;
|
||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
|
||
}
|
||
/* Getting Started uses <details> for native collapsed-by-default
|
||
behaviour. The summary owns the padding when collapsed; row layout
|
||
reveals on expand. Chevron rotates 90deg when open. */
|
||
.home-mock details.home-getting-started {
|
||
padding: 0;
|
||
}
|
||
.home-mock .home-gs-summary {
|
||
list-style: none;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 16px 24px;
|
||
user-select: none;
|
||
}
|
||
.home-mock .home-gs-summary::-webkit-details-marker { display: none; }
|
||
.home-mock .home-gs-summary-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--hp-text-primary);
|
||
}
|
||
.home-mock .home-gs-summary-hint {
|
||
flex: 1;
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
}
|
||
.home-mock .home-gs-summary-chev {
|
||
font-size: 18px;
|
||
color: var(--hp-text-muted);
|
||
transition: transform 0.15s;
|
||
}
|
||
.home-mock details.home-getting-started[open] .home-gs-summary-chev {
|
||
transform: rotate(90deg);
|
||
}
|
||
.home-mock details.home-getting-started[open] {
|
||
padding-bottom: 18px;
|
||
}
|
||
.home-mock details.home-getting-started[open] .home-gs-item {
|
||
margin-left: 24px;
|
||
margin-right: 24px;
|
||
}
|
||
/* Install-hero is the scroll target for Getting Started's first row.
|
||
Offset the anchor jump by the 72px sticky .app-header height + a
|
||
bit of breathing room so the hero's eyebrow lands cleanly under
|
||
the header bar. */
|
||
.home-mock .install-hero { scroll-margin-top: 88px; }
|
||
|
||
.home-mock .home-overview > h2,
|
||
.home-mock .home-usage > header h2 {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin: 0 0 6px;
|
||
color: var(--hp-text-primary);
|
||
}
|
||
.home-mock .home-usage > header p {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
margin: 0 0 14px;
|
||
}
|
||
|
||
.home-mock .home-card-close {
|
||
position: absolute;
|
||
top: 12px;
|
||
right: 12px;
|
||
width: 28px;
|
||
height: 28px;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: transparent;
|
||
color: var(--hp-text-muted);
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.home-mock .home-card-close:hover,
|
||
.home-mock .home-card-close:focus-visible {
|
||
background: var(--hp-border-light);
|
||
color: var(--hp-text-primary);
|
||
outline: none;
|
||
}
|
||
|
||
.home-mock .home-gs-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px 14px;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 10px;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
margin-top: 8px;
|
||
transition: border-color 0.15s, background 0.15s;
|
||
}
|
||
.home-mock .home-gs-item:hover {
|
||
border-color: var(--hp-primary);
|
||
background: var(--hp-primary-light);
|
||
}
|
||
.home-mock .home-gs-item .ico {
|
||
font-size: 20px;
|
||
flex: 0 0 24px;
|
||
text-align: center;
|
||
}
|
||
.home-mock .home-gs-item .text {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
.home-mock .home-gs-item .text strong {
|
||
font-weight: 600;
|
||
color: var(--hp-text-primary);
|
||
}
|
||
.home-mock .home-gs-item .text .desc {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
}
|
||
.home-mock .home-gs-item .arrow {
|
||
color: var(--hp-text-muted);
|
||
font-size: 16px;
|
||
}
|
||
|
||
.home-mock .home-overview-body {
|
||
font-size: 14px;
|
||
line-height: 1.6;
|
||
color: var(--hp-text-primary);
|
||
}
|
||
.home-mock .home-overview-body p { margin: 0 0 10px; }
|
||
.home-mock .home-overview-body p:last-child { margin-bottom: 0; }
|
||
.home-mock .home-overview-body a { color: var(--hp-primary); }
|
||
|
||
.home-mock .home-usage-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.home-mock .home-usage-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
padding: 14px;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 10px;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
transition: border-color 0.15s, background 0.15s;
|
||
}
|
||
/* Hover affordance only on the anchor variants (VS Code, Claude
|
||
Desktop). The Terminal tile renders as a plain <div> — the setup
|
||
hero above already covers the terminal install path, so a
|
||
click-through from here would round-trip to content the user just
|
||
scrolled past. */
|
||
.home-mock a.home-usage-item:hover {
|
||
border-color: var(--hp-primary);
|
||
background: var(--hp-primary-light);
|
||
}
|
||
.home-mock .home-usage-item .ico { font-size: 20px; }
|
||
.home-mock .home-usage-item strong { font-weight: 600; }
|
||
.home-mock .home-usage-item span {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.5;
|
||
}
|
||
.home-mock .home-usage-foot {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
margin: 0;
|
||
}
|
||
@media (max-width: 720px) {
|
||
.home-mock .home-usage-grid { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
.home-mock .what-is {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 14px;
|
||
margin-bottom: 18px;
|
||
}
|
||
.home-mock .what-is-item {
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 12px;
|
||
padding: 18px 22px;
|
||
color: inherit;
|
||
text-decoration: none;
|
||
display: block;
|
||
transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
|
||
}
|
||
.home-mock a.what-is-item:hover {
|
||
border-color: var(--hp-primary);
|
||
box-shadow: 0 4px 12px rgba(0, 115, 209, 0.10);
|
||
transform: translateY(-1px);
|
||
}
|
||
.home-mock .what-is-item .ico { font-size: 22px; margin-bottom: 8px; display: block; }
|
||
.home-mock .what-is-item .ttl {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--hp-text-primary);
|
||
margin-bottom: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
}
|
||
.home-mock .what-is-item .ttl .arrow {
|
||
color: var(--hp-primary);
|
||
font-weight: 600;
|
||
transition: transform 0.15s ease;
|
||
}
|
||
.home-mock a.what-is-item:hover .ttl .arrow {
|
||
transform: translateX(2px);
|
||
}
|
||
.home-mock .what-is-item .desc {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.55;
|
||
}
|
||
|
||
.home-mock .look-around-lead {
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.55;
|
||
margin: -4px 0 12px;
|
||
}
|
||
.home-mock .look-around-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||
margin-bottom: 22px;
|
||
}
|
||
|
||
.home-mock .install-block {
|
||
background: rgba(15, 23, 42, 0.55);
|
||
border: 1px solid rgba(255, 255, 255, 0.10);
|
||
border-radius: 12px;
|
||
padding: 18px 20px;
|
||
}
|
||
.home-mock .install-block + .install-block { margin-top: 14px; }
|
||
.home-mock .os-tabs {
|
||
display: flex;
|
||
gap: 4px;
|
||
margin-bottom: 8px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.10);
|
||
}
|
||
.home-mock .os-tab {
|
||
background: transparent;
|
||
border: none;
|
||
color: rgba(255, 255, 255, 0.60);
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
padding: 8px 14px;
|
||
cursor: pointer;
|
||
border-bottom: 2px solid transparent;
|
||
margin-bottom: -1px;
|
||
font-family: inherit;
|
||
}
|
||
.home-mock .os-tab:hover { color: rgba(255, 255, 255, 0.85); }
|
||
.home-mock .os-tab.is-active {
|
||
color: white;
|
||
border-bottom-color: #FBBF24;
|
||
}
|
||
/* `[hidden]` UA stylesheet has lower specificity than `.install-cmd { display: flex }`,
|
||
so we need an explicit override to actually hide the inactive tab panel. */
|
||
.home-mock .install-cmd[hidden] { display: none; }
|
||
.home-mock .install-block .label {
|
||
font-size: 11px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.6px;
|
||
opacity: 0.78;
|
||
margin-bottom: 10px;
|
||
font-weight: 600;
|
||
}
|
||
.home-mock .install-cmd {
|
||
background: #0F172A;
|
||
border: 1px solid rgba(255, 255, 255, 0.10);
|
||
border-radius: 8px;
|
||
padding: 14px 18px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 13.5px;
|
||
color: #FBBF24;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
}
|
||
.home-mock .install-cmd .multiline { display: block; line-height: 1.7; white-space: pre-wrap; flex: 1; }
|
||
.home-mock .install-cmd .copy-btn {
|
||
background: rgba(255, 255, 255, 0.10);
|
||
color: white;
|
||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||
padding: 7px 14px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
font-weight: 500;
|
||
flex-shrink: 0;
|
||
}
|
||
.home-mock .install-cmd .copy-btn:hover { background: rgba(255, 255, 255, 0.18); }
|
||
|
||
.home-mock .install-note {
|
||
font-size: 12.5px;
|
||
color: rgba(255, 255, 255, 0.85);
|
||
margin-top: 12px;
|
||
line-height: 1.55;
|
||
}
|
||
.home-mock .install-note code,
|
||
.home-mock .install-note > code {
|
||
background: rgba(255, 255, 255, 0.12);
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11.5px;
|
||
}
|
||
/* Links inside the blue install-hero need a non-blue color — the default
|
||
`<a>` style elsewhere on /home renders as `var(--hp-primary)` blue,
|
||
which is invisible against the hero's blue background. Use the same
|
||
high-contrast white-with-underline pattern the lead paragraph uses,
|
||
and bump hover to full white so the affordance stays obvious. */
|
||
.home-mock .install-hero a {
|
||
color: #ffffff;
|
||
text-decoration: underline;
|
||
text-decoration-color: rgba(255, 255, 255, 0.6);
|
||
text-underline-offset: 2px;
|
||
}
|
||
.home-mock .install-hero a:hover,
|
||
.home-mock .install-hero a:focus {
|
||
color: #ffffff;
|
||
text-decoration-color: #ffffff;
|
||
}
|
||
|
||
.home-mock .section-label {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.6px;
|
||
color: var(--hp-text-secondary);
|
||
margin: 30px 0 12px;
|
||
}
|
||
|
||
.home-mock .grid-2 {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 18px;
|
||
margin-bottom: 18px;
|
||
}
|
||
.home-mock .card {
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 12px;
|
||
padding: 22px;
|
||
}
|
||
.home-mock .card h3 { font-size: 14px; font-weight: 600; margin-bottom: 6px; }
|
||
.home-mock .card p { font-size: 13px; color: var(--hp-text-secondary); line-height: 1.55; margin-bottom: 12px; }
|
||
.home-mock .card-list { list-style: none; padding: 0; margin: 0; }
|
||
.home-mock .card-list li {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 10px;
|
||
padding: 8px 0;
|
||
font-size: 13px;
|
||
border-bottom: 1px solid var(--hp-border-light);
|
||
}
|
||
.home-mock .card-list li:last-child { border-bottom: none; }
|
||
.home-mock .card-list .num {
|
||
width: 22px; height: 22px;
|
||
border-radius: 50%;
|
||
background: var(--hp-primary-light);
|
||
color: var(--hp-primary);
|
||
flex-shrink: 0;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-weight: 700; font-size: 11px;
|
||
}
|
||
.home-mock .card-list .step-text { flex: 1; line-height: 1.55; }
|
||
.home-mock .card-list .step-text strong { display: block; margin-bottom: 2px; }
|
||
.home-mock .card-list .step-text code {
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11.5px;
|
||
background: var(--hp-border-light);
|
||
padding: 1px 5px;
|
||
border-radius: 3px;
|
||
color: var(--hp-text-primary);
|
||
}
|
||
|
||
.home-mock .explore-list { list-style: none; padding: 0; margin: 0; }
|
||
.home-mock .explore-list li + li { margin-top: 8px; }
|
||
.home-mock .explore-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px 14px;
|
||
background: var(--hp-border-light);
|
||
border: 1px solid transparent;
|
||
border-radius: 8px;
|
||
text-decoration: none;
|
||
color: var(--hp-text-primary);
|
||
transition: border-color 0.15s ease, background 0.15s ease;
|
||
}
|
||
.home-mock .explore-item:hover {
|
||
border-color: var(--hp-primary);
|
||
background: var(--hp-primary-light);
|
||
}
|
||
.home-mock .explore-item .ico {
|
||
font-size: 22px;
|
||
flex-shrink: 0;
|
||
width: 28px;
|
||
text-align: center;
|
||
}
|
||
.home-mock .explore-item .explore-text {
|
||
flex: 1;
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.45;
|
||
}
|
||
.home-mock .explore-item .explore-text strong {
|
||
display: block;
|
||
color: var(--hp-text-primary);
|
||
font-size: 13.5px;
|
||
font-weight: 600;
|
||
margin-bottom: 2px;
|
||
}
|
||
.home-mock .explore-item .arrow {
|
||
color: var(--hp-primary);
|
||
font-size: 16px;
|
||
flex-shrink: 0;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.home-mock .advanced-pointer {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
margin-top: 10px;
|
||
padding: 16px 22px;
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-left: 4px solid var(--hp-primary);
|
||
border-radius: 12px;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
transition: background 0.15s ease, border-color 0.15s ease;
|
||
}
|
||
.home-mock .advanced-pointer:hover {
|
||
background: var(--hp-primary-light);
|
||
border-color: var(--hp-primary);
|
||
}
|
||
.home-mock .advanced-pointer .ico {
|
||
font-size: 22px;
|
||
flex-shrink: 0;
|
||
line-height: 1;
|
||
}
|
||
.home-mock .advanced-pointer-text {
|
||
flex: 1;
|
||
font-size: 13px;
|
||
color: var(--hp-text-secondary);
|
||
line-height: 1.55;
|
||
}
|
||
.home-mock .advanced-pointer-text strong {
|
||
display: block;
|
||
color: var(--hp-text-primary);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
margin-bottom: 2px;
|
||
}
|
||
.home-mock .advanced-pointer .arrow {
|
||
color: var(--hp-primary);
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.home-mock .setup-cta-lead {
|
||
font-size: 13px;
|
||
color: rgba(255, 255, 255, 0.82);
|
||
margin-bottom: 14px;
|
||
line-height: 1.55;
|
||
}
|
||
.home-mock .setup-cta-lead code {
|
||
background: rgba(255, 255, 255, 0.12);
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11.5px;
|
||
}
|
||
.home-mock .setup-cta-row {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 14px;
|
||
}
|
||
.home-mock .btn-setup-primary {
|
||
font-family: inherit;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--hp-primary);
|
||
background: #FFFFFF;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 10px 22px;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
white-space: nowrap;
|
||
transition: background 0.15s ease;
|
||
}
|
||
.home-mock .btn-setup-primary:hover { background: #F0F7FF; }
|
||
.home-mock .btn-setup-primary.copied { background: #047857; color: #fff; }
|
||
.home-mock .btn-setup-primary[disabled] { opacity: 0.7; cursor: wait; }
|
||
.home-mock .setup-cta-meta {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.home-mock .setup-cta-hint {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.65);
|
||
}
|
||
.home-mock details.manual-fallback {
|
||
margin-top: 14px;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.10);
|
||
padding-top: 12px;
|
||
}
|
||
.home-mock details.manual-fallback > summary {
|
||
cursor: pointer;
|
||
list-style: none;
|
||
font-size: 12.5px;
|
||
color: rgba(255, 255, 255, 0.78);
|
||
padding: 4px 0;
|
||
user-select: none;
|
||
}
|
||
.home-mock details.manual-fallback > summary::-webkit-details-marker { display: none; }
|
||
.home-mock details.manual-fallback > summary::before {
|
||
content: "▸ ";
|
||
display: inline-block;
|
||
transition: transform 0.15s ease;
|
||
margin-right: 4px;
|
||
}
|
||
.home-mock details.manual-fallback[open] > summary::before { content: "▾ "; }
|
||
.home-mock .manual-preview-wrap { margin-top: 10px; }
|
||
.home-mock .manual-preview-wrap .setup-preview-pre {
|
||
background: #0F172A;
|
||
color: #E2E8F0;
|
||
border: 1px solid rgba(255, 255, 255, 0.10);
|
||
border-radius: 8px;
|
||
padding: 14px 16px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 12.5px;
|
||
line-height: 1.55;
|
||
overflow-x: auto;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
margin: 0;
|
||
}
|
||
/* Override the global `code { background: var(--bg); }` rule so the inner
|
||
<code> renders white-on-navy instead of dark-on-pale (unreadable). */
|
||
.home-mock .manual-preview-wrap .setup-preview-pre code,
|
||
.home-mock .manual-preview-wrap .setup-preview-code {
|
||
background: transparent;
|
||
color: #E2E8F0;
|
||
padding: 0;
|
||
border-radius: 0;
|
||
font-family: inherit;
|
||
font-size: inherit;
|
||
}
|
||
.home-mock .manual-preview-wrap .placeholder-token {
|
||
background: rgba(251, 191, 36, 0.20);
|
||
color: #FBBF24;
|
||
padding: 0 4px;
|
||
border-radius: 3px;
|
||
font-style: italic;
|
||
}
|
||
.home-mock .manual-preview-wrap .token-revealed {
|
||
background: rgba(16, 185, 129, 0.18);
|
||
color: #6EE7B7;
|
||
padding: 0 4px;
|
||
border-radius: 3px;
|
||
font-style: normal;
|
||
word-break: break-all;
|
||
}
|
||
|
||
/* In-hero (dark) variant: shown for not-onboarded users underneath the
|
||
inline install steps inside the blue hero. */
|
||
.home-mock .self-mark {
|
||
margin-top: 16px;
|
||
font-size: 13px;
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
.home-mock .self-mark button {
|
||
background: rgba(255, 255, 255, 0.15);
|
||
color: white;
|
||
border: 1px solid rgba(255, 255, 255, 0.30);
|
||
padding: 6px 12px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
margin-left: 6px;
|
||
}
|
||
.home-mock .self-mark button:hover { background: rgba(255, 255, 255, 0.25); }
|
||
.home-mock .self-mark button:disabled { opacity: 0.5; cursor: default; }
|
||
.home-mock .self-mark .status { margin-left: 10px; font-style: italic; }
|
||
|
||
/* Onboarded "steps complete" badge — shown inside the blue hero in
|
||
place of the hidden Step 1 + Step 2 install-blocks. */
|
||
.home-mock .install-done {
|
||
margin-top: 4px;
|
||
margin-bottom: 4px;
|
||
padding: 12px 16px;
|
||
background: rgba(255, 255, 255, 0.10);
|
||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||
border-radius: 10px;
|
||
color: rgba(255, 255, 255, 0.92);
|
||
font-size: 13px;
|
||
line-height: 1.55;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 10px;
|
||
}
|
||
.home-mock .install-done .check {
|
||
font-size: 16px;
|
||
line-height: 1.3;
|
||
flex-shrink: 0;
|
||
}
|
||
.home-mock .install-done strong { font-weight: 600; color: white; }
|
||
|
||
/* News perex (admin-edited) — bottom of /home.
|
||
Keeps the styling within the admin-defined class vocabulary
|
||
(callout, video-embed) so authors get a predictable surface.
|
||
Full-width grid classes are intentionally NOT included here —
|
||
they live on /news where the layout has more room. */
|
||
.home-mock .home-news {
|
||
margin-top: 28px;
|
||
background: white;
|
||
border: 1px solid var(--hp-border);
|
||
border-radius: 12px;
|
||
overflow: hidden; /* keep the green strip's corners rounded */
|
||
}
|
||
.home-mock .home-news-head {
|
||
display: flex;
|
||
align-items: baseline;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
padding: 12px 22px;
|
||
background: #D1FAE5; /* same green as .callout-success */
|
||
border-bottom: 1px solid #A7F3D0;
|
||
}
|
||
.home-mock .home-news-head h2 {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #047857; /* darker green for contrast on the band */
|
||
margin: 0;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.6px;
|
||
}
|
||
.home-mock .home-news-more {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: #047857;
|
||
text-decoration: none;
|
||
}
|
||
.home-mock .home-news-more:hover { text-decoration: underline; }
|
||
.home-mock .home-news-body {
|
||
font-size: 14px;
|
||
line-height: 1.55;
|
||
color: var(--hp-text-primary);
|
||
padding: 18px 22px;
|
||
}
|
||
.home-mock .home-news-body p { margin: 0 0 8px; }
|
||
.home-mock .home-news-body p:last-child { margin-bottom: 0; }
|
||
|
||
/* Callout, video-embed, news-hero, news-grid-*, news-cta — shared news
|
||
content vocabulary now lives in app/web/static/style-custom.css under
|
||
"News content vocabulary (shared)". Editing those rules in one place
|
||
updates /home perex, /news, and /admin/news preview together. */
|
||
|
||
.home-mock .install-done code {
|
||
background: rgba(15, 23, 42, 0.55);
|
||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 12px;
|
||
color: #FBBF24;
|
||
}
|
||
|
||
/* ─────────────────────────────────────────────────────────────────
|
||
Onboarding polish — friction fixes from internal usability testing.
|
||
Each block is tagged with its priority (P0/P1/P2) — P0 resolves the
|
||
highest-frequency confusion, P2 is nice-to-have polish.
|
||
───────────────────────────────────────────────────────────────── */
|
||
|
||
/* P0-1 — "Don't have a terminal open?" disclosure inside Step 1's
|
||
blue install-block. Yellow accent matches Claude footer mode dots
|
||
so it reads as a hint, not an error. */
|
||
.home-mock details.terminal-howto {
|
||
margin-top: 10px;
|
||
padding: 8px 12px;
|
||
background: rgba(251, 191, 36, 0.10);
|
||
border: 1px solid rgba(251, 191, 36, 0.30);
|
||
border-radius: 8px;
|
||
}
|
||
.home-mock details.terminal-howto > summary {
|
||
cursor: pointer;
|
||
list-style: none;
|
||
font-size: 12.5px;
|
||
font-weight: 600;
|
||
color: #FBBF24;
|
||
user-select: none;
|
||
}
|
||
.home-mock details.terminal-howto > summary::-webkit-details-marker { display: none; }
|
||
.home-mock details.terminal-howto > summary::before { content: "▸ "; margin-right: 4px; }
|
||
.home-mock details.terminal-howto[open] > summary::before { content: "▾ "; }
|
||
.home-mock .terminal-howto-body {
|
||
margin-top: 10px;
|
||
font-size: 12.5px;
|
||
color: rgba(255, 255, 255, 0.92);
|
||
line-height: 1.6;
|
||
}
|
||
.home-mock .terminal-howto-body p { margin: 0 0 6px; }
|
||
.home-mock .terminal-howto-body kbd {
|
||
background: rgba(255, 255, 255, 0.15);
|
||
border: 1px solid rgba(255, 255, 255, 0.30);
|
||
border-bottom-width: 2px;
|
||
border-radius: 4px;
|
||
padding: 1px 6px;
|
||
font-size: 11px;
|
||
font-family: var(--hp-font-mono);
|
||
color: white;
|
||
}
|
||
|
||
/* P1-6 — Auto-detect badge replaces the "Mark me as onboarded"
|
||
button as the primary affordance. Pulse dot signals it's actively
|
||
listening; the manual self-mark button stays as a fallback. */
|
||
.home-mock .auto-detect-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-top: 16px;
|
||
background: rgba(255, 255, 255, 0.10);
|
||
border: 1px solid rgba(255, 255, 255, 0.20);
|
||
color: rgba(255, 255, 255, 0.92);
|
||
padding: 8px 14px;
|
||
border-radius: 8px;
|
||
font-size: 12.5px;
|
||
}
|
||
.home-mock .auto-detect-badge .pulse {
|
||
width: 8px; height: 8px;
|
||
border-radius: 50%;
|
||
background: #FBBF24;
|
||
box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.7);
|
||
animation: agnes-pulse 1.6s infinite;
|
||
}
|
||
.home-mock .auto-detect-badge code {
|
||
background: rgba(0, 0, 0, 0.18);
|
||
padding: 1px 5px;
|
||
border-radius: 3px;
|
||
font-family: var(--hp-font-mono);
|
||
font-size: 11.5px;
|
||
}
|
||
@keyframes agnes-pulse {
|
||
0% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.65); }
|
||
70% { box-shadow: 0 0 0 8px rgba(251, 191, 36, 0); }
|
||
100% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0); }
|
||
}
|
||
|
||
/* P0-2 — Post-CTA modal with "where to paste" 3-step guide. Modal
|
||
markup lives at body level; this styles the backdrop + card. The
|
||
.home-mock prefix is intentionally NOT used so the modal is
|
||
render-isolated from the home page's CSS variables. */
|
||
.cta-modal-backdrop {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(15, 23, 42, 0.55);
|
||
-webkit-backdrop-filter: blur(2px);
|
||
backdrop-filter: blur(2px);
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
padding: 20px;
|
||
}
|
||
.cta-modal-backdrop.is-open { display: flex; }
|
||
.cta-modal {
|
||
background: white;
|
||
border-radius: 14px;
|
||
padding: 28px 32px;
|
||
max-width: 520px;
|
||
width: 100%;
|
||
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
|
||
color: #111827;
|
||
font-family: inherit;
|
||
}
|
||
.cta-modal h2 {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin: 0 0 6px;
|
||
color: #047857;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.cta-modal .lead {
|
||
font-size: 13px;
|
||
color: #6B7280;
|
||
margin: 0 0 18px;
|
||
line-height: 1.55;
|
||
}
|
||
.cta-modal ol {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0 0 18px;
|
||
counter-reset: step;
|
||
}
|
||
.cta-modal ol li {
|
||
counter-increment: step;
|
||
display: flex;
|
||
gap: 14px;
|
||
align-items: flex-start;
|
||
padding: 12px;
|
||
background: #F9FAFB;
|
||
border-radius: 8px;
|
||
margin-bottom: 10px;
|
||
font-size: 13.5px;
|
||
line-height: 1.5;
|
||
}
|
||
.cta-modal ol li::before {
|
||
content: counter(step);
|
||
width: 26px; height: 26px;
|
||
border-radius: 50%;
|
||
background: #0073D1;
|
||
color: white;
|
||
font-weight: 700;
|
||
font-size: 12px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
.cta-modal ol li code,
|
||
.cta-modal ol li kbd {
|
||
background: #fff;
|
||
border: 1px solid #E5E7EB;
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
font-family: var(--hp-font-mono, ui-monospace, monospace);
|
||
font-size: 12px;
|
||
}
|
||
.cta-modal ol li kbd { border-bottom-width: 2px; }
|
||
.cta-modal ol li strong { display: block; margin-bottom: 4px; color: #111827; }
|
||
.cta-modal-foot {
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
gap: 12px;
|
||
padding-top: 12px;
|
||
border-top: 1px solid #F3F4F6;
|
||
}
|
||
.cta-modal-foot .meta { font-size: 12px; color: #6B7280; }
|
||
.cta-modal-foot button {
|
||
background: #0073D1;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 8px 18px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
}
|
||
.cta-modal-foot button:hover { background: #0056A3; }
|
||
</style>
|
||
|
||
<div class="home-mock">
|
||
|
||
{# Homepage status frame — five counters with 24h/7d toggle.
|
||
Two gates: (a) operator flag instance.home.show_status_frame /
|
||
AGNES_HOME_SHOW_STATUS_FRAME (default on, evaluated in router and
|
||
passed as `status_frame_enabled`); (b) the user being onboarded —
|
||
fresh users see a clean install-hero before zero-value stats.
|
||
Router skips `compute_home_stats` (saves the DB hit) when either
|
||
gate is closed, so `home_stats` is None in that branch. #}
|
||
{% if status_frame_enabled and onboarded and home_stats %}
|
||
{% include "_home_stats.html" %}
|
||
{% endif %}
|
||
|
||
{% set display_name = (user.name or (user.email or "").split("@")[0] or "there") %}
|
||
|
||
{# Install-hero renders only for not-onboarded users. Once `agnes init`
|
||
POSTs /api/me/onboarded (or the user clicks the in-hero X) the hero
|
||
disappears entirely — the rest of /home (connector tiles, news,
|
||
etc.) stays. Offboarding escape hatch moved to a discrete strip
|
||
below; see `.offboard-strip`. #}
|
||
{% if not onboarded %}
|
||
{# Getting Started renders FIRST in the not-onboarded flow as a
|
||
collapsed-by-default <details>. Click the summary to expand the
|
||
two-row map. First row anchors back to the install hero just
|
||
below (#install-hero); second row leaves the page for
|
||
/setup-advanced. Per-device dismiss X (data-dismiss-key) survives
|
||
on the generic .home-card-close handler — selector widened in JS
|
||
to accept <details> containers too. Disappears post-onboarding
|
||
alongside the hero so the in-page anchor never dangles. #}
|
||
<details class="home-getting-started" id="homeGettingStarted">
|
||
<summary class="home-gs-summary">
|
||
<span class="home-gs-summary-title">Getting Started</span>
|
||
<span class="home-gs-summary-hint">Two quick next steps — click to expand</span>
|
||
<span class="home-gs-summary-chev" aria-hidden="true">›</span>
|
||
</summary>
|
||
<button type="button" class="home-card-close"
|
||
data-dismiss-key="agnes_home_gs_dismissed"
|
||
aria-label="Dismiss Getting Started">×</button>
|
||
<a class="home-gs-item" href="#install-hero">
|
||
<span class="ico" aria-hidden="true">🚀</span>
|
||
<div class="text">
|
||
<strong>Setup {{ instance_brand }} in your Claude Code</strong>
|
||
<span class="desc">One-time install — walkthrough in the section below.</span>
|
||
</div>
|
||
<span class="arrow" aria-hidden="true">↓</span>
|
||
</a>
|
||
<a class="home-gs-item" href="/setup-advanced">
|
||
<span class="ico" aria-hidden="true">🛠️</span>
|
||
<div class="text">
|
||
<strong>Go deeper into your AI workspace</strong>
|
||
<span class="desc">VS Code layout, recommended plugins, multi-model second opinions, custom skills + rules + hooks, project workflows.</span>
|
||
</div>
|
||
<span class="arrow" aria-hidden="true">→</span>
|
||
</a>
|
||
</details>
|
||
|
||
<div class="install-hero" id="install-hero">
|
||
<button type="button" class="install-hero-close" id="installHeroClose"
|
||
data-target-source="self_acknowledged"
|
||
aria-label="I'm already set up — close this setup hero">×</button>
|
||
<div class="eyebrow">Welcome, {{ display_name }} — let's get you set up</div>
|
||
<h1>Connect Claude Code on your machine to your team's data</h1>
|
||
<p class="lead">
|
||
{{ instance_brand }} gives <strong>Claude Code</strong> on your computer access to your team's <strong>curated data, plugins, third-party tools (Asana, Google Workspace, Atlassian), and shared knowledge</strong> — so you can ask questions and get answers in plain language, right from your terminal. This page walks you through the <strong>one-time setup (~10 minutes)</strong>; the install script also connects your tools for you, so there's no extra page to visit. Everything it installs lives in your home folder (<code style="background: rgba(255,255,255,0.12); padding: 1px 6px; border-radius: 4px; font-family: var(--hp-font-mono); font-size: 12.5px;">~/{{ workspace_dir }}</code>) and can be removed in one command.
|
||
</p>
|
||
|
||
<div class="install-block">
|
||
<div class="label">Step 1 — install Claude Code</div>
|
||
<div class="os-tabs" role="tablist" aria-label="Operating system">
|
||
<button type="button" role="tab" class="os-tab is-active"
|
||
data-os-tab="unix" aria-selected="true">macOS / Linux / WSL</button>
|
||
<button type="button" role="tab" class="os-tab"
|
||
data-os-tab="windows" aria-selected="false">Windows (PowerShell)</button>
|
||
</div>
|
||
<div class="install-cmd" role="tabpanel" data-os-panel="unix">
|
||
<span class="multiline" id="install-cmd-claude-unix">curl -fsSL https://claude.ai/install.sh | bash</span>
|
||
<button class="copy-btn" data-copy-target="install-cmd-claude-unix">Copy</button>
|
||
</div>
|
||
<div class="install-cmd" role="tabpanel" data-os-panel="windows" hidden>
|
||
<span class="multiline" id="install-cmd-claude-windows">irm https://claude.ai/install.ps1 | iex</span>
|
||
<button class="copy-btn" data-copy-target="install-cmd-claude-windows">Copy</button>
|
||
</div>
|
||
|
||
{# P0-1 — terminal-howto disclosure. Two panels (unix / windows)
|
||
that flip in lockstep with the install-cmd OS tabs above. #}
|
||
<details class="terminal-howto">
|
||
<summary>Don't have a terminal open? — show how</summary>
|
||
<div class="terminal-howto-body" data-howto-panel="unix">
|
||
<p><strong>macOS:</strong> press <kbd>⌘</kbd> + <kbd>Space</kbd>, type <kbd>Terminal</kbd>, press <kbd>Enter</kbd>.</p>
|
||
<p><strong>Linux:</strong> press <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>T</kbd> (most distros), or open the apps menu and search "Terminal".</p>
|
||
<p><strong>WSL:</strong> open the Windows Start menu, search "Ubuntu" (or your installed distro), press <kbd>Enter</kbd>.</p>
|
||
<p>Paste the command above with <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd> (Linux/WSL) or <kbd>⌘</kbd> + <kbd>V</kbd> (macOS), then press <kbd>Enter</kbd>.</p>
|
||
</div>
|
||
<div class="terminal-howto-body" data-howto-panel="windows" hidden>
|
||
<p><strong>Windows:</strong> press <kbd>Win</kbd> + <kbd>R</kbd>, type <kbd>powershell</kbd>, press <kbd>Enter</kbd>. (Or: open Start menu, type "PowerShell".)</p>
|
||
<p>Paste the command above with <kbd>Ctrl</kbd> + <kbd>V</kbd> and press <kbd>Enter</kbd>.</p>
|
||
</div>
|
||
</details>
|
||
|
||
<div class="install-note">
|
||
Verify with <code>claude --version</code>. Sign in once with <code>claude</code> and complete the OAuth flow.
|
||
Don't have a Claude license yet? See <a href="/setup-advanced#claude-plan">plan options on /setup-advanced</a> (Pro / Max 5× / Max 20×).
|
||
</div>
|
||
</div>
|
||
|
||
{% if home_automode.show %}
|
||
<div class="install-block">
|
||
<div class="label">Step 2 — turn on auto-mode (recommended before Step 4)</div>
|
||
<div class="install-note">
|
||
In the Claude Code session you just signed into, press <strong>Shift + Tab</strong>. Claude cycles modes: default → <strong>auto-accept edits</strong> → plan mode → default; the footer shows <code>⏵⏵</code> when auto-accept is on. On the first cycle to auto-accept, Claude asks whether to make it the default — say <strong>yes</strong>. Closed the session already? Run <code>claude</code> again, then press <strong>Shift + Tab</strong>.
|
||
<br><br>
|
||
Want full auto-approve including Bash? See <a href="/setup-advanced#yolo">YOLO mode</a> on /setup-advanced — pairs <code>--dangerously-skip-permissions</code> with a reviewed <code>~/.claude/settings.local.json</code> allowlist. Skip if you're not sure.
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="install-block">
|
||
<div class="label">Step 3 — create your workspace folder</div>
|
||
<div class="os-tabs" role="tablist" aria-label="Operating system">
|
||
<button type="button" role="tab" class="os-tab is-active"
|
||
data-os-tab="unix" aria-selected="true">macOS / Linux / WSL</button>
|
||
<button type="button" role="tab" class="os-tab"
|
||
data-os-tab="windows" aria-selected="false">Windows (PowerShell)</button>
|
||
</div>
|
||
<div class="install-cmd" role="tabpanel" data-os-panel="unix">
|
||
<span class="multiline" id="install-cmd-mkdir-unix">mkdir -p ~/{{ workspace_dir }} && cd ~/{{ workspace_dir }}</span>
|
||
<button class="copy-btn" data-copy-target="install-cmd-mkdir-unix">Copy</button>
|
||
</div>
|
||
<div class="install-cmd" role="tabpanel" data-os-panel="windows" hidden>
|
||
<span class="multiline" id="install-cmd-mkdir-windows">New-Item -ItemType Directory -Force -Path "$HOME\{{ workspace_dir }}" | Out-Null
|
||
Set-Location "$HOME\{{ workspace_dir }}"</span>
|
||
<button class="copy-btn" data-copy-target="install-cmd-mkdir-windows">Copy</button>
|
||
</div>
|
||
<div class="install-note">
|
||
This is where {{ instance_brand }} will live. Run the command in the terminal you opened for Step 1, then keep that terminal handy — the next step pastes the setup script into Claude Code from this same directory.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="install-block">
|
||
<div class="label">Step 4 — install {{ instance_brand }} from inside Claude Code</div>
|
||
<p class="setup-cta-lead">
|
||
Click the button — {{ instance_brand }} <strong>creates a 90-day login token</strong>, copies a ready-to-paste setup script to your clipboard, and a follow-up popup tells you exactly where to paste it. The script bootstraps everything in <code>~/{{ workspace_dir }}</code> once Claude Code receives it.
|
||
</p>
|
||
<div class="setup-cta-row">
|
||
<button type="button" id="setupClaudeBtn" class="btn-setup-primary" onclick="setupNewClaude(this)">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||
</svg>
|
||
Setup a new Claude Code
|
||
</button>
|
||
<span class="setup-cta-meta">
|
||
<span class="setup-cta-hint">Valid 90 days · token stays in clipboard only</span>
|
||
</span>
|
||
</div>
|
||
<div id="setupClaudeError" class="setup-error" role="alert" style="display:none;"></div>
|
||
|
||
<details class="manual-fallback">
|
||
<summary>Or paste manually (preview the script)</summary>
|
||
<div class="manual-preview-wrap">
|
||
{% with preview_mode=True %}
|
||
{% include "_claude_setup_instructions.jinja" %}
|
||
{% endwith %}
|
||
</div>
|
||
<div class="install-note">
|
||
The preview above is the exact text the button copies; the placeholder is replaced with a real token at click time. Don't create <code>~/{{ workspace_dir }}/Projects/</code> manually — the bundled plugin offers to set it up after install.
|
||
</div>
|
||
</details>
|
||
|
||
</div>
|
||
|
||
{# P1-6 — auto-detect badge is the PRIMARY affordance after the
|
||
install-script copy: agnes-init's first POST to
|
||
/api/me/onboarded flips state automatically and the page
|
||
reloads. The manual "Mark me as onboarded" button below it
|
||
stays as a fallback when auto-flip never lands. #}
|
||
<div class="auto-detect-badge" role="status" aria-live="polite">
|
||
<span class="pulse" aria-hidden="true"></span>
|
||
<span>Waiting for your first <code>agnes pull</code> — auto-detects within ~30 s of the setup script finishing.</span>
|
||
</div>
|
||
|
||
{# Self-mark fallback for the auto-flip. The hero's X close button
|
||
does the same thing more visibly; both target the not-onboarded
|
||
→ onboarded direction. The onboarded → offboarded variant lives
|
||
below the hero (.offboard-strip) so it stays reachable once the
|
||
hero is gone. #}
|
||
<div class="self-mark">
|
||
Already set this up?
|
||
<button id="self-mark-btn" type="button"
|
||
data-target-onboarded="true"
|
||
data-target-source="self_acknowledged">Mark me as onboarded</button>
|
||
<span id="self-mark-status" class="status" role="status" aria-live="polite"></span>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if onboarded %}
|
||
{# Offboarding escape hatch shown only after the hero has disappeared.
|
||
Lets the analyst (e.g. after wiping ~/FoundryAI) flip the
|
||
users.onboarded boolean back to false so the full install hero
|
||
renders again on next reload. Discrete by design — onboarded
|
||
users land on /home expecting the nav hub, not a setup screen. #}
|
||
<div class="offboard-strip">
|
||
<span>Workspace ready — wiped it and need the full setup view back?</span>
|
||
<button id="offboard-btn" type="button"
|
||
data-target-source="self_unmark">Mark me as offboarded</button>
|
||
<span id="offboard-status" class="status" role="status" aria-live="polite"></span>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# Auto-mode card used to live here as a `<details>` reference block;
|
||
moved into the install-hero as the new Step 2 so users enable it
|
||
BEFORE Step 3's install runs ~20 commands. Gated by the same
|
||
`home_automode.show` flag at the call site. #}
|
||
|
||
{# Getting Started was previously rendered HERE (between the offboard
|
||
strip and Overview) as a full-card <section>. Moved up to render
|
||
BEFORE the install-hero as a collapsed-by-default <details> — see
|
||
the block right after `{% if not onboarded %}` near line ~1357. #}
|
||
|
||
{# Overview section — operator-owned, opt-in. Body comes from the
|
||
`instance.overview` yaml field via get_instance_overview()
|
||
(`AGNES_INSTANCE_OVERVIEW` env override). Empty value hides the
|
||
whole section, keeping the OSS vendor-neutral. #}
|
||
{# Overview is operator-owned reference content, not per-user
|
||
chrome — no dismiss button on purpose. A one-time per-device
|
||
hide would mean returning users who wanted to re-read the
|
||
privacy posture / telemetry policy can't get back to it
|
||
without clearing localStorage. The whole section is opt-in at
|
||
the operator level (empty yaml → section absent), which is
|
||
the right axis of control. #}
|
||
{% if config.INSTANCE_OVERVIEW %}
|
||
<section class="home-overview">
|
||
<h2>Overview</h2>
|
||
<div class="home-overview-body">{{ config.INSTANCE_OVERVIEW | safe }}</div>
|
||
</section>
|
||
{% endif %}
|
||
|
||
{# Usage modes — Terminal / VS Code / Claude Desktop · claude.ai.
|
||
Generic OSS-shipped copy linking to /setup and to /setup-advanced
|
||
anchors. Helps onboarded users find the right surface once they
|
||
know which workflow fits them. #}
|
||
<section class="home-usage">
|
||
<header>
|
||
<h2>Where you can use {{ instance_brand }}</h2>
|
||
<p>Same workspace, three surfaces. Pick whichever fits your flow — all three share the same plugins, data access, and credentials.</p>
|
||
</header>
|
||
<div class="home-usage-grid">
|
||
{# Terminal tile is informational — the setup hero above already
|
||
walks through the Claude Code CLI install, so a click-through
|
||
here would round-trip back to content the user just scrolled
|
||
past. Rendered as a non-anchor div; hover/cursor styles only
|
||
apply to the anchor variants below. #}
|
||
<div class="home-usage-item">
|
||
<span class="ico" aria-hidden="true">⌨️</span>
|
||
<strong>Terminal</strong>
|
||
<span>Default. Run <code>claude</code> from any project under <code>~/{{ workspace_dir }}</code>. Lowest overhead, fastest feedback loop.</span>
|
||
</div>
|
||
<a class="home-usage-item" href="/setup-advanced#vscode">
|
||
<span class="ico" aria-hidden="true">📑</span>
|
||
<strong>VS Code</strong>
|
||
<span>Split-terminal layout — conversation on one side, diffs and tool output on the other. See <em>Go deeper</em> for the recommended layout.</span>
|
||
</a>
|
||
<a class="home-usage-item" href="/setup-advanced#claude-app">
|
||
<span class="ico" aria-hidden="true">💻</span>
|
||
<strong>Claude Desktop / claude.ai</strong>
|
||
<span>Connect this {{ instance_brand }} instance's plugin marketplace to your Claude Desktop or claude.ai account — same plugins, no terminal required.</span>
|
||
</a>
|
||
</div>
|
||
<p class="home-usage-foot">For the deepest integration, create every project under <code>~/{{ workspace_dir }}/Projects/</code> — existing or new. The bundled plugin keeps each project in sync with the central catalog automatically, and the session-analysis loop is scoped to that root.</p>
|
||
</section>
|
||
|
||
{# Connectors `<details data-section="connectors">` block removed —
|
||
the install-hero's Step 4 clipboard payload (rendered via
|
||
`_claude_setup_instructions.jinja` inside the "Or paste manually"
|
||
fallback) already inlines the same Asana / GWS / Atlassian
|
||
prompts from app/web/connector_prompts.py via
|
||
app/web/setup_instructions.py::_connectors_block. Showing them
|
||
a second time as standalone cards duplicated UX without adding
|
||
reach — the install script visits them all in sequence. Brief
|
||
mention in the install-hero lead paragraph above covers the
|
||
benefits ("third-party tools (Asana, Google Workspace,
|
||
Atlassian)"); deep ops live in /setup-advanced. #}
|
||
|
||
<div class="section-label">Want to look around first?</div>
|
||
<p class="look-around-lead">You don't need {{ instance_brand }} installed locally to browse what's available. Anything you bookmark or subscribe to will be there waiting after you set {{ instance_brand }} up.</p>
|
||
|
||
<div class="what-is look-around-grid">
|
||
<a class="what-is-item" href="/marketplace">
|
||
<span class="ico">🧩</span>
|
||
<div class="ttl">Plugin marketplace <span class="arrow">→</span></div>
|
||
<div class="desc">Curated and community-built plugins for Claude Code — Asana, Jira, Google Workspace, and more. Browse, install, sync.</div>
|
||
</a>
|
||
<a class="what-is-item" href="/catalog">
|
||
<span class="ico">📦</span>
|
||
<div class="ttl">Curated data packages <span class="arrow">→</span></div>
|
||
<div class="desc">Tables, schema, and metric definitions your team has registered. Subscribe and Claude can query them with documented business rules.</div>
|
||
</a>
|
||
{# Corporate memory + Activity center are both admin-only — matches
|
||
the top-nav gating logic (the "Memory" link is hidden for
|
||
non-admin in _app_header.html, and the /corporate-memory route
|
||
is `require_admin`). Without this gate non-admin users would
|
||
see a Corporate memory tile that 403s when clicked. #}
|
||
{% if is_admin %}
|
||
<a class="what-is-item" href="/corporate-memory">
|
||
<span class="ico">🧠</span>
|
||
<div class="ttl">Corporate memory <span class="arrow">→</span></div>
|
||
<div class="desc">Shared analyst knowledge and prior solutions to draw from. Searchable, versioned, fed back into Claude's context on demand.</div>
|
||
</a>
|
||
<a class="what-is-item" href="/activity-center">
|
||
<span class="ico">📈</span>
|
||
<div class="ttl">Activity center <span class="arrow">→</span></div>
|
||
<div class="desc">Per-user analytics on {{ instance_brand }} adoption across your team. Sessions, plugin installs, prompt patterns.</div>
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{# Legacy `.advanced-pointer` row removed — same link now lives in
|
||
the Getting Started card at the top of /home. `.advanced-pointer`
|
||
CSS (~line 721) is harmless dead style; left in place to keep
|
||
this diff focused. #}
|
||
|
||
{% if news_intro %}
|
||
<section class="home-news">
|
||
<header class="home-news-head">
|
||
<h2>What's new</h2>
|
||
<a class="home-news-more" href="/news">Read more →</a>
|
||
</header>
|
||
<div class="home-news-body">{{ news_intro | safe }}</div>
|
||
</section>
|
||
{% endif %}
|
||
|
||
</div>
|
||
|
||
{# P0-2 — Post-CTA modal. Opens after the shared CTA include below has
|
||
created the token + copied the script. _claude_setup_cta.jinja still
|
||
owns the token request + clipboard write; this modal layers on top
|
||
with a 3-step "where to paste" guide. Lives at body level so the
|
||
home-mock styles don't bleed into it. #}
|
||
{% if not onboarded %}
|
||
<div class="cta-modal-backdrop" id="cta-modal-backdrop" role="dialog" aria-modal="true" aria-labelledby="cta-modal-title" hidden>
|
||
<div class="cta-modal">
|
||
<h2 id="cta-modal-title"><span aria-hidden="true">✅</span> Setup script copied to clipboard</h2>
|
||
<p class="lead">Now paste it into Claude Code on your machine — three steps:</p>
|
||
<ol>
|
||
<li>
|
||
<div>
|
||
<strong>Open a terminal</strong>
|
||
macOS: <kbd>⌘</kbd>+<kbd>Space</kbd>, type "Terminal". Windows: <kbd>Win</kbd>+<kbd>R</kbd>, type <code>powershell</code>. Linux: <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>T</kbd>.
|
||
</div>
|
||
</li>
|
||
<li>
|
||
<div>
|
||
<strong>Start Claude Code</strong>
|
||
Type <code>claude</code> and press <kbd>Enter</kbd>. You'll see a prompt waiting for input.
|
||
</div>
|
||
</li>
|
||
<li>
|
||
<div>
|
||
<strong>Paste & press Enter</strong>
|
||
macOS: <kbd>⌘</kbd>+<kbd>V</kbd>. Windows/Linux: <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>V</kbd>. Claude runs the setup and tells you when it's done.
|
||
</div>
|
||
</li>
|
||
</ol>
|
||
<div class="cta-modal-foot">
|
||
<span class="meta">Token is in clipboard only — never displayed here.</span>
|
||
<button type="button" id="cta-modal-close">Got it</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# Shared "Setup a new Claude Code" CTA behaviour — provides the JS that
|
||
POSTs /auth/tokens, copies the rendered instructions to the clipboard,
|
||
and falls back to a modal when the clipboard API is blocked. The button
|
||
above (id="setupClaudeBtn") is the primary trigger. #}
|
||
{% include "_claude_setup_cta.jinja" %}
|
||
|
||
<script>
|
||
(function () {
|
||
function wireCopy(btn) {
|
||
btn.addEventListener('click', function () {
|
||
var src = document.getElementById(btn.getAttribute('data-copy-target'));
|
||
if (!src) return;
|
||
// textContent (not innerText) so collapsed <details> content still
|
||
// copies — innerText returns "" for nodes whose ancestor has
|
||
// display:none (which is what closed <details> applies).
|
||
var raw = src.textContent || '';
|
||
var text = raw.replace(/ /g, ' ');
|
||
navigator.clipboard.writeText(text).then(function () {
|
||
var orig = btn.textContent;
|
||
btn.textContent = 'Copied';
|
||
setTimeout(function () { btn.textContent = orig; }, 1500);
|
||
}).catch(function () { btn.textContent = 'Copy failed'; });
|
||
});
|
||
}
|
||
document.querySelectorAll('.copy-btn[data-copy-target]').forEach(wireCopy);
|
||
|
||
// OS tab switching for Step 1. Flips both the command panel AND the
|
||
// P0-1 terminal-howto body so the howto matches the active OS.
|
||
document.querySelectorAll('.os-tab[data-os-tab]').forEach(function (tab) {
|
||
tab.addEventListener('click', function () {
|
||
var target = tab.getAttribute('data-os-tab');
|
||
var scope = tab.closest('.install-block') || document;
|
||
scope.querySelectorAll('.os-tab[data-os-tab]').forEach(function (t) {
|
||
var on = t.getAttribute('data-os-tab') === target;
|
||
t.classList.toggle('is-active', on);
|
||
t.setAttribute('aria-selected', on ? 'true' : 'false');
|
||
});
|
||
scope.querySelectorAll('[data-os-panel]').forEach(function (p) {
|
||
if (p.getAttribute('data-os-panel') === target) {
|
||
p.removeAttribute('hidden');
|
||
} else {
|
||
p.setAttribute('hidden', '');
|
||
}
|
||
});
|
||
// P0-1 — flip the howto bodies in lockstep.
|
||
scope.querySelectorAll('[data-howto-panel]').forEach(function (p) {
|
||
if (p.getAttribute('data-howto-panel') === target) {
|
||
p.removeAttribute('hidden');
|
||
} else {
|
||
p.setAttribute('hidden', '');
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
// P0-2 — Post-CTA modal. _claude_setup_cta.jinja owns the click
|
||
// handler that POSTs /auth/tokens + copies the script; we wait for
|
||
// its success signal (agnes:setup-script-copied custom event) and
|
||
// then open the "where to paste" guide. Without the event the modal
|
||
// simply never opens — the include's own fallback paths handle
|
||
// older browsers / blocked clipboard.
|
||
var ctaModal = document.getElementById('cta-modal-backdrop');
|
||
var ctaModalClose = document.getElementById('cta-modal-close');
|
||
function openCtaModal() {
|
||
if (!ctaModal) return;
|
||
ctaModal.removeAttribute('hidden');
|
||
ctaModal.classList.add('is-open');
|
||
if (ctaModalClose) ctaModalClose.focus();
|
||
}
|
||
function closeCtaModal() {
|
||
if (!ctaModal) return;
|
||
ctaModal.classList.remove('is-open');
|
||
ctaModal.setAttribute('hidden', '');
|
||
var setupBtn = document.getElementById('setupClaudeBtn');
|
||
if (setupBtn) setupBtn.focus();
|
||
}
|
||
if (ctaModal) {
|
||
document.addEventListener('agnes:setup-script-copied', openCtaModal);
|
||
if (ctaModalClose) ctaModalClose.addEventListener('click', closeCtaModal);
|
||
ctaModal.addEventListener('click', function (ev) {
|
||
if (ev.target === ctaModal) closeCtaModal();
|
||
});
|
||
document.addEventListener('keydown', function (ev) {
|
||
if (ev.key === 'Escape' && ctaModal.classList.contains('is-open')) closeCtaModal();
|
||
});
|
||
}
|
||
|
||
// Shared poster for /api/me/onboarded — reused by every UI surface
|
||
// that flips users.onboarded (in-hero X close, "Mark me as onboarded"
|
||
// fallback button, the offboard strip). Reloads on success so the
|
||
// template re-renders with the new state.
|
||
function postOnboarded(triggerBtn, statusEl, targetOnboarded, targetSource) {
|
||
if (triggerBtn) triggerBtn.disabled = true;
|
||
if (statusEl) statusEl.textContent = targetOnboarded ? 'Marking…' : 'Resetting…';
|
||
fetch('/api/me/onboarded', {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ source: targetSource, onboarded: targetOnboarded }),
|
||
}).then(function (resp) {
|
||
if (resp.ok) {
|
||
if (statusEl) statusEl.textContent = 'Done. Reloading…';
|
||
window.location.reload();
|
||
} else {
|
||
if (statusEl) statusEl.textContent = 'Failed (' + resp.status + '). Try again.';
|
||
if (triggerBtn) triggerBtn.disabled = false;
|
||
}
|
||
}).catch(function () {
|
||
if (statusEl) statusEl.textContent = 'Network error. Try again.';
|
||
if (triggerBtn) triggerBtn.disabled = false;
|
||
});
|
||
}
|
||
|
||
// "Mark me as onboarded" fallback button inside the hero (rendered
|
||
// only when not-onboarded — the X close button is the primary path).
|
||
var btn = document.getElementById('self-mark-btn');
|
||
var status = document.getElementById('self-mark-status');
|
||
if (btn) {
|
||
btn.addEventListener('click', function () {
|
||
postOnboarded(
|
||
btn, status, true,
|
||
btn.getAttribute('data-target-source') || 'self_acknowledged'
|
||
);
|
||
});
|
||
}
|
||
|
||
// Hero X close — confirm first so a stray click doesn't flip state.
|
||
var heroClose = document.getElementById('installHeroClose');
|
||
if (heroClose) {
|
||
heroClose.addEventListener('click', function () {
|
||
var ok = window.confirm(
|
||
"Are you already onboarded? Closing this will mark your account as onboarded " +
|
||
"and hide the setup steps. You can revert later from the strip below the hero."
|
||
);
|
||
if (!ok) return;
|
||
postOnboarded(
|
||
heroClose, status, true,
|
||
heroClose.getAttribute('data-target-source') || 'self_acknowledged'
|
||
);
|
||
});
|
||
}
|
||
|
||
// Offboarding strip — only rendered when onboarded. Flips back to
|
||
// the install hero on next reload.
|
||
var offBtn = document.getElementById('offboard-btn');
|
||
var offStatus = document.getElementById('offboard-status');
|
||
if (offBtn) {
|
||
offBtn.addEventListener('click', function () {
|
||
postOnboarded(
|
||
offBtn, offStatus, false,
|
||
offBtn.getAttribute('data-target-source') || 'self_unmark'
|
||
);
|
||
});
|
||
}
|
||
|
||
// ── Generic dismiss-card handler ─────────────────────────────────
|
||
// Any `<button class="home-card-close" data-dismiss-key="...">`
|
||
// inside a `<section>` becomes dismissible: clicking sets the
|
||
// localStorage key to '1' and hides the section; on next page
|
||
// load the section starts hidden if the key is set. Used by the
|
||
// Getting Started card; future dismissible cards drop in with
|
||
// zero per-card JS.
|
||
document.querySelectorAll('.home-card-close[data-dismiss-key]').forEach(function (btn) {
|
||
var key = btn.getAttribute('data-dismiss-key');
|
||
var section = btn.closest('section, details');
|
||
if (!section || !key) return;
|
||
try {
|
||
if (localStorage.getItem(key) === '1') {
|
||
section.hidden = true;
|
||
return;
|
||
}
|
||
} catch (e) { /* private-mode: render visible, no-op */ }
|
||
btn.addEventListener('click', function () {
|
||
try { localStorage.setItem(key, '1'); } catch (e) { /* ignore */ }
|
||
section.hidden = true;
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
{% endblock %}
|