Files
claudemesh/apps/cli/src/ui/qr.ts
Alejandro Gutiérrez ee12510ef1
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled
refactor: rename cli-v2 → cli, archive legacy cli, plus broker-side grants + auto-migrate
- 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>
2026-04-15 08:44:52 +01:00

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}`;
}