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>
75 lines
2.8 KiB
TypeScript
75 lines
2.8 KiB
TypeScript
import { whoAmI } from "~/services/auth/facade.js";
|
|
import { getSessionInfo } from "~/services/session/resolve.js";
|
|
import { render } from "~/ui/render.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, session }, null, 2));
|
|
return result.signed_in || result.local || session ? EXIT.SUCCESS : EXIT.AUTH_FAILED;
|
|
}
|
|
|
|
// 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})`)}`],
|
|
["token", `${result.token_source} ${dim("(~/.claudemesh/auth.json)")}`],
|
|
...(result.meshes
|
|
? [["meshes", `${result.meshes.owned} owned · ${result.meshes.guest} guest`] as [string, string]]
|
|
: []),
|
|
]);
|
|
} else {
|
|
render.kv([
|
|
["web", dim("not signed in · run `claudemesh login` for account features")],
|
|
]);
|
|
}
|
|
if (result.local) {
|
|
render.blank();
|
|
render.kv([
|
|
["local", `${result.local.meshes.length} mesh${result.local.meshes.length === 1 ? "" : "es"} · ${dim(result.local.config_path)}`],
|
|
]);
|
|
for (const m of result.local.meshes) {
|
|
console.log(` ${clay("●")} ${bold(m.slug)} ${dim(`member ${m.member_id.slice(0, 8)}… pk ${m.pubkey_prefix}…`)}`);
|
|
}
|
|
}
|
|
render.blank();
|
|
|
|
return EXIT.SUCCESS;
|
|
}
|