runcloud 0.1.91 → 0.1.93
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.
- package/README.md +18 -0
- package/dist/commands/login.js +51 -8
- package/dist/config.js +9 -0
- package/dist/deviceLogin.js +8 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ curl -fsSL https://run.cloud/install | sh
|
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
runcloud login # sign in with email, Google, or GitHub; stores a token in ~/.run-cloud/credentials
|
|
31
|
+
runcloud whoami # show the stored credential: account, API, expiry, file
|
|
31
32
|
runcloud logout # delete the stored credential
|
|
32
33
|
runcloud account # show your entitlement and metered balance
|
|
33
34
|
```
|
|
@@ -37,6 +38,23 @@ show a short code to confirm, so login works over SSH and on headless machines t
|
|
|
37
38
|
pick with `--email`, `--google`, `--github`, or `--browser` to skip the prompt.
|
|
38
39
|
|
|
39
40
|
The token expires — re-run `runcloud login` when a command reports you're logged out.
|
|
41
|
+
Logging in while a valid session exists reports that session and offers to keep it,
|
|
42
|
+
so running it out of habit no longer replaces a working credential.
|
|
43
|
+
|
|
44
|
+
`runcloud whoami` answers which credential is in play without calling the API, and
|
|
45
|
+
exits non-zero when there is none or it has expired:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
Account you@example.com
|
|
49
|
+
API https://api.run.cloud
|
|
50
|
+
Expires never
|
|
51
|
+
Credential ~/.run-cloud/credentials
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
It reports whatever would actually be sent, including a credential supplied through
|
|
55
|
+
`RUN_CLOUD_API_KEY`, which takes precedence over the saved file. The account line is
|
|
56
|
+
only filled in when the sign-in method revealed an address — email sign-in does, the
|
|
57
|
+
browser and device handoffs do not.
|
|
40
58
|
|
|
41
59
|
## Remote simulators
|
|
42
60
|
|
package/dist/commands/login.js
CHANGED
|
@@ -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, 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
|
-
|
|
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
|
-
|
|
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 {
|
|
@@ -215,12 +227,25 @@ export async function revokeRunCloudCredential(apiUrl, token) {
|
|
|
215
227
|
'revoke it from the run.cloud dashboard.');
|
|
216
228
|
}
|
|
217
229
|
}
|
|
230
|
+
function expiresAtLabel(creds) {
|
|
231
|
+
if (creds.expiresAt >= Number.MAX_SAFE_INTEGER)
|
|
232
|
+
return null;
|
|
233
|
+
return new Date(creds.expiresAt * 1000).toLocaleString();
|
|
234
|
+
}
|
|
218
235
|
export function existingLoginSummary(creds) {
|
|
219
236
|
const who = creds.account ? ` as ${creds.account}` : '';
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
237
|
+
const expires = expiresAtLabel(creds);
|
|
238
|
+
return `You are already logged in${who} (${creds.apiUrl}${expires ? `, valid until ${expires}` : ''}).`;
|
|
239
|
+
}
|
|
240
|
+
export function whoamiReport(creds) {
|
|
241
|
+
const rows = [
|
|
242
|
+
['Account', creds.account ?? 'not recorded (browser sign-in never sees an address)'],
|
|
243
|
+
['API', creds.apiUrl],
|
|
244
|
+
['Expires', expiresAtLabel(creds) ?? 'never'],
|
|
245
|
+
['Credential', credentialsDisplayPath()],
|
|
246
|
+
];
|
|
247
|
+
const width = Math.max(...rows.map(([label]) => label.length));
|
|
248
|
+
return rows.map(([label, value]) => `${label.padEnd(width + 2)}${value}`).join('\n');
|
|
224
249
|
}
|
|
225
250
|
export async function confirmReplaceExistingLogin(creds) {
|
|
226
251
|
if (!creds)
|
|
@@ -305,6 +330,7 @@ export function registerLogin(program) {
|
|
|
305
330
|
apiUrl: parsed.apiUrl,
|
|
306
331
|
appUrl: opts.appUrl,
|
|
307
332
|
expiresAt: Math.floor(Date.now() / 1000) + parsed.expiresIn,
|
|
333
|
+
...(parsed.account ? { account: parsed.account } : {}),
|
|
308
334
|
});
|
|
309
335
|
console.log('Logged in.');
|
|
310
336
|
return;
|
|
@@ -367,6 +393,7 @@ export function registerLogin(program) {
|
|
|
367
393
|
appUrl: opts.appUrl,
|
|
368
394
|
expiresAt: Math.floor(Date.now() / 1000) + payload.expiresIn,
|
|
369
395
|
revocable: true,
|
|
396
|
+
...(payload.account ? { account: payload.account } : {}),
|
|
370
397
|
});
|
|
371
398
|
console.log(`Logged in. Credentials saved to ${credentialsDisplayPath()}`);
|
|
372
399
|
return;
|
|
@@ -387,8 +414,10 @@ export function registerLogin(program) {
|
|
|
387
414
|
apiUrl: payload.apiUrl,
|
|
388
415
|
appUrl: opts.appUrl,
|
|
389
416
|
expiresAt: Math.floor(Date.now() / 1000) + payload.expiresIn,
|
|
417
|
+
...(payload.account ? { account: payload.account } : {}),
|
|
390
418
|
});
|
|
391
|
-
console.log(`Logged in.
|
|
419
|
+
console.log(`Logged in${payload.account ? ` as ${payload.account}` : ''}. ` +
|
|
420
|
+
`Credentials saved to ${credentialsDisplayPath()}`);
|
|
392
421
|
}
|
|
393
422
|
finally {
|
|
394
423
|
close();
|
|
@@ -404,6 +433,20 @@ export function registerLogin(program) {
|
|
|
404
433
|
process.exitCode = 1;
|
|
405
434
|
}
|
|
406
435
|
});
|
|
436
|
+
program
|
|
437
|
+
.command('whoami')
|
|
438
|
+
.description(isRunCloud
|
|
439
|
+
? 'Show which run.cloud account this machine is signed in as'
|
|
440
|
+
: 'Show which Newly account this machine is signed in as')
|
|
441
|
+
.action(() => {
|
|
442
|
+
const creds = loadCredentials();
|
|
443
|
+
if (!creds) {
|
|
444
|
+
console.error(`Not logged in. Run \`${loginCommandName()}\`.`);
|
|
445
|
+
process.exitCode = 1;
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
console.log(whoamiReport(creds));
|
|
449
|
+
});
|
|
407
450
|
program
|
|
408
451
|
.command('logout')
|
|
409
452
|
.description(isRunCloud
|
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');
|
package/dist/deviceLogin.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
1
|
+
export const CLI_VERSION = '0.1.93';
|