@myapihq/cli 1.2.5 → 1.2.7

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 (42) hide show
  1. package/dist/commands/billing.d.ts +2 -0
  2. package/dist/commands/billing.js +94 -2
  3. package/dist/commands/container-validation.test.d.ts +1 -0
  4. package/dist/commands/container-validation.test.js +45 -0
  5. package/dist/commands/container.d.ts +15 -0
  6. package/dist/commands/container.js +242 -0
  7. package/dist/commands/domain.js +5 -0
  8. package/dist/commands/email/campaign.js +16 -1
  9. package/dist/commands/email/message.js +26 -3
  10. package/dist/commands/email/template.js +8 -1
  11. package/dist/commands/fn.d.ts +3 -0
  12. package/dist/commands/fn.js +115 -24
  13. package/dist/commands/funnel.d.ts +1 -0
  14. package/dist/commands/funnel.js +83 -0
  15. package/dist/commands/keys-validation.test.d.ts +1 -0
  16. package/dist/commands/keys-validation.test.js +87 -0
  17. package/dist/commands/keys.d.ts +6 -2
  18. package/dist/commands/keys.js +189 -55
  19. package/dist/commands/payments-validation.test.d.ts +1 -0
  20. package/dist/commands/payments-validation.test.js +31 -0
  21. package/dist/commands/payments.d.ts +13 -0
  22. package/dist/commands/payments.js +219 -0
  23. package/dist/commands/pixel.js +15 -2
  24. package/dist/commands/update.d.ts +1 -1
  25. package/dist/commands/update.js +68 -45
  26. package/dist/commands/webhook.js +10 -1
  27. package/dist/completion.d.ts +3 -1
  28. package/dist/completion.js +160 -55
  29. package/dist/exposes.test.js +2 -0
  30. package/dist/index.js +34 -13
  31. package/dist/sdk-billing-usage.test.d.ts +1 -0
  32. package/dist/sdk-billing-usage.test.js +74 -0
  33. package/dist/sdk-container.test.d.ts +1 -0
  34. package/dist/sdk-container.test.js +115 -0
  35. package/dist/sdk-function.test.js +73 -6
  36. package/dist/sdk-funnel-publish.test.d.ts +1 -0
  37. package/dist/sdk-funnel-publish.test.js +89 -0
  38. package/dist/sdk-iam.test.d.ts +1 -0
  39. package/dist/sdk-iam.test.js +190 -0
  40. package/dist/sdk-payments.test.d.ts +1 -0
  41. package/dist/sdk-payments.test.js +139 -0
  42. package/package.json +4 -4
@@ -1,47 +1,215 @@
1
1
  import { hq } from '@myapihq/sdk';
2
2
  import { requireConfig } from '../config.js';
3
3
  import { success, error, printTable, info, printJson } from '../output.js';
4
- import { formatDate } from '../utils.js';
5
- import { ask } from '../prompt.js';
4
+ import { ask, confirm, isNonInteractive } from '../prompt.js';
6
5
  import { requireArg } from '../helpers.js';
7
6
  export const SCHEMA = {
8
7
  name: 'string',
8
+ // Capability IAM (design-iam-capability-keys-2026-05-15).
9
+ grant: 'string', // slot:access list, e.g. "email:write,crm:read"
10
+ org: 'string', // lock the key to one org id
11
+ 'spend-cap': 'string', // per-key spend ceiling, in dollars
12
+ kind: 'string', // revoke-all filter: function|manual|account
9
13
  };
10
14
  export const EXPOSES = [
11
15
  'POST /hq/account/create/key',
12
16
  'GET /hq/account/keys',
17
+ 'POST /hq/account/keys/revoke-all',
13
18
  'DELETE /hq/account/delete/key/{key_id}',
14
19
  ];
15
- const KEYS_HEADER = `Usage: myapi keys <subcommand>
20
+ // "*" is a valid grant target (wildcard slot) but not a named slot.
21
+ const VALID_GRANT_TARGETS = new Set([...hq.GRANTABLE_SLOTS, '*']);
22
+ const KEY_KINDS = ['function', 'manual', 'account'];
23
+ // Parses a `--grant` string into a Grants map. Accepts comma-separated
24
+ // `slot` (write implied) or `slot:access` entries. Validates every slot
25
+ // against the closed vocabulary so a typo fails before the network call.
26
+ //
27
+ // `_parseGrants` is the pure form — returns the Grants map on success or an
28
+ // error-message string on failure (suitable for unit tests). `parseGrants`
29
+ // is the thin wrapper that calls `error()` (which exits) on failure.
30
+ export function _parseGrants(raw) {
31
+ const grants = {};
32
+ for (const part of raw.split(',').map(s => s.trim()).filter(Boolean)) {
33
+ const [slot, accessRaw] = part.includes(':') ? part.split(':', 2) : [part, 'write'];
34
+ if (!VALID_GRANT_TARGETS.has(slot)) {
35
+ return `Unknown slot "${slot}" in --grant. Valid slots: ${[...hq.GRANTABLE_SLOTS].join(', ')} (or "*").`;
36
+ }
37
+ if (accessRaw !== 'read' && accessRaw !== 'write') {
38
+ return `Invalid access "${accessRaw}" for "${slot}" in --grant. Use read or write.`;
39
+ }
40
+ grants[slot] = accessRaw;
41
+ }
42
+ if (Object.keys(grants).length === 0) {
43
+ return '--grant was empty. Example: --grant email:write,crm:read (or --grant email for write)';
44
+ }
45
+ return grants;
46
+ }
47
+ function parseGrants(raw) {
48
+ const r = _parseGrants(raw);
49
+ if (typeof r === 'string')
50
+ error(r);
51
+ return r;
52
+ }
53
+ // Dollars → cents. The CLI surface is dollars (matches `billing topup`);
54
+ // the API is cents. Pure form returns cents or an error-message string.
55
+ export function _dollarsToCents(raw) {
56
+ // Number('') and Number(' ') are both 0 — reject empty input explicitly
57
+ // so a missing value never silently becomes a $0 cap.
58
+ const trimmed = raw.trim();
59
+ const n = Number(trimmed);
60
+ if (trimmed === '' || !Number.isFinite(n) || n < 0) {
61
+ return `"${raw}" is not a valid dollar amount — use a non-negative number, e.g. 50 or 9.99.`;
62
+ }
63
+ return Math.round(n * 100);
64
+ }
65
+ function dollarsToCents(raw, flagName) {
66
+ const r = _dollarsToCents(raw);
67
+ if (typeof r === 'string')
68
+ error(`${flagName}: ${r}`);
69
+ return r;
70
+ }
71
+ function grantsSummary(g) {
72
+ const entries = Object.entries(g);
73
+ if (entries.length === 0)
74
+ return 'none';
75
+ // write is the common case — show bare slot; annotate only read.
76
+ return entries.map(([s, a]) => (a === 'write' ? s : `${s}:${a}`)).join(',');
77
+ }
78
+ // Plain-English one-liner — the "what can this key do" the IAM model promises
79
+ // you can answer by reading the key.
80
+ function describeKey(k) {
81
+ const parts = [k.org_id ? `org ${k.org_id}` : 'all orgs', grantsSummary(k.grants)];
82
+ if (k.spend_cap_cents != null) {
83
+ parts.push(`cap $${(k.spend_cap_cents / 100).toFixed(2)}/${k.spend_cap_period}`);
84
+ }
85
+ return parts.join(' · ');
86
+ }
87
+ // ── Subcommands ──────────────────────────────────────────────────────────────
88
+ export async function createNew(flags) {
89
+ const config = requireConfig();
90
+ let name = flags.name || '';
91
+ if (!name)
92
+ name = await ask('Enter a name for the new key: ');
93
+ if (!name.trim())
94
+ error('Key name cannot be empty. Use --name <name>');
95
+ const opts = {};
96
+ if (typeof flags.grant === 'string')
97
+ opts.grants = parseGrants(flags.grant);
98
+ if (typeof flags.org === 'string')
99
+ opts.orgId = flags.org;
100
+ if (typeof flags['spend-cap'] === 'string')
101
+ opts.spendCapCents = dollarsToCents(flags['spend-cap'], '--spend-cap');
102
+ const key = await hq.createApiKey(config.api_key, name.trim(), opts);
103
+ if (flags.json) {
104
+ printJson(key);
105
+ return;
106
+ }
107
+ success('New API key created!');
108
+ info(`Name: ${key.name}`);
109
+ info(`Key: ${key.api_key}`);
110
+ info(`Scope: ${describeKey(key)}`);
111
+ info('');
112
+ info("Copy the key now — you won't be able to see it again.");
113
+ }
114
+ export async function list(flags) {
115
+ const config = requireConfig();
116
+ const keysList = await hq.listApiKeys(config.api_key);
117
+ if (flags.json) {
118
+ printJson(keysList);
119
+ return;
120
+ }
121
+ const rows = keysList.map(k => ({
122
+ Name: k.name || 'Unnamed',
123
+ ID: k.id,
124
+ Kind: k.kind,
125
+ Scope: k.org_id ? 'org' : 'account',
126
+ Grants: grantsSummary(k.grants),
127
+ // "spent / cap" for the current period; "—" when uncapped.
128
+ Cap: k.spend_cap_cents != null
129
+ ? `$${((k.current_period_spend_cents ?? 0) / 100).toFixed(2)} / $${(k.spend_cap_cents / 100).toFixed(2)} (${k.spend_cap_period})`
130
+ : '—',
131
+ }));
132
+ printTable(rows, {
133
+ flags,
134
+ empty: 'No API keys yet. Create one with: myapi keys create',
135
+ });
136
+ }
137
+ export async function revoke(id, _flags) {
138
+ requireArg(id, 'id', 'myapi keys revoke <id>');
139
+ const config = requireConfig();
140
+ await hq.revokeApiKey(config.api_key, id);
141
+ success(`Key ${id} revoked successfully.`);
142
+ }
143
+ export async function revokeAll(flags) {
144
+ const config = requireConfig();
145
+ const kind = flags.kind;
146
+ if (kind && !KEY_KINDS.includes(kind)) {
147
+ error(`Invalid --kind "${kind}". Use one of: ${KEY_KINDS.join(', ')}.`);
148
+ }
149
+ const scope = kind
150
+ ? `all "${kind}" keys`
151
+ : 'EVERY active key in the account — including the key this CLI is using';
152
+ if (!flags.yes && !flags.y) {
153
+ if (isNonInteractive()) {
154
+ error(`revoke-all is destructive and needs confirmation. Re-run with --yes:\n myapi keys revoke-all${kind ? ` --kind ${kind}` : ''} --yes`);
155
+ }
156
+ const ok = await confirm(`Revoke ${scope}? This cannot be undone. (y/N) `, false);
157
+ if (!ok) {
158
+ info('Aborted.');
159
+ return;
160
+ }
161
+ }
162
+ const res = await hq.revokeAllKeys(config.api_key, kind);
163
+ success(`Revoked ${res.revoked} key(s).`);
164
+ if (!kind) {
165
+ info('Your current key was revoked too — re-authenticate with: myapi auth setup');
166
+ }
167
+ }
168
+ // ── Dispatcher ───────────────────────────────────────────────────────────────
169
+ function header(prefix) {
170
+ return `Usage: myapi ${prefix} <subcommand>
16
171
 
17
- Manage programmatic API keys for your account.
172
+ Manage programmatic API keys. A key carries its authority inline — org scope,
173
+ slot grants, and an optional spend cap. A minted key is always a subset of the
174
+ key that minted it (no privilege escalation).
18
175
 
19
176
  Subcommands:
20
- list List all API keys with IDs and creation dates
21
- create Create a new API key (the key value is shown once)
22
- revoke <id> Permanently revoke an API key by ID
177
+ list List keys with kind, scope, grants, and spend cap
178
+ create Mint a new key (value shown once)
179
+ revoke <id> Revoke one key by ID
180
+ revoke-all Kill switch — revoke every key (or --kind function|manual|account)
23
181
 
24
- Alias for: myapi auth api-keys`;
25
- const API_KEYS_HEADER = `Usage: myapi auth api-keys <subcommand>
182
+ create flags:
183
+ --name <name> Key name (prompted if omitted)
184
+ --grant <list> Slot grants, e.g. --grant email:write,crm:read
185
+ (bare slot = write; "*" = all slots). Omit for unrestricted.
186
+ --org <org_id> Lock the key to one org. Omit for account-wide.
187
+ --spend-cap <usd> Per-key spend ceiling in dollars, e.g. --spend-cap 50
26
188
 
27
- Manage programmatic API keys for your account.
28
-
29
- Subcommands:
30
- list List all API keys with IDs and creation dates
31
- create Create a new API key (the key value is shown once)
32
- revoke <id> Permanently revoke an API key by ID
33
-
34
- Alias: myapi keys <subcommand>`;
189
+ ${prefix === 'keys' ? 'Alias for: myapi auth api-keys' : 'Alias: myapi keys <subcommand>'}`;
190
+ }
35
191
  function subcommandUsage(prefix) {
36
192
  return {
37
193
  'list': `myapi ${prefix} list [--json]`,
38
- 'create': `myapi ${prefix} create [--name <name>]\n\nCreates a new API key. If --name is omitted you will be prompted.`,
39
- 'revoke': `myapi ${prefix} revoke <id>\n\nPermanently revokes an API key by ID. The key stops working immediately and the action cannot be undone.`,
194
+ 'create': `myapi ${prefix} create [--name <name>] [--grant <list>] [--org <org_id>] [--spend-cap <usd>]
195
+
196
+ Mints a key whose authority is a subset of the calling key's. Examples:
197
+ myapi ${prefix} create --name ci --grant funnel:write,storage:read
198
+ myapi ${prefix} create --name billing-fn --org <org_id> --grant email --spend-cap 25
199
+ myapi ${prefix} create --name readonly --grant '*:read'
200
+
201
+ Omitting --grant mints an unrestricted key. --grant slots: ${[...hq.GRANTABLE_SLOTS].join(', ')}.`,
202
+ 'revoke': `myapi ${prefix} revoke <id>\n\nRevokes one key by ID. Takes effect immediately; cannot be undone.`,
203
+ 'revoke-all': `myapi ${prefix} revoke-all [--kind function|manual|account] [--yes]
204
+
205
+ Kill switch. With no --kind, revokes EVERY active key in the account — including
206
+ the key this CLI is authenticated with (recovery: myapi auth setup). --kind
207
+ narrows it to one provenance class. Destructive — confirms unless --yes.`,
40
208
  };
41
209
  }
42
210
  async function dispatch(prefix, subcommand, args, flags) {
43
211
  if (!subcommand || (flags.help && !subcommand)) {
44
- info(prefix === 'keys' ? KEYS_HEADER : API_KEYS_HEADER);
212
+ info(header(prefix));
45
213
  return;
46
214
  }
47
215
  if (flags.help) {
@@ -56,6 +224,7 @@ async function dispatch(prefix, subcommand, args, flags) {
56
224
  case 'create': return createNew(flags);
57
225
  case 'list': return list(flags);
58
226
  case 'revoke': return revoke(args[0], flags);
227
+ case 'revoke-all': return revokeAll(flags);
59
228
  default: error(`Unknown subcommand: ${subcommand}. Run "myapi ${prefix} --help" for available subcommands.`);
60
229
  }
61
230
  }
@@ -65,38 +234,3 @@ export async function run(subcommand, args, flags) {
65
234
  export async function runApiKeys(subcommand, args, flags) {
66
235
  return dispatch('auth api-keys', subcommand, args, flags);
67
236
  }
68
- export async function createNew(flags) {
69
- const config = requireConfig();
70
- let name = flags.name || '';
71
- if (!name)
72
- name = await ask('Enter a name for the new key: ');
73
- if (!name.trim())
74
- error('Key name cannot be empty. Use --name <name>');
75
- const keyInfo = await hq.createApiKey(config.api_key, name.trim());
76
- success(`New API key created!\n\nName: ${keyInfo.prefix}...\nKey: ${keyInfo.api_key}\n\nMake sure to copy your new API key now. You won't be able to see it again!`);
77
- }
78
- export async function list(flags) {
79
- const config = requireConfig();
80
- const keysList = await hq.listApiKeys(config.api_key);
81
- if (flags.json) {
82
- printJson(keysList);
83
- return;
84
- }
85
- const formattedKeys = keysList.map(k => ({
86
- Name: k.name || 'Unnamed',
87
- Prefix: k.prefix,
88
- ID: k.id,
89
- 'Created At': formatDate(k.created_at),
90
- 'Last Used': k.last_used_at ? formatDate(k.last_used_at) : 'Never',
91
- }));
92
- printTable(formattedKeys, {
93
- flags,
94
- empty: 'No API keys yet. Create one with: myapi keys create',
95
- });
96
- }
97
- export async function revoke(id, _flags) {
98
- requireArg(id, 'id', 'myapi keys revoke <id>');
99
- const config = requireConfig();
100
- await hq.revokeApiKey(config.api_key, id);
101
- success(`Key ${id} revoked successfully.`);
102
- }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ // Unit tests for payments.ts CLI's pure dollar-amount validator. A charge
2
+ // must be strictly positive — $0 and negatives are rejected (unlike a spend
3
+ // cap, where $0 is a meaningful "block everything" value).
4
+ import { describe, it, expect } from 'vitest';
5
+ import { _amountToCents } from './payments.js';
6
+ describe('_amountToCents — pure helper', () => {
7
+ describe('valid amounts', () => {
8
+ it.each([
9
+ ['19', 1900],
10
+ ['9.99', 999],
11
+ ['0.01', 1],
12
+ [' 25 ', 2500], // surrounding whitespace tolerated
13
+ ['100', 10000],
14
+ ['0.999', 100], // rounds to nearest cent
15
+ ])('converts %s → %d cents', (raw, cents) => {
16
+ expect(_amountToCents(raw)).toBe(cents);
17
+ });
18
+ });
19
+ describe('rejections', () => {
20
+ it.each([
21
+ ['empty string', ''],
22
+ ['whitespace only', ' '],
23
+ ['zero', '0'],
24
+ ['negative', '-5'],
25
+ ['non-numeric', 'abc'],
26
+ ['NaN-ish', 'one dollar'],
27
+ ])('rejects %s', (_label, raw) => {
28
+ expect(_amountToCents(raw)).toMatch(/not a valid amount/);
29
+ });
30
+ });
31
+ });
@@ -0,0 +1,13 @@
1
+ import type { FlagSchema } from '../flags.js';
2
+ import { type Flags } from '../helpers.js';
3
+ import type { Exposes } from '../exposes.js';
4
+ export declare const EXPOSES: Exposes;
5
+ export declare const SCHEMA: FlagSchema;
6
+ export declare function _amountToCents(raw: string): number | string;
7
+ export declare function connect(flags: Flags): Promise<void>;
8
+ export declare function status(flags: Flags): Promise<void>;
9
+ export declare function charge(flags: Flags): Promise<void>;
10
+ export declare function list(flags: Flags): Promise<void>;
11
+ export declare function get(id: string, flags: Flags): Promise<void>;
12
+ export declare function refund(id: string, flags: Flags): Promise<void>;
13
+ export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
@@ -0,0 +1,219 @@
1
+ import { payments as sdkPayments } from '@myapihq/sdk';
2
+ import { requireConfig } from '../config.js';
3
+ import { success, error, printTable, info, printJson } from '../output.js';
4
+ import { formatDate } from '../utils.js';
5
+ import { requireOrg } from '../helpers.js';
6
+ export const EXPOSES = [
7
+ 'POST /payments/orgs/{org_id}/connect',
8
+ 'GET /payments/orgs/{org_id}/connect',
9
+ 'POST /payments/orgs/{org_id}/charges',
10
+ 'GET /payments/orgs/{org_id}/charges',
11
+ 'GET /payments/orgs/{org_id}/charges/{id}',
12
+ 'POST /payments/orgs/{org_id}/charges/{id}/refund',
13
+ 'POST /payments/webhook/{org_id}',
14
+ ];
15
+ export const SCHEMA = {
16
+ 'stripe-key': 'string',
17
+ amount: 'string',
18
+ description: 'string',
19
+ email: 'string',
20
+ every: 'string',
21
+ 'success-url': 'string',
22
+ 'cancel-url': 'string',
23
+ };
24
+ // Dollars → positive cents. The CLI surface is dollars; the API is cents.
25
+ // Pure form returns cents or an error-message string. Charges must be > 0,
26
+ // so $0 is rejected (unlike a spend cap, where $0 is meaningful).
27
+ export function _amountToCents(raw) {
28
+ const trimmed = raw.trim();
29
+ const n = Number(trimmed);
30
+ if (trimmed === '' || !Number.isFinite(n) || n <= 0) {
31
+ return `"${raw}" is not a valid amount — use a positive number, e.g. 19 or 9.99.`;
32
+ }
33
+ return Math.round(n * 100);
34
+ }
35
+ function dollars(cents) {
36
+ return `$${(cents / 100).toFixed(2)}`;
37
+ }
38
+ function summarizeCharge(c) {
39
+ return {
40
+ id: c.id,
41
+ amount: `${dollars(c.amount_cents)} ${c.currency.toUpperCase()}`,
42
+ every: c.every || 'one-off',
43
+ status: c.status,
44
+ email: c.customer_email || '',
45
+ created_at: c.created_at ? formatDate(c.created_at) : '',
46
+ };
47
+ }
48
+ // ── Subcommands ──────────────────────────────────────────────────────────────
49
+ export async function connect(flags) {
50
+ const config = requireConfig();
51
+ const orgId = requireOrg(flags, config, 'myapi payments connect --stripe-key <sk_...> [--org <id>]');
52
+ const key = flags['stripe-key'];
53
+ if (!key) {
54
+ error('Missing --stripe-key.\nUsage: myapi payments connect --stripe-key <sk_...>\n\n→ Your own Stripe secret key (starts with "sk_"). T0 is bring-your-own-Stripe.');
55
+ }
56
+ if (!key.startsWith('sk_'))
57
+ error('--stripe-key must be a Stripe secret key (starts with "sk_").');
58
+ const res = await sdkPayments.connect(config.api_key, orgId, key);
59
+ success('Stripe connected.');
60
+ info(`Tier: ${res.tier}`);
61
+ info(`Stripe account: ${res.stripe_account_id}`);
62
+ info(`Onboarding: ${res.onboarding_status}`);
63
+ }
64
+ export async function status(flags) {
65
+ const config = requireConfig();
66
+ const orgId = requireOrg(flags, config, 'myapi payments status [--org <id>]');
67
+ const res = await sdkPayments.getConnect(config.api_key, orgId);
68
+ if (flags.json) {
69
+ printJson(res);
70
+ return;
71
+ }
72
+ info(`Tier: ${res.tier}`);
73
+ info(`Stripe account: ${res.stripe_account_id}`);
74
+ info(`Onboarding: ${res.onboarding_status}`);
75
+ if (res.application_fee_bps != null) {
76
+ info(`Platform fee: ${res.application_fee_bps} bps`);
77
+ }
78
+ }
79
+ export async function charge(flags) {
80
+ const config = requireConfig();
81
+ const orgId = requireOrg(flags, config, 'myapi payments charge --amount <usd> [--description <text>] [--email <addr>] [--every month|year]');
82
+ const amountRaw = flags.amount;
83
+ if (!amountRaw) {
84
+ error('Missing --amount.\nUsage: myapi payments charge --amount <usd> [--description <text>] [--email <addr>] [--every month|year]\n\n→ --amount is in dollars (e.g. 19 or 9.99).');
85
+ }
86
+ const cents = _amountToCents(amountRaw);
87
+ if (typeof cents === 'string')
88
+ error(`--amount: ${cents}`);
89
+ const every = flags.every;
90
+ if (every && every !== 'month' && every !== 'year') {
91
+ error(`Invalid --every "${every}". Use "month" or "year" for a subscription, or omit for a one-off payment.`);
92
+ }
93
+ const payload = { amount_cents: cents };
94
+ if (flags.description)
95
+ payload.description = flags.description;
96
+ if (flags.email)
97
+ payload.email = flags.email;
98
+ if (every)
99
+ payload.every = every;
100
+ if (flags['success-url'])
101
+ payload.success_url = flags['success-url'];
102
+ if (flags['cancel-url'])
103
+ payload.cancel_url = flags['cancel-url'];
104
+ const res = await sdkPayments.createCharge(config.api_key, orgId, payload);
105
+ if (flags.json) {
106
+ printJson(res);
107
+ return;
108
+ }
109
+ success(`Charge created: ${res.payment_id}`);
110
+ info(`Status: ${res.status}`);
111
+ info('');
112
+ info('Send the customer to this hosted Stripe Checkout URL:');
113
+ info(` ${res.checkout_url}`);
114
+ }
115
+ export async function list(flags) {
116
+ const config = requireConfig();
117
+ const orgId = requireOrg(flags, config, 'myapi payments list [--org <id>]');
118
+ const charges = await sdkPayments.listCharges(config.api_key, orgId);
119
+ if (flags.json) {
120
+ printJson(charges);
121
+ return;
122
+ }
123
+ printTable(charges.map(summarizeCharge), {
124
+ flags,
125
+ empty: 'No charges yet. Create one with: myapi payments charge --amount <usd>',
126
+ });
127
+ }
128
+ export async function get(id, flags) {
129
+ const config = requireConfig();
130
+ const orgId = requireOrg(flags, config, 'myapi payments get <charge_id> [--org <id>]');
131
+ if (!id)
132
+ error('Missing charge id.\nUsage: myapi payments get <charge_id>');
133
+ const c = await sdkPayments.getCharge(config.api_key, orgId, id);
134
+ if (flags.json) {
135
+ printJson(c);
136
+ return;
137
+ }
138
+ info(`ID: ${c.id}`);
139
+ info(`Amount: ${dollars(c.amount_cents)} ${c.currency.toUpperCase()}`);
140
+ info(`Billing: ${c.every ? `every ${c.every}` : 'one-off'}`);
141
+ if (c.description)
142
+ info(`Description: ${c.description}`);
143
+ if (c.customer_email)
144
+ info(`Customer: ${c.customer_email}`);
145
+ info(`Status: ${c.status}`);
146
+ if (c.created_at)
147
+ info(`Created: ${formatDate(c.created_at)}`);
148
+ if (c.succeeded_at)
149
+ info(`Succeeded: ${formatDate(c.succeeded_at)}`);
150
+ if (c.refunded_at)
151
+ info(`Refunded: ${formatDate(c.refunded_at)}`);
152
+ }
153
+ export async function refund(id, flags) {
154
+ const config = requireConfig();
155
+ const orgId = requireOrg(flags, config, 'myapi payments refund <charge_id> [--org <id>]');
156
+ if (!id)
157
+ error('Missing charge id.\nUsage: myapi payments refund <charge_id>');
158
+ const res = await sdkPayments.refundCharge(config.api_key, orgId, id);
159
+ success(`Charge ${res.id} refunded (status: ${res.status}).`);
160
+ }
161
+ // ── Dispatcher ───────────────────────────────────────────────────────────────
162
+ const SUBCOMMAND_USAGE = {
163
+ 'connect': `myapi payments connect --stripe-key <sk_...> [--org <id>]
164
+
165
+ Links your own Stripe account (T0 — bring-your-own-Stripe). The key is
166
+ validated live against Stripe, stored encrypted, and never echoed. Connect
167
+ Express (T1) is not yet available.`,
168
+ 'status': 'myapi payments status [--org <id>] [--json]',
169
+ 'charge': `myapi payments charge --amount <usd> [--description <text>] [--email <addr>] [--every month|year] [--success-url <url>] [--cancel-url <url>] [--org <id>]
170
+
171
+ Opens a Stripe Checkout Session on your connected account and returns a
172
+ hosted checkout URL to send the customer to. --amount is in dollars.
173
+ --every makes it a recurring subscription; omit it for a one-off payment.
174
+
175
+ Examples:
176
+ myapi payments charge --amount 19 --description "Pro plan"
177
+ myapi payments charge --amount 9.99 --every month --email user@example.com`,
178
+ 'list': 'myapi payments list [--org <id>] [--json]',
179
+ 'get': 'myapi payments get <charge_id> [--org <id>] [--json]',
180
+ 'refund': `myapi payments refund <charge_id> [--org <id>]
181
+
182
+ Issues a full refund. Partial refunds are not supported.`,
183
+ };
184
+ export async function run(subcommand, args, flags) {
185
+ if (!subcommand || (flags.help && !subcommand)) {
186
+ info(`Usage: myapi payments <subcommand>
187
+
188
+ Take payments with Stripe Checkout. Connect your Stripe account, then
189
+ create one-off or recurring charges and refund them.
190
+
191
+ Subcommands:
192
+ connect Link your Stripe account (T0 — bring your own key)
193
+ status Show the org's Stripe connection status
194
+ charge Create a charge and get a hosted checkout URL
195
+ list List charges in your org
196
+ get <charge_id> Inspect a charge
197
+ refund <charge_id> Full-refund a charge
198
+
199
+ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
200
+ return;
201
+ }
202
+ if (flags.help) {
203
+ const usage = SUBCOMMAND_USAGE[subcommand];
204
+ if (usage)
205
+ info(`Usage: ${usage}`);
206
+ else
207
+ info(`Unknown subcommand: ${subcommand}. Run "myapi payments --help" for the list.`);
208
+ return;
209
+ }
210
+ switch (subcommand) {
211
+ case 'connect': return connect(flags);
212
+ case 'status': return status(flags);
213
+ case 'charge': return charge(flags);
214
+ case 'list': return list(flags);
215
+ case 'get': return get(args[0], flags);
216
+ case 'refund': return refund(args[0], flags);
217
+ default: error(`Unknown subcommand: ${subcommand}. Run "myapi payments --help" for available subcommands.`);
218
+ }
219
+ }
@@ -75,7 +75,13 @@ export async function visits(flags) {
75
75
  printJson(res);
76
76
  return;
77
77
  }
78
- printTable(res.visits);
78
+ // `type` is constant ('visit') across this list — omitted from the table.
79
+ printTable(res.visits.map(v => ({
80
+ pixel_id: v.pixel_id,
81
+ from_url: v.from_url,
82
+ to_url: v.to_url,
83
+ ts: v.ts,
84
+ })));
79
85
  info(`Total: ${res.total} | Showing: ${res.limit} | Offset: ${res.offset}`);
80
86
  }
81
87
  // Engagement events (open / click / page_visit / sent) — filterable by
@@ -106,7 +112,14 @@ export async function events(flags) {
106
112
  printJson(res);
107
113
  return;
108
114
  }
109
- printTable(res.events);
115
+ // `type` is constant ('event') across this list — omitted from the table.
116
+ printTable(res.events.map(e => ({
117
+ pixel_id: e.pixel_id,
118
+ event_type: e.event_type,
119
+ url: e.url ?? '',
120
+ campaign_id: e.campaign_id ?? '',
121
+ ts: e.ts,
122
+ })));
110
123
  info(`Total: ${res.total} | Showing: ${res.limit} | Offset: ${res.offset}`);
111
124
  }
112
125
  // Geographic distribution sample of the org's pixel audience.
@@ -1,7 +1,7 @@
1
1
  import type { Flags } from '../helpers.js';
2
2
  import type { Exposes } from '../exposes.js';
3
3
  export declare const EXPOSES: Exposes;
4
+ export declare function cachedLatestVersion(): string | null;
4
5
  export declare function checkForUpdate(currentVersion: string): Promise<void>;
5
6
  export declare function update(flags?: Flags): Promise<void>;
6
- export declare function latestVersion(): Promise<string | null>;
7
7
  export declare function isNewer(latest: string, current: string): boolean;