Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions public/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/sh
# Install qc, the qrypt.chat terminal client (npm: @profullstack/qryptchat).
#
# curl -fsSL https://qrypt.chat/install.sh | sh
#
# Uses bun if present, otherwise npm (Node >= 22). When the global npm prefix
# is not writable it installs into ~/.local instead of asking for sudo.
set -eu

PKG="@profullstack/qryptchat"

say() { printf '%s\n' "$*"; }
die() { printf 'qc install: %s\n' "$*" >&2; exit 1; }
have() { command -v "$1" >/dev/null 2>&1; }

say "Installing qc (qrypt.chat terminal client)..."

if have bun; then
bun add -g "$PKG"
BIN_DIR="${BUN_INSTALL:-$HOME/.bun}/bin"
elif have npm && have node; then
major=$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)
[ "$major" -ge 22 ] || die "Node >= 22 required (found $(node -v)). Upgrade Node or install bun: https://bun.sh"
prefix=$(npm config get prefix)
modules="$prefix/lib/node_modules"
[ -d "$modules" ] || modules="$prefix/lib"
if [ -w "$modules" ] && [ -w "$prefix/bin" ]; then
npm install -g "$PKG"
BIN_DIR="$prefix/bin"
else
BIN_DIR="$HOME/.local/bin"
say "npm prefix $prefix is not writable; installing into $HOME/.local"
npm install -g --prefix "$HOME/.local" "$PKG"
fi
else
die "needs bun (https://bun.sh) or Node >= 22 with npm (https://nodejs.org)"
fi

say ""
if have qc; then
say "$(qc --version 2>/dev/null || echo qc) installed."
else
say "qc installed to $BIN_DIR, which is not on your PATH. Add it:"
say " export PATH=\"$BIN_DIR:\$PATH\""
fi
say ""
say "Next:"
say " qc login # approve in your browser; keys are sealed to this device"
say " qc # open the TUI"
say " qc --help # CLI and MCP usage"
31 changes: 31 additions & 0 deletions src/app/home-content.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
'use client';

import Link from 'next/link';
import { useState } from 'react';
import { useI18n } from '@/lib/hooks/useI18n.js';

const INSTALL_CMD = 'curl -fsSL https://qrypt.chat/install.sh | sh';

export default function HomeContent() {
const { t } = useI18n();
const [copied, setCopied] = useState(false);

const copyInstall = async () => {
try {
await navigator.clipboard.writeText(INSTALL_CMD);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
// Clipboard blocked (insecure context or denied); the command stays selectable.
}
};

return (
<>
Expand All @@ -19,6 +33,16 @@ export default function HomeContent() {
<Link href="/auth" className="btn btn-primary">{t('nav.register')}</Link>
<Link href="/auth" className="btn btn-secondary">{t('nav.login')}</Link>
</div>
<div className="hero-install">
<span className="hero-install-label">Terminal client (qc): TUI, CLI and MCP</span>
<div className="hero-install-cmd">
<span className="hero-install-prompt" aria-hidden="true">$</span>
<code>{INSTALL_CMD}</code>
<button type="button" className="hero-install-copy" onClick={copyInstall} aria-label="Copy install command">
{copied ? 'Copied' : 'Copy'}
</button>
</div>
</div>
</div>
<div className="hero-visual">
<img src="/hero.svg" alt="Illustration of a locked, end-to-end encrypted QryptChat conversation protected by post-quantum cryptography" className="hero-image" width="500" height="500" />
Expand Down Expand Up @@ -111,6 +135,13 @@ export default function HomeContent() {
.hero-actions .btn-primary:hover { background-color: var(--color-bg-secondary); transform: translateY(-2px); box-shadow: 0 10px 25px rgba(0,0,0,.2); }
.hero-actions .btn-secondary { background-color: transparent; color: white; border-color: rgba(255,255,255,.3); }
.hero-actions .btn-secondary:hover { background-color: rgba(255,255,255,.1); border-color: white; }
.hero-install { margin-top: var(--space-8); }
.hero-install-label { display: block; font-size: .875rem; opacity: .8; margin-bottom: var(--space-2); }
.hero-install-cmd { display: flex; align-items: center; gap: var(--space-3); max-width: 100%; background: rgba(0,0,0,.35); border: 1px solid rgba(255,255,255,.2); border-radius: var(--radius-md); padding: var(--space-3) var(--space-4); }
.hero-install-cmd code { flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9375rem; color: #fff; text-align: left; user-select: all; }
.hero-install-prompt { opacity: .6; user-select: none; }
.hero-install-copy { flex-shrink: 0; background: transparent; color: #fff; border: 1px solid rgba(255,255,255,.35); border-radius: var(--radius-md); padding: var(--space-1) var(--space-3); font-size: .8125rem; font-weight: 600; cursor: pointer; }
.hero-install-copy:hover { background: rgba(255,255,255,.1); border-color: #fff; }
.hero-image { width: 100%; height: auto; max-width: 500px; opacity: 0.9; filter: drop-shadow(0 10px 30px rgba(0,0,0,.3)); }
.features { padding: var(--space-20) 0; background-color: var(--color-bg-secondary); }
.section-header { text-align: center; margin-bottom: var(--space-16); }
Expand Down
Loading