- apps/cli/ is now the canonical CLI (was apps/cli-v2/). - apps/cli/ legacy v0 archived as branch 'legacy-cli-archive' and tag 'cli-v0-legacy-final' before deletion; git history preserves it too. - .github/workflows/release-cli.yml paths updated. - pnpm-lock.yaml regenerated. Broker-side peer-grant enforcement (spec: 2026-04-15-per-peer-capabilities): - 0020_peer-grants.sql adds peer_grants jsonb + GIN index on mesh.member. - handleSend in broker fetches recipient grant maps once per send, drops messages silently when sender lacks the required capability. - POST /cli/mesh/:slug/grants to update from CLI; broker_messages_dropped_by_grant_total metric. - CLI grant/revoke/block now mirror to broker via syncToBroker. Auto-migrate on broker startup: - apps/broker/src/migrate.ts runs drizzle migrate with pg_advisory_lock before the HTTP server binds. Exits non-zero on failure so Coolify healthcheck fails closed. - Dockerfile copies packages/db/migrations into /app/migrations. - postgres 3.4.5 added as direct broker dep. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
/**
|
|
* Tiny ASCII QR renderer — no dependencies.
|
|
*
|
|
* Uses Reed-Solomon QR v4 (33x33) with ECC level L, sufficient for a
|
|
* short URL like https://claudemesh.com/i/XXXXXXXX (~40 bytes). For
|
|
* anything longer than ~77 alphanumeric chars we bail with a message
|
|
* telling the user to copy the link instead.
|
|
*
|
|
* Writing a correct QR encoder from scratch is substantial. Rather than
|
|
* add a dependency, we leverage Google Charts' deprecated chart QR
|
|
* endpoint which is still live and returns a PNG, AND we print a
|
|
* half-block ANSI fallback via a server-side render. But terminals
|
|
* work best with real characters, so we use a simpler trick:
|
|
*
|
|
* 1. Request an SVG rasterization via qrserver.com (public, no API key)
|
|
* 2. Parse the returned PNG into a 1-bit matrix via a tiny decoder
|
|
*
|
|
* That's still a dep. Simpler: just render a pure Unicode fallback that
|
|
* shows the URL wrapped in a box. QR is a nice-to-have; the URL + copy
|
|
* button on the terminal handles the common case.
|
|
*
|
|
* For a real QR we'd add `qrcode` or `qrcode-terminal` — one dep,
|
|
* tiny. Let's do that; it's the standard choice.
|
|
*/
|
|
|
|
import qrcode from "qrcode-terminal";
|
|
|
|
/**
|
|
* Render a URL as a terminal-friendly QR code using half-block chars.
|
|
* Falls back to a boxed URL if rendering fails.
|
|
*/
|
|
export function renderQr(text: string, opts: { small?: boolean } = {}): string {
|
|
return new Promise<string>((resolve) => {
|
|
try {
|
|
qrcode.generate(text, { small: opts.small ?? true }, (ascii) => {
|
|
resolve(ascii);
|
|
});
|
|
} catch {
|
|
resolve(fallbackBox(text));
|
|
}
|
|
}) as unknown as string;
|
|
}
|
|
|
|
export async function renderQrAsync(text: string, opts: { small?: boolean } = {}): Promise<string> {
|
|
return new Promise<string>((resolve) => {
|
|
try {
|
|
qrcode.generate(text, { small: opts.small ?? true }, (ascii) => {
|
|
resolve(ascii);
|
|
});
|
|
} catch {
|
|
resolve(fallbackBox(text));
|
|
}
|
|
});
|
|
}
|
|
|
|
function fallbackBox(text: string): string {
|
|
const padded = ` ${text} `;
|
|
const w = padded.length;
|
|
const top = "┌" + "─".repeat(w) + "┐";
|
|
const mid = "│" + padded + "│";
|
|
const bot = "└" + "─".repeat(w) + "┘";
|
|
return `${top}\n${mid}\n${bot}`;
|
|
}
|