@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.
Files changed (3) hide show
  1. package/README.md +19 -0
  2. package/dist/index.js +120 -0
  3. 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>]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@userland.fun/cli",
3
- "version": "0.3.2",
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",