@irtio/cli 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/api-keys-UTLYMZYN.js +222 -0
  2. package/dist/api.js +1 -1
  3. package/dist/{chunk-3HQMVCYA.js → chunk-BQBOBFBO.js} +4 -4
  4. package/dist/{chunk-RNAH5T4W.js → chunk-IDF46P7R.js} +2 -0
  5. package/dist/{chunk-DKWG7MGO.js → chunk-JL235KIE.js} +1 -1
  6. package/dist/{chunk-ZD4ND6X6.js → chunk-OCVALOGK.js} +1 -1
  7. package/dist/{chunk-RQSJZWQC.js → chunk-WFMRNGO5.js} +134 -105
  8. package/dist/{delete-project-VENS2B44.js → delete-project-MXUYNGAO.js} +2 -2
  9. package/dist/deploy.d.ts +3 -1
  10. package/dist/deploy.js +163 -5
  11. package/dist/{dev-QM26ONKS.js → dev-AUZ4OLA3.js} +139 -10
  12. package/dist/index.js +39 -15
  13. package/dist/init.js +2 -2
  14. package/dist/{keys-JHLMEGRA.js → keys-NXRIBJZP.js} +3 -3
  15. package/dist/{leaderboard-SYPSBPS3.js → leaderboard-ZBJBKETM.js} +65 -11
  16. package/dist/{login-2M73HBZT.js → login-3RVN5PPN.js} +2 -2
  17. package/dist/{logs-2W7CPZO5.js → logs-AT7G6YRH.js} +3 -3
  18. package/dist/{migrate-T3DZJREY.js → migrate-FMXTRVUV.js} +3 -3
  19. package/dist/{ratings-VG32WFDG.js → ratings-XBLX2MUW.js} +3 -3
  20. package/dist/{rollback-SO74MVZV.js → rollback-LI4TDLQA.js} +3 -3
  21. package/dist/{rooms-VI33P4RA.js → rooms-52Q5KBUS.js} +179 -20
  22. package/dist/simulate.d.ts +115 -10
  23. package/dist/simulate.js +270 -36
  24. package/dist/{static-deploy-KOWFKWZA.js → static-deploy-7UCYINJB.js} +5 -5
  25. package/dist/{status-HF3ZEKB7.js → status-JZGKH2P6.js} +3 -3
  26. package/dist/{usage-4G23QXCH.js → usage-7S447INI.js} +3 -3
  27. package/dist/{whoami-KTMTQNHM.js → whoami-UFSWPWK6.js} +2 -2
  28. package/package.json +7 -7
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  readProjectConfig
3
- } from "./chunk-DKWG7MGO.js";
3
+ } from "./chunk-JL235KIE.js";
4
4
  import {
5
5
  HelpRequested,
6
6
  helpFor,
7
7
  helpRequested
8
- } from "./chunk-ZD4ND6X6.js";
8
+ } from "./chunk-OCVALOGK.js";
9
9
  import {
10
10
  createApiClient,
11
11
  isLoginRequired
12
- } from "./chunk-RNAH5T4W.js";
12
+ } from "./chunk-IDF46P7R.js";
13
13
  import {
14
14
  resolveControlUrlForUser
15
15
  } from "./chunk-UPHQM6NZ.js";
@@ -33,6 +33,9 @@ delete remove a whole board, or one player's row with --player. Needs your log
33
33
 
34
34
  options:
35
35
  --limit <n> how many rows (default 10, max 100)
36
+ --period <key> read a past period (2026-09-03, 2026-W36, 2026-09, or a season
37
+ name). Default: whichever period the board is in now
38
+ --bucket <name> which cohort to read, on a board configured with buckets
36
39
  --cursor <c> continue from a previous page's nextCursor
37
40
  --all page through the whole board, following cursors
38
41
  --around <player> show the rows around one player instead of the top, with their rank
@@ -63,6 +66,23 @@ function parseLeaderboardArgs(args) {
63
66
  case "--all":
64
67
  parsed.all = true;
65
68
  break;
69
+ // ---- M6 lane B: boards ----
70
+ case "--period": {
71
+ const value = args[++i];
72
+ if (value === void 0 || value === "") {
73
+ throw new Error("irtio leaderboard: --period needs a period key, e.g. 2026-W36");
74
+ }
75
+ parsed.period = value;
76
+ break;
77
+ }
78
+ case "--bucket": {
79
+ const value = args[++i];
80
+ if (value === void 0 || value === "") {
81
+ throw new Error("irtio leaderboard: --bucket needs a bucket name, e.g. eu");
82
+ }
83
+ parsed.bucket = value;
84
+ break;
85
+ }
66
86
  case "--yes":
67
87
  parsed.yes = true;
68
88
  break;
@@ -125,7 +145,12 @@ function parseLeaderboardArgs(args) {
125
145
  }
126
146
  function formatLeaderboard(body, around) {
127
147
  const direction = body.direction === "lower" ? "lower is better" : "higher is better";
128
- const out = [pc.dim(`board ${body.board} \xB7 ${direction}`), ""];
148
+ const scope = [
149
+ body.period === void 0 || body.period === "" ? void 0 : `period ${body.period}`,
150
+ body.bucket === void 0 || body.bucket === "" ? void 0 : `bucket ${body.bucket}`
151
+ ].filter((part) => part !== void 0);
152
+ const header = [`board ${body.board}`, ...scope, direction].join(" \xB7 ");
153
+ const out = [pc.dim(header), ""];
129
154
  if (body.entries.length === 0) {
130
155
  out.push(
131
156
  pc.dim(
@@ -150,20 +175,29 @@ async function runLeaderboard(options) {
150
175
  const base = `${controlUrl}/v1/leaderboard/${encodeURIComponent(
151
176
  options.project
152
177
  )}/${encodeURIComponent(options.board)}`;
178
+ const scoped = () => {
179
+ const query = new URLSearchParams();
180
+ if (options.period !== void 0) query.set("period", options.period);
181
+ if (options.bucket !== void 0) query.set("bucket", options.bucket);
182
+ return query;
183
+ };
184
+ const withQuery = (url, query) => {
185
+ const q = query.toString();
186
+ return `${url}${q === "" ? "" : `?${q}`}`;
187
+ };
153
188
  if (options.around !== void 0) {
154
189
  const body2 = await readPublicJson(
155
190
  fetchImpl,
156
- `${base}/around/${encodeURIComponent(options.around)}`
191
+ withQuery(`${base}/around/${encodeURIComponent(options.around)}`, scoped())
157
192
  );
158
193
  for (const line of formatLeaderboard(body2, options.around)) log(line);
159
194
  return body2;
160
195
  }
161
196
  const pageUrl = (cursor) => {
162
- const query = new URLSearchParams();
197
+ const query = scoped();
163
198
  if (options.limit !== void 0) query.set("limit", String(options.limit));
164
199
  if (cursor !== void 0) query.set("cursor", cursor);
165
- const q = query.toString();
166
- return `${base}/top${q === "" ? "" : `?${q}`}`;
200
+ return withQuery(`${base}/top`, query);
167
201
  };
168
202
  let body = await readPublicJson(fetchImpl, pageUrl(options.cursor));
169
203
  const entries = [...body.entries];
@@ -210,10 +244,22 @@ function formatBoardList(boards) {
210
244
  ];
211
245
  }
212
246
  const nameWidth = Math.max(5, ...boards.map((b) => b.board.length));
213
- const out = [pc.dim(`${"BOARD".padEnd(nameWidth)} ${"ROWS".padStart(7)} DIRECTION`)];
247
+ const rotates = boards.some((b) => b.period !== void 0 || b.buckets === true);
248
+ const periodOf = (b) => b.period === void 0 ? "all-time" : `${b.period}${b.currentPeriod === void 0 ? "" : ` (${b.currentPeriod})`}`;
249
+ const periodWidth = rotates ? Math.max(6, ...boards.map((b) => periodOf(b).length)) : 0;
250
+ const head = rotates ? `${"BOARD".padEnd(nameWidth)} ${"ROWS".padStart(7)} ${"PERIOD".padEnd(periodWidth)} DIRECTION` : `${"BOARD".padEnd(nameWidth)} ${"ROWS".padStart(7)} DIRECTION`;
251
+ const out = [pc.dim(head)];
214
252
  for (const b of boards) {
215
- const note = b.configured && b.rows === 0 ? pc.dim(" (configured, no rows yet)") : "";
216
- out.push(`${b.board.padEnd(nameWidth)} ${String(b.rows).padStart(7)} ${b.direction}${note}`);
253
+ const notes = [
254
+ b.buckets === true ? "buckets" : void 0,
255
+ b.keep === void 0 ? void 0 : `keeps ${b.keep}`,
256
+ b.configured && b.rows === 0 ? "configured, no rows yet" : void 0
257
+ ].filter((n) => n !== void 0);
258
+ const note = notes.length === 0 ? "" : pc.dim(` (${notes.join(", ")})`);
259
+ const scope = rotates ? `${periodOf(b).padEnd(periodWidth)} ` : "";
260
+ out.push(
261
+ `${b.board.padEnd(nameWidth)} ${String(b.rows).padStart(7)} ${scope}${b.direction}${note}`
262
+ );
217
263
  }
218
264
  return out;
219
265
  }
@@ -312,6 +358,12 @@ async function leaderboard(args, deps = {}) {
312
358
  if (parsed.cursor !== void 0 && parsed.all === true) {
313
359
  throw new Error("irtio leaderboard: --cursor and --all are two ways to do the same thing");
314
360
  }
361
+ if (parsed.period !== void 0 && parsed.action !== void 0) {
362
+ throw new Error("irtio leaderboard: --period is for a read, not for list or delete");
363
+ }
364
+ if (parsed.bucket !== void 0 && parsed.action !== void 0) {
365
+ throw new Error("irtio leaderboard: --bucket is for a read, not for list or delete");
366
+ }
315
367
  if (parsed.around !== void 0 && (parsed.cursor !== void 0 || parsed.all === true)) {
316
368
  throw new Error("irtio leaderboard: --around does not page; it is already a window");
317
369
  }
@@ -323,6 +375,8 @@ async function leaderboard(args, deps = {}) {
323
375
  ...parsed.around !== void 0 ? { around: parsed.around } : {},
324
376
  ...parsed.cursor !== void 0 ? { cursor: parsed.cursor } : {},
325
377
  ...parsed.all === true ? { all: true } : {},
378
+ ...parsed.period !== void 0 ? { period: parsed.period } : {},
379
+ ...parsed.bucket !== void 0 ? { bucket: parsed.bucket } : {},
326
380
  ...parsed.url !== void 0 ? { controlUrl: parsed.url } : {}
327
381
  });
328
382
  } catch (err) {
@@ -2,8 +2,8 @@ import {
2
2
  HelpRequested,
3
3
  helpFor,
4
4
  helpRequested
5
- } from "./chunk-ZD4ND6X6.js";
6
- import "./chunk-RNAH5T4W.js";
5
+ } from "./chunk-OCVALOGK.js";
6
+ import "./chunk-IDF46P7R.js";
7
7
  import {
8
8
  credentialsPath,
9
9
  resolveControlUrl,
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  readProjectConfig
3
- } from "./chunk-DKWG7MGO.js";
3
+ } from "./chunk-JL235KIE.js";
4
4
  import {
5
5
  HelpRequested,
6
6
  helpFor,
7
7
  helpRequested
8
- } from "./chunk-ZD4ND6X6.js";
8
+ } from "./chunk-OCVALOGK.js";
9
9
  import {
10
10
  createApiClient,
11
11
  isLoginRequired
12
- } from "./chunk-RNAH5T4W.js";
12
+ } from "./chunk-IDF46P7R.js";
13
13
  import {
14
14
  resolveControlUrlForUser
15
15
  } from "./chunk-UPHQM6NZ.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readProjectConfig
3
- } from "./chunk-DKWG7MGO.js";
3
+ } from "./chunk-JL235KIE.js";
4
4
  import {
5
5
  bundleRoom
6
6
  } from "./chunk-OTSFRVJN.js";
@@ -8,11 +8,11 @@ import {
8
8
  HelpRequested,
9
9
  helpFor,
10
10
  helpRequested
11
- } from "./chunk-ZD4ND6X6.js";
11
+ } from "./chunk-OCVALOGK.js";
12
12
  import {
13
13
  createApiClient,
14
14
  isLoginRequired
15
- } from "./chunk-RNAH5T4W.js";
15
+ } from "./chunk-IDF46P7R.js";
16
16
  import {
17
17
  resolveControlUrlForUser
18
18
  } from "./chunk-UPHQM6NZ.js";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  readProjectConfig
3
- } from "./chunk-DKWG7MGO.js";
3
+ } from "./chunk-JL235KIE.js";
4
4
  import {
5
5
  HelpRequested,
6
6
  helpFor,
7
7
  helpRequested
8
- } from "./chunk-ZD4ND6X6.js";
8
+ } from "./chunk-OCVALOGK.js";
9
9
  import {
10
10
  createApiClient,
11
11
  isLoginRequired
12
- } from "./chunk-RNAH5T4W.js";
12
+ } from "./chunk-IDF46P7R.js";
13
13
  import {
14
14
  resolveControlUrlForUser
15
15
  } from "./chunk-UPHQM6NZ.js";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  readProjectConfig
3
- } from "./chunk-DKWG7MGO.js";
3
+ } from "./chunk-JL235KIE.js";
4
4
  import {
5
5
  HelpRequested,
6
6
  helpFor,
7
7
  helpRequested
8
- } from "./chunk-ZD4ND6X6.js";
8
+ } from "./chunk-OCVALOGK.js";
9
9
  import {
10
10
  createApiClient,
11
11
  isLoginRequired
12
- } from "./chunk-RNAH5T4W.js";
12
+ } from "./chunk-IDF46P7R.js";
13
13
  import {
14
14
  resolveControlUrlForUser
15
15
  } from "./chunk-UPHQM6NZ.js";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  readProjectConfig
3
- } from "./chunk-DKWG7MGO.js";
3
+ } from "./chunk-JL235KIE.js";
4
4
  import {
5
5
  HelpRequested,
6
6
  helpFor,
7
7
  helpRequested
8
- } from "./chunk-ZD4ND6X6.js";
8
+ } from "./chunk-OCVALOGK.js";
9
9
  import {
10
10
  createApiClient,
11
11
  isLoginRequired
12
- } from "./chunk-RNAH5T4W.js";
12
+ } from "./chunk-IDF46P7R.js";
13
13
  import {
14
14
  resolveControlUrlForUser
15
15
  } from "./chunk-UPHQM6NZ.js";
@@ -19,27 +19,42 @@ import "fs";
19
19
  import "fs/promises";
20
20
  import "path";
21
21
  import pc from "picocolors";
22
- var USAGE = `usage: irtio rooms [options]
22
+ var USAGE = `usage: irtio rooms [--active|--idle] [--idle-longer-than <d>] [options]
23
+ irtio rooms get <room> [options]
24
+ irtio rooms set <room> --retention <d|forever|clear> [options]
23
25
  irtio rooms saves <room> [options]
24
26
  irtio rooms restore <room> --save <id> [options]
25
- irtio rooms delete <room> [options]
27
+ irtio rooms delete <room> [--force] [options]
26
28
 
27
- Lists a project's rooms, with the retention window each one's type declares. Rows not reported for
28
- longer than the control plane's row-pruning window (30 days by default) are pruned; that prunes the
29
- listing only and never a room's stored state.
29
+ Lists a project's rooms, with the retention window in force for each one and when it will be
30
+ deleted. Rows not reported for longer than the control plane's row-pruning window (30 days by
31
+ default) are pruned; that prunes the listing only and never a room's stored state.
30
32
 
31
33
  subcommands:
34
+ get <room> one room in full: its type and size class, how many save generations
35
+ it holds, how big its stored state is, and the exact deletion time
36
+ set <room> --retention <d|forever|clear>
37
+ override the retention window for THIS room. A duration like 30m, 6h
38
+ or 7d keeps it that long past its last activity; forever keeps it
39
+ indefinitely; clear goes back to what its type declares
32
40
  saves <room> list a room's save generations, newest first
33
41
  restore <room> --save <id>
34
42
  bring a room back from a save. This DISCARDS its current state, and
35
43
  stops the tenant if it is live
36
44
  delete <room> delete a room's stored state: its snapshot, its saves and its alarms.
37
- This is permanent. Refused while the room is awake
45
+ This is permanent. Refused while the room is awake unless --force
46
+
47
+ list filters:
48
+ --active only rooms that are awake in a live tenant
49
+ --idle only rooms that are not
50
+ --idle-longer-than <d>
51
+ only rooms whose last report is older than a duration like 30m, 6h or 7d
38
52
 
39
53
  options:
54
+ --force with delete: disconnect the room's clients first, then delete it
40
55
  --project <id> project id (default: the project file)
41
56
  -c, --config <f> the project file to read (default irtio.json)
42
- --url <control> control plane (default: your stored login)
57
+ --url <control> control plane (default: your stored login, or IRT_API_KEY)
43
58
  -h, --help print this
44
59
  `;
45
60
  function parseRoomsArgs(args) {
@@ -47,7 +62,7 @@ function parseRoomsArgs(args) {
47
62
  const parsed = { sub: "list" };
48
63
  let rest = args;
49
64
  const first = args[0];
50
- if (first === "saves" || first === "restore" || first === "delete") {
65
+ if (first === "saves" || first === "restore" || first === "delete" || first === "get" || first === "set") {
51
66
  parsed.sub = first;
52
67
  const room = args[1];
53
68
  if (room === void 0 || room.startsWith("-")) {
@@ -65,6 +80,33 @@ function parseRoomsArgs(args) {
65
80
  parsed.save = value;
66
81
  break;
67
82
  }
83
+ // ---- M6 lane A: rooms API ----
84
+ case "--active":
85
+ parsed.active = true;
86
+ break;
87
+ case "--idle":
88
+ parsed.active = false;
89
+ break;
90
+ case "--idle-longer-than": {
91
+ const value = rest[++i];
92
+ if (value === void 0) {
93
+ throw new Error("irtio rooms: --idle-longer-than needs a duration like 30m, 6h or 7d");
94
+ }
95
+ parsed.idleLongerThan = value;
96
+ break;
97
+ }
98
+ case "--retention": {
99
+ const value = rest[++i];
100
+ if (value === void 0) {
101
+ throw new Error("irtio rooms: --retention needs a duration, forever, or clear");
102
+ }
103
+ parsed.retention = value;
104
+ break;
105
+ }
106
+ case "--force":
107
+ parsed.force = true;
108
+ break;
109
+ // ---- end M6 lane A ----
68
110
  case "--project": {
69
111
  const value = rest[++i];
70
112
  if (value === void 0) throw new Error("irtio rooms: --project needs a value");
@@ -94,6 +136,23 @@ function parseRoomsArgs(args) {
94
136
  "irtio rooms restore: --save <id> is required \u2014 run `irtio rooms saves <room>` to see them"
95
137
  );
96
138
  }
139
+ if (parsed.sub === "set" && parsed.retention === void 0) {
140
+ throw new Error(
141
+ "irtio rooms set: --retention <d|forever|clear> is required. A duration like 7d keeps this room that long past its last activity, forever keeps it indefinitely, and clear goes back to what its type declares"
142
+ );
143
+ }
144
+ if (parsed.active !== void 0 && parsed.sub !== "list") {
145
+ throw new Error("irtio rooms: --active and --idle are filters for the listing");
146
+ }
147
+ if (parsed.idleLongerThan !== void 0 && parsed.sub !== "list") {
148
+ throw new Error("irtio rooms: --idle-longer-than is a filter for the listing");
149
+ }
150
+ if (parsed.force === true && parsed.sub !== "delete") {
151
+ throw new Error("irtio rooms: --force belongs to `irtio rooms delete`");
152
+ }
153
+ if (parsed.retention !== void 0 && parsed.sub !== "set") {
154
+ throw new Error("irtio rooms: --retention belongs to `irtio rooms set`");
155
+ }
97
156
  return parsed;
98
157
  }
99
158
  function formatBytes(bytes) {
@@ -114,16 +173,60 @@ function formatAge(iso, now) {
114
173
  function statusColor(status) {
115
174
  return status === "active" || status === "running" ? pc.green(status) : status === "draining" ? pc.yellow(status) : pc.dim(status);
116
175
  }
117
- function formatTable(rows) {
176
+ function formatTable(rows, now) {
118
177
  if (rows.length === 0) return [pc.dim("no rooms")];
119
178
  const idWidth = Math.max(4, ...rows.map((r) => r.roomId.length));
120
- const out = [pc.dim(`${"ROOM".padEnd(idWidth)} STATUS RETAIN LAST SEEN`)];
179
+ const sharded = rows.some((r) => (r.shard ?? 0) > 0);
180
+ const head = `${"ROOM".padEnd(idWidth)} STATUS RETAIN DELETES LAST SEEN` + (sharded ? " SHARD" : "");
181
+ const out = [pc.dim(head)];
121
182
  for (const r of rows) {
122
183
  const retention = r.retention ?? "forever";
184
+ const mark = r.retentionOverride ? "*" : "";
123
185
  out.push(
124
- `${r.roomId.padEnd(idWidth)} ${statusColor(r.status).padEnd(10)} ${retention.padEnd(6)} ${pc.dim(r.lastSeen)}`
186
+ `${r.roomId.padEnd(idWidth)} ${statusColor(r.status).padEnd(10)} ${`${retention}${mark}`.padEnd(6)} ${formatDeadline(r.deletesAt, now).padEnd(13)} ${pc.dim(r.lastSeen)}` + (sharded ? ` ${String(r.shard ?? 0)}` : "")
125
187
  );
126
188
  }
189
+ if (rows.some((r) => r.retentionOverride)) {
190
+ out.push(pc.dim("* set on this room, not declared by its type"));
191
+ }
192
+ return out;
193
+ }
194
+ function formatDeadline(deletesAt, now) {
195
+ if (!deletesAt) return pc.dim("never");
196
+ const ms = Date.parse(deletesAt) - now;
197
+ if (!Number.isFinite(ms)) return pc.dim("never");
198
+ if (ms <= 0) return pc.yellow("next sweep");
199
+ const mins = Math.floor(ms / 6e4);
200
+ if (mins < 60) return `in ${mins}m`;
201
+ const hours = Math.floor(mins / 60);
202
+ if (hours < 48) return `in ${hours}h`;
203
+ return `in ${Math.floor(hours / 24)}d`;
204
+ }
205
+ function formatDetail(room, now) {
206
+ const line = (label, value) => `${pc.dim(label.padEnd(12))}${value}`;
207
+ const out = [
208
+ line("room", pc.bold(room.roomId)),
209
+ line("status", statusColor(room.status)),
210
+ line("type", room.type ?? pc.dim("not a room id"))
211
+ ];
212
+ out.push(line("class", room.class ?? pc.dim("not declared (billed as small)")));
213
+ if (room.clients !== null && room.clients !== void 0) {
214
+ const max = room.maxClients ? `/${room.maxClients}` : "";
215
+ out.push(line("clients", `${room.clients}${max}`));
216
+ }
217
+ out.push(line("retention", room.retention ?? "forever"));
218
+ if (room.retentionOverride) {
219
+ out.push(line("", pc.dim(`set on this room (${room.retentionOverride}), not by its type`)));
220
+ }
221
+ out.push(line("deletes", formatDeadline(room.deletesAt, now)));
222
+ out.push(line("saves", String(room.saves)));
223
+ out.push(
224
+ line(
225
+ "state",
226
+ room.stateBytes === void 0 ? pc.dim("unknown while the room is awake; read it after it sleeps") : formatBytes(room.stateBytes)
227
+ )
228
+ );
229
+ out.push(line("last seen", pc.dim(room.lastSeen)));
127
230
  return out;
128
231
  }
129
232
  function formatSaves(rows, now) {
@@ -180,8 +283,16 @@ async function runDeleteRoom(options) {
180
283
  log(
181
284
  `${pc.yellow("deleting")} room ${pc.bold(options.room)}: its snapshot, its saves and its alarms`
182
285
  );
286
+ if (options.force === true) {
287
+ log(
288
+ pc.yellow(
289
+ "forced: if the room is awake, every client in it is disconnected first and sees E_ROOM_DELETED. They cannot rejoin the same room; a fresh one starts empty"
290
+ )
291
+ );
292
+ }
183
293
  const result = await client.del(
184
- `/v1/projects/${options.project}/rooms/${encodeURIComponent(options.room)}`
294
+ `/v1/projects/${options.project}/rooms/${encodeURIComponent(options.room)}`,
295
+ options.force === true ? { force: "true" } : {}
185
296
  );
186
297
  log(pc.green(`deleted ${options.room}: ${result.objects} stored object(s) removed`));
187
298
  log(pc.dim("player data and leaderboard scores are keyed to the player and are untouched"));
@@ -191,10 +302,40 @@ async function runRooms(options) {
191
302
  const log = options.log ?? ((line) => console.log(line));
192
303
  const controlUrl = await resolveControlUrlForUser(options.controlUrl);
193
304
  const client = options.client ?? await createApiClient(controlUrl);
194
- const rows = await client.get(`/v1/projects/${options.project}/rooms`);
195
- for (const line of formatTable(rows)) log(line);
305
+ const rows = await client.get(`/v1/projects/${options.project}/rooms`, {
306
+ ...options.active !== void 0 ? { active: String(options.active) } : {},
307
+ ...options.idleLongerThan !== void 0 ? { "idle-longer-than": options.idleLongerThan } : {}
308
+ });
309
+ for (const line of formatTable(rows, options.now ?? Date.now())) log(line);
196
310
  return rows;
197
311
  }
312
+ async function runGetRoom(options) {
313
+ const log = options.log ?? ((line) => console.log(line));
314
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
315
+ const client = options.client ?? await createApiClient(controlUrl);
316
+ const room = await client.get(
317
+ `/v1/projects/${options.project}/rooms/${encodeURIComponent(options.room)}`
318
+ );
319
+ for (const line of formatDetail(room, options.now ?? Date.now())) log(line);
320
+ return room;
321
+ }
322
+ async function runSetRoom(options) {
323
+ const log = options.log ?? ((line) => console.log(line));
324
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
325
+ const client = options.client ?? await createApiClient(controlUrl);
326
+ const retention = options.retention === "clear" ? null : options.retention;
327
+ const room = await client.patch(
328
+ `/v1/projects/${options.project}/rooms/${encodeURIComponent(options.room)}`,
329
+ { retention }
330
+ );
331
+ log(
332
+ retention === null ? pc.green(
333
+ `${options.room}: override cleared, back to its type's window (${room.retention ?? "forever"})`
334
+ ) : pc.green(`${options.room}: retention set to ${retention}`)
335
+ );
336
+ for (const line of formatDetail(room, options.now ?? Date.now())) log(line);
337
+ return room;
338
+ }
198
339
  async function rooms(args, deps = {}) {
199
340
  const log = deps.log ?? ((line) => console.log(line));
200
341
  const errorLog = deps.errorLog ?? ((line) => console.error(line));
@@ -216,15 +357,31 @@ async function rooms(args, deps = {}) {
216
357
  if (parsed.sub === "saves") {
217
358
  await runSaves({ ...common, room: parsed.room });
218
359
  } else if (parsed.sub === "delete") {
219
- await runDeleteRoom({ ...common, room: parsed.room });
360
+ await runDeleteRoom({
361
+ ...common,
362
+ room: parsed.room,
363
+ ...parsed.force === true ? { force: true } : {}
364
+ });
220
365
  } else if (parsed.sub === "restore") {
221
366
  await runRestore({
222
367
  ...common,
223
368
  room: parsed.room,
224
369
  saveId: parsed.save
225
370
  });
371
+ } else if (parsed.sub === "get") {
372
+ await runGetRoom({ ...common, room: parsed.room });
373
+ } else if (parsed.sub === "set") {
374
+ await runSetRoom({
375
+ ...common,
376
+ room: parsed.room,
377
+ retention: parsed.retention
378
+ });
226
379
  } else {
227
- await runRooms(common);
380
+ await runRooms({
381
+ ...common,
382
+ ...parsed.active !== void 0 ? { active: parsed.active } : {},
383
+ ...parsed.idleLongerThan !== void 0 ? { idleLongerThan: parsed.idleLongerThan } : {}
384
+ });
228
385
  }
229
386
  } catch (err) {
230
387
  if (err instanceof HelpRequested) {
@@ -246,7 +403,9 @@ export {
246
403
  parseRoomsArgs,
247
404
  rooms,
248
405
  runDeleteRoom,
406
+ runGetRoom,
249
407
  runRestore,
250
408
  runRooms,
251
- runSaves
409
+ runSaves,
410
+ runSetRoom
252
411
  };