runcloud 0.1.92 → 0.1.94

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.
@@ -3,7 +3,7 @@ import crypto from 'node:crypto';
3
3
  import { hostname } from 'node:os';
4
4
  import { spawn } from 'node:child_process';
5
5
  import { ApiClient, ApiError } from '../api.js';
6
- import { clearCredentials, credentialsDisplayPath, loadCredentials, loginCommandName, normalizeApiUrl, saveCredentials, } from '../config.js';
6
+ import { clearCredentials, credentialsDisplayPath, loadCredentials, loginCommandName, normalizeApiUrl, sanitizeAccount, saveCredentials, } from '../config.js';
7
7
  import { acquirePromptIO, ask, PromptAbortedError, select, } from '../prompt.js';
8
8
  import { AuthError, createApiKey, fetchProviders, isValidEmail, isValidOtp, normalizeOtp, sendEmailCode, verifyEmailCode, } from '../runCloudAuth.js';
9
9
  const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
@@ -91,7 +91,13 @@ export async function startCallbackServer(state, appOrigin, timeoutMs) {
91
91
  res.writeHead(200, { ...corsHeaders, 'Content-Type': 'application/json' });
92
92
  res.end(JSON.stringify({ ok: true }));
93
93
  clearTimeout(timer);
94
- resolvePayload(parsed);
94
+ const account = sanitizeAccount(parsed.account);
95
+ resolvePayload({
96
+ token: parsed.token,
97
+ expiresIn: parsed.expiresIn,
98
+ apiUrl: parsed.apiUrl,
99
+ ...(account ? { account } : {}),
100
+ });
95
101
  }
96
102
  catch {
97
103
  res.writeHead(400, corsHeaders);
@@ -186,7 +192,13 @@ export function parseManualCredential(blob) {
186
192
  throw new Error('Pasted credential is missing required fields (expected JSON with token, expiresIn, apiUrl). ' +
187
193
  'Copy the entire blob shown in the browser.');
188
194
  }
189
- return { token: c.token, expiresIn: c.expiresIn, apiUrl: c.apiUrl };
195
+ const account = sanitizeAccount(c.account);
196
+ return {
197
+ token: c.token,
198
+ expiresIn: c.expiresIn,
199
+ apiUrl: c.apiUrl,
200
+ ...(account ? { account } : {}),
201
+ };
190
202
  }
191
203
  export async function verifyRunCloudCredential(apiUrl, token) {
192
204
  try {
@@ -318,6 +330,7 @@ export function registerLogin(program) {
318
330
  apiUrl: parsed.apiUrl,
319
331
  appUrl: opts.appUrl,
320
332
  expiresAt: Math.floor(Date.now() / 1000) + parsed.expiresIn,
333
+ ...(parsed.account ? { account: parsed.account } : {}),
321
334
  });
322
335
  console.log('Logged in.');
323
336
  return;
@@ -380,6 +393,7 @@ export function registerLogin(program) {
380
393
  appUrl: opts.appUrl,
381
394
  expiresAt: Math.floor(Date.now() / 1000) + payload.expiresIn,
382
395
  revocable: true,
396
+ ...(payload.account ? { account: payload.account } : {}),
383
397
  });
384
398
  console.log(`Logged in. Credentials saved to ${credentialsDisplayPath()}`);
385
399
  return;
@@ -400,8 +414,10 @@ export function registerLogin(program) {
400
414
  apiUrl: payload.apiUrl,
401
415
  appUrl: opts.appUrl,
402
416
  expiresAt: Math.floor(Date.now() / 1000) + payload.expiresIn,
417
+ ...(payload.account ? { account: payload.account } : {}),
403
418
  });
404
- console.log(`Logged in. Credentials saved to ${credentialsDisplayPath()}`);
419
+ console.log(`Logged in${payload.account ? ` as ${payload.account}` : ''}. ` +
420
+ `Credentials saved to ${credentialsDisplayPath()}`);
405
421
  }
406
422
  finally {
407
423
  close();
@@ -358,8 +358,17 @@ export function registerSandbox(program) {
358
358
  else
359
359
  console.log(renderSandbox(exposedRecord, 'created'));
360
360
  }));
361
- withOutput(sandbox.command('list').description('List sandboxes').option('--state <state>', 'filter by state')).action((opts) => run(async () => {
362
- const qs = opts.state ? `?state=${encodeURIComponent(opts.state)}` : '';
361
+ withOutput(sandbox
362
+ .command('list')
363
+ .description('List sandboxes')
364
+ .option('--state <state>', 'filter by state')
365
+ .option('--name <name>', 'filter by name (exact)')).action((opts) => run(async () => {
366
+ const query = new URLSearchParams();
367
+ if (opts.state)
368
+ query.set('state', opts.state);
369
+ if (opts.name)
370
+ query.set('name', opts.name);
371
+ const qs = query.size ? `?${query}` : '';
363
372
  const api = runCloudApi();
364
373
  const [data, boxes] = await Promise.all([
365
374
  api.get(`/run-cloud/sandboxes${qs}`),
@@ -525,6 +534,17 @@ export function registerSandbox(program) {
525
534
  console.log(`${style.green('✓')} ${lifecycle.pastTense} ${id}`);
526
535
  }));
527
536
  }
537
+ withOutput(sandbox
538
+ .command('rename')
539
+ .description('Change a sandbox name (the label `list --name` filters on)')
540
+ .argument('<id>', 'sandbox id')
541
+ .argument('<name>', 'new name; pass "" to clear it')).action((id, name, opts) => run(async () => {
542
+ const record = await runCloudApi().post(`/run-cloud/sandboxes/${encodeURIComponent(id)}/name`, { name });
543
+ if (opts.json)
544
+ printJson(record);
545
+ else
546
+ console.log(`${style.green('✓')} Renamed ${id} to ${name || '(no name)'}`);
547
+ }));
528
548
  withOutput(sandbox
529
549
  .command('logs')
530
550
  .description('Show saved console logs (available after a sandbox is deleted)')
package/dist/config.js CHANGED
@@ -1,6 +1,15 @@
1
1
  import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { basename, join } from 'node:path';
4
+ const MAX_ACCOUNT_LENGTH = 320;
5
+ export function sanitizeAccount(value) {
6
+ if (typeof value !== 'string')
7
+ return undefined;
8
+ const trimmed = value.trim();
9
+ if (trimmed.length === 0 || trimmed.length > MAX_ACCOUNT_LENGTH)
10
+ return undefined;
11
+ return trimmed;
12
+ }
4
13
  export function isRunCloud() {
5
14
  const names = [process.argv[1], process.execPath].map((path) => basename(path ?? ''));
6
15
  return names.some((name) => name === 'runcloud' || name === 'run-cloud' || name === 'run-cloud.js');
@@ -1,4 +1,4 @@
1
- import { normalizeApiUrl } from "./config.js";
1
+ import { normalizeApiUrl, sanitizeAccount } from "./config.js";
2
2
  export async function runDeviceLogin(deps) {
3
3
  const fetchFn = deps.fetchFn ?? fetch;
4
4
  const log = deps.log ?? ((m) => console.log(m));
@@ -32,7 +32,13 @@ export async function runDeviceLogin(deps) {
32
32
  });
33
33
  if (res.ok) {
34
34
  const body = (await res.json());
35
- return { token: body.token, apiUrl: body.apiUrl, expiresIn: body.expiresIn };
35
+ const account = sanitizeAccount(body.account);
36
+ return {
37
+ token: body.token,
38
+ apiUrl: body.apiUrl,
39
+ expiresIn: body.expiresIn,
40
+ ...(account ? { account } : {}),
41
+ };
36
42
  }
37
43
  const { error } = (await res.json().catch(() => ({ error: "unknown" })));
38
44
  if (error === "authorization_pending")
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = '0.1.92';
1
+ export const CLI_VERSION = '0.1.94';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.1.92",
3
+ "version": "0.1.94",
4
4
  "description": "Create and control run.cloud remote mobile simulators and cloud sandboxes",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -54,11 +54,13 @@ runcloud sandbox exec "$SANDBOX_ID" "npm install && npm test"
54
54
  The main lifecycle commands are:
55
55
 
56
56
  - `runcloud sandbox create`
57
- - `runcloud sandbox list [--state <state>]`
57
+ - `runcloud sandbox list [--state <state>] [--name <name>]`
58
58
  - `runcloud sandbox get <id>`
59
59
  - `runcloud sandbox exec <id> <cmd...>`
60
60
  - `runcloud sandbox shell <id>`
61
61
  - `runcloud sandbox pause|resume <id>`
62
+ - `runcloud sandbox rename <id> <name>` — relabel an existing sandbox; pair with
63
+ `list --name` to find a sandbox again instead of creating a second one
62
64
  - `runcloud sandbox logs <id> [--lines <n>]`
63
65
  - `runcloud sandbox metrics <id> [--range <range>] [--watch]`
64
66
  - `runcloud sandbox rm <id>`