Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Using hush from an agent needed a clone and docs/MCP.md. It now needs one
command, and the instructions are served by the deployment itself.
`go install github.com/orchard9/hush/cmd/hush-mcp@latest` is the whole
install: cmd/hush-mcp imports only the standard library, so module graph
pruning never reaches the private go-chassis dependency cmd/hushd needs.
Verified against an empty module cache and the public proxy, then create ->
reveal end to end against production with the resulting binary.
The page carries the per-client configuration for Claude Code, Codex CLI,
Gemini CLI, VS Code, Claude Desktop, Cursor and omp. Each command was run
against the installed client rather than copied from documentation, which is
how the differences on it are there at all: VS Code's wrapper key is
`servers`, not `mcpServers`; gemini defaults to project scope, not user;
Claude Code rejects `--env` immediately before the server name.
The shared browser crypto moves from base.html into templates/crypto.html,
which the two pages that encrypt parse and this one does not. An empty
`{{define}}` cannot replace a non-empty one — text/template reads an empty
body as no definition — so the shell holds the call and the partial holds the
code, and the docs page ships no script at all.
Three things this exposed, fixed here:
- The public Ingress enumerates paths, so a handler without one 404s at the
edge while working in `make dev`. The Ingress is now its own manifest:
hush.yaml pins a `:bootstrap` image that does not exist, so re-applying it
to publish a path would roll the workload onto an unpullable image.
`make deploy-ingress` applies the route alone.
- release.sh guarded HEAD against `@{upstream}`, which is the GitHub mirror
here, while Kaniko clones Gitea. A commit pushed to one and not the other
would have built the previous commit silently. It now fetches and compares
the branch that actually gets built.
- smoke.sh checks that /mcp serves the install command, so a stale rollout or
an unexecutable template fails the release instead of being found later.
Confirmed it fails: against production before this deploy it reported 404.
107 lines
4.5 KiB
HTML
107 lines
4.5 KiB
HTML
{{define "base.html"}}<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>hush</title>
|
|
<meta name="color-scheme" content="dark">
|
|
<meta name="referrer" content="no-referrer">
|
|
<!-- The policy is a HEADER, set in internal/web/web.go, and every inline block
|
|
below carries that response's nonce. There is deliberately no CSP <meta>
|
|
here: two policies on one response intersect, so a <meta> cannot loosen the
|
|
header, and it silently made this page's own script look permitted while
|
|
the browser blocked it. No external origins either — no CDN, no font host,
|
|
no analytics: a third-party script on this page could read the key out of
|
|
the fragment. -->
|
|
<style nonce="{{.Nonce}}">
|
|
:root{
|
|
color-scheme:dark;
|
|
--bg:#08090a; /* page */
|
|
--panel:#0e1012; /* the one card */
|
|
--inset:#0a0b0c; /* fields and output, recessed from the card */
|
|
--line:#1c1e22; /* hairline */
|
|
--line-lit:#2b2f35; /* hairline, hovered or focused */
|
|
--fg:#f1f2f4;
|
|
--dim:#8d939d; /* supporting copy */
|
|
--faint:#5d626b; /* placeholder, footer */
|
|
--accent:#8ce0b8; /* used sparingly: the mark, focus, success */
|
|
--warn:#f2c078;
|
|
--bad:#ff8189;
|
|
--r:12px;
|
|
--mono:ui-monospace,SFMono-Regular,Menlo,monospace;
|
|
}
|
|
*{box-sizing:border-box}
|
|
html{-webkit-text-size-adjust:100%}
|
|
body{
|
|
margin:0;background:var(--bg);color:var(--fg);
|
|
font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;
|
|
font-feature-settings:"kern" 1;-webkit-font-smoothing:antialiased;
|
|
display:flex;min-height:100vh;align-items:center;justify-content:center;padding:24px;
|
|
}
|
|
main{width:100%;max-width:440px}
|
|
.card{
|
|
background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
|
|
padding:28px 26px;box-shadow:0 30px 60px -40px #000,0 1px 0 #ffffff05 inset;
|
|
}
|
|
h1{font-size:21px;font-weight:600;letter-spacing:-.02em;margin:0 0 6px}
|
|
h1 span{color:var(--accent)}
|
|
p.lede{color:var(--dim);margin:0 0 22px;font-size:13.5px}
|
|
textarea,button{font:inherit;width:100%;border-radius:10px}
|
|
textarea{
|
|
color:var(--fg);background:var(--inset);border:1px solid var(--line);
|
|
font:13px/1.55 var(--mono);padding:13px 14px;min-height:132px;resize:vertical;
|
|
transition:border-color .12s,box-shadow .12s;
|
|
}
|
|
textarea::placeholder{color:var(--faint)}
|
|
textarea:hover{border-color:var(--line-lit)}
|
|
textarea:focus,button:focus-visible{outline:0;border-color:var(--line-lit);box-shadow:0 0 0 3px #8ce0b81f}
|
|
button{
|
|
margin-top:14px;padding:11px 16px;border:1px solid transparent;
|
|
background:var(--fg);color:#08090a;font-weight:600;font-size:14px;
|
|
cursor:pointer;transition:background .12s,border-color .12s,color .12s;
|
|
}
|
|
button:hover:not(:disabled){background:#fff}
|
|
button:active:not(:disabled){transform:translateY(.5px)}
|
|
/* A disabled solid button is a grey slab that still dominates the card and
|
|
reads as "loading" rather than "not now". It recedes instead. */
|
|
button:disabled{background:none;color:var(--faint);border-color:var(--line);cursor:not-allowed}
|
|
button.ghost{background:none;color:var(--dim);border-color:var(--line);font-weight:500}
|
|
button.ghost:hover{background:none;color:var(--fg);border-color:var(--line-lit)}
|
|
.row{display:flex;gap:10px}
|
|
.row>*{flex:1}
|
|
.out{
|
|
background:var(--inset);border:1px solid var(--line);border-radius:10px;
|
|
padding:13px 14px;font:13px/1.6 var(--mono);word-break:break-all;
|
|
white-space:pre-wrap;user-select:all;
|
|
}
|
|
.note{color:var(--dim);font-size:12.5px;line-height:1.5;margin:12px 0 0}
|
|
.note.err{color:var(--bad)}
|
|
.note.warn{color:var(--warn)}
|
|
.hide{display:none}
|
|
footer{margin:14px 2px 0;color:var(--faint);font-size:11.5px;line-height:1.5}
|
|
footer code{font:11px var(--mono);color:var(--dim)}
|
|
footer a{color:var(--dim);text-decoration:none;border-bottom:1px solid var(--line)}
|
|
footer a:hover{color:var(--fg);border-color:var(--line-lit)}
|
|
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
|
|
</style>
|
|
<!-- Page-specific CSS, nonced by the page that defines it. Empty for the two
|
|
product pages, which need nothing beyond the shell above. -->
|
|
{{block "styles" .}}{{end}}
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<div class="card">
|
|
{{template "content" .}}
|
|
</div>
|
|
<footer>
|
|
Encrypted in your browser. The key travels in the link's <code>#fragment</code>,
|
|
which browsers never send to a server.{{block "nav" .}}{{end}}
|
|
</footer>
|
|
</main>
|
|
<!-- The pages that encrypt pull in templates/crypto.html, which defines this.
|
|
The MCP page defines it empty: it runs no script at all. -->
|
|
{{template "crypto" .}}
|
|
{{template "script" .}}
|
|
</body>
|
|
</html>{{end}}
|