feat(cli): 1.32.0 — multi-session UX bundle (self-identity, --self fan-out, broker welcome)
Some checks failed
CI / Typecheck (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

Nine UX bugs surfaced from a real two-session interconnect smoke
test, shipped together.

Self-identity is visible
- peer list now shows the caller as (this session), sorted to top.
  Daemon path resolves session pubkey via /v1/sessions/me so
  isThisSession is set correctly warm.
- whoami shows session pubkey, session id, mesh, role, groups, cwd,
  pid when run inside a launched session.

Sibling-session disambiguation
- peer list rows carry sid:<short> tag so visually-identical rows
  can be told apart at a glance.

Daemon hidden by default
- claudemesh-daemon presence rows hidden from peer list by default.
  --all opts back in. Header shows N daemon hidden when applicable.

--self flag works end-to-end
- Argv parser was greedy: --self ate the next arg as its value.
  BOOLEAN_FLAGS set in cli/argv.ts now lists known no-value switches.
- message send subcommand now passes self through (only legacy send
  was wired before).
- Help text lists --self.

Member-pubkey fan-out
- Sending to your own member pubkey with --self now resolves to every
  connected sibling session and sends one message per recipient.
  Required because the broker drain matches target_spec only against
  full session pubkeys; member-pubkey sends queued but never drained.

Broker welcome at launch
- After the launch banner, one line confirms WS state, peer count,
  and unread inbox count. Best-effort — falls back gracefully.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-05-04 17:02:28 +01:00
parent 25586d298f
commit 7460d34335
9 changed files with 353 additions and 23 deletions

View File

@@ -1,25 +1,51 @@
import { whoAmI } from "~/services/auth/facade.js";
import { getSessionInfo } from "~/services/session/resolve.js";
import { render } from "~/ui/render.js";
import { bold, clay, dim } from "~/ui/styles.js";
import { bold, clay, dim, yellow } from "~/ui/styles.js";
import { EXIT } from "~/constants/exit-codes.js";
export async function whoami(opts: { json?: boolean }): Promise<number> {
const result = await whoAmI();
// 1.32.0+: surface the calling session's identity when whoami is run
// from inside a `claudemesh launch`-spawned shell. Previously the
// command only reported web sign-in + local mesh memberships, and a
// launched session had to dig env vars + parse config.json to figure
// out its own session pubkey.
const session = await getSessionInfo();
if (opts.json) {
console.log(JSON.stringify({ schema_version: "1.0", ...result }, null, 2));
return result.signed_in || result.local ? EXIT.SUCCESS : EXIT.AUTH_FAILED;
console.log(JSON.stringify({ schema_version: "1.0", ...result, session }, null, 2));
return result.signed_in || result.local || session ? EXIT.SUCCESS : EXIT.AUTH_FAILED;
}
// Show whatever we have. Both the web session and the local mesh
// config are independent surfaces of identity; suppress sections that
// are empty.
if (!result.signed_in && !result.local) {
// Show whatever we have. Web session, local mesh config, and the
// launched-session identity are three independent surfaces.
if (!result.signed_in && !result.local && !session) {
render.err("Not signed in", "Run `claudemesh login` to sign in or `claudemesh <invite>` to join.");
return EXIT.AUTH_FAILED;
}
render.section("whoami");
if (session) {
const sessionPk = session.presence?.sessionPubkey;
const groups = (session.groups ?? []).join(", ") || dim("(none)");
render.kv([
["this session", `${yellow(session.displayName)} on ${bold(session.mesh)}`],
["session id", dim(session.sessionId)],
...(sessionPk
? [["session pubkey", dim(`${sessionPk.slice(0, 16)}… (full: ${sessionPk})`)] as [string, string]]
: []),
...(session.role
? [["role", session.role] as [string, string]]
: []),
["groups", groups],
...(session.cwd ? [["cwd", dim(session.cwd)] as [string, string]] : []),
["pid", String(session.pid)],
]);
render.blank();
}
if (result.signed_in) {
render.kv([
["user", `${bold(result.user!.display_name)} ${dim(`(${result.user!.email})`)}`],