agnes-the-ai-analyst/app/web/templates/home_not_onboarded.html
Vojtech ae67c40a81
fix(onboarding): /home install flow + agnes init UX hardening (#350)
* fix(web): /home Step 2 recommends --dangerously-skip-permissions for setup

The Step 4 paste runs ~20 shell commands (CLI install, workspace
bootstrap, marketplace clone, MCP register, connector logins). Previous
Step 2 recommended auto-accept-edits via Shift + Tab, which covers file
edits but not Bash — users still clicked ~20 Yes prompts during setup.

Step 2 now leads with `claude --dangerously-skip-permissions` as the
recommended session flag (Bash + edits both skip). Session-scoped, drops
on next plain `claude` — safe here because the pasted script is
generated by this server and ends after a fixed sequence; the flag does
not weaken future Claude sessions.

Auto-accept-edits via Shift + Tab kept as the strict-review fallback;
persistent YOLO allowlist link to /setup-advanced#yolo unchanged.

* fix(web): swap /home Steps 2↔3, claude --yolo as copy-button command

Folder creation moves to Step 2; Step 3 launches Claude from that
directory with `claude --dangerously-skip-permissions`. The YOLO flag
is rendered through the standard .install-cmd + copy-button affordance
(matching Step 1 + Step 2), not inline prose. Step 4 paste runs ~20
shell commands that auto-accept-edits would not cover (Bash still
prompts), so the YOLO flag is the default recommendation; session-
scoped, drops on next plain `claude`.

Setup script's pwd-check warning copy refreshed to reference "/home
Step 2" (the new folder-creation step number).

# Conflicts:
#	CHANGELOG.md

* fix(web): open YOLO setup-advanced link in new tab

Step 3 install-hero's persistent-YOLO link now opens /setup-advanced#yolo
in a new window so users don't lose their /home install context mid-
setup. target="_blank" + rel="noopener" (no reverse-tabnabbing).

* fix(web): merge /home Step 3 fallback prose into prior paragraph

Drop the <br><br> between the 'Session-scoped' line and the 'Prefer
reviewing each command' line so the strict-review fallback flows on
the same paragraph — less vertical space in the install-hero block.

* docs(web): add "What leaves your machine" privacy callout on /home

Install-hero lead now includes a short privacy paragraph: explains that
session telemetry (prompts / tool-calls / tool-responses) flows back to
the central catalog for failure-pattern analysis while raw data rows
the user queries locally stay on their machine. Points at /agnes-private
as the per-session opt-out.

Also collapses leftover cherry-pick conflict markers in CHANGELOG.md
into one clean [Unreleased] section.

* fix(init): harden agnes init UX — 5 issues from David's report

1. chmod +x hooks. agnes init + agnes refresh-marketplace --bootstrap
   now set the execute bit on every .sh they land on disk
   (`<workspace>/.claude/hooks/*.sh` after init; every `.sh` under the
   `~/.agnes/marketplace` clone after a bootstrap/pull). Git checkout
   doesn't always preserve filemode (filemode=false repos, ZIP
   extractions), so hooks were firing with "Permission denied" — silent
   SessionStart / PreToolUse breakage. Best-effort, no-op on Windows.

2. --token-file + AGNES_TOKEN. agnes init now accepts `--token-file
   <path>` and an `AGNES_TOKEN` env fallback alongside `--token`.
   Precedence: --token > --token-file > AGNES_TOKEN. The file / env-var
   paths dodge Claude Code's auto-classifier, which sometimes flags a
   long bearer token in `--token "eyJ..."` command line as a credential-
   exfil pattern. The pasted setup script now uses `--token-file
   ~/.agnes/token` (token written via single-quoted heredoc, umask 077)
   for the same reason.

3. Bash(agnes *) in allow. Default `.claude/settings.json` permissions.
   allow seeded by agnes init now includes `Bash(agnes *)` alongside the
   bare `Bash` entry, so Claude Code's classifier sees an explicit allow
   for subsequent `agnes <verb>` calls inside the workspace it just
   bootstrapped.

4. .zshrc PATH dedup. Setup-script step 1's PATH-persist snippet
   (no-CA install path) replaced with a `grep -qF + ||` idiom so a
   re-run doesn't append a duplicate `export PATH=...` line. Fixed-
   string match (not regex) per the dedup-bug report.

5. `!` prefix doc note. Setup-script step 3 now explicitly tells the
   user: if Claude Code blocks an `agnes` command, prefix it with `!`
   (e.g. `! agnes init …`) to run the command directly in the shell,
   bypassing the auto-classifier.

* release: 0.55.1 — /home onboarding install-hero rework + agnes init UX hardening

---------

Co-authored-by: ZdenekSrotyr <zdenek.srotyr@keboola.com>
2026-05-19 15:26:35 +02:00

1555 lines
60 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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">&rsaquo;</span>
</summary>
<button type="button" class="home-card-close"
data-dismiss-key="agnes_home_gs_dismissed"
aria-label="Dismiss Getting Started">&times;</button>
<a class="home-gs-item" href="#install-hero">
<span class="ico" aria-hidden="true">&#x1F680;</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">&darr;</span>
</a>
<a class="home-gs-item" href="/setup-advanced">
<span class="ico" aria-hidden="true">&#x1F6E0;&#xFE0F;</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">&rarr;</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">&times;</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>
<p class="lead lead-privacy">
<strong>What leaves your machine.</strong> Session telemetry — prompts, tool calls, and tool responses — flows back to the central catalog so the team can analyse failure patterns. Raw data rows you query locally stay on your machine; only the prompt/response transcript travels. Need a session off the record? Toggle Private in Claude Code (by typing <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;">/agnes-private</code>) to disable telemetry for that chat — nothing from that session reaches the catalog.
</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>
<div class="install-block">
<div class="label">Step 2 — 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 }} &amp;&amp; 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 — Step 3 launches Claude Code from this same directory.
</div>
</div>
{% if home_automode.show %}
<div class="install-block">
<div class="label">Step 3 — start Claude Code with permission-skip (recommended before Step 4)</div>
<div class="install-note">
The Step 4 paste runs many shell commands (CLI install, workspace bootstrap, marketplace clone, MCP register, connector logins). Launch Claude Code from your Step 2 directory with this flag so the setup completes without ~20 Yes/No prompt approvals:
</div>
<div class="install-cmd">
<span class="multiline" id="install-cmd-claude-yolo">claude --dangerously-skip-permissions</span>
<button class="copy-btn" data-copy-target="install-cmd-claude-yolo">Copy</button>
</div>
<div class="install-note">
Session-scoped — drops on next plain <code>claude</code>. Safe here because the script you paste in Step 4 is generated by this server and ends after a fixed sequence; the flag does not weaken future Claude sessions. Prefer reviewing each command? Run plain <code>claude</code> and press <strong>Shift + Tab</strong> to cycle on <strong>auto-accept edits</strong> (default → auto-accept edits → plan mode; footer shows <code>⏵⏵</code>). Covers file edits, not Bash — expect ~20 prompt clicks during Step 4.
<br><br>
Persistent YOLO (allowlisted): see <a href="/setup-advanced#yolo" target="_blank" rel="noopener">YOLO mode</a> on /setup-advanced — pairs <code>--dangerously-skip-permissions</code> with a reviewed <code>~/.claude/settings.local.json</code> allowlist.
</div>
</div>
{% endif %}
<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">&#x2328;&#xFE0F;</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">&#x1F4D1;</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">&#x1F4BB;</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">&#x1F9E9;</span>
<div class="ttl">Plugin marketplace <span class="arrow">&rarr;</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">&#x1F4E6;</span>
<div class="ttl">Curated data packages <span class="arrow">&rarr;</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>
{# Curated Memory is user-facing — the /corporate-memory route runs
on get_current_user, so the tile shows for everyone, matching
its primary-nav placement. #}
<a class="what-is-item" href="/corporate-memory">
<span class="ico">&#x1F9E0;</span>
<div class="ttl">Curated memory <span class="arrow">&rarr;</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>
{# Activity center admin-only — matches the top-nav gating logic. #}
{% if is_admin %}
<a class="what-is-item" href="/activity-center">
<span class="ico">&#x1F4C8;</span>
<div class="ttl">Activity center <span class="arrow">&rarr;</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 &rarr;</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">&#x2705;</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 &amp; 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 %}