@userland.fun/cli 0.3.2 → 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 +19 -0
- package/dist/index.js +120 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,6 +25,10 @@ userland auth status
|
|
|
25
25
|
userland auth save-key --api-key <api-key>
|
|
26
26
|
userland auth logout
|
|
27
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
|
|
28
32
|
userland accounts list
|
|
29
33
|
userland accounts use <account-id>
|
|
30
34
|
userland accounts status --account <account-id>
|
|
@@ -57,6 +61,10 @@ npm run userland -- auth status
|
|
|
57
61
|
npm run userland -- auth save-key --api-key <api-key>
|
|
58
62
|
npm run userland -- auth logout
|
|
59
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
|
|
60
68
|
npm run userland -- accounts list
|
|
61
69
|
npm run userland -- accounts use <account-id>
|
|
62
70
|
npm run userland -- accounts status --account <account-id>
|
|
@@ -82,6 +90,17 @@ npm run userland -- apps domains verify <app-id> <hostname>
|
|
|
82
90
|
|
|
83
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.
|
|
84
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
|
+
|
|
85
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.
|
|
86
105
|
|
|
87
106
|
Status and limits:
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,10 @@ async function main() {
|
|
|
31
31
|
await authCommand(args);
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
+
if (command === "api-keys") {
|
|
35
|
+
await apiKeysCommand(args);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
34
38
|
if (command === "accounts") {
|
|
35
39
|
await accountsCommand(args);
|
|
36
40
|
return;
|
|
@@ -123,6 +127,30 @@ async function authCommand(args) {
|
|
|
123
127
|
await logoutCommand(rest);
|
|
124
128
|
return;
|
|
125
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
|
+
}
|
|
126
154
|
usage(1);
|
|
127
155
|
}
|
|
128
156
|
async function accountsCommand(args) {
|
|
@@ -333,6 +361,77 @@ async function logoutCommand(args) {
|
|
|
333
361
|
console.log("local_credentials=removed");
|
|
334
362
|
console.log(`credentials_file=${filePath}`);
|
|
335
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
|
+
}
|
|
336
435
|
async function listAccountsCommand() {
|
|
337
436
|
const response = await apiFetch("/v0/accounts", {
|
|
338
437
|
method: "GET"
|
|
@@ -1027,6 +1126,22 @@ function parseAuthOptions(args) {
|
|
|
1027
1126
|
}
|
|
1028
1127
|
return options;
|
|
1029
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
|
+
}
|
|
1030
1145
|
function parseSecretSetOptions(args) {
|
|
1031
1146
|
const options = {};
|
|
1032
1147
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -1303,6 +1418,10 @@ function usage(exitCode) {
|
|
|
1303
1418
|
userland auth status
|
|
1304
1419
|
userland auth save-key --api-key <api-key> [--account <account-id>] [--api-base-url <url>] [--console-url <url>]
|
|
1305
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]
|
|
1306
1425
|
userland accounts list
|
|
1307
1426
|
userland accounts use <account-id>
|
|
1308
1427
|
userland accounts status [--account <account-id>]
|
|
@@ -1336,6 +1455,7 @@ function usage(exitCode) {
|
|
|
1336
1455
|
Aliases:
|
|
1337
1456
|
userland auth signup [--no-browser] [--email <email>] [--no-save]
|
|
1338
1457
|
userland auth login [--no-browser] [--email <email>] [--no-save]
|
|
1458
|
+
userland api-keys list|create|rename|revoke ...
|
|
1339
1459
|
userland publish <dir> [--app <app-id>] [--message <message>] [--account <account-id>]
|
|
1340
1460
|
userland releases <app-id> [--account <account-id>]
|
|
1341
1461
|
userland versions <app-id> [--account <account-id>]
|