Skip to content

Commit b08e825

Browse files
authored
feat: support --open flag and template strings (#607)
* feat(token): optionally open example app in browser after creating * feat(room,token): support template strings * feat(docs): document `--open` flag and template strings * chore: fix lints * chore(room): support `--open` flag for `lk room join`
1 parent 97eea0c commit b08e825

File tree

10 files changed

+336
-89
lines changed

10 files changed

+336
-89
lines changed

README.md

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ make install
5858

5959
See `lk --help` for a complete list of subcommands. The `--help` flag can also be used on any subcommand for more information.
6060

61-
## Set up your project (new)
61+
## Set up your project
6262

6363
The quickest way to get started is to authenticate with your LiveKit Cloud account and link an existing project. If you haven't created an account or project yet, head [here](https://cloud.livekit.io) first. Then run the following:
6464

@@ -198,7 +198,8 @@ It's possible to publish from video streams coming over a TCP socket. `lk` can a
198198
Run `lk` like this:
199199
200200
```shell
201-
lk room join --identity bot \
201+
lk room join \
202+
--identity bot \
202203
--publish h264:///127.0.0.1:16400 \
203204
<room_name>
204205
```
@@ -285,12 +286,14 @@ Here's an example:
285286
```shell
286287
lk egress test-template \
287288
--base-url http://localhost:3000 \
288-
--room test-room --layout speaker --video-publishers 3
289+
--room test-room \
290+
--layout speaker \
291+
--video-publishers 3
289292
```
290293

291294
This command will launch a browser pointed at `http://localhost:3000`, while simulating 3 publishers publishing to your livekit instance.
292295

293-
## Load Testing
296+
## Load testing
294297

295298
Load testing utility for LiveKit. This tool is quite versatile and is able to simulate various types of load.
296299

@@ -302,7 +305,8 @@ This guide requires a LiveKit server instance to be set up. You can start a load
302305

303306
```shell
304307
lk load-test \
305-
--room test-room --video-publishers 8
308+
--room test-room \
309+
--video-publishers 8
306310
```
307311

308312
This simulates 8 video publishers to the room, with no subscribers. Video tracks are published with simulcast, at 720p, 360p, and 180p.
@@ -313,7 +317,8 @@ To test audio capabilities in your app, you can also simulate simultaneous speak
313317

314318
```shell
315319
lk load-test \
316-
--room test-room --audio-publishers 5
320+
--room test-room \
321+
--audio-publishers 5
317322
```
318323

319324
The above simulates 5 concurrent speakers, each playing back a pre-recorded audio sample at the same time.
@@ -325,10 +330,12 @@ Generate a token so you can log into the room:
325330
326331
```shell
327332
lk token create --join \
328-
--room test-room --identity test-user
333+
--room test-room \
334+
--identity test-user \
335+
--open meet
329336
```
330337
331-
Head over to the [example web client](https://meet.livekit.io/?tab=custom) and paste in the token, you can see the simulated tracks published by the load tester.
338+
This will open [Meet](https://meet.livekit.io/?tab=custom), a video conferencing example app, and join the simulated room. Alternatively, you can omit the `--open` parameter, visit the site and paste in the token yourself.
332339
333340
![Load tester screenshot](.github/load-test-screenshot.jpg?raw=true)
334341
@@ -400,9 +407,9 @@ You can customize various parameters of the test such as
400407
- `--layout`: layout to simulate (speaker, 3x3, 4x4, or 5x5)
401408
- `--simulate-speakers`: randomly rotate publishers to speak
402409

403-
### Agent Load Testing
410+
### Agent load testing
404411

405-
The agent load testing utility allows you to dispatch a running agent to a number of rooms and simulate a user in each room that would echo whatever the agent says.
412+
The agent load testing utility allows you to dispatch a running agent to a number of rooms and simulate a user in each room that would echo whatever the agent says.
406413

407414
> **Note**: Before running the test, ensure that:
408415
> - Your agent is already running using `start` instead of `dev` with the specified `agent_name` configured
@@ -426,6 +433,45 @@ The above simulates 5 concurrent rooms, where each room has:
426433
427434
Once the specified duration is over (or if the load test is manually stopped), the load test statistics will be displayed in the form of a table.
428435
436+
437+
## Additional notes
438+
439+
### Parameter precedence
440+
441+
CLI commands support various ways to set some parameters, including via environment variables, local configuration files, and command line flags. The precedence order is as follows:
442+
443+
1. Command line flags (e.g. `--api-key`, `--room`)
444+
2. Environment variables (e.g. `LIVEKIT_API_KEY`, `LIVEKIT_URL`)
445+
3. Local configuration files (by default `./livekit.toml`, override with `--config`)
446+
4. Default project configuration (set with `lk project set-default`)
447+
448+
If you have multiple projects configured, you can specify which project to use with the `--project` flag. This will override the default project for that command only.
449+
450+
### Template strings
451+
452+
Some command parameters support template strings, which are substituted with real values at runtime. This is useful for generating unique identities and room names, or tagging entities with timestamps and other metadata. Supported template strings include:
453+
454+
- `%t`: Compact timestamp (`"20250702150405"`)
455+
- `%T`: ISO 8601 timestamp (`"2025-07-02T15:04:05Z07:00"`)
456+
- `%Y`: Year (`"2025"`)
457+
- `%m`: Month (`"07"`)
458+
- `%d`: Day of the month (`"02"`)
459+
- `%H`: Hour (`"15"`)
460+
- `%M`: Minute (`"04"`)
461+
- `%S`: Second (`"05"`)
462+
- `%x`: Random 6-character hexadecimal string (`"a1b2c3"`)
463+
- `%U`: Current user (`"username"`)
464+
- `%h`: Current hostname (`"my-computer.local"`)
465+
- `%p`: Current PID (`"12345"`)
466+
467+
For example, you can use the following command to generate a token whose identity is your current `user@hostname`, and a room with a random suffix:
468+
469+
```shell
470+
lk token create --join \
471+
--identity "%U@%h" \
472+
--room "room-%x"
473+
```
474+
429475
<!--BEGIN_REPO_NAV-->
430476
<br/><table>
431477
<thead><tr><th colspan="2">LiveKit Ecosystem</th></tr></thead>

cmd/lk/agent.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,7 @@ var (
8989
Flags: []cli.Flag{
9090
secretsFlag,
9191
secretsFileFlag,
92-
&cli.BoolFlag{
93-
Name: "silent",
94-
Usage: "If set, will not prompt for confirmation",
95-
Required: false,
96-
Value: false,
97-
},
92+
silentFlag,
9893
},
9994
ArgsUsage: "[working-dir]",
10095
},

cmd/lk/app.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,17 @@ var (
141141
)
142142

143143
func requireProject(ctx context.Context, cmd *cli.Command) (context.Context, error) {
144+
return requireProjectWithOpts(ctx, cmd)
145+
}
146+
147+
func requireProjectWithOpts(ctx context.Context, cmd *cli.Command, opts ...loadOption) (context.Context, error) {
144148
var err error
145149
if project != nil {
146150
return ctx, nil
147151
}
148-
if _, err = loadProjectConfig(ctx, cmd); err != nil {
152+
if ctx, err = loadProjectConfig(ctx, cmd); err != nil {
149153
// something is wrong with CLI config file
150-
return nil, err
154+
return ctx, err
151155
}
152156
if project, err = loadProjectDetails(cmd); err != nil {
153157
// something is wrong with project config file
@@ -158,7 +162,7 @@ func requireProject(ctx context.Context, cmd *cli.Command) (context.Context, err
158162
return selectProject(ctx, cmd)
159163
}
160164

161-
return nil, err
165+
return ctx, err
162166
}
163167

164168
func selectProject(ctx context.Context, cmd *cli.Command) (context.Context, error) {

cmd/lk/room.go

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/urfave/cli/v3"
2929
"google.golang.org/protobuf/encoding/protojson"
3030

31+
"github.com/livekit/protocol/auth"
3132
"github.com/livekit/protocol/livekit"
3233
"github.com/livekit/protocol/logger"
3334
lksdk "github.com/livekit/server-sdk-go/v2"
@@ -143,8 +144,9 @@ var (
143144
Action: joinRoom,
144145
ArgsUsage: "ROOM_NAME",
145146
Flags: []cli.Flag{
146-
identityFlag,
147-
hidden(optional(roomFlag)),
147+
optional(identityFlag),
148+
optional(roomFlag),
149+
openFlag,
148150
&cli.BoolFlag{
149151
Name: "publish-demo",
150152
Usage: "Publish demo video as a loop",
@@ -209,6 +211,7 @@ var (
209211
Action: getParticipant,
210212
Flags: []cli.Flag{
211213
roomFlag,
214+
optional(identityFlag),
212215
},
213216
},
214217
{
@@ -219,14 +222,14 @@ var (
219222
Action: removeParticipant,
220223
Flags: []cli.Flag{
221224
roomFlag,
225+
optional(identityFlag),
222226
},
223227
},
224228
{
225-
Name: "forward",
226-
Usage: "Forward a participant to a different room",
227-
ArgsUsage: "ROOM_NAME",
228-
Before: createRoomClient,
229-
Action: forwardParticipant,
229+
Name: "forward",
230+
Usage: "Forward a participant to a different room",
231+
Before: createRoomClient,
232+
Action: forwardParticipant,
230233
Flags: []cli.Flag{
231234
roomFlag,
232235
identityFlag,
@@ -237,11 +240,10 @@ var (
237240
},
238241
},
239242
{
240-
Name: "move",
241-
Usage: "Move a participant to a different room",
242-
ArgsUsage: "ROOM_NAME",
243-
Before: createRoomClient,
244-
Action: moveParticipant,
243+
Name: "move",
244+
Usage: "Move a participant to a different room",
245+
Before: createRoomClient,
246+
Action: moveParticipant,
245247
Flags: []cli.Flag{
246248
roomFlag,
247249
identityFlag,
@@ -259,6 +261,7 @@ var (
259261
Action: updateParticipant,
260262
Flags: []cli.Flag{
261263
roomFlag,
264+
optional(identityFlag),
262265
&cli.StringFlag{
263266
Name: "metadata",
264267
Usage: "JSON describing participant metadata (existing values for unset fields)",
@@ -583,12 +586,12 @@ var (
583586
)
584587

585588
func createRoomClient(ctx context.Context, cmd *cli.Command) (context.Context, error) {
586-
pc, err := loadProjectDetails(cmd)
589+
_, err := requireProject(ctx, cmd)
587590
if err != nil {
588591
return nil, err
589592
}
590593

591-
roomClient = lksdk.NewRoomServiceClient(pc.URL, pc.APIKey, pc.APISecret, withDefaultClientOpts(pc)...)
594+
roomClient = lksdk.NewRoomServiceClient(project.URL, project.APIKey, project.APISecret, withDefaultClientOpts(project)...)
592595
return nil, nil
593596
}
594597

@@ -832,7 +835,7 @@ func joinRoom(ctx context.Context, cmd *cli.Command) error {
832835
}
833836
}
834837

835-
pc, err := loadProjectDetails(cmd)
838+
_, err := requireProject(ctx, cmd)
836839
if err != nil {
837840
return err
838841
}
@@ -842,9 +845,13 @@ func joinRoom(ctx context.Context, cmd *cli.Command) error {
842845
return err
843846
}
844847

845-
autoSubscribe := cmd.Bool("auto-subscribe")
846-
847848
participantIdentity := cmd.String("identity")
849+
if participantIdentity == "" {
850+
participantIdentity = util.ExpandTemplate("participant-%x")
851+
fmt.Printf("Using generated participant identity [%s]\n", util.Accented(participantIdentity))
852+
}
853+
854+
autoSubscribe := cmd.Bool("auto-subscribe")
848855

849856
done := make(chan os.Signal, 1)
850857
roomCB := &lksdk.RoomCallback{
@@ -961,9 +968,9 @@ func joinRoom(ctx context.Context, cmd *cli.Command) error {
961968
}
962969
}
963970

964-
room, err := lksdk.ConnectToRoom(pc.URL, lksdk.ConnectInfo{
965-
APIKey: pc.APIKey,
966-
APISecret: pc.APISecret,
971+
room, err := lksdk.ConnectToRoom(project.URL, lksdk.ConnectInfo{
972+
APIKey: project.APIKey,
973+
APISecret: project.APISecret,
967974
RoomName: roomName,
968975
ParticipantIdentity: participantIdentity,
969976
ParticipantAttributes: participantAttributes,
@@ -1045,6 +1052,20 @@ func joinRoom(ctx context.Context, cmd *cli.Command) error {
10451052
}
10461053
}
10471054

1055+
if cmd.IsSet("open") {
1056+
switch cmd.String("open") {
1057+
case string(util.OpenTargetMeet):
1058+
at := auth.NewAccessToken(project.APIKey, project.APISecret).
1059+
SetIdentity(participantIdentity + "_observer").
1060+
SetVideoGrant(&auth.VideoGrant{
1061+
Room: roomName,
1062+
RoomJoin: true,
1063+
})
1064+
token, _ := at.ToJWT()
1065+
_ = util.OpenInMeet(project.URL, token)
1066+
}
1067+
}
1068+
10481069
<-done
10491070
return nil
10501071
}

cmd/lk/sip.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ var (
297297
Action: createSIPParticipant,
298298
ArgsUsage: RequestDesc[livekit.CreateSIPParticipantRequest](),
299299
Flags: []cli.Flag{
300+
optional(roomFlag),
301+
optional(identityFlag),
300302
&cli.StringFlag{
301303
Name: "trunk",
302304
Usage: "`SIP_TRUNK_ID` to use for the call (overrides json config)",
@@ -309,14 +311,6 @@ var (
309311
Name: "call",
310312
Usage: "`SIP_CALL_TO` number to use (overrides json config)",
311313
},
312-
&cli.StringFlag{
313-
Name: "room",
314-
Usage: "`ROOM_NAME` to place the call to (overrides json config)",
315-
},
316-
&cli.StringFlag{
317-
Name: "identity",
318-
Usage: "`PARTICIPANT_IDENTITY` to use (overrides json config)",
319-
},
320314
&cli.StringFlag{
321315
Name: "name",
322316
Usage: "`PARTICIPANT_NAME` to use (overrides json config)",

0 commit comments

Comments
 (0)