@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.
Files changed (3) hide show
  1. package/README.md +21 -0
  2. package/dist/index.js +128 -0
  3. 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>]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@userland.fun/cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Userland command-line tools for publishing and operating apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",