chore: wrap up the gap-closing session
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

- info/inbox commands → unified render.ts
- install route: drop in-memory counter, rely on PostHog + structured logs
- docs: roadmap, CLAUDE.md reflect alpha.31 state
- tests workflow now also builds + smoke-tests the CLI bundle
- homebrew tap bootstrap kit in packaging/homebrew-tap-bootstrap/
  (README + copy of the formula template for dropping into the tap repo)
- upstream Claude Code issue draft for rich <channel> UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-15 08:53:59 +01:00
parent ee12510ef1
commit 45d85f5eaa
10 changed files with 333 additions and 54 deletions

View File

@@ -7,6 +7,8 @@
import { withMesh } from "./connect.js";
import type { InboundPush } from "~/services/broker/facade.js";
import { render } from "~/ui/render.js";
import { bold, dim } from "~/ui/styles.js";
export interface InboxFlags {
mesh?: string;
@@ -14,47 +16,34 @@ export interface InboxFlags {
wait?: number;
}
function formatMessage(msg: InboundPush, useColor: boolean): string {
const dim = (s: string) => (useColor ? `\x1b[2m${s}\x1b[22m` : s);
const bold = (s: string) => (useColor ? `\x1b[1m${s}\x1b[22m` : s);
function formatMessage(msg: InboundPush): string {
const text = msg.plaintext ?? `[encrypted: ${msg.ciphertext.slice(0, 32)}…]`;
const from = msg.senderPubkey.slice(0, 8);
const time = new Date(msg.createdAt).toLocaleTimeString();
const kindTag = msg.kind === "direct" ? "→ direct" : msg.kind;
return ` ${bold(from)} ${dim(`[${kindTag}] ${time}`)}\n ${text}`;
}
export async function runInbox(flags: InboxFlags): Promise<void> {
const useColor =
!process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
const dim = (s: string) => (useColor ? `\x1b[2m${s}\x1b[22m` : s);
const bold = (s: string) => (useColor ? `\x1b[1m${s}\x1b[22m` : s);
const waitMs = (flags.wait ?? 1) * 1000;
await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
// Wait briefly for broker to push any held messages.
await new Promise<void>((resolve) => setTimeout(resolve, waitMs));
const messages = client.drainPushBuffer();
if (flags.json) {
console.log(JSON.stringify(messages, null, 2));
process.stdout.write(JSON.stringify(messages, null, 2) + "\n");
return;
}
if (messages.length === 0) {
console.log(dim(`No messages on mesh "${mesh.slug}".`));
render.info(dim(`No messages on mesh "${mesh.slug}".`));
return;
}
console.log(bold(`Inbox — ${mesh.slug}`) + dim(` (${messages.length} message${messages.length === 1 ? "" : "s"})`));
console.log("");
render.section(`inbox — ${mesh.slug} (${messages.length} message${messages.length === 1 ? "" : "s"})`);
for (const msg of messages) {
console.log(formatMessage(msg, useColor));
console.log("");
process.stdout.write(formatMessage(msg) + "\n\n");
}
});
}

View File

@@ -6,6 +6,7 @@
import { withMesh } from "./connect.js";
import { readConfig } from "~/services/config/facade.js";
import { render } from "~/ui/render.js";
export interface InfoFlags {
mesh?: string;
@@ -13,11 +14,6 @@ export interface InfoFlags {
}
export async function runInfo(flags: InfoFlags): Promise<void> {
const useColor =
!process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
const dim = (s: string) => (useColor ? `\x1b[2m${s}\x1b[22m` : s);
const bold = (s: string) => (useColor ? `\x1b[1m${s}\x1b[22m` : s);
const config = readConfig();
await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
@@ -39,20 +35,24 @@ export async function runInfo(flags: InfoFlags): Promise<void> {
};
if (flags.json) {
console.log(JSON.stringify(output, null, 2));
process.stdout.write(JSON.stringify(output, null, 2) + "\n");
return;
}
console.log(bold(mesh.slug) + dim(` · ${mesh.brokerUrl}`));
console.log(dim(` mesh: ${mesh.meshId}`));
console.log(dim(` member: ${mesh.memberId}`));
console.log(` peers: ${peers.length} connected`);
console.log(` state: ${state.length} keys`);
render.section(`${mesh.slug} · ${mesh.brokerUrl}`);
render.kv([
["mesh", mesh.meshId],
["member", mesh.memberId],
["peers", `${peers.length} connected`],
["state", `${state.length} keys`],
]);
if (brokerInfo && typeof brokerInfo === "object") {
const extras: Array<[string, string]> = [];
for (const [k, v] of Object.entries(brokerInfo)) {
if (["slug", "meshId", "brokerUrl"].includes(k)) continue;
console.log(dim(` ${k}: ${JSON.stringify(v)}`));
extras.push([k, JSON.stringify(v)]);
}
if (extras.length) render.kv(extras);
}
});
}