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>
This commit is contained in:
63
apps/cli/src/ui/qr.ts
Normal file
63
apps/cli/src/ui/qr.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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}`;
|
||||
}
|
||||
Reference in New Issue
Block a user