feat(cli): default-aggregation for topic list + notification list
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

ships v0.5.0 phase 1.

omitting --mesh on these read verbs now routes through
/v1/me/topics and /v1/me/notifications instead of prompting
the user to pick a mesh. behavior preserved for explicit
--mesh foo.

implementation: resolveMeshForMint helper in commands/me.ts
silently picks the first joined mesh for apikey-mint when
flags.mesh is null. /v1/me/* endpoints resolve the user from
the apikey issuer regardless of which mesh issued the key, so
mint location is irrelevant — only the user identity matters.

help text updated to reflect the new default.

phase 2 (task list, state list, memory recall) needs /v1/me/*
aggregator endpoints first; deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-05-03 04:56:33 +01:00
parent e60980cfd7
commit 5ceb311d74
4 changed files with 57 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "claudemesh-cli",
"version": "1.14.0",
"version": "1.15.0",
"description": "Peer mesh for Claude Code sessions — CLI + MCP server.",
"keywords": [
"claude-code",

View File

@@ -16,10 +16,23 @@
import { withRestKey } from "~/services/api/with-rest-key.js";
import { request } from "~/services/api/client.js";
import { readConfig } from "~/services/config/facade.js";
import { render } from "~/ui/render.js";
import { bold, clay, cyan, dim, green, yellow } from "~/ui/styles.js";
import { EXIT } from "~/constants/exit-codes.js";
/**
* /v1/me/* endpoints resolve the caller's user from the apikey issuer
* regardless of which mesh issued the key — every mesh works. When the
* user didn't pass --mesh, silently pick the first joined mesh for
* apikey-mint instead of prompting; the endpoint sees the same user.
*/
function resolveMeshForMint(explicit: string | null | undefined): string | null {
if (explicit) return explicit;
const cfg = readConfig();
return cfg.meshes[0]?.slug ?? null;
}
interface WorkspaceMesh {
meshId: string;
slug: string;
@@ -53,7 +66,7 @@ export interface MeFlags {
export async function runMe(flags: MeFlags): Promise<number> {
return withRestKey(
{
meshSlug: flags.mesh ?? null,
meshSlug: resolveMeshForMint(flags.mesh),
purpose: "workspace-overview",
capabilities: ["read"],
},
@@ -136,7 +149,7 @@ export interface MeTopicsFlags extends MeFlags {
export async function runMeTopics(flags: MeTopicsFlags): Promise<number> {
return withRestKey(
{
meshSlug: flags.mesh ?? null,
meshSlug: resolveMeshForMint(flags.mesh),
purpose: "workspace-topics",
capabilities: ["read"],
},
@@ -232,7 +245,7 @@ export async function runMeNotifications(
): Promise<number> {
return withRestKey(
{
meshSlug: flags.mesh ?? null,
meshSlug: resolveMeshForMint(flags.mesh),
purpose: "workspace-notifications",
capabilities: ["read"],
},
@@ -321,7 +334,7 @@ export interface MeActivityFlags extends MeFlags {
export async function runMeActivity(flags: MeActivityFlags): Promise<number> {
return withRestKey(
{
meshSlug: flags.mesh ?? null,
meshSlug: resolveMeshForMint(flags.mesh),
purpose: "workspace-activity",
capabilities: ["read"],
},
@@ -416,7 +429,7 @@ export async function runMeSearch(flags: MeSearchFlags): Promise<number> {
return withRestKey(
{
meshSlug: flags.mesh ?? null,
meshSlug: resolveMeshForMint(flags.mesh),
purpose: "workspace-search",
capabilities: ["read"],
},

View File

@@ -114,7 +114,7 @@ Bridge (forward a topic between two meshes, v0.2.0)
Topic (conversation scope, v0.2.0)
claudemesh topic create <name> create a topic [--description --visibility]
claudemesh topic list list topics in the mesh
claudemesh topic list list topics across all meshes (or --mesh <slug>)
claudemesh topic join <topic> subscribe (via name or id)
claudemesh topic leave <topic> unsubscribe
claudemesh topic members <t> list topic subscribers
@@ -129,7 +129,7 @@ Topic (conversation scope, v0.2.0)
claudemesh me activity cross-mesh recent messages [--since=ISO]
claudemesh me search <q> cross-mesh search (topics + messages)
claudemesh member list mesh roster with online state [--online]
claudemesh notification list recent @-mentions of you [--since <ISO>]
claudemesh notification list @-mentions across all meshes (or --mesh <slug>)
Schedule (resource form)
claudemesh schedule msg <m> one-shot or recurring (alias: remind)
@@ -628,7 +628,17 @@ async function main(): Promise<void> {
};
const arg = positionals[1] ?? "";
if (sub === "create") { const { runTopicCreate } = await import("~/commands/topic.js"); process.exit(await runTopicCreate(arg, f)); }
else if (sub === "list") { const { runTopicList } = await import("~/commands/topic.js"); process.exit(await runTopicList(f)); }
else if (sub === "list") {
// v0.5.0: aggregate across every joined mesh when --mesh is omitted.
// The per-mesh runTopicList prompts for a mesh; me topics doesn't
// need one and surfaces last-activity + unread per topic.
if (!f.mesh) {
const { runMeTopics } = await import("~/commands/me.js");
process.exit(await runMeTopics({ mesh: undefined, json: f.json, unread: !!flags.unread }));
}
const { runTopicList } = await import("~/commands/topic.js");
process.exit(await runTopicList(f));
}
else if (sub === "join") { const { runTopicJoin } = await import("~/commands/topic.js"); process.exit(await runTopicJoin(arg, f)); }
else if (sub === "leave") { const { runTopicLeave } = await import("~/commands/topic.js"); process.exit(await runTopicLeave(arg, f)); }
else if (sub === "members") { const { runTopicMembers } = await import("~/commands/topic.js"); process.exit(await runTopicMembers(arg, f)); }
@@ -668,10 +678,22 @@ async function main(): Promise<void> {
since: flags.since as string,
};
if (sub === "list") {
// v0.5.0: aggregate across every joined mesh when --mesh is omitted.
if (!f.mesh) {
const { runMeNotifications } = await import("~/commands/me.js");
process.exit(
await runMeNotifications({
mesh: undefined,
json: f.json,
all: !!flags.all,
since: f.since,
}),
);
}
const { runNotificationList } = await import("~/commands/notification.js");
process.exit(await runNotificationList(f));
} else {
console.error("Usage: claudemesh notification list [--since <ISO>]");
console.error("Usage: claudemesh notification list [--mesh <slug>] [--since <ISO>] [--all]");
process.exit(EXIT.INVALID_ARGS);
}
break;