@userland.fun/cli 0.3.1 → 0.4.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.
- package/README.md +21 -0
- package/dist/index.js +128 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ npm install -g @userland.fun/cli
|
|
|
17
17
|
Then run:
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
+
userland --version
|
|
20
21
|
userland login
|
|
21
22
|
userland login --no-browser
|
|
22
23
|
userland signup
|
|
@@ -24,6 +25,10 @@ userland auth status
|
|
|
24
25
|
userland auth save-key --api-key <api-key>
|
|
25
26
|
userland auth logout
|
|
26
27
|
userland auth logout --revoke
|
|
28
|
+
userland auth api-keys list
|
|
29
|
+
userland auth api-keys create --name "CI deploy key"
|
|
30
|
+
userland auth api-keys rename <api-key-id> --name "Production deploy"
|
|
31
|
+
userland auth api-keys revoke <api-key-id> --yes
|
|
27
32
|
userland accounts list
|
|
28
33
|
userland accounts use <account-id>
|
|
29
34
|
userland accounts status --account <account-id>
|
|
@@ -48,6 +53,7 @@ userland apps domains verify <app-id> <hostname>
|
|
|
48
53
|
From this repo, the same commands can be run from source:
|
|
49
54
|
|
|
50
55
|
```sh
|
|
56
|
+
npm run userland -- --version
|
|
51
57
|
npm run userland -- login
|
|
52
58
|
npm run userland -- login --no-browser
|
|
53
59
|
npm run userland -- signup
|
|
@@ -55,6 +61,10 @@ npm run userland -- auth status
|
|
|
55
61
|
npm run userland -- auth save-key --api-key <api-key>
|
|
56
62
|
npm run userland -- auth logout
|
|
57
63
|
npm run userland -- auth logout --revoke
|
|
64
|
+
npm run userland -- auth api-keys list
|
|
65
|
+
npm run userland -- auth api-keys create --name "CI deploy key"
|
|
66
|
+
npm run userland -- auth api-keys rename <api-key-id> --name "Production deploy"
|
|
67
|
+
npm run userland -- auth api-keys revoke <api-key-id> --yes
|
|
58
68
|
npm run userland -- accounts list
|
|
59
69
|
npm run userland -- accounts use <account-id>
|
|
60
70
|
npm run userland -- accounts status --account <account-id>
|
|
@@ -80,6 +90,17 @@ npm run userland -- apps domains verify <app-id> <hostname>
|
|
|
80
90
|
|
|
81
91
|
The CLI does not store platform passwords. App commands prefer `USERLAND_API_KEY` when it is set, then fall back to the saved API key. `auth save-key` remains available for CI, support, and manually copied API keys.
|
|
82
92
|
|
|
93
|
+
API key lifecycle commands use the same authenticated management endpoints as the browser console:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
userland auth api-keys list
|
|
97
|
+
userland auth api-keys create --name "CI deploy key"
|
|
98
|
+
userland auth api-keys rename key_... --name "Production deploy"
|
|
99
|
+
userland auth api-keys revoke key_... --yes
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`auth api-keys list` prints metadata only. `auth api-keys create` prints the raw key exactly once and does not write it to `~/.userland/credentials.json`. `auth api-keys revoke` prompts in interactive terminals unless `--yes` is passed.
|
|
103
|
+
|
|
83
104
|
Most users do not need to select an account. If no account is selected, the API uses the actor's default account. Team, client, and agency workflows can select an account with `--account <account-id>`, `USERLAND_ACCOUNT_ID`, or `userland accounts use <account-id>`. Platform account members manage apps, releases, secrets, billing, and settings; they are separate from app users inside a published app.
|
|
84
105
|
|
|
85
106
|
Status and limits:
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,10 @@ async function main() {
|
|
|
19
19
|
if (isHelpCommand(command)) {
|
|
20
20
|
usage(0);
|
|
21
21
|
}
|
|
22
|
+
if (isVersionCommand(command)) {
|
|
23
|
+
console.log(CLI_VERSION);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
22
26
|
if (command === "apps") {
|
|
23
27
|
await appsCommand(args);
|
|
24
28
|
return;
|
|
@@ -27,6 +31,10 @@ async function main() {
|
|
|
27
31
|
await authCommand(args);
|
|
28
32
|
return;
|
|
29
33
|
}
|
|
34
|
+
if (command === "api-keys") {
|
|
35
|
+
await apiKeysCommand(args);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
30
38
|
if (command === "accounts") {
|
|
31
39
|
await accountsCommand(args);
|
|
32
40
|
return;
|
|
@@ -119,6 +127,30 @@ async function authCommand(args) {
|
|
|
119
127
|
await logoutCommand(rest);
|
|
120
128
|
return;
|
|
121
129
|
}
|
|
130
|
+
if (subcommand === "api-keys") {
|
|
131
|
+
await apiKeysCommand(rest);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
usage(1);
|
|
135
|
+
}
|
|
136
|
+
async function apiKeysCommand(args) {
|
|
137
|
+
const [subcommand, keyId, ...rest] = args;
|
|
138
|
+
if (subcommand === "list") {
|
|
139
|
+
await apiKeysListCommand();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (subcommand === "create") {
|
|
143
|
+
await apiKeysCreateCommand([keyId, ...rest].filter((value) => value !== undefined));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (subcommand === "rename" && keyId) {
|
|
147
|
+
await apiKeysRenameCommand(keyId, rest);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (subcommand === "revoke" && keyId) {
|
|
151
|
+
await apiKeysRevokeCommand(keyId, rest);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
122
154
|
usage(1);
|
|
123
155
|
}
|
|
124
156
|
async function accountsCommand(args) {
|
|
@@ -329,6 +361,77 @@ async function logoutCommand(args) {
|
|
|
329
361
|
console.log("local_credentials=removed");
|
|
330
362
|
console.log(`credentials_file=${filePath}`);
|
|
331
363
|
}
|
|
364
|
+
async function apiKeysListCommand() {
|
|
365
|
+
const response = await apiFetch("/v0/auth/api-keys", {
|
|
366
|
+
method: "GET"
|
|
367
|
+
});
|
|
368
|
+
for (const key of response.api_keys) {
|
|
369
|
+
console.log([
|
|
370
|
+
key.api_key_id,
|
|
371
|
+
key.revoked_at ? "revoked" : "active",
|
|
372
|
+
key.created_at,
|
|
373
|
+
key.last_used_at ?? "never",
|
|
374
|
+
key.key_prefix,
|
|
375
|
+
key.name ?? ""
|
|
376
|
+
].join("\t"));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async function apiKeysCreateCommand(args) {
|
|
380
|
+
const options = parseApiKeyOptions(args);
|
|
381
|
+
if (!options.name) {
|
|
382
|
+
throw new Error("--name is required.");
|
|
383
|
+
}
|
|
384
|
+
const response = await apiFetch("/v0/auth/api-keys", {
|
|
385
|
+
method: "POST",
|
|
386
|
+
body: JSON.stringify({ name: options.name })
|
|
387
|
+
});
|
|
388
|
+
console.log(`Created API key ${response.api_key_id}`);
|
|
389
|
+
console.log(`Name: ${response.key.name ?? ""}`);
|
|
390
|
+
console.log(`Prefix: ${response.key_prefix}`);
|
|
391
|
+
console.log("");
|
|
392
|
+
console.log("API key:");
|
|
393
|
+
console.log(response.api_key);
|
|
394
|
+
console.log("");
|
|
395
|
+
console.log(response.warning);
|
|
396
|
+
}
|
|
397
|
+
async function apiKeysRenameCommand(apiKeyId, args) {
|
|
398
|
+
const options = parseApiKeyOptions(args);
|
|
399
|
+
if (!options.name) {
|
|
400
|
+
throw new Error("--name is required.");
|
|
401
|
+
}
|
|
402
|
+
const response = await apiFetch(`/v0/auth/api-keys/${encodeURIComponent(apiKeyId)}`, {
|
|
403
|
+
method: "PATCH",
|
|
404
|
+
body: JSON.stringify({ name: options.name })
|
|
405
|
+
});
|
|
406
|
+
console.log(`Renamed API key ${response.api_key_id}`);
|
|
407
|
+
console.log(`Name: ${response.key.name ?? ""}`);
|
|
408
|
+
}
|
|
409
|
+
async function apiKeysRevokeCommand(apiKeyId, args) {
|
|
410
|
+
const options = parseApiKeyOptions(args);
|
|
411
|
+
const credentials = await readCredentials();
|
|
412
|
+
if (credentials?.api_key_id === apiKeyId) {
|
|
413
|
+
console.log("This is the API key saved for the current CLI credentials. Subsequent saved-credential commands may fail.");
|
|
414
|
+
}
|
|
415
|
+
if (!options.yes) {
|
|
416
|
+
if (!process.stdin.isTTY) {
|
|
417
|
+
throw new Error("Pass --yes to revoke an API key non-interactively.");
|
|
418
|
+
}
|
|
419
|
+
const answer = await promptLine(`Revoke API key ${apiKeyId}? Type yes to continue: `);
|
|
420
|
+
if (answer.toLowerCase() !== "yes") {
|
|
421
|
+
console.log("revocation=cancelled");
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const response = await apiFetch(`/v0/auth/api-keys/${encodeURIComponent(apiKeyId)}`, {
|
|
426
|
+
method: "DELETE"
|
|
427
|
+
});
|
|
428
|
+
if (response.revoked) {
|
|
429
|
+
console.log(`Revoked API key ${response.api_key_id}`);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
console.log(`API key ${response.api_key_id} was already revoked.`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
332
435
|
async function listAccountsCommand() {
|
|
333
436
|
const response = await apiFetch("/v0/accounts", {
|
|
334
437
|
method: "GET"
|
|
@@ -1023,6 +1126,22 @@ function parseAuthOptions(args) {
|
|
|
1023
1126
|
}
|
|
1024
1127
|
return options;
|
|
1025
1128
|
}
|
|
1129
|
+
function parseApiKeyOptions(args) {
|
|
1130
|
+
const options = {};
|
|
1131
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1132
|
+
const arg = args[index];
|
|
1133
|
+
if (arg === "--name") {
|
|
1134
|
+
options.name = args[++index];
|
|
1135
|
+
}
|
|
1136
|
+
else if (arg === "--yes" || arg === "-y") {
|
|
1137
|
+
options.yes = true;
|
|
1138
|
+
}
|
|
1139
|
+
else {
|
|
1140
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return options;
|
|
1144
|
+
}
|
|
1026
1145
|
function parseSecretSetOptions(args) {
|
|
1027
1146
|
const options = {};
|
|
1028
1147
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -1287,14 +1406,22 @@ function docsUrlForError(message) {
|
|
|
1287
1406
|
function isHelpCommand(command) {
|
|
1288
1407
|
return command === "--help" || command === "-h" || command === "help";
|
|
1289
1408
|
}
|
|
1409
|
+
function isVersionCommand(command) {
|
|
1410
|
+
return command === "--version" || command === "version";
|
|
1411
|
+
}
|
|
1290
1412
|
function usage(exitCode) {
|
|
1291
1413
|
const message = `Usage:
|
|
1292
1414
|
userland [--help]
|
|
1415
|
+
userland --version
|
|
1293
1416
|
userland signup [--no-browser] [--email <email>] [--api-base-url <url>] [--console-url <url>] [--no-save]
|
|
1294
1417
|
userland login [--no-browser] [--email <email>] [--api-base-url <url>] [--console-url <url>] [--no-save]
|
|
1295
1418
|
userland auth status
|
|
1296
1419
|
userland auth save-key --api-key <api-key> [--account <account-id>] [--api-base-url <url>] [--console-url <url>]
|
|
1297
1420
|
userland auth logout [--revoke]
|
|
1421
|
+
userland auth api-keys list
|
|
1422
|
+
userland auth api-keys create --name <name>
|
|
1423
|
+
userland auth api-keys rename <api-key-id> --name <name>
|
|
1424
|
+
userland auth api-keys revoke <api-key-id> [--yes]
|
|
1298
1425
|
userland accounts list
|
|
1299
1426
|
userland accounts use <account-id>
|
|
1300
1427
|
userland accounts status [--account <account-id>]
|
|
@@ -1328,6 +1455,7 @@ function usage(exitCode) {
|
|
|
1328
1455
|
Aliases:
|
|
1329
1456
|
userland auth signup [--no-browser] [--email <email>] [--no-save]
|
|
1330
1457
|
userland auth login [--no-browser] [--email <email>] [--no-save]
|
|
1458
|
+
userland api-keys list|create|rename|revoke ...
|
|
1331
1459
|
userland publish <dir> [--app <app-id>] [--message <message>] [--account <account-id>]
|
|
1332
1460
|
userland releases <app-id> [--account <account-id>]
|
|
1333
1461
|
userland versions <app-id> [--account <account-id>]
|