@myapihq/cli 2.1.0 → 2.2.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.
@@ -3,5 +3,5 @@ import { type Flags } from '../helpers.js';
3
3
  import type { Exposes } from '../exposes.js';
4
4
  export declare const SCHEMA: FlagSchema;
5
5
  export declare const EXPOSES: Exposes;
6
- export declare const HELP = "Usage: myapi auth <subcommand>\n\nAuthentication for your app's end users \u2014 a managed OIDC identity provider\n(\u00E0 la Kinde/Auth0). One auth tenant per org; register OIDC clients (apps)\nagainst it; sign users in via the hosted login page or the JS SDK; verify\ntokens locally against the tenant JWKS.\n\nEnd users sign in with managed Google, email/password, or magic links \u2014 pick\nwhich via `tenant create --connections`. (Operator/account commands moved to\n`myapi account`.)\n\nSubcommands:\n client Register and list OIDC clients (your apps)\n domain Serve auth on your own domain (auth.acme.com)\n tenant Show or create your org's OIDC auth tenant (+ sign-in methods)\n usage Monthly active users (MAU) for the current period";
6
+ export declare const HELP = "Usage: myapi auth <subcommand>\n\nAuthentication for your app's end users \u2014 a managed OIDC identity provider\n(\u00E0 la Kinde/Auth0). One auth tenant per org; register OIDC clients (apps)\nagainst it; sign users in via the hosted login page or the JS SDK; verify\ntokens locally against the tenant JWKS.\n\nEnd users sign in with managed Google, email/password, or magic links \u2014 pick\nwhich via `tenant create --connections`. (Operator/account commands moved to\n`myapi account`.)\n\nSubcommands:\n client Register, list, delete, and rotate OIDC clients (your apps)\n domain Serve auth on your own domain (auth.acme.com)\n tenant Show or create your org's OIDC auth tenant (+ sign-in methods)\n usage Monthly active users (MAU) for the current period";
7
7
  export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
@@ -1,5 +1,6 @@
1
1
  import { auth as sdkAuth } from '@myapihq/sdk';
2
2
  import { requireConfig } from '../config.js';
3
+ import { confirm, isNonInteractive } from '../prompt.js';
3
4
  import { success, error, info, printTable, printJson } from '../output.js';
4
5
  import { requireOrg } from '../helpers.js';
5
6
  // `myapi auth` — the END-USER auth product (my-auth-api): a managed OIDC IdP
@@ -21,8 +22,11 @@ export const EXPOSES = [
21
22
  'POST /auth/orgs/{org_id}/clients',
22
23
  'GET /auth/orgs/{org_id}/clients',
23
24
  'GET /auth/orgs/{org_id}/usage',
25
+ 'DELETE /auth/orgs/{org_id}/clients/{client_id}',
26
+ 'POST /auth/orgs/{org_id}/clients/{client_id}/rotate',
24
27
  'POST /auth/orgs/{org_id}/domain',
25
28
  'GET /auth/orgs/{org_id}/domain',
29
+ 'POST /auth/orgs/{org_id}/domain/verify',
26
30
  'DELETE /auth/orgs/{org_id}/domain',
27
31
  ];
28
32
  const SUBCOMMAND_USAGE = {
@@ -40,27 +44,35 @@ in against. One per org.
40
44
  'usage': `myapi auth usage [--org <id>] [--json]
41
45
 
42
46
  Monthly active users (MAU) for the current period — auth is billed per MAU.`,
43
- 'domain': `myapi auth domain [show|set|delete] [--domain <host>] [--org <id>] [--json]
47
+ 'domain': `myapi auth domain [show|set|verify|delete] [--domain <host>] [--org <id>] [--json]
44
48
 
45
49
  Serve auth on your own domain (e.g. auth.acme.com) instead of the default issuer.
50
+ Three steps: set → publish the ownership TXT → verify → publish the A record.
46
51
 
47
52
  myapi auth domain Show the current custom domain + status
48
- myapi auth domain set --domain auth.acme.com Register it (prints the DNS record to create)
53
+ myapi auth domain set --domain auth.acme.com Register it (prints a TXT challenge to publish)
54
+ myapi auth domain verify Check the TXT; on success prints the A record
49
55
  myapi auth domain delete Remove the custom domain
50
56
 
51
- After 'set', create the printed DNS A record; TLS provisions automatically
52
- (~30 min) and the domain becomes your issuer once active.`,
53
- 'client': `myapi auth client <list|create> [--org <id>] [--json]
57
+ Flow: 'set' returns a TXT record to prove ownership; create it, then 'verify'.
58
+ Once verified, create the printed A record; TLS provisions automatically (~30 min)
59
+ and the domain becomes your issuer when active.`,
60
+ 'client': `myapi auth client <list|create|delete|rotate> [--org <id>] [--json]
54
61
 
55
62
  OIDC clients are the apps that authenticate against your tenant.
56
63
 
57
64
  myapi auth client list
58
65
  myapi auth client create --name "My App" --type spa --redirect https://app.example.com/callback
66
+ myapi auth client delete <client_id> [--yes]
67
+ myapi auth client rotate <client_id>
59
68
 
60
- --name <n> Human label for the app (required)
69
+ --name <n> Human label for the app (required for create)
61
70
  --type spa|web spa = public (no secret); web = confidential (secret once)
62
- --redirect <urls> Allowed redirect URIs, comma-separated (required).
63
- Absolute https (or http://localhost for dev).`,
71
+ --redirect <urls> Allowed redirect URIs, comma-separated (required for create).
72
+ Absolute https (or http://localhost for dev).
73
+
74
+ delete Revoke a client (irreversible; stops authenticating immediately).
75
+ rotate Re-issue a 'web' client's secret (shown once; old secret stops working).`,
64
76
  };
65
77
  export const HELP = `Usage: myapi auth <subcommand>
66
78
 
@@ -74,7 +86,7 @@ which via \`tenant create --connections\`. (Operator/account commands moved to
74
86
  \`myapi account\`.)
75
87
 
76
88
  Subcommands:
77
- client Register and list OIDC clients (your apps)
89
+ client Register, list, delete, and rotate OIDC clients (your apps)
78
90
  domain Serve auth on your own domain (auth.acme.com)
79
91
  tenant Show or create your org's OIDC auth tenant (+ sign-in methods)
80
92
  usage Monthly active users (MAU) for the current period`;
@@ -165,25 +177,34 @@ async function usage(flags) {
165
177
  info(`Period: ${u.period}`);
166
178
  info(`Active users: ${u.active_users} MAU`);
167
179
  info(`Price: $${(u.price_cents_each / 100).toFixed(2)} per MAU`);
180
+ if (u.as_of)
181
+ info(`As of: ${u.as_of} (MAU is aggregated, not real-time)`);
168
182
  }
169
183
  async function domain(args, flags) {
170
184
  const action = args[0] || 'show';
171
185
  const config = requireConfig();
172
- const orgId = requireOrg(flags, config, 'myapi auth domain [show|set|delete] [--org <id>]');
186
+ const orgId = requireOrg(flags, config, 'myapi auth domain [show|set|verify|delete] [--org <id>]');
173
187
  const showDomain = (d) => {
174
188
  info(`Domain: ${d.domain}`);
175
189
  info(`Status: ${d.status}`);
176
- if (d.status !== 'active') {
190
+ if (d.status === 'awaiting_verification' && d.verification) {
191
+ info('');
192
+ info('Prove ownership — create this DNS record:');
193
+ info(` ${d.verification.type} ${d.verification.name} → ${d.verification.value}`);
194
+ info('\nThen run: myapi auth domain verify');
195
+ }
196
+ else if (d.dns) {
177
197
  info('');
178
- info(`DNS — create this record:`);
198
+ info('DNS — create this record:');
179
199
  info(` ${d.dns.type} ${d.dns.name} → ${d.dns.value}`);
180
- if (d.next)
181
- info(`\n${d.next}`);
182
200
  }
183
- else {
201
+ if (d.status === 'active') {
184
202
  info(`Issuer: ${d.issuer}`);
185
203
  info(`Login URL: ${d.login_url}`);
186
204
  }
205
+ else if (d.next) {
206
+ info(`\n${d.next}`);
207
+ }
187
208
  };
188
209
  if (action === 'show') {
189
210
  try {
@@ -215,6 +236,35 @@ async function domain(args, flags) {
215
236
  showDomain(d);
216
237
  return;
217
238
  }
239
+ if (action === 'verify') {
240
+ try {
241
+ const d = await sdkAuth.verifyDomain(config.api_key, orgId);
242
+ if (flags.json) {
243
+ printJson(d);
244
+ return;
245
+ }
246
+ if (d.status === 'awaiting_verification') {
247
+ // Shouldn't normally happen (backend returns 422 when unverified), but
248
+ // be defensive.
249
+ info('Still awaiting verification — the TXT record was not found.');
250
+ showDomain(d);
251
+ }
252
+ else {
253
+ success(`Ownership verified — domain is now "${d.status}".`);
254
+ showDomain(d);
255
+ }
256
+ }
257
+ catch (e) {
258
+ if (e?.code === 'DOMAIN_NOT_VERIFIED') {
259
+ const exp = e?.body?.expected;
260
+ error(`Ownership TXT record not found yet (DNS can take a few minutes). Create it and retry:\n ${exp ? `${exp.type} ${exp.name} → ${exp.value}` : 'see: myapi auth domain show'}`);
261
+ }
262
+ if (e?.code === 'NO_DOMAIN' || e?.status === 404)
263
+ error('No custom auth domain set. Set one first: myapi auth domain set --domain auth.example.com');
264
+ throw e;
265
+ }
266
+ return;
267
+ }
218
268
  if (action === 'delete' || action === 'remove') {
219
269
  const host = flags.domain || args[1];
220
270
  let target = host;
@@ -231,12 +281,12 @@ async function domain(args, flags) {
231
281
  success(`Custom auth domain removed: ${target}`);
232
282
  return;
233
283
  }
234
- error(`Unknown action "${action}". Use: myapi auth domain [show|set|delete]`);
284
+ error(`Unknown action "${action}". Use: myapi auth domain [show|set|verify|delete]`);
235
285
  }
236
286
  async function client(args, flags) {
237
287
  const action = args[0] || 'list';
238
288
  const config = requireConfig();
239
- const orgId = requireOrg(flags, config, 'myapi auth client <list|create> [--org <id>]');
289
+ const orgId = requireOrg(flags, config, 'myapi auth client <list|create|delete|rotate> [--org <id>]');
240
290
  if (action === 'list') {
241
291
  const res = await sdkAuth.listClients(config.api_key, orgId);
242
292
  if (flags.json) {
@@ -282,5 +332,48 @@ async function client(args, flags) {
282
332
  }
283
333
  return;
284
334
  }
285
- error(`Unknown action "${action}". Use: myapi auth client <list|create>`);
335
+ if (action === 'delete') {
336
+ const clientId = args[1];
337
+ if (!clientId)
338
+ error('Usage: myapi auth client delete <client_id> [--yes]');
339
+ // Destructive + irreversible — echo the exact client_id and confirm (the
340
+ // client stops authenticating immediately). --yes bypasses; refuse to hang
341
+ // in non-interactive contexts without it.
342
+ if (!flags.yes && !flags.y) {
343
+ if (isNonInteractive()) {
344
+ error(`Deleting client ${clientId} is irreversible. Re-run with --yes:\n myapi auth client delete ${clientId} --yes`);
345
+ }
346
+ const ok = await confirm(`› Delete OIDC client ${clientId}? It stops authenticating immediately. (y/N) `, false);
347
+ if (!ok) {
348
+ info('Cancelled.');
349
+ return;
350
+ }
351
+ }
352
+ const res = await sdkAuth.deleteClient(config.api_key, orgId, clientId);
353
+ if (flags.json) {
354
+ printJson(res);
355
+ return;
356
+ }
357
+ success(`Client deleted: ${clientId}`);
358
+ return;
359
+ }
360
+ if (action === 'rotate') {
361
+ const clientId = args[1];
362
+ if (!clientId)
363
+ error('Usage: myapi auth client rotate <client_id>');
364
+ const c = await sdkAuth.rotateClient(config.api_key, orgId, clientId);
365
+ if (flags.json) {
366
+ printJson(c);
367
+ return;
368
+ }
369
+ success(`Secret rotated for client: ${c.client_id || clientId}`);
370
+ info('The previous secret stops working immediately.');
371
+ if (c.client_secret) {
372
+ info('');
373
+ info('New client secret (shown ONCE — store it now, it cannot be retrieved again):');
374
+ info(` ${c.client_secret}`);
375
+ }
376
+ return;
377
+ }
378
+ error(`Unknown action "${action}". Use: myapi auth client <list|create|delete|rotate>`);
286
379
  }
@@ -13,7 +13,7 @@ The root service. It manages accounts, API keys, organizations, and billing. No
13
13
 
14
14
  ## Capabilities
15
15
  <!-- llm:start -->
16
- MyApiHQ is the platform's foundation. Every other service (domain, funnel, email, image, storage, pixel, webhook, workflow, url) requires both an `api_key` and (for org-scoped resources) an `org_id` minted here. Setup is one command — `myapi account setup` — which provisions an account, generates an api_key, creates a default org, and stores everything in `~/.myapi/config.json`. Subsequent commands pick up those defaults automatically.
16
+ MyApiHQ is the platform's foundation. Every other service (domain, funnel, auth, payments, fn, workflow, database, storage, email, webhook, crm, llm, image, pixel, url) requires both an `api_key` and (for org-scoped resources) an `org_id` minted here. Setup is one command — `myapi account setup` — which provisions an account, generates an api_key, creates a default org, and stores everything in `~/.myapi/config.json`. Subsequent commands pick up those defaults automatically.
17
17
 
18
18
  ```
19
19
  myapihq ──► org_id + api_key
@@ -39,9 +39,10 @@ its clients.
39
39
  returns a `client_secret` **once** — store it immediately. `--redirect` lists
40
40
  allowed callback URIs (absolute https, or http://localhost for dev).
41
41
  - **Usage** — `auth usage` shows monthly active users (auth is billed per MAU).
42
- - **Custom domain** — `auth domain set --domain auth.acme.com` serves auth on
43
- your own domain; it prints the DNS record, TLS provisions automatically, and
44
- the domain becomes your issuer once active.
42
+ - **Custom domain** — serve auth on `auth.acme.com`. Three steps: `auth domain
43
+ set --domain auth.acme.com` prints a **TXT ownership challenge**; publish it,
44
+ then `auth domain verify`; that prints the **A record** to publish, TLS
45
+ provisions automatically, and the domain becomes your issuer once active.
45
46
 
46
47
  **Sign-in methods** are chosen per tenant via
47
48
  `auth tenant create --connections google,password,magic` (default `google`):
@@ -61,8 +62,10 @@ CLI is only the management surface.
61
62
  | `myapi auth tenant create` | Create/enable the tenant (`--connections google,password,magic`; `--theme <json>`) |
62
63
  | `myapi auth client list` | List the OIDC clients (apps) registered to your tenant |
63
64
  | `myapi auth client create` | Register an OIDC client (`--name`, `--type spa\|web`, `--redirect`) |
64
- | `myapi auth usage` | Monthly active users (MAU) for the current period |
65
- | `myapi auth domain` | Show/set/delete a custom auth domain (`set --domain auth.acme.com`) |
65
+ | `myapi auth client delete <id>` | Revoke a client (irreversible); `--yes` to skip the confirm |
66
+ | `myapi auth client rotate <id>` | Re-issue a `web` client's secret (shown once) |
67
+ | `myapi auth usage` | Monthly active users (MAU) for the current period (`as_of` shows freshness) |
68
+ | `myapi auth domain` | Custom auth domain: `set` (TXT challenge) → `verify` → A record; also `show`/`delete` |
66
69
  <!-- generated:end -->
67
70
 
68
71
  ## Examples
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@myapihq/cli",
3
3
  "license": "Apache-2.0",
4
- "version": "2.1.0",
4
+ "version": "2.2.0",
5
5
  "description": "MyAPI command-line interface",
6
6
  "type": "module",
7
7
  "files": [
@@ -31,7 +31,7 @@
31
31
  "lint:skills:strict": "node scripts/copy-skills.js && node scripts/lint-skills.js --strict"
32
32
  },
33
33
  "dependencies": {
34
- "@myapihq/sdk": "^2.1.0"
34
+ "@myapihq/sdk": "^2.2.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^25.6.0",