chore: wrap up the gap-closing session
- 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:
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
|
||||
import { headers } from "next/headers";
|
||||
|
||||
// In-memory counter (resets on deploy — good enough for a signal).
|
||||
// For persistent tracking, write to DB or use PostHog server SDK.
|
||||
let installFetches = 0;
|
||||
// Persistent counts live in PostHog (event: install_script_fetched).
|
||||
// Stdout logs are aggregated by the Coolify log collector. No in-memory
|
||||
// counter — it reset on every deploy and misled operators into thinking
|
||||
// the signal was fresh.
|
||||
|
||||
const SCRIPT = `#!/usr/bin/env bash
|
||||
# claudemesh-cli installer
|
||||
@@ -140,19 +141,24 @@ say ""
|
||||
`;
|
||||
|
||||
export async function GET(): Promise<Response> {
|
||||
installFetches++;
|
||||
|
||||
// Log server-side for monitoring
|
||||
// Log server-side for Coolify log aggregation.
|
||||
const h = await headers();
|
||||
const ua = h.get("user-agent") ?? "unknown";
|
||||
const ip = h.get("x-forwarded-for") ?? h.get("x-real-ip") ?? "unknown";
|
||||
const referer = h.get("referer") ?? "direct";
|
||||
|
||||
const ts = new Date().toISOString();
|
||||
console.log(
|
||||
`[install] #${installFetches} | ip=${ip} | ua=${ua.slice(0, 80)} | ref=${referer}`,
|
||||
JSON.stringify({
|
||||
level: "info",
|
||||
event: "install_script_fetched",
|
||||
ts,
|
||||
ip,
|
||||
ua: ua.slice(0, 120),
|
||||
referer,
|
||||
}),
|
||||
);
|
||||
|
||||
// PostHog server-side event (if configured)
|
||||
// PostHog server-side event — source of truth for counts.
|
||||
try {
|
||||
const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY;
|
||||
const posthogHost = process.env.NEXT_PUBLIC_POSTHOG_HOST;
|
||||
@@ -164,10 +170,10 @@ export async function GET(): Promise<Response> {
|
||||
api_key: posthogKey,
|
||||
event: "install_script_fetched",
|
||||
distinct_id: ip,
|
||||
timestamp: ts,
|
||||
properties: {
|
||||
user_agent: ua,
|
||||
referer,
|
||||
install_count: installFetches,
|
||||
},
|
||||
}),
|
||||
}).catch(() => {}); // fire-and-forget
|
||||
|
||||
Reference in New Issue
Block a user