@irtio/cli 0.5.1 → 0.6.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 (38) hide show
  1. package/dist/api.d.ts +52 -0
  2. package/dist/api.js +15 -0
  3. package/dist/bundle.js +1 -1
  4. package/dist/{static-deploy-BP3MDXCP.js → chunk-3HQMVCYA.js} +89 -48
  5. package/dist/chunk-DKWG7MGO.js +93 -0
  6. package/dist/{chunk-KRQUAEN2.js → chunk-OTSFRVJN.js} +12 -6
  7. package/dist/{chunk-I37DLT7K.js → chunk-RNAH5T4W.js} +14 -3
  8. package/dist/chunk-RQSJZWQC.js +452 -0
  9. package/dist/{chunk-NVUKSP5U.js → chunk-UPHQM6NZ.js} +12 -1
  10. package/dist/chunk-ZD4ND6X6.js +31 -0
  11. package/dist/chunk-ZK5JLUD4.js +94 -0
  12. package/dist/credentials.d.ts +61 -0
  13. package/dist/credentials.js +20 -0
  14. package/dist/delete-project-VENS2B44.js +118 -0
  15. package/dist/deploy.d.ts +149 -0
  16. package/dist/deploy.js +567 -0
  17. package/dist/{dev-7UZZGE4U.js → dev-QM26ONKS.js} +3095 -294
  18. package/dist/index.js +113 -28
  19. package/dist/init.d.ts +2 -1
  20. package/dist/init.js +42 -1
  21. package/dist/{keys-KEKO3EJ6.js → keys-JHLMEGRA.js} +49 -18
  22. package/dist/leaderboard-SYPSBPS3.js +352 -0
  23. package/dist/{login-OV2EFNTJ.js → login-2M73HBZT.js} +25 -1
  24. package/dist/{logs-5OUDPAIX.js → logs-2W7CPZO5.js} +42 -17
  25. package/dist/{migrate-UD245ULI.js → migrate-T3DZJREY.js} +44 -19
  26. package/dist/ratings-VG32WFDG.js +297 -0
  27. package/dist/{rollback-CLQVYFHW.js → rollback-SO74MVZV.js} +41 -17
  28. package/dist/{rooms-OJ3JLYHD.js → rooms-VI33P4RA.js} +73 -20
  29. package/dist/simulate.d.ts +215 -3
  30. package/dist/simulate.js +865 -64
  31. package/dist/static-deploy-KOWFKWZA.js +19 -0
  32. package/dist/status-HF3ZEKB7.js +219 -0
  33. package/dist/usage-4G23QXCH.js +213 -0
  34. package/dist/{whoami-S73O6KJF.js → whoami-KTMTQNHM.js} +21 -2
  35. package/package.json +24 -7
  36. package/dist/chunk-BPE452KF.js +0 -180
  37. package/dist/chunk-D7CDJRFF.js +0 -24
  38. package/dist/deploy-YVCVDVMS.js +0 -396
@@ -0,0 +1,297 @@
1
+ import {
2
+ readProjectConfig
3
+ } from "./chunk-DKWG7MGO.js";
4
+ import {
5
+ HelpRequested,
6
+ helpFor,
7
+ helpRequested
8
+ } from "./chunk-ZD4ND6X6.js";
9
+ import {
10
+ createApiClient,
11
+ isLoginRequired
12
+ } from "./chunk-RNAH5T4W.js";
13
+ import {
14
+ resolveControlUrlForUser
15
+ } from "./chunk-UPHQM6NZ.js";
16
+
17
+ // src/ratings.ts
18
+ import pc from "picocolors";
19
+ var USAGE = `usage: irtio ratings <queue> [options]
20
+ irtio ratings list [options]
21
+ irtio ratings delete <queue> --player <id> --yes [options]
22
+
23
+ Prints a project's skill ratings for one queue, best first. Needs your login: a rating is a
24
+ number the matchmaker uses, not a scoreboard for players to read.
25
+
26
+ There is no submit command. A rating moves only from room code (room.ratings.report), which
27
+ is what makes it mean anything.
28
+
29
+ list every queue with ratings on the project, with its row count
30
+ delete remove one player's rating in one queue. Needs --player, your login and --yes;
31
+ without --yes it prints what would go and changes nothing.
32
+
33
+ options:
34
+ --limit <n> how many rows (default 20, max 100)
35
+ --cursor <c> continue from a previous page's nextCursor
36
+ --all page through the whole queue, following cursors
37
+ --player <id> with delete: whose rating to remove
38
+ --yes with delete: actually delete
39
+ --project <id> project id (default: the project file)
40
+ -c, --config <f> the project file to read (default irtio.json)
41
+ --url <control> control plane (default: your stored login)
42
+ -h, --help print this
43
+ `;
44
+ function parseRatingsArgs(args) {
45
+ if (helpRequested(args)) throw helpFor(USAGE);
46
+ const parsed = {};
47
+ if (args[0] === "list" || args[0] === "delete") parsed.action = args[0];
48
+ for (let i = parsed.action === void 0 ? 0 : 1; i < args.length; i++) {
49
+ const arg = args[i];
50
+ switch (arg) {
51
+ case "--cursor": {
52
+ const value = args[++i];
53
+ if (value === void 0 || value === "") {
54
+ throw new Error("irtio ratings: --cursor needs a value");
55
+ }
56
+ parsed.cursor = value;
57
+ break;
58
+ }
59
+ case "--all":
60
+ parsed.all = true;
61
+ break;
62
+ case "--yes":
63
+ parsed.yes = true;
64
+ break;
65
+ case "--player": {
66
+ const value = args[++i];
67
+ if (value === void 0 || value === "") {
68
+ throw new Error("irtio ratings: --player needs a player id");
69
+ }
70
+ parsed.player = value;
71
+ break;
72
+ }
73
+ case "--limit": {
74
+ const value = Number(args[++i]);
75
+ if (!Number.isInteger(value) || value < 1) {
76
+ throw new Error("irtio ratings: --limit needs a whole number of at least 1");
77
+ }
78
+ parsed.limit = value;
79
+ break;
80
+ }
81
+ case "--project": {
82
+ const value = args[++i];
83
+ if (value === void 0) throw new Error("irtio ratings: --project needs a value");
84
+ parsed.project = value;
85
+ break;
86
+ }
87
+ case "-c":
88
+ case "--config": {
89
+ const value = args[++i];
90
+ if (value === void 0 || value === "") {
91
+ throw new Error("irtio ratings: --config needs a value");
92
+ }
93
+ parsed.config = value;
94
+ break;
95
+ }
96
+ case "--url": {
97
+ const value = args[++i];
98
+ if (value === void 0) throw new Error("irtio ratings: --url needs a value");
99
+ parsed.url = value;
100
+ break;
101
+ }
102
+ default:
103
+ if (arg.startsWith("-")) {
104
+ throw new Error(`irtio ratings: unknown option ${JSON.stringify(arg)}`);
105
+ }
106
+ if (parsed.queue !== void 0) {
107
+ throw new Error("irtio ratings: give exactly one queue name");
108
+ }
109
+ parsed.queue = arg;
110
+ }
111
+ }
112
+ return parsed;
113
+ }
114
+ function formatRatings(body) {
115
+ const out = [pc.dim(`queue ${body.queue} \xB7 Glicko-2, higher is stronger`), ""];
116
+ if (body.entries.length === 0) {
117
+ out.push(pc.dim("no ratings in this queue yet"));
118
+ out.push(pc.dim("a rating appears when room code calls room.ratings.report"));
119
+ return out;
120
+ }
121
+ const rankWidth = Math.max(4, ...body.entries.map((e) => String(e.rank).length));
122
+ const idWidth = Math.max(6, ...body.entries.map((e) => e.playerId.length));
123
+ out.push(pc.dim(`${"RANK".padEnd(rankWidth)} ${"PLAYER".padEnd(idWidth)} RATING \xB1DEV GAMES`));
124
+ for (const e of body.entries) {
125
+ out.push(
126
+ `${String(e.rank).padEnd(rankWidth)} ${e.playerId.padEnd(idWidth)} ${e.rating.toFixed(0).padStart(6)} ${e.deviation.toFixed(0).padStart(4)} ${String(e.games).padStart(5)}`
127
+ );
128
+ }
129
+ out.push("");
130
+ out.push(
131
+ pc.dim("\xB1DEV is how sure the rating is. 350 means a new player; under 80 means settled.")
132
+ );
133
+ return out;
134
+ }
135
+ async function runRatings(options) {
136
+ const log = options.log ?? ((line) => console.log(line));
137
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
138
+ const client = options.client ?? await createApiClient(controlUrl);
139
+ const base = `/v1/projects/${encodeURIComponent(options.project)}/ratings/${encodeURIComponent(
140
+ options.queue
141
+ )}`;
142
+ const pageUrl = (cursor) => {
143
+ const query = new URLSearchParams();
144
+ if (options.limit !== void 0) query.set("limit", String(options.limit));
145
+ if (cursor !== void 0) query.set("cursor", cursor);
146
+ const q = query.toString();
147
+ return `${base}${q === "" ? "" : `?${q}`}`;
148
+ };
149
+ let body = await client.get(pageUrl(options.cursor));
150
+ const entries = [...body.entries];
151
+ if (options.all === true) {
152
+ let next = body.nextCursor;
153
+ for (let page = 1; next !== void 0 && page < 100; page++) {
154
+ body = await client.get(pageUrl(next));
155
+ for (const entry of body.entries) {
156
+ entries.push({ ...entry, rank: entries.length + 1 });
157
+ }
158
+ next = body.nextCursor;
159
+ }
160
+ if (next !== void 0) {
161
+ throw new Error(
162
+ "irtio ratings: --all stopped after a hundred pages. Read it in pieces with --cursor"
163
+ );
164
+ }
165
+ }
166
+ const combined = { ...body, entries };
167
+ for (const line of formatRatings(combined)) log(line);
168
+ if (options.all !== true && body.nextCursor !== void 0) {
169
+ log("");
170
+ log(pc.dim(`more rows: --cursor ${body.nextCursor}`));
171
+ }
172
+ return combined;
173
+ }
174
+ function formatQueueList(queues) {
175
+ if (queues.length === 0) {
176
+ return [
177
+ pc.dim("no ratings on this project yet"),
178
+ pc.dim("a rating reaches a queue from room code: room.ratings.report")
179
+ ];
180
+ }
181
+ const nameWidth = Math.max(5, ...queues.map((q) => q.queue.length));
182
+ const out = [pc.dim(`${"QUEUE".padEnd(nameWidth)} ${"ROWS".padStart(7)} MATCHING`)];
183
+ for (const q of queues) {
184
+ const matching = q.skill ? "skill" : "fifo";
185
+ const note = q.configured && q.rows === 0 ? pc.dim(" (configured, no ratings yet)") : !q.configured ? pc.dim(" (ratings only; not a configured queue)") : !q.skill ? pc.dim(" (ratings recorded, but this queue fills in arrival order)") : "";
186
+ out.push(`${q.queue.padEnd(nameWidth)} ${String(q.rows).padStart(7)} ${matching}${note}`);
187
+ }
188
+ return out;
189
+ }
190
+ async function runQueueList(options) {
191
+ const log = options.log ?? ((line) => console.log(line));
192
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
193
+ const client = options.client ?? await createApiClient(controlUrl);
194
+ const body = await client.get(
195
+ `/v1/projects/${encodeURIComponent(options.project)}/ratings`
196
+ );
197
+ for (const line of formatQueueList(body.queues)) log(line);
198
+ return body.queues;
199
+ }
200
+ async function runRatingDelete(options) {
201
+ const log = options.log ?? ((line) => console.log(line));
202
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
203
+ const client = options.client ?? await createApiClient(controlUrl);
204
+ const path = `/v1/projects/${encodeURIComponent(options.project)}/ratings/${encodeURIComponent(
205
+ options.queue
206
+ )}/${encodeURIComponent(options.player)}`;
207
+ if (!options.yes) {
208
+ log(
209
+ `this would delete ${pc.bold(options.player)}'s rating in queue ${pc.bold(options.queue)} in project ${options.project}`
210
+ );
211
+ log(pc.dim("nothing was deleted. This cannot be undone once it runs"));
212
+ log(`to proceed: irtio ratings delete ${options.queue} --player ${options.player} --yes`);
213
+ return false;
214
+ }
215
+ const body = await client.del(path);
216
+ log(
217
+ body.deleted ? pc.green(`deleted ${options.player}'s rating in queue ${options.queue}`) : pc.dim(`${options.player} had no rating in queue ${options.queue}; nothing to delete`)
218
+ );
219
+ return true;
220
+ }
221
+ async function ratings(args, deps = {}) {
222
+ const log = deps.log ?? ((line) => console.log(line));
223
+ const errorLog = deps.errorLog ?? ((line) => console.error(line));
224
+ try {
225
+ const parsed = parseRatingsArgs(args);
226
+ const config = await readProjectConfig(process.cwd(), "irtio ratings", parsed.config);
227
+ const project = parsed.project ?? config.project;
228
+ if (project === void 0) {
229
+ throw new Error(
230
+ "irtio ratings: no project id. Pass --project or run this from a project with irtio.json"
231
+ );
232
+ }
233
+ const common = {
234
+ project,
235
+ log,
236
+ ...parsed.url !== void 0 ? { controlUrl: parsed.url } : {},
237
+ ...deps.client !== void 0 ? { client: deps.client } : {}
238
+ };
239
+ if (parsed.action === "list") {
240
+ if (parsed.queue !== void 0) throw new Error("irtio ratings list: takes no queue name");
241
+ await runQueueList(common);
242
+ return;
243
+ }
244
+ if (parsed.action === "delete") {
245
+ if (parsed.queue === void 0) {
246
+ throw new Error(
247
+ "irtio ratings delete: which queue? e.g. irtio ratings delete ranked --player irt:abc --yes"
248
+ );
249
+ }
250
+ if (parsed.player === void 0) {
251
+ throw new Error("irtio ratings delete: --player is required; there is no whole-queue wipe");
252
+ }
253
+ await runRatingDelete({
254
+ ...common,
255
+ queue: parsed.queue,
256
+ player: parsed.player,
257
+ yes: parsed.yes === true
258
+ });
259
+ return;
260
+ }
261
+ if (parsed.queue === void 0) {
262
+ throw new Error("irtio ratings: which queue? e.g. irtio ratings ranked");
263
+ }
264
+ if (parsed.cursor !== void 0 && parsed.all === true) {
265
+ throw new Error("irtio ratings: --cursor and --all are two ways to do the same thing");
266
+ }
267
+ await runRatings({
268
+ ...common,
269
+ queue: parsed.queue,
270
+ ...parsed.limit !== void 0 ? { limit: parsed.limit } : {},
271
+ ...parsed.cursor !== void 0 ? { cursor: parsed.cursor } : {},
272
+ ...parsed.all !== void 0 ? { all: parsed.all } : {}
273
+ });
274
+ } catch (err) {
275
+ if (err instanceof HelpRequested) {
276
+ log(err.usage);
277
+ return;
278
+ }
279
+ if (isLoginRequired(err)) {
280
+ errorLog("irtio ratings: not logged in. Run `irtio login` first");
281
+ process.exitCode = 1;
282
+ return;
283
+ }
284
+ errorLog(err instanceof Error ? err.message : String(err));
285
+ process.exitCode = 1;
286
+ }
287
+ }
288
+ export {
289
+ USAGE,
290
+ formatQueueList,
291
+ formatRatings,
292
+ parseRatingsArgs,
293
+ ratings,
294
+ runQueueList,
295
+ runRatingDelete,
296
+ runRatings
297
+ };
@@ -1,17 +1,38 @@
1
+ import {
2
+ readProjectConfig
3
+ } from "./chunk-DKWG7MGO.js";
4
+ import {
5
+ HelpRequested,
6
+ helpFor,
7
+ helpRequested
8
+ } from "./chunk-ZD4ND6X6.js";
1
9
  import {
2
10
  createApiClient,
3
11
  isLoginRequired
4
- } from "./chunk-I37DLT7K.js";
12
+ } from "./chunk-RNAH5T4W.js";
5
13
  import {
6
14
  resolveControlUrlForUser
7
- } from "./chunk-NVUKSP5U.js";
15
+ } from "./chunk-UPHQM6NZ.js";
8
16
 
9
17
  // src/rollback.ts
10
- import { existsSync } from "fs";
11
- import { readFile } from "fs/promises";
12
- import * as path from "path";
18
+ import "fs";
19
+ import "fs/promises";
20
+ import "path";
13
21
  import pc from "picocolors";
22
+ var USAGE = `usage: irtio rollback <version> [options]
23
+
24
+ Re-promotes deployment <version> and brings rooms that migrated past it back to their
25
+ pre-migration state. This DISCARDS whatever those rooms wrote since their migration.
26
+
27
+ options:
28
+ <version> the deployment version to go back to
29
+ --project <id> project id (default: the project file)
30
+ -c, --config <f> the project file to read (default irtio.json)
31
+ --url <control> control plane (default: your stored login)
32
+ -h, --help print this
33
+ `;
14
34
  function parseRollbackArgs(args) {
35
+ if (helpRequested(args)) throw helpFor(USAGE);
15
36
  const parsed = {};
16
37
  for (let i = 0; i < args.length; i++) {
17
38
  const arg = args[i];
@@ -22,6 +43,14 @@ function parseRollbackArgs(args) {
22
43
  parsed.project = value;
23
44
  break;
24
45
  }
46
+ case "-c":
47
+ case "--config": {
48
+ const value = args[++i];
49
+ if (value === void 0 || value === "")
50
+ throw new Error("irtio rollback: --config needs a value");
51
+ parsed.config = value;
52
+ break;
53
+ }
25
54
  case "--url": {
26
55
  const value = args[++i];
27
56
  if (value === void 0) throw new Error("irtio rollback: --url needs a value");
@@ -75,23 +104,13 @@ async function runRollback(options) {
75
104
  }
76
105
  return result;
77
106
  }
78
- async function readIrtioJsonProject(cwd) {
79
- const file = path.join(cwd, "irtio.json");
80
- if (!existsSync(file)) return void 0;
81
- let parsed;
82
- try {
83
- parsed = JSON.parse(await readFile(file, "utf8"));
84
- } catch {
85
- throw new Error(`irtio rollback: ${file} is not valid JSON`);
86
- }
87
- return typeof parsed.project === "string" && parsed.project.length > 0 ? parsed.project : void 0;
88
- }
89
107
  async function rollback(args, deps = {}) {
90
108
  const log = deps.log ?? ((line) => console.log(line));
91
109
  const errorLog = deps.errorLog ?? ((line) => console.error(line));
92
110
  try {
93
111
  const parsed = parseRollbackArgs(args);
94
- const project = parsed.project ?? await readIrtioJsonProject(process.cwd());
112
+ const config = await readProjectConfig(process.cwd(), "irtio rollback", parsed.config);
113
+ const project = parsed.project ?? config.project;
95
114
  if (project === void 0) {
96
115
  throw new Error(
97
116
  "irtio rollback: no project id \u2014 pass --project or run this from a project with irtio.json"
@@ -105,6 +124,10 @@ async function rollback(args, deps = {}) {
105
124
  ...deps.client !== void 0 ? { client: deps.client } : {}
106
125
  });
107
126
  } catch (err) {
127
+ if (err instanceof HelpRequested) {
128
+ log(err.usage);
129
+ return;
130
+ }
108
131
  if (isLoginRequired(err)) {
109
132
  errorLog(pc.red("not logged in"));
110
133
  errorLog("run: irtio login");
@@ -116,6 +139,7 @@ async function rollback(args, deps = {}) {
116
139
  }
117
140
  }
118
141
  export {
142
+ USAGE,
119
143
  parseRollbackArgs,
120
144
  rollback,
121
145
  runRollback
@@ -1,21 +1,53 @@
1
+ import {
2
+ readProjectConfig
3
+ } from "./chunk-DKWG7MGO.js";
4
+ import {
5
+ HelpRequested,
6
+ helpFor,
7
+ helpRequested
8
+ } from "./chunk-ZD4ND6X6.js";
1
9
  import {
2
10
  createApiClient,
3
11
  isLoginRequired
4
- } from "./chunk-I37DLT7K.js";
12
+ } from "./chunk-RNAH5T4W.js";
5
13
  import {
6
14
  resolveControlUrlForUser
7
- } from "./chunk-NVUKSP5U.js";
15
+ } from "./chunk-UPHQM6NZ.js";
8
16
 
9
17
  // src/rooms.ts
10
- import { existsSync } from "fs";
11
- import { readFile } from "fs/promises";
12
- import * as path from "path";
18
+ import "fs";
19
+ import "fs/promises";
20
+ import "path";
13
21
  import pc from "picocolors";
22
+ var USAGE = `usage: irtio rooms [options]
23
+ irtio rooms saves <room> [options]
24
+ irtio rooms restore <room> --save <id> [options]
25
+ irtio rooms delete <room> [options]
26
+
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.
30
+
31
+ subcommands:
32
+ saves <room> list a room's save generations, newest first
33
+ restore <room> --save <id>
34
+ bring a room back from a save. This DISCARDS its current state, and
35
+ stops the tenant if it is live
36
+ 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
38
+
39
+ options:
40
+ --project <id> project id (default: the project file)
41
+ -c, --config <f> the project file to read (default irtio.json)
42
+ --url <control> control plane (default: your stored login)
43
+ -h, --help print this
44
+ `;
14
45
  function parseRoomsArgs(args) {
46
+ if (helpRequested(args)) throw helpFor(USAGE);
15
47
  const parsed = { sub: "list" };
16
48
  let rest = args;
17
49
  const first = args[0];
18
- if (first === "saves" || first === "restore") {
50
+ if (first === "saves" || first === "restore" || first === "delete") {
19
51
  parsed.sub = first;
20
52
  const room = args[1];
21
53
  if (room === void 0 || room.startsWith("-")) {
@@ -39,6 +71,14 @@ function parseRoomsArgs(args) {
39
71
  parsed.project = value;
40
72
  break;
41
73
  }
74
+ case "-c":
75
+ case "--config": {
76
+ const value = args[++i];
77
+ if (value === void 0 || value === "")
78
+ throw new Error("irtio rooms: --config needs a value");
79
+ parsed.config = value;
80
+ break;
81
+ }
42
82
  case "--url": {
43
83
  const value = rest[++i];
44
84
  if (value === void 0) throw new Error("irtio rooms: --url needs a value");
@@ -77,10 +117,11 @@ function statusColor(status) {
77
117
  function formatTable(rows) {
78
118
  if (rows.length === 0) return [pc.dim("no rooms")];
79
119
  const idWidth = Math.max(4, ...rows.map((r) => r.roomId.length));
80
- const out = [pc.dim(`${"ROOM".padEnd(idWidth)} STATUS LAST SEEN`)];
120
+ const out = [pc.dim(`${"ROOM".padEnd(idWidth)} STATUS RETAIN LAST SEEN`)];
81
121
  for (const r of rows) {
122
+ const retention = r.retention ?? "forever";
82
123
  out.push(
83
- `${r.roomId.padEnd(idWidth)} ${statusColor(r.status).padEnd(10)} ${pc.dim(r.lastSeen)}`
124
+ `${r.roomId.padEnd(idWidth)} ${statusColor(r.status).padEnd(10)} ${retention.padEnd(6)} ${pc.dim(r.lastSeen)}`
84
125
  );
85
126
  }
86
127
  return out;
@@ -132,6 +173,20 @@ async function runRestore(options) {
132
173
  }
133
174
  return result;
134
175
  }
176
+ async function runDeleteRoom(options) {
177
+ const log = options.log ?? ((line) => console.log(line));
178
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
179
+ const client = options.client ?? await createApiClient(controlUrl);
180
+ log(
181
+ `${pc.yellow("deleting")} room ${pc.bold(options.room)}: its snapshot, its saves and its alarms`
182
+ );
183
+ const result = await client.del(
184
+ `/v1/projects/${options.project}/rooms/${encodeURIComponent(options.room)}`
185
+ );
186
+ log(pc.green(`deleted ${options.room}: ${result.objects} stored object(s) removed`));
187
+ log(pc.dim("player data and leaderboard scores are keyed to the player and are untouched"));
188
+ return result;
189
+ }
135
190
  async function runRooms(options) {
136
191
  const log = options.log ?? ((line) => console.log(line));
137
192
  const controlUrl = await resolveControlUrlForUser(options.controlUrl);
@@ -140,23 +195,13 @@ async function runRooms(options) {
140
195
  for (const line of formatTable(rows)) log(line);
141
196
  return rows;
142
197
  }
143
- async function readIrtioJsonProject(cwd) {
144
- const file = path.join(cwd, "irtio.json");
145
- if (!existsSync(file)) return void 0;
146
- let parsed;
147
- try {
148
- parsed = JSON.parse(await readFile(file, "utf8"));
149
- } catch {
150
- throw new Error(`irtio rooms: ${file} is not valid JSON`);
151
- }
152
- return typeof parsed.project === "string" && parsed.project.length > 0 ? parsed.project : void 0;
153
- }
154
198
  async function rooms(args, deps = {}) {
155
199
  const log = deps.log ?? ((line) => console.log(line));
156
200
  const errorLog = deps.errorLog ?? ((line) => console.error(line));
157
201
  try {
158
202
  const parsed = parseRoomsArgs(args);
159
- const project = parsed.project ?? await readIrtioJsonProject(process.cwd());
203
+ const config = await readProjectConfig(process.cwd(), "irtio rooms", parsed.config);
204
+ const project = parsed.project ?? config.project;
160
205
  if (project === void 0) {
161
206
  throw new Error(
162
207
  "irtio rooms: no project id \u2014 pass --project or run this from a project with irtio.json"
@@ -170,6 +215,8 @@ async function rooms(args, deps = {}) {
170
215
  };
171
216
  if (parsed.sub === "saves") {
172
217
  await runSaves({ ...common, room: parsed.room });
218
+ } else if (parsed.sub === "delete") {
219
+ await runDeleteRoom({ ...common, room: parsed.room });
173
220
  } else if (parsed.sub === "restore") {
174
221
  await runRestore({
175
222
  ...common,
@@ -180,6 +227,10 @@ async function rooms(args, deps = {}) {
180
227
  await runRooms(common);
181
228
  }
182
229
  } catch (err) {
230
+ if (err instanceof HelpRequested) {
231
+ log(err.usage);
232
+ return;
233
+ }
183
234
  if (isLoginRequired(err)) {
184
235
  errorLog(pc.red("not logged in"));
185
236
  errorLog("run: irtio login");
@@ -191,8 +242,10 @@ async function rooms(args, deps = {}) {
191
242
  }
192
243
  }
193
244
  export {
245
+ USAGE,
194
246
  parseRoomsArgs,
195
247
  rooms,
248
+ runDeleteRoom,
196
249
  runRestore,
197
250
  runRooms,
198
251
  runSaves