rooms.sh 0.0.1

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.
@@ -0,0 +1,556 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib.ts
4
+ import { spawn } from "child_process";
5
+ import { promises as fs, readFileSync } from "fs";
6
+ import { createRequire } from "module";
7
+ import path from "path";
8
+ import { fileURLToPath } from "url";
9
+ import { build } from "esbuild";
10
+ var DEFAULT_ORIGIN = "http://localhost:3000";
11
+ var ALLOWED_BARE_IMPORTS = /* @__PURE__ */ new Set(["@rooms.sh/sdk"]);
12
+ var RUNTIME_SDK_SPECIFIER = "__rooms_sdk__";
13
+ var TARGET_ENV_KEY = "ROOMS_TARGET";
14
+ var CLI_PACKAGE_NAME = "rooms.sh";
15
+ var SDK_PACKAGE_NAME = "@rooms.sh/sdk";
16
+ var TYPESCRIPT_VERSION = "^5.9.3";
17
+ var DEFAULT_ROOM_ENTRY_FILE = "room.ts";
18
+ var DEFAULT_TSCONFIG_FILE = "tsconfig.json";
19
+ function getPackageVersion() {
20
+ const packageJsonPath = path.resolve(
21
+ path.dirname(fileURLToPath(import.meta.url)),
22
+ "../package.json"
23
+ );
24
+ const packageJson = JSON.parse(
25
+ readFileSync(packageJsonPath, "utf8")
26
+ );
27
+ return packageJson.version ?? "0.0.1";
28
+ }
29
+ var CLI_PACKAGE_VERSION = getPackageVersion();
30
+ var SDK_PACKAGE_VERSION = CLI_PACKAGE_VERSION;
31
+ function parseAgentTarget(target, options = {}) {
32
+ if (target.startsWith("http://") || target.startsWith("https://")) {
33
+ const url = new URL(target);
34
+ const token = url.searchParams.get("token");
35
+ const match = url.pathname.match(/^\/api\/agent\/rooms\/([^/]+)$/);
36
+ if (!token || !match?.[1]) {
37
+ throw new Error(
38
+ "Agent URL must look like /api/agent/rooms/:roomId?token=..."
39
+ );
40
+ }
41
+ return buildAgentTarget(url.origin, match[1], token);
42
+ }
43
+ if (!options.roomId) {
44
+ throw new Error("A room id is required when providing a raw token.");
45
+ }
46
+ return buildAgentTarget(options.origin ?? DEFAULT_ORIGIN, options.roomId, target);
47
+ }
48
+ function buildAgentTarget(origin, roomId, token) {
49
+ const base = new URL(`/api/agent/rooms/${roomId}`, origin);
50
+ base.searchParams.set("token", token);
51
+ const source = new URL(`/api/agent/rooms/${roomId}/source`, origin);
52
+ source.searchParams.set("token", token);
53
+ const build2 = new URL(`/api/agent/rooms/${roomId}/build`, origin);
54
+ build2.searchParams.set("token", token);
55
+ const normalizedOrigin = origin.endsWith("/") ? origin.slice(0, -1) : origin;
56
+ return {
57
+ origin,
58
+ roomId,
59
+ token,
60
+ contextUrl: base.toString(),
61
+ sourceUrl: source.toString(),
62
+ buildUrl: build2.toString(),
63
+ buildStatusUrlTemplate: `${normalizedOrigin}/api/agent/rooms/${roomId}/builds/{buildJobId}?token=${encodeURIComponent(token)}`
64
+ };
65
+ }
66
+ async function runProcess(command, args, options = {}) {
67
+ await new Promise((resolve, reject) => {
68
+ const child = spawn(command, args, {
69
+ cwd: options.cwd,
70
+ stdio: "inherit",
71
+ env: process.env
72
+ });
73
+ child.on("error", reject);
74
+ child.on("exit", (code) => {
75
+ if (code === 0) {
76
+ resolve();
77
+ return;
78
+ }
79
+ reject(new Error(`${command} ${args.join(" ")} exited with code ${code ?? "unknown"}`));
80
+ });
81
+ });
82
+ }
83
+ function resolveNodeCommand() {
84
+ const pathEntries = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
85
+ for (const entry of pathEntries) {
86
+ const candidate = path.join(entry, "node");
87
+ try {
88
+ readFileSync(candidate);
89
+ return candidate;
90
+ } catch {
91
+ continue;
92
+ }
93
+ }
94
+ return "node";
95
+ }
96
+ var NODE_COMMAND = resolveNodeCommand();
97
+ function createNodeRequire(fromDirectory) {
98
+ return createRequire(path.join(fromDirectory, "__rooms_cli__.js"));
99
+ }
100
+ function resolveProjectBinary(specifier, fromDirectory) {
101
+ const require2 = createNodeRequire(fromDirectory);
102
+ return require2.resolve(specifier, {
103
+ paths: [fromDirectory]
104
+ });
105
+ }
106
+ async function typecheckRoomEntry(entryFile, options = {}) {
107
+ const resolvedEntry = path.resolve(entryFile);
108
+ const projectRoot = options.project ? path.dirname(path.resolve(options.project)) : path.dirname(resolvedEntry);
109
+ const tscBin = resolveProjectBinary("typescript/bin/tsc", projectRoot);
110
+ if (options.project) {
111
+ const resolvedProject = path.resolve(options.project);
112
+ await runProcess(NODE_COMMAND, [tscBin, "--noEmit", "--project", resolvedProject], {
113
+ cwd: path.dirname(resolvedProject)
114
+ });
115
+ return;
116
+ }
117
+ await runProcess(
118
+ NODE_COMMAND,
119
+ [
120
+ tscBin,
121
+ "--noEmit",
122
+ "--module",
123
+ "ESNext",
124
+ "--moduleResolution",
125
+ "bundler",
126
+ "--target",
127
+ "ES2022",
128
+ "--allowSyntheticDefaultImports",
129
+ "--skipLibCheck",
130
+ resolvedEntry
131
+ ],
132
+ {
133
+ cwd: projectRoot
134
+ }
135
+ );
136
+ }
137
+ function allowlistedImportsPlugin() {
138
+ return {
139
+ name: "rooms-allowlisted-imports",
140
+ setup(buildApi) {
141
+ buildApi.onResolve({ filter: /^[^./].*/ }, (args) => {
142
+ if (args.path === "@rooms.sh/sdk") {
143
+ return {
144
+ path: RUNTIME_SDK_SPECIFIER,
145
+ external: true
146
+ };
147
+ }
148
+ if (ALLOWED_BARE_IMPORTS.has(args.path)) {
149
+ return null;
150
+ }
151
+ return {
152
+ errors: [
153
+ {
154
+ text: `Unsupported bare import "${args.path}". Only @rooms.sh/sdk and relative imports are allowed.`
155
+ }
156
+ ]
157
+ };
158
+ });
159
+ }
160
+ };
161
+ }
162
+ async function bundleRoomEntry(entryFile) {
163
+ const resolvedEntry = path.resolve(entryFile);
164
+ await fs.access(resolvedEntry);
165
+ const result = await build({
166
+ entryPoints: [resolvedEntry],
167
+ bundle: true,
168
+ format: "esm",
169
+ platform: "neutral",
170
+ target: "es2022",
171
+ write: false,
172
+ sourcemap: false,
173
+ legalComments: "none",
174
+ logLevel: "silent",
175
+ plugins: [allowlistedImportsPlugin()]
176
+ });
177
+ const output = result.outputFiles?.[0];
178
+ if (!output) {
179
+ throw new Error("esbuild did not emit a bundle.");
180
+ }
181
+ return {
182
+ bundleCode: output.text,
183
+ entryPoint: path.relative(process.cwd(), resolvedEntry),
184
+ bundleSize: output.text.length
185
+ };
186
+ }
187
+ async function fetchJson(url, init) {
188
+ const response = await fetch(url, {
189
+ ...init,
190
+ headers: {
191
+ "content-type": "application/json",
192
+ ...init?.headers ?? {}
193
+ }
194
+ });
195
+ const text = await response.text();
196
+ const parsed = text ? JSON.parse(text) : {};
197
+ if (!response.ok) {
198
+ throw new Error(
199
+ typeof parsed.error === "string" ? parsed.error : `Request failed with status ${response.status}`
200
+ );
201
+ }
202
+ return parsed;
203
+ }
204
+ async function readProjectPackageManifest(projectDir) {
205
+ const packageJsonPath = path.join(projectDir, "package.json");
206
+ try {
207
+ const packageJson = await fs.readFile(packageJsonPath, "utf8");
208
+ return JSON.parse(packageJson);
209
+ } catch (error) {
210
+ if (error.code === "ENOENT") {
211
+ return null;
212
+ }
213
+ throw error;
214
+ }
215
+ }
216
+ async function uploadBundle(target, bundle, options = {}) {
217
+ return await fetchJson(target.sourceUrl, {
218
+ method: "PUT",
219
+ body: JSON.stringify({
220
+ bundleCode: bundle.bundleCode,
221
+ entryPoint: bundle.entryPoint,
222
+ roomName: options.roomName
223
+ })
224
+ });
225
+ }
226
+ async function createBuild(target) {
227
+ return await fetchJson(target.buildUrl, {
228
+ method: "POST",
229
+ body: JSON.stringify({})
230
+ });
231
+ }
232
+ async function waitForBuild(target, buildJobId, options = {}) {
233
+ const pollIntervalMs = options.pollIntervalMs ?? 1e3;
234
+ const statusUrl = target.buildStatusUrlTemplate.replace("{buildJobId}", buildJobId);
235
+ for (; ; ) {
236
+ const build2 = await fetchJson(statusUrl);
237
+ if (!build2) {
238
+ throw new Error("Build job disappeared.");
239
+ }
240
+ if (build2.status === "succeeded") {
241
+ return build2;
242
+ }
243
+ if (build2.status === "failed") {
244
+ throw new Error(build2.error ?? build2.log ?? "Build failed.");
245
+ }
246
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
247
+ }
248
+ }
249
+ function slugifyRoomName(name) {
250
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
251
+ }
252
+ function detectPackageManager() {
253
+ const userAgent = process.env.npm_config_user_agent ?? "";
254
+ if (userAgent.startsWith("pnpm/")) {
255
+ return "pnpm";
256
+ }
257
+ if (userAgent.startsWith("yarn/")) {
258
+ return "yarn";
259
+ }
260
+ if (userAgent.startsWith("bun/")) {
261
+ return "bun";
262
+ }
263
+ return "npm";
264
+ }
265
+ async function ensureDirectoryIsWritable(projectDir) {
266
+ try {
267
+ const existingEntries = await fs.readdir(projectDir);
268
+ if (existingEntries.length > 0) {
269
+ throw new Error(`Directory ${projectDir} already exists and is not empty.`);
270
+ }
271
+ } catch (error) {
272
+ if (error.code === "ENOENT") {
273
+ await fs.mkdir(projectDir, { recursive: true });
274
+ return;
275
+ }
276
+ throw error;
277
+ }
278
+ }
279
+ function createRoomProjectPackageJson(projectName) {
280
+ return {
281
+ name: projectName,
282
+ private: true,
283
+ type: "module",
284
+ scripts: {
285
+ typecheck: "tsc --noEmit",
286
+ "publish:room": `${CLI_PACKAGE_NAME} publish`
287
+ },
288
+ dependencies: {
289
+ [SDK_PACKAGE_NAME]: `^${SDK_PACKAGE_VERSION}`
290
+ },
291
+ devDependencies: {
292
+ [CLI_PACKAGE_NAME]: `^${CLI_PACKAGE_VERSION}`,
293
+ typescript: TYPESCRIPT_VERSION
294
+ }
295
+ };
296
+ }
297
+ function createRoomProjectTsconfig() {
298
+ return {
299
+ compilerOptions: {
300
+ target: "ES2022",
301
+ module: "ESNext",
302
+ moduleResolution: "bundler",
303
+ strict: true,
304
+ noEmit: true,
305
+ allowSyntheticDefaultImports: true,
306
+ skipLibCheck: true
307
+ },
308
+ include: ["./room.ts"]
309
+ };
310
+ }
311
+ function createRoomEntrySource(roomName, roomId) {
312
+ return `import {
313
+ ambientLight,
314
+ defineItem,
315
+ defineRoom,
316
+ directionalLight,
317
+ geometry,
318
+ material,
319
+ mesh,
320
+ placeFloor,
321
+ } from "@rooms.sh/sdk"
322
+
323
+ const cream = material.standard({ color: 0xf3ecdf, roughness: 0.95 })
324
+ const blush = material.standard({ color: 0xd8afba, roughness: 0.9 })
325
+ const plum = material.standard({ color: 0x5b425f, roughness: 0.88 })
326
+ const walnut = material.standard({ color: 0x57392b, roughness: 0.82 })
327
+
328
+ const items = {
329
+ platform: defineItem({
330
+ id: "platform",
331
+ nodes: [
332
+ mesh(
333
+ geometry.box({ width: 1.8, height: 0.32, depth: 1.2 }),
334
+ walnut,
335
+ {
336
+ position: [0, 0.16, 0],
337
+ },
338
+ ),
339
+ mesh(
340
+ geometry.roundedBox({
341
+ width: 1.86,
342
+ height: 0.18,
343
+ depth: 1.26,
344
+ radius: 0.08,
345
+ }),
346
+ blush,
347
+ {
348
+ position: [0, 0.41, 0],
349
+ },
350
+ ),
351
+ ],
352
+ }),
353
+ orbLamp: defineItem({
354
+ id: "orbLamp",
355
+ nodes: [
356
+ mesh(
357
+ geometry.cylinder({ radiusTop: 0.05, radiusBottom: 0.08, height: 1.2 }),
358
+ plum,
359
+ {
360
+ position: [0, 0.6, 0],
361
+ },
362
+ ),
363
+ mesh(
364
+ geometry.sphere({ radius: 0.24 }),
365
+ material.glass({ color: 0xfff4ef, transmission: 0.9, opacity: 0.7 }),
366
+ {
367
+ position: [0, 1.28, 0],
368
+ },
369
+ ),
370
+ ],
371
+ }),
372
+ }
373
+
374
+ export default defineRoom({
375
+ id: ${JSON.stringify(roomId)},
376
+ shell: {
377
+ width: 7,
378
+ depth: 7,
379
+ height: 3.4,
380
+ floorMaterial: cream,
381
+ wallMaterial: material.standard({ color: 0xf8f3fb, roughness: 0.98 }),
382
+ ceilingMaterial: material.standard({ color: 0xfffcff, roughness: 1 }),
383
+ },
384
+ atmosphere: {
385
+ background: "#f1d8f4",
386
+ fogColor: "#f1d8f4",
387
+ fogDensity: 0.028,
388
+ },
389
+ items,
390
+ instances: [
391
+ placeFloor("platform", "platform", 0, 0),
392
+ placeFloor("lamp", "orbLamp", -2.1, -1.9),
393
+ ],
394
+ lights: [
395
+ ambientLight("#fff6fb", 0.9),
396
+ directionalLight("#ffd7ee", 1.2, [3, 4.8, 2], [0, 0.8, 0]),
397
+ ],
398
+ cameras: {
399
+ wide: {
400
+ position: [0, 2.2, 6.2],
401
+ target: [0, 0.9, 0],
402
+ fov: 52,
403
+ },
404
+ top: {
405
+ position: [0, 7.4, 0.01],
406
+ target: [0, 0.4, 0],
407
+ fov: 38,
408
+ },
409
+ },
410
+ defaultCamera: "wide",
411
+ })
412
+ `;
413
+ }
414
+ async function writeProjectFile(projectDir, relativePath, contents) {
415
+ const filePath = path.join(projectDir, relativePath);
416
+ await fs.writeFile(filePath, contents, "utf8");
417
+ }
418
+ async function fileExists(filePath) {
419
+ try {
420
+ await fs.access(filePath);
421
+ return true;
422
+ } catch (error) {
423
+ if (error.code === "ENOENT") {
424
+ return false;
425
+ }
426
+ throw error;
427
+ }
428
+ }
429
+ async function findNearestProjectDirectory(startPath) {
430
+ let currentDir = path.resolve(startPath);
431
+ for (; ; ) {
432
+ if (await fileExists(path.join(currentDir, "package.json"))) {
433
+ return currentDir;
434
+ }
435
+ const parentDir = path.dirname(currentDir);
436
+ if (parentDir === currentDir) {
437
+ return null;
438
+ }
439
+ currentDir = parentDir;
440
+ }
441
+ }
442
+ async function installProjectDependencies(projectDir, packageManager) {
443
+ await runProcess(packageManager, ["install"], { cwd: projectDir });
444
+ }
445
+ function readEnvFileValue(contents, key) {
446
+ for (const rawLine of contents.split(/\r?\n/u)) {
447
+ const line = rawLine.trim();
448
+ if (!line || line.startsWith("#")) {
449
+ continue;
450
+ }
451
+ const separatorIndex = line.indexOf("=");
452
+ if (separatorIndex === -1) {
453
+ continue;
454
+ }
455
+ const envKey = line.slice(0, separatorIndex).trim();
456
+ if (envKey !== key) {
457
+ continue;
458
+ }
459
+ let value = line.slice(separatorIndex + 1).trim();
460
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
461
+ value = value.slice(1, -1);
462
+ }
463
+ return value;
464
+ }
465
+ return null;
466
+ }
467
+ async function readTargetFromEnv(projectDir) {
468
+ const envPath = path.join(projectDir, ".env");
469
+ try {
470
+ const envContents = await fs.readFile(envPath, "utf8");
471
+ return readEnvFileValue(envContents, TARGET_ENV_KEY);
472
+ } catch (error) {
473
+ if (error.code === "ENOENT") {
474
+ return null;
475
+ }
476
+ throw error;
477
+ }
478
+ }
479
+ async function initRoomProject(options) {
480
+ const roomSlug = slugifyRoomName(options.name);
481
+ if (!roomSlug) {
482
+ throw new Error("Room name must include at least one letter or number.");
483
+ }
484
+ const projectDir = path.resolve(options.directory ?? roomSlug);
485
+ const packageManager = options.packageManager ?? detectPackageManager();
486
+ await ensureDirectoryIsWritable(projectDir);
487
+ await writeProjectFile(
488
+ projectDir,
489
+ "package.json",
490
+ `${JSON.stringify(createRoomProjectPackageJson(roomSlug), null, 2)}
491
+ `
492
+ );
493
+ await writeProjectFile(
494
+ projectDir,
495
+ DEFAULT_TSCONFIG_FILE,
496
+ `${JSON.stringify(createRoomProjectTsconfig(), null, 2)}
497
+ `
498
+ );
499
+ await writeProjectFile(projectDir, ".env", `${TARGET_ENV_KEY}=${options.target}
500
+ `);
501
+ await writeProjectFile(projectDir, ".gitignore", "node_modules\n.env\n");
502
+ await writeProjectFile(projectDir, DEFAULT_ROOM_ENTRY_FILE, createRoomEntrySource(options.name, roomSlug));
503
+ if (!options.skipInstall) {
504
+ await installProjectDependencies(projectDir, packageManager);
505
+ }
506
+ return {
507
+ projectDir,
508
+ packageManager
509
+ };
510
+ }
511
+ async function publishRoomProject(projectDir, options = {}) {
512
+ const resolvedBaseDir = path.resolve(projectDir);
513
+ const entryFile = path.resolve(
514
+ resolvedBaseDir,
515
+ options.entryFile ?? DEFAULT_ROOM_ENTRY_FILE
516
+ );
517
+ const resolvedProjectDir = await findNearestProjectDirectory(path.dirname(entryFile)) ?? resolvedBaseDir;
518
+ const targetInput = options.target ?? await readTargetFromEnv(resolvedProjectDir);
519
+ if (!targetInput) {
520
+ throw new Error(
521
+ `Missing agent target. Pass --target or set ${TARGET_ENV_KEY} in ${path.join(resolvedProjectDir, ".env")}.`
522
+ );
523
+ }
524
+ const target = parseAgentTarget(targetInput);
525
+ const defaultProject = path.join(resolvedProjectDir, DEFAULT_TSCONFIG_FILE);
526
+ const project = options.project ? path.resolve(resolvedProjectDir, options.project) : await fileExists(defaultProject) ? defaultProject : void 0;
527
+ await typecheckRoomEntry(entryFile, {
528
+ project
529
+ });
530
+ const bundle = await bundleRoomEntry(entryFile);
531
+ const packageManifest = await readProjectPackageManifest(resolvedProjectDir);
532
+ const roomName = typeof packageManifest?.name === "string" && packageManifest.name.trim().length > 0 ? packageManifest.name.trim() : void 0;
533
+ await uploadBundle(target, bundle, { roomName });
534
+ const build2 = await createBuild(target);
535
+ const finalBuild = await waitForBuild(target, build2.buildJobId);
536
+ return {
537
+ target,
538
+ bundle,
539
+ build: build2,
540
+ finalBuild
541
+ };
542
+ }
543
+
544
+ export {
545
+ parseAgentTarget,
546
+ buildAgentTarget,
547
+ typecheckRoomEntry,
548
+ bundleRoomEntry,
549
+ readProjectPackageManifest,
550
+ uploadBundle,
551
+ createBuild,
552
+ waitForBuild,
553
+ readTargetFromEnv,
554
+ initRoomProject,
555
+ publishRoomProject
556
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ initRoomProject,
4
+ publishRoomProject
5
+ } from "./chunk-4BUZNJPJ.js";
6
+
7
+ // src/cli.ts
8
+ import path from "path";
9
+ import process from "process";
10
+ function printUsage() {
11
+ console.log(`rooms.sh init --name <room-name> --target <agent-room-url> [--directory <path>] [--skip-install]
12
+ rooms.sh publish [entry-file] [--target <agent-room-url>] [--project <tsconfig>]
13
+
14
+ Examples:
15
+ npx rooms.sh@latest init --name "Pink Loft" --target "http://localhost:3000/api/agent/rooms/ROOM_ID?token=rt_..."
16
+ npm run publish:room
17
+ rooms.sh publish ./room.ts --target "http://localhost:3000/api/agent/rooms/ROOM_ID?token=rt_..."
18
+ `);
19
+ }
20
+ function parseArgs(argv) {
21
+ const [command, ...rest] = argv;
22
+ const parsed = { command };
23
+ const positional = [];
24
+ for (let index = 0; index < rest.length; index += 1) {
25
+ const part = rest[index];
26
+ if (!part.startsWith("--")) {
27
+ positional.push(part);
28
+ continue;
29
+ }
30
+ switch (part) {
31
+ case "--skip-install":
32
+ parsed.skipInstall = true;
33
+ break;
34
+ case "--target":
35
+ case "--name":
36
+ case "--project":
37
+ case "--directory": {
38
+ const value = rest[index + 1];
39
+ if (!value) {
40
+ throw new Error(`Missing value for ${part}`);
41
+ }
42
+ if (part === "--target") {
43
+ parsed.target = value;
44
+ }
45
+ if (part === "--name") {
46
+ parsed.name = value;
47
+ }
48
+ if (part === "--project") {
49
+ parsed.project = value;
50
+ }
51
+ if (part === "--directory") {
52
+ parsed.directory = value;
53
+ }
54
+ index += 1;
55
+ break;
56
+ }
57
+ default:
58
+ throw new Error(`Unknown argument ${part}`);
59
+ }
60
+ }
61
+ if ((command === "publish" || command === "push") && positional[0]) {
62
+ parsed.entryFile = positional[0];
63
+ }
64
+ return parsed;
65
+ }
66
+ async function main() {
67
+ const args = parseArgs(process.argv.slice(2));
68
+ if (args.command === "init") {
69
+ if (!args.name || !args.target) {
70
+ printUsage();
71
+ process.exitCode = 1;
72
+ return;
73
+ }
74
+ console.log(`Scaffolding ${args.name}`);
75
+ const result = await initRoomProject({
76
+ name: args.name,
77
+ target: args.target,
78
+ directory: args.directory,
79
+ skipInstall: args.skipInstall
80
+ });
81
+ console.log(`Created project at ${result.projectDir}`);
82
+ if (!args.skipInstall) {
83
+ console.log(`Installed dependencies with ${result.packageManager}`);
84
+ }
85
+ const relativeProjectDir = path.relative(process.cwd(), result.projectDir);
86
+ const displayProjectDir = relativeProjectDir && relativeProjectDir !== "." && !relativeProjectDir.startsWith("..") ? relativeProjectDir : result.projectDir;
87
+ console.log(`Next steps:
88
+ cd ${displayProjectDir}
89
+ npm run publish:room`);
90
+ return;
91
+ }
92
+ if (args.command === "publish" || args.command === "push") {
93
+ const projectDir = process.cwd();
94
+ if (args.entryFile) {
95
+ console.log(`Publishing ${path.resolve(projectDir, args.entryFile)}`);
96
+ } else {
97
+ console.log(`Publishing ${path.resolve(projectDir, "room.ts")}`);
98
+ }
99
+ const result = await publishRoomProject(projectDir, {
100
+ entryFile: args.entryFile,
101
+ target: args.target,
102
+ project: args.project
103
+ });
104
+ console.log(`Bundle size: ${result.bundle.bundleSize} bytes`);
105
+ console.log(`Uploaded bundle to room ${result.target.roomId}`);
106
+ console.log(`Build job: ${result.build.buildJobId}`);
107
+ console.log(
108
+ `Build succeeded: ${String(
109
+ result.finalBuild.producedVersionId ?? result.build.buildJobId
110
+ )}`
111
+ );
112
+ if (typeof result.finalBuild.log === "string" && result.finalBuild.log.length > 0) {
113
+ console.log(result.finalBuild.log);
114
+ }
115
+ return;
116
+ }
117
+ printUsage();
118
+ process.exitCode = 1;
119
+ }
120
+ void main().catch((error) => {
121
+ console.error(error instanceof Error ? error.message : String(error));
122
+ process.exitCode = 1;
123
+ });
package/dist/lib.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ interface AgentTarget {
2
+ origin: string;
3
+ roomId: string;
4
+ token: string;
5
+ contextUrl: string;
6
+ sourceUrl: string;
7
+ buildUrl: string;
8
+ buildStatusUrlTemplate: string;
9
+ }
10
+ interface BundleResult {
11
+ bundleCode: string;
12
+ entryPoint: string;
13
+ bundleSize: number;
14
+ }
15
+ interface ProjectPackageManifest {
16
+ name?: string;
17
+ }
18
+ interface InitRoomProjectResult {
19
+ projectDir: string;
20
+ packageManager: PackageManagerName;
21
+ }
22
+ interface PublishRoomProjectResult {
23
+ target: AgentTarget;
24
+ bundle: BundleResult;
25
+ build: {
26
+ buildJobId: string;
27
+ queuedAt: number;
28
+ };
29
+ finalBuild: {
30
+ status?: string;
31
+ error?: string;
32
+ log?: string;
33
+ producedVersionId?: string;
34
+ };
35
+ }
36
+ type PackageManagerName = "npm" | "pnpm" | "yarn" | "bun";
37
+ declare function parseAgentTarget(target: string, options?: {
38
+ roomId?: string;
39
+ origin?: string;
40
+ }): AgentTarget;
41
+ declare function buildAgentTarget(origin: string, roomId: string, token: string): AgentTarget;
42
+ declare function typecheckRoomEntry(entryFile: string, options?: {
43
+ project?: string;
44
+ }): Promise<void>;
45
+ declare function bundleRoomEntry(entryFile: string): Promise<BundleResult>;
46
+ declare function readProjectPackageManifest(projectDir: string): Promise<ProjectPackageManifest | null>;
47
+ declare function uploadBundle(target: AgentTarget, bundle: BundleResult, options?: {
48
+ roomName?: string;
49
+ }): Promise<Record<string, unknown>>;
50
+ declare function createBuild(target: AgentTarget): Promise<{
51
+ buildJobId: string;
52
+ queuedAt: number;
53
+ }>;
54
+ declare function waitForBuild(target: AgentTarget, buildJobId: string, options?: {
55
+ pollIntervalMs?: number;
56
+ }): Promise<{
57
+ status?: string;
58
+ error?: string;
59
+ log?: string;
60
+ producedVersionId?: string;
61
+ }>;
62
+ declare function readTargetFromEnv(projectDir: string): Promise<string | null>;
63
+ declare function initRoomProject(options: {
64
+ name: string;
65
+ target: string;
66
+ directory?: string;
67
+ packageManager?: PackageManagerName;
68
+ skipInstall?: boolean;
69
+ }): Promise<{
70
+ projectDir: string;
71
+ packageManager: PackageManagerName;
72
+ }>;
73
+ declare function publishRoomProject(projectDir: string, options?: {
74
+ entryFile?: string;
75
+ target?: string;
76
+ project?: string;
77
+ }): Promise<{
78
+ target: AgentTarget;
79
+ bundle: BundleResult;
80
+ build: {
81
+ buildJobId: string;
82
+ queuedAt: number;
83
+ };
84
+ finalBuild: {
85
+ status?: string;
86
+ error?: string;
87
+ log?: string;
88
+ producedVersionId?: string;
89
+ };
90
+ }>;
91
+
92
+ export { type AgentTarget, type BundleResult, type InitRoomProjectResult, type ProjectPackageManifest, type PublishRoomProjectResult, buildAgentTarget, bundleRoomEntry, createBuild, initRoomProject, parseAgentTarget, publishRoomProject, readProjectPackageManifest, readTargetFromEnv, typecheckRoomEntry, uploadBundle, waitForBuild };
package/dist/lib.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildAgentTarget,
4
+ bundleRoomEntry,
5
+ createBuild,
6
+ initRoomProject,
7
+ parseAgentTarget,
8
+ publishRoomProject,
9
+ readProjectPackageManifest,
10
+ readTargetFromEnv,
11
+ typecheckRoomEntry,
12
+ uploadBundle,
13
+ waitForBuild
14
+ } from "./chunk-4BUZNJPJ.js";
15
+ export {
16
+ buildAgentTarget,
17
+ bundleRoomEntry,
18
+ createBuild,
19
+ initRoomProject,
20
+ parseAgentTarget,
21
+ publishRoomProject,
22
+ readProjectPackageManifest,
23
+ readTargetFromEnv,
24
+ typecheckRoomEntry,
25
+ uploadBundle,
26
+ waitForBuild
27
+ };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "rooms.sh",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "private": false,
6
+ "bin": {
7
+ "rooms.sh": "./dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "scripts": {
16
+ "build": "tsup",
17
+ "lint": "eslint",
18
+ "format": "prettier --write \"src/**/*.{ts,tsx}\"",
19
+ "typecheck": "tsc --noEmit"
20
+ },
21
+ "dependencies": {
22
+ "esbuild": "^0.25.11"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^25.1.0",
26
+ "tsup": "^8.5.0",
27
+ "typescript": "^5.9.3",
28
+ "vitest": "^3.2.4"
29
+ }
30
+ }