Skip to content

feat(analytics): show session detail and list participants under --experimental-auth - #1018

Merged
Topherhindman merged 9 commits into
mainfrom
devx-793-cli-session-participants
Oct 10, 2026
Merged

Topherhindman merged 9 commits into
mainfrom
devx-793-cli-session-participants

Conversation

@Topherhindman

@Topherhindman Topherhindman commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes DEVX-793
Based on main.
Depends on livekit/public-api-server#48.

Under --experimental-auth, lk analytics session get now prints the session detail the Public API returns: totals, a summary of each timeline, and the first page of participants. The new lk analytics session participant list pages through the rest. This PR also adds the pieces the later session reads in this stack share: PageOptions, pageFlags and sessionRead.

What changed

  • Regenerated client (chore(public)): oapi.gen.go is regenerated from livekit/public-api-server@1124a4d. It picks up the session detail's participants page, connection seconds (was connection minutes), the session detail losing room_id, ListSessionParticipants' sort_by and sort_order, and the removal of the transcript recording file type. Nothing hand-written reads the changed types, so only oapi.gen.go changes.
  • Client (feat(public)):
    • GetSession returns the detail next to the list row. The detail is nil while the server is still finalizing it.
    • ListSessionParticipants returns one page of a session's participants and the cursor for the next. ParticipantListOptions takes friendly sort names (joined or left, asc or desc), like SessionListOptions. Its Validate rejects a negative limit or an unknown name before any request is sent.
    • PageOptions holds a page's limit and cursor, with its own negative-limit check and query params, so every session read that pages can share it.
  • Command (feat(analytics)):
    • session get prints the list row, then the totals (bandwidth in and out, connection time), one row per timeline (points, and average and peak over non-empty buckets), and the first page of participants. --json prints the API's own {session, detail} response, every timeline point included. A session whose detail is still being finalized prints its row and says so.
    • When more participants remain, session get prints a hint with the full lk analytics session participant list command and the detail's cursor. When the detail couldn't read them, the hint names the listing alone. Both say to use the same flags as session get, which carry --experimental-auth and --project.
    • session participant list SESSION_ID pages a session's participants, one row per identity. --json prints {items, nextCursor}.
    • sessionRead builds the action of a session read that only the Public API serves. It refuses to run without --experimental-auth before anything else. Then it reads SESSION_ID and the options, so a bad flag fails before the project lookup. pageFlags and pageOptions declare and read --limit and --cursor.
    • A permission denial says the user doesn't have access to the project. cloudAPIError would suggest API-key credentials, which this read can't use.

Usage

lk --experimental-auth analytics --experimental session get SESSION_ID [--json]
lk --experimental-auth analytics --experimental session participant list SESSION_ID \
    [--sort-by joined|left] [--sort-order asc|desc] [--limit N] [--json]
  • Participant list: newest join first by default. --limit defaults to 50, and the server caps a page at 100.
  • Paging: --cursor is hidden. A page with more after it prints a hint to re-run with its --cursor.
  • Auth: without --experimental-auth, session participant list fails with this command is only available under --experimental-auth (user-based auth). session get keeps using the API-key endpoint there.

session get prints the totals, then the timelines and participants, and ends with the hint when more participants remain:

┌──────────────┬───────────────┬─────────────────┐
│ Bandwidth In │ Bandwidth Out │ Connection Time │
├──────────────┼───────────────┼─────────────────┤
│ 1.5 MB       │ 2.5 KB        │ 1h2m3s          │
└──────────────┴───────────────┴─────────────────┘
...
More participants available — list them with lk analytics session participant list RM_1 --cursor c2, using the same flags as this command

Since review

  • 8fc74ad fix(analytics): print the participant list hint as a full lk command
  • 7f2b295 fix(analytics): strip terminal escapes from participant identities and names
  • ca73a04 fix(analytics): say a participant page is empty only when the server would
  • 1b3665b fix(analytics): name the project when a participant list isn't found
  • af22fee fix(analytics): strip terminal escapes from room names

Testing

  • go build ./..., go vet ./pkg/... ./cmd/lk/ and go test ./pkg/... ./cmd/lk/ pass on every commit.
  • New tests:
    • pkg/public: TestGetSessionReturnsDetail (the detail, and nil while finalizing), TestGetSessionMissingSession, TestListSessionParticipantsQuery and TestListSessionParticipantsRejectsBadOptions.
    • pkg/public/render: TestSessionDetailText, TestSessionDetailTextLastParticipantsPage, TestSessionDetailTextParticipantsUnread, TestSessionDetailTextFinalizing, TestSessionDetailJSON and TestSessionParticipantsPage.
    • cmd/lk: TestParticipantListOptions, TestParticipantListRequiresExperimentalAuth, TestFetchSessionParticipants, TestFetchSessionParticipantsErrors and TestSessionAPIError.
  • The commands above were run on staging on 2026-10-08, against public-api-server's stack run locally, with a real sign-in and staging data.

Before merge

Not in this PR

  • API-key mode: the API-key analytics endpoint has no participant listing.
  • Participant sessions: one row per connection, with the client each connected from, come in DEVX-800 at the top of this stack.

@Topherhindman
Topherhindman force-pushed the devx-793-cli-session-participants branch from fed8275 to 4859532 Compare October 9, 2026 02:57
Base automatically changed from public-api-session-filters to main October 9, 2026 03:20
@Topherhindman
Topherhindman force-pushed the devx-793-cli-session-participants branch from 4859532 to 0800de1 Compare October 9, 2026 05:10
@Topherhindman
Topherhindman marked this pull request as ready for review October 9, 2026 06:27
Picks up the session detail's participants page and connection seconds
(was connection minutes), ListSessionParticipants' sort_by and
sort_order, the split of ParticipantInfo's per-connection fields into
ParticipantDetail, and the removal of the transcript recording file
type.

Also picks up the session detail losing room_id, ParticipantInfo losing
is_active, the agent endpoint token grant, and doc comments: each
participant field's source, and Duration's wire format ("86400s", not
ISO 8601).

Nothing hand-written reads the changed types, so only oapi.gen.go
changes.

Generated from livekit/public-api-server@1124a4d
GetSession dropped the response's detail, so `lk analytics session get`
printed only the list row. It now returns the detail too, and the
command prints the totals (bandwidth and connection time), a summary of
each timeline (points, average and peak over non-empty buckets), and the
first page of participants. --json emits the API's own {session,
detail} response, every timeline point included.

A session whose detail is still being finalized prints its row and says
so. When more participants remain, a hint names `session participant
list` with the detail's cursor; when the detail couldn't read them, it
names the listing alone. Both say to run it with the same flags as this
command, which carry --experimental-auth and --project.
ListSessionParticipants returns one page of a session's participants and
the cursor for the next. ParticipantListOptions takes friendly sort names
("joined" or "left", "asc" or "desc") like SessionListOptions, and its
Validate rejects a negative limit or an unknown name before any request
is sent.

The page limit and cursor are a PageOptions, with its own negative-limit
check and the query params, so every session read that pages can share
them.
`lk analytics session participant list SESSION_ID` pages a session's
participants, one row per identity, with --limit (default 50),
--sort-by (joined or left), --sort-order and a hidden --cursor; the
cursor `session get` prints continues from its first page. Text by
default, with a hint to re-run with --cursor for the next page, and
{items, nextCursor} with --json.

The API-key analytics endpoint has no participant listing, so the
command runs only under --experimental-auth, calls the Public API with
the signed-in user's session token, and refuses to run otherwise before
reading its arguments. sessionRead builds that action from the
command's options reader and fetch function: the gate, the SESSION_ID
argument, the options, so a bad flag fails before the project lookup,
then the client and the project. pageFlags and pageOptions declare and
read --limit and --cursor.

A permission denial says the user doesn't have access to the project.
cloudAPIError would suggest API-key credentials, which this read can't
use.
The session detail's hints named `session participant list` without the
`lk analytics` prefix, so they couldn't be run as printed. They now print
the full command, like other lk hints (`lk skills update`).
…d names

A participant chooses its own identity and name, and the session detail
and the participant list printed them as they came. In a terminal an
escape sequence in one, such as \x1b]0;pwned\x07 or \x1b[2J, set the
window title or cleared the screen; only output that wasn't a terminal
had them stripped.

stripControls removes the control characters a terminal acts on: C0
controls except tab and newline, DEL, and C1 controls, so no ESC, 8-bit
CSI or OSC is left to start a sequence. The participant table runs each
identity and name through it. --json needs nothing: JSON escapes control
characters itself.
…would

A page with no participants but a next cursor printed "No participants
found" and then "More participants available", in both the participant
list and the session detail. The server counts a page as empty only with
no items and no next cursor; renderParticipants now does too, so the
empty text never sits beside a hint that there's more.
A NotFound from the participant list fell through to cloudAPIError and
printed the server's message alone. The API answers an unknown session
and a mistyped --project the same way, so sessionReadError now says
"no session RM_... in project p_..." for it. IsNotFound recognizes the
NotFound.
@Topherhindman
Topherhindman force-pushed the devx-793-cli-session-participants branch from 0800de1 to 1b3665b Compare October 9, 2026 20:26
Whoever creates a room chooses its name, and the session list and the
session detail printed it as it came. In a terminal an escape sequence
in one, such as \x1b]0;pwned\x07 or \x1b[2J, set the window title or
cleared the screen. sessionRow now runs the room name through dashText,
as the participant table does with identities and names.

A session's tags aren't printed in text mode, so they need nothing; the
test carries escapes in them too, so a tags column can't add them back
unnoticed. --json escapes both itself.
@Topherhindman
Topherhindman merged commit baedf03 into main Oct 10, 2026
25 checks passed
@Topherhindman
Topherhindman deleted the devx-793-cli-session-participants branch October 10, 2026 03:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants